From 41b8fd104ee4e5fa31f681d1aa18dbd16a6039f9 Mon Sep 17 00:00:00 2001 From: Brad Cornes Date: Sat, 9 May 2020 22:11:35 +0100 Subject: [PATCH] diagnostics --- src/lsp/providers/diagnosticsProvider.ts | 79 ++++++++++++++++++++++++ src/lsp/server.ts | 8 +++ src/lsp/util/css.ts | 2 +- src/lsp/util/find.ts | 13 ++-- src/lsp/util/state.ts | 1 + 5 files changed, 96 insertions(+), 7 deletions(-) create mode 100644 src/lsp/providers/diagnosticsProvider.ts diff --git a/src/lsp/providers/diagnosticsProvider.ts b/src/lsp/providers/diagnosticsProvider.ts new file mode 100644 index 0000000..51565e9 --- /dev/null +++ b/src/lsp/providers/diagnosticsProvider.ts @@ -0,0 +1,79 @@ +import { + TextDocument, + Diagnostic, + DiagnosticSeverity, +} from 'vscode-languageserver' +import { State } from '../util/state' +import { isCssDoc } from '../util/css' +import { findClassNamesInRange } from '../util/find' +import { getClassNameParts } from '../util/getClassNameAtPosition' +const dlv = require('dlv') + +function provideCssDiagnostics(state: State, document: TextDocument): void { + const classNames = findClassNamesInRange(document) + + let diagnostics: Diagnostic[] = classNames + .map(({ className, range }) => { + const parts = getClassNameParts(state, className) + if (!parts) return null + const info = dlv(state.classNames.classNames, parts) + let message: string + if (info.__context && info.__context.length > 0) { + if (info.__context.length === 1) { + message = `\`@apply\` cannot be used with \`.${className}\` because it is nested inside of an at-rule (${info.__context[0]}).` + } else { + message = `\`@apply\` cannot be used with \`.${className}\` because it is nested inside of at-rules (${info.__context.join( + ', ' + )}).` + } + } else if (info.__pseudo && info.__pseudo.length > 0) { + if (info.__pseudo.length === 1) { + message = `\`@apply\` cannot be used with \`.${className}\` because its definition includes a pseudo-selector (${info.__pseudo[0]})` + } else { + message = `\`@apply\` cannot be used with \`.${className}\` because its definition includes pseudo-selectors (${info.__pseudo.join( + ', ' + )})` + } + } + + if (!message) return null + + return { + severity: DiagnosticSeverity.Error, + range, + message, + // source: 'ex', + } + }) + .filter(Boolean) + + // if (state.editor.capabilities.diagnosticRelatedInformation) { + // diagnostic.relatedInformation = [ + // { + // location: { + // uri: document.uri, + // range: Object.assign({}, diagnostic.range), + // }, + // message: '', + // }, + // { + // location: { + // uri: document.uri, + // range: Object.assign({}, diagnostic.range), + // }, + // message: '', + // }, + // ] + // } + + state.editor.connection.sendDiagnostics({ uri: document.uri, diagnostics }) +} + +export async function provideDiagnostics( + state: State, + document: TextDocument +): Promise { + if (isCssDoc(state, document)) { + return provideCssDiagnostics(state, document) + } +} diff --git a/src/lsp/server.ts b/src/lsp/server.ts index b28b1ba..480aa28 100644 --- a/src/lsp/server.ts +++ b/src/lsp/server.ts @@ -26,6 +26,7 @@ import { import { provideHover } from './providers/hoverProvider' import { URI } from 'vscode-uri' import { getDocumentSettings } from './util/getDocumentSettings' +import { provideDiagnostics } from './providers/diagnosticsProvider' let state: State = { enabled: false } let connection = createConnection(ProposedFeatures.all) @@ -45,6 +46,9 @@ documents.onDidOpen((event) => { documents.onDidClose((event) => { documentSettings.delete(event.document.uri) }) +documents.onDidChangeContent((change) => { + provideDiagnostics(state, change.document) +}) documents.listen(connection) connection.onInitialize( @@ -64,6 +68,10 @@ connection.onInitialize( capabilities: { configuration: capabilities.workspace && !!capabilities.workspace.configuration, + diagnosticRelatedInformation: + capabilities.textDocument && + capabilities.textDocument.publishDiagnostics && + capabilities.textDocument.publishDiagnostics.relatedInformation, }, } diff --git a/src/lsp/util/css.ts b/src/lsp/util/css.ts index 84b7888..d1acbea 100644 --- a/src/lsp/util/css.ts +++ b/src/lsp/util/css.ts @@ -11,7 +11,7 @@ export const CSS_LANGUAGES = [ 'stylus', ] -function isCssDoc(state: State, doc: TextDocument): boolean { +export function isCssDoc(state: State, doc: TextDocument): boolean { const userCssLanguages = Object.keys( state.editor.userLanguages ).filter((lang) => CSS_LANGUAGES.includes(state.editor.userLanguages[lang])) diff --git a/src/lsp/util/find.ts b/src/lsp/util/find.ts index 3fb0451..17b6a12 100644 --- a/src/lsp/util/find.ts +++ b/src/lsp/util/find.ts @@ -72,7 +72,7 @@ export function findJsxStrings(str: string): StringInfo[] { export function findClassNamesInRange( doc: TextDocument, - range: Range + range?: Range ): DocumentClassName[] { const classLists = findClassListsInRange(doc, range) return [].concat.apply( @@ -111,10 +111,11 @@ export function findClassNamesInRange( export function findClassListsInRange( doc: TextDocument, - range: Range + range?: Range ): DocumentClassList[] { const text = doc.getText(range) const matches = findAll(/(@apply\s+)(?[^;}]+)[;}]/g, text) + const globalStart: Position = range ? range.start : { line: 0, character: 0 } return matches.map((match) => { const start = indexToPosition(text, match.index + match[1].length) @@ -126,12 +127,12 @@ export function findClassListsInRange( classList: match.groups.classList, range: { start: { - line: range.start.line + start.line, - character: range.start.character + start.character, + line: globalStart.line + start.line, + character: globalStart.character + start.character, }, end: { - line: range.start.line + end.line, - character: range.start.character + end.character, + line: globalStart.line + end.line, + character: globalStart.character + end.character, }, }, } diff --git a/src/lsp/util/state.ts b/src/lsp/util/state.ts index daaa050..fc474eb 100644 --- a/src/lsp/util/state.ts +++ b/src/lsp/util/state.ts @@ -21,6 +21,7 @@ export type EditorState = { userLanguages: Record capabilities: { configuration: boolean + diagnosticRelatedInformation: boolean } }