Skip to content

Commit aae216c

Browse files
committed
Refactor
1 parent 29c0d15 commit aae216c

File tree

6 files changed

+144
-59
lines changed

6 files changed

+144
-59
lines changed

packages/tailwindcss-language-server/src/language/cssServer.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -336,11 +336,7 @@ function replace(delta = 0) {
336336
}
337337

338338
function createVirtualCssDocument(textDocument: TextDocument): TextDocument {
339-
return TextDocument.create(
340-
textDocument.uri,
341-
textDocument.languageId,
342-
textDocument.version,
343-
textDocument
339+
let content = textDocument
344340
.getText()
345341
.replace(/@screen(\s+[^{]+){/g, replace(-2))
346342
.replace(/@variants(\s+[^{]+){/g, replace())
@@ -350,7 +346,13 @@ function createVirtualCssDocument(textDocument: TextDocument): TextDocument {
350346
/@media(\s+screen\s*\([^)]+\))/g,
351347
(_match, screen) => `@media (${MEDIA_MARKER})${' '.repeat(screen.length - 4)}`,
352348
)
353-
.replace(/(?<=\b(?:theme|config)\([^)]*)[.[\]]/g, '_'),
349+
.replace(/(?<=\b(?:theme|config)\([^)]*)[.[\]]/g, '_')
350+
351+
return TextDocument.create(
352+
textDocument.uri,
353+
textDocument.languageId,
354+
textDocument.version,
355+
content,
354356
)
355357
}
356358

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

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import {
3939
import { customClassesIn } from './util/classes'
4040
import { IS_SCRIPT_SOURCE, IS_TEMPLATE_SOURCE } from './metadata/extensions'
4141
import * as postcss from 'postcss'
42+
import { findFileDirective } from './completions/file-paths'
4243

4344
let isUtil = (className) =>
4445
Array.isArray(className.__info)
@@ -1613,27 +1614,28 @@ async function provideFileDirectiveCompletions(
16131614
return null
16141615
}
16151616

1616-
let pattern = state.v4
1617-
? /@(?<directive>config|plugin|source)\s*(?<partial>'[^']*|"[^"]*)$/
1618-
: /@(?<directive>config)\s*(?<partial>'[^']*|"[^"]*)$/
1619-
16201617
let text = document.getText({ start: { line: position.line, character: 0 }, end: position })
1621-
let match = text.match(pattern)
1622-
if (!match) {
1623-
return null
1618+
1619+
let fd = await findFileDirective(state, text)
1620+
if (!fd) return null
1621+
1622+
let { partial, suggest } = fd
1623+
1624+
function isAllowedFile(name: string) {
1625+
if (suggest === 'script') return IS_SCRIPT_SOURCE.test(name)
1626+
1627+
if (suggest === 'source') return IS_TEMPLATE_SOURCE.test(name)
1628+
1629+
return false
16241630
}
1625-
let directive = match.groups.directive
1626-
let partial = match.groups.partial.slice(1) // remove quote
1631+
16271632
let valueBeforeLastSlash = partial.substring(0, partial.lastIndexOf('/'))
16281633
let valueAfterLastSlash = partial.substring(partial.lastIndexOf('/') + 1)
16291634

16301635
let entries = await state.editor.readDirectory(document, valueBeforeLastSlash || '.')
16311636

1632-
let isAllowedFile = directive === 'source' ? IS_TEMPLATE_SOURCE : IS_SCRIPT_SOURCE
1633-
1634-
// Only show directories and JavaScript/TypeScript files
16351637
entries = entries.filter(([name, type]) => {
1636-
return type.isDirectory || isAllowedFile.test(name)
1638+
return type.isDirectory || isAllowedFile(name)
16371639
})
16381640

16391641
return withDefaults(
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import type { State } from '../util/state'
2+
3+
// @config, @plugin, @source
4+
const PATTERN_CUSTOM_V4 = /@(?<directive>config|plugin|source)\s*(?<partial>'[^']*|"[^"]*)$/
5+
const PATTERN_CUSTOM_V3 = /@(?<directive>config)\s*(?<partial>'[^']*|"[^"]*)$/
6+
7+
export type FileDirective = {
8+
directive: string
9+
partial: string
10+
suggest: 'script' | 'source'
11+
}
12+
13+
export async function findFileDirective(state: State, text: string): Promise<FileDirective | null> {
14+
if (state.v4) {
15+
let match = text.match(PATTERN_CUSTOM_V4)
16+
17+
if (!match) return null
18+
19+
let directive = match.groups.directive
20+
let partial = match.groups.partial.slice(1) // remove leading quote
21+
22+
// Most suggestions are for JS files so we'll default to that
23+
let suggest: FileDirective['suggest'] = 'script'
24+
25+
// If we're looking at @source then it's for a template file
26+
if (directive === 'source') {
27+
suggest = 'source'
28+
}
29+
30+
return { directive, partial, suggest }
31+
}
32+
33+
let match = text.match(PATTERN_CUSTOM_V3)
34+
if (!match) return null
35+
36+
let directive = match.groups.directive
37+
let partial = match.groups.partial.slice(1) // remove leading quote
38+
39+
return { directive, partial, suggest: 'script' }
40+
}

packages/tailwindcss-language-service/src/diagnostics/getInvalidTailwindDirectiveDiagnostics.ts

Lines changed: 55 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -37,51 +37,23 @@ export function getInvalidTailwindDirectiveDiagnostics(
3737
regex = /(?:\s|^)@tailwind\s+(?<value>[^;]+)/g
3838
}
3939

40-
let hasVariantsDirective = state.jit && semver.gte(state.version, '2.1.99')
41-
4240
ranges.forEach((range) => {
4341
let text = getTextWithoutComments(document, 'css', range)
4442
let matches = findAll(regex, text)
4543

46-
let valid = [
47-
'utilities',
48-
'components',
49-
'screens',
50-
semver.gte(state.version, '1.0.0-beta.1') ? 'base' : 'preflight',
51-
hasVariantsDirective && 'variants',
52-
].filter(Boolean)
44+
matches.forEach((match) => {
45+
let layerName = match.groups.value
5346

54-
let suggestable = valid
47+
let result = validateLayerName(state, layerName)
48+
if (!result) return
5549

56-
if (hasVariantsDirective) {
57-
// Don't suggest `screens`, because it's deprecated
58-
suggestable = suggestable.filter((value) => value !== 'screens')
59-
}
60-
61-
matches.forEach((match) => {
62-
if (valid.includes(match.groups.value)) {
63-
return null
64-
}
65-
66-
let message = `'${match.groups.value}' is not a valid value.`
67-
let suggestions: string[] = []
68-
69-
if (match.groups.value === 'preflight') {
70-
suggestions.push('base')
71-
message += ` Did you mean 'base'?`
72-
} else {
73-
let suggestion = closest(match.groups.value, suggestable)
74-
if (suggestion) {
75-
suggestions.push(suggestion)
76-
message += ` Did you mean '${suggestion}'?`
77-
}
78-
}
50+
let { message, suggestions } = result
7951

8052
diagnostics.push({
8153
code: DiagnosticKind.InvalidTailwindDirective,
8254
range: absoluteRange(
8355
{
84-
start: indexToPosition(text, match.index + match[0].length - match.groups.value.length),
56+
start: indexToPosition(text, match.index + match[0].length - layerName.length),
8557
end: indexToPosition(text, match.index + match[0].length),
8658
},
8759
range,
@@ -98,3 +70,52 @@ export function getInvalidTailwindDirectiveDiagnostics(
9870

9971
return diagnostics
10072
}
73+
74+
function validateLayerName(
75+
state: State,
76+
layerName: string,
77+
): { message: string; suggestions: string[] } | null {
78+
let valid = ['utilities', 'components', 'screens']
79+
80+
if (semver.gte(state.version, '1.0.0-beta.1')) {
81+
valid.push('base')
82+
} else {
83+
valid.push('preflight')
84+
}
85+
86+
let hasVariantsDirective = state.jit && semver.gte(state.version, '2.1.99')
87+
88+
if (hasVariantsDirective) {
89+
valid.push('variants')
90+
}
91+
92+
if (valid.includes(layerName)) {
93+
return null
94+
}
95+
96+
let suggestable = valid
97+
98+
if (hasVariantsDirective) {
99+
// Don't suggest `screens`, because it's deprecated
100+
suggestable = suggestable.filter((value) => value !== 'screens')
101+
}
102+
103+
let message = `'${layerName}' is not a valid value.`
104+
let suggestions: string[] = []
105+
106+
if (layerName === 'preflight') {
107+
suggestions.push('base')
108+
message += ` Did you mean 'base'?`
109+
} else {
110+
let suggestion = closest(layerName, suggestable)
111+
if (suggestion) {
112+
suggestions.push(suggestion)
113+
message += ` Did you mean '${suggestion}'?`
114+
}
115+
}
116+
117+
return {
118+
message,
119+
suggestions,
120+
}
121+
}

packages/tailwindcss-language-service/src/util/css-vars.ts

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,16 @@
11
export function replaceCssVarsWithFallbacks(str: string): string {
2+
return replaceCssVars(str, (name, fallback) => {
3+
// If we have a fallback then we should use that value directly
4+
if (fallback) return fallback
5+
6+
// Don't replace the variable otherwise
7+
return null
8+
})
9+
}
10+
11+
type CssVarReplacer = (name: string, fallback: string | null) => string | null
12+
13+
function replaceCssVars(str: string, replace: CssVarReplacer): string {
214
for (let i = 0; i < str.length; ++i) {
315
if (!str.startsWith('var(', i)) continue
416

@@ -13,17 +25,25 @@ export function replaceCssVarsWithFallbacks(str: string): string {
1325
} else if (str[j] === ',' && depth === 0 && fallbackStart === null) {
1426
fallbackStart = j + 1
1527
} else if (str[j] === ')' && depth === 0) {
16-
if (fallbackStart === null) {
17-
i = j + 1
28+
let varName = str.slice(i + 4, j)
29+
let fallback = fallbackStart === null ? null : str.slice(fallbackStart, j)
30+
let replacement = replace(varName, fallback)
31+
32+
if (replacement !== null) {
33+
str = str.slice(0, i) + replacement + str.slice(j + 1)
34+
35+
// We don't want to skip past anything here because `replacement`
36+
// might contain more var(…) calls in which case `i` will already
37+
// be pointing at the right spot to start looking for them
1838
break
1939
}
2040

21-
let fallbackEnd = j
22-
str = str.slice(0, i) + str.slice(fallbackStart, fallbackEnd) + str.slice(j + 1)
41+
// Skip past the closing parenthesis and onto the next `var(`
42+
i = j + 1
2343
break
2444
}
2545
}
2646
}
2747

2848
return str
29-
}
49+
}

packages/tailwindcss-language-service/src/util/v4/design-system.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import type { Rule } from './ast'
33
import type { NamedVariant } from './candidate'
44

55
export interface Theme {
6-
// Prefix didn't exist on
6+
// Prefix didn't exist for earlier Tailwind versions
77
prefix?: string
88
entries(): [string, any][]
99
}

0 commit comments

Comments
 (0)