Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 63 additions & 0 deletions src/parse-selector.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2851,6 +2851,69 @@ describe('Selector Nodes', () => {
})
})

describe('Dashed idents in pseudo-element functions', () => {
it('should not parse dashed ident as type selector in ::view-transition-new()', () => {
const root = parse_selector('::view-transition-new(--light-box-img)')
const selector = root.first_child
const pseudoElement = selector?.first_child
expect(pseudoElement?.type).toBe(PSEUDO_ELEMENT_SELECTOR)
expect(pseudoElement?.name).toBe('view-transition-new')
// The dashed ident should NOT produce a TYPE_SELECTOR child
const child = pseudoElement?.first_child
expect(child?.type).not.toBe(TYPE_SELECTOR)
})

it('should not parse dashed ident as type selector in ::view-transition-old()', () => {
const root = parse_selector('::view-transition-old(--light-box-img)')
const selector = root.first_child
const pseudoElement = selector?.first_child
expect(pseudoElement?.type).toBe(PSEUDO_ELEMENT_SELECTOR)
expect(pseudoElement?.name).toBe('view-transition-old')
const child = pseudoElement?.first_child
expect(child?.type).not.toBe(TYPE_SELECTOR)
})

it('should parse selector list with multiple view-transition pseudo-elements with dashed idents', () => {
const root = parse_selector(
'::view-transition-new(--light-box-img), ::view-transition-old(--light-box-img)',
)
expect(root.children.length).toBe(2)
const [sel1, sel2] = root.children
const pe1 = sel1?.first_child
const pe2 = sel2?.first_child
expect(pe1?.type).toBe(PSEUDO_ELEMENT_SELECTOR)
expect(pe1?.name).toBe('view-transition-new')
expect(pe1?.first_child?.type).not.toBe(TYPE_SELECTOR)
expect(pe2?.type).toBe(PSEUDO_ELEMENT_SELECTOR)
expect(pe2?.name).toBe('view-transition-old')
expect(pe2?.first_child?.type).not.toBe(TYPE_SELECTOR)
})

it('should still parse normal idents as type selectors inside pseudo-class functions', () => {
const root = parse_selector(':is(div, span)')
const selector = root.first_child
const pseudoClass = selector?.first_child
expect(pseudoClass?.type).toBe(PSEUDO_CLASS_SELECTOR)
expect(pseudoClass?.name).toBe('is')
// Normal idents inside :is() should still produce TYPE_SELECTOR nodes
const selectorList = pseudoClass?.first_child
const firstSel = selectorList?.first_child
expect(firstSel?.first_child?.type).toBe(TYPE_SELECTOR)
})

it('should not parse dashed ident as type selector in ::slotted()', () => {
// ::slotted() normally takes a compound selector, but if a dashed ident
// is used it should not be classified as a type selector
const root = parse_selector('::slotted(--custom)')
const selector = root.first_child
const pseudoElement = selector?.first_child
expect(pseudoElement?.type).toBe(PSEUDO_ELEMENT_SELECTOR)
expect(pseudoElement?.name).toBe('slotted')
const child = pseudoElement?.first_child
expect(child?.type).not.toBe(TYPE_SELECTOR)
})
})

describe('Multiline comments', () => {
it('should handle multiline comments in selectors', () => {
const root = parse_selector(`div
Expand Down
11 changes: 11 additions & 0 deletions src/parse-selector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ import {
CHAR_SINGLE_QUOTE,
CHAR_DOUBLE_QUOTE,
CHAR_COLON,
CHAR_MINUS_HYPHEN,
} from './string-utils'
import { ANplusBParser } from './parse-anplusb'
import { CSSNode } from './css-node'
Expand Down Expand Up @@ -411,6 +412,16 @@ export class SelectorParser {
// Parse type selector or namespace selector (ns|E or ns|*)
// Called when we've seen an IDENT token
private parse_type_or_namespace_selector(start: number, end: number): number | null {
// Dashed idents (--foo) are CSS custom property names and cannot be type selectors.
// They appear legitimately as arguments to pseudo-element functions like
// ::view-transition-new(--my-ident), so we must not classify them as TYPE_SELECTOR.
if (
this.source.charCodeAt(start) === CHAR_MINUS_HYPHEN &&
this.source.charCodeAt(start + 1) === CHAR_MINUS_HYPHEN
) {
return null
}

// Save position before skipping whitespace/comments
const saved = this.lexer.save_position()

Expand Down
Loading