Skip to content

fix: $ dropped from attribute names - #336

Open
theRizwan wants to merge 1 commit into
postcss:mainfrom
theRizwan:fix/dollar-in-attribute-name
Open

fix: $ dropped from attribute names#336
theRizwan wants to merge 1 commit into
postcss:mainfrom
theRizwan:fix/dollar-in-attribute-name

Conversation

@theRizwan

Copy link
Copy Markdown
Contributor

Fixes #211. Fixes #306.

Both reports are the same defect seen through different syntax.

The bug

parser().astSync("[#{$attr}]").toString();   // "[#{attr}]"
parser().astSync("[$attr]").toString();      // "[attr]"
parser().astSync("[#{$a}=#{$b}]").toString() // "[#{a}=#{$b}]"

The $ is dropped when it belongs to the attribute name. The value position already worked, which is why [lang=#{$locale}] round-trips in the existing tests while [#{$attr}] does not.

Cause

attribute() has a branch for $ in the value position, and the tokens.caret case immediately below turns $ into the suffix match operator when the next token is =. Nothing handled a $ that is part of the attribute name, so it fell through to tokens.caret, failed the = check, and was discarded without being recorded anywhere.

The new branch mirrors the value-position handling, and is guarded on the next token not being =.

Why the guard matters

Without it, $= stops parsing as an operator. With it, a name and an operator can coexist:

parser().astSync('[$attr$="x"]')
// attribute "$attr", operator "$=", value "x"

[a$=x] and [data-weird-attr$="Something=weird"] are unaffected, and both remain covered by the existing tests.

Verification

Compared against main across 4,800 generated attribute selectors, varying the name, namespace, operator, value, quoting, insensitivity flag and padding:

newly round-tripping 991
regressions (round-tripped before, broken now) 0
parsed before, throws now 0
raw TypeError 45 → 30

The TypeError reduction is a side effect rather than the aim: a trailing $ such as [a$] now returns before reaching the unguarded next[TOKEN.TYPE] lookahead in tokens.caret. That lookahead is the subject of #334 and is not otherwise touched here.

npm test passes, including oxlint, the type check and the coverage thresholds: 94.99% lines, 95.41% branches, 97.71% functions against gates of 94/94/96. Test count 789 to 799.

Tests

Added to nonstandard.mjs, next to the existing sass escapes cases, since these arrive through preprocessor interpolation: interpolated attribute name, bare $ name, interpolation on both sides, a $ name combined with $=, and a namespaced $ name.

Not addressed

[#{$attr} i] still drops the insensitivity flag, both before and after this change. The flag survives only when an operator is present, which is a separate defect and left alone.

`attribute()` handled `$` in the value position, and treated it as the suffix
match operator when followed by `=`, but had no branch for a `$` that belongs to
the attribute name. Those fell through to the `tokens.caret` case, which only
acts when the next token is `=`, so the character was silently discarded:

    parser().astSync("[#{$attr}]").toString()   // "[#{attr}]"
    parser().astSync("[$attr]").toString()      // "[$attr]" -> "[attr]"

Preprocessors reach this through interpolation, which is how both reports arrived.
The value position already worked, so `[lang=#{$locale}]` round-tripped while
`[#{$attr}]` did not.

The new branch mirrors the existing value-position handling and is guarded on the
next token not being `=`, which keeps the suffix match operator intact. That guard
is what allows a name and an operator to coexist:

    [$attr$="x"]   attribute "$attr", operator "$=", value "x"

Compared against main across 4,800 generated attribute selectors: 991 newly
round-trip, 0 regressions, and none that previously parsed now throw. A trailing
`$` such as `[a$]` also stops raising a raw TypeError as a side effect, since the
new branch returns before the unguarded lookahead is reached.

Fixes postcss#211
Fixes postcss#306
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.

SCSS interpreter in attribute lost $ can't parse attribute with scss expression

1 participant