diff --git a/src/lsp/providers/diagnosticsProvider.ts b/src/lsp/providers/diagnosticsProvider.ts
index 866c07d56dabc00b1759e3d154f43154c872931c..bb59ff571ca903793d4eb34a45d4775e9e9beaca 100644
--- a/src/lsp/providers/diagnosticsProvider.ts
+++ b/src/lsp/providers/diagnosticsProvider.ts
@@ -2,6 +2,7 @@ import {
TextDocument,
Diagnostic,
DiagnosticSeverity,
+ Range,
} from 'vscode-languageserver'
import { State, Settings } from '../util/state'
import { isCssDoc } from '../util/css'
@@ -18,6 +19,8 @@ import { equal, flatten } from '../../util/array'
import { getDocumentSettings } from '../util/getDocumentSettings'
const dlv = require('dlv')
import semver from 'semver'
+import { getLanguageBoundaries } from '../util/getLanguageBoundaries'
+import { absoluteRange } from '../util/absoluteRange'
function getUnsupportedApplyDiagnostics(
state: State,
@@ -140,35 +143,51 @@ ): Diagnostic[] {
let severity = settings.lint.unknownScreen
if (severity === 'ignore') return []
- let text = document.getText()
- let matches = findAll(/(?:\s|^)@screen\s+(?<screen>[^\s{]+)/g, text)
+ let diagnostics: Diagnostic[] = []
+ let ranges: Range[] = []
- let screens = Object.keys(
- dlv(state.config, 'theme.screens', dlv(state.config, 'screens', {}))
- )
+ if (isCssDoc(state, document)) {
+ ranges.push(undefined)
+ } else {
+ let boundaries = getLanguageBoundaries(state, document)
+ if (!boundaries) return []
+ ranges.push(...boundaries.css)
+ }
- return matches
- .map((match) => {
+ ranges.forEach((range) => {
+ let text = document.getText(range)
+ let matches = findAll(/(?:\s|^)@screen\s+(?<screen>[^\s{]+)/g, text)
+
+ let screens = Object.keys(
+ dlv(state.config, 'theme.screens', dlv(state.config, 'screens', {}))
+ )
+
+ matches.forEach((match) => {
if (screens.includes(match.groups.screen)) {
return null
}
- return {
- range: {
- start: indexToPosition(
- text,
- match.index + match[0].length - match.groups.screen.length
- ),
- end: indexToPosition(text, match.index + match[0].length),
- },
+ diagnostics.push({
+ range: absoluteRange(
+ {
+ start: indexToPosition(
+ text,
+ match.index + match[0].length - match.groups.screen.length
+ ),
+ end: indexToPosition(text, match.index + match[0].length),
+ },
+ range
+ ),
severity:
severity === 'error'
? DiagnosticSeverity.Error
: DiagnosticSeverity.Warning,
message: 'Unknown screen',
- }
+ })
})
- .filter(Boolean)
+ })
+
+ return diagnostics
}
function getUnknownVariantDiagnostics(
@@ -179,43 +198,54 @@ ): Diagnostic[] {
let severity = settings.lint.unknownVariant
if (severity === 'ignore') return []
- let text = document.getText()
- let matches = findAll(/(?:\s|^)@variants\s+(?<variants>[^{]+)/g, text)
+ 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)
+ }
- return flatten(
- matches
- .map((match) => {
- let diagnostics: Diagnostic[] = []
- let variants = match.groups.variants.split(/(\s*,\s*)/)
- let listStartIndex =
- match.index + match[0].length - match.groups.variants.length
+ ranges.forEach((range) => {
+ let text = document.getText(range)
+ let matches = findAll(/(?:\s|^)@variants\s+(?<variants>[^{]+)/g, text)
+
+ matches.forEach((match) => {
+ let variants = match.groups.variants.split(/(\s*,\s*)/)
+ let listStartIndex =
+ match.index + match[0].length - match.groups.variants.length
- for (let i = 0; i < variants.length; i += 2) {
- let variant = variants[i].trim()
- if (state.variants.includes(variant)) {
- continue
- }
+ for (let i = 0; i < variants.length; i += 2) {
+ let variant = variants[i].trim()
+ if (state.variants.includes(variant)) {
+ continue
+ }
- let variantStartIndex =
- listStartIndex + variants.slice(0, i).join('').length
+ let variantStartIndex =
+ listStartIndex + variants.slice(0, i).join('').length
- diagnostics.push({
- range: {
+ diagnostics.push({
+ range: absoluteRange(
+ {
start: indexToPosition(text, variantStartIndex),
end: indexToPosition(text, variantStartIndex + variant.length),
},
- severity:
- severity === 'error'
- ? DiagnosticSeverity.Error
- : DiagnosticSeverity.Warning,
- message: `Unknown variant: ${variant}`,
- })
- }
+ range
+ ),
+ severity:
+ severity === 'error'
+ ? DiagnosticSeverity.Error
+ : DiagnosticSeverity.Warning,
+ message: `Unknown variant: ${variant}`,
+ })
+ }
+ })
+ })
- return diagnostics
- })
- .filter(Boolean)
- )
+ return diagnostics
}
function getUnknownConfigKeyDiagnostics(
@@ -226,14 +256,25 @@ ): Diagnostic[] {
let severity = settings.lint.unknownConfigKey
if (severity === 'ignore') return []
- let text = document.getText()
- let matches = findAll(
- /(?<prefix>\s|^)(?<helper>config|theme)\((?<quote>['"])(?<key>[^)]+)\k<quote>\)/g,
- text
- )
+ 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(
+ /(?<prefix>\s|^)(?<helper>config|theme)\((?<quote>['"])(?<key>[^)]+)\k<quote>\)/g,
+ text
+ )
- return matches
- .map((match) => {
+ matches.forEach((match) => {
let base = match.groups.helper === 'theme' ? ['theme'] : []
let keys = match.groups.key.split(/[.\[\]]/).filter(Boolean)
let value = dlv(state.config, [...base, ...keys])
@@ -251,19 +292,24 @@ match.groups.helper.length +
1 + // open paren
match.groups.quote.length
- return {
- range: {
- start: indexToPosition(text, startIndex),
- end: indexToPosition(text, startIndex + match.groups.key.length),
- },
+ diagnostics.push({
+ range: absoluteRange(
+ {
+ start: indexToPosition(text, startIndex),
+ end: indexToPosition(text, startIndex + match.groups.key.length),
+ },
+ range
+ ),
severity:
severity === 'error'
? DiagnosticSeverity.Error
: DiagnosticSeverity.Warning,
message: `Unknown ${match.groups.helper} key: ${match.groups.key}`,
- }
+ })
})
- .filter(Boolean)
+ })
+
+ return diagnostics
}
function getUnsupportedTailwindDirectiveDiagnostics(
@@ -274,30 +320,44 @@ ): Diagnostic[] {
let severity = settings.lint.unsupportedTailwindDirective
if (severity === 'ignore') return []
- let text = document.getText()
- let matches = findAll(/(?:\s|^)@tailwind\s+(?<value>[^;]+)/g, text)
+ 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 allowed = [
- 'utilities',
- 'components',
- 'screens',
- semver.gte(state.version, '1.0.0-beta.1') ? 'base' : 'preflight',
- ]
+ let valid = [
+ 'utilities',
+ 'components',
+ 'screens',
+ semver.gte(state.version, '1.0.0-beta.1') ? 'base' : 'preflight',
+ ]
- return matches
- .map((match) => {
- if (allowed.includes(match.groups.value)) {
+ matches.forEach((match) => {
+ if (valid.includes(match.groups.value)) {
return null
}
- return {
- range: {
- start: indexToPosition(
- text,
- match.index + match[0].length - match.groups.value.length
- ),
- end: indexToPosition(text, match.index + match[0].length),
- },
+ diagnostics.push({
+ range: absoluteRange(
+ {
+ start: indexToPosition(
+ text,
+ match.index + match[0].length - match.groups.value.length
+ ),
+ end: indexToPosition(text, match.index + match[0].length),
+ },
+ range
+ ),
severity:
severity === 'error'
? DiagnosticSeverity.Error
@@ -305,9 +365,11 @@ : DiagnosticSeverity.Warning,
message: `Unsupported value: ${match.groups.value}${
match.groups.value === 'preflight' ? '. Use base instead.' : ''
}`,
- }
+ })
})
- .filter(Boolean)
+ })
+
+ return diagnostics
}
export async function provideDiagnostics(
@@ -319,19 +381,15 @@
const diagnostics: Diagnostic[] = settings.validate
? [
...getUtilityConflictDiagnostics(state, document, settings),
- ...(isCssDoc(state, document)
- ? [
- ...getUnsupportedApplyDiagnostics(state, document, settings),
- ...getUnknownScreenDiagnostics(state, document, settings),
- ...getUnknownVariantDiagnostics(state, document, settings),
- ...getUnknownConfigKeyDiagnostics(state, document, settings),
- ...getUnsupportedTailwindDirectiveDiagnostics(
- state,
- document,
- settings
- ),
- ]
- : []),
+ ...getUnsupportedApplyDiagnostics(state, document, settings),
+ ...getUnknownScreenDiagnostics(state, document, settings),
+ ...getUnknownVariantDiagnostics(state, document, settings),
+ ...getUnknownConfigKeyDiagnostics(state, document, settings),
+ ...getUnsupportedTailwindDirectiveDiagnostics(
+ state,
+ document,
+ settings
+ ),
]
: []
diff --git a/src/lsp/util/find.ts b/src/lsp/util/find.ts
index 0f996a43f914e393b546ae08a4410908687126b3..609de6207d066862bb645e4d9ca199c915010b61 100644
--- a/src/lsp/util/find.ts
+++ b/src/lsp/util/find.ts
@@ -10,6 +10,7 @@ import {
getClassAttributeLexer,
getComputedClassAttributeLexer,
} from './lexers'
+import { getLanguageBoundaries } from './getLanguageBoundaries'
export function findAll(re: RegExp, str: string): RegExpMatchArray[] {
let match: RegExpMatchArray
@@ -230,70 +231,13 @@ if (isCssDoc(state, doc)) {
return findClassListsInCssRange(doc)
}
- 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 [].concat.apply(
- [],
- [
- ...htmlRanges.map((range) => findClassListsInHtmlRange(doc, range)),
- ...cssRanges.map((range) => findClassListsInCssRange(doc, range)),
- ]
- )
- }
+ let boundaries = getLanguageBoundaries(state, doc)
+ if (!boundaries) return []
- 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 [].concat.apply(
- [],
- [
- ...htmlRanges.map((range) => findClassListsInHtmlRange(doc, range)),
- ...cssRanges.map((range) => findClassListsInCssRange(doc, range)),
- ]
- )
- }
-
- return []
+ return flatten([
+ ...boundaries.html.map((range) => findClassListsInHtmlRange(doc, range)),
+ ...boundaries.css.map((range) => findClassListsInCssRange(doc, range)),
+ ])
}
export function indexToPosition(str: string, index: number): Position {
diff --git a/src/lsp/util/getLanguageBoundaries.ts b/src/lsp/util/getLanguageBoundaries.ts
new file mode 100644
index 0000000000000000000000000000000000000000..dfef2300d3a9052cb3f60970538858b81d8af39b
--- /dev/null
+++ b/src/lsp/util/getLanguageBoundaries.ts
@@ -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
+}