Skip to content

Improve conflict diagnostics #503

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 4, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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])
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
148 changes: 100 additions & 48 deletions packages/tailwindcss-language-service/src/util/find.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,15 +77,31 @@ export async function findClassNamesInRange(
includeCustom: boolean = true
): Promise<DocumentClassName[]> {
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(
state: State,
doc: TextDocument
): Promise<DocumentClassName[]> {
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[] {
Expand Down Expand Up @@ -182,15 +198,15 @@ export async function findClassListsInHtmlRange(
state: State,
doc: TextDocument,
range?: Range
): Promise<DocumentClassList[]> {
): Promise<Array<DocumentClassList | DocumentClassList[]>> {
const text = doc.getText(range)

const matches = matchClassAttributes(
text,
(await state.editor.getConfiguration(doc.uri)).tailwindCSS.classAttributes
)

const result: DocumentClassList[] = []
const result: Array<DocumentClassList | DocumentClassList[]> = []

matches.forEach((match) => {
const subtext = text.substr(match.index + match[0].length - 1)
Expand All @@ -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) {
Expand All @@ -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)
Expand All @@ -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<DocumentClassList[]> {
let classLists: DocumentClassList[]
): Promise<Array<DocumentClassList | DocumentClassList[]>> {
let classLists: Array<DocumentClassList | DocumentClassList[]>
if (mode === 'css') {
classLists = findClassListsInCssRange(doc, range)
} else {
Expand All @@ -296,7 +348,7 @@ export async function findClassListsInRange(
export async function findClassListsInDocument(
state: State,
doc: TextDocument
): Promise<DocumentClassList[]> {
): Promise<Array<DocumentClassList | DocumentClassList[]>> {
if (isCssDoc(state, doc)) {
return findClassListsInCssRange(doc)
}
Expand Down