Thanks to visit codestin.com
Credit goes to github.com

Skip to content
This repository was archived by the owner on Dec 28, 2021. It is now read-only.

Conversation

romainmenke
Copy link
Member

@romainmenke romainmenke commented Oct 30, 2021

This change was a bit trial and error and might contain issues.
Best to do a thorough review and question everything that looks weird or is unexpected.

⚠️ This is a breaking change for this plugin but it does make it spec compliant (or that is the intention)

In the context of postcss-preset-env there should be a moment where we can ship unaltered css to user agents, so it is important to fix spec issues imho.

RuleExit -> Rule

Processing rules outside in (used to be inside out) ensures that the parent is always fully resolved and doesn't contain any nesting itself.

This was needed as we now optionally wrap the parent selector in :is() to support :

  • body > h1, body p { & span {} } -> :is(body > h1, body p) span {}

If the parent selector contains nesting it would not be valid after wrapping in :is() :

  • & h1, & h2 { & span {} } -> :is(& h1, & h2) span {} (does not start with &)

First pass to split up and sanitise rules

The spec doesn't allow declarations after nesting :
https://www.w3.org/TR/css-nesting-1/#mixing

article {
  color: green;
  & { color: blue; }
  color: red; /* invalid */
}

This is easy to handle on its own before expanding nested css.

  • clone the rule before itself
  • for clone : strip all rule and at-rule nodes
  • for clone : strip all nodes after a rule or at-rule was encountered
  • for node : strip all nodes that aren't of type rule or at-rule

⚠️ Are there cases or syntax where this would remove things that should stay?

Rule within rule detect change :

- node.selectors.every((selector) => selector.trim().lastIndexOf('&') === 0 && validSelector.test(selector))
+ node.selectors.every((selector) => selector.trim().indexOf('&') === 0 && selector.indexOf('|') === -1))

⚠️ I might be wrong about this bit

A rule within rule selector is a valid if & is the first character and when it is a list if every selector in the list starts with &.

No other restrictions seem to exist.

A special provision for | exists. If | is encountered the rule is skipped.
This does not appear to be part of the spec. But this case existed before.

Nest rule within rule detect change :

- comma(node.params).every((selector) => selector.split('&').length >= 2 && validSelector.test(selector))
+ comma(node.params).every((selector) => selector.split('&').length >= 2 && selector.indexOf('|') === -1))

⚠️ I might be wrong about this bit

A nest rule within rule selector is a valid if & exists and when it is a list if every selector in the list has &.

No other restrictions seem to exist.

A special provision for | exists. If | is encountered the rule is skipped.
This does not appear to be part of the spec. But this case existed before.

Merge selectors change :

This is highly opinionated but I try to avoid .reduce() (actually banned it in our org).
It results in code that is difficult to grasp, difficult to refactor and prone to bugs.

  • no more .reduce()
  • parts of the child selectors are now wrapped in :is() when inserting the parent would alter or break the selector.
  • parent selectors are now wrapped in :is() when it is a list of selectors.

These regexp's are used to wrap the child selectors :

/&((?:[\w-_|])(?:[^\s,{]*))/g
/([\w-_|]+)(?:&)/g

Tested with examples like these :

foo &foo foo & baz
&foo{
&foo,&baz {
&foo:is(.foo)
.foo&
.foo& .baz &

⚠️ There might be cases I overlooked, best to verify this.

test/direct.expect.css

This file has a lot of changes because we now wrap with :is().
The code is a bit too dense for me to be sure that all changes are correct and that no bugs were introduced here.

@Antonio-Laguna
Copy link
Member

Antonio-Laguna commented Oct 30, 2021

@romainmenke nice work! I've started reviewing this and tests don't run for me. Only lint works and it throws on TypeError: toSelector.replaceAll is not a function

replaceAll was introduced on Node 15. If we want to keep compatibility with version 12 we'll have to use an alternative.

@romainmenke romainmenke force-pushed the fix-spec-issues--witty-giant-schnauzer-c06ca8bda8 branch from 99dcd6c to 7e44004 Compare October 30, 2021 12:02
@romainmenke romainmenke force-pushed the fix-spec-issues--witty-giant-schnauzer-c06ca8bda8 branch from 7e44004 to e5f3ee2 Compare October 30, 2021 12:03
@romainmenke
Copy link
Member Author

@Antonio-Laguna Thank you for checking!

I updated it to just use replace() as these are regexp op's with a g flag anyway.
I also added a github workflow to build and test in all current LTS node versions.

Comment on lines +12 to +24
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: 16

- run: npm install --ignore-scripts
- run: npm run build

- uses: actions/setup-node@v2
with:
node-version: ${{ matrix.node }}
- run: npm install --ignore-scripts
- run: npm run test
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Build fails in node 12, but this is fine.
We only support older node versions externally, not persé for building the plugin itself.

By switching node version and running install again we can build in node 16 and then test in node X

@Antonio-Laguna
Copy link
Member

@romainmenke this is solid work. I've tested against all odd cases and it seems that produces spec complaint code :)

}

// foo &foo foo & baz -> foo &:is(foo) foo & baz
toSelector = toSelector.replace(/&((?:[\w-_|])(?:[^\s,{]*))/g, (match, p1) => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we’re open to regular expressions, then we should use a tokenizer. I will try to make a PR to this branch today. I expect to start on the work in about 5 hours.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jonathantneal Maybe with postcss-selector-parser?

romainmenke#1

Might be too big a hammer, but could be easier to maintain than another tokenizer :)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was off by several hours, because I forgot it’s the day we all hand out treats to the neighborhood kids!

Copy link
Collaborator

@jonathantneal jonathantneal left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh so good!

@jonathantneal jonathantneal merged commit 2ee61c4 into csstools:main Nov 3, 2021
@romainmenke romainmenke deleted the fix-spec-issues--witty-giant-schnauzer-c06ca8bda8 branch November 3, 2021 12:31
romainmenke added a commit to csstools/postcss-plugins that referenced this pull request Nov 16, 2021
* fix spec issues

* also fix nesting rules

* one more test

* cleanup

* more fixes :

- fix csstools/postcss-nesting#87
- add test to ensure that csstools/postcss-nesting#85 was fixed

* update CI
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants