prevent crash on config error

master
Brad Cornes 2020-04-22 20:29:36 +01:00
parent 850ad5c0a5
commit 568e078522
3 changed files with 97 additions and 55 deletions

View File

@ -17,7 +17,7 @@ function glob(pattern, options = {}) {
let g = new nodeGlob.Glob(pattern, options)
let matches = []
let max = dlv(options, 'max', Infinity)
g.on('match', match => {
g.on('match', (match) => {
matches.push(path.resolve(options.cwd || process.cwd(), match))
if (matches.length === max) {
g.abort()
@ -38,39 +38,35 @@ function arraysEqual(arr1, arr2) {
)
}
const CONFIG_GLOB =
'**/{tailwind,tailwind.config,tailwind-config,.tailwindrc}.js'
export default async function getClassNames(
cwd = process.cwd(),
{ onChange = () => {} } = {}
) {
let configPath
let postcss
let tailwindcss
let version
async function run() {
let configPath
let postcss
let tailwindcss
let version
try {
configPath = await glob(
'**/{tailwind,tailwind.config,tailwind-config,.tailwindrc}.js',
{
cwd,
ignore: '**/node_modules/**',
max: 1
}
)
configPath = await glob(CONFIG_GLOB, {
cwd,
ignore: '**/node_modules/**',
max: 1,
})
invariant(configPath.length === 1, 'No Tailwind CSS config found.')
configPath = configPath[0]
postcss = importFrom(cwd, 'postcss')
tailwindcss = importFrom(cwd, 'tailwindcss')
version = importFrom(cwd, 'tailwindcss/package.json').version
} catch (_) {
return null
}
async function run() {
const sepLocation = semver.gte(version, '0.99.0')
? ['separator']
: ['options', 'separator']
let userSeperator
let hook = Hook(configPath, exports => {
let hook = Hook(configPath, (exports) => {
userSeperator = dlv(exports, sepLocation)
dset(exports, sepLocation, '__TAILWIND_SEPARATOR__')
return exports
@ -97,35 +93,48 @@ export default async function getClassNames(
}
return {
configPath,
config: resolveConfig({ cwd, config }),
separator: typeof userSeperator === 'undefined' ? ':' : userSeperator,
classNames: await extractClassNames(ast),
dependencies: [configPath, ...hook.deps],
dependencies: hook.deps,
plugins: getPlugins(config),
variants: getVariants({ config, version, postcss })
variants: getVariants({ config, version, postcss }),
}
}
let watcher
function watch(files) {
function watch(files = []) {
if (watcher) watcher.close()
watcher = chokidar
.watch(files)
.watch([CONFIG_GLOB, ...files])
.on('change', handleChange)
.on('unlink', handleChange)
}
let result = await run()
watch(result.dependencies)
async function handleChange() {
const prevDeps = result.dependencies
result = await run()
const prevDeps = result ? result.dependencies : []
try {
result = await run()
} catch (_) {
onChange(null)
return
}
if (!arraysEqual(prevDeps, result.dependencies)) {
watch(result.dependencies)
}
onChange(result)
}
let result
try {
result = await run()
} catch (_) {
watch()
return null
}
watch(result.dependencies)
return result
}

View File

@ -18,7 +18,7 @@ import {
DidChangeConfigurationNotification,
} from 'vscode-languageserver'
import getTailwindState from 'tailwindcss-class-names'
import { State, Settings } from './util/state'
import { State, Settings, EditorState } from './util/state'
import {
provideCompletions,
resolveCompletionItem,
@ -27,7 +27,7 @@ import { provideHover } from './providers/hoverProvider'
import { URI } from 'vscode-uri'
import { getDocumentSettings } from './util/getDocumentSettings'
let state: State = null
let state: State = { enabled: false }
let connection = createConnection(ProposedFeatures.all)
let documents = new TextDocuments()
let workspaceFolder: string | null
@ -46,28 +46,43 @@ documents.listen(connection)
connection.onInitialize(
async (params: InitializeParams): Promise<InitializeResult> => {
state = await getTailwindState(
params.rootPath || URI.parse(params.rootUri).path,
{
onChange: (newState: State): void => {
state = { ...newState, editor: state.editor }
connection.sendNotification('tailwindcss/configUpdated', [
state.dependencies[0],
state.config,
state.plugins,
])
},
}
)
const capabilities = params.capabilities
state.editor = {
const editorState: EditorState = {
connection,
documents,
documentSettings,
globalSettings,
capabilities: { configuration: capabilities.workspace && !!capabilities.workspace.configuration },
capabilities: {
configuration:
capabilities.workspace && !!capabilities.workspace.configuration,
},
}
const tailwindState = await getTailwindState(
params.rootPath || URI.parse(params.rootUri).path,
{
onChange: (newState: State): void => {
if (newState) {
state = { ...newState, enabled: true, editor: editorState }
connection.sendNotification('tailwindcss/configUpdated', [
state.configPath,
state.config,
state.plugins,
])
} else {
state = { enabled: false, editor: editorState }
// TODO
// connection.sendNotification('tailwindcss/configUpdated', [null])
}
},
}
)
if (tailwindState) {
state = { enabled: true, editor: editorState, ...tailwindState }
} else {
state = { enabled: false, editor: editorState }
}
return {
@ -79,7 +94,20 @@ connection.onInitialize(
textDocumentSync: documents.syncKind,
completionProvider: {
resolveProvider: true,
triggerCharacters: ['"', "'", '`', ' ', '.', '[', state.separator],
triggerCharacters: [
// class attributes
'"',
"'",
'`',
// between class names
' ',
// @apply and emmet-style
'.',
// config/theme helper
'[',
// TODO: restart server if separater changes?
typeof state.separator === 'undefined' ? ':' : state.separator,
],
},
hoverProvider: true,
},
@ -97,7 +125,7 @@ connection.onInitialized &&
}
connection.sendNotification('tailwindcss/configUpdated', [
state.dependencies[0],
state.configPath,
state.config,
state.plugins,
])
@ -120,18 +148,21 @@ connection.onDidChangeConfiguration((change) => {
connection.onCompletion(
(params: CompletionParams): Promise<CompletionList> => {
if (!state.enabled) return null
return provideCompletions(state, params)
}
)
connection.onCompletionResolve(
(item: CompletionItem): CompletionItem => {
if (!state.enabled) return null
return resolveCompletionItem(state, item)
}
)
connection.onHover(
(params: TextDocumentPositionParams): Hover => {
if (!state.enabled) return null
return provideHover(state, params)
}
)

View File

@ -28,13 +28,15 @@ export type Settings = {
}
export type State = null | {
config: any
separator: string
plugins: any[]
variants: string[]
classNames: ClassNames
dependencies: string[]
editor: EditorState
enabled: boolean
configPath?: string
config?: any
separator?: string
plugins?: any[]
variants?: string[]
classNames?: ClassNames
dependencies?: string[]
editor?: EditorState
}
export type DocumentClassList = {