Home

tailwind-ctp-intellisense @master - refs - log -
-
https://git.jolheiser.com/tailwind-ctp-intellisense.git
Tailwind intellisense + Catppuccin
tree log patch
tidy up code actions
Brad Cornes <bradlc41@gmail.com>
4 years ago
4 changed files, 310 additions(+), 300 deletions(-)
M src/lsp/providers/codeActions/codeActionProvider.ts -> src/lsp/providers/codeActions/codeActionProvider.ts
diff --git a/src/lsp/providers/codeActions/codeActionProvider.ts b/src/lsp/providers/codeActions/codeActionProvider.ts
index 62d2f09b41a553dac7bec6db1f963d07d5102b6c..1ad8c557040e553c6d546be503df3ad396ba6f89 100644
--- a/src/lsp/providers/codeActions/codeActionProvider.ts
+++ b/src/lsp/providers/codeActions/codeActionProvider.ts
@@ -1,20 +1,7 @@
-import {
   CodeAction,
-  CodeActionParams,
-  CodeActionKind,
-  Range,
   TextEdit,
-} from 'vscode-languageserver'
-import { State } from '../../util/state'
-import { isWithinRange } from '../../util/isWithinRange'
-import { getClassNameParts } from '../../util/getClassNameAtPosition'
-const dlv = require('dlv')
-import dset from 'dset'
-import { removeRangesFromString } from '../../util/removeRangesFromString'
-import {
   CodeActionKind,
-import { cssObjToAst } from '../../util/cssObjToAst'
-import isObject from '../../../util/isObject'
+import { State } from '../../util/state'
 import { getDiagnostics } from '../diagnostics/diagnosticsProvider'
 import { rangesEqual } from '../../util/rangesEqual'
 import {
@@ -22,24 +9,18 @@   DiagnosticKind,
   isInvalidApplyDiagnostic,
   AugmentedDiagnostic,
   CodeAction,
-  CodeAction,
-  CodeAction,
   CodeActionParams,
   CodeAction,
-  CodeActionKind,
-  CodeAction,
   Range,
   isInvalidTailwindDirectiveDiagnostic,
   isInvalidScreenDiagnostic,
   isInvalidVariantDiagnostic,
 } from '../diagnostics/types'
 import { flatten, dedupeBy } from '../../../util/array'
-import { joinWithAnd } from '../../util/joinWithAnd'
+import { provideUtilityConflictsCodeActions } from './provideUtilityConflictsCodeActions'
-  CodeActionParams,
   CodeAction,
-import { isCssDoc } from '../../util/css'
-import { absoluteRange } from '../../util/absoluteRange'
+  )
-import type { NodeSource, Root } from 'postcss'
+import { provideSuggestionCodeActions } from './provideSuggestionCodeActions'
 
 async function getDiagnosticsFromCodeActionParams(
   state: State,
@@ -66,349 +47,75 @@ export async function provideCodeActions(
   state: State,
   params: CodeActionParams
 ): Promise<CodeAction[]> {
-  let codes = params.context.diagnostics
-    .map((diagnostic) => diagnostic.code)
-    .filter(Boolean) as DiagnosticKind[]
-
   let diagnostics = await getDiagnosticsFromCodeActionParams(
     state,
     params,
-    codes
-  )
-
-  let actions = diagnostics.map((diagnostic) => {
-    if (isInvalidApplyDiagnostic(diagnostic)) {
-      return provideInvalidApplyCodeActions(state, params, diagnostic)
-    }
-
-    if (isUtilityConflictsDiagnostic(diagnostic)) {
-} from 'vscode-languageserver'
   CodeAction,
-    }
-  CodeActionParams,
   TextEdit,
-    if (
-      isInvalidConfigPathDiagnostic(diagnostic) ||
-      isInvalidTailwindDirectiveDiagnostic(diagnostic) ||
-      isInvalidScreenDiagnostic(diagnostic) ||
-      isInvalidVariantDiagnostic(diagnostic)
-} from 'vscode-languageserver'
 import { State } from '../../util/state'
-      return diagnostic.suggestions.map((suggestion) => ({
-        title: `Replace with '${suggestion}'`,
-        kind: CodeActionKind.QuickFix,
-import { State } from '../../util/state'
   CodeAction,
-        edit: {
-          changes: {
-            [params.textDocument.uri]: [
-import { State } from '../../util/state'
   TextEdit,
-                range: diagnostic.range,
-                newText: suggestion,
-import { State } from '../../util/state'
 import { isWithinRange } from '../../util/isWithinRange'
-            ],
-          },
-import { isWithinRange } from '../../util/isWithinRange'
   CodeAction,
-      }))
     }
-  CodeActionParams,
   TextEdit,
-    return []
-  })
-  CodeActionParams,
   TextEdit,
-  return Promise.all(actions)
-    .then(flatten)
-    .then((x) => dedupeBy(x, (item) => JSON.stringify(item.edit)))
-}
 
-function classNameToAst(
-  state: State,
-  classNameParts: string[],
-  selector: string,
-import { getClassNameParts } from '../../util/getClassNameAtPosition'
   CodeAction,
-) {
-  const baseClassName = dlv(
-    state.classNames.classNames,
-    classNameParts[classNameParts.length - 1]
-  )
-import { getClassNameParts } from '../../util/getClassNameAtPosition'
 } from 'vscode-languageserver'
 import {
-import { State } from '../../util/state'
-  }
-  const info = dlv(state.classNames.classNames, classNameParts)
-  let context = info.__context || []
-const dlv = require('dlv')
   CodeAction,
-  const globalContexts = state.classNames.context
-  let screens = dlv(
-    state.config,
-    'theme.screens',
-const dlv = require('dlv')
 } from 'vscode-languageserver'
-  )
-  if (!isObject(screens)) screens = {}
-  screens = Object.keys(screens)
-import {
   CodeAction,
-
-  for (let i = 0; i < classNameParts.length - 1; i++) {
-    let part = classNameParts[i]
-    let common = globalContexts[part]
-    if (!common) return null
-    if (screens.includes(part)) {
-      path.push(`@screen ${part}`)
-import {
   isInvalidScreenDiagnostic,
-    }
-  }
   CodeActionParams,
-  TextEdit,
-import {
   CodeAction,
-import { State } from '../../util/state'
-
-  let obj = {}
-  for (let i = 1; i <= path.length; i++) {
-    dset(obj, path.slice(0, i), {})
-  }
-  let rule = {
-    // TODO: use proper selector parser
-    [selector + pseudo.join('')]: {
-      [`@apply ${classNameParts[classNameParts.length - 1]}${
-        important ? ' !important' : ''
-import { removeRangesFromString } from '../../util/removeRangesFromString'
 } from 'vscode-languageserver'
-    },
-  }
-  if (path.length) {
-import {
   CodeActionKind,
-  } else {
-import detectIndent from 'detect-indent'
   CodeAction,
-  }
-
-  return cssObjToAst(obj, state.modules.postcss)
-  Range,
+} from 'vscode-languageserver'
   Range,
 
-async function provideUtilityConflictsCodeActions(
-  state: State,
-  params: CodeActionParams,
-  diagnostic: UtilityConflictsDiagnostic
-): Promise<CodeAction[]> {
-  return [
-    {
-      title: `Delete ${joinWithAnd(
-        diagnostic.otherClassNames.map(
-          (otherClassName) => `'${otherClassName.className}'`
-        )
-      )}`,
-import { cssObjToAst } from '../../util/cssObjToAst'
   CodeAction,
-      diagnostics: [diagnostic],
-      edit: {
-        changes: {
-          [params.textDocument.uri]: [
-import { cssObjToAst } from '../../util/cssObjToAst'
 } from 'vscode-languageserver'
-              range: diagnostic.className.classList.range,
-              newText: removeRangesFromString(
-import {
   TextEdit,
-                diagnostic.otherClassNames.map(
-import isObject from '../../../util/isObject'
   CodeAction,
-                )
-              ),
-            },
-          ],
-        },
-import isObject from '../../../util/isObject'
 } from 'vscode-languageserver'
-    },
-  ]
-}
-
-function postcssSourceToRange(source: NodeSource): Range {
-import {
 } from 'vscode-languageserver'
-    start: {
-import { getDiagnostics } from '../diagnostics/diagnosticsProvider'
   CodeAction,
-import {
 } from 'vscode-languageserver'
-  CodeActionParams,
-    },
-    end: {
-      line: source.end.line - 1,
-      character: source.end.column,
-    },
-  }
-  Range,
   Range,
 
-async function provideInvalidApplyCodeActions(
-  state: State,
-  params: CodeActionParams,
-import {
+  CodeAction,
     ) {
-): Promise<CodeAction[]> {
-  CodeActionKind,
   CodeAction,
-import {
       return diagnostic.suggestions.map((suggestion) => ({
-import {
+  CodeAction,
         title: `Replace with '${suggestion}'`,
-  let cssText = documentText
-import { rangesEqual } from '../../util/rangesEqual'
   CodeAction,
-import {
 import { State } from '../../util/state'
-  CodeActionParams,
-
 import {
-          changes: {
-    /\s+/
-  ).length
-
-  let className = diagnostic.className.className
-  let classNameParts = getClassNameParts(state, className)
-  let classNameInfo = dlv(state.classNames.classNames, classNameParts)
-
-  if (Array.isArray(classNameInfo)) {
-    return []
-  }
-
-  if (!isCssDoc(state, document)) {
-  DiagnosticKind,
   CodeAction,
-    if (!languageBoundaries) return []
-    cssRange = languageBoundaries.css.find((range) =>
-      isWithinRange(diagnostic.range.start, range)
-    )
-    if (!cssRange) return []
-  DiagnosticKind,
 import { State } from '../../util/state'
-  }
-
-  try {
   CodeAction,
   CodeAction,
-import {
-        return (root: Root) => {
-          root.walkRules((rule) => {
-            if (changes.length) return false
-
-            rule.walkAtRules('apply', (atRule) => {
-              let atRuleRange = postcssSourceToRange(atRule.source)
-              if (cssRange) {
-  isInvalidApplyDiagnostic,
 import { State } from '../../util/state'
-              }
   CodeActionParams,
-  TextEdit,
-              if (!isWithinRange(diagnostic.range.start, atRuleRange))
-                return true
-
-              let ast = classNameToAst(
-                state,
-                classNameParts,
-                rule.selector,
-                diagnostic.className.classList.important
-              )
-
   CodeAction,
-import {
 import { State } from '../../util/state'
-
-              rule.after(ast.nodes)
-              let insertedRule = rule.next()
-              if (!insertedRule) return false
-
-              if (totalClassNamesInClassList === 1) {
-                atRule.remove()
-  InvalidApplyDiagnostic,
   CodeActionKind,
   CodeAction,
-  isInvalidConfigPathDiagnostic,
-                  range: diagnostic.className.classList.range,
-  InvalidApplyDiagnostic,
 } from 'vscode-languageserver'
-                    diagnostic.className.classList.classList,
-                    diagnostic.className.relativeRange
-                  ),
-                })
-              }
-
-              let ruleRange = postcssSourceToRange(rule.source)
-              if (cssRange) {
-                ruleRange = absoluteRange(ruleRange, cssRange)
-              }
-
-              let outputIndent: string
-  isUtilityConflictsDiagnostic,
   Range,
 
   CodeAction,
-
-                range: ruleRange,
-  isUtilityConflictsDiagnostic,
 import { State } from '../../util/state'
-                  rule.toString() +
-                  (insertedRule.raws.before || '\n\n') +
-                  insertedRule
-                    .toString()
-                    .replace(/\n\s*\n/g, '\n')
-                    .replace(/(@apply [^;\n]+)$/gm, '$1;')
-  UtilityConflictsDiagnostic,
   Range,
-                    .replace(/^\s+/gm, (m: string) => {
-                      if (typeof outputIndent === 'undefined') outputIndent = m
-                      return m.replace(
-                        new RegExp(outputIndent, 'g'),
-                        documentIndent.indent
-                      )
-                    }),
-  CodeAction,
     })
-  CodeActionParams,
   TextEdit,
-              return false
-            })
-  isInvalidConfigPathDiagnostic,
   TextEdit,
-        }
-      }),
-  isInvalidConfigPathDiagnostic,
 import { isWithinRange } from '../../util/isWithinRange'
-  } catch (_) {
-    return []
-  }
-
-  if (!changes.length) {
-    return []
-  }
-
-  return [
-import detectIndent from 'detect-indent'
 } from 'vscode-languageserver'
-      title: 'Extract to new rule',
-      kind: CodeActionKind.QuickFix,
-      diagnostics: [diagnostic],
-      edit: {
-        changes: {
-          [params.textDocument.uri]: changes,
 import { isWithinRange } from '../../util/isWithinRange'
-  CodeAction,
-      },
-    },
-import isObject from '../../../util/isObject'
 import { State } from '../../util/state'
 }
I src/lsp/providers/codeActions/provideInvalidApplyCodeActions.ts
diff --git a/src/lsp/providers/codeActions/provideInvalidApplyCodeActions.ts b/src/lsp/providers/codeActions/provideInvalidApplyCodeActions.ts
new file mode 100644
index 0000000000000000000000000000000000000000..a0412aa590bac8f9d6c618571ce48593efbf7223
--- /dev/null
+++ b/src/lsp/providers/codeActions/provideInvalidApplyCodeActions.ts
@@ -0,0 +1,223 @@
+import {
+  CodeAction,
+  CodeActionParams,
+  CodeActionKind,
+  TextEdit,
+  Range,
+} from 'vscode-languageserver'
+import { State } from '../../util/state'
+import { InvalidApplyDiagnostic } from '../diagnostics/types'
+import { getClassNameParts } from '../../util/getClassNameAtPosition'
+import { getLanguageBoundaries } from '../../util/getLanguageBoundaries'
+import { isCssDoc } from '../../util/css'
+import { isWithinRange } from '../../util/isWithinRange'
+const dlv = require('dlv')
+import type { Root, NodeSource } from 'postcss'
+import { absoluteRange } from '../../util/absoluteRange'
+import { removeRangesFromString } from '../../util/removeRangesFromString'
+import detectIndent from 'detect-indent'
+import isObject from '../../../util/isObject'
+import { cssObjToAst } from '../../util/cssObjToAst'
+import dset from 'dset'
+
+export async function provideInvalidApplyCodeActions(
+  state: State,
+  params: CodeActionParams,
+  diagnostic: InvalidApplyDiagnostic
+): Promise<CodeAction[]> {
+  let document = state.editor.documents.get(params.textDocument.uri)
+  let documentText = document.getText()
+  let cssRange: Range
+  let cssText = documentText
+  const { postcss } = state.modules
+  let changes: TextEdit[] = []
+
+  let totalClassNamesInClassList = diagnostic.className.classList.classList.split(
+    /\s+/
+  ).length
+
+  let className = diagnostic.className.className
+  let classNameParts = getClassNameParts(state, className)
+  let classNameInfo = dlv(state.classNames.classNames, classNameParts)
+
+  if (Array.isArray(classNameInfo)) {
+    return []
+  }
+
+  if (!isCssDoc(state, document)) {
+    let languageBoundaries = getLanguageBoundaries(state, document)
+    if (!languageBoundaries) return []
+    cssRange = languageBoundaries.css.find((range) =>
+      isWithinRange(diagnostic.range.start, range)
+    )
+    if (!cssRange) return []
+    cssText = document.getText(cssRange)
+  }
+
+  try {
+    await postcss([
+      postcss.plugin('', (_options = {}) => {
+        return (root: Root) => {
+          root.walkRules((rule) => {
+            if (changes.length) return false
+
+            rule.walkAtRules('apply', (atRule) => {
+              let atRuleRange = postcssSourceToRange(atRule.source)
+              if (cssRange) {
+                atRuleRange = absoluteRange(atRuleRange, cssRange)
+              }
+
+              if (!isWithinRange(diagnostic.range.start, atRuleRange))
+                return true
+
+              let ast = classNameToAst(
+                state,
+                classNameParts,
+                rule.selector,
+                diagnostic.className.classList.important
+              )
+
+              if (!ast) return false
+
+              rule.after(ast.nodes)
+              let insertedRule = rule.next()
+              if (!insertedRule) return false
+
+              if (totalClassNamesInClassList === 1) {
+                atRule.remove()
+              } else {
+                changes.push({
+                  range: diagnostic.className.classList.range,
+                  newText: removeRangesFromString(
+                    diagnostic.className.classList.classList,
+                    diagnostic.className.relativeRange
+                  ),
+                })
+              }
+
+              let ruleRange = postcssSourceToRange(rule.source)
+              if (cssRange) {
+                ruleRange = absoluteRange(ruleRange, cssRange)
+              }
+
+              let outputIndent: string
+              let documentIndent = detectIndent(documentText)
+
+              changes.push({
+                range: ruleRange,
+                newText:
+                  rule.toString() +
+                  (insertedRule.raws.before || '\n\n') +
+                  insertedRule
+                    .toString()
+                    .replace(/\n\s*\n/g, '\n')
+                    .replace(/(@apply [^;\n]+)$/gm, '$1;')
+                    .replace(/([^\s^]){$/gm, '$1 {')
+                    .replace(/^\s+/gm, (m: string) => {
+                      if (typeof outputIndent === 'undefined') outputIndent = m
+                      return m.replace(
+                        new RegExp(outputIndent, 'g'),
+                        documentIndent.indent
+                      )
+                    }),
+              })
+
+              return false
+            })
+          })
+        }
+      }),
+    ]).process(cssText, { from: undefined })
+  } catch (_) {
+    return []
+  }
+
+  if (!changes.length) {
+    return []
+  }
+
+  return [
+    {
+      title: 'Extract to new rule',
+      kind: CodeActionKind.QuickFix,
+      diagnostics: [diagnostic],
+      edit: {
+        changes: {
+          [params.textDocument.uri]: changes,
+        },
+      },
+    },
+  ]
+}
+
+function postcssSourceToRange(source: NodeSource): Range {
+  return {
+    start: {
+      line: source.start.line - 1,
+      character: source.start.column - 1,
+    },
+    end: {
+      line: source.end.line - 1,
+      character: source.end.column,
+    },
+  }
+}
+
+function classNameToAst(
+  state: State,
+  classNameParts: string[],
+  selector: string,
+  important: boolean = false
+) {
+  const baseClassName = dlv(
+    state.classNames.classNames,
+    classNameParts[classNameParts.length - 1]
+  )
+  if (!baseClassName) {
+    return null
+  }
+  const info = dlv(state.classNames.classNames, classNameParts)
+  let context = info.__context || []
+  let pseudo = info.__pseudo || []
+  const globalContexts = state.classNames.context
+  let screens = dlv(
+    state.config,
+    'theme.screens',
+    dlv(state.config, 'screens', {})
+  )
+  if (!isObject(screens)) screens = {}
+  screens = Object.keys(screens)
+  const path = []
+
+  for (let i = 0; i < classNameParts.length - 1; i++) {
+    let part = classNameParts[i]
+    let common = globalContexts[part]
+    if (!common) return null
+    if (screens.includes(part)) {
+      path.push(`@screen ${part}`)
+      context = context.filter((con) => !common.includes(con))
+    }
+  }
+
+  path.push(...context)
+
+  let obj = {}
+  for (let i = 1; i <= path.length; i++) {
+    dset(obj, path.slice(0, i), {})
+  }
+  let rule = {
+    // TODO: use proper selector parser
+    [selector + pseudo.join('')]: {
+      [`@apply ${classNameParts[classNameParts.length - 1]}${
+        important ? ' !important' : ''
+      }`]: '',
+    },
+  }
+  if (path.length) {
+    dset(obj, path, rule)
+  } else {
+    obj = rule
+  }
+
+  return cssObjToAst(obj, state.modules.postcss)
+}
I src/lsp/providers/codeActions/provideSuggestionCodeActions.ts
diff --git a/src/lsp/providers/codeActions/provideSuggestionCodeActions.ts b/src/lsp/providers/codeActions/provideSuggestionCodeActions.ts
new file mode 100644
index 0000000000000000000000000000000000000000..9da5fcb58f4863d4f6edc2643319263f95c22aa6
--- /dev/null
+++ b/src/lsp/providers/codeActions/provideSuggestionCodeActions.ts
@@ -0,0 +1,38 @@
+import { State } from '../../util/state'
+import {
+  CodeActionParams,
+  CodeAction,
+  CodeActionKind,
+} from 'vscode-languageserver'
+import {
+  InvalidConfigPathDiagnostic,
+  InvalidTailwindDirectiveDiagnostic,
+  InvalidScreenDiagnostic,
+  InvalidVariantDiagnostic,
+} from '../diagnostics/types'
+
+export function provideSuggestionCodeActions(
+  _state: State,
+  params: CodeActionParams,
+  diagnostic:
+    | InvalidConfigPathDiagnostic
+    | InvalidTailwindDirectiveDiagnostic
+    | InvalidScreenDiagnostic
+    | InvalidVariantDiagnostic
+): CodeAction[] {
+  return diagnostic.suggestions.map((suggestion) => ({
+    title: `Replace with '${suggestion}'`,
+    kind: CodeActionKind.QuickFix,
+    diagnostics: [diagnostic],
+    edit: {
+      changes: {
+        [params.textDocument.uri]: [
+          {
+            range: diagnostic.range,
+            newText: suggestion,
+          },
+        ],
+      },
+    },
+  }))
+}
I src/lsp/providers/codeActions/provideUtilityConflictsCodeActions.ts
diff --git a/src/lsp/providers/codeActions/provideUtilityConflictsCodeActions.ts b/src/lsp/providers/codeActions/provideUtilityConflictsCodeActions.ts
new file mode 100644
index 0000000000000000000000000000000000000000..007cd88bcd1c777298102f6e73260f3c62ea0178
--- /dev/null
+++ b/src/lsp/providers/codeActions/provideUtilityConflictsCodeActions.ts
@@ -0,0 +1,42 @@
+import { State } from '../../util/state'
+import {
+  CodeActionParams,
+  CodeAction,
+  CodeActionKind,
+} from 'vscode-languageserver'
+import { UtilityConflictsDiagnostic } from '../diagnostics/types'
+import { joinWithAnd } from '../../util/joinWithAnd'
+import { removeRangesFromString } from '../../util/removeRangesFromString'
+
+export async function provideUtilityConflictsCodeActions(
+  _state: State,
+  params: CodeActionParams,
+  diagnostic: UtilityConflictsDiagnostic
+): Promise<CodeAction[]> {
+  return [
+    {
+      title: `Delete ${joinWithAnd(
+        diagnostic.otherClassNames.map(
+          (otherClassName) => `'${otherClassName.className}'`
+        )
+      )}`,
+      kind: CodeActionKind.QuickFix,
+      diagnostics: [diagnostic],
+      edit: {
+        changes: {
+          [params.textDocument.uri]: [
+            {
+              range: diagnostic.className.classList.range,
+              newText: removeRangesFromString(
+                diagnostic.className.classList.classList,
+                diagnostic.otherClassNames.map(
+                  (otherClassName) => otherClassName.relativeRange
+                )
+              ),
+            },
+          ],
+        },
+      },
+    },
+  ]
+}