From 568e078522bf2a9db05218b64917d5a82cf9ce76 Mon Sep 17 00:00:00 2001 From: Brad Cornes Date: Wed, 22 Apr 2020 20:29:36 +0100 Subject: [PATCH] prevent crash on config error --- packages/tailwindcss-class-names/src/index.js | 65 +++++++++-------- .../tailwindcss-language-server/src/server.ts | 71 +++++++++++++------ .../src/util/state.ts | 16 +++-- 3 files changed, 97 insertions(+), 55 deletions(-) diff --git a/packages/tailwindcss-class-names/src/index.js b/packages/tailwindcss-class-names/src/index.js index f9f39c4..1835e66 100644 --- a/packages/tailwindcss-class-names/src/index.js +++ b/packages/tailwindcss-class-names/src/index.js @@ -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 } diff --git a/packages/tailwindcss-language-server/src/server.ts b/packages/tailwindcss-language-server/src/server.ts index 58fada3..729d1f0 100644 --- a/packages/tailwindcss-language-server/src/server.ts +++ b/packages/tailwindcss-language-server/src/server.ts @@ -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 => { - 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 => { + 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) } ) diff --git a/packages/tailwindcss-language-server/src/util/state.ts b/packages/tailwindcss-language-server/src/util/state.ts index 4b25832..175dade 100644 --- a/packages/tailwindcss-language-server/src/util/state.ts +++ b/packages/tailwindcss-language-server/src/util/state.ts @@ -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 = {