diff --git a/.changeset/itchy-brooms-kick.md b/.changeset/itchy-brooms-kick.md new file mode 100644 index 00000000..5de51a04 --- /dev/null +++ b/.changeset/itchy-brooms-kick.md @@ -0,0 +1,5 @@ +--- +"yak-swc": patch +--- + +fix a parsing bug for unquoted urls inside url() diff --git a/packages/yak-swc/css_in_js_parser/src/parse_css.rs b/packages/yak-swc/css_in_js_parser/src/parse_css.rs index 380feb02..374c7658 100644 --- a/packages/yak-swc/css_in_js_parser/src/parse_css.rs +++ b/packages/yak-swc/css_in_js_parser/src/parse_css.rs @@ -11,6 +11,7 @@ pub struct ParserState { pub current_scopes: Vec, pub current_declaration: Declaration, pub pending_css_segment: String, + pub paren_depth: usize, } impl ParserState { @@ -23,6 +24,7 @@ impl ParserState { current_scopes: Vec::new(), current_declaration: Declaration::new(), pending_css_segment: String::new(), + paren_depth: 0, } } } @@ -97,6 +99,7 @@ pub fn parse_css( state.current_comment_state = CommentStateType::None; state.is_inside_property_value = false; state.is_inside_at_rule = false; + state.paren_depth = 0; state.current_declaration = Declaration::new(); state.pending_css_segment.clone() + css_string } else { @@ -171,9 +174,20 @@ pub fn parse_css( } } + // Detect parens outside of strings for property values + // e.g. + // .foo { background: url('https://example.com'); } + if state.is_inside_string.is_none() && state.is_inside_property_value { + if current_character == '(' { + state.paren_depth += 1; + } else if current_character == ')' && state.paren_depth > 0 { + state.paren_depth -= 1; + } + } + // Inside a string, just add the character to the current code no matter what // e.g. content: "{ ; } @ !" - if state.is_inside_string.is_some() { + if state.is_inside_string.is_some() || state.paren_depth > 0 { current_code.push(current_character); state.current_declaration.value.push(current_character); char_position += 1; @@ -389,6 +403,19 @@ mod tests { assert_debug_snapshot!((state, declarations)); } + #[test] + fn test_parse_css_incomplete_css_1_ending_inside_parens_string() { + let (state, declarations) = parse_css( + r#" + .foo { + .fancy { + background: url(https://example.com + "#, + None, + ); + assert_debug_snapshot!((state, declarations)); + } + #[test] fn test_parse_css_incomplete_css_1_ending_outside_a_comment() { let (state, declarations) = parse_css( @@ -437,7 +464,7 @@ mod tests { } } } - background: url("https://example.com"); + background: url(https://example.com); body { padding: 0; } @@ -494,4 +521,21 @@ mod tests { ); assert_debug_snapshot!((state, declarations)); } + + #[test] + fn test_parse_css_with_dynamic_values() { + let (state1, _) = parse_css( + r#" + .foo { + transform: translate(-50%, -50%) rotate("#, + None, + ); + let (state2, _) = parse_css(r#"20deg) translate(0, -88px) rotate("#, Some(state1)); + let (_, declarations3) = parse_css(r#"90deg);"#, Some(state2)); + assert_eq!(declarations3.len(), 1); + assert_eq!( + declarations3[0].value.trim(), + "translate(-50%, -50%) rotate(20deg) translate(0, -88px) rotate(90deg)" + ); + } } diff --git a/packages/yak-swc/css_in_js_parser/src/snapshots/css_in_js_parser__parse_css__tests__parse_css_complete_css_with_media_rule.snap b/packages/yak-swc/css_in_js_parser/src/snapshots/css_in_js_parser__parse_css__tests__parse_css_complete_css_with_media_rule.snap index fa67ce3b..8d1b33a3 100644 --- a/packages/yak-swc/css_in_js_parser/src/snapshots/css_in_js_parser__parse_css__tests__parse_css_complete_css_with_media_rule.snap +++ b/packages/yak-swc/css_in_js_parser/src/snapshots/css_in_js_parser__parse_css__tests__parse_css_complete_css_with_media_rule.snap @@ -16,6 +16,7 @@ expression: "(state, declarations)" closed: false, }, pending_css_segment: "", + paren_depth: 0, }, [ Declaration { @@ -58,7 +59,7 @@ expression: "(state, declarations)" }, Declaration { property: "background", - value: "url(\"https://example.com\")", + value: "url(https://example.com)", scope: [ CssScope { name: ".foo", diff --git a/packages/yak-swc/css_in_js_parser/src/snapshots/css_in_js_parser__parse_css__tests__parse_css_incomplete_css_1.snap b/packages/yak-swc/css_in_js_parser/src/snapshots/css_in_js_parser__parse_css__tests__parse_css_incomplete_css_1.snap index 7df156f9..5f2ae649 100644 --- a/packages/yak-swc/css_in_js_parser/src/snapshots/css_in_js_parser__parse_css__tests__parse_css_incomplete_css_1.snap +++ b/packages/yak-swc/css_in_js_parser/src/snapshots/css_in_js_parser__parse_css__tests__parse_css_incomplete_css_1.snap @@ -25,6 +25,7 @@ expression: "(state, declarations)" closed: false, }, pending_css_segment: "", + paren_depth: 0, }, [ Declaration { diff --git a/packages/yak-swc/css_in_js_parser/src/snapshots/css_in_js_parser__parse_css__tests__parse_css_incomplete_css_1_ending_inside_a_comment.snap b/packages/yak-swc/css_in_js_parser/src/snapshots/css_in_js_parser__parse_css__tests__parse_css_incomplete_css_1_ending_inside_a_comment.snap index 9419297e..8ab5b879 100644 --- a/packages/yak-swc/css_in_js_parser/src/snapshots/css_in_js_parser__parse_css__tests__parse_css_incomplete_css_1_ending_inside_a_comment.snap +++ b/packages/yak-swc/css_in_js_parser/src/snapshots/css_in_js_parser__parse_css__tests__parse_css_incomplete_css_1_ending_inside_a_comment.snap @@ -25,6 +25,7 @@ expression: "(state, declarations)" closed: false, }, pending_css_segment: "", + paren_depth: 0, }, [], ) diff --git a/packages/yak-swc/css_in_js_parser/src/snapshots/css_in_js_parser__parse_css__tests__parse_css_incomplete_css_1_ending_inside_a_double_quote_string.snap b/packages/yak-swc/css_in_js_parser/src/snapshots/css_in_js_parser__parse_css__tests__parse_css_incomplete_css_1_ending_inside_a_double_quote_string.snap index 20c74c5d..42fe92e2 100644 --- a/packages/yak-swc/css_in_js_parser/src/snapshots/css_in_js_parser__parse_css__tests__parse_css_incomplete_css_1_ending_inside_a_double_quote_string.snap +++ b/packages/yak-swc/css_in_js_parser/src/snapshots/css_in_js_parser__parse_css__tests__parse_css_incomplete_css_1_ending_inside_a_double_quote_string.snap @@ -27,6 +27,7 @@ expression: "(state, declarations)" closed: false, }, pending_css_segment: "background: url(\"https://example.com\n ", + paren_depth: 1, }, [], ) diff --git a/packages/yak-swc/css_in_js_parser/src/snapshots/css_in_js_parser__parse_css__tests__parse_css_incomplete_css_1_ending_inside_a_string.snap b/packages/yak-swc/css_in_js_parser/src/snapshots/css_in_js_parser__parse_css__tests__parse_css_incomplete_css_1_ending_inside_a_string.snap index daaad3cc..c1c3abe0 100644 --- a/packages/yak-swc/css_in_js_parser/src/snapshots/css_in_js_parser__parse_css__tests__parse_css_incomplete_css_1_ending_inside_a_string.snap +++ b/packages/yak-swc/css_in_js_parser/src/snapshots/css_in_js_parser__parse_css__tests__parse_css_incomplete_css_1_ending_inside_a_string.snap @@ -27,6 +27,7 @@ expression: "(state, declarations)" closed: false, }, pending_css_segment: "background: url('https://example.com\n ", + paren_depth: 1, }, [], ) diff --git a/packages/yak-swc/css_in_js_parser/src/snapshots/css_in_js_parser__parse_css__tests__parse_css_incomplete_css_1_ending_inside_parens_string.snap b/packages/yak-swc/css_in_js_parser/src/snapshots/css_in_js_parser__parse_css__tests__parse_css_incomplete_css_1_ending_inside_parens_string.snap new file mode 100644 index 00000000..c9841d3b --- /dev/null +++ b/packages/yak-swc/css_in_js_parser/src/snapshots/css_in_js_parser__parse_css__tests__parse_css_incomplete_css_1_ending_inside_parens_string.snap @@ -0,0 +1,31 @@ +--- +source: css_in_js_parser/src/parse_css.rs +expression: "(state, declarations)" +--- +( + ParserState { + is_inside_string: None, + current_comment_state: None, + is_inside_property_value: true, + is_inside_at_rule: false, + current_scopes: [ + CssScope { + name: ".foo", + scope_type: Selector, + }, + CssScope { + name: ".fancy", + scope_type: Selector, + }, + ], + current_declaration: Declaration { + property: "background", + value: "url(https://example.com\n ", + scope: [], + closed: false, + }, + pending_css_segment: "background: url(https://example.com\n ", + paren_depth: 1, + }, + [], +) diff --git a/packages/yak-swc/css_in_js_parser/src/snapshots/css_in_js_parser__parse_css__tests__parse_css_incomplete_css_1_ending_outside_a_comment.snap b/packages/yak-swc/css_in_js_parser/src/snapshots/css_in_js_parser__parse_css__tests__parse_css_incomplete_css_1_ending_outside_a_comment.snap index bd9ce838..0ce2f8f9 100644 --- a/packages/yak-swc/css_in_js_parser/src/snapshots/css_in_js_parser__parse_css__tests__parse_css_incomplete_css_1_ending_outside_a_comment.snap +++ b/packages/yak-swc/css_in_js_parser/src/snapshots/css_in_js_parser__parse_css__tests__parse_css_incomplete_css_1_ending_outside_a_comment.snap @@ -25,6 +25,7 @@ expression: "(state, declarations)" closed: false, }, pending_css_segment: "color: blue\n", + paren_depth: 0, }, [], ) diff --git a/packages/yak-swc/css_in_js_parser/src/snapshots/css_in_js_parser__parse_css__tests__parse_css_incomplete_css_1_with_media_rule.snap b/packages/yak-swc/css_in_js_parser/src/snapshots/css_in_js_parser__parse_css__tests__parse_css_incomplete_css_1_with_media_rule.snap index a66dab51..21f20546 100644 --- a/packages/yak-swc/css_in_js_parser/src/snapshots/css_in_js_parser__parse_css__tests__parse_css_incomplete_css_1_with_media_rule.snap +++ b/packages/yak-swc/css_in_js_parser/src/snapshots/css_in_js_parser__parse_css__tests__parse_css_incomplete_css_1_with_media_rule.snap @@ -33,6 +33,7 @@ expression: "(state, declarations)" closed: false, }, pending_css_segment: "", + paren_depth: 0, }, [ Declaration { diff --git a/packages/yak-swc/css_in_js_parser/src/snapshots/css_in_js_parser__parse_css__tests__parse_css_incomplete_css_ending_without_semicolon.snap b/packages/yak-swc/css_in_js_parser/src/snapshots/css_in_js_parser__parse_css__tests__parse_css_incomplete_css_ending_without_semicolon.snap index 419db925..a6f0270f 100644 --- a/packages/yak-swc/css_in_js_parser/src/snapshots/css_in_js_parser__parse_css__tests__parse_css_incomplete_css_ending_without_semicolon.snap +++ b/packages/yak-swc/css_in_js_parser/src/snapshots/css_in_js_parser__parse_css__tests__parse_css_incomplete_css_ending_without_semicolon.snap @@ -21,6 +21,7 @@ expression: "(state, declarations)" closed: false, }, pending_css_segment: "color: orange\n", + paren_depth: 0, }, [], ) diff --git a/packages/yak-swc/css_in_js_parser/src/snapshots/css_in_js_parser__parse_css__tests__parse_css_resume_incomplete_css_ending_without_semicolon.snap b/packages/yak-swc/css_in_js_parser/src/snapshots/css_in_js_parser__parse_css__tests__parse_css_resume_incomplete_css_ending_without_semicolon.snap index 47db6d52..4bac12f2 100644 --- a/packages/yak-swc/css_in_js_parser/src/snapshots/css_in_js_parser__parse_css__tests__parse_css_resume_incomplete_css_ending_without_semicolon.snap +++ b/packages/yak-swc/css_in_js_parser/src/snapshots/css_in_js_parser__parse_css__tests__parse_css_resume_incomplete_css_ending_without_semicolon.snap @@ -21,6 +21,7 @@ expression: "(state1, state2, all_declarations)" closed: false, }, pending_css_segment: "color: orange\n", + paren_depth: 0, }, ParserState { is_inside_string: None, @@ -40,6 +41,7 @@ expression: "(state1, state2, all_declarations)" closed: false, }, pending_css_segment: "", + paren_depth: 0, }, [ Declaration { diff --git a/packages/yak-swc/css_in_js_parser/src/snapshots/css_in_js_parser__parse_css__tests__parse_css_starting_with_an_invalid_curly.snap b/packages/yak-swc/css_in_js_parser/src/snapshots/css_in_js_parser__parse_css__tests__parse_css_starting_with_an_invalid_curly.snap index 89d0d714..a6f0270f 100644 --- a/packages/yak-swc/css_in_js_parser/src/snapshots/css_in_js_parser__parse_css__tests__parse_css_starting_with_an_invalid_curly.snap +++ b/packages/yak-swc/css_in_js_parser/src/snapshots/css_in_js_parser__parse_css__tests__parse_css_starting_with_an_invalid_curly.snap @@ -1,6 +1,5 @@ --- source: css_in_js_parser/src/parse_css.rs -assertion_line: 496 expression: "(state, declarations)" --- ( @@ -22,6 +21,7 @@ expression: "(state, declarations)" closed: false, }, pending_css_segment: "color: orange\n", + paren_depth: 0, }, [], ) diff --git a/packages/yak-swc/yak_swc/tests/fixture/parens/input.tsx b/packages/yak-swc/yak_swc/tests/fixture/parens/input.tsx new file mode 100644 index 00000000..09268119 --- /dev/null +++ b/packages/yak-swc/yak_swc/tests/fixture/parens/input.tsx @@ -0,0 +1,10 @@ +import { styled, css } from "next-yak"; + +export const Card = styled.div` + background: url("/card-bg.jpg") no-repeat; + ${({$active}) => $active && css` + backgorund: url(/card-bg-active.jpg) no-repeat; + `} + transform: translate(-50%, -50%) rotate(${({ index }) => index * 30}deg) + translate(0, -88px) rotate(${({ index }) => -index * 30}deg); +`; \ No newline at end of file diff --git a/packages/yak-swc/yak_swc/tests/fixture/parens/output.dev.tsx b/packages/yak-swc/yak_swc/tests/fixture/parens/output.dev.tsx new file mode 100644 index 00000000..d96c2756 --- /dev/null +++ b/packages/yak-swc/yak_swc/tests/fixture/parens/output.dev.tsx @@ -0,0 +1,19 @@ +import { styled, css, __yak_unitPostFix } from "next-yak/internal"; +import __styleYak from "./input.yak.module.css!=!./input?./input.yak.module.css"; +export const Card = /*YAK Extracted CSS: +.Card { + background: url("/card-bg.jpg") no-repeat; +} +.Card__$active { + backgorund: url(/card-bg-active.jpg) no-repeat; +} +.Card { + transform: translate(-50%, -50%) rotate(var(--Card__transform_m7uBBu)) +translate(0, -88px) rotate(var(--Card__transform_m7uBBu-01)); +} +*/ /*#__PURE__*/ styled.div(__styleYak.Card, ({ $active })=>$active && /*#__PURE__*/ css(__styleYak.Card__$active), { + "style": { + "--Card__transform_m7uBBu": __yak_unitPostFix(({ index })=>index * 30, "deg"), + "--Card__transform_m7uBBu-01": __yak_unitPostFix(({ index })=>-index * 30, "deg") + } +}); diff --git a/packages/yak-swc/yak_swc/tests/fixture/parens/output.prod.tsx b/packages/yak-swc/yak_swc/tests/fixture/parens/output.prod.tsx new file mode 100644 index 00000000..66ae0b79 --- /dev/null +++ b/packages/yak-swc/yak_swc/tests/fixture/parens/output.prod.tsx @@ -0,0 +1,19 @@ +import { styled, css, __yak_unitPostFix } from "next-yak/internal"; +import __styleYak from "./input.yak.module.css!=!./input?./input.yak.module.css"; +export const Card = /*YAK Extracted CSS: +.Card { + background: url("/card-bg.jpg") no-repeat; +} +.Card__$active { + backgorund: url(/card-bg-active.jpg) no-repeat; +} +.Card { + transform: translate(-50%, -50%) rotate(var(--ym7uBBu)) +translate(0, -88px) rotate(var(--ym7uBBu1)); +} +*/ /*#__PURE__*/ styled.div(__styleYak.Card, ({ $active })=>$active && /*#__PURE__*/ css(__styleYak.Card__$active), { + "style": { + "--ym7uBBu": __yak_unitPostFix(({ index })=>index * 30, "deg"), + "--ym7uBBu1": __yak_unitPostFix(({ index })=>-index * 30, "deg") + } +});