From ddfaea21cc5feca1bbf71ddb36645a61f01daea3 Mon Sep 17 00:00:00 2001 From: Brad Cornes Date: Fri, 4 Mar 2022 15:41:44 +0000 Subject: [PATCH] Improve conflict diagnostics (#503) --- .../diagnostics/getCssConflictDiagnostics.ts | 4 +- .../getRecommendedVariantOrderDiagnostics.ts | 2 +- .../src/documentColorProvider.ts | 2 +- .../src/util/find.ts | 148 ++++++++++++------ 4 files changed, 105 insertions(+), 51 deletions(-) diff --git a/packages/tailwindcss-language-service/src/diagnostics/getCssConflictDiagnostics.ts b/packages/tailwindcss-language-service/src/diagnostics/getCssConflictDiagnostics.ts index 4459d24..e73aab6 100644 --- a/packages/tailwindcss-language-service/src/diagnostics/getCssConflictDiagnostics.ts +++ b/packages/tailwindcss-language-service/src/diagnostics/getCssConflictDiagnostics.ts @@ -20,7 +20,9 @@ export async function getCssConflictDiagnostics( const classLists = await findClassListsInDocument(state, document) classLists.forEach((classList) => { - const classNames = getClassNamesInClassList(classList) + const classNames = Array.isArray(classList) + ? classList.flatMap(getClassNamesInClassList) + : getClassNamesInClassList(classList) classNames.forEach((className, index) => { if (state.jit) { diff --git a/packages/tailwindcss-language-service/src/diagnostics/getRecommendedVariantOrderDiagnostics.ts b/packages/tailwindcss-language-service/src/diagnostics/getRecommendedVariantOrderDiagnostics.ts index 2361957..51e8e82 100644 --- a/packages/tailwindcss-language-service/src/diagnostics/getRecommendedVariantOrderDiagnostics.ts +++ b/packages/tailwindcss-language-service/src/diagnostics/getRecommendedVariantOrderDiagnostics.ts @@ -22,7 +22,7 @@ export async function getRecommendedVariantOrderDiagnostics( let diagnostics: RecommendedVariantOrderDiagnostic[] = [] const classLists = await findClassListsInDocument(state, document) - classLists.forEach((classList) => { + classLists.flat().forEach((classList) => { const classNames = getClassNamesInClassList(classList) classNames.forEach((className) => { let { rules } = jit.generateRules(state, [className.className]) diff --git a/packages/tailwindcss-language-service/src/documentColorProvider.ts b/packages/tailwindcss-language-service/src/documentColorProvider.ts index 081d1c0..f40b93a 100644 --- a/packages/tailwindcss-language-service/src/documentColorProvider.ts +++ b/packages/tailwindcss-language-service/src/documentColorProvider.ts @@ -20,7 +20,7 @@ export async function getDocumentColors( if (settings.tailwindCSS.colorDecorators === false) return colors let classLists = await findClassListsInDocument(state, document) - classLists.forEach((classList) => { + classLists.flat().forEach((classList) => { let classNames = getClassNamesInClassList(classList) classNames.forEach((className) => { let color = getColor(state, className.className) diff --git a/packages/tailwindcss-language-service/src/util/find.ts b/packages/tailwindcss-language-service/src/util/find.ts index b52d685..8471910 100644 --- a/packages/tailwindcss-language-service/src/util/find.ts +++ b/packages/tailwindcss-language-service/src/util/find.ts @@ -77,7 +77,15 @@ export async function findClassNamesInRange( includeCustom: boolean = true ): Promise { const classLists = await findClassListsInRange(state, doc, range, mode, includeCustom) - return flatten(classLists.map(getClassNamesInClassList)) + return flatten( + classLists.flatMap((classList) => { + if (Array.isArray(classList)) { + return classList.map(getClassNamesInClassList) + } else { + return [getClassNamesInClassList(classList)] + } + }) + ) } export async function findClassNamesInDocument( @@ -85,7 +93,15 @@ export async function findClassNamesInDocument( doc: TextDocument ): Promise { const classLists = await findClassListsInDocument(state, doc) - return flatten(classLists.map(getClassNamesInClassList)) + return flatten( + classLists.flatMap((classList) => { + if (Array.isArray(classList)) { + return classList.map(getClassNamesInClassList) + } else { + return [getClassNamesInClassList(classList)] + } + }) + ) } export function findClassListsInCssRange(doc: TextDocument, range?: Range): DocumentClassList[] { @@ -182,7 +198,7 @@ export async function findClassListsInHtmlRange( state: State, doc: TextDocument, range?: Range -): Promise { +): Promise> { const text = doc.getText(range) const matches = matchClassAttributes( @@ -190,7 +206,7 @@ export async function findClassListsInHtmlRange( (await state.editor.getConfiguration(doc.uri)).tailwindCSS.classAttributes ) - const result: DocumentClassList[] = [] + const result: Array = [] matches.forEach((match) => { const subtext = text.substr(match.index + match[0].length - 1) @@ -201,9 +217,11 @@ export async function findClassListsInHtmlRange( : getClassAttributeLexer() lexer.reset(subtext) - let classLists: { value: string; offset: number }[] = [] - let token: moo.Token + let classLists: Array<{ value: string; offset: number } | { value: string; offset: number }[]> = + [] + let rootClassList: { value: string; offset: number }[] = [] let currentClassList: { value: string; offset: number } + let depth = 0 try { for (let token of lexer) { @@ -218,56 +236,53 @@ export async function findClassListsInHtmlRange( } } else { if (currentClassList) { - classLists.push({ - value: currentClassList.value, - offset: currentClassList.offset, - }) + if (depth === 0) { + rootClassList.push({ + value: currentClassList.value, + offset: currentClassList.offset, + }) + } else { + classLists.push({ + value: currentClassList.value, + offset: currentClassList.offset, + }) + } } currentClassList = undefined } + if (token.type === 'lbrace') { + depth += 1 + } else if (token.type === 'rbrace') { + depth -= 1 + } } } catch (_) {} if (currentClassList) { - classLists.push({ - value: currentClassList.value, - offset: currentClassList.offset, - }) + if (depth === 0) { + rootClassList.push({ + value: currentClassList.value, + offset: currentClassList.offset, + }) + } else { + classLists.push({ + value: currentClassList.value, + offset: currentClassList.offset, + }) + } } + classLists.push(rootClassList) + result.push( ...classLists - .map(({ value, offset }) => { - if (value.trim() === '') { - return null - } - - const before = value.match(/^\s*/) - const beforeOffset = before === null ? 0 : before[0].length - const after = value.match(/\s*$/) - const afterOffset = after === null ? 0 : -after[0].length - - const start = indexToPosition( - text, - match.index + match[0].length - 1 + offset + beforeOffset - ) - const end = indexToPosition( - text, - match.index + match[0].length - 1 + offset + value.length + afterOffset - ) - - return { - classList: value.substr(beforeOffset, value.length + afterOffset), - range: { - start: { - line: (range?.start.line || 0) + start.line, - character: (end.line === 0 ? range?.start.character || 0 : 0) + start.character, - }, - end: { - line: (range?.start.line || 0) + end.line, - character: (end.line === 0 ? range?.start.character || 0 : 0) + end.character, - }, - }, + .map((classList) => { + if (Array.isArray(classList)) { + return classList + .map((classList) => resolveClassList(classList, text, match, range)) + .filter((x) => x !== null) + } else { + return resolveClassList(classList, text, match, range) } }) .filter((x) => x !== null) @@ -277,14 +292,51 @@ export async function findClassListsInHtmlRange( return result } +function resolveClassList( + classList: { value: string; offset: number }, + text: string, + match: RegExpMatchArray, + range?: Range +): DocumentClassList { + let { value, offset } = classList + if (value.trim() === '') { + return null + } + + const before = value.match(/^\s*/) + const beforeOffset = before === null ? 0 : before[0].length + const after = value.match(/\s*$/) + const afterOffset = after === null ? 0 : -after[0].length + + const start = indexToPosition(text, match.index + match[0].length - 1 + offset + beforeOffset) + const end = indexToPosition( + text, + match.index + match[0].length - 1 + offset + value.length + afterOffset + ) + + return { + classList: value.substr(beforeOffset, value.length + afterOffset), + range: { + start: { + line: (range?.start.line || 0) + start.line, + character: (end.line === 0 ? range?.start.character || 0 : 0) + start.character, + }, + end: { + line: (range?.start.line || 0) + end.line, + character: (end.line === 0 ? range?.start.character || 0 : 0) + end.character, + }, + }, + } +} + export async function findClassListsInRange( state: State, doc: TextDocument, range?: Range, mode?: 'html' | 'css', includeCustom: boolean = true -): Promise { - let classLists: DocumentClassList[] +): Promise> { + let classLists: Array if (mode === 'css') { classLists = findClassListsInCssRange(doc, range) } else { @@ -296,7 +348,7 @@ export async function findClassListsInRange( export async function findClassListsInDocument( state: State, doc: TextDocument -): Promise { +): Promise> { if (isCssDoc(state, doc)) { return findClassListsInCssRange(doc) }