Skip to content

Commit 6a59311

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 0145094 commit 6a59311

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
cloneCandidate,
34
cloneVariant,
@@ -1090,139 +1091,6 @@ function isAttributeSelector(node: SelectorParser.SelectorAstNode): boolean {
10901091
return node.kind === 'selector' && value[0] === '[' && value[value.length - 1] === ']'
10911092
}
10921093

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

15221390
// Expecting an attribute selector
15231391
else if (isAttributeSelector(target)) {
1524-
let attribute = parseAttributeSelector(target.value)
1525-
if (attribute === null) continue // Invalid attribute selector
1392+
let attributeSelector = AttributeSelectorParser.parse(target.value)
1393+
if (attributeSelector === null) continue // Invalid attribute selector
15261394

15271395
// Migrate `data-*`
1528-
if (attribute.key.startsWith('data-')) {
1529-
let name = attribute.key.slice(5) // Remove `data-`
1396+
if (attributeSelector.attribute.startsWith('data-')) {
1397+
let name = attributeSelector.attribute.slice(5) // Remove `data-`
15301398

15311399
replaceObject(variant, {
15321400
kind: 'functional',
15331401
root: 'data',
15341402
modifier: null,
15351403
value:
1536-
attribute.value === null
1404+
attributeSelector.value === null
15371405
? { kind: 'named', value: name }
15381406
: {
15391407
kind: 'arbitrary',
1540-
value: `${name}${attribute.operator}${attribute.quote}${attribute.value}${attribute.quote}${attribute.modifier ? ` ${attribute.modifier}` : ''}`,
1408+
value: `${name}${attributeSelector.operator}${attributeSelector.quote ?? ''}${attributeSelector.value}${attributeSelector.quote ?? ''}${attributeSelector.sensitivity ? ` ${attributeSelector.sensitivity}` : ''}`,
15411409
},
15421410
} satisfies Variant)
15431411
}
15441412

15451413
// Migrate `aria-*`
1546-
else if (attribute.key.startsWith('aria-')) {
1547-
let name = attribute.key.slice(5) // Remove `aria-`
1414+
else if (attributeSelector.attribute.startsWith('aria-')) {
1415+
let name = attributeSelector.attribute.slice(5) // Remove `aria-`
15481416
replaceObject(variant, {
15491417
kind: 'functional',
15501418
root: 'aria',
15511419
modifier: null,
15521420
value:
1553-
attribute.value === null
1421+
attributeSelector.value === null
15541422
? { kind: 'arbitrary', value: name } // aria-[foo]
1555-
: attribute.operator === '=' &&
1556-
attribute.value === 'true' &&
1557-
attribute.modifier === null
1423+
: attributeSelector.operator === '=' &&
1424+
attributeSelector.value === 'true' &&
1425+
attributeSelector.sensitivity === null
15581426
? { kind: 'named', value: name } // aria-[foo="true"] or aria-[foo='true'] or aria-[foo=true]
15591427
: {
15601428
kind: 'arbitrary',
1561-
value: `${attribute.key}${attribute.operator}${attribute.quote}${attribute.value}${attribute.quote}${attribute.modifier ? ` ${attribute.modifier}` : ''}`,
1429+
value: `${attributeSelector.attribute}${attributeSelector.operator}${attributeSelector.quote ?? ''}${attributeSelector.value}${attributeSelector.quote ?? ''}${attributeSelector.sensitivity ? ` ${attributeSelector.sensitivity}` : ''}`,
15621430
}, // aria-[foo~="true"], aria-[foo|="true"], …
15631431
} satisfies Variant)
15641432
}

0 commit comments

Comments
 (0)