Skip to content

Commit ebbb1e6

Browse files
committed
Improve theme helper detection
1 parent cccb8c6 commit ebbb1e6

File tree

5 files changed

+85
-129
lines changed

5 files changed

+85
-129
lines changed

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

Lines changed: 17 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,12 @@
11
import { State, Settings } from '../util/state'
2-
import type { TextDocument, Range, DiagnosticSeverity } from 'vscode-languageserver'
2+
import type { TextDocument } from 'vscode-languageserver'
33
import { InvalidConfigPathDiagnostic, DiagnosticKind } from './types'
4-
import { isCssDoc } from '../util/css'
5-
import { getLanguageBoundaries } from '../util/getLanguageBoundaries'
6-
import { findAll, indexToPosition } from '../util/find'
4+
import { findHelperFunctionsInDocument } from '../util/find'
75
import { stringToPath } from '../util/stringToPath'
86
import isObject from '../util/isObject'
97
import { closest } from '../util/closest'
10-
import { absoluteRange } from '../util/absoluteRange'
118
import { combinations } from '../util/combinations'
129
import dlv from 'dlv'
13-
import { getTextWithoutComments } from '../util/doc'
1410

1511
function pathToString(path: string | string[]): string {
1612
if (typeof path === 'string') return path
@@ -167,61 +163,24 @@ export function getInvalidConfigPathDiagnostics(
167163
if (severity === 'ignore') return []
168164

169165
let diagnostics: InvalidConfigPathDiagnostic[] = []
170-
let ranges: Range[] = []
171-
172-
if (isCssDoc(state, document)) {
173-
ranges.push(undefined)
174-
} else {
175-
let boundaries = getLanguageBoundaries(state, document)
176-
if (!boundaries) return []
177-
ranges.push(...boundaries.filter((b) => b.type === 'css').map(({ range }) => range))
178-
}
179166

180-
ranges.forEach((range) => {
181-
let text = getTextWithoutComments(document, 'css', range)
182-
let matches = findAll(/(?<prefix>\s|^)(?<helper>config|theme)\((?<path>[^)]*)\)/g, text)
183-
184-
matches.forEach((match) => {
185-
let path = match.groups.path.replace(/^['"]+|['"]+$/g, '')
186-
let alpha: string
187-
let matches = path.match(/^([^\s]+)(?![^\[]*\])(?:\s*\/\s*([^\/\s]+))$/)
188-
if (matches) {
189-
path = matches[1]
190-
alpha = matches[2]
191-
}
192-
console.log({ path, alpha })
167+
findHelperFunctionsInDocument(state, document).forEach((helperFn) => {
168+
let base = helperFn.helper === 'theme' ? ['theme'] : []
169+
let result = validateConfigPath(state, helperFn.path, base)
170+
171+
if (result.isValid === true) {
193172
return
173+
}
194174

195-
// let base = match.groups.helper === 'theme' ? ['theme'] : []
196-
// let result = validateConfigPath(state, match.groups.path, base)
197-
198-
// if (result.isValid === true) {
199-
// return null
200-
// }
201-
202-
// let startIndex =
203-
// match.index +
204-
// match.groups.prefix.length +
205-
// match.groups.helper.length +
206-
// 1 + // open paren
207-
// match.groups.innerPrefix.length
208-
209-
// diagnostics.push({
210-
// code: DiagnosticKind.InvalidConfigPath,
211-
// range: absoluteRange(
212-
// {
213-
// start: indexToPosition(text, startIndex),
214-
// end: indexToPosition(text, startIndex + match.groups.path.length),
215-
// },
216-
// range
217-
// ),
218-
// severity:
219-
// severity === 'error'
220-
// ? 1 /* DiagnosticSeverity.Error */
221-
// : 2 /* DiagnosticSeverity.Warning */,
222-
// message: result.reason,
223-
// suggestions: result.suggestions,
224-
// })
175+
diagnostics.push({
176+
code: DiagnosticKind.InvalidConfigPath,
177+
range: helperFn.ranges.path,
178+
severity:
179+
severity === 'error'
180+
? 1 /* DiagnosticSeverity.Error */
181+
: 2 /* DiagnosticSeverity.Warning */,
182+
message: result.reason,
183+
suggestions: result.suggestions,
225184
})
226185
})
227186

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,12 @@ export async function getDocumentColors(
3636

3737
let helperFns = findHelperFunctionsInDocument(state, document)
3838
helperFns.forEach((fn) => {
39-
let keys = stringToPath(fn.value)
39+
let keys = stringToPath(fn.path)
4040
let base = fn.helper === 'theme' ? ['theme'] : []
4141
let value = dlv(state.config, [...base, ...keys])
4242
let color = getColorFromValue(value)
4343
if (color && typeof color !== 'string' && (color.alpha ?? 1) !== 0) {
44-
colors.push({ range: fn.valueRange, color: culoriColorToVscodeColor(color) })
44+
colors.push({ range: fn.ranges.path, color: culoriColorToVscodeColor(color) })
4545
}
4646
})
4747

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

Lines changed: 25 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@ import type { Hover, TextDocument, Position } from 'vscode-languageserver'
33
import { stringifyCss, stringifyConfigValue } from './util/stringify'
44
import dlv from 'dlv'
55
import { isCssContext } from './util/css'
6-
import { findClassNameAtPosition } from './util/find'
6+
import { findClassNameAtPosition, findHelperFunctionsInRange } from './util/find'
77
import { validateApply } from './util/validateApply'
88
import { getClassNameParts } from './util/getClassNameAtPosition'
99
import * as jit from './util/jit'
1010
import { validateConfigPath } from './diagnostics/getInvalidConfigPathDiagnostics'
11-
import { getTextWithoutComments } from './util/doc'
11+
import { isWithinRange } from './util/isWithinRange'
1212

1313
export async function doHover(
1414
state: State,
@@ -22,49 +22,34 @@ export async function doHover(
2222
}
2323

2424
function provideCssHelperHover(state: State, document: TextDocument, position: Position): Hover {
25-
if (!isCssContext(state, document, position)) return null
26-
27-
const line = getTextWithoutComments(document, 'css').split('\n')[position.line]
28-
29-
const match = line.match(/(?<helper>theme|config)\((?<quote>['"])(?<key>[^)]+)\k<quote>[^)]*\)/)
30-
31-
if (match === null) return null
32-
33-
const startChar = match.index + match.groups.helper.length + 2
34-
const endChar = startChar + match.groups.key.length
35-
36-
if (position.character < startChar || position.character >= endChar) {
25+
if (!isCssContext(state, document, position)) {
3726
return null
3827
}
3928

40-
let key = match.groups.key
41-
.split(/(\[[^\]]+\]|\.)/)
42-
.filter(Boolean)
43-
.filter((x) => x !== '.')
44-
.map((x) => x.replace(/^\[([^\]]+)\]$/, '$1'))
45-
46-
if (key.length === 0) return null
47-
48-
if (match.groups.helper === 'theme') {
49-
key = ['theme', ...key]
29+
let helperFns = findHelperFunctionsInRange(document, {
30+
start: { line: position.line, character: 0 },
31+
end: { line: position.line + 1, character: 0 },
32+
})
33+
34+
for (let helperFn of helperFns) {
35+
if (isWithinRange(position, helperFn.ranges.path)) {
36+
let validated = validateConfigPath(
37+
state,
38+
helperFn.path,
39+
helperFn.helper === 'theme' ? ['theme'] : []
40+
)
41+
let value = validated.isValid ? stringifyConfigValue(validated.value) : null
42+
if (value === null) {
43+
return null
44+
}
45+
return {
46+
contents: { kind: 'markdown', value: ['```plaintext', value, '```'].join('\n') },
47+
range: helperFn.ranges.path,
48+
}
49+
}
5050
}
5151

52-
const value = validateConfigPath(state, key).isValid
53-
? stringifyConfigValue(dlv(state.config, key))
54-
: null
55-
56-
if (value === null) return null
57-
58-
return {
59-
contents: { kind: 'markdown', value: ['```plaintext', value, '```'].join('\n') },
60-
range: {
61-
start: { line: position.line, character: startChar },
62-
end: {
63-
line: position.line,
64-
character: endChar,
65-
},
66-
},
67-
}
52+
return null
6853
}
6954

7055
async function provideClassNameHover(

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

Lines changed: 36 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -359,36 +359,48 @@ export function findHelperFunctionsInRange(
359359
range?: Range
360360
): DocumentHelperFunction[] {
361361
const text = getTextWithoutComments(doc, 'css', range)
362-
const matches = findAll(
363-
/(?<before>^|\s)(?<helper>theme|config)\((?:(?<single>')([^']+)'|(?<double>")([^"]+)")[^)]*\)/gm,
362+
let matches = findAll(
363+
/(?<prefix>\s|^)(?<helper>config|theme)(?<innerPrefix>\(\s*)(?<path>[^)]*?)\s*\)/g,
364364
text
365365
)
366366

367367
return matches.map((match) => {
368-
let value = match[4] || match[6]
369-
let startIndex = match.index + match.groups.before.length
368+
let quotesBefore = ''
369+
let path = match.groups.path.replace(/['"]+$/, '').replace(/^['"]+/, (m) => {
370+
quotesBefore = m
371+
return ''
372+
})
373+
let matches = path.match(/^([^\s]+)(?![^\[]*\])(?:\s*\/\s*([^\/\s]+))$/)
374+
if (matches) {
375+
path = matches[1]
376+
}
377+
path = path.replace(/['"]*\s*$/, '')
378+
379+
let startIndex =
380+
match.index +
381+
match.groups.prefix.length +
382+
match.groups.helper.length +
383+
match.groups.innerPrefix.length
384+
370385
return {
371-
full: match[0].substr(match.groups.before.length),
372-
value,
373386
helper: match.groups.helper === 'theme' ? 'theme' : 'config',
374-
quotes: match.groups.single ? "'" : '"',
375-
range: resolveRange(
376-
{
377-
start: indexToPosition(text, startIndex),
378-
end: indexToPosition(text, match.index + match[0].length),
379-
},
380-
range
381-
),
382-
valueRange: resolveRange(
383-
{
384-
start: indexToPosition(text, startIndex + match.groups.helper.length + 1),
385-
end: indexToPosition(
386-
text,
387-
startIndex + match.groups.helper.length + 1 + 1 + value.length + 1
388-
),
389-
},
390-
range
391-
),
387+
path,
388+
ranges: {
389+
full: resolveRange(
390+
{
391+
start: indexToPosition(text, startIndex),
392+
end: indexToPosition(text, startIndex + match.groups.path.length),
393+
},
394+
range
395+
),
396+
path: resolveRange(
397+
{
398+
start: indexToPosition(text, startIndex + quotesBefore.length),
399+
end: indexToPosition(text, startIndex + quotesBefore.length + path.length),
400+
},
401+
range
402+
),
403+
},
392404
}
393405
})
394406
}

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

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -124,12 +124,12 @@ export type DocumentClassName = {
124124
}
125125

126126
export type DocumentHelperFunction = {
127-
full: string
128127
helper: 'theme' | 'config'
129-
value: string
130-
quotes: '"' | "'"
131-
range: Range
132-
valueRange: Range
128+
path: string
129+
ranges: {
130+
full: Range
131+
path: Range
132+
}
133133
}
134134

135135
export type ClassNameMeta = {

0 commit comments

Comments
 (0)