Skip to content

Commit 226ae47

Browse files
committed
use dedicated AttributeSelectorParser
This implementation is also 2-3x faster than the original one. ``` AttributeSelectorParser.parse - src/attribute-selector-parser.bench.ts > parsing 2.24x faster than REGEX.test(…) 2.59x faster than ….match(REGEX) 2.84x faster than parseAttributeSelector ```
1 parent 705476c commit 226ae47

File tree

1 file changed

+14
-146
lines changed

1 file changed

+14
-146
lines changed

packages/tailwindcss/src/canonicalize-candidates.ts

Lines changed: 14 additions & 146 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import * as AttributeSelectorParser from './attribute-selector-parser'
12
import {
23
printModifier,
34
type Candidate,
@@ -1088,139 +1089,6 @@ function isAttributeSelector(node: SelectorParser.SelectorAstNode): boolean {
10881089
return node.kind === 'selector' && value[0] === '[' && value[value.length - 1] === ']'
10891090
}
10901091

1091-
function isAsciiWhitespace(char: string) {
1092-
return char === ' ' || char === '\t' || char === '\n' || char === '\r' || char === '\f'
1093-
}
1094-
1095-
enum AttributePart {
1096-
Start,
1097-
Attribute,
1098-
Value,
1099-
Modifier,
1100-
End,
1101-
}
1102-
1103-
function parseAttributeSelector(value: string) {
1104-
let attribute = {
1105-
key: '',
1106-
operator: null as '=' | '~=' | '|=' | '^=' | '$=' | '*=' | null,
1107-
quote: '',
1108-
value: null as string | null,
1109-
modifier: null as 'i' | 's' | null,
1110-
}
1111-
1112-
let state = AttributePart.Start
1113-
outer: for (let i = 0; i < value.length; i++) {
1114-
// Skip whitespace
1115-
if (isAsciiWhitespace(value[i])) {
1116-
if (attribute.quote === '' && state !== AttributePart.Value) {
1117-
continue
1118-
}
1119-
}
1120-
1121-
switch (state) {
1122-
case AttributePart.Start: {
1123-
if (value[i] === '[') {
1124-
state = AttributePart.Attribute
1125-
} else {
1126-
return null
1127-
}
1128-
break
1129-
}
1130-
1131-
case AttributePart.Attribute: {
1132-
switch (value[i]) {
1133-
case ']': {
1134-
return attribute
1135-
}
1136-
1137-
case '=': {
1138-
attribute.operator = '='
1139-
state = AttributePart.Value
1140-
continue outer
1141-
}
1142-
1143-
case '~':
1144-
case '|':
1145-
case '^':
1146-
case '$':
1147-
case '*': {
1148-
if (value[i + 1] === '=') {
1149-
attribute.operator = (value[i] + '=') as '=' | '~=' | '|=' | '^=' | '$=' | '*='
1150-
i++
1151-
state = AttributePart.Value
1152-
continue outer
1153-
}
1154-
1155-
return null
1156-
}
1157-
}
1158-
1159-
attribute.key += value[i]
1160-
break
1161-
}
1162-
1163-
case AttributePart.Value: {
1164-
// End of attribute selector
1165-
if (value[i] === ']') {
1166-
return attribute
1167-
}
1168-
1169-
// Quoted value
1170-
else if (value[i] === "'" || value[i] === '"') {
1171-
attribute.value ??= ''
1172-
1173-
attribute.quote = value[i]
1174-
1175-
for (let j = i + 1; j < value.length; j++) {
1176-
if (value[j] === '\\' && j + 1 < value.length) {
1177-
// Skip the escaped character
1178-
j++
1179-
attribute.value += value[j]
1180-
} else if (value[j] === attribute.quote) {
1181-
i = j
1182-
state = AttributePart.Modifier
1183-
continue outer
1184-
} else {
1185-
attribute.value += value[j]
1186-
}
1187-
}
1188-
}
1189-
1190-
// Unquoted value
1191-
else {
1192-
if (isAsciiWhitespace(value[i])) {
1193-
state = AttributePart.Modifier
1194-
} else {
1195-
attribute.value ??= ''
1196-
attribute.value += value[i]
1197-
}
1198-
}
1199-
break
1200-
}
1201-
1202-
case AttributePart.Modifier: {
1203-
if (value[i] === 'i' || value[i] === 's') {
1204-
attribute.modifier = value[i] as 'i' | 's'
1205-
state = AttributePart.End
1206-
} else if (value[i] == ']') {
1207-
return attribute
1208-
}
1209-
break
1210-
}
1211-
1212-
case AttributePart.End: {
1213-
if (value[i] === ']') {
1214-
return attribute
1215-
}
1216-
break
1217-
}
1218-
}
1219-
}
1220-
1221-
return attribute
1222-
}
1223-
12241092
function modernizeArbitraryValuesVariant(
12251093
designSystem: DesignSystem,
12261094
variant: Variant,
@@ -1519,44 +1387,44 @@ function modernizeArbitraryValuesVariant(
15191387

15201388
// Expecting an attribute selector
15211389
else if (isAttributeSelector(target)) {
1522-
let attribute = parseAttributeSelector(target.value)
1523-
if (attribute === null) continue // Invalid attribute selector
1390+
let attributeSelector = AttributeSelectorParser.parse(target.value)
1391+
if (attributeSelector === null) continue // Invalid attribute selector
15241392

15251393
// Migrate `data-*`
1526-
if (attribute.key.startsWith('data-')) {
1527-
let name = attribute.key.slice(5) // Remove `data-`
1394+
if (attributeSelector.attribute.startsWith('data-')) {
1395+
let name = attributeSelector.attribute.slice(5) // Remove `data-`
15281396

15291397
replaceObject(variant, {
15301398
kind: 'functional',
15311399
root: 'data',
15321400
modifier: null,
15331401
value:
1534-
attribute.value === null
1402+
attributeSelector.value === null
15351403
? { kind: 'named', value: name }
15361404
: {
15371405
kind: 'arbitrary',
1538-
value: `${name}${attribute.operator}${attribute.quote}${attribute.value}${attribute.quote}${attribute.modifier ? ` ${attribute.modifier}` : ''}`,
1406+
value: `${name}${attributeSelector.operator}${attributeSelector.quote ?? ''}${attributeSelector.value}${attributeSelector.quote ?? ''}${attributeSelector.sensitivity ? ` ${attributeSelector.sensitivity}` : ''}`,
15391407
},
15401408
} satisfies Variant)
15411409
}
15421410

15431411
// Migrate `aria-*`
1544-
else if (attribute.key.startsWith('aria-')) {
1545-
let name = attribute.key.slice(5) // Remove `aria-`
1412+
else if (attributeSelector.attribute.startsWith('aria-')) {
1413+
let name = attributeSelector.attribute.slice(5) // Remove `aria-`
15461414
replaceObject(variant, {
15471415
kind: 'functional',
15481416
root: 'aria',
15491417
modifier: null,
15501418
value:
1551-
attribute.value === null
1419+
attributeSelector.value === null
15521420
? { kind: 'arbitrary', value: name } // aria-[foo]
1553-
: attribute.operator === '=' &&
1554-
attribute.value === 'true' &&
1555-
attribute.modifier === null
1421+
: attributeSelector.operator === '=' &&
1422+
attributeSelector.value === 'true' &&
1423+
attributeSelector.sensitivity === null
15561424
? { kind: 'named', value: name } // aria-[foo="true"] or aria-[foo='true'] or aria-[foo=true]
15571425
: {
15581426
kind: 'arbitrary',
1559-
value: `${attribute.key}${attribute.operator}${attribute.quote}${attribute.value}${attribute.quote}${attribute.modifier ? ` ${attribute.modifier}` : ''}`,
1427+
value: `${attributeSelector.attribute}${attributeSelector.operator}${attributeSelector.quote ?? ''}${attributeSelector.value}${attributeSelector.quote ?? ''}${attributeSelector.sensitivity ? ` ${attributeSelector.sensitivity}` : ''}`,
15601428
}, // aria-[foo~="true"], aria-[foo|="true"], …
15611429
} satisfies Variant)
15621430
}

0 commit comments

Comments
 (0)