add initial class name conflict diagnostics

master
Brad Cornes 2020-06-12 11:41:39 +01:00
parent d5b5176756
commit de6273dad2
2 changed files with 116 additions and 64 deletions

View File

@ -5,10 +5,16 @@ import {
} from 'vscode-languageserver' } from 'vscode-languageserver'
import { State } from '../util/state' import { State } from '../util/state'
import { isCssDoc } from '../util/css' import { isCssDoc } from '../util/css'
import { findClassNamesInRange } from '../util/find' import {
findClassNamesInRange,
findClassListsInDocument,
getClassNamesInClassList,
} from '../util/find'
import { getClassNameMeta } from '../util/getClassNameMeta' import { getClassNameMeta } from '../util/getClassNameMeta'
import { getClassNameDecls } from '../util/getClassNameDecls'
import { equal } from '../../util/array'
function provideCssDiagnostics(state: State, document: TextDocument): void { function getCssDiagnostics(state: State, document: TextDocument): Diagnostic[] {
const classNames = findClassNamesInRange(document, undefined, 'css') const classNames = findClassNamesInRange(document, undefined, 'css')
let diagnostics: Diagnostic[] = classNames let diagnostics: Diagnostic[] = classNames
@ -46,38 +52,73 @@ function provideCssDiagnostics(state: State, document: TextDocument): void {
severity: DiagnosticSeverity.Error, severity: DiagnosticSeverity.Error,
range, range,
message, message,
// source: 'ex',
} }
}) })
.filter(Boolean) .filter(Boolean)
// if (state.editor.capabilities.diagnosticRelatedInformation) { return diagnostics
// diagnostic.relatedInformation = [ }
// {
// location: {
// uri: document.uri,
// range: Object.assign({}, diagnostic.range),
// },
// message: '',
// },
// {
// location: {
// uri: document.uri,
// range: Object.assign({}, diagnostic.range),
// },
// message: '',
// },
// ]
// }
state.editor.connection.sendDiagnostics({ uri: document.uri, diagnostics }) function getConflictDiagnostics(
state: State,
document: TextDocument
): Diagnostic[] {
let diagnostics: Diagnostic[] = []
const classLists = findClassListsInDocument(state, document)
classLists.forEach((classList) => {
const classNames = getClassNamesInClassList(classList)
classNames.forEach((className, index) => {
let otherClassNames = classNames.filter((_className, i) => i !== index)
otherClassNames.forEach((otherClassName) => {
let decls = getClassNameDecls(state, className.className)
if (!decls) return
let otherDecls = getClassNameDecls(state, otherClassName.className)
if (!otherDecls) return
let meta = getClassNameMeta(state, className.className)
let otherMeta = getClassNameMeta(state, otherClassName.className)
if (
equal(Object.keys(decls), Object.keys(otherDecls)) &&
!Array.isArray(meta) &&
!Array.isArray(otherMeta) &&
equal(meta.context, otherMeta.context) &&
equal(meta.pseudo, otherMeta.pseudo)
) {
diagnostics.push({
range: className.range,
severity: DiagnosticSeverity.Warning,
message: `You cant use \`${className.className}\` and \`${otherClassName.className}\` together`,
relatedInformation: [
{
message: otherClassName.className,
location: {
uri: document.uri,
range: otherClassName.range,
},
},
],
})
}
})
})
})
return diagnostics
} }
export async function provideDiagnostics( export async function provideDiagnostics(
state: State, state: State,
document: TextDocument document: TextDocument
): Promise<void> { ): Promise<void> {
if (isCssDoc(state, document)) { state.editor.connection.sendDiagnostics({
return provideCssDiagnostics(state, document) uri: document.uri,
} diagnostics: [
...getConflictDiagnostics(state, document),
...(isCssDoc(state, document) ? getCssDiagnostics(state, document) : []),
],
})
} }

View File

@ -6,6 +6,7 @@ import { isHtmlContext, isHtmlDoc, isSvelteDoc, isVueDoc } from './html'
import { isWithinRange } from './isWithinRange' import { isWithinRange } from './isWithinRange'
import { isJsContext, isJsDoc } from './js' import { isJsContext, isJsDoc } from './js'
import { getClassAttributeLexer } from './lexers' import { getClassAttributeLexer } from './lexers'
import { flatten } from '../../util/array'
export function findAll(re: RegExp, str: string): RegExpMatchArray[] { export function findAll(re: RegExp, str: string): RegExpMatchArray[] {
let match: RegExpMatchArray let match: RegExpMatchArray
@ -24,15 +25,10 @@ export function findLast(re: RegExp, str: string): RegExpMatchArray {
return matches[matches.length - 1] return matches[matches.length - 1]
} }
export function findClassNamesInRange( export function getClassNamesInClassList({
doc: TextDocument, classList,
range?: Range, range,
mode?: 'html' | 'css' }: DocumentClassList): DocumentClassName[] {
): DocumentClassName[] {
const classLists = findClassListsInRange(doc, range, mode)
return [].concat.apply(
[],
classLists.map(({ classList, range }) => {
const parts = classList.split(/(\s+)/) const parts = classList.split(/(\s+)/)
const names: DocumentClassName[] = [] const names: DocumentClassName[] = []
let index = 0 let index = 0
@ -46,8 +42,7 @@ export function findClassNamesInRange(
start: { start: {
line: range.start.line + start.line, line: range.start.line + start.line,
character: character:
(end.line === 0 ? range.start.character : 0) + (end.line === 0 ? range.start.character : 0) + start.character,
start.character,
}, },
end: { end: {
line: range.start.line + end.line, line: range.start.line + end.line,
@ -60,8 +55,23 @@ export function findClassNamesInRange(
index += parts[i].length index += parts[i].length
} }
return names return names
}) }
)
export function findClassNamesInRange(
doc: TextDocument,
range?: Range,
mode?: 'html' | 'css'
): DocumentClassName[] {
const classLists = findClassListsInRange(doc, range, mode)
return flatten(classLists.map(getClassNamesInClassList))
}
export function findClassNamesInDocument(
state: State,
doc: TextDocument
): DocumentClassName[] {
const classLists = findClassListsInDocument(state, doc)
return flatten(classLists.map(getClassNamesInClassList))
} }
export function findClassListsInCssRange( export function findClassListsInCssRange(
@ -98,7 +108,7 @@ export function findClassListsInCssRange(
export function findClassListsInHtmlRange( export function findClassListsInHtmlRange(
doc: TextDocument, doc: TextDocument,
range: Range range?: Range
): DocumentClassList[] { ): DocumentClassList[] {
const text = doc.getText(range) const text = doc.getText(range)
const matches = findAll(/[\s:]class(?:Name)?=['"`{]/g, text) const matches = findAll(/[\s:]class(?:Name)?=['"`{]/g, text)
@ -174,15 +184,16 @@ export function findClassListsInHtmlRange(
classList: value, classList: value,
range: { range: {
start: { start: {
line: range.start.line + start.line, line: (range?.start.line || 0) + start.line,
character: character:
(end.line === 0 ? range.start.character : 0) + (end.line === 0 ? range?.start.character || 0 : 0) +
start.character, start.character,
}, },
end: { end: {
line: range.start.line + end.line, line: (range?.start.line || 0) + end.line,
character: character:
(end.line === 0 ? range.start.character : 0) + end.character, (end.line === 0 ? range?.start.character || 0 : 0) +
end.character,
}, },
}, },
} }
@ -196,8 +207,8 @@ export function findClassListsInHtmlRange(
export function findClassListsInRange( export function findClassListsInRange(
doc: TextDocument, doc: TextDocument,
range: Range, range?: Range,
mode: 'html' | 'css' mode?: 'html' | 'css'
): DocumentClassList[] { ): DocumentClassList[] {
if (mode === 'css') { if (mode === 'css') {
return findClassListsInCssRange(doc, range) return findClassListsInCssRange(doc, range)