Skip to content

Commit ddfaea2

Browse files
authored
Improve conflict diagnostics (tailwindlabs#503)
1 parent 86497bb commit ddfaea2

File tree

4 files changed

+105
-51
lines changed

4 files changed

+105
-51
lines changed

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@ export async function getCssConflictDiagnostics(
2020
const classLists = await findClassListsInDocument(state, document)
2121

2222
classLists.forEach((classList) => {
23-
const classNames = getClassNamesInClassList(classList)
23+
const classNames = Array.isArray(classList)
24+
? classList.flatMap(getClassNamesInClassList)
25+
: getClassNamesInClassList(classList)
2426

2527
classNames.forEach((className, index) => {
2628
if (state.jit) {

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ export async function getRecommendedVariantOrderDiagnostics(
2222
let diagnostics: RecommendedVariantOrderDiagnostic[] = []
2323
const classLists = await findClassListsInDocument(state, document)
2424

25-
classLists.forEach((classList) => {
25+
classLists.flat().forEach((classList) => {
2626
const classNames = getClassNamesInClassList(classList)
2727
classNames.forEach((className) => {
2828
let { rules } = jit.generateRules(state, [className.className])

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ export async function getDocumentColors(
2020
if (settings.tailwindCSS.colorDecorators === false) return colors
2121

2222
let classLists = await findClassListsInDocument(state, document)
23-
classLists.forEach((classList) => {
23+
classLists.flat().forEach((classList) => {
2424
let classNames = getClassNamesInClassList(classList)
2525
classNames.forEach((className) => {
2626
let color = getColor(state, className.className)

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

Lines changed: 100 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -77,15 +77,31 @@ export async function findClassNamesInRange(
7777
includeCustom: boolean = true
7878
): Promise<DocumentClassName[]> {
7979
const classLists = await findClassListsInRange(state, doc, range, mode, includeCustom)
80-
return flatten(classLists.map(getClassNamesInClassList))
80+
return flatten(
81+
classLists.flatMap((classList) => {
82+
if (Array.isArray(classList)) {
83+
return classList.map(getClassNamesInClassList)
84+
} else {
85+
return [getClassNamesInClassList(classList)]
86+
}
87+
})
88+
)
8189
}
8290

8391
export async function findClassNamesInDocument(
8492
state: State,
8593
doc: TextDocument
8694
): Promise<DocumentClassName[]> {
8795
const classLists = await findClassListsInDocument(state, doc)
88-
return flatten(classLists.map(getClassNamesInClassList))
96+
return flatten(
97+
classLists.flatMap((classList) => {
98+
if (Array.isArray(classList)) {
99+
return classList.map(getClassNamesInClassList)
100+
} else {
101+
return [getClassNamesInClassList(classList)]
102+
}
103+
})
104+
)
89105
}
90106

91107
export function findClassListsInCssRange(doc: TextDocument, range?: Range): DocumentClassList[] {
@@ -182,15 +198,15 @@ export async function findClassListsInHtmlRange(
182198
state: State,
183199
doc: TextDocument,
184200
range?: Range
185-
): Promise<DocumentClassList[]> {
201+
): Promise<Array<DocumentClassList | DocumentClassList[]>> {
186202
const text = doc.getText(range)
187203

188204
const matches = matchClassAttributes(
189205
text,
190206
(await state.editor.getConfiguration(doc.uri)).tailwindCSS.classAttributes
191207
)
192208

193-
const result: DocumentClassList[] = []
209+
const result: Array<DocumentClassList | DocumentClassList[]> = []
194210

195211
matches.forEach((match) => {
196212
const subtext = text.substr(match.index + match[0].length - 1)
@@ -201,9 +217,11 @@ export async function findClassListsInHtmlRange(
201217
: getClassAttributeLexer()
202218
lexer.reset(subtext)
203219

204-
let classLists: { value: string; offset: number }[] = []
205-
let token: moo.Token
220+
let classLists: Array<{ value: string; offset: number } | { value: string; offset: number }[]> =
221+
[]
222+
let rootClassList: { value: string; offset: number }[] = []
206223
let currentClassList: { value: string; offset: number }
224+
let depth = 0
207225

208226
try {
209227
for (let token of lexer) {
@@ -218,56 +236,53 @@ export async function findClassListsInHtmlRange(
218236
}
219237
} else {
220238
if (currentClassList) {
221-
classLists.push({
222-
value: currentClassList.value,
223-
offset: currentClassList.offset,
224-
})
239+
if (depth === 0) {
240+
rootClassList.push({
241+
value: currentClassList.value,
242+
offset: currentClassList.offset,
243+
})
244+
} else {
245+
classLists.push({
246+
value: currentClassList.value,
247+
offset: currentClassList.offset,
248+
})
249+
}
225250
}
226251
currentClassList = undefined
227252
}
253+
if (token.type === 'lbrace') {
254+
depth += 1
255+
} else if (token.type === 'rbrace') {
256+
depth -= 1
257+
}
228258
}
229259
} catch (_) {}
230260

231261
if (currentClassList) {
232-
classLists.push({
233-
value: currentClassList.value,
234-
offset: currentClassList.offset,
235-
})
262+
if (depth === 0) {
263+
rootClassList.push({
264+
value: currentClassList.value,
265+
offset: currentClassList.offset,
266+
})
267+
} else {
268+
classLists.push({
269+
value: currentClassList.value,
270+
offset: currentClassList.offset,
271+
})
272+
}
236273
}
237274

275+
classLists.push(rootClassList)
276+
238277
result.push(
239278
...classLists
240-
.map(({ value, offset }) => {
241-
if (value.trim() === '') {
242-
return null
243-
}
244-
245-
const before = value.match(/^\s*/)
246-
const beforeOffset = before === null ? 0 : before[0].length
247-
const after = value.match(/\s*$/)
248-
const afterOffset = after === null ? 0 : -after[0].length
249-
250-
const start = indexToPosition(
251-
text,
252-
match.index + match[0].length - 1 + offset + beforeOffset
253-
)
254-
const end = indexToPosition(
255-
text,
256-
match.index + match[0].length - 1 + offset + value.length + afterOffset
257-
)
258-
259-
return {
260-
classList: value.substr(beforeOffset, value.length + afterOffset),
261-
range: {
262-
start: {
263-
line: (range?.start.line || 0) + start.line,
264-
character: (end.line === 0 ? range?.start.character || 0 : 0) + start.character,
265-
},
266-
end: {
267-
line: (range?.start.line || 0) + end.line,
268-
character: (end.line === 0 ? range?.start.character || 0 : 0) + end.character,
269-
},
270-
},
279+
.map((classList) => {
280+
if (Array.isArray(classList)) {
281+
return classList
282+
.map((classList) => resolveClassList(classList, text, match, range))
283+
.filter((x) => x !== null)
284+
} else {
285+
return resolveClassList(classList, text, match, range)
271286
}
272287
})
273288
.filter((x) => x !== null)
@@ -277,14 +292,51 @@ export async function findClassListsInHtmlRange(
277292
return result
278293
}
279294

295+
function resolveClassList(
296+
classList: { value: string; offset: number },
297+
text: string,
298+
match: RegExpMatchArray,
299+
range?: Range
300+
): DocumentClassList {
301+
let { value, offset } = classList
302+
if (value.trim() === '') {
303+
return null
304+
}
305+
306+
const before = value.match(/^\s*/)
307+
const beforeOffset = before === null ? 0 : before[0].length
308+
const after = value.match(/\s*$/)
309+
const afterOffset = after === null ? 0 : -after[0].length
310+
311+
const start = indexToPosition(text, match.index + match[0].length - 1 + offset + beforeOffset)
312+
const end = indexToPosition(
313+
text,
314+
match.index + match[0].length - 1 + offset + value.length + afterOffset
315+
)
316+
317+
return {
318+
classList: value.substr(beforeOffset, value.length + afterOffset),
319+
range: {
320+
start: {
321+
line: (range?.start.line || 0) + start.line,
322+
character: (end.line === 0 ? range?.start.character || 0 : 0) + start.character,
323+
},
324+
end: {
325+
line: (range?.start.line || 0) + end.line,
326+
character: (end.line === 0 ? range?.start.character || 0 : 0) + end.character,
327+
},
328+
},
329+
}
330+
}
331+
280332
export async function findClassListsInRange(
281333
state: State,
282334
doc: TextDocument,
283335
range?: Range,
284336
mode?: 'html' | 'css',
285337
includeCustom: boolean = true
286-
): Promise<DocumentClassList[]> {
287-
let classLists: DocumentClassList[]
338+
): Promise<Array<DocumentClassList | DocumentClassList[]>> {
339+
let classLists: Array<DocumentClassList | DocumentClassList[]>
288340
if (mode === 'css') {
289341
classLists = findClassListsInCssRange(doc, range)
290342
} else {
@@ -296,7 +348,7 @@ export async function findClassListsInRange(
296348
export async function findClassListsInDocument(
297349
state: State,
298350
doc: TextDocument
299-
): Promise<DocumentClassList[]> {
351+
): Promise<Array<DocumentClassList | DocumentClassList[]>> {
300352
if (isCssDoc(state, doc)) {
301353
return findClassListsInCssRange(doc)
302354
}

0 commit comments

Comments
 (0)