diff --git a/packages/language-server/src/plugins/css/CSSDocument.ts b/packages/language-server/src/plugins/css/CSSDocument.ts index 0d221ac29..4b2d09975 100644 --- a/packages/language-server/src/plugins/css/CSSDocument.ts +++ b/packages/language-server/src/plugins/css/CSSDocument.ts @@ -1,7 +1,7 @@ import { Stylesheet, TextDocument } from 'vscode-css-languageservice'; import { Position } from 'vscode-languageserver'; -import { getLanguageService } from './service'; import { Document, DocumentMapper, ReadableDocument, TagInformation } from '../../lib/documents'; +import { CSSLanguageServices, getLanguageService } from './service'; export interface CSSDocumentBase extends DocumentMapper, TextDocument { languageId: string; @@ -15,7 +15,7 @@ export class CSSDocument extends ReadableDocument implements DocumentMapper { public stylesheet: Stylesheet; public languageId: string; - constructor(private parent: Document) { + constructor(private parent: Document, languageServices: CSSLanguageServices) { super(); if (this.parent.styleInfo) { @@ -29,7 +29,9 @@ export class CSSDocument extends ReadableDocument implements DocumentMapper { } this.languageId = this.language; - this.stylesheet = getLanguageService(this.language).parseStylesheet(this); + this.stylesheet = getLanguageService(languageServices, this.languageId).parseStylesheet( + this + ); } /** diff --git a/packages/language-server/src/plugins/css/CSSPlugin.ts b/packages/language-server/src/plugins/css/CSSPlugin.ts index fc77479de..548f141ec 100644 --- a/packages/language-server/src/plugins/css/CSSPlugin.ts +++ b/packages/language-server/src/plugins/css/CSSPlugin.ts @@ -13,7 +13,8 @@ import { SymbolInformation, CompletionItem, CompletionItemKind, - SelectionRange + SelectionRange, + WorkspaceFolder } from 'vscode-languageserver'; import { Document, @@ -38,11 +39,12 @@ import { SelectionRangeProvider } from '../interfaces'; import { CSSDocument, CSSDocumentBase } from './CSSDocument'; -import { getLanguage, getLanguageService } from './service'; +import { CSSLanguageServices, getLanguage, getLanguageService } from './service'; import { GlobalVars } from './global-vars'; import { getIdClassCompletion } from './features/getIdClassCompletion'; import { AttributeContext, getAttributeContextAtPosition } from '../../lib/documents/parseHtml'; import { StyleAttributeDocument } from './StyleAttributeDocument'; +import { getDocumentContext } from '../documentContext'; export class CSSPlugin implements @@ -57,10 +59,19 @@ export class CSSPlugin __name = 'css'; private configManager: LSConfigManager; private cssDocuments = new WeakMap(); + private cssLanguageServices: CSSLanguageServices; + private workspaceFolders: WorkspaceFolder[]; private triggerCharacters = ['.', ':', '-', '/']; private globalVars = new GlobalVars(); - constructor(docManager: DocumentManager, configManager: LSConfigManager) { + constructor( + docManager: DocumentManager, + configManager: LSConfigManager, + workspaceFolders: WorkspaceFolder[], + cssLanguageServices: CSSLanguageServices + ) { + this.cssLanguageServices = cssLanguageServices; + this.workspaceFolders = workspaceFolders; this.configManager = configManager; this.updateConfigs(); @@ -71,7 +82,7 @@ export class CSSPlugin }); docManager.on('documentChange', (document) => - this.cssDocuments.set(document, new CSSDocument(document)) + this.cssDocuments.set(document, new CSSDocument(document, this.cssLanguageServices)) ); docManager.on('documentClose', (document) => this.cssDocuments.delete(document)); } @@ -82,7 +93,7 @@ export class CSSPlugin } const cssDocument = this.getCSSDoc(document); - const [range] = getLanguageService(extractLanguage(cssDocument)).getSelectionRanges( + const [range] = this.getLanguageService(extractLanguage(cssDocument)).getSelectionRanges( cssDocument, [cssDocument.getGeneratedPosition(position)], cssDocument.stylesheet @@ -107,7 +118,7 @@ export class CSSPlugin return []; } - return getLanguageService(kind) + return this.getLanguageService(kind) .doValidation(cssDocument, cssDocument.stylesheet) .map((diagnostic) => ({ ...diagnostic, source: getLanguage(kind) })) .map((diagnostic) => mapObjWithRangeToOriginal(cssDocument, diagnostic)); @@ -131,13 +142,16 @@ export class CSSPlugin this.inStyleAttributeWithoutInterpolation(attributeContext, document.getText()) ) { const [start, end] = attributeContext.valueRange; - return this.doHoverInternal(new StyleAttributeDocument(document, start, end), position); + return this.doHoverInternal( + new StyleAttributeDocument(document, start, end, this.cssLanguageServices), + position + ); } return null; } private doHoverInternal(cssDocument: CSSDocumentBase, position: Position) { - const hoverInfo = getLanguageService(extractLanguage(cssDocument)).doHover( + const hoverInfo = this.getLanguageService(extractLanguage(cssDocument)).doHover( cssDocument, cssDocument.getGeneratedPosition(position), cssDocument.stylesheet @@ -145,11 +159,11 @@ export class CSSPlugin return hoverInfo ? mapHoverToParent(cssDocument, hoverInfo) : hoverInfo; } - getCompletions( + async getCompletions( document: Document, position: Position, completionContext?: CompletionContext - ): CompletionList | null { + ): Promise { const triggerCharacter = completionContext?.triggerCharacter; const triggerKind = completionContext?.triggerKind; const isCustomTriggerCharacter = triggerKind === CompletionTriggerKind.TriggerCharacter; @@ -182,7 +196,7 @@ export class CSSPlugin return this.getCompletionsInternal( document, position, - new StyleAttributeDocument(document, start, end) + new StyleAttributeDocument(document, start, end, this.cssLanguageServices) ); } else { return getIdClassCompletion(cssDocument, attributeContext); @@ -200,7 +214,7 @@ export class CSSPlugin ); } - private getCompletionsInternal( + private async getCompletionsInternal( document: Document, position: Position, cssDocument: CSSDocumentBase @@ -219,7 +233,7 @@ export class CSSPlugin return null; } - const lang = getLanguageService(type); + const lang = this.getLanguageService(type); let emmetResults: CompletionList = { isIncomplete: false, items: [] @@ -256,10 +270,11 @@ export class CSSPlugin ]); } - const results = lang.doComplete( + const results = await lang.doComplete2( cssDocument, cssDocument.getGeneratedPosition(position), - cssDocument.stylesheet + cssDocument.stylesheet, + getDocumentContext(cssDocument.uri, this.workspaceFolders) ); return CompletionList.create( this.appendGlobalVars( @@ -301,7 +316,7 @@ export class CSSPlugin return []; } - return getLanguageService(extractLanguage(cssDocument)) + return this.getLanguageService(extractLanguage(cssDocument)) .findDocumentColors(cssDocument, cssDocument.stylesheet) .map((colorInfo) => mapObjWithRangeToOriginal(cssDocument, colorInfo)); } @@ -319,7 +334,7 @@ export class CSSPlugin return []; } - return getLanguageService(extractLanguage(cssDocument)) + return this.getLanguageService(extractLanguage(cssDocument)) .getColorPresentations( cssDocument, cssDocument.stylesheet, @@ -340,7 +355,7 @@ export class CSSPlugin return []; } - return getLanguageService(extractLanguage(cssDocument)) + return this.getLanguageService(extractLanguage(cssDocument)) .findDocumentSymbols(cssDocument, cssDocument.stylesheet) .map((symbol) => { if (!symbol.containerName) { @@ -359,16 +374,16 @@ export class CSSPlugin private getCSSDoc(document: Document) { let cssDoc = this.cssDocuments.get(document); if (!cssDoc || cssDoc.version < document.version) { - cssDoc = new CSSDocument(document); + cssDoc = new CSSDocument(document, this.cssLanguageServices); this.cssDocuments.set(document, cssDoc); } return cssDoc; } private updateConfigs() { - getLanguageService('css')?.configure(this.configManager.getCssConfig()); - getLanguageService('scss')?.configure(this.configManager.getScssConfig()); - getLanguageService('less')?.configure(this.configManager.getLessConfig()); + this.getLanguageService('css')?.configure(this.configManager.getCssConfig()); + this.getLanguageService('scss')?.configure(this.configManager.getScssConfig()); + this.getLanguageService('less')?.configure(this.configManager.getLessConfig()); } private featureEnabled(feature: keyof LSCSSConfig) { @@ -377,6 +392,10 @@ export class CSSPlugin this.configManager.enabled(`css.${feature}.enable`) ); } + + private getLanguageService(kind: string) { + return getLanguageService(this.cssLanguageServices, kind); + } } function shouldExcludeValidation(kind?: string) { diff --git a/packages/language-server/src/plugins/css/FileSystemProvider.ts b/packages/language-server/src/plugins/css/FileSystemProvider.ts new file mode 100644 index 000000000..953b9446c --- /dev/null +++ b/packages/language-server/src/plugins/css/FileSystemProvider.ts @@ -0,0 +1,94 @@ +import { stat, readdir, Stats } from 'fs'; +import { promisify } from 'util'; +import { + FileStat, + FileSystemProvider as CSSFileSystemProvider, + FileType +} from 'vscode-css-languageservice'; +import { urlToPath } from '../../utils'; + +interface StatLike { + isDirectory(): boolean; + isFile(): boolean; + isSymbolicLink(): boolean; +} + +export class FileSystemProvider implements CSSFileSystemProvider { + // TODO use fs/promises after we bumps the target nodejs versions + private promisifyStat = promisify(stat); + private promisifyReaddir = promisify(readdir); + + constructor() { + this.readDirectory = this.readDirectory.bind(this); + this.stat = this.stat.bind(this); + } + + async stat(uri: string): Promise { + const path = urlToPath(uri); + + if (!path) { + return this.unknownStat(); + } + + let stat: Stats; + try { + stat = await this.promisifyStat(path); + } catch (error) { + if ( + error != null && + typeof error === 'object' && + 'code' in error && + (error as { code: string }).code === 'ENOENT' + ) { + return { + type: FileType.Unknown, + ctime: -1, + mtime: -1, + size: -1 + }; + } + + throw error; + } + + return { + ctime: stat.ctimeMs, + mtime: stat.mtimeMs, + size: stat.size, + type: this.getFileType(stat) + }; + } + + private unknownStat(): FileStat { + return { + type: FileType.Unknown, + ctime: -1, + mtime: -1, + size: -1 + }; + } + + private getFileType(stat: StatLike) { + return stat.isDirectory() + ? FileType.Directory + : stat.isFile() + ? FileType.File + : stat.isSymbolicLink() + ? FileType.SymbolicLink + : FileType.Unknown; + } + + async readDirectory(uri: string): Promise> { + const path = urlToPath(uri); + + if (!path) { + return []; + } + + const files = await this.promisifyReaddir(path, { + withFileTypes: true + }); + + return files.map((file) => [file.name, this.getFileType(file)]); + } +} diff --git a/packages/language-server/src/plugins/css/StyleAttributeDocument.ts b/packages/language-server/src/plugins/css/StyleAttributeDocument.ts index 6bec75b8e..fca96ce8f 100644 --- a/packages/language-server/src/plugins/css/StyleAttributeDocument.ts +++ b/packages/language-server/src/plugins/css/StyleAttributeDocument.ts @@ -1,6 +1,6 @@ import { Stylesheet } from 'vscode-css-languageservice'; import { Position } from 'vscode-languageserver'; -import { getLanguageService } from './service'; +import { CSSLanguageServices, getLanguageService } from './service'; import { Document, DocumentMapper, ReadableDocument } from '../../lib/documents'; const PREFIX = '__ {'; @@ -15,11 +15,12 @@ export class StyleAttributeDocument extends ReadableDocument implements Document constructor( private readonly parent: Document, private readonly attrStart: number, - private readonly attrEnd: number + private readonly attrEnd: number, + languageServices: CSSLanguageServices ) { super(); - this.stylesheet = getLanguageService(this.languageId).parseStylesheet(this); + this.stylesheet = getLanguageService(languageServices).parseStylesheet(this); } /** diff --git a/packages/language-server/src/plugins/css/service.ts b/packages/language-server/src/plugins/css/service.ts index 046d71bb7..8158abc56 100644 --- a/packages/language-server/src/plugins/css/service.ts +++ b/packages/language-server/src/plugins/css/service.ts @@ -3,7 +3,8 @@ import { getSCSSLanguageService, getLESSLanguageService, LanguageService, - ICSSDataProvider + ICSSDataProvider, + LanguageServiceOptions } from 'vscode-css-languageservice'; import { pesudoClass } from './features/svelte-selectors'; @@ -28,22 +29,6 @@ const customDataProvider: ICSSDataProvider = { } }; -const [css, scss, less] = [ - getCSSLanguageService, - getSCSSLanguageService, - getLESSLanguageService -].map((getService) => - getService({ - customDataProviders: [customDataProvider] - }) -); - -const langs = { - css, - scss, - less -}; - export function getLanguage(kind?: string) { switch (kind) { case 'scss': @@ -59,7 +44,28 @@ export function getLanguage(kind?: string) { } } -export function getLanguageService(kind?: string): LanguageService { +export type CSSLanguageServices = Record<'css' | 'less' | 'scss', LanguageService>; + +export function getLanguageService(langs: CSSLanguageServices, kind?: string): LanguageService { const lang = getLanguage(kind); return langs[lang]; } + +export function createLanguageServices(options?: LanguageServiceOptions): CSSLanguageServices { + const [css, less, scss] = [ + getCSSLanguageService, + getLESSLanguageService, + getSCSSLanguageService + ].map((getService) => + getService({ + customDataProviders: [customDataProvider], + ...(options ?? {}) + }) + ); + + return { + css, + less, + scss + }; +} diff --git a/packages/language-server/src/plugins/documentContext.ts b/packages/language-server/src/plugins/documentContext.ts new file mode 100644 index 000000000..b6c279b37 --- /dev/null +++ b/packages/language-server/src/plugins/documentContext.ts @@ -0,0 +1,42 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +// adopted from https://github.com/microsoft/vscode/blob/5ffcfde11d8b1b57634627f5094907789db09776/extensions/css-language-features/server/src/utils/documentContext.ts + +import { DocumentContext } from 'vscode-css-languageservice'; +import { WorkspaceFolder } from 'vscode-languageserver'; +import { Utils, URI } from 'vscode-uri'; + +export function getDocumentContext( + documentUri: string, + workspaceFolders: WorkspaceFolder[] +): DocumentContext { + function getRootFolder(): string | undefined { + for (const folder of workspaceFolders) { + let folderURI = folder.uri; + if (!folderURI.endsWith('/')) { + folderURI = folderURI + '/'; + } + if (documentUri.startsWith(folderURI)) { + return folderURI; + } + } + return undefined; + } + + return { + resolveReference: (ref: string, base = documentUri) => { + if (ref[0] === '/') { + // resolve absolute path against the current workspace folder + const folderUri = getRootFolder(); + if (folderUri) { + return folderUri + ref.substr(1); + } + } + base = base.substr(0, base.lastIndexOf('/') + 1); + return Utils.resolvePath(URI.parse(base), ref).toString(); + } + }; +} diff --git a/packages/language-server/src/server.ts b/packages/language-server/src/server.ts index 398e9bdca..690906a4f 100644 --- a/packages/language-server/src/server.ts +++ b/packages/language-server/src/server.ts @@ -38,6 +38,8 @@ import { FallbackWatcher } from './lib/FallbackWatcher'; import { configLoader } from './lib/documents/configLoader'; import { setIsTrusted } from './importPackage'; import { SORT_IMPORT_CODE_ACTION_KIND } from './plugins/typescript/features/CodeActionsProvider'; +import { createLanguageServices } from './plugins/css/service'; +import { FileSystemProvider } from './plugins/css/FileSystemProvider'; namespace TagCloseRequest { export const type: RequestType = @@ -147,7 +149,15 @@ export function startServer(options?: LSOptions) { // Order of plugin registration matters for FirstNonNull, which affects for example hover info pluginHost.register((sveltePlugin = new SveltePlugin(configManager))); pluginHost.register(new HTMLPlugin(docManager, configManager)); - pluginHost.register(new CSSPlugin(docManager, configManager)); + + const cssLanguageServices = createLanguageServices({ + clientCapabilities: evt.capabilities, + fileSystemProvider: new FileSystemProvider() + }); + const workspaceFolders = evt.workspaceFolders ?? [{ name: '', uri: evt.rootUri ?? '' }]; + pluginHost.register( + new CSSPlugin(docManager, configManager, workspaceFolders, cssLanguageServices) + ); pluginHost.register( new TypeScriptPlugin( configManager, diff --git a/packages/language-server/src/svelte-check.ts b/packages/language-server/src/svelte-check.ts index 82e3b91ce..502988cf6 100644 --- a/packages/language-server/src/svelte-check.ts +++ b/packages/language-server/src/svelte-check.ts @@ -1,7 +1,9 @@ import { isAbsolute } from 'path'; import ts from 'typescript'; import { Diagnostic, Position, Range } from 'vscode-languageserver'; +import { WorkspaceFolder } from 'vscode-languageserver-protocol'; import { Document, DocumentManager } from './lib/documents'; +import { FileSystemProvider } from './plugins/css/FileSystemProvider'; import { Logger } from './logger'; import { LSConfigManager } from './ls-config'; import { @@ -11,6 +13,7 @@ import { SveltePlugin, TypeScriptPlugin } from './plugins'; +import { createLanguageServices } from './plugins/css/service'; import { convertRange, getDiagnosticTag, mapSeverity } from './plugins/typescript/utils'; import { pathToUrl, urlToPath } from './utils'; @@ -62,7 +65,18 @@ export class SvelteCheck { this.pluginHost.register(new SveltePlugin(this.configManager)); } if (shouldRegister('css')) { - this.pluginHost.register(new CSSPlugin(this.docManager, this.configManager)); + const services = createLanguageServices({ + fileSystemProvider: new FileSystemProvider() + }); + const workspaceFolders: WorkspaceFolder[] = [ + { + name: '', + uri: pathToUrl(workspacePath) + } + ]; + this.pluginHost.register( + new CSSPlugin(this.docManager, this.configManager, workspaceFolders, services) + ); } if (shouldRegister('js') || options.tsconfig) { this.lsAndTSDocResolver = new LSAndTSDocResolver( diff --git a/packages/language-server/test/plugins/css/CSSPlugin.test.ts b/packages/language-server/test/plugins/css/CSSPlugin.test.ts index cc029d3ee..4491f8560 100644 --- a/packages/language-server/test/plugins/css/CSSPlugin.test.ts +++ b/packages/language-server/test/plugins/css/CSSPlugin.test.ts @@ -13,13 +13,26 @@ import { import { DocumentManager, Document } from '../../../src/lib/documents'; import { CSSPlugin } from '../../../src/plugins'; import { LSConfigManager } from '../../../src/ls-config'; +import { createLanguageServices } from '../../../src/plugins/css/service'; +import { pathToUrl } from '../../../src/utils'; +import { FileType, LanguageServiceOptions } from 'vscode-css-languageservice'; describe('CSS Plugin', () => { - function setup(content: string) { + function setup(content: string, lsOptions?: LanguageServiceOptions) { const document = new Document('file:///hello.svelte', content); const docManager = new DocumentManager(() => document); const pluginManager = new LSConfigManager(); - const plugin = new CSSPlugin(docManager, pluginManager); + const plugin = new CSSPlugin( + docManager, + pluginManager, + [ + { + name: '', + uri: pathToUrl(process.cwd()) + } + ], + createLanguageServices(lsOptions) + ); docManager.openDocument('some doc'); return { plugin, document }; } @@ -72,10 +85,10 @@ describe('CSS Plugin', () => { }); describe('provides completions', () => { - it('for normal css', () => { + it('for normal css', async () => { const { plugin, document } = setup(''); - const completions = plugin.getCompletions(document, Position.create(0, 7), { + const completions = await plugin.getCompletions(document, Position.create(0, 7), { triggerCharacter: '.' } as CompletionContext); assert.ok( @@ -96,27 +109,27 @@ describe('CSS Plugin', () => { }); }); - it('for :global modifier', () => { + it('for :global modifier', async () => { const { plugin, document } = setup(''); - const completions = plugin.getCompletions(document, Position.create(0, 9), { + const completions = await plugin.getCompletions(document, Position.create(0, 9), { triggerCharacter: ':' } as CompletionContext); const globalCompletion = completions?.items.find((item) => item.label === ':global()'); assert.ok(globalCompletion); }); - it('not for stylus', () => { + it('not for stylus', async () => { const { plugin, document } = setup(''); - const completions = plugin.getCompletions(document, Position.create(0, 21), { + const completions = await plugin.getCompletions(document, Position.create(0, 21), { triggerCharacter: '.' } as CompletionContext); assert.deepStrictEqual(completions, null); }); - it('for style attribute', () => { + it('for style attribute', async () => { const { plugin, document } = setup('
'); - const completions = plugin.getCompletions(document, Position.create(0, 22), { + const completions = await plugin.getCompletions(document, Position.create(0, 22), { triggerKind: CompletionTriggerKind.Invoked } as CompletionContext); assert.deepStrictEqual( @@ -148,9 +161,48 @@ describe('CSS Plugin', () => { ); }); - it('not for style attribute with interpolation', () => { + it('not for style attribute with interpolation', async () => { const { plugin, document } = setup('
'); - assert.deepStrictEqual(plugin.getCompletions(document, Position.create(0, 21)), null); + assert.deepStrictEqual( + await plugin.getCompletions(document, Position.create(0, 21)), + null + ); + }); + + it('for path completion', async () => { + const { plugin, document } = setup('', { + fileSystemProvider: { + stat: () => + Promise.resolve({ + ctime: Date.now(), + mtime: Date.now(), + size: 0, + type: FileType.File + }), + readDirectory: () => Promise.resolve([['foo.css', FileType.File]]) + } + }); + const completions = await plugin.getCompletions(document, Position.create(0, 16)); + assert.deepStrictEqual( + completions?.items.find((item) => item.label === 'foo.css'), + { + label: 'foo.css', + kind: 17, + textEdit: { + newText: 'foo.css', + range: { + end: { + character: 18, + line: 0 + }, + start: { + character: 16, + line: 0 + } + } + } + } + ); }); }); diff --git a/packages/language-server/test/plugins/css/features/getIdClassCompletion.test.ts b/packages/language-server/test/plugins/css/features/getIdClassCompletion.test.ts index 6d3321246..a49c9efc2 100644 --- a/packages/language-server/test/plugins/css/features/getIdClassCompletion.test.ts +++ b/packages/language-server/test/plugins/css/features/getIdClassCompletion.test.ts @@ -9,6 +9,8 @@ import { NodeType, CSSNode } from '../../../../src/plugins/css/features/getIdClassCompletion'; +import { createLanguageServices } from '../../../../src/plugins/css/service'; +import { pathToUrl } from '../../../../src/utils'; describe('getIdClassCompletion', () => { function createDocument(content: string) { @@ -16,7 +18,7 @@ describe('getIdClassCompletion', () => { } function createCSSDocument(content: string) { - return new CSSDocument(createDocument(content)); + return new CSSDocument(createDocument(content), createLanguageServices()); } function testSelectors(items: CompletionItem[], expectedSelectors: string[]) { @@ -47,30 +49,35 @@ describe('getIdClassCompletion', () => { const document = createDocument(content); const docManager = new DocumentManager(() => document); const pluginManager = new LSConfigManager(); - const plugin = new CSSPlugin(docManager, pluginManager); + const plugin = new CSSPlugin( + docManager, + pluginManager, + [{ name: '', uri: pathToUrl(process.cwd()) }], + createLanguageServices() + ); docManager.openDocument('some doc'); return { plugin, document }; } - it('provides css classes completion for class attribute', () => { + it('provides css classes completion for class attribute', async () => { const { plugin, document } = setup('
'); - assert.deepStrictEqual(plugin.getCompletions(document, { line: 0, character: 11 }), { + assert.deepStrictEqual(await plugin.getCompletions(document, { line: 0, character: 11 }), { isIncomplete: false, items: [{ label: 'abc', kind: CompletionItemKind.Keyword }] } as CompletionList); }); - it('provides css classes completion for class directive', () => { + it('provides css classes completion for class directive', async () => { const { plugin, document } = setup('
'); - assert.deepStrictEqual(plugin.getCompletions(document, { line: 0, character: 11 }), { + assert.deepStrictEqual(await plugin.getCompletions(document, { line: 0, character: 11 }), { isIncomplete: false, items: [{ label: 'abc', kind: CompletionItemKind.Keyword }] } as CompletionList); }); - it('provides css id completion for id attribute', () => { + it('provides css id completion for id attribute', async () => { const { plugin, document } = setup('
'); - assert.deepStrictEqual(plugin.getCompletions(document, { line: 0, character: 8 }), { + assert.deepStrictEqual(await plugin.getCompletions(document, { line: 0, character: 8 }), { isIncomplete: false, items: [{ label: 'abc', kind: CompletionItemKind.Keyword }] } as CompletionList);