Repository navigation
v0.19.0
What's Changed
- fix: parse nested boolean groups in
@supportsandstyle()into child nodes by @bartveneman in #319 - feat!: wrap
@import layer()andsupports()in a Function node by @bartveneman in #318 - feat!: put the lowercase argument first in
str_starts_withandstr_index_ofby @bartveneman in #320 - feat!: rename
next_token_fasttonext_tokenand drop the Token-returningnext_tokenby @bartveneman in #321
Full Changelog: v0.18.12...v0.19.0
Breaking changes
Lexer.next_token() now returns a TokenType number (#321)
next_token_fast() is renamed to next_token(). The old next_token(), which allocated and returned a Token, is removed. This only affects code that uses Lexer directly (the @projectwallace/css-parser/tokenizer subpath). tokenize() still yields Token objects, so its users need no changes.
Migration
-
If you called
next_token_fast(), rename the call:// before const type = lexer.next_token_fast(skip_whitespace) // after const type = lexer.next_token(skip_whitespace)
-
If you called the old
next_token(), it returned aToken | null. It now returns the token type, and the position is on the lexer (token_start,token_end,token_line,token_column):// before const token = lexer.next_token() if (token?.type === TOKEN_IDENT) { const text = source.slice(token.start, token.end) } // after const type = lexer.next_token() if (type === TOKEN_IDENT) { const text = source.slice(lexer.token_start, lexer.token_end) }
If you need a standalone object, build it yourself:
const token = { type: lexer.next_token(), start: lexer.token_start, end: lexer.token_end, line: lexer.token_line, column: lexer.token_column, }
The old method never actually returned
null. End of input is signalled byTOKEN_EOF, as before.
str_starts_with and str_index_of argument order flipped (#320)
Both functions now take the lowercase argument first, matching str_equals(lowercase, str). They are exported from the package root and from @projectwallace/css-parser/string-utils.
| Function | Before | After |
|---|---|---|
str_starts_with |
str_starts_with(str, prefix) |
str_starts_with(prefix, str) |
str_index_of |
str_index_of(str, searchChar) |
str_index_of(searchChar, str) |
Migration
Both signatures are (string, string), so TypeScript will not flag callers that still use the old order. Update each call site by hand.
-
Search your code for
str_starts_with(andstr_index_of(. -
Swap the two arguments so the lowercase string comes first:
-str_starts_with('WEBKIT-transform', 'webkit') // true +str_starts_with('webkit', 'WEBKIT-transform') // true -str_index_of('background-COLOR', 'color') // 11 +str_index_of('color', 'background-COLOR') // 11
-
The first argument must be lowercase. Matching is still case-insensitive on the second argument.
-
str_equalsandstr_equals_rangeare unchanged.
@import layer() and supports() are now Function nodes (#318)
In an @import prelude, layer(...) and supports(...) used to be LayerName and SupportsQuery nodes. They are now Function nodes. This affects anyone who matches LAYER_NAME or SUPPORTS_QUERY on the direct children of an @import prelude. @layer and @supports preludes are unchanged.
| Source | Before | After |
|---|---|---|
layer(a.b) |
LayerName with name='a.b', text='layer(a.b)' |
Function with name='layer', value='a.b' and a child LayerName (name='a.b', text='a.b') |
layer() |
LayerName with name='' |
Function with name='layer' and no children |
layer (bare) |
LayerName with name='' |
unchanged |
supports(display: grid) |
SupportsQuery with value='display: grid', text='supports(display: grid)' and a SupportsDeclaration child |
Function with name='supports', value='display: grid' and a child SupportsQuery (text='display: grid') containing the SupportsDeclaration |
supports((a: b) and (c: d)) |
SupportsQuery with a raw value and no children |
Function with name='supports'. Its children match an @supports (a: b) and (c: d) prelude: SupportsQuery, Operator, SupportsQuery |
The Function owns the outer supports( and ). A SupportsQuery only owns parentheses that are in the source.
Migration
// before
for (const node of prelude.children) {
if (node.type === LAYER_NAME) {
use(node.name) // 'test.me'
}
if (node.type === SUPPORTS_QUERY) {
use(node.value) // 'display: grid'
}
}
// after
for (const node of prelude.children) {
if (node.type === FUNCTION && node.name === 'layer') {
const layer = node.first_child // null for layer()
use(layer?.name) // 'test.me'
} else if (node.type === LAYER_NAME) {
// bare `layer` (name is '')
}
if (node.type === FUNCTION && node.name === 'supports') {
use(node.value) // 'display: grid' (raw text inside the parentheses)
const query = node.first_child // SupportsQuery, or the condition's first node
}
}- Bare
layeris still a top-levelLayerNamewithname === ''. Onlylayer(…)is wrapped, so handle both. layer()has no children, sofirst_childisnull.- Don't assume a particular child type for
supports(). It holds either a singleSupportsQueryor the compound-condition nodes. - The
Functionchild type union now includesLayerName, sofirst_childnarrows correctly in TypeScript. - No new node types were added and node type numbers are unchanged.
Also worth noting (not breaking)
#319 parses nested boolean groups in @supports and style() into child nodes. For example, ((display:grid) and (display:flex)) used to be a leaf SupportsQuery. It now has children SupportsQuery, Operator(and), SupportsQuery. The same applies to @container style((--a:1) and (--b:2)).
- Only children are added.
.text,.value,.startand.endare unchanged, and simple declarations keep their shape. - Code that treats "no children" as "unparsed, handle the raw text myself" will now see children.
SupportsQuerychildren are now typedSupportsDeclaration | SupportsQuery | PreludeOperator | Function. Code that assumedchildren[0]is aSupportsDeclarationwithout narrowing may need updating.