Skip to content

Commit 76cbaa4

Browse files
authored
Add support for arbitrary variants (#557)
* Support arbitrary variants * Bump typescript and types versions
1 parent 5516e33 commit 76cbaa4

File tree

8 files changed

+1234
-966
lines changed

8 files changed

+1234
-966
lines changed

package-lock.json

Lines changed: 1142 additions & 946 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/tailwindcss-language-server/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
"@tailwindcss/typography": "0.5.0",
3939
"@types/debounce": "1.2.0",
4040
"@types/node": "14.14.34",
41-
"@types/vscode": "1.60.0",
41+
"@types/vscode": "1.67.0",
4242
"builtin-modules": "3.2.0",
4343
"chokidar": "3.5.1",
4444
"color-name": "1.1.4",
@@ -65,7 +65,7 @@
6565
"stack-trace": "0.0.10",
6666
"tailwindcss": "3.0.11",
6767
"terser": "4.6.12",
68-
"typescript": "4.2.4",
68+
"typescript": "4.6.4",
6969
"vscode-css-languageservice": "5.4.1",
7070
"vscode-languageserver": "7.0.0",
7171
"vscode-languageserver-textdocument": "1.0.1",

packages/tailwindcss-language-service/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,6 @@
3838
"prettier": "2.3.0",
3939
"tsdx": "0.14.1",
4040
"tslib": "2.2.0",
41-
"typescript": "4.2.4"
41+
"typescript": "4.6.4"
4242
}
4343
}

packages/tailwindcss-language-service/src/completionProvider.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -364,10 +364,10 @@ async function provideClassAttributeCompletions(
364364
try {
365365
let tokens = Array.from(lexer)
366366
let last = tokens[tokens.length - 1]
367-
if (last.type.startsWith('start') || last.type === 'classlist') {
367+
if (last.type.startsWith('start') || last.type === 'classlist' || last.type.startsWith('arb')) {
368368
let classList = ''
369369
for (let i = tokens.length - 1; i >= 0; i--) {
370-
if (tokens[i].type === 'classlist') {
370+
if (tokens[i].type === 'classlist' || tokens[i].type.startsWith('arb')) {
371371
classList = tokens[i].value + classList
372372
} else {
373373
break

packages/tailwindcss-language-service/src/util/find.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@ export async function findClassListsInHtmlRange(
207207

208208
try {
209209
for (let token of lexer) {
210-
if (token.type === 'classlist') {
210+
if (token.type === 'classlist' || token.type.startsWith('arb')) {
211211
if (currentClassList) {
212212
currentClassList.value += token.value
213213
} else {
Lines changed: 79 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,94 @@
11
import { State } from './state'
2+
import * as jit from './jit'
23

34
export function getVariantsFromClassName(
45
state: State,
56
className: string
67
): { variants: string[]; offset: number } {
7-
let str = className
88
let allVariants = Object.keys(state.variants)
9-
let allVariantsByLength = allVariants.sort((a, b) => b.length - a.length)
9+
let parts = Array.from(splitAtTopLevelOnly(className, state.separator)).filter(Boolean)
1010
let variants = new Set<string>()
1111
let offset = 0
1212

13-
while (str) {
14-
let found = false
15-
for (let variant of allVariantsByLength) {
16-
if (str.startsWith(variant + state.separator)) {
17-
variants.add(variant)
18-
str = str.substr(variant.length + state.separator.length)
19-
offset += variant.length + state.separator.length
20-
found = true
21-
break
22-
}
13+
for (let part of parts) {
14+
if (
15+
allVariants.includes(part) ||
16+
(state.jit &&
17+
part.startsWith('[') &&
18+
part.endsWith(']') &&
19+
jit.generateRules(state, [`${part}${state.separator}[color:red]`]).rules.length > 0)
20+
) {
21+
variants.add(part)
22+
offset += part.length + state.separator.length
23+
continue
2324
}
24-
if (!found) str = ''
25+
26+
break
2527
}
2628

2729
return { variants: Array.from(variants), offset }
2830
}
31+
32+
const REGEX_SPECIAL = /[\\^$.*+?()[\]{}|]/g
33+
const REGEX_HAS_SPECIAL = RegExp(REGEX_SPECIAL.source)
34+
35+
function regexEscape(string: string): string {
36+
return string && REGEX_HAS_SPECIAL.test(string)
37+
? string.replace(REGEX_SPECIAL, '\\$&')
38+
: string || ''
39+
}
40+
41+
function* splitAtTopLevelOnly(input: string, separator: string): Generator<string> {
42+
let SPECIALS = new RegExp(`[(){}\\[\\]${regexEscape(separator)}]`, 'g')
43+
44+
let depth = 0
45+
let lastIndex = 0
46+
let found = false
47+
let separatorIndex = 0
48+
let separatorStart = 0
49+
let separatorLength = separator.length
50+
51+
// Find all paren-like things & character
52+
// And only split on commas if they're top-level
53+
for (let match of input.matchAll(SPECIALS)) {
54+
let matchesSeparator = match[0] === separator[separatorIndex]
55+
let atEndOfSeparator = separatorIndex === separatorLength - 1
56+
let matchesFullSeparator = matchesSeparator && atEndOfSeparator
57+
58+
if (match[0] === '(') depth++
59+
if (match[0] === ')') depth--
60+
if (match[0] === '[') depth++
61+
if (match[0] === ']') depth--
62+
if (match[0] === '{') depth++
63+
if (match[0] === '}') depth--
64+
65+
if (matchesSeparator && depth === 0) {
66+
if (separatorStart === 0) {
67+
separatorStart = match.index
68+
}
69+
70+
separatorIndex++
71+
}
72+
73+
if (matchesFullSeparator && depth === 0) {
74+
found = true
75+
76+
yield input.substring(lastIndex, separatorStart)
77+
lastIndex = separatorStart + separatorLength
78+
}
79+
80+
if (separatorIndex === separatorLength) {
81+
separatorIndex = 0
82+
separatorStart = 0
83+
}
84+
}
85+
86+
// Provide the last segment of the string if available
87+
// Otherwise the whole string since no `char`s were found
88+
// This mirrors the behavior of string.split()
89+
if (found) {
90+
yield input.substring(lastIndex)
91+
} else {
92+
yield input
93+
}
94+
}

packages/tailwindcss-language-service/src/util/lexers.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { lazy } from './lazy'
33

44
const classAttributeStates: () => { [x: string]: moo.Rules } = () => ({
55
doubleClassList: {
6+
arb: { match: new RegExp('(?<!\\\\)\\['), push: 'arbitrary' },
67
lbrace: { match: new RegExp('(?<!\\\\)\\{'), push: 'interpBrace' },
78
rbrace: { match: new RegExp('(?<!\\\\)\\}'), pop: 1 },
89
end: { match: new RegExp('(?<!\\\\)"'), pop: 1 },
@@ -40,6 +41,11 @@ const classAttributeStates: () => { [x: string]: moo.Rules } = () => ({
4041
double: { match: new RegExp('(?<!\\\\)"'), pop: 1 },
4142
text: { match: new RegExp('[\\s\\S]'), lineBreaks: true },
4243
},
44+
arbitrary: {
45+
arb: { match: new RegExp('(?<!\\\\)\\]'), pop: 1 },
46+
space: { match: /\s/, pop: 1, lineBreaks: true },
47+
arb2: { match: new RegExp('[\\s\\S]'), lineBreaks: true },
48+
},
4349
})
4450

4551
const simpleClassAttributeStates: { [x: string]: moo.Rules } = {

packages/vscode-tailwindcss/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -321,7 +321,7 @@
321321
"check": "tsc --noEmit"
322322
},
323323
"devDependencies": {
324-
"@types/vscode": "1.60.0",
324+
"@types/vscode": "1.67.0",
325325
"color-name": "1.1.4",
326326
"concurrently": "7.0.0",
327327
"rimraf": "3.0.2",

0 commit comments

Comments
 (0)