Skip to content

Commit

Permalink
Support content with interpolation (#494)
Browse files Browse the repository at this point in the history
  • Loading branch information
davesnx authored Jul 11, 2024
1 parent dd210dc commit c20946f
Show file tree
Hide file tree
Showing 15 changed files with 548 additions and 2,470 deletions.
12 changes: 7 additions & 5 deletions e2e/rescript-v10-JSX4/src/content_test.res
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,13 @@ describe("content as string", () => {
})

let testData = list{
(%css("content: ''"), CSS.contentRule(#text("''"))),
(%css("content: '\"'"), CSS.contentRule(#text(`'"'`))),
(%css(`content: '\"'`), CSS.contentRule(#text("'\"'"))),
(%css("content: ' '"), CSS.contentRule(#text("' '"))),
(%css("content: 'single'"), CSS.contentRule(#text("'single'"))),
(%css(`content: ''`), CSS.contentRule(#text("''"))),
(%css(`content: ""`), CSS.contentRule(#text("''"))),
(%css(`content: ' '`), CSS.contentRule(#text("' '"))),
(%css(`content: " "`), CSS.contentRule(#text("' '"))),
(%css(`content: '"'`), CSS.contentRule(#text("\""))),
(%css(`content: "'"`), CSS.contentRule(#text("'"))),
(%css(`content: 'xxx'`), CSS.contentRule(#text(`xxx`))),
(%css(`font-family: "Lola"`), CSS.fontFamily("Lola")),
(%css(`font-family: "Lola del rio"`), CSS.fontFamily("Lola del rio")),
}
Expand Down
12 changes: 7 additions & 5 deletions e2e/rescript-v9-JSX3/src/content_test.res
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,13 @@ describe("content as string", () => {
})

let testData = list{
(%css("content: ''"), CSS.contentRule(#text("''"))),
(%css("content: '\"'"), CSS.contentRule(#text(`'"'`))),
(%css(`content: '\"'`), CSS.contentRule(#text("'\"'"))),
(%css("content: ' '"), CSS.contentRule(#text("' '"))),
(%css("content: 'single'"), CSS.contentRule(#text("'single'"))),
(%css(`content: ''`), CSS.contentRule(#text("''"))),
(%css(`content: ""`), CSS.contentRule(#text("''"))),
(%css(`content: ' '`), CSS.contentRule(#text("' '"))),
(%css(`content: " "`), CSS.contentRule(#text("' '"))),
(%css(`content: '"'`), CSS.contentRule(#text("\""))),
(%css(`content: "'"`), CSS.contentRule(#text("'"))),
(%css(`content: 'xxx'`), CSS.contentRule(#text(`xxx`))),
(%css(`font-family: "Lola"`), CSS.fontFamily("Lola")),
(%css(`font-family: "Lola del rio"`), CSS.fontFamily("Lola del rio")),
}
Expand Down
2 changes: 1 addition & 1 deletion packages/css-property-parser/lib/Parser.re
Original file line number Diff line number Diff line change
Expand Up @@ -1027,7 +1027,7 @@ and property_contain = [%value.rec
"'none' | 'strict' | 'content' | 'size' || 'layout' || 'style' || 'paint'"
]
and property_content = [%value.rec
"'normal' | 'none' | [ <content-replacement> | <content-list> ] [ '/' <string> ]?"
"'normal' | 'none' | <string> | <interpolation> | [ <content-replacement> | <content-list> ] [ '/' <string> ]?"
]
and property_content_visibility = [%value.rec "'visible' | 'hidden' | 'auto'"]
and property_counter_increment = [%value.rec
Expand Down
122 changes: 46 additions & 76 deletions packages/css-property-parser/test/Standard_test.re
Original file line number Diff line number Diff line change
Expand Up @@ -90,16 +90,12 @@ let tests = [
let pp_length = (ppf, x) => Fmt.pf(ppf, "%S", render_length(x));
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(
__POS__,
to_check,
parse("59invalid"),
Error("unknown dimension"),
);
check(__POS__, to_check, parse("0"), Ok(`Zero));
check(__POS__, to_check, parse("60"), Error("expected length"));
let expect = check(__POS__, to_check);
expect(parse("56cm"), Ok(`Cm(56.)));
expect(parse("57px"), Ok(`Px(57.)));
expect(parse("59invalid"), Error("unknown dimension"));
expect(parse("0"), Ok(`Zero));
expect(parse("60"), Error("expected length"));
}),
test("<angle>", () => {
let parse = parse([%value "<angle>"]);
Expand Down Expand Up @@ -307,13 +303,9 @@ let tests = [
test("<ident>", () => {
let parse = parse([%value "<ident>"]);
let to_check = Alcotest.result(Alcotest.string, Alcotest.string);
check(__POS__, to_check, parse("test"), Ok("test"));
check(
__POS__,
to_check,
parse("'ohno'"),
Error("expected an indentifier"),
);
let expect = check(__POS__, to_check);
expect(parse("test"), Ok("test"));
expect(parse("'ohno'"), Error("expected an indentifier"));
}),
test("<css-wide-keywords>", () => {
let parse = parse([%value "<css-wide-keywords>"]);
Expand Down Expand Up @@ -370,34 +362,34 @@ let tests = [
test("<string>", () => {
let parse = parse([%value "<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"));
check(__POS__, to_check, parse("68.9"), Error("expected a string"));
let expect = check(__POS__, to_check);
expect(parse({|'tuturu'|}), Ok("tuturu"));
expect(parse({|'67.8'|}), Ok("67.8"));
expect(parse({|ident|}), Error("expected a string"));
expect(parse({|68.9|}), Error("expected a string"));
expect(parse({|"this is a 'string'."|}), Ok("this is a 'string'."));
expect(parse({|""|}), Ok(""));
expect(parse({|''|}), Ok(""));
expect(parse({|" "|}), Ok(" "));
expect(parse({|'"'|}), Ok("\""));
expect(parse({|' '|}), Ok(" "));
expect(parse({|"this is a \"string\"."|}), Ok("this is a \"string\"."));
expect(parse({|'this is a "string".'|}), Ok("this is a \"string\"."));
expect(parse({|'this is a \'string\'.'|}), Ok("this is a \'string\'."));
}),
test("<dashed-ident>", () => {
let parse = parse([%value "<dashed-ident>"]);
let to_check = Alcotest.result(Alcotest.string, Alcotest.string);
check(__POS__, to_check, parse("--random"), Ok("--random"));
check(
__POS__,
to_check,
parse("random'"),
Error("expected a --variable"),
);
let expect = check(__POS__, to_check);
expect(parse("--random"), Ok("--random"));
expect(parse("random'"), Error("expected a --variable"));
}),
test("<url>", () => {
let parse = parse([%value "<url>"]);
let to_check = Alcotest.result(Alcotest.string, Alcotest.string);
check(
__POS__,
to_check,
parse("url(https://google.com)"),
Ok("https://google.com"),
);
check(
__POS__,
to_check,
let expect = check(__POS__, to_check);
expect(parse("url(https://google.com)"), Ok("https://google.com"));
expect(
parse("url(\"https://duckduckgo.com\")"),
Ok("https://duckduckgo.com"),
);
Expand All @@ -406,14 +398,10 @@ let tests = [
test("<hex-color>", () => {
let parse = parse([%value "<hex-color>"]);
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(
__POS__,
to_check,
parse("#abcdefghi"),
Error("expected a hex-color"),
);
let expect = check(__POS__, to_check);
expect(parse("#abc"), Ok("abc"));
expect(parse("#abcdefgh"), Ok("abcdefgh"));
expect(parse("#abcdefghi"), Error("expected a hex-color"));
}),
test("<linenames>", () => {
let parse = parse([%value "<line_names>"]);
Expand All @@ -426,14 +414,10 @@ let tests = [
),
Alcotest.string,
);
check(__POS__, to_check, parse("[abc]"), Ok(((), ["abc"], ())));
check(__POS__, to_check, parse("[a b]"), Ok(((), ["a", "b"], ())));
check(
__POS__,
to_check,
parse("[a b c]"),
Ok(((), ["a", "b", "c"], ())),
);
let expect = check(__POS__, to_check);
expect(parse("[abc]"), Ok(((), ["abc"], ())));
expect(parse("[a b]"), Ok(((), ["a", "b"], ())));
expect(parse("[a b c]"), Ok(((), ["a", "b", "c"], ())));
}),
test("chars", () => {
let parse = parse([%value "<string>? ',' <string>"]);
Expand All @@ -446,12 +430,8 @@ let tests = [
),
Alcotest.string,
);
check(
__POS__,
to_check,
parse("'lola' , 'flores'"),
Ok((Some("lola"), (), "flores")),
);
let expect = check(__POS__, to_check);
expect(parse("'lola' , 'flores'"), Ok((Some("lola"), (), "flores")));
}),
test("custom-ident vs all", () => {
let parse = parse([%value "<custom-ident> | 'all'"]);
Expand All @@ -464,29 +444,19 @@ let tests = [
let pp_output = (ppf, x) => Fmt.pf(ppf, "%S", render_output(x));
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")));
let expect = check(__POS__, to_check);
expect(parse("all"), Ok(`All));
expect(parse("moar"), Ok(`Custom_ident("moar")));
}),
test("interpolation", () => {
let parse = parse([%value "<interpolation>"]);
let to_check =
Alcotest.result(Alcotest.list(Alcotest.string), Alcotest.string);
check(
__POS__,
to_check,
parse("$(Module.value)"),
Ok(["Module", "value"]),
);
check(
__POS__,
to_check,
parse("$(Module'.value')"),
Ok(["Module'", "value'"]),
);
let expect = check(__POS__, to_check);
expect(parse("$(Module.value)"), Ok(["Module", "value"]));
expect(parse("$(Module'.value')"), Ok(["Module'", "value'"]));
/* TODO: Add error message into interpolation */
check(
__POS__,
to_check,
expect(
parse("asd"),
Error("Expected 'delimiter $' but instead got ident asd"),
);
Expand Down
97 changes: 91 additions & 6 deletions packages/ppx/src/Property_to_runtime.re
Original file line number Diff line number Diff line change
Expand Up @@ -2935,11 +2935,11 @@ let transform =

let render_origin = (~loc) =>
fun
| `Center => variant_to_expression(~loc, `Center)
| `Left => variant_to_expression(~loc, `Left)
| `Right => variant_to_expression(~loc, `Right)
| `Bottom => variant_to_expression(~loc, `Bottom)
| `Top => variant_to_expression(~loc, `Top)
| `Center as x
| `Left as x
| `Right as x
| `Top as x
| `Bottom as x => variant_to_expression(~loc, x)
| `Function_calc(fc) => render_function_calc(~loc, fc)
| `Interpolation(v) => render_variable(~loc, v)
| `Length(l) => render_length(~loc, l)
Expand Down Expand Up @@ -4647,7 +4647,91 @@ let contain = unsupportedProperty(Property_parser.property_contain);
let content_visibility =
unsupportedProperty(Property_parser.property_content_visibility);

let content = unsupportedProperty(Property_parser.property_content);
let render_quote = (~loc, quote: Types.quote) => {
switch (quote) {
| `Close_quote => [%expr `closeQuote]
| `No_close_quote => [%expr `noCloseQuote]
| `No_open_quote => [%expr `noOpenQuote]
| `Open_quote => [%expr `openQuote]
};
};

let render_content_string = (~loc, str) => {
let length = String.length(str);
let str =
if (length == 0) {
[%expr {js|''|js}];
} else if (length == 1 && str.[0] == '"') {
[%expr {js|'"'|js}];
} else if (length == 1 && str.[0] == ' ') {
[%expr {js|' '|js}];
} else if (length == 1 && str.[0] == '\'') {
[%expr {js|"'"|js}];
} else if (length == 2 && str.[0] == '"' && str.[1] == '"') {
[%expr {js|""|js}];
} else {
let first = str.[0];
let last = str.[length - 1];
switch (first, last) {
| ('\'', '\'') => [%expr [%e render_string(~loc, str)]]
| ('"', '"') => [%expr [%e render_string(~loc, str)]]
| _ =>
[%expr [%e render_string(~loc, str)]];
};
};
[%expr `text([%e str])];
};

let render_content_list = (~loc, content_list: Types.content_list) => {
content_list
|> List.map(content_item =>
switch (content_item) {
| `Contents => [%expr `contents]
| `Quote(quote) => render_quote(~loc, quote)
| `String(str) => render_content_string(~loc, str)
| `Url(u) => render_url(~loc, u)
| `Counter(_label, _, _style) => raise(Unsupported_feature)
| `Function_attr(_attr) => raise(Unsupported_feature)
}
)
|> Builder.pexp_array(~loc);
};

let content =
polymorphic(Property_parser.property_content, (~loc, value) => {
switch (value) {
| `Normal => [[%expr CSS.contentRule(`normal)]]
| `None => [[%expr CSS.contentRule(`none)]]
| `String(str) => [
[%expr CSS.contentRule([%e render_content_string(~loc, str)])],
]
| `Interpolation(v) => [
[%expr CSS.contentRule([%e render_variable(~loc, v)])],
]
| `Static(`Content_list(lst), None) => [
[%expr CSS.contentsRule([%e render_content_list(~loc, lst)], None)],
]
| `Static(`Content_list(lst), Some((_, alt))) => [
[%expr
CSS.contentsRule(
[%e render_content_list(~loc, lst)],
Some([%e render_string(~loc, alt)]),
)
],
]
| `Static(`Content_replacement(image), None) => [
[%expr CSS.contentRule([%e render_image(~loc, image)])],
]
| `Static(`Content_replacement(image), Some((_, alt))) => [
[%expr
CSS.contentsRule(
[|`url([%e render_image(~loc, image)])|],
Some([%e render_string(~loc, alt)]),
)
],
]
}
});

let empty_cells = unsupportedProperty(Property_parser.property_empty_cells);

Expand Down Expand Up @@ -4985,6 +5069,7 @@ let properties = [
("clip-path", found(clip_path)),
("clip-rule", found(clip_rule)),
("clip", found(clip)),
("content", found(content)),
("color-adjust", found(color_adjust)),
("color-interpolation-filters", found(color_interpolation_filters)),
("color-interpolation", found(color_interpolation)),
Expand Down
Loading

0 comments on commit c20946f

Please sign in to comment.