Home

tailwind-ctp-intellisense @master - refs - log -
-
https://git.jolheiser.com/tailwind-ctp-intellisense.git
Tailwind intellisense + Catppuccin
tree log patch
refactor diagnostics and add types
Brad Cornes <bradlc41@gmail.com>
4 years ago
5 changed files, 252 additions(+), 110 deletions(-)
M src/lsp/providers/codeActionProvider/index.ts -> src/lsp/providers/codeActionProvider/index.ts
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
                   ),
                 },
               ]
I src/lsp/providers/diagnostics/types.ts
diff --git a/src/lsp/providers/diagnostics/types.ts b/src/lsp/providers/diagnostics/types.ts
new file mode 100644
index 0000000000000000000000000000000000000000..684cda7c824aa7cc382c6ffed6b30010d31ddc97
--- /dev/null
+++ b/src/lsp/providers/diagnostics/types.ts
@@ -0,0 +1,82 @@
+import { Diagnostic } from 'vscode-languageserver'
+import { DocumentClassName, DocumentClassList } from '../../util/state'
+
+export enum DiagnosticKind {
+  UtilityConflicts = 'utilityConflicts',
+  InvalidApply = 'invalidApply',
+  InvalidScreen = 'invalidScreen',
+  InvalidVariant = 'invalidVariant',
+  InvalidConfigPath = 'invalidConfigPath',
+  InvalidTailwindDirective = 'invalidTailwindDirective',
+}
+
+export type UtilityConflictsDiagnostic = Diagnostic & {
+  code: DiagnosticKind.UtilityConflicts
+  className: DocumentClassName
+  otherClassName: DocumentClassName
+}
+
+export function isUtilityConflictsDiagnostic(
+  diagnostic: AugmentedDiagnostic
+): diagnostic is UtilityConflictsDiagnostic {
+  return diagnostic.code === DiagnosticKind.UtilityConflicts
+}
+
+export type InvalidApplyDiagnostic = Diagnostic & {
+  code: DiagnosticKind.InvalidApply
+  className: DocumentClassName
+}
+
+export function isInvalidApplyDiagnostic(
+  diagnostic: AugmentedDiagnostic
+): diagnostic is InvalidApplyDiagnostic {
+  return diagnostic.code === DiagnosticKind.InvalidApply
+}
+
+export type InvalidScreenDiagnostic = Diagnostic & {
+  code: DiagnosticKind.InvalidScreen
+}
+
+export function isInvalidScreenDiagnostic(
+  diagnostic: AugmentedDiagnostic
+): diagnostic is InvalidScreenDiagnostic {
+  return diagnostic.code === DiagnosticKind.InvalidScreen
+}
+
+export type InvalidVariantDiagnostic = Diagnostic & {
+  code: DiagnosticKind.InvalidVariant
+}
+
+export function isInvalidVariantDiagnostic(
+  diagnostic: AugmentedDiagnostic
+): diagnostic is InvalidVariantDiagnostic {
+  return diagnostic.code === DiagnosticKind.InvalidVariant
+}
+
+export type InvalidConfigPathDiagnostic = Diagnostic & {
+  code: DiagnosticKind.InvalidConfigPath
+}
+
+export function isInvalidConfigPathDiagnostic(
+  diagnostic: AugmentedDiagnostic
+): diagnostic is InvalidConfigPathDiagnostic {
+  return diagnostic.code === DiagnosticKind.InvalidConfigPath
+}
+
+export type InvalidTailwindDirectiveDiagnostic = Diagnostic & {
+  code: DiagnosticKind.InvalidTailwindDirective
+}
+
+export function isInvalidTailwindDirectiveDiagnostic(
+  diagnostic: AugmentedDiagnostic
+): diagnostic is InvalidTailwindDirectiveDiagnostic {
+  return diagnostic.code === DiagnosticKind.InvalidTailwindDirective
+}
+
+export type AugmentedDiagnostic =
+  | UtilityConflictsDiagnostic
+  | InvalidApplyDiagnostic
+  | InvalidScreenDiagnostic
+  | InvalidVariantDiagnostic
+  | InvalidConfigPathDiagnostic
+  | InvalidTailwindDirectiveDiagnostic
M src/lsp/providers/diagnosticsProvider.ts -> src/lsp/providers/diagnostics/diagnosticsProvider.ts
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),
   })
 }
 
M src/lsp/server.ts -> src/lsp/server.ts
diff --git a/src/lsp/server.ts b/src/lsp/server.ts
index 6c928e68b9df731b3b77b24aa82e85621eb5f7f1..f4b8013534ea0de51cbb9c67f30c90d3187afb76 100644
--- a/src/lsp/server.ts
+++ b/src/lsp/server.ts
@@ -32,7 +32,7 @@ import {
   provideDiagnostics,
   updateAllDiagnostics,
   clearAllDiagnostics,
-} from './providers/diagnosticsProvider'
+} from './providers/diagnostics/diagnosticsProvider'
 import { createEmitter } from '../lib/emitter'
 import { provideCodeActions } from './providers/codeActionProvider'
 
I src/lsp/util/rangesEqual.ts
diff --git a/src/lsp/util/rangesEqual.ts b/src/lsp/util/rangesEqual.ts
new file mode 100644
index 0000000000000000000000000000000000000000..220cebd50e242933d666ee7581d68e4030dd321b
--- /dev/null
+++ b/src/lsp/util/rangesEqual.ts
@@ -0,0 +1,10 @@
+import { Range } from 'vscode-languageserver'
+
+export function rangesEqual(a: Range, b: Range): boolean {
+  return (
+    a.start.line === b.start.line &&
+    a.start.character === b.start.character &&
+    a.end.line === b.end.line &&
+    a.end.character === b.end.character
+  )
+}