Skip to content

v0.19.0

Choose a tag to compare

@bartveneman bartveneman released this 21 Oct 18:13
· 12 commits to main since this release
Immutable release. Only release title and notes can be modified.
b489a96

What's Changed

  • fix: parse nested boolean groups in @supports and style() into child nodes by @bartveneman in #319
  • feat!: wrap @import layer() and supports() in a Function node by @bartveneman in #318
  • feat!: put the lowercase argument first in str_starts_with and str_index_of by @bartveneman in #320
  • feat!: rename next_token_fast to next_token and drop the Token-returning next_token by @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 a Token | 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 by TOKEN_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.

  1. Search your code for str_starts_with( and str_index_of(.

  2. 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
  3. The first argument must be lowercase. Matching is still case-insensitive on the second argument.

  4. str_equals and str_equals_range are 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 layer is still a top-level LayerName with name === ''. Only layer(…) is wrapped, so handle both.
  • layer() has no children, so first_child is null.
  • Don't assume a particular child type for supports(). It holds either a single SupportsQuery or the compound-condition nodes.
  • The Function child type union now includes LayerName, so first_child narrows 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, .start and .end are unchanged, and simple declarations keep their shape.
  • Code that treats "no children" as "unparsed, handle the raw text myself" will now see children.
  • SupportsQuery children are now typed SupportsDeclaration | SupportsQuery | PreludeOperator | Function. Code that assumed children[0] is a SupportsDeclaration without narrowing may need updating.