Skip to content

Commit 09e81a2

Browse files
committed
Update to match CSS selectors 4
This commit updates `css-selector-parser`, which is completely different. You’ll get some different errors if you do weird things. Otherwise, this changes: * change to throw on empty selector * change to on invalid attribute selectors such as `[a=b,c]`, use `[a="b,c"]` instead * remove `any`, `matches`: use `:is()` instead
1 parent 5a09a89 commit 09e81a2

14 files changed

+274
-288
lines changed

index.js

+5-2
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,15 @@
22
* @typedef {import('unist').Position} Position
33
* @typedef {import('unist').Node} Node
44
* @typedef {import('./lib/types.js').SelectState} SelectState
5+
*/
6+
7+
/**
58
* @typedef {Record<string, unknown> & {type: string, position?: Position | undefined}} NodeLike
69
*/
710

8-
import {queryToSelectors, walk} from './lib/walk.js'
911
import {parse} from './lib/parse.js'
1012
import {parent} from './lib/util.js'
13+
import {walk} from './lib/walk.js'
1114

1215
/**
1316
* Check that the given `node` matches `selector`.
@@ -85,7 +88,7 @@ export function selectAll(selector, tree) {
8588
function createState(selector, tree) {
8689
return {
8790
// State of the query.
88-
rootQuery: queryToSelectors(parse(selector)),
91+
rootQuery: parse(selector),
8992
results: [],
9093
scopeNodes: tree
9194
? parent(tree) &&

lib/attribute.js

+46-19
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
/**
2-
* @typedef {import('./types.js').Rule} Rule
3-
* @typedef {import('./types.js').RuleAttr} RuleAttr
2+
* @typedef {import('css-selector-parser').AstAttribute} AstAttribute
3+
* @typedef {import('css-selector-parser').AstRule} AstRule
44
* @typedef {import('./types.js').Node} Node
55
*/
66

7+
import {unreachable} from 'devlop'
78
import {zwitch} from 'zwitch'
89

9-
/** @type {(query: RuleAttr, node: Node) => boolean} */
10+
/** @type {(query: AstAttribute, node: Node) => boolean} */
1011
const handle = zwitch('operator', {
1112
unknown: unknownOperator,
1213
// @ts-expect-error: hush.
@@ -21,15 +22,17 @@ const handle = zwitch('operator', {
2122
})
2223

2324
/**
24-
* @param {Rule} query
25+
* @param {AstRule} query
2526
* @param {Node} node
2627
* @returns {boolean}
2728
*/
2829
export function attribute(query, node) {
2930
let index = -1
3031

31-
while (++index < query.attrs.length) {
32-
if (!handle(query.attrs[index], node)) return false
32+
if (query.attributes) {
33+
while (++index < query.attributes.length) {
34+
if (!handle(query.attributes[index], node)) return false
35+
}
3336
}
3437

3538
return true
@@ -40,7 +43,7 @@ export function attribute(query, node) {
4043
*
4144
* `[attr]`
4245
*
43-
* @param {RuleAttr} query
46+
* @param {AstAttribute} query
4447
* @param {Node} node
4548
* @returns {boolean}
4649
*/
@@ -54,13 +57,15 @@ function exists(query, node) {
5457
*
5558
* `[attr=value]`
5659
*
57-
* @param {RuleAttr} query
60+
* @param {AstAttribute} query
5861
* @param {Node} node
5962
* @returns {boolean}
6063
*/
6164
function exact(query, node) {
65+
const queryValue = attributeValue(query)
66+
6267
// @ts-expect-error: Looks like a record.
63-
return exists(query, node) && String(node[query.name]) === query.value
68+
return exists(query, node) && String(node[query.name]) === queryValue
6469
}
6570

6671
/**
@@ -71,7 +76,7 @@ function exact(query, node) {
7176
*
7277
* `[attr~=value]`
7378
*
74-
* @param {RuleAttr} query
79+
* @param {AstAttribute} query
7580
* @param {Node} node
7681
* @returns {boolean}
7782
*/
@@ -82,36 +87,39 @@ function containsArray(query, node) {
8287

8388
if (value === null || value === undefined) return false
8489

85-
// If this is an array, and the query is contained in it, return true.
90+
const queryValue = attributeValue(query)
91+
92+
// If this is an array, and the query is contained in it, return `true`.
8693
// Coverage comment in place because TS turns `Array.isArray(unknown)`
8794
// into `Array<any>` instead of `Array<unknown>`.
8895
// type-coverage:ignore-next-line
89-
if (Array.isArray(value) && value.includes(query.value)) {
96+
if (Array.isArray(value) && value.includes(queryValue)) {
9097
return true
9198
}
9299

93100
// For all other values, return whether this is an exact match.
94-
return String(value) === query.value
101+
return String(value) === queryValue
95102
}
96103

97104
/**
98105
* Check whether an attribute has a substring as its start.
99106
*
100107
* `[attr^=value]`
101108
*
102-
* @param {RuleAttr} query
109+
* @param {AstAttribute} query
103110
* @param {Node} node
104111
* @returns {boolean}
105112
*/
106113
function begins(query, node) {
107114
/** @type {unknown} */
108115
// @ts-expect-error: Looks like a record.
109116
const value = node[query.name]
117+
const queryValue = attributeValue(query)
110118

111119
return Boolean(
112120
query.value &&
113121
typeof value === 'string' &&
114-
value.slice(0, query.value.length) === query.value
122+
value.slice(0, queryValue.length) === queryValue
115123
)
116124
}
117125

@@ -120,19 +128,20 @@ function begins(query, node) {
120128
*
121129
* `[attr$=value]`
122130
*
123-
* @param {RuleAttr} query
131+
* @param {AstAttribute} query
124132
* @param {Node} node
125133
* @returns {boolean}
126134
*/
127135
function ends(query, node) {
128136
/** @type {unknown} */
129137
// @ts-expect-error: Looks like a record.
130138
const value = node[query.name]
139+
const queryValue = attributeValue(query)
131140

132141
return Boolean(
133142
query.value &&
134143
typeof value === 'string' &&
135-
value.slice(-query.value.length) === query.value
144+
value.slice(-queryValue.length) === queryValue
136145
)
137146
}
138147

@@ -141,16 +150,18 @@ function ends(query, node) {
141150
*
142151
* `[attr*=value]`
143152
*
144-
* @param {RuleAttr} query
153+
* @param {AstAttribute} query
145154
* @param {Node} node
146155
* @returns {boolean}
147156
*/
148157
function containsString(query, node) {
149158
/** @type {unknown} */
150159
// @ts-expect-error: Looks like a record.
151160
const value = node[query.name]
161+
const queryValue = attributeValue(query)
162+
152163
return Boolean(
153-
query.value && typeof value === 'string' && value.includes(query.value)
164+
typeof value === 'string' && queryValue && value.includes(queryValue)
154165
)
155166
}
156167

@@ -164,3 +175,19 @@ function unknownOperator(query) {
164175
// @ts-expect-error: `operator` guaranteed.
165176
throw new Error('Unknown operator `' + query.operator + '`')
166177
}
178+
179+
/**
180+
* @param {AstAttribute} query
181+
* @returns {string}
182+
*/
183+
function attributeValue(query) {
184+
const queryValue = query.value
185+
186+
/* c8 ignore next 4 -- never happens with our config */
187+
if (!queryValue) unreachable('Attribute values should be defined')
188+
if (queryValue.type === 'Substitution') {
189+
unreachable('Substitutions are not enabled')
190+
}
191+
192+
return queryValue.value
193+
}

lib/name.js

-14
This file was deleted.

lib/parse.js

+11-9
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,26 @@
11
/**
2-
* @typedef {import('./types.js').Selectors} Selectors
3-
* @typedef {import('./types.js').RuleSet} RuleSet
2+
* @typedef {import('css-selector-parser').AstSelector} AstSelector
43
*/
54

6-
import {CssSelectorParser} from 'css-selector-parser'
5+
import {createParser} from 'css-selector-parser'
76

8-
const parser = new CssSelectorParser()
7+
const cssSelectorParse = createParser({syntax: 'selectors-4'})
98

10-
parser.registerAttrEqualityMods('~', '^', '$', '*')
11-
parser.registerSelectorPseudos('any', 'matches', 'not', 'has')
12-
parser.registerNestingOperators('>', '+', '~')
9+
// .
10+
// const parser = new CssSelectorParser()
11+
12+
// parser.registerAttrEqualityMods('~', '^', '$', '*')
13+
// parser.registerSelectorPseudos('any', 'matches', 'not', 'has')
14+
// parser.registerNestingOperators('>', '+', '~')
1315

1416
/**
1517
* @param {string} selector
16-
* @returns {Selectors | RuleSet | null}
18+
* @returns {AstSelector}
1719
*/
1820
export function parse(selector) {
1921
if (typeof selector !== 'string') {
2022
throw new TypeError('Expected `string` as selector, not `' + selector + '`')
2123
}
2224

23-
return parser.parse(selector)
25+
return cssSelectorParse(selector)
2426
}

0 commit comments

Comments
 (0)