diff --git a/.changeset/famous-ghosts-roll.md b/.changeset/famous-ghosts-roll.md new file mode 100644 index 0000000000..3da89ca103 --- /dev/null +++ b/.changeset/famous-ghosts-roll.md @@ -0,0 +1,7 @@ +--- +'@emotion/cache': patch +'@emotion/css': patch +'@emotion/react': patch +--- + +Fixed an edge case issue with incorrect rules being generated. When a context selector (`&`) was used not at the beginning of a selector (which is not valid SCSS but is allowed by the Stylis parser that we are using) within a group of selectors containing a pseudoclass then it was not replaced correctly with the current context selector. diff --git a/packages/babel-plugin/package.json b/packages/babel-plugin/package.json index fea2354535..de8b594f23 100644 --- a/packages/babel-plugin/package.json +++ b/packages/babel-plugin/package.json @@ -24,7 +24,7 @@ "escape-string-regexp": "^4.0.0", "find-root": "^1.1.0", "source-map": "^0.5.7", - "stylis": "^4.0.3" + "stylis": "^4.0.10" }, "peerDependencies": { "@babel/core": "^7.0.0" diff --git a/packages/cache/package.json b/packages/cache/package.json index 736a6f0f88..f29734c00b 100644 --- a/packages/cache/package.json +++ b/packages/cache/package.json @@ -19,7 +19,7 @@ "@emotion/sheet": "^1.0.0", "@emotion/utils": "^1.0.0", "@emotion/weak-memoize": "^0.2.5", - "stylis": "^4.0.3" + "stylis": "^4.0.10" }, "devDependencies": { "@emotion/hash": "*", diff --git a/packages/cache/src/stylis-plugins.js b/packages/cache/src/stylis-plugins.js index 62e5904c1f..b080e6db86 100644 --- a/packages/cache/src/stylis-plugins.js +++ b/packages/cache/src/stylis-plugins.js @@ -7,13 +7,37 @@ import { token, char, from, - identifier, peek, - position + position, + slice } from 'stylis' const last = arr => (arr.length ? arr[arr.length - 1] : null) +// based on https://github.com/thysultan/stylis.js/blob/e6843c373ebcbbfade25ebcc23f540ed8508da0a/src/Tokenizer.js#L239-L244 +const identifierWithPointTracking = (begin, points, index) => { + let previous = 0 + let character = 0 + + while (true) { + previous = character + character = peek() + + // &\f + if (previous === 38 && character === 12) { + points[index] = 1 + } + + if (token(character)) { + break + } + + next() + } + + return slice(begin, position) +} + const toRules = (parsed, points) => { // pretend we've started with a comma let index = -1 @@ -30,7 +54,11 @@ const toRules = (parsed, points) => { // it's very unlikely for this sequence to actually appear in a different context, so we just leverage this fact here points[index] = 1 } - parsed[index] += identifier(position - 1) + parsed[index] += identifierWithPointTracking( + position - 1, + points, + index + ) break case 2: parsed[index] += delimit(character) diff --git a/packages/css-prettifier/package.json b/packages/css-prettifier/package.json index 10dcb73b13..78d08dba67 100644 --- a/packages/css-prettifier/package.json +++ b/packages/css-prettifier/package.json @@ -16,7 +16,7 @@ "repository": "https://github.com/emotion-js/emotion/tree/main/packages/css-prettifier", "dependencies": { "@emotion/memoize": "^0.7.4", - "stylis": "^4.0.3" + "stylis": "^4.0.10" }, "devDependencies": {}, "publishConfig": { diff --git a/packages/css/test/__snapshots__/selectivity.test.js.snap b/packages/css/test/__snapshots__/selectivity.test.js.snap index dbdd3f969c..1fad1643f1 100644 --- a/packages/css/test/__snapshots__/selectivity.test.js.snap +++ b/packages/css/test/__snapshots__/selectivity.test.js.snap @@ -124,6 +124,26 @@ exports[`css media query with nested selector without declarations on root 1`] = }" `; +exports[`css should allow a weird class containing & when pseudoclass appears in the selector group 1`] = ` +".css-13p6h3h:hover, +.css-13p6h3h .t\\\\&t { + background: blue; +}" +`; + +exports[`css should allow for context selector being appended to an element type 1`] = ` +"a.css-ciaq1 { + background: blue; +}" +`; + +exports[`css should allow for context selector being appended to an element type when pseudoclass appears in the selector group 1`] = ` +".css-miigdc:hover, +a.css-miigdc { + background: blue; +}" +`; + exports[`orphaned pseudos in nested atrules 1`] = ` "@media (max-width: 400px) { @supports (display: grid) { diff --git a/packages/css/test/selectivity.test.js b/packages/css/test/selectivity.test.js index 18586f8113..dd66dc32b0 100644 --- a/packages/css/test/selectivity.test.js +++ b/packages/css/test/selectivity.test.js @@ -117,6 +117,37 @@ describe('css', () => { ` expect(sheet).toMatchSnapshot() }) + + // this isn't compatible with SCSS but is allowed in Stylis + test('should allow for context selector being appended to an element type', () => { + css` + a& { + background: blue; + } + ` + expect(sheet).toMatchSnapshot() + }) + + // #2488 + test('should allow for context selector being appended to an element type when pseudoclass appears in the selector group', () => { + css` + &:hover, + a& { + background: blue; + } + ` + expect(sheet).toMatchSnapshot() + }) + + test('should allow a weird class containing & when pseudoclass appears in the selector group', () => { + css` + &:hover, + .t\\&t { + background: blue; + } + ` + expect(sheet).toMatchSnapshot() + }) }) describe('orphaned pseudos', () => { diff --git a/packages/jest/package.json b/packages/jest/package.json index ba705c039b..3a7bd14762 100644 --- a/packages/jest/package.json +++ b/packages/jest/package.json @@ -21,7 +21,7 @@ "@emotion/css-prettifier": "^1.0.0", "chalk": "^4.1.0", "specificity": "^0.4.1", - "stylis": "^4.0.3" + "stylis": "^4.0.10" }, "peerDependencies": { "@types/jest": "^26.0.14", diff --git a/yarn.lock b/yarn.lock index 6a8f6fe8df..e841eb4cc6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -26417,10 +26417,10 @@ stylehacks@^4.0.0: postcss "^7.0.0" postcss-selector-parser "^3.0.0" -stylis@^4.0.3: - version "4.0.3" - resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.0.3.tgz#0d714765f3f694a685550f0c45411ebf90a9bded" - integrity sha512-iAxdFyR9cHKp4H5M2dJlDnvcb/3TvPprzlKjvYVbH7Sh+y8hjY/mUu/ssdcvVz6Z4lKI3vsoS0jAkMYmX7ozfA== +stylis@^4.0.10: + version "4.0.10" + resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.0.10.tgz#446512d1097197ab3f02fb3c258358c3f7a14240" + integrity sha512-m3k+dk7QeJw660eIKRRn3xPF6uuvHs/FFzjX3HQ5ove0qYsiygoAhwn5a3IYKaZPo5LrYD0rfVmtv1gNY1uYwg== sudo-prompt@^8.2.0: version "8.2.5"