Home

tailwind-ctp-intellisense @master - refs - log -
-
https://git.jolheiser.com/tailwind-ctp-intellisense.git
Tailwind intellisense + Catppuccin
tree log patch
refactor invalid config path diagnostics
Brad Cornes <bradlc41@gmail.com>
4 years ago
3 changed files, 174 additions(+), 94 deletions(-)
M src/lsp/providers/diagnostics/getInvalidConfigPathDiagnostics.tssrc/lsp/providers/diagnostics/getInvalidConfigPathDiagnostics.ts
diff --git a/src/lsp/providers/diagnostics/getInvalidConfigPathDiagnostics.ts b/src/lsp/providers/diagnostics/getInvalidConfigPathDiagnostics.ts
index 95293341d536810ead06332a6125e96c6a77a776..ccf52fcb1aa3b88bd1b73aa9963e0e7729b29cca 100644
--- a/src/lsp/providers/diagnostics/getInvalidConfigPathDiagnostics.ts
+++ b/src/lsp/providers/diagnostics/getInvalidConfigPathDiagnostics.ts
@@ -8,8 +8,164 @@ import { stringToPath } from '../../util/stringToPath'
 import isObject from '../../../util/isObject'
 import { closest } from '../../util/closest'
 import { absoluteRange } from '../../util/absoluteRange'
+import { combinations } from '../../util/combinations'
 const dlv = require('dlv')
 
+function pathToString(path: string | string[]): string {
+  if (typeof path === 'string') return path
+  return path.reduce((acc, cur, i) => {
+    if (i === 0) return cur
+    if (cur.includes('.')) return `${acc}[${cur}]`
+    return `${acc}.${cur}`
+  }, '')
+}
+
+function validateConfigPath(
+  state: State,
+  path: string | string[],
+  base: string[] = []
+):
+  | { isValid: true; value: any }
+  | { isValid: false; reason: string; suggestions: string[] } {
+  let keys = Array.isArray(path) ? path : stringToPath(path)
+  let value = dlv(state.config, [...base, ...keys])
+  let suggestions: string[] = []
+
+  function findAlternativePath(): string[] {
+    let points = combinations('123456789'.substr(0, keys.length - 1)).map((x) =>
+      x.split('').map((x) => parseInt(x, 10))
+    )
+
+    let possibilities: string[][] = points
+      .map((p) => {
+        let result = []
+        let i = 0
+        p.forEach((x) => {
+          result.push(keys.slice(i, x).join('.'))
+          i = x
+        })
+        result.push(keys.slice(i).join('.'))
+        return result
+      })
+      .slice(1) // skip original path
+
+    return possibilities.find(
+      (possibility) => validateConfigPath(state, possibility, base).isValid
+    )
+  }
+
+  if (typeof value === 'undefined') {
+    let reason = `'${pathToString(path)}' does not exist in your theme config.`
+    let parentPath = [...base, ...keys.slice(0, keys.length - 1)]
+    let parentValue = dlv(state.config, parentPath)
+
+    if (isObject(parentValue)) {
+      let closestValidKey = closest(
+        keys[keys.length - 1],
+        Object.keys(parentValue).filter(
+          (key) => validateConfigPath(state, [...parentPath, key]).isValid
+        )
+      )
+      if (closestValidKey) {
+        suggestions.push(
+          pathToString([...keys.slice(0, keys.length - 1), closestValidKey])
+        )
+        reason += ` Did you mean '${suggestions[0]}'?`
+      }
+    } else {
+      let altPath = findAlternativePath()
+      if (altPath) {
+        return {
+          isValid: false,
+          reason: `${reason} Did you mean '${pathToString(altPath)}'?`,
+          suggestions: [pathToString(altPath)],
+        }
+      }
+    }
+
+    return {
+      isValid: false,
+      reason,
+      suggestions,
+    }
+  }
+
+  if (
+    !(
+      typeof value === 'string' ||
+      typeof value === 'number' ||
+      value instanceof String ||
+      value instanceof Number ||
+      Array.isArray(value)
+    )
+  ) {
+    let reason = `'${pathToString(
+      path
+    )}' was found but does not resolve to a string.`
+
+    if (isObject(value)) {
+      let validKeys = Object.keys(value).filter(
+        (key) => validateConfigPath(state, [...keys, key], base).isValid
+      )
+      if (validKeys.length) {
+        suggestions.push(
+          ...validKeys.map((validKey) => pathToString([...keys, validKey]))
+        )
+        reason += ` Did you mean something like '${suggestions[0]}'?`
+      }
+    }
+    return {
+      isValid: false,
+      reason,
+      suggestions,
+    }
+  }
+
+  // The value resolves successfully, but we need to check that there
+  // wasn't any funny business. If you have a theme object:
+  // { msg: 'hello' } and do theme('msg.0')
+  // this will resolve to 'h', which is probably not intentional, so we
+  // check that all of the keys are object or array keys (i.e. not string
+  // indexes)
+  let isValid = true
+  for (let i = keys.length - 1; i >= 0; i--) {
+    let key = keys[i]
+    let parentValue = dlv(state.config, [...base, ...keys.slice(0, i)])
+    if (/^[0-9]+$/.test(key)) {
+      if (!isObject(parentValue) && !Array.isArray(parentValue)) {
+        isValid = false
+        break
+      }
+    } else if (!isObject(parentValue)) {
+      isValid = false
+      break
+    }
+  }
+  if (!isValid) {
+    let reason = `'${pathToString(path)}' does not exist in your theme config.`
+
+    let altPath = findAlternativePath()
+    if (altPath) {
+      return {
+        isValid: false,
+        reason: `${reason} Did you mean '${pathToString(altPath)}'?`,
+        suggestions: [pathToString(altPath)],
+      }
+    }
+
+    return {
+      isValid: false,
+      reason,
+      suggestions: [],
+    }
+  }
+
+  return {
+    isValid: true,
+    value,
+  }
+}
+
 export function getInvalidConfigPathDiagnostics(
   state: State,
   document: TextDocument,
@@ -38,85 +194,9 @@     )
 
     matches.forEach((match) => {
       let base = match.groups.helper === 'theme' ? ['theme'] : []
-      let keys = stringToPath(match.groups.key)
-      let value = dlv(state.config, [...base, ...keys])
-
-      const isValid = (val: unknown): boolean =>
-        typeof val === 'string' ||
-        typeof val === 'number' ||
-        val instanceof String ||
-        val instanceof Number ||
-        Array.isArray(val)
-
-      const stitch = (keys: string[]): string =>
-        keys.reduce((acc, cur, i) => {
-          if (i === 0) return cur
-          if (cur.includes('.')) return `${acc}[${cur}]`
-          return `${acc}.${cur}`
-        }, '')
-
-      let message: string
-      let suggestions: string[] = []
-
-      if (isValid(value)) {
-        // The value resolves successfully, but we need to check that there
-        // wasn't any funny business. If you have a theme object:
-        // { msg: 'hello' } and do theme('msg.0')
-        // this will resolve to 'h', which is probably not intentional, so we
-        // check that all of the keys are object or array keys (i.e. not string
-        // indexes)
-        let valid = true
-        for (let i = keys.length - 1; i >= 0; i--) {
-          let key = keys[i]
-          let parentValue = dlv(state.config, [...base, ...keys.slice(0, i)])
-          if (/^[0-9]+$/.test(key)) {
-            if (!isObject(parentValue) && !Array.isArray(parentValue)) {
-              valid = false
-              break
-            }
-          } else if (!isObject(parentValue)) {
-            valid = false
-            break
-          }
-        }
-        if (!valid) {
-          message = `'${match.groups.key}' does not exist in your theme config.`
-        }
-      } else if (typeof value === 'undefined') {
-        message = `'${match.groups.key}' does not exist in your theme config.`
-        let parentValue = dlv(state.config, [
-          ...base,
-          ...keys.slice(0, keys.length - 1),
-        ])
-        if (isObject(parentValue)) {
-          let closestValidKey = closest(
-            keys[keys.length - 1],
-            Object.keys(parentValue).filter((key) => isValid(parentValue[key]))
-          )
-          if (closestValidKey) {
-            suggestions.push(
-              stitch([...keys.slice(0, keys.length - 1), closestValidKey])
-            )
-            message += ` Did you mean '${suggestions[0]}'?`
-          }
-        }
-      } else {
-        message = `'${match.groups.key}' was found but does not resolve to a string.`
-
-        if (isObject(value)) {
-          let validKeys = Object.keys(value).filter((key) =>
-            isValid(value[key])
-          )
-          if (validKeys.length) {
-            suggestions.push(
-              ...validKeys.map((validKey) => stitch([...keys, validKey]))
-            )
-            message += ` Did you mean something like '${suggestions[0]}'?`
-          }
-        }
-      }
+      let result = validateConfigPath(state, match.groups.key, base)
 
-      if (!message) {
+      if (result.isValid === true) {
         return null
       }
 
@@ -140,8 +220,8 @@         severity:
           severity === 'error'
             ? DiagnosticSeverity.Error
             : DiagnosticSeverity.Warning,
-        message,
-        suggestions,
+        message: result.reason,
+        suggestions: result.suggestions,
       })
     })
   })
Isrc/lsp/util/combinations.ts
diff --git a/src/lsp/util/combinations.ts b/src/lsp/util/combinations.ts
new file mode 100644
index 0000000000000000000000000000000000000000..2c9868b207b01b60cc0789b800058285257e187a
--- /dev/null
+++ b/src/lsp/util/combinations.ts
@@ -0,0 +1,13 @@
+export function combinations(str: string): string[] {
+  let fn = function (active: string, rest: string, a: string[]) {
+    if (!active && !rest) return
+    if (!rest) {
+      a.push(active)
+    } else {
+      fn(active + rest[0], rest.slice(1), a)
+      fn(active, rest.slice(1), a)
+    }
+    return a
+  }
+  return fn('', str, [])
+}
M src/lsp/util/getClassNameAtPosition.tssrc/lsp/util/getClassNameAtPosition.ts
diff --git a/src/lsp/util/getClassNameAtPosition.ts b/src/lsp/util/getClassNameAtPosition.ts
index 083832ca5e8b2707a1405fdbcd83f6c163e18f65..7418b2f157723967380453978a082e42fdb9ce49 100644
--- a/src/lsp/util/getClassNameAtPosition.ts
+++ b/src/lsp/util/getClassNameAtPosition.ts
@@ -1,4 +1,5 @@
 import { State } from './state'
+import { combinations } from './combinations'
 const dlv = require('dlv')
 
 export function getClassNameParts(state: State, className: string): string[] {
@@ -41,17 +42,3 @@     }
     return false
   })
 }
-
-function combinations(str: string): string[] {
-  let fn = function (active: string, rest: string, a: string[]) {
-    if (!active && !rest) return
-    if (!rest) {
-      a.push(active)
-    } else {
-      fn(active + rest[0], rest.slice(1), a)
-      fn(active, rest.slice(1), a)
-    }
-    return a
-  }
-  return fn('', str, [])
-}