Skip to content

fix: do not reuse a pseudo's name as the namespace of a following | - #358

Open
theRizwan wants to merge 1 commit into
postcss:mainfrom
theRizwan:fix/pipe-after-pseudo
Open

theRizwan wants to merge 1 commit into
postcss:mainfrom
theRizwan:fix/pipe-after-pseudo

Conversation

@theRizwan

Copy link
Copy Markdown
Contributor

The bug

A | straight after a pseudo reuses the pseudo's name as its namespace, so the name is printed twice:

parser().processSync(":hover|a");    // ":hoverhover|a"
parser().processSync("a:hover|b");   // "a:hoverhover|b"
parser().processSync("::before|*");  // "::beforebefore|*"

Every parse and print adds another copy, so running the output back through the parser keeps growing it: :hovernshoverns|a, then :hovernshovernshovernshoverns|a.

pseudo() consumes the word hover through splitWord(). The loop then reaches the | and calls namespace() through combinator(), and namespace() looks back at the previous token, which is that same hover word, and uses it again as the prefix.

The fix

When word() or universal() see a | next, the token before it is still theirs, so the lookback is right. When the | is reached from the main loop instead, the token before it has already been consumed by something else, and only a skipped & (from nesting()) can still be a prefix. namespace() now takes a prefixed flag, and combinator() passes false.

:hover|a now parses as the pseudo followed by |a, a tag with an empty namespace. That is what [x]|a already gives, since ] is not a prefix token either.

Verification

Two tests in namespaces.mjs, one for |b after a pseudo-class and one for |* after a pseudo-element. All four of their checks fail on main.

I also ran a lossless round trip over 50,000 random selectors built from common fragments. Lossy output that changes when printed and parsed again went from 127 cases to 21, and none of the 21 involve a pseudo. They come from the Sass and Less interpolation spelling.

npm test is green: lint, type check, and coverage gates.

`:hover|a` printed as `:hoverhover|a`. The pseudo consumed the word
`hover`, and then `namespace()` looked back at that same token and used it
again as the prefix of `|a`. Each parse and print added another copy.

When the `|` is reached from the main loop, the token before it has
already been consumed, so only a skipped `&` can still be a prefix. The
`|` now starts an empty namespace there, the way it already does after
`]` in `[x]|a`.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant