From dd210dc408730df61040053f02ed69dacd1a5030 Mon Sep 17 00:00:00 2001 From: David Sancho Date: Wed, 10 Jul 2024 19:04:03 +0200 Subject: [PATCH] Support define CSS variables and use CSS variables (#492) --- e2e/melange/src/ui/ui.re | 2 + .../css-property-parser/lib/Combinator.re | 8 +- .../css-property-parser/lib/Combinator.rei | 8 +- packages/css-property-parser/lib/Modifier.rei | 7 + packages/css-property-parser/lib/Parser.re | 57 ++++++- .../css-property-parser/lib/Parser_helper.re | 75 ---------- packages/css-property-parser/lib/Standard.re | 38 ++++- packages/css-property-parser/lib/Standard.rei | 129 ++++++++++++++++ packages/css-property-parser/lib/dune | 5 +- packages/css-property-parser/ppx/Generate.re | 8 +- .../test/Combinators_test.re | 1 - .../test/Modifiers_test.re | 1 - .../css-property-parser/test/Standard_test.re | 140 ++++++++++-------- .../test/snapshots/Spec.expected.re | 11 +- .../test/snapshots/Spec.re | 3 +- packages/parser/lib/Lexer.re | 24 ++- packages/parser/lib/dune | 7 +- packages/ppx/src/Css_to_runtime.re | 2 +- packages/ppx/src/Property_to_runtime.re | 89 +++++++---- packages/ppx/src/Property_to_runtime.rei | 2 +- packages/ppx/src/Property_to_string.re | 6 +- packages/ppx/src/Property_to_string.rei | 2 +- .../ppx/test/css-support/random.t/input.re | 2 + packages/ppx/test/css-support/random.t/run.t | 1 + packages/ppx/test/native/Static_test.re | 71 +++++---- .../reason/reason-classname-cx.t/run.t | 8 +- .../run.t | 5 +- .../reason-static-open-property.t/run.t | 5 +- .../reason/reason-styled-global.t/input.re | 10 +- .../reason/reason-styled-global.t/run.t | 4 + packages/renderer/ast_renderer.re | 19 +-- packages/renderer/lexer_renderer.re | 17 +-- packages/runtime/native/shared/Css_types.ml | 94 +++++++----- packages/runtime/native/shared/Properties.ml | 6 + 34 files changed, 536 insertions(+), 331 deletions(-) delete mode 100644 packages/css-property-parser/lib/Parser_helper.re create mode 100644 packages/css-property-parser/lib/Standard.rei diff --git a/e2e/melange/src/ui/ui.re b/e2e/melange/src/ui/ui.re index 31d129214..1b10c1f1d 100644 --- a/e2e/melange/src/ui/ui.re +++ b/e2e/melange/src/ui/ui.re @@ -1,3 +1,5 @@ +let c = CSS.color(`var({js|--color-link|js})); + [%styled.global {| div { diff --git a/packages/css-property-parser/lib/Combinator.re b/packages/css-property-parser/lib/Combinator.re index d6d24fc81..7a25a1ce5 100644 --- a/packages/css-property-parser/lib/Combinator.re +++ b/packages/css-property-parser/lib/Combinator.re @@ -19,7 +19,7 @@ let rec match_longest = ((left_key, left_rule), rules) => }; }; -let combine_static = rules => { +let static = rules => { let rec match_everything = (values, rules) => switch (rules) { | [] => return_match(values |> List.rev) @@ -30,7 +30,7 @@ let combine_static = rules => { match_everything([], rules); }; -let combine_xor = +let xor = fun | [] => failwith("xor doesn't makes sense without a single value") | [left, ...rules] => { @@ -40,7 +40,7 @@ let combine_xor = value; }; -let combine_and = rules => { +let and_ = rules => { // TODO: an array is a better choice let rec match_everything = (values, rules) => switch (rules) { @@ -59,4 +59,4 @@ let combine_and = rules => { }; // [ A || B ] = [ A? && B? ]! -let combine_or = rules => rules |> List.map(optional) |> combine_and; +let or_ = rules => rules |> List.map(optional) |> and_; diff --git a/packages/css-property-parser/lib/Combinator.rei b/packages/css-property-parser/lib/Combinator.rei index 051b6ee48..b490517ed 100644 --- a/packages/css-property-parser/lib/Combinator.rei +++ b/packages/css-property-parser/lib/Combinator.rei @@ -1,10 +1,10 @@ type combinator('a, 'b) = list(Rule.rule('a)) => Rule.rule('b); // TODO: docs for infix operators -let combine_static: combinator('a, list('a)); +let static: combinator('a, list('a)); -let combine_xor: combinator('a, 'a); +let xor: combinator('a, 'a); -let combine_and: combinator('a, list('a)); +let and_: combinator('a, list('a)); -let combine_or: combinator('a, list(option('a))); +let or_: combinator('a, list(option('a))); diff --git a/packages/css-property-parser/lib/Modifier.rei b/packages/css-property-parser/lib/Modifier.rei index 307ab0f0f..c14af53fc 100644 --- a/packages/css-property-parser/lib/Modifier.rei +++ b/packages/css-property-parser/lib/Modifier.rei @@ -3,11 +3,18 @@ type modifier('a, 'b) = Rule.rule('a) => Rule.rule('b); type range = (int, option(int)); let one: modifier('a, 'a); + let optional: modifier('a, option('a)); + let zero_or_more: modifier('a, list('a)); + let one_or_more: modifier('a, list('a)); + let repeat: range => modifier('a, list('a)); + let repeat_by_comma: range => modifier('a, list('a)); + let at_least_one: modifier(list(option('a)), list(option('a))); + let at_least_one_2: modifier((option('a), option('b)), (option('a), option('b))); diff --git a/packages/css-property-parser/lib/Parser.re b/packages/css-property-parser/lib/Parser.re index cd86edf66..633f8f643 100644 --- a/packages/css-property-parser/lib/Parser.re +++ b/packages/css-property-parser/lib/Parser.re @@ -1,10 +1,12 @@ open Standard; -open Combinator; open Modifier; open Rule.Match; -open Parser_helper; open Styled_ppx_css_parser; +module StringMap = Map.Make(String); + +let (let.ok) = Result.bind; + let rec _legacy_gradient = [%value.rec "<-webkit-gradient()> | <-legacy-linear-gradient> | <-legacy-repeating-linear-gradient> | <-legacy-radial-gradient> | <-legacy-repeating-radial-gradient>" ] @@ -1956,6 +1958,51 @@ and wq_name = [%value.rec "[ ]? "] and x = [%value.rec ""] and y = [%value.rec ""]; +let apply_parser = (parser, tokens_with_loc) => { + open Styled_ppx_css_parser.Lexer; + + let tokens = + tokens_with_loc + |> List.map(({txt, _}) => + switch (txt) { + | Ok(token) => token + | Error((token, _)) => token + } + ) + |> List.rev; + + let tokens_without_ws = tokens |> List.filter((!=)(Tokens.WS)); + + let (output, remaining_tokens) = parser(tokens_without_ws); + let.ok output = + switch (output) { + | Ok(data) => Ok(data) + | Error([message, ..._]) => Error(message) + | Error([]) => Error("weird") + }; + let.ok () = + switch (remaining_tokens) { + | [] + | [Tokens.EOF] => Ok() + | tokens => + let tokens = + tokens |> List.map(Tokens.show_token) |> String.concat(", "); + Error("tokens remaining: " ++ tokens); + }; + Ok(output); +}; + +let parse = (rule_parser: Rule.rule('a), str) => { + let.ok tokens_with_loc = + Styled_ppx_css_parser.Lexer.from_string(str) + |> Result.map_error(_ => "frozen"); + + apply_parser(rule_parser, tokens_with_loc); +}; + +let check = (prop: Rule.rule('a), value) => + parse(prop, value) |> Result.is_ok; + let check_map = StringMap.of_seq( List.to_seq([ @@ -3397,12 +3444,6 @@ let check_map = ]), ); -let parse = Parser_helper.parse; - -module StringMap = Map.Make(String); - -let (let.ok) = Result.bind; - let check_value = (~name, value) => { let.ok check = check_map diff --git a/packages/css-property-parser/lib/Parser_helper.re b/packages/css-property-parser/lib/Parser_helper.re deleted file mode 100644 index dfb149a19..000000000 --- a/packages/css-property-parser/lib/Parser_helper.re +++ /dev/null @@ -1,75 +0,0 @@ -open Styled_ppx_css_parser.Tokens; - -module StringMap = Map.Make(String); - -let (let.ok) = Result.bind; - -let apply_parser = (parser, tokens_with_loc) => { - open Styled_ppx_css_parser.Lexer; - - let tokens = - tokens_with_loc - |> List.map(({txt, _}) => - switch (txt) { - | Ok(token) => token - | Error((token, _)) => token - } - ) - |> List.rev; - - let tokens_without_ws = tokens |> List.filter((!=)(WS)); - - let (output, remaining_tokens) = parser(tokens_without_ws); - let.ok output = - switch (output) { - | Ok(data) => Ok(data) - | Error([message, ..._]) => Error(message) - | Error([]) => Error("weird") - }; - let.ok () = - switch (remaining_tokens) { - | [] - | [EOF] => Ok() - | tokens => - let tokens = tokens |> List.map(show_token) |> String.concat(", "); - Error("tokens remaining: " ++ tokens); - }; - Ok(output); -}; - -let parse = (rule_parser: Rule.rule('a), str) => { - let.ok tokens_with_loc = - Styled_ppx_css_parser.Lexer.from_string(str) - |> Result.map_error(_ => "frozen"); - - apply_parser(rule_parser, tokens_with_loc); -}; - -let check = (prop: Rule.rule('a), value) => - parse(prop, value) |> Result.is_ok; - -// TODO: workarounds -let invalid = Rule.Pattern.expect(STRING("not-implemented")); -let attr_name = invalid; -let attr_fallback = invalid; -let string_token = invalid; -let ident_token = invalid; -let dimension = invalid; -let declaration_value = invalid; -let positive_integer = Standard.integer; -let function_token = invalid; -let any_value = invalid; -let hash_token = invalid; -let zero = invalid; -let custom_property_name = invalid; -let declaration_list = invalid; -let name_repeat = invalid; -let ratio = invalid; -let an_plus_b = invalid; -let declaration = invalid; -let y = invalid; -let x = invalid; -let decibel = invalid; -let urange = invalid; -let semitones = invalid; -let url_token = invalid; diff --git a/packages/css-property-parser/lib/Standard.re b/packages/css-property-parser/lib/Standard.re index 40c0e70a4..029c9992b 100644 --- a/packages/css-property-parser/lib/Standard.re +++ b/packages/css-property-parser/lib/Standard.re @@ -1,5 +1,4 @@ open Styled_ppx_css_parser.Tokens; -open Combinator; open Rule.Let; open Rule.Pattern; @@ -12,9 +11,7 @@ let function_call = (name, rule) => { fun | FUNCTION(called_name) when name == called_name => Ok() | token => - Error([ - "expected a function " ++ name ++ ". got an " ++ show_token(token), - ]), + Error(["expected a " ++ name ++ ". got an " ++ show_token(token)]), ); let.bind_match value = rule; let.bind_match () = expect(RIGHT_PAREN); @@ -160,7 +157,7 @@ let ident = // https://drafts.csswg.org/css-values-4/#textual-values let css_wide_keywords = - combine_xor([ + Combinator.xor([ value(`Initial, keyword("initial")), value(`Inherit, keyword("inherit")), value(`Unset, keyword("unset")), @@ -204,9 +201,12 @@ let url = { | _ => Error(["expected a url"]), ); let url_fun = function_call("url", string); - combine_xor([url_token, url_fun]); + Combinator.xor([url_token, url_fun]); }; +// https://drafts.csswg.org/css-variables-2/#funcdef-var +/* let var = function_call("var", dashed_ident); */ + // css-color-4 // https://drafts.csswg.org/css-color-4/#hex-notation let hex_color = @@ -267,3 +267,29 @@ let flex_value = } | _ => Error(["expected flex_value"]), ); + +// TODO: workarounds +let invalid = expect(STRING("not-implemented")); +let attr_name = invalid; +let attr_fallback = invalid; +let string_token = invalid; +let ident_token = invalid; +let dimension = invalid; +let declaration_value = invalid; +let positive_integer = integer; +let function_token = invalid; +let any_value = invalid; +let hash_token = invalid; +let zero = invalid; +let custom_property_name = invalid; +let declaration_list = invalid; +let name_repeat = invalid; +let ratio = invalid; +let an_plus_b = invalid; +let declaration = invalid; +let y = invalid; +let x = invalid; +let decibel = invalid; +let urange = invalid; +let semitones = invalid; +let url_token = invalid; diff --git a/packages/css-property-parser/lib/Standard.rei b/packages/css-property-parser/lib/Standard.rei new file mode 100644 index 000000000..e8620a565 --- /dev/null +++ b/packages/css-property-parser/lib/Standard.rei @@ -0,0 +1,129 @@ +let keyword: string => Rule.rule(unit); + +let comma: Rule.rule(unit); + +let delim: string => Rule.rule(unit); + +let function_call: (string, Rule.rule('a)) => Rule.rule('a); + +let integer: Rule.rule(int); + +let number: Rule.rule(float); + +let length: + Rule.rule( + [> + | `Cap(float) + | `Ch(float) + | `Cm(float) + | `Cqb(float) + | `Cqh(float) + | `Cqi(float) + | `Cqmax(float) + | `Cqmin(float) + | `Cqw(float) + | `Em(float) + | `Ex(float) + | `Ic(float) + | `In(float) + | `Lh(float) + | `Mm(float) + | `Pc(float) + | `Pt(float) + | `Px(float) + | `Q(float) + | `Rem(float) + | `Rlh(float) + | `Vb(float) + | `Vh(float) + | `Vi(float) + | `Vmax(float) + | `Vmin(float) + | `Vw(float) + | `Zero + ], + ); + +let angle: + Rule.rule( + [> | `Deg(float) | `Grad(float) | `Rad(float) | `Turn(float)], + ); + +let time: Rule.rule([> | `Ms(float) | `S(float)]); + +let frequency: Rule.rule([> | `Hz(float) | `KHz(float)]); + +let resolution: Rule.rule([> | `Dpcm(float) | `Dpi(float) | `Dppx(float)]); + +let percentage: Rule.rule(float); + +let ident: Rule.rule(string); + +let css_wide_keywords: + Rule.rule([> | `Inherit | `Initial | `Revert | `RevertLayer | `Unset]); + +let custom_ident: Rule.rule(string); + +let dashed_ident: Rule.rule(string); + +let string: Rule.rule(string); + +let url: Rule.rule(string); + +/* let var: Rule.rule(string); */ + +let hex_color: Rule.rule(string); + +let interpolation: Rule.rule(list(string)); + +let line_names: Rule.rule((unit, list(string), unit)); + +let flex_value: Rule.rule([> | `Fr(float)]); + +let invalid: Rule.rule(unit); + +let attr_name: Rule.rule(unit); + +let attr_fallback: Rule.rule(unit); + +let string_token: Rule.rule(unit); + +let ident_token: Rule.rule(unit); + +let dimension: Rule.rule(unit); + +let declaration_value: Rule.rule(unit); + +let positive_integer: Rule.rule(int); + +let function_token: Rule.rule(unit); + +let any_value: Rule.rule(unit); + +let hash_token: Rule.rule(unit); + +let zero: Rule.rule(unit); + +let custom_property_name: Rule.rule(unit); + +let declaration_list: Rule.rule(unit); + +let name_repeat: Rule.rule(unit); + +let ratio: Rule.rule(unit); + +let an_plus_b: Rule.rule(unit); + +let declaration: Rule.rule(unit); + +let y: Rule.rule(unit); + +let x: Rule.rule(unit); + +let decibel: Rule.rule(unit); + +let urange: Rule.rule(unit); + +let semitones: Rule.rule(unit); + +let url_token: Rule.rule(unit); diff --git a/packages/css-property-parser/lib/dune b/packages/css-property-parser/lib/dune index 2e48f7b28..9860881b0 100644 --- a/packages/css-property-parser/lib/dune +++ b/packages/css-property-parser/lib/dune @@ -1,7 +1,10 @@ (library (name Css_property_parser) (public_name styled-ppx.css-property-parser) - (libraries sedlex styled-ppx.css-parser ppx_deriving.runtime compiler-libs) + (libraries + sedlex + styled-ppx.css-parser ; since we use the Tokens from the CSS parser + ) (preprocess (per_module ((pps reason_css_parser_ppx sedlex.ppx) diff --git a/packages/css-property-parser/ppx/Generate.re b/packages/css-property-parser/ppx/Generate.re index 1d0913f5f..83336a645 100644 --- a/packages/css-property-parser/ppx/Generate.re +++ b/packages/css-property-parser/ppx/Generate.re @@ -564,10 +564,10 @@ module Make = (Builder: Ppxlib.Ast_builder.S) => { }; let op_ident = fun - | Static => evar("combine_static") - | Xor => evar("combine_xor") - | And => evar("combine_and") - | Or => evar("combine_or"); + | Static => evar("Combinator.static") + | Xor => evar("Combinator.xor") + | And => evar("Combinator.and_") + | Or => evar("Combinator.or_"); let map_value = (content, (name, value)) => { let variant = pexp_variant(name, content ? Some(evar("v")) : None); diff --git a/packages/css-property-parser/test/Combinators_test.re b/packages/css-property-parser/test/Combinators_test.re index 33aa05e64..2f7f92404 100644 --- a/packages/css-property-parser/test/Combinators_test.re +++ b/packages/css-property-parser/test/Combinators_test.re @@ -2,7 +2,6 @@ open Alcotest; open Css_property_parser; open Rule.Match; open Modifier; -open Combinator; open Standard; let check = (location, testable, recived, expected) => diff --git a/packages/css-property-parser/test/Modifiers_test.re b/packages/css-property-parser/test/Modifiers_test.re index 5ae7c57a6..1d64a7362 100644 --- a/packages/css-property-parser/test/Modifiers_test.re +++ b/packages/css-property-parser/test/Modifiers_test.re @@ -1,6 +1,5 @@ open Alcotest; open Css_property_parser; -open Combinator; open Standard; open Modifier; open Parser; diff --git a/packages/css-property-parser/test/Standard_test.re b/packages/css-property-parser/test/Standard_test.re index 0f1c9915c..d705ef678 100644 --- a/packages/css-property-parser/test/Standard_test.re +++ b/packages/css-property-parser/test/Standard_test.re @@ -1,30 +1,33 @@ -open Alcotest; open Css_property_parser; -open Combinator; open Standard; open Modifier; open Parser; open Rule.Match; let check = (location, testable, recived, expected) => - check(~pos=location, testable, "", expected, recived); + Alcotest.check(~pos=location, testable, "", expected, recived); -let test = (title, body) => test_case(title, `Quick, body); +let test = (title, body) => Alcotest.test_case(title, `Quick, body); let tests = [ // TODO: case insensitive test("integer", () => { let parse = parse([%value ""]); - check(__POS__, result(int, Alcotest.string), parse("54"), Ok(54)); check( __POS__, - result(int, Alcotest.string), + Alcotest.result(Alcotest.int, Alcotest.string), + parse("54"), + Ok(54), + ); + check( + __POS__, + Alcotest.result(Alcotest.int, Alcotest.string), parse("54.4"), Error("expected an integer, received a float"), ); check( __POS__, - result(int, Alcotest.string), + Alcotest.result(Alcotest.int, Alcotest.string), parse("ident"), Error("expected an integer"), ); @@ -33,19 +36,19 @@ let tests = [ let parse = parse([%value ""]); check( __POS__, - result(float(1.), Alcotest.string), + Alcotest.result(Alcotest.float(1.), Alcotest.string), parse("55"), Ok(55.), ); check( __POS__, - result(float(1.), Alcotest.string), + Alcotest.result(Alcotest.float(1.), Alcotest.string), parse("55.5"), Ok(55.5), ); check( __POS__, - result(float(1.), Alcotest.string), + Alcotest.result(Alcotest.float(1.), Alcotest.string), parse("ident"), Error("expected a number, receveid (IDENT \"ident\")"), ); @@ -85,8 +88,8 @@ let tests = [ }; }; let pp_length = (ppf, x) => Fmt.pf(ppf, "%S", render_length(x)); - let length = testable(pp_length, (==)); - let to_check = result(length, Alcotest.string); + let length = Alcotest.testable(pp_length, (==)); + let to_check = Alcotest.result(length, Alcotest.string); check(__POS__, to_check, parse("56cm"), Ok(`Cm(56.))); check(__POS__, to_check, parse("57px"), Ok(`Px(57.))); check( @@ -109,34 +112,34 @@ let tests = [ }; }; let pp_length = (ppf, x) => Fmt.pf(ppf, "%S", render_angle(x)); - let angle = testable(pp_length, (==)); + let angle = Alcotest.testable(pp_length, (==)); check( __POS__, - result(angle, Alcotest.string), + Alcotest.result(angle, Alcotest.string), parse("1deg"), Ok(`Deg(1.)), ); check( __POS__, - result(angle, Alcotest.string), + Alcotest.result(angle, Alcotest.string), parse("0.2turn"), Ok(`Turn(0.2)), ); check( __POS__, - result(angle, Alcotest.string), + Alcotest.result(angle, Alcotest.string), parse("59px"), Error("unknown dimension"), ); check( __POS__, - result(angle, Alcotest.string), + Alcotest.result(angle, Alcotest.string), parse("0"), Ok(`Deg(0.)), ); check( __POS__, - result(angle, Alcotest.string), + Alcotest.result(angle, Alcotest.string), parse("60"), Error("expected angle"), ); @@ -150,34 +153,34 @@ let tests = [ }; }; let pp_length = (ppf, x) => Fmt.pf(ppf, "%S", render_time(x)); - let time = testable(pp_length, (==)); + let time = Alcotest.testable(pp_length, (==)); check( __POS__, - result(time, Alcotest.string), + Alcotest.result(time, Alcotest.string), parse(".5s"), Ok(`S(0.5)), ); check( __POS__, - result(time, Alcotest.string), + Alcotest.result(time, Alcotest.string), parse("50ms"), Ok(`Ms(50.)), ); check( __POS__, - result(time, Alcotest.string), + Alcotest.result(time, Alcotest.string), parse("59px"), Error("unknown time unit"), ); check( __POS__, - result(time, Alcotest.string), + Alcotest.result(time, Alcotest.string), parse("0"), Error("expected time"), ); check( __POS__, - result(time, Alcotest.string), + Alcotest.result(time, Alcotest.string), parse("60"), Error("expected time"), ); @@ -191,34 +194,34 @@ let tests = [ }; }; let pp_length = (ppf, x) => Fmt.pf(ppf, "%S", render_frequency(x)); - let frequency = testable(pp_length, (==)); + let frequency = Alcotest.testable(pp_length, (==)); check( __POS__, - result(frequency, Alcotest.string), + Alcotest.result(frequency, Alcotest.string), parse("6hz"), Ok(`Hz(6.)), ); check( __POS__, - result(frequency, Alcotest.string), + Alcotest.result(frequency, Alcotest.string), parse(".6kHz"), Ok(`KHz(0.6)), ); check( __POS__, - result(frequency, Alcotest.string), + Alcotest.result(frequency, Alcotest.string), parse("59px"), Error("unknown dimension px"), ); check( __POS__, - result(frequency, Alcotest.string), + Alcotest.result(frequency, Alcotest.string), parse("0"), Error("expected frequency. got(NUMBER 0.)"), ); check( __POS__, - result(frequency, Alcotest.string), + Alcotest.result(frequency, Alcotest.string), parse("60"), Error("expected frequency. got(NUMBER 60.)"), ); @@ -233,34 +236,34 @@ let tests = [ }; }; let pp_length = (ppf, x) => Fmt.pf(ppf, "%S", render_resolution(x)); - let resolution = testable(pp_length, (==)); + let resolution = Alcotest.testable(pp_length, (==)); check( __POS__, - result(resolution, Alcotest.string), + Alcotest.result(resolution, Alcotest.string), parse("6x"), Ok(`Dppx(6.)), ); check( __POS__, - result(resolution, Alcotest.string), + Alcotest.result(resolution, Alcotest.string), parse("3dpi"), Ok(`Dpi(3.)), ); check( __POS__, - result(resolution, Alcotest.string), + Alcotest.result(resolution, Alcotest.string), parse("59px"), Error("unknown dimension"), ); check( __POS__, - result(resolution, Alcotest.string), + Alcotest.result(resolution, Alcotest.string), parse("0"), Error("expected resolution"), ); check( __POS__, - result(resolution, Alcotest.string), + Alcotest.result(resolution, Alcotest.string), parse("60"), Error("expected resolution"), ); @@ -269,36 +272,41 @@ let tests = [ let parse = parse([%value ""]); check( __POS__, - result(float(1.), Alcotest.string), + Alcotest.result(Alcotest.float(1.), Alcotest.string), parse("61%"), Ok(61.), ); check( __POS__, - result(float(1.), Alcotest.string), + Alcotest.result(Alcotest.float(1.), Alcotest.string), parse("62.3%"), Ok(62.3), ); check( __POS__, - result(float(1.), Alcotest.string), + Alcotest.result(Alcotest.float(1.), Alcotest.string), parse("63.4:"), Error("expected percentage"), ); }), test("keyword", () => { let parse = parse([%value "gintoki"]); - check(__POS__, result(unit, Alcotest.string), parse("gintoki"), Ok()); check( __POS__, - result(unit, Alcotest.string), + Alcotest.result(Alcotest.unit, Alcotest.string), + parse("gintoki"), + Ok(), + ); + check( + __POS__, + Alcotest.result(Alcotest.unit, Alcotest.string), parse("nope"), Error("Expected 'ident gintoki' but instead got ident nope"), ); }), test("", () => { let parse = parse([%value ""]); - let to_check = result(Alcotest.string, Alcotest.string); + let to_check = Alcotest.result(Alcotest.string, Alcotest.string); check(__POS__, to_check, parse("test"), Ok("test")); check( __POS__, @@ -320,48 +328,48 @@ let tests = [ }; let pp_css_wide_keywords = (ppf, x) => Fmt.pf(ppf, "%S", render_css_wide_keywords(x)); - let css_wide_keywords = testable(pp_css_wide_keywords, (==)); + let css_wide_keywords = Alcotest.testable(pp_css_wide_keywords, (==)); check( __POS__, - result(css_wide_keywords, Alcotest.string), + Alcotest.result(css_wide_keywords, Alcotest.string), parse("initial"), Ok(`Initial), ); check( __POS__, - result(css_wide_keywords, Alcotest.string), + Alcotest.result(css_wide_keywords, Alcotest.string), parse("inherit"), Ok(`Inherit), ); check( __POS__, - result(css_wide_keywords, Alcotest.string), + Alcotest.result(css_wide_keywords, Alcotest.string), parse("unset"), Ok(`Unset), ); check( __POS__, - result(css_wide_keywords, Alcotest.string), + Alcotest.result(css_wide_keywords, Alcotest.string), parse("revert"), Ok(`Revert), ); check( __POS__, - result(css_wide_keywords, Alcotest.string), + Alcotest.result(css_wide_keywords, Alcotest.string), parse("revert-layer"), Ok(`RevertLayer), ); - /* TODO: combine_xor should combine the error messages */ + /* TODO: xor should combine the error messages */ check( __POS__, - result(css_wide_keywords, Alcotest.string), + Alcotest.result(css_wide_keywords, Alcotest.string), parse("nope"), Error("Expected 'ident revert-layer' but instead got ident nope"), ); }), test("", () => { let parse = parse([%value ""]); - let to_check = result(Alcotest.string, Alcotest.string); + let to_check = Alcotest.result(Alcotest.string, Alcotest.string); check(__POS__, to_check, parse("'tuturu'"), Ok("tuturu")); check(__POS__, to_check, parse("'67.8'"), Ok("67.8")); check(__POS__, to_check, parse("ident"), Error("expected a string")); @@ -369,7 +377,7 @@ let tests = [ }), test("", () => { let parse = parse([%value ""]); - let to_check = result(Alcotest.string, Alcotest.string); + let to_check = Alcotest.result(Alcotest.string, Alcotest.string); check(__POS__, to_check, parse("--random"), Ok("--random")); check( __POS__, @@ -380,7 +388,7 @@ let tests = [ }), test("", () => { let parse = parse([%value ""]); - let to_check = result(Alcotest.string, Alcotest.string); + let to_check = Alcotest.result(Alcotest.string, Alcotest.string); check( __POS__, to_check, @@ -397,7 +405,7 @@ let tests = [ // css-color-4 test("", () => { let parse = parse([%value ""]); - let to_check = result(Alcotest.string, Alcotest.string); + let to_check = Alcotest.result(Alcotest.string, Alcotest.string); check(__POS__, to_check, parse("#abc"), Ok("abc")); check(__POS__, to_check, parse("#abcdefgh"), Ok("abcdefgh")); check( @@ -410,7 +418,14 @@ let tests = [ test("", () => { let parse = parse([%value ""]); let to_check = - result(triple(unit, list(Alcotest.string), unit), Alcotest.string); + Alcotest.result( + Alcotest.triple( + Alcotest.unit, + Alcotest.list(Alcotest.string), + Alcotest.unit, + ), + Alcotest.string, + ); check(__POS__, to_check, parse("[abc]"), Ok(((), ["abc"], ()))); check(__POS__, to_check, parse("[a b]"), Ok(((), ["a", "b"], ()))); check( @@ -423,8 +438,12 @@ let tests = [ test("chars", () => { let parse = parse([%value "? ',' "]); let to_check = - result( - triple(option(Alcotest.string), unit, Alcotest.string), + Alcotest.result( + Alcotest.triple( + Alcotest.option(Alcotest.string), + Alcotest.unit, + Alcotest.string, + ), Alcotest.string, ); check( @@ -443,14 +462,15 @@ let tests = [ }; }; let pp_output = (ppf, x) => Fmt.pf(ppf, "%S", render_output(x)); - let output = testable(pp_output, (==)); - let to_check = result(output, Alcotest.string); + let output = Alcotest.testable(pp_output, (==)); + let to_check = Alcotest.result(output, Alcotest.string); check(__POS__, to_check, parse("all"), Ok(`All)); check(__POS__, to_check, parse("moar"), Ok(`Custom_ident("moar"))); }), test("interpolation", () => { let parse = parse([%value ""]); - let to_check = result(list(Alcotest.string), Alcotest.string); + let to_check = + Alcotest.result(Alcotest.list(Alcotest.string), Alcotest.string); check( __POS__, to_check, diff --git a/packages/css-property-parser/test/snapshots/Spec.expected.re b/packages/css-property-parser/test/snapshots/Spec.expected.re index a12e9086f..79c1f93f3 100644 --- a/packages/css-property-parser/test/snapshots/Spec.expected.re +++ b/packages/css-property-parser/test/snapshots/Spec.expected.re @@ -1,8 +1,7 @@ open Standard; -open Combinator; open Modifier; open Rule.Match; -open Parser_helper; +open Driver; module Types = { type _legacy_gradient = [ | `Function__webkit_gradient(function__webkit_gradient) @@ -99,7 +98,7 @@ let rec _legacy_gradient: list(Tokens.token), ) = tokens => - combine_xor( + Combinator.xor( [ map(function__webkit_gradient, v => `Function__webkit_gradient(v)), map(_legacy_linear_gradient, v => `_legacy_linear_gradient(v)), @@ -120,7 +119,7 @@ and _legacy_linear_gradient: list(Tokens.token), ) = tokens => - combine_xor( + Combinator.xor( [ map( function_call( @@ -156,7 +155,7 @@ and property_height: list(Tokens.token), ) = tokens => - combine_xor( + Combinator.xor( [ map(keyword("auto"), _v => `Auto), map(extended_length, v => `Extended_length(v)), @@ -167,7 +166,7 @@ and property_height: map( function_call( "fit-content", - combine_xor([ + Combinator.xor([ map(extended_length, v => `Extended_length(v)), map(extended_percentage, v => `Extended_percentage(v)), ]), diff --git a/packages/css-property-parser/test/snapshots/Spec.re b/packages/css-property-parser/test/snapshots/Spec.re index c07021eb4..2df08185f 100644 --- a/packages/css-property-parser/test/snapshots/Spec.re +++ b/packages/css-property-parser/test/snapshots/Spec.re @@ -1,8 +1,7 @@ open Standard; -open Combinator; open Modifier; open Rule.Match; -open Parser_helper; +open Driver; let rec _legacy_gradient = [%value.rec "<-webkit-gradient()> | <-legacy-linear-gradient> | <-legacy-repeating-linear-gradient> | <-legacy-radial-gradient> | <-legacy-repeating-radial-gradient>" diff --git a/packages/parser/lib/Lexer.re b/packages/parser/lib/Lexer.re index afcc3b49d..8aab93cee 100644 --- a/packages/parser/lib/Lexer.re +++ b/packages/parser/lib/Lexer.re @@ -654,8 +654,6 @@ let get_next_tokens_with_location = lexbuf => { (token, position_start, position_end); }; -open Tokens; - let check_if_three_codepoints_would_start_an_identifier = check(check_if_three_codepoints_would_start_an_identifier); let check_if_three_code_points_would_start_a_number = @@ -767,13 +765,13 @@ let consume_string = (ending_code_point, lexbuf) => { }; }; -let handle_consume_identifier_ = +let handle_consume_identifier = fun | Error((_, error)) => Error((Tokens.BAD_IDENT, error)) | Ok(string) => Ok(string); // https://drafts.csswg.org/css-syntax-3/#consume-ident-like-token -let consume_ident_like_ = lexbuf => { +let consume_ident_like = lexbuf => { let read_url = string => { // TODO: the whitespace trickery here? let _ = consume_whitespace_(lexbuf); @@ -790,7 +788,7 @@ let consume_ident_like_ = lexbuf => { }; // TODO: should it return IDENT() when error? - let.ok string = consume_identifier(lexbuf) |> handle_consume_identifier_; + let.ok string = consume_identifier(lexbuf) |> handle_consume_identifier; switch%sedlex (lexbuf) { | '(' => @@ -808,7 +806,7 @@ let consume_numeric = lexbuf => { let (number, _kind) = consume_number(lexbuf); if (check_if_three_codepoints_would_start_an_identifier(lexbuf)) { // TODO: should it be BAD_IDENT? - let.ok string = consume_identifier(lexbuf) |> handle_consume_identifier_; + let.ok string = consume_identifier(lexbuf) |> handle_consume_identifier; Ok(Tokens.DIMENSION(number, string)); } else { switch%sedlex (lexbuf) { @@ -828,11 +826,11 @@ let consume = lexbuf => { | identifier_start_code_point => Sedlexing.rollback(lexbuf); let.ok string = - consume_identifier(lexbuf) |> handle_consume_identifier_; + consume_identifier(lexbuf) |> handle_consume_identifier; Ok(Tokens.HASH(string, `ID)); | _ => let.ok string = - consume_identifier(lexbuf) |> handle_consume_identifier_; + consume_identifier(lexbuf) |> handle_consume_identifier; Ok(Tokens.HASH(string, `UNRESTRICTED)); }; | _ => Ok(DELIM("#")) @@ -844,7 +842,7 @@ let consume = lexbuf => { consume_numeric(lexbuf); | starts_an_identifier => Sedlexing.rollback(lexbuf); - consume_ident_like_(lexbuf); + consume_ident_like(lexbuf); | _ => let _ = Sedlexing.next(lexbuf); Ok(DELIM("-")); @@ -882,8 +880,7 @@ let consume = lexbuf => { | "@" => if (check_if_three_codepoints_would_start_an_identifier(lexbuf)) { // TODO: grr BAD_IDENT - let.ok string = - consume_identifier(lexbuf) |> handle_consume_identifier_; + let.ok string = consume_identifier(lexbuf) |> handle_consume_identifier; Ok(Tokens.AT_KEYWORD(string)); } else { Ok(DELIM("@")); @@ -894,7 +891,7 @@ let consume = lexbuf => { switch%sedlex (lexbuf) { | starts_with_a_valid_escape => Sedlexing.rollback(lexbuf); - consume_ident_like_(lexbuf); + consume_ident_like(lexbuf); // TODO: this error should be different | _ => Error((DELIM("/"), Invalid_code_point)) }; @@ -904,7 +901,7 @@ let consume = lexbuf => { consume_numeric(lexbuf); | identifier_start_code_point => let _ = Sedlexing.backtrack(lexbuf); - consume_ident_like_(lexbuf); + consume_ident_like(lexbuf); | eof => Ok(EOF) | any => Ok(DELIM(lexeme(lexbuf))) | _ => unreachable(lexbuf) @@ -916,7 +913,6 @@ type token_with_location = { loc: Location.t, }; -/* TODO: Use lex_buffer from parser to keep track of the file */ let from_string = string => { let lexbuf = Sedlexing.Utf8.from_string(string); let rec read = acc => { diff --git a/packages/parser/lib/dune b/packages/parser/lib/dune index 04e9919f5..5a557e3ca 100644 --- a/packages/parser/lib/dune +++ b/packages/parser/lib/dune @@ -5,6 +5,11 @@ (library (name styled_ppx_css_parser) (public_name styled-ppx.css-parser) - (libraries sedlex menhirLib ppxlib) + (libraries + sedlex + menhirLib + ppxlib + (re_export ppx_deriving.runtime) ; We expose show_token from ppx_deriving + ) (preprocess (pps sedlex.ppx ppx_deriving.show))) diff --git a/packages/ppx/src/Css_to_runtime.re b/packages/ppx/src/Css_to_runtime.re index 157d531e2..1e4ab3d79 100644 --- a/packages/ppx/src/Css_to_runtime.re +++ b/packages/ppx/src/Css_to_runtime.re @@ -252,7 +252,7 @@ and render_declaration = (~loc: Ppxlib.location, d: declaration) => { ) ) { | Ok(exprs) => exprs - | Error(`Not_found) => [ + | Error(`Property_not_found) => [ Error.expr( ~loc=property_location, "Unknown property '" ++ property ++ "'", diff --git a/packages/ppx/src/Property_to_runtime.re b/packages/ppx/src/Property_to_runtime.re index cebd2b590..903a47541 100644 --- a/packages/ppx/src/Property_to_runtime.re +++ b/packages/ppx/src/Property_to_runtime.re @@ -66,7 +66,7 @@ let render_variable = (~loc, name) => let transform_with_variable = (parser, mapper, value_to_expr) => { Css_property_parser.( emit( - Combinator.combine_xor([ + Combinator.xor([ /* If the entire CSS value is interpolated, we treat it as a `Variable */ Rule.Match.map(Standard.interpolation, data => `Variable(data)), /* Otherwise it's a regular CSS `Value and match the parser */ @@ -2195,21 +2195,39 @@ let overflow_y = let overflow = polymorphic(Property_parser.property_overflow, (~loc) => fun - | `Xor([all]) => [ - [%expr CSS.overflow([%e variant_to_expression(~loc, all)])], - ] - | `Xor([x, y]) => [ - [%expr CSS.overflowX([%e variant_to_expression(~loc, x)])], - [%expr CSS.overflowY([%e variant_to_expression(~loc, y)])], - ] | `Interpolation(i) => [ [%expr CSS.overflow([%e render_variable(~loc, i)])], ] - | `Xor(_) => raise(Unsupported_feature) - | _ => raise(Unsupported_feature) + | `Xor([x]) => [ + [%expr CSS.overflow([%e variant_to_expression(~loc, x)])], + ] + | `Xor(many) => { + let overflows = + many + |> List.map(variant_to_expression(~loc)) + |> Builder.pexp_array(~loc); + [[%expr CSS.overflows([%e overflows])]]; + } + | `_non_standard_overflow(non_standard) => { + switch (non_standard) { + | `_moz_scrollbars_none => [ + [%expr CSS.unsafe("overflow", "-moz-scrollbars-none")], + ] + | `_moz_scrollbars_horizontal => [ + [%expr CSS.unsafe("overflow", "-moz-scrollbars-horizontal")], + ] + | `_moz_scrollbars_vertical => [ + [%expr CSS.unsafe("overflow", "-moz-scrollbars-vertical")], + ] + | _moz_hidden_unscrollable => [ + [%expr CSS.unsafe("overflow", "-moz-hidden-unscrollable")], + ] + }; + } ); -// let overflow_clip_margin = unsupportedProperty(Property_parser.property_overflow_clip_margin); +/* let overflow_clip_margin = + unsupportedProperty(Property_parser.property_overflow_clip_margin); */ let overflow_block = monomorphic( @@ -5260,11 +5278,21 @@ let findProperty = name => { properties |> List.find_opt(((key, _)) => key == name); }; +let isVariableDeclaration = name => String.sub(name, 0, 2) == "--"; + +let render_variable_declaration = (~loc, property, value) => { + [%expr + CSS.unsafe( + [%e render_string(~loc, property)], + [%e render_string(~loc, value)], + )]; +}; + let render_to_expr = (~loc, property, value, important) => { let.ok expr_of_string = switch (findProperty(property)) { | Some((_, (_, expr_of_string))) => Ok(expr_of_string) - | None => Error(`Not_found) + | None => Error(`Property_not_found) }; switch (expr_of_string(~loc, value)) { @@ -5272,26 +5300,29 @@ let render_to_expr = (~loc, property, value, important) => { Ok(expr |> List.map(expr => [%expr CSS.important([%e expr])])) | Ok(expr) => Ok(expr) | Error(err) => Error(`Invalid_value(err)) - /* | exception (Invalid_value(v)) => Error(`Invalid_value(v)) */ + | exception (Invalid_value(v)) => Error(`Invalid_value(v)) }; }; -let render = (~loc: Location.t, property, value, important) => { - let.ok is_valid_string = - Property_parser.check_property(~name=property, value) - |> Result.map_error((`Unknown_value) => `Not_found); +let render = (~loc: Location.t, property, value, important) => + if (isVariableDeclaration(property)) { + Ok([render_variable_declaration(~loc, property, value)]); + } else { + let.ok is_valid_string = + Property_parser.check_property(~name=property, value) + |> Result.map_error((`Unknown_value) => `Property_not_found); - switch (render_css_global_values(~loc, property, value)) { - | Ok(value) => Ok(value) - | Error(_) => - switch (render_to_expr(~loc, property, value, important)) { + switch (render_css_global_values(~loc, property, value)) { | Ok(value) => Ok(value) - | exception (Invalid_value(v)) => - Error(`Invalid_value(value ++ ". " ++ v)) - | Error(_) - | exception Unsupported_feature => - let.ok () = is_valid_string ? Ok() : Error(`Invalid_value(value)); - Ok([render_when_unsupported_features(~loc, property, value)]); - } + | Error(_) => + switch (render_to_expr(~loc, property, value, important)) { + | Ok(value) => Ok(value) + | exception (Invalid_value(v)) => + Error(`Invalid_value(value ++ ". " ++ v)) + | Error(_) + | exception Unsupported_feature => + let.ok () = is_valid_string ? Ok() : Error(`Invalid_value(value)); + Ok([render_when_unsupported_features(~loc, property, value)]); + } + }; }; -}; diff --git a/packages/ppx/src/Property_to_runtime.rei b/packages/ppx/src/Property_to_runtime.rei index cc51d41dd..6210697e3 100644 --- a/packages/ppx/src/Property_to_runtime.rei +++ b/packages/ppx/src/Property_to_runtime.rei @@ -4,5 +4,5 @@ let render: (~loc: Location.t, string, string, bool) => result( list(Parsetree.expression), - [ | `Invalid_value(string) | `Not_found], + [ | `Invalid_value(string) | `Property_not_found], ); diff --git a/packages/ppx/src/Property_to_string.re b/packages/ppx/src/Property_to_string.re index 8ce3f9957..a018d816a 100644 --- a/packages/ppx/src/Property_to_string.re +++ b/packages/ppx/src/Property_to_string.re @@ -298,7 +298,7 @@ let found = ({ast_of_string, string_to_expr, _}) => { let transform_with_variable = (parser, mapper, value_to_expr) => emit( - Combinator.combine_xor([ + Combinator.xor([ /* If the CSS value is an interpolation, we treat as one ` ariable */ Rule.Match.map(Standard.interpolation, data => `Variable(data)), @@ -589,7 +589,7 @@ let render_to_expr = (property, value) => { let.ok expr_of_string = switch (findProperty(property)) { | Some((_, (_, expr_of_string))) => Ok(expr_of_string) - | None => Error(`Not_found) + | None => Error(`Property_not_found) }; expr_of_string(value) |> Result.map_error(str => `Invalid_value(str)); @@ -598,7 +598,7 @@ let render_to_expr = (property, value) => { let parse_declarations = (property: string, value: string) => { let.ok _ = Parser.check_property(~name=property, value) - |> Result.map_error((`Unknown_value) => `Not_found); + |> Result.map_error((`Unknown_value) => `Property_not_found); switch (render_css_global_values(property, value)) { | Ok(value) => Ok(value) diff --git a/packages/ppx/src/Property_to_string.rei b/packages/ppx/src/Property_to_string.rei index c79eaac29..22c164bc0 100644 --- a/packages/ppx/src/Property_to_string.rei +++ b/packages/ppx/src/Property_to_string.rei @@ -4,5 +4,5 @@ let parse_declarations: (string, string) => result( list(Parsetree.expression), - [ | `Invalid_value(string) | `Not_found], + [ | `Invalid_value(string) | `Property_not_found], ); diff --git a/packages/ppx/test/css-support/random.t/input.re b/packages/ppx/test/css-support/random.t/input.re index 527265153..5bf7984d3 100644 --- a/packages/ppx/test/css-support/random.t/input.re +++ b/packages/ppx/test/css-support/random.t/input.re @@ -128,3 +128,5 @@ let value = `clip; ]; [%cx {|aspect-ratio: 16 / 9;|}]; + +[%css {|color: var(--color-link);|}]; diff --git a/packages/ppx/test/css-support/random.t/run.t b/packages/ppx/test/css-support/random.t/run.t index bc59119ad..2b947ea9f 100644 --- a/packages/ppx/test/css-support/random.t/run.t +++ b/packages/ppx/test/css-support/random.t/run.t @@ -177,5 +177,6 @@ If this test fail means that the module is not in sync with the ppx ), |]); CSS.style([|CSS.aspectRatio(`ratio((16, 9)))|]); + CSS.color(`var({js|--color-link|js})); $ dune build diff --git a/packages/ppx/test/native/Static_test.re b/packages/ppx/test/native/Static_test.re index 3fc6c12d9..2a96fc096 100644 --- a/packages/ppx/test/native/Static_test.re +++ b/packages/ppx/test/native/Static_test.re @@ -559,21 +559,21 @@ let properties_static_css_tests = [ [%expr [%css "overflow: scroll"]], [%expr CSS.overflow(`scroll)], ), - /* ( - [%css "overflow: scroll visible"], - [%expr [%css "overflow: scroll visible"]], - [%expr CSS.overflowX(`scroll), CSS.overflowY(`visible)], - ), */ - /* ( - [%css "text-overflow: clip"], - [%expr [%css "text-overflow: clip"]], - [%expr CSS.textOverflow(`clip)], - ), */ - /* ( - [%css "text-overflow: ellipsis"], - [%expr [%css "text-overflow: ellipsis"]], - [%expr CSS.textOverflow(`ellipsis)], - ), */ + ( + [%css "overflow: scroll visible"], + [%expr [%css "overflow: scroll visible"]], + [%expr CSS.overflows([|`scroll, `visible|])], + ), + ( + [%css "text-overflow: clip"], + [%expr [%css "text-overflow: clip"]], + [%expr CSS.textOverflow(`clip)], + ), + ( + [%css "text-overflow: ellipsis"], + [%expr [%css "text-overflow: ellipsis"]], + [%expr CSS.textOverflow(`ellipsis)], + ), ( [%css "text-transform: capitalize"], [%expr [%css "text-transform: capitalize"]], @@ -858,11 +858,13 @@ let properties_static_css_tests = [ |]) ], ), - /* ( - [%css "color: var(--main-c, #fff)"], - [%expr [%css "color: var(--main-c, #fff)"]], - [%expr CSS.color(`var({js|--main-c|js}, `hex({js|fff|js})))] - ), */ + /* + TODO: Variables don't support default argument on Parser.re (propertyp-arser) + ( + [%css "color: var(--main-c, #fff)"], + [%expr [%css "color: var(--main-c, #fff)"]], + [%expr CSS.color(`var(({js|--main-c|js}, `hex({js|fff|js}))))], + ), */ ( [%css "background-image: url('img_tree.gif')"], [%expr [%css "background-image: url('img_tree.gif')"]], @@ -1148,18 +1150,18 @@ let properties_static_css_tests = [ [%expr [%css "border-image-source: none"]], [%expr CSS.borderImageSource(`none)], ), - /* ( - [%css "border-image-source: linear-gradient(to top, red, yellow)"], - [%expr [%css "border-image-source: linear-gradient(to top, red, yellow)"]], - [%expr - CSS.borderImageSource( - `linearGradient(( - Some(`SideOrCorner(`Top)), - [|(CSS.red, None), (CSS.yellow, None)|], - )), - ) - ], - ), */ + ( + [%css "border-image-source: linear-gradient(to top, red, yellow)"], + [%expr [%css "border-image-source: linear-gradient(to top, red, yellow)"]], + [%expr + CSS.borderImageSource( + `linearGradient(( + Some(`Top), + [|(Some(CSS.red), None), (Some(CSS.yellow), None)|]: CSS.Types.Gradient.color_stop_list, + )), + ) + ], + ), ( [%css "image-rendering: smooth"], [%expr [%css "image-rendering: smooth"]], @@ -1294,6 +1296,11 @@ let properties_static_css_tests = [ [%expr [%css "tab-size: calc(10 + 10)"]], [%expr CSS.tabSize(`calc(`add((`num(10.), `num(10.)))))], ), + ( + [%css {|color: var(--color-link);|}], + [%expr [%css {|color: var(--color-link);|}]], + [%expr CSS.color(`var({js|--color-link|js}))], + ), // unsupported /* ( diff --git a/packages/ppx/test/snapshot/reason/reason-classname-cx.t/run.t b/packages/ppx/test/snapshot/reason/reason-classname-cx.t/run.t index 948543a43..0f3ab081d 100644 --- a/packages/ppx/test/snapshot/reason/reason-classname-cx.t/run.t +++ b/packages/ppx/test/snapshot/reason/reason-classname-cx.t/run.t @@ -1,13 +1,9 @@ $ refmt --parse re --print ml input.re > output.ml $ standalone --impl output.ml -o output.ml $ refmt --parse ml --print re output.ml - let className = - CSS.style([|CSS.label("className"), CSS.display(`block)|]); + let className = CSS.style([|CSS.label("className"), CSS.display(`block)|]); let classNameWithMultiLine = - CSS.style([| - CSS.label("classNameWithMultiLine"), - CSS.display(`block), - |]); + CSS.style([|CSS.label("classNameWithMultiLine"), CSS.display(`block)|]); let classNameWithArray = CSS.style([|CSS.label("classNameWithArray"), cssProperty|]); let cssRule = CSS.color(CSS.blue); diff --git a/packages/ppx/test/snapshot/reason/reason-media-queries-and-selectors.t/run.t b/packages/ppx/test/snapshot/reason/reason-media-queries-and-selectors.t/run.t index fe40051ad..ad9f9c678 100644 --- a/packages/ppx/test/snapshot/reason/reason-media-queries-and-selectors.t/run.t +++ b/packages/ppx/test/snapshot/reason/reason-media-queries-and-selectors.t/run.t @@ -969,10 +969,7 @@ {js|(min-width: 600px)|js}, [|CSS.backgroundColor(CSS.blue)|], ), - CSS.selector( - {js|&:hover|js}, - [|CSS.backgroundColor(CSS.green)|], - ), + CSS.selector({js|&:hover|js}, [|CSS.backgroundColor(CSS.green)|]), CSS.selector( {js|& > p|js}, [|CSS.color(CSS.pink), CSS.fontSize(`pxFloat(24.))|], diff --git a/packages/ppx/test/snapshot/reason/reason-static-open-property.t/run.t b/packages/ppx/test/snapshot/reason/reason-static-open-property.t/run.t index fa8978002..4d2d00201 100644 --- a/packages/ppx/test/snapshot/reason/reason-static-open-property.t/run.t +++ b/packages/ppx/test/snapshot/reason/reason-static-open-property.t/run.t @@ -963,10 +963,7 @@ external assign2: (Js.t({..}), Js.t({..}), Js.t({..})) => Js.t({..}) = "Object.assign"; let styles = - CSS.style([| - CSS.label("OneSingleProperty"), - CSS.display(`block), - |]); + CSS.style([|CSS.label("OneSingleProperty"), CSS.display(`block)|]); let make = (props: makeProps) => { let className = styles ++ getOrEmpty(classNameGet(props)); let stylesObject = {"className": className, "ref": innerRefGet(props)}; diff --git a/packages/ppx/test/snapshot/reason/reason-styled-global.t/input.re b/packages/ppx/test/snapshot/reason/reason-styled-global.t/input.re index cdf76ca52..274e26e75 100644 --- a/packages/ppx/test/snapshot/reason/reason-styled-global.t/input.re +++ b/packages/ppx/test/snapshot/reason/reason-styled-global.t/input.re @@ -1,4 +1,5 @@ -[%styled.global {| +[%styled.global + {| /*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */ /* Document @@ -348,4 +349,9 @@ template { [hidden] { display: none; } -|}]; + +:root { + --shiki-color-text: oklch(37.53% 0 0); +} +|} +]; diff --git a/packages/ppx/test/snapshot/reason/reason-styled-global.t/run.t b/packages/ppx/test/snapshot/reason/reason-styled-global.t/run.t index 6729ab2ba..cb88e7053 100644 --- a/packages/ppx/test/snapshot/reason/reason-styled-global.t/run.t +++ b/packages/ppx/test/snapshot/reason/reason-styled-global.t/run.t @@ -131,5 +131,9 @@ CSS.selector({js|summary|js}, [|CSS.display(`listItem)|]), CSS.selector({js|template|js}, [|CSS.display(`none)|]), CSS.selector({js|[hidden]|js}, [|CSS.display(`none)|]), + CSS.selector( + {js|:root|js}, + [|CSS.unsafe({js|--shiki-color-text|js}, {js|oklch(37.53% 0 0)|js})|], + ), |]), ); diff --git a/packages/renderer/ast_renderer.re b/packages/renderer/ast_renderer.re index 38aaad68e..cf7c10ea7 100644 --- a/packages/renderer/ast_renderer.re +++ b/packages/renderer/ast_renderer.re @@ -1,15 +1,3 @@ -let render_help = () => { - print_endline(""); - print_endline(""); - print_endline( - {| ast-renderer pretty-prints the CSS AST of parser/css_lexer.re|}, - ); - print_endline(""); - print_endline({| EXAMPLE: dune exec ast-renderer ".a { color: red }"|}); - print_endline(""); - print_endline(""); -}; - let args = Sys.argv |> Array.to_list; let input = List.nth_opt(args, 1); let help = @@ -31,7 +19,12 @@ let loc = switch (input, help) { | (Some(_), true) -| (None, _) => render_help() +| (None, _) => + print_endline( + "\n `ast-renderer` pretty-prints the CSS AST of the input string. + + EXAMPLE: dune exec ast-renderer \".a { color: red }\"\n", + ) | (Some(css), _) => switch (Styled_ppx_css_parser.Driver.parse_declaration_list(~loc, css)) { | Ok(declarations) => diff --git a/packages/renderer/lexer_renderer.re b/packages/renderer/lexer_renderer.re index bf31cfd94..ae703055e 100644 --- a/packages/renderer/lexer_renderer.re +++ b/packages/renderer/lexer_renderer.re @@ -1,13 +1,3 @@ -let render_help = () => { - print_endline(""); - print_endline(""); - print_endline({| lexer-renderer pretty-prints the lexer of CSS|}); - print_endline(""); - print_endline({| EXAMPLE: esy x lexer-renderer ".a { color: red }"|}); - print_endline(""); - print_endline(""); -}; - let args = Sys.argv |> Array.to_list; let input = List.nth_opt(args, 1); let help = @@ -23,7 +13,12 @@ let help = switch (input, help) { | (Some(_), true) -| (None, _) => render_help() +| (None, _) => + print_endline( + "\n `lexer-renderer` pretty-prints the result of CSS lexering of the input string. + + EXAMPLE: dune exec lexer-renderer \".a { color: red }\"\n", + ) | (Some(css), _) => let okInput = Styled_ppx_css_parser.Lexer.tokenize(css) |> Result.get_ok; let debug = Styled_ppx_css_parser.Lexer.to_debug(okInput); diff --git a/packages/runtime/native/shared/Css_types.ml b/packages/runtime/native/shared/Css_types.ml index b37087a28..caf2385b3 100644 --- a/packages/runtime/native/shared/Css_types.ml +++ b/packages/runtime/native/shared/Css_types.ml @@ -1124,11 +1124,24 @@ module ColorMixMethod = struct [ `in1 of Rectangular_or_Polar_color_space.t | `in2 of PolarColorSpace.t * HueSize.t ] + + let toString (x : t) : string = + match x with + | `in1 x -> Rectangular_or_Polar_color_space.toString x + | `in2 (x, y) -> + (match x with + | `hsl -> "hsl" + | `hwb -> "hwb" + | `lch -> "lch" + | `oklch -> "oklch") + ^ {js| |js} + ^ HueSize.toString y end module Color = struct type rgb = int * int * int + (* TODO: Move calc_min_max to , , ,