diff --git a/src/lsp/providers/codeActionProvider/index.ts b/src/lsp/providers/codeActionProvider/index.ts
index 19d724e27e2e2fe641eceadbea635c44cf65eccd..28b51444d56fb61000f2e34b32affdfdd19e4f01 100644
--- a/src/lsp/providers/codeActionProvider/index.ts
+++ b/src/lsp/providers/codeActionProvider/index.ts
@@ -4,10 +4,9 @@ CodeActionParams,
CodeActionKind,
Range,
TextEdit,
- Diagnostic,
} from 'vscode-languageserver'
import { State } from '../../util/state'
-import { findLast, findClassNamesInRange } from '../../util/find'
+import { findLast } from '../../util/find'
import { isWithinRange } from '../../util/isWithinRange'
import { getClassNameParts } from '../../util/getClassNameAtPosition'
const dlv = require('dlv')
@@ -16,19 +15,50 @@ import { removeRangeFromString } from '../../util/removeRangeFromString'
import detectIndent from 'detect-indent'
import { cssObjToAst } from '../../util/cssObjToAst'
import isObject from '../../../util/isObject'
+import { getDiagnostics } from '../diagnostics/diagnosticsProvider'
+import { rangesEqual } from '../../util/rangesEqual'
+import {
+ DiagnosticKind,
+ isInvalidApplyDiagnostic,
+ AugmentedDiagnostic,
+ InvalidApplyDiagnostic,
+} from '../diagnostics/types'
-export function provideCodeActions(
+async function getDiagnosticsFromCodeActionParams(
+ state: State,
+ params: CodeActionParams,
+ only?: DiagnosticKind[]
+): Promise<AugmentedDiagnostic[]> {
+ let document = state.editor.documents.get(params.textDocument.uri)
+ let diagnostics = await getDiagnostics(state, document, only)
+
+ return params.context.diagnostics
+ .map((diagnostic) => {
+ return diagnostics.find((d) => {
+ return rangesEqual(d.range, diagnostic.range)
+ })
+ })
+ .filter(Boolean)
+}
+
+export async function provideCodeActions(
state: State,
params: CodeActionParams
): Promise<CodeAction[]> {
- if (params.context.diagnostics.length === 0) {
- return null
- }
+ let codes = params.context.diagnostics
+ .map((diagnostic) => diagnostic.code)
+ .filter(Boolean) as DiagnosticKind[]
+
+ let diagnostics = await getDiagnosticsFromCodeActionParams(
+ state,
+ params,
+ codes
+ )
return Promise.all(
- params.context.diagnostics
+ diagnostics
.map((diagnostic) => {
- if (diagnostic.code === 'invalidApply') {
+ if (isInvalidApplyDiagnostic(diagnostic)) {
return provideInvalidApplyCodeAction(state, params, diagnostic)
}
@@ -127,31 +157,14 @@
async function provideInvalidApplyCodeAction(
state: State,
params: CodeActionParams,
- diagnostic: Diagnostic
+ diagnostic: InvalidApplyDiagnostic
): Promise<CodeAction> {
let document = state.editor.documents.get(params.textDocument.uri)
let documentText = document.getText()
const { postcss } = state.modules
let change: TextEdit
- let documentClassNames = findClassNamesInRange(
- document,
- {
- start: {
- line: Math.max(0, diagnostic.range.start.line - 10),
- character: 0,
- },
- end: { line: diagnostic.range.start.line + 10, character: 0 },
- },
- 'css'
- )
- let documentClassName = documentClassNames.find((className) =>
- isWithinRange(diagnostic.range.start, className.range)
- )
- if (!documentClassName) {
- return null
- }
- let totalClassNamesInClassList = documentClassName.classList.classList.split(
+ let totalClassNamesInClassList = diagnostic.className.classList.classList.split(
/\s+/
).length
@@ -184,7 +197,7 @@ let ast = classNameToAst(
state,
className,
rule.selector,
- documentClassName.classList.important
+ diagnostic.className.classList.important
)
if (!ast) {
@@ -250,10 +263,10 @@ [params.textDocument.uri]: [
...(totalClassNamesInClassList > 1
? [
{
- range: documentClassName.classList.range,
+ range: diagnostic.className.classList.range,
newText: removeRangeFromString(
- documentClassName.classList.classList,
- documentClassName.relativeRange
+ diagnostic.className.classList.classList,
+ diagnostic.className.relativeRange
),
},
]
diff --git a/src/lsp/providers/diagnosticsProvider.ts b/src/lsp/providers/diagnostics/diagnosticsProvider.ts
rename from src/lsp/providers/diagnosticsProvider.ts
rename to src/lsp/providers/diagnostics/diagnosticsProvider.ts
index f6fa9092b7d83df2bd1647b212f32527f8c843ee..56362d5488cc8776715de6feac2096c57149f255 100644
--- a/src/lsp/providers/diagnosticsProvider.ts
+++ b/src/lsp/providers/diagnostics/diagnosticsProvider.ts
@@ -1,95 +1,103 @@
-import {
- TextDocument,
- Diagnostic,
- DiagnosticSeverity,
- Range,
-} from 'vscode-languageserver'
-import { State, Settings } from '../util/state'
-import { isCssDoc } from '../util/css'
+import { TextDocument, DiagnosticSeverity, Range } from 'vscode-languageserver'
+import { State, Settings } from '../../util/state'
+import { isCssDoc } from '../../util/css'
import {
findClassNamesInRange,
findClassListsInDocument,
getClassNamesInClassList,
findAll,
indexToPosition,
-} from '../util/find'
-import { getClassNameMeta } from '../util/getClassNameMeta'
-import { getClassNameDecls } from '../util/getClassNameDecls'
-import { equal } from '../../util/array'
-import { getDocumentSettings } from '../util/getDocumentSettings'
+} from '../../util/find'
+import { getClassNameMeta } from '../../util/getClassNameMeta'
+import { getClassNameDecls } from '../../util/getClassNameDecls'
+import { equal } 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'
-import { isObject } from '../../class-names/isObject'
-import { stringToPath } from '../util/stringToPath'
-import { closest } from '../util/closest'
+import { getLanguageBoundaries } from '../../util/getLanguageBoundaries'
+import { absoluteRange } from '../../util/absoluteRange'
+import { isObject } from '../../../class-names/isObject'
+import { stringToPath } from '../../util/stringToPath'
+import { closest } from '../../util/closest'
+import {
+ InvalidApplyDiagnostic,
+ DiagnosticKind,
+ UtilityConflictsDiagnostic,
+ InvalidScreenDiagnostic,
+ InvalidVariantDiagnostic,
+ InvalidConfigPathDiagnostic,
+ InvalidTailwindDirectiveDiagnostic,
+ AugmentedDiagnostic,
+} from './types'
function getInvalidApplyDiagnostics(
state: State,
document: TextDocument,
settings: Settings
-): Diagnostic[] {
+): InvalidApplyDiagnostic[] {
let severity = settings.lint.invalidApply
if (severity === 'ignore') return []
const classNames = findClassNamesInRange(document, undefined, 'css')
- let diagnostics: Diagnostic[] = classNames
- .map(({ className, range }) => {
- const meta = getClassNameMeta(state, className)
- if (!meta) return null
+ let diagnostics: InvalidApplyDiagnostic[] = classNames.map((className) => {
+ const meta = getClassNameMeta(state, className.className)
+ if (!meta) return null
- let message: string
+ let message: string
- if (Array.isArray(meta)) {
- message = `'@apply' cannot be used with '${className}' because it is included in multiple rulesets.`
- } else if (meta.source !== 'utilities') {
- message = `'@apply' cannot be used with '${className}' because it is not a utility.`
- } else if (meta.context && meta.context.length > 0) {
- if (meta.context.length === 1) {
- message = `'@apply' cannot be used with '${className}' because it is nested inside of an at-rule ('${meta.context[0]}').`
- } else {
- message = `'@apply' cannot be used with '${className}' because it is nested inside of at-rules (${meta.context
- .map((c) => `'${c}'`)
- .join(', ')}).`
- }
- } else if (meta.pseudo && meta.pseudo.length > 0) {
- if (meta.pseudo.length === 1) {
- message = `'@apply' cannot be used with '${className}' because its definition includes a pseudo-selector ('${meta.pseudo[0]}')`
- } else {
- message = `'@apply' cannot be used with '${className}' because its definition includes pseudo-selectors (${meta.pseudo
- .map((p) => `'${p}'`)
- .join(', ')}).`
- }
+ if (Array.isArray(meta)) {
+ message = `'@apply' cannot be used with '${className.className}' because it is included in multiple rulesets.`
+ } else if (meta.source !== 'utilities') {
+ message = `'@apply' cannot be used with '${className.className}' because it is not a utility.`
+ } else if (meta.context && meta.context.length > 0) {
+ if (meta.context.length === 1) {
+ message = `'@apply' cannot be used with '${className.className}' because it is nested inside of an at-rule ('${meta.context[0]}').`
+ } else {
+ message = `'@apply' cannot be used with '${
+ className.className
+ }' because it is nested inside of at-rules (${meta.context
+ .map((c) => `'${c}'`)
+ .join(', ')}).`
}
+ } else if (meta.pseudo && meta.pseudo.length > 0) {
+ if (meta.pseudo.length === 1) {
+ message = `'@apply' cannot be used with '${className.className}' because its definition includes a pseudo-selector ('${meta.pseudo[0]}')`
+ } else {
+ message = `'@apply' cannot be used with '${
+ className.className
+ }' because its definition includes pseudo-selectors (${meta.pseudo
+ .map((p) => `'${p}'`)
+ .join(', ')}).`
+ }
+ }
- if (!message) return null
+ if (!message) return null
- return {
- severity:
- severity === 'error'
- ? DiagnosticSeverity.Error
- : DiagnosticSeverity.Warning,
- range,
- message,
- code: 'invalidApply',
- }
- })
- .filter(Boolean)
+ return {
+ code: DiagnosticKind.InvalidApply,
+ severity:
+ severity === 'error'
+ ? DiagnosticSeverity.Error
+ : DiagnosticSeverity.Warning,
+ range: className.range,
+ message,
+ className,
+ }
+ })
- return diagnostics
+ return diagnostics.filter(Boolean)
}
function getUtilityConflictDiagnostics(
state: State,
document: TextDocument,
settings: Settings
-): Diagnostic[] {
+): UtilityConflictsDiagnostic[] {
let severity = settings.lint.utilityConflicts
if (severity === 'ignore') return []
- let diagnostics: Diagnostic[] = []
+ let diagnostics: UtilityConflictsDiagnostic[] = []
const classLists = findClassListsInDocument(state, document)
classLists.forEach((classList) => {
@@ -115,6 +123,9 @@ equal(meta.context, otherMeta.context) &&
equal(meta.pseudo, otherMeta.pseudo)
) {
diagnostics.push({
+ code: DiagnosticKind.UtilityConflicts,
+ className,
+ otherClassName,
range: className.range,
severity:
severity === 'error'
@@ -143,11 +154,11 @@ function getInvalidScreenDiagnostics(
state: State,
document: TextDocument,
settings: Settings
-): Diagnostic[] {
+): InvalidScreenDiagnostic[] {
let severity = settings.lint.invalidScreen
if (severity === 'ignore') return []
- let diagnostics: Diagnostic[] = []
+ let diagnostics: InvalidScreenDiagnostic[] = []
let ranges: Range[] = []
if (isCssDoc(state, document)) {
@@ -178,6 +189,7 @@ message += ` Did you mean '${suggestion}'?`
}
diagnostics.push({
+ code: DiagnosticKind.InvalidScreen,
range: absoluteRange(
{
start: indexToPosition(
@@ -204,11 +216,11 @@ function getInvalidVariantDiagnostics(
state: State,
document: TextDocument,
settings: Settings
-): Diagnostic[] {
+): InvalidVariantDiagnostic[] {
let severity = settings.lint.invalidVariant
if (severity === 'ignore') return []
- let diagnostics: Diagnostic[] = []
+ let diagnostics: InvalidVariantDiagnostic[] = []
let ranges: Range[] = []
if (isCssDoc(state, document)) {
@@ -244,6 +256,7 @@ let variantStartIndex =
listStartIndex + variants.slice(0, i).join('').length
diagnostics.push({
+ code: DiagnosticKind.InvalidVariant,
range: absoluteRange(
{
start: indexToPosition(text, variantStartIndex),
@@ -268,11 +281,11 @@ function getInvalidConfigPathDiagnostics(
state: State,
document: TextDocument,
settings: Settings
-): Diagnostic[] {
+): InvalidConfigPathDiagnostic[] {
let severity = settings.lint.invalidConfigPath
if (severity === 'ignore') return []
- let diagnostics: Diagnostic[] = []
+ let diagnostics: InvalidConfigPathDiagnostic[] = []
let ranges: Range[] = []
if (isCssDoc(state, document)) {
@@ -381,6 +394,7 @@ 1 + // open paren
match.groups.quote.length
diagnostics.push({
+ code: DiagnosticKind.InvalidConfigPath,
range: absoluteRange(
{
start: indexToPosition(text, startIndex),
@@ -404,11 +418,11 @@ function getInvalidTailwindDirectiveDiagnostics(
state: State,
document: TextDocument,
settings: Settings
-): Diagnostic[] {
+): InvalidTailwindDirectiveDiagnostic[] {
let severity = settings.lint.invalidTailwindDirective
if (severity === 'ignore') return []
- let diagnostics: Diagnostic[] = []
+ let diagnostics: InvalidTailwindDirectiveDiagnostic[] = []
let ranges: Range[] = []
if (isCssDoc(state, document)) {
@@ -446,6 +460,7 @@ }
}
diagnostics.push({
+ code: DiagnosticKind.InvalidTailwindDirective,
range: absoluteRange(
{
start: indexToPosition(
@@ -468,26 +483,48 @@
return diagnostics
}
-export async function provideDiagnostics(
+export async function getDiagnostics(
state: State,
- document: TextDocument
-): Promise<void> {
+ document: TextDocument,
+ only: DiagnosticKind[] = [
+ DiagnosticKind.UtilityConflicts,
+ DiagnosticKind.InvalidApply,
+ DiagnosticKind.InvalidScreen,
+ DiagnosticKind.InvalidVariant,
+ DiagnosticKind.InvalidConfigPath,
+ DiagnosticKind.InvalidTailwindDirective,
+ ]
+): Promise<AugmentedDiagnostic[]> {
const settings = await getDocumentSettings(state, document)
- const diagnostics: Diagnostic[] = settings.validate
+ return settings.validate
? [
- ...getUtilityConflictDiagnostics(state, document, settings),
- ...getInvalidApplyDiagnostics(state, document, settings),
- ...getInvalidScreenDiagnostics(state, document, settings),
- ...getInvalidVariantDiagnostics(state, document, settings),
- ...getInvalidConfigPathDiagnostics(state, document, settings),
- ...getInvalidTailwindDirectiveDiagnostics(state, document, settings),
+ ...(only.includes(DiagnosticKind.UtilityConflicts)
+ ? getUtilityConflictDiagnostics(state, document, settings)
+ : []),
+ ...(only.includes(DiagnosticKind.InvalidApply)
+ ? getInvalidApplyDiagnostics(state, document, settings)
+ : []),
+ ...(only.includes(DiagnosticKind.InvalidScreen)
+ ? getInvalidScreenDiagnostics(state, document, settings)
+ : []),
+ ...(only.includes(DiagnosticKind.InvalidVariant)
+ ? getInvalidVariantDiagnostics(state, document, settings)
+ : []),
+ ...(only.includes(DiagnosticKind.InvalidConfigPath)
+ ? getInvalidConfigPathDiagnostics(state, document, settings)
+ : []),
+ ...(only.includes(DiagnosticKind.InvalidTailwindDirective)
+ ? getInvalidTailwindDirectiveDiagnostics(state, document, settings)
+ : []),
]
: []
+}
+export async function provideDiagnostics(state: State, document: TextDocument) {
state.editor.connection.sendDiagnostics({
uri: document.uri,
- diagnostics,
+ diagnostics: await getDiagnostics(state, document),
})
}