enable diagnostics in multi-language documents
parent
81aad28bc8
commit
4f17e2358d
|
@ -2,6 +2,7 @@ import {
|
||||||
TextDocument,
|
TextDocument,
|
||||||
Diagnostic,
|
Diagnostic,
|
||||||
DiagnosticSeverity,
|
DiagnosticSeverity,
|
||||||
|
Range,
|
||||||
} from 'vscode-languageserver'
|
} from 'vscode-languageserver'
|
||||||
import { State, Settings } from '../util/state'
|
import { State, Settings } from '../util/state'
|
||||||
import { isCssDoc } from '../util/css'
|
import { isCssDoc } from '../util/css'
|
||||||
|
@ -18,6 +19,8 @@ import { equal, flatten } from '../../util/array'
|
||||||
import { getDocumentSettings } from '../util/getDocumentSettings'
|
import { getDocumentSettings } from '../util/getDocumentSettings'
|
||||||
const dlv = require('dlv')
|
const dlv = require('dlv')
|
||||||
import semver from 'semver'
|
import semver from 'semver'
|
||||||
|
import { getLanguageBoundaries } from '../util/getLanguageBoundaries'
|
||||||
|
import { absoluteRange } from '../util/absoluteRange'
|
||||||
|
|
||||||
function getUnsupportedApplyDiagnostics(
|
function getUnsupportedApplyDiagnostics(
|
||||||
state: State,
|
state: State,
|
||||||
|
@ -140,35 +143,51 @@ function getUnknownScreenDiagnostics(
|
||||||
let severity = settings.lint.unknownScreen
|
let severity = settings.lint.unknownScreen
|
||||||
if (severity === 'ignore') return []
|
if (severity === 'ignore') return []
|
||||||
|
|
||||||
let text = document.getText()
|
let diagnostics: Diagnostic[] = []
|
||||||
|
let ranges: Range[] = []
|
||||||
|
|
||||||
|
if (isCssDoc(state, document)) {
|
||||||
|
ranges.push(undefined)
|
||||||
|
} else {
|
||||||
|
let boundaries = getLanguageBoundaries(state, document)
|
||||||
|
if (!boundaries) return []
|
||||||
|
ranges.push(...boundaries.css)
|
||||||
|
}
|
||||||
|
|
||||||
|
ranges.forEach((range) => {
|
||||||
|
let text = document.getText(range)
|
||||||
let matches = findAll(/(?:\s|^)@screen\s+(?<screen>[^\s{]+)/g, text)
|
let matches = findAll(/(?:\s|^)@screen\s+(?<screen>[^\s{]+)/g, text)
|
||||||
|
|
||||||
let screens = Object.keys(
|
let screens = Object.keys(
|
||||||
dlv(state.config, 'theme.screens', dlv(state.config, 'screens', {}))
|
dlv(state.config, 'theme.screens', dlv(state.config, 'screens', {}))
|
||||||
)
|
)
|
||||||
|
|
||||||
return matches
|
matches.forEach((match) => {
|
||||||
.map((match) => {
|
|
||||||
if (screens.includes(match.groups.screen)) {
|
if (screens.includes(match.groups.screen)) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
diagnostics.push({
|
||||||
range: {
|
range: absoluteRange(
|
||||||
|
{
|
||||||
start: indexToPosition(
|
start: indexToPosition(
|
||||||
text,
|
text,
|
||||||
match.index + match[0].length - match.groups.screen.length
|
match.index + match[0].length - match.groups.screen.length
|
||||||
),
|
),
|
||||||
end: indexToPosition(text, match.index + match[0].length),
|
end: indexToPosition(text, match.index + match[0].length),
|
||||||
},
|
},
|
||||||
|
range
|
||||||
|
),
|
||||||
severity:
|
severity:
|
||||||
severity === 'error'
|
severity === 'error'
|
||||||
? DiagnosticSeverity.Error
|
? DiagnosticSeverity.Error
|
||||||
: DiagnosticSeverity.Warning,
|
: DiagnosticSeverity.Warning,
|
||||||
message: 'Unknown screen',
|
message: 'Unknown screen',
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.filter(Boolean)
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
return diagnostics
|
||||||
}
|
}
|
||||||
|
|
||||||
function getUnknownVariantDiagnostics(
|
function getUnknownVariantDiagnostics(
|
||||||
|
@ -179,13 +198,22 @@ function getUnknownVariantDiagnostics(
|
||||||
let severity = settings.lint.unknownVariant
|
let severity = settings.lint.unknownVariant
|
||||||
if (severity === 'ignore') return []
|
if (severity === 'ignore') return []
|
||||||
|
|
||||||
let text = document.getText()
|
let diagnostics: Diagnostic[] = []
|
||||||
|
let ranges: Range[] = []
|
||||||
|
|
||||||
|
if (isCssDoc(state, document)) {
|
||||||
|
ranges.push(undefined)
|
||||||
|
} else {
|
||||||
|
let boundaries = getLanguageBoundaries(state, document)
|
||||||
|
if (!boundaries) return []
|
||||||
|
ranges.push(...boundaries.css)
|
||||||
|
}
|
||||||
|
|
||||||
|
ranges.forEach((range) => {
|
||||||
|
let text = document.getText(range)
|
||||||
let matches = findAll(/(?:\s|^)@variants\s+(?<variants>[^{]+)/g, text)
|
let matches = findAll(/(?:\s|^)@variants\s+(?<variants>[^{]+)/g, text)
|
||||||
|
|
||||||
return flatten(
|
matches.forEach((match) => {
|
||||||
matches
|
|
||||||
.map((match) => {
|
|
||||||
let diagnostics: Diagnostic[] = []
|
|
||||||
let variants = match.groups.variants.split(/(\s*,\s*)/)
|
let variants = match.groups.variants.split(/(\s*,\s*)/)
|
||||||
let listStartIndex =
|
let listStartIndex =
|
||||||
match.index + match[0].length - match.groups.variants.length
|
match.index + match[0].length - match.groups.variants.length
|
||||||
|
@ -200,10 +228,13 @@ function getUnknownVariantDiagnostics(
|
||||||
listStartIndex + variants.slice(0, i).join('').length
|
listStartIndex + variants.slice(0, i).join('').length
|
||||||
|
|
||||||
diagnostics.push({
|
diagnostics.push({
|
||||||
range: {
|
range: absoluteRange(
|
||||||
|
{
|
||||||
start: indexToPosition(text, variantStartIndex),
|
start: indexToPosition(text, variantStartIndex),
|
||||||
end: indexToPosition(text, variantStartIndex + variant.length),
|
end: indexToPosition(text, variantStartIndex + variant.length),
|
||||||
},
|
},
|
||||||
|
range
|
||||||
|
),
|
||||||
severity:
|
severity:
|
||||||
severity === 'error'
|
severity === 'error'
|
||||||
? DiagnosticSeverity.Error
|
? DiagnosticSeverity.Error
|
||||||
|
@ -211,11 +242,10 @@ function getUnknownVariantDiagnostics(
|
||||||
message: `Unknown variant: ${variant}`,
|
message: `Unknown variant: ${variant}`,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
return diagnostics
|
return diagnostics
|
||||||
})
|
|
||||||
.filter(Boolean)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getUnknownConfigKeyDiagnostics(
|
function getUnknownConfigKeyDiagnostics(
|
||||||
|
@ -226,14 +256,25 @@ function getUnknownConfigKeyDiagnostics(
|
||||||
let severity = settings.lint.unknownConfigKey
|
let severity = settings.lint.unknownConfigKey
|
||||||
if (severity === 'ignore') return []
|
if (severity === 'ignore') return []
|
||||||
|
|
||||||
let text = document.getText()
|
let diagnostics: Diagnostic[] = []
|
||||||
|
let ranges: Range[] = []
|
||||||
|
|
||||||
|
if (isCssDoc(state, document)) {
|
||||||
|
ranges.push(undefined)
|
||||||
|
} else {
|
||||||
|
let boundaries = getLanguageBoundaries(state, document)
|
||||||
|
if (!boundaries) return []
|
||||||
|
ranges.push(...boundaries.css)
|
||||||
|
}
|
||||||
|
|
||||||
|
ranges.forEach((range) => {
|
||||||
|
let text = document.getText(range)
|
||||||
let matches = findAll(
|
let matches = findAll(
|
||||||
/(?<prefix>\s|^)(?<helper>config|theme)\((?<quote>['"])(?<key>[^)]+)\k<quote>\)/g,
|
/(?<prefix>\s|^)(?<helper>config|theme)\((?<quote>['"])(?<key>[^)]+)\k<quote>\)/g,
|
||||||
text
|
text
|
||||||
)
|
)
|
||||||
|
|
||||||
return matches
|
matches.forEach((match) => {
|
||||||
.map((match) => {
|
|
||||||
let base = match.groups.helper === 'theme' ? ['theme'] : []
|
let base = match.groups.helper === 'theme' ? ['theme'] : []
|
||||||
let keys = match.groups.key.split(/[.\[\]]/).filter(Boolean)
|
let keys = match.groups.key.split(/[.\[\]]/).filter(Boolean)
|
||||||
let value = dlv(state.config, [...base, ...keys])
|
let value = dlv(state.config, [...base, ...keys])
|
||||||
|
@ -251,19 +292,24 @@ function getUnknownConfigKeyDiagnostics(
|
||||||
1 + // open paren
|
1 + // open paren
|
||||||
match.groups.quote.length
|
match.groups.quote.length
|
||||||
|
|
||||||
return {
|
diagnostics.push({
|
||||||
range: {
|
range: absoluteRange(
|
||||||
|
{
|
||||||
start: indexToPosition(text, startIndex),
|
start: indexToPosition(text, startIndex),
|
||||||
end: indexToPosition(text, startIndex + match.groups.key.length),
|
end: indexToPosition(text, startIndex + match.groups.key.length),
|
||||||
},
|
},
|
||||||
|
range
|
||||||
|
),
|
||||||
severity:
|
severity:
|
||||||
severity === 'error'
|
severity === 'error'
|
||||||
? DiagnosticSeverity.Error
|
? DiagnosticSeverity.Error
|
||||||
: DiagnosticSeverity.Warning,
|
: DiagnosticSeverity.Warning,
|
||||||
message: `Unknown ${match.groups.helper} key: ${match.groups.key}`,
|
message: `Unknown ${match.groups.helper} key: ${match.groups.key}`,
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.filter(Boolean)
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
return diagnostics
|
||||||
}
|
}
|
||||||
|
|
||||||
function getUnsupportedTailwindDirectiveDiagnostics(
|
function getUnsupportedTailwindDirectiveDiagnostics(
|
||||||
|
@ -274,30 +320,44 @@ function getUnsupportedTailwindDirectiveDiagnostics(
|
||||||
let severity = settings.lint.unsupportedTailwindDirective
|
let severity = settings.lint.unsupportedTailwindDirective
|
||||||
if (severity === 'ignore') return []
|
if (severity === 'ignore') return []
|
||||||
|
|
||||||
let text = document.getText()
|
let diagnostics: Diagnostic[] = []
|
||||||
|
let ranges: Range[] = []
|
||||||
|
|
||||||
|
if (isCssDoc(state, document)) {
|
||||||
|
ranges.push(undefined)
|
||||||
|
} else {
|
||||||
|
let boundaries = getLanguageBoundaries(state, document)
|
||||||
|
if (!boundaries) return []
|
||||||
|
ranges.push(...boundaries.css)
|
||||||
|
}
|
||||||
|
|
||||||
|
ranges.forEach((range) => {
|
||||||
|
let text = document.getText(range)
|
||||||
let matches = findAll(/(?:\s|^)@tailwind\s+(?<value>[^;]+)/g, text)
|
let matches = findAll(/(?:\s|^)@tailwind\s+(?<value>[^;]+)/g, text)
|
||||||
|
|
||||||
let allowed = [
|
let valid = [
|
||||||
'utilities',
|
'utilities',
|
||||||
'components',
|
'components',
|
||||||
'screens',
|
'screens',
|
||||||
semver.gte(state.version, '1.0.0-beta.1') ? 'base' : 'preflight',
|
semver.gte(state.version, '1.0.0-beta.1') ? 'base' : 'preflight',
|
||||||
]
|
]
|
||||||
|
|
||||||
return matches
|
matches.forEach((match) => {
|
||||||
.map((match) => {
|
if (valid.includes(match.groups.value)) {
|
||||||
if (allowed.includes(match.groups.value)) {
|
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
diagnostics.push({
|
||||||
range: {
|
range: absoluteRange(
|
||||||
|
{
|
||||||
start: indexToPosition(
|
start: indexToPosition(
|
||||||
text,
|
text,
|
||||||
match.index + match[0].length - match.groups.value.length
|
match.index + match[0].length - match.groups.value.length
|
||||||
),
|
),
|
||||||
end: indexToPosition(text, match.index + match[0].length),
|
end: indexToPosition(text, match.index + match[0].length),
|
||||||
},
|
},
|
||||||
|
range
|
||||||
|
),
|
||||||
severity:
|
severity:
|
||||||
severity === 'error'
|
severity === 'error'
|
||||||
? DiagnosticSeverity.Error
|
? DiagnosticSeverity.Error
|
||||||
|
@ -305,9 +365,11 @@ function getUnsupportedTailwindDirectiveDiagnostics(
|
||||||
message: `Unsupported value: ${match.groups.value}${
|
message: `Unsupported value: ${match.groups.value}${
|
||||||
match.groups.value === 'preflight' ? '. Use base instead.' : ''
|
match.groups.value === 'preflight' ? '. Use base instead.' : ''
|
||||||
}`,
|
}`,
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.filter(Boolean)
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
return diagnostics
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function provideDiagnostics(
|
export async function provideDiagnostics(
|
||||||
|
@ -319,8 +381,6 @@ export async function provideDiagnostics(
|
||||||
const diagnostics: Diagnostic[] = settings.validate
|
const diagnostics: Diagnostic[] = settings.validate
|
||||||
? [
|
? [
|
||||||
...getUtilityConflictDiagnostics(state, document, settings),
|
...getUtilityConflictDiagnostics(state, document, settings),
|
||||||
...(isCssDoc(state, document)
|
|
||||||
? [
|
|
||||||
...getUnsupportedApplyDiagnostics(state, document, settings),
|
...getUnsupportedApplyDiagnostics(state, document, settings),
|
||||||
...getUnknownScreenDiagnostics(state, document, settings),
|
...getUnknownScreenDiagnostics(state, document, settings),
|
||||||
...getUnknownVariantDiagnostics(state, document, settings),
|
...getUnknownVariantDiagnostics(state, document, settings),
|
||||||
|
@ -331,8 +391,6 @@ export async function provideDiagnostics(
|
||||||
settings
|
settings
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
: []),
|
|
||||||
]
|
|
||||||
: []
|
: []
|
||||||
|
|
||||||
state.editor.connection.sendDiagnostics({
|
state.editor.connection.sendDiagnostics({
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
import { Range } from 'vscode-languageserver'
|
||||||
|
|
||||||
|
export function absoluteRange(range: Range, reference?: Range) {
|
||||||
|
return {
|
||||||
|
start: {
|
||||||
|
line: (reference?.start.line || 0) + range.start.line,
|
||||||
|
character:
|
||||||
|
(range.end.line === 0 ? reference?.start.character || 0 : 0) +
|
||||||
|
range.start.character,
|
||||||
|
},
|
||||||
|
end: {
|
||||||
|
line: (reference?.start.line || 0) + range.end.line,
|
||||||
|
character:
|
||||||
|
(range.end.line === 0 ? reference?.start.character || 0 : 0) +
|
||||||
|
range.end.character,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,6 +10,7 @@ import {
|
||||||
getClassAttributeLexer,
|
getClassAttributeLexer,
|
||||||
getComputedClassAttributeLexer,
|
getComputedClassAttributeLexer,
|
||||||
} from './lexers'
|
} from './lexers'
|
||||||
|
import { getLanguageBoundaries } from './getLanguageBoundaries'
|
||||||
|
|
||||||
export function findAll(re: RegExp, str: string): RegExpMatchArray[] {
|
export function findAll(re: RegExp, str: string): RegExpMatchArray[] {
|
||||||
let match: RegExpMatchArray
|
let match: RegExpMatchArray
|
||||||
|
@ -230,70 +231,13 @@ export function findClassListsInDocument(
|
||||||
return findClassListsInCssRange(doc)
|
return findClassListsInCssRange(doc)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isVueDoc(doc)) {
|
let boundaries = getLanguageBoundaries(state, doc)
|
||||||
let text = doc.getText()
|
if (!boundaries) return []
|
||||||
let blocks = findAll(
|
|
||||||
/<(?<type>template|style|script)\b[^>]*>.*?(<\/\k<type>>|$)/gis,
|
|
||||||
text
|
|
||||||
)
|
|
||||||
let htmlRanges: Range[] = []
|
|
||||||
let cssRanges: Range[] = []
|
|
||||||
for (let i = 0; i < blocks.length; i++) {
|
|
||||||
let range = {
|
|
||||||
start: indexToPosition(text, blocks[i].index),
|
|
||||||
end: indexToPosition(text, blocks[i].index + blocks[i][0].length),
|
|
||||||
}
|
|
||||||
if (blocks[i].groups.type === 'style') {
|
|
||||||
cssRanges.push(range)
|
|
||||||
} else {
|
|
||||||
htmlRanges.push(range)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return [].concat.apply(
|
|
||||||
[],
|
|
||||||
[
|
|
||||||
...htmlRanges.map((range) => findClassListsInHtmlRange(doc, range)),
|
|
||||||
...cssRanges.map((range) => findClassListsInCssRange(doc, range)),
|
|
||||||
]
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isHtmlDoc(state, doc) || isJsDoc(state, doc) || isSvelteDoc(doc)) {
|
return flatten([
|
||||||
let text = doc.getText()
|
...boundaries.html.map((range) => findClassListsInHtmlRange(doc, range)),
|
||||||
let styleBlocks = findAll(/<style(?:\s[^>]*>|>).*?(<\/style>|$)/gis, text)
|
...boundaries.css.map((range) => findClassListsInCssRange(doc, range)),
|
||||||
let htmlRanges: Range[] = []
|
])
|
||||||
let cssRanges: Range[] = []
|
|
||||||
let currentIndex = 0
|
|
||||||
|
|
||||||
for (let i = 0; i < styleBlocks.length; i++) {
|
|
||||||
htmlRanges.push({
|
|
||||||
start: indexToPosition(text, currentIndex),
|
|
||||||
end: indexToPosition(text, styleBlocks[i].index),
|
|
||||||
})
|
|
||||||
cssRanges.push({
|
|
||||||
start: indexToPosition(text, styleBlocks[i].index),
|
|
||||||
end: indexToPosition(
|
|
||||||
text,
|
|
||||||
styleBlocks[i].index + styleBlocks[i][0].length
|
|
||||||
),
|
|
||||||
})
|
|
||||||
currentIndex = styleBlocks[i].index + styleBlocks[i][0].length
|
|
||||||
}
|
|
||||||
htmlRanges.push({
|
|
||||||
start: indexToPosition(text, currentIndex),
|
|
||||||
end: indexToPosition(text, text.length),
|
|
||||||
})
|
|
||||||
|
|
||||||
return [].concat.apply(
|
|
||||||
[],
|
|
||||||
[
|
|
||||||
...htmlRanges.map((range) => findClassListsInHtmlRange(doc, range)),
|
|
||||||
...cssRanges.map((range) => findClassListsInCssRange(doc, range)),
|
|
||||||
]
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return []
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function indexToPosition(str: string, index: number): Position {
|
export function indexToPosition(str: string, index: number): Position {
|
||||||
|
|
|
@ -0,0 +1,75 @@
|
||||||
|
import { TextDocument, Range } from 'vscode-languageserver'
|
||||||
|
import { isVueDoc, isHtmlDoc, isSvelteDoc } from './html'
|
||||||
|
import { State } from './state'
|
||||||
|
import { findAll, indexToPosition } from './find'
|
||||||
|
import { isJsDoc } from './js'
|
||||||
|
|
||||||
|
export interface LanguageBoundaries {
|
||||||
|
html: Range[]
|
||||||
|
css: Range[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getLanguageBoundaries(
|
||||||
|
state: State,
|
||||||
|
doc: TextDocument
|
||||||
|
): LanguageBoundaries | null {
|
||||||
|
if (isVueDoc(doc)) {
|
||||||
|
let text = doc.getText()
|
||||||
|
let blocks = findAll(
|
||||||
|
/<(?<type>template|style|script)\b[^>]*>.*?(<\/\k<type>>|$)/gis,
|
||||||
|
text
|
||||||
|
)
|
||||||
|
let htmlRanges: Range[] = []
|
||||||
|
let cssRanges: Range[] = []
|
||||||
|
for (let i = 0; i < blocks.length; i++) {
|
||||||
|
let range = {
|
||||||
|
start: indexToPosition(text, blocks[i].index),
|
||||||
|
end: indexToPosition(text, blocks[i].index + blocks[i][0].length),
|
||||||
|
}
|
||||||
|
if (blocks[i].groups.type === 'style') {
|
||||||
|
cssRanges.push(range)
|
||||||
|
} else {
|
||||||
|
htmlRanges.push(range)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
html: htmlRanges,
|
||||||
|
css: cssRanges,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isHtmlDoc(state, doc) || isJsDoc(state, doc) || isSvelteDoc(doc)) {
|
||||||
|
let text = doc.getText()
|
||||||
|
let styleBlocks = findAll(/<style(?:\s[^>]*>|>).*?(<\/style>|$)/gis, text)
|
||||||
|
let htmlRanges: Range[] = []
|
||||||
|
let cssRanges: Range[] = []
|
||||||
|
let currentIndex = 0
|
||||||
|
|
||||||
|
for (let i = 0; i < styleBlocks.length; i++) {
|
||||||
|
htmlRanges.push({
|
||||||
|
start: indexToPosition(text, currentIndex),
|
||||||
|
end: indexToPosition(text, styleBlocks[i].index),
|
||||||
|
})
|
||||||
|
cssRanges.push({
|
||||||
|
start: indexToPosition(text, styleBlocks[i].index),
|
||||||
|
end: indexToPosition(
|
||||||
|
text,
|
||||||
|
styleBlocks[i].index + styleBlocks[i][0].length
|
||||||
|
),
|
||||||
|
})
|
||||||
|
currentIndex = styleBlocks[i].index + styleBlocks[i][0].length
|
||||||
|
}
|
||||||
|
htmlRanges.push({
|
||||||
|
start: indexToPosition(text, currentIndex),
|
||||||
|
end: indexToPosition(text, text.length),
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
html: htmlRanges,
|
||||||
|
css: cssRanges,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
Loading…
Reference in New Issue