From de95bcdb35938f3282dcf5532027a8a53299ebab Mon Sep 17 00:00:00 2001 From: Adam Skoufis Date: Tue, 19 Nov 2024 15:53:20 +1100 Subject: [PATCH] Fix buggy selector transformation --- .changeset/flat-humans-hammer.md | 7 ++++ packages/css/src/transformCss.test.ts | 55 +++++++++++++++++++++++++++ packages/css/src/transformCss.ts | 14 ++++--- 3 files changed, 71 insertions(+), 5 deletions(-) create mode 100644 .changeset/flat-humans-hammer.md diff --git a/.changeset/flat-humans-hammer.md b/.changeset/flat-humans-hammer.md new file mode 100644 index 00000000..4fe3765c --- /dev/null +++ b/.changeset/flat-humans-hammer.md @@ -0,0 +1,7 @@ +--- +'@vanilla-extract/css': patch +--- + +Fixes a bug causing invalid selectors to be generated in some cases + +When transforming selectors containing consective copies of the same classname, e.g: `&&`, if `&` was a substring of another local classname and that classname happened to end with the same character that `&` started with, the generated selector would be invalid. diff --git a/packages/css/src/transformCss.test.ts b/packages/css/src/transformCss.test.ts index fe9fa25c..f152c871 100644 --- a/packages/css/src/transformCss.test.ts +++ b/packages/css/src/transformCss.test.ts @@ -2299,6 +2299,61 @@ describe('transformCss', () => { } `); }); + + it('should handle consecutive references to the same classname that is a substring of another classname with the same start and end character', () => { + const classname1 = 'debugName_hash1'; + const classname2 = 'debugName_hash1d'; + + expect( + transformCss({ + composedClassLists: [], + localClassNames: [classname1, classname2], + + cssObjs: [ + { + type: 'local', + selector: classname1, + rule: { + selectors: { + // Bug reproduction + ['&&']: { + background: 'black', + }, + // Exhaustive tests for combinations of two adjacent classnames + [`${classname2}&`]: { + background: 'orange', + }, + [`&${classname2}&`]: { + background: 'orange', + }, + [`${classname2}${classname2}&`]: { + background: 'orange', + }, + }, + }, + }, + { + type: 'local', + selector: classname2, + rule: {}, + }, + ], + }).join('\n'), + ).toMatchInlineSnapshot(` + .debugName_hash1.debugName_hash1 { + background: black; + } + .debugName_hash1d.debugName_hash1 { + background: orange; + } + .debugName_hash1.debugName_hash1d.debugName_hash1 { + background: orange; + } + .debugName_hash1d.debugName_hash1d.debugName_hash1 { + background: orange; + } + `); + }); }); endFileScope(); diff --git a/packages/css/src/transformCss.ts b/packages/css/src/transformCss.ts index 6b0248ee..0c11a64b 100644 --- a/packages/css/src/transformCss.ts +++ b/packages/css/src/transformCss.ts @@ -317,11 +317,15 @@ class Stylesheet { const [endIndex, [firstMatch]] = results[i]; const startIndex = endIndex - firstMatch.length + 1; - if (startIndex >= lastReplaceIndex) { - // Class names can be substrings of other class names - // e.g. '_1g1ptzo1' and '_1g1ptzo10' - // If the startIndex >= lastReplaceIndex, then - // this is the case and this replace should be skipped + // Class names can be substrings of other class names + // e.g. '_1g1ptzo1' and '_1g1ptzo10' + // Additionally, concatenated classnames can be substrings of other classnames + // 'debugName_18fxj81' and 'debugName_18fxj81d' + // If this is the case then this replace should be skipped + const match = + startIndex >= lastReplaceIndex || endIndex >= lastReplaceIndex; + + if (match) { continue; }