tailwind-ctp-intellisense @master -
refs -
log -
-
https://git.jolheiser.com/tailwind-ctp-intellisense.git
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,
})
})
})
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, [])
+}
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, [])
-}