From a77637ed924e5d4c09d442b878f4f0cf27f53f79 Mon Sep 17 00:00:00 2001 From: Romain Menke Date: Thu, 3 Nov 2022 19:29:28 +0100 Subject: [PATCH] postcss-nesting: fix pseudo's and allow ampersand everywhere --- experimental/postcss-nesting/.tape.mjs | 3 ++ experimental/postcss-nesting/CHANGELOG.md | 15 ++++++++++ experimental/postcss-nesting/README.md | 15 ++++++++-- experimental/postcss-nesting/src/index.ts | 5 ++++ .../src/lib/ampersand-to-scope.ts | 25 +++++++++++++++++ .../lib/merge-selectors/merge-selectors.ts | 6 ---- .../test/ampersand-everywhere.css | 28 +++++++++++++++++++ .../test/ampersand-everywhere.expect.css | 28 +++++++++++++++++++ .../postcss-nesting/test/examples/example.css | 4 +++ .../test/examples/example.expect.css | 6 ++++ .../postcss-nesting/test/ignore.expect.css | 2 +- .../test/relative-selectors.expect.css | 4 +-- 12 files changed, 129 insertions(+), 12 deletions(-) create mode 100644 experimental/postcss-nesting/src/lib/ampersand-to-scope.ts create mode 100644 experimental/postcss-nesting/test/ampersand-everywhere.css create mode 100644 experimental/postcss-nesting/test/ampersand-everywhere.expect.css diff --git a/experimental/postcss-nesting/.tape.mjs b/experimental/postcss-nesting/.tape.mjs index 276cdc28c..22adda690 100644 --- a/experimental/postcss-nesting/.tape.mjs +++ b/experimental/postcss-nesting/.tape.mjs @@ -92,4 +92,7 @@ postcssTape(plugin)({ 'relative-selectors': { message: 'examples of relative selectors', }, + 'ampersand-everywhere': { + message: 'allow & to be used everywhere', + } }); diff --git a/experimental/postcss-nesting/CHANGELOG.md b/experimental/postcss-nesting/CHANGELOG.md index 06fd37e6c..4d73c526a 100644 --- a/experimental/postcss-nesting/CHANGELOG.md +++ b/experimental/postcss-nesting/CHANGELOG.md @@ -1,5 +1,20 @@ # Changes to PostCSS Nesting Experimental +### Unreleased + +- match Chrome's behavior for pseudo elements. +- add support for `&` everywhere. + +```css +.foo { + ::before {} +} + +/* becomes */ + +.foo ::before {} +``` + ### 1.0.0 (October 29, 2022) Implement the 28 October version of [css-nestng-1](https://drafts.csswg.org/css-nesting/) diff --git a/experimental/postcss-nesting/README.md b/experimental/postcss-nesting/README.md index 2169621c0..e43ec8fd3 100644 --- a/experimental/postcss-nesting/README.md +++ b/experimental/postcss-nesting/README.md @@ -23,6 +23,11 @@ a, b { } } +& { + color: pink; +} + + /* becomes */ a, b { @@ -30,11 +35,15 @@ a, b { } :is(a,b) c, :is(a,b) d { - color: white; -} + color: white; + } :is(e) :is(a,b) { - color: yellow; + color: yellow; + } + +:scope { + color: pink; } ``` diff --git a/experimental/postcss-nesting/src/index.ts b/experimental/postcss-nesting/src/index.ts index 57c5ea98f..5491d40ca 100644 --- a/experimental/postcss-nesting/src/index.ts +++ b/experimental/postcss-nesting/src/index.ts @@ -1,4 +1,5 @@ import type { PluginCreator } from 'postcss'; +import ampersandToScope from './lib/ampersand-to-scope.js'; import walk from './lib/walk.js'; const creator: PluginCreator = () => { @@ -6,6 +7,10 @@ const creator: PluginCreator = () => { postcssPlugin: 'postcss-nesting', Rule(rule, { result }) { walk(rule, result); + + if (rule.selector.trim().includes('&')) { + ampersandToScope(rule); + } }, }; }; diff --git a/experimental/postcss-nesting/src/lib/ampersand-to-scope.ts b/experimental/postcss-nesting/src/lib/ampersand-to-scope.ts new file mode 100644 index 000000000..f86df2cd2 --- /dev/null +++ b/experimental/postcss-nesting/src/lib/ampersand-to-scope.ts @@ -0,0 +1,25 @@ +import type { Container, Node, Rule } from 'postcss'; +import parser from 'postcss-selector-parser'; + +export default function ampersandToScope(rule: Rule) { + let parent: Container = rule.parent; + + while (parent) { + if (parent.type === 'rule') { + return; + } + + parent = parent.parent; + } + + const selectorAST = parser().astSync(rule.selector); + if (!selectorAST) { + return; + } + + selectorAST.walkNesting((nesting) => { + nesting.replaceWith(parser.pseudo({value: ':scope'})); + }); + + rule.selector = selectorAST.toString(); +} diff --git a/experimental/postcss-nesting/src/lib/merge-selectors/merge-selectors.ts b/experimental/postcss-nesting/src/lib/merge-selectors/merge-selectors.ts index 8e8ac9dd9..44db02e73 100644 --- a/experimental/postcss-nesting/src/lib/merge-selectors/merge-selectors.ts +++ b/experimental/postcss-nesting/src/lib/merge-selectors/merge-selectors.ts @@ -31,12 +31,6 @@ export default function mergeSelectors(node: Node, postcssResult: Result, fromSe return false; } - // see : https://github.com/w3c/csswg-drafts/issues/7979 - if (parser.isPseudoElement(maybeCombinator)) { - startsWithCombinator = true; - return false; - } - return false; }); diff --git a/experimental/postcss-nesting/test/ampersand-everywhere.css b/experimental/postcss-nesting/test/ampersand-everywhere.css new file mode 100644 index 000000000..8a4badcfe --- /dev/null +++ b/experimental/postcss-nesting/test/ampersand-everywhere.css @@ -0,0 +1,28 @@ +& { + order: 1; +} + +.foo & { + order: 2; +} + + +.foo:has(&, :not(&)) { + order: 3; +} + +&& + & { + order: 4; +} + +@media screen { + & { + order: 5; + } +} + +@container (.foo) { + & { + order: 6; + } +} diff --git a/experimental/postcss-nesting/test/ampersand-everywhere.expect.css b/experimental/postcss-nesting/test/ampersand-everywhere.expect.css new file mode 100644 index 000000000..005622056 --- /dev/null +++ b/experimental/postcss-nesting/test/ampersand-everywhere.expect.css @@ -0,0 +1,28 @@ +:scope { + order: 1; +} + +.foo :scope { + order: 2; +} + + +.foo:has(:scope, :not(:scope)) { + order: 3; +} + +:scope:scope + :scope { + order: 4; +} + +@media screen { + :scope { + order: 5; + } +} + +@container (.foo) { + :scope { + order: 6; + } +} diff --git a/experimental/postcss-nesting/test/examples/example.css b/experimental/postcss-nesting/test/examples/example.css index 02e29f9d6..6efe34dae 100644 --- a/experimental/postcss-nesting/test/examples/example.css +++ b/experimental/postcss-nesting/test/examples/example.css @@ -9,3 +9,7 @@ a, b { color: yellow; } } + +& { + color: pink; +} diff --git a/experimental/postcss-nesting/test/examples/example.expect.css b/experimental/postcss-nesting/test/examples/example.expect.css index ba54560b1..8c3000be1 100644 --- a/experimental/postcss-nesting/test/examples/example.expect.css +++ b/experimental/postcss-nesting/test/examples/example.expect.css @@ -1,9 +1,15 @@ a, b { color: red; } + :is(a,b) c, :is(a,b) d { color: white; } + :is(e) :is(a,b) { color: yellow; } + +:scope { + color: pink; +} diff --git a/experimental/postcss-nesting/test/ignore.expect.css b/experimental/postcss-nesting/test/ignore.expect.css index f37c478e1..525aa13e4 100644 --- a/experimental/postcss-nesting/test/ignore.expect.css +++ b/experimental/postcss-nesting/test/ignore.expect.css @@ -6,7 +6,7 @@ a, b { order: 2; } -& e { +:scope e { order: 3; } diff --git a/experimental/postcss-nesting/test/relative-selectors.expect.css b/experimental/postcss-nesting/test/relative-selectors.expect.css index 4e447aa6a..31179ad20 100644 --- a/experimental/postcss-nesting/test/relative-selectors.expect.css +++ b/experimental/postcss-nesting/test/relative-selectors.expect.css @@ -19,7 +19,7 @@ } -:is(.foo)::before { +:is(.foo) ::before { order: 5; } @@ -44,6 +44,6 @@ } -:is(.foo)::before, :is(.foo) .other { +:is(.foo) ::before, :is(.foo) .other { order: 15; }