From ec9b0e2abadf81bd001b63d9ad910c5d4f569f87 Mon Sep 17 00:00:00 2001 From: Barak Igal Date: Thu, 7 Jul 2022 12:36:38 +0300 Subject: [PATCH 1/5] feat: language service resolver cache --- src/lib/server.ts | 71 +++++++++++++++++++++------------------ src/lib/vscode-service.ts | 2 +- src/lib/wrap-fs.ts | 7 ++-- test/unit/service.spec.ts | 16 ++++----- 4 files changed, 51 insertions(+), 45 deletions(-) diff --git a/src/lib/server.ts b/src/lib/server.ts index fdcee8b0..583562d9 100644 --- a/src/lib/server.ts +++ b/src/lib/server.ts @@ -10,50 +10,57 @@ import { import { TextDocument } from 'vscode-languageserver-textdocument'; import { initializeResult } from './capabilities'; -import { VscodeStylableLanguageService } from './vscode-service'; +import { VSCodeStylableLanguageService } from './vscode-service'; import { wrapFs } from './wrap-fs'; +import { URI } from 'vscode-uri'; const connection = createConnection(new IPCMessageReader(process), new IPCMessageWriter(process)); -let vscodeStylableLSP: VscodeStylableLanguageService; -connection.listen(); connection.onInitialize((params) => { const docs = new TextDocuments(TextDocument); const wrappedFs = wrapFs(fs, docs); + const stylable = Stylable.create({ + projectRoot: params.rootPath || '', + fileSystem: wrappedFs as MinimalFS, + requireModule: require, + cssParser: safeParse, + resolverCache: new Map(), + }); + const lsp = new VSCodeStylableLanguageService(connection, docs, wrappedFs, stylable); - vscodeStylableLSP = new VscodeStylableLanguageService( - connection, - docs, - wrappedFs, - Stylable.create({ - projectRoot: params.rootPath || '', - fileSystem: wrappedFs as MinimalFS, - requireModule: require, - cssParser: safeParse - }) - ); + docs.onDidChangeContent((event) => { + const { fsPath } = URI.parse(event.document.uri); + stylable.initCache({ + filter(_, entity) { + if ('resolvedPath' in entity) { + return entity.resolvedPath !== fsPath; + } + return true; + }, + }); + }); docs.listen(connection); - docs.onDidChangeContent(vscodeStylableLSP.diagnoseWithVsCodeConfig.bind(vscodeStylableLSP)); - docs.onDidClose(vscodeStylableLSP.onDidClose.bind(vscodeStylableLSP)); + docs.onDidChangeContent(lsp.diagnoseWithVsCodeConfig.bind(lsp)); + docs.onDidClose(lsp.onDidClose.bind(lsp)); - connection.onCompletion(vscodeStylableLSP.onCompletion.bind(vscodeStylableLSP)); - connection.onDefinition(vscodeStylableLSP.onDefinition.bind(vscodeStylableLSP)); - connection.onHover(vscodeStylableLSP.onHover.bind(vscodeStylableLSP)); - connection.onReferences(vscodeStylableLSP.onReferences.bind(vscodeStylableLSP)); - connection.onDocumentColor(vscodeStylableLSP.onDocumentColor.bind(vscodeStylableLSP)); - connection.onColorPresentation(vscodeStylableLSP.onColorPresentation.bind(vscodeStylableLSP)); - connection.onRenameRequest(vscodeStylableLSP.onRenameRequest.bind(vscodeStylableLSP)); - connection.onSignatureHelp(vscodeStylableLSP.onSignatureHelp.bind(vscodeStylableLSP)); - connection.onDocumentFormatting(vscodeStylableLSP.onDocumentFormatting.bind(vscodeStylableLSP)); - connection.onDocumentRangeFormatting(vscodeStylableLSP.onDocumentRangeFormatting.bind(vscodeStylableLSP)); + connection.onCompletion(lsp.onCompletion.bind(lsp)); + connection.onDefinition(lsp.onDefinition.bind(lsp)); + connection.onHover(lsp.onHover.bind(lsp)); + connection.onReferences(lsp.onReferences.bind(lsp)); + connection.onDocumentColor(lsp.onDocumentColor.bind(lsp)); + connection.onColorPresentation(lsp.onColorPresentation.bind(lsp)); + connection.onRenameRequest(lsp.onRenameRequest.bind(lsp)); + connection.onSignatureHelp(lsp.onSignatureHelp.bind(lsp)); + connection.onDocumentFormatting(lsp.onDocumentFormatting.bind(lsp)); + connection.onDocumentRangeFormatting(lsp.onDocumentRangeFormatting.bind(lsp)); // eslint-disable-next-line @typescript-eslint/no-misused-promises - connection.onDidChangeConfiguration(vscodeStylableLSP.onChangeConfig.bind(vscodeStylableLSP)); - + connection.onDidChangeConfiguration(lsp.onChangeConfig.bind(lsp)); + connection.onInitialized(() => { + connection.client.register(DidChangeConfigurationNotification.type, undefined).catch(console.error); + lsp.loadClientConfiguration().then(console.log).catch(console.error); + }); return initializeResult; }); -connection.onInitialized(() => { - connection.client.register(DidChangeConfigurationNotification.type, undefined).catch(console.error); - vscodeStylableLSP.loadClientConfiguration().then(console.log).catch(console.error); -}); +connection.listen(); diff --git a/src/lib/vscode-service.ts b/src/lib/vscode-service.ts index e022d0fa..d5e7847f 100644 --- a/src/lib/vscode-service.ts +++ b/src/lib/vscode-service.ts @@ -37,7 +37,7 @@ export interface ExtensionConfiguration { formatting: CSSBeautifyOptions; } -export class VscodeStylableLanguageService { +export class VSCodeStylableLanguageService { public textDocuments: TextDocuments; public languageService: StylableLanguageService; private connection: Connection; diff --git a/src/lib/wrap-fs.ts b/src/lib/wrap-fs.ts index d376eccc..c6f3c6f8 100644 --- a/src/lib/wrap-fs.ts +++ b/src/lib/wrap-fs.ts @@ -4,9 +4,8 @@ import { URI } from 'vscode-uri'; import type { TextDocument } from 'vscode-languageserver-textdocument'; export function wrapFs(fs: IFileSystem, docs: TextDocuments): IFileSystem { - const readFileSync = ((path: string, ...args: [ReadFileOptions]) => { - const file = docs.get(URI.file(path).toString()); - return file ? file.getText() : fs.readFileSync(path, ...args); - }) as IBaseFileSystemSyncActions['readFileSync']; + const readFileSync = ((path: string, ...args: [ReadFileOptions]) => + docs.get(URI.file(path).toString())?.getText() ?? + fs.readFileSync(path, ...args)) as IBaseFileSystemSyncActions['readFileSync']; return { ...fs, readFileSync }; } diff --git a/test/unit/service.spec.ts b/test/unit/service.spec.ts index ab83240e..ec12f7c5 100644 --- a/test/unit/service.spec.ts +++ b/test/unit/service.spec.ts @@ -8,7 +8,7 @@ import { URI } from 'vscode-uri'; import { TestConnection } from '../lsp-testkit/connection.spec'; import { expect, plan } from '../testkit/chai.spec'; -import { VscodeStylableLanguageService } from '../../src/lib/vscode-service'; +import { VSCodeStylableLanguageService } from '../../src/lib/vscode-service'; import { getRangeAndText } from '../testkit/text.spec'; import { TestDocuments } from '../lsp-testkit/test-documents'; @@ -67,7 +67,7 @@ describe('Service component test', () => { const memFs = createMemoryFs({ [baseFileName]: rangeAndText.text }); const { requireModule } = createCjsModuleSystem({ fs: memFs }); - const stylableLSP = new VscodeStylableLanguageService( + const stylableLSP = new VSCodeStylableLanguageService( connection, new TestDocuments({ [baseTextDocument.uri]: baseTextDocument, @@ -121,7 +121,7 @@ describe('Service component test', () => { const memFs = createMemoryFs({ [baseFileName]: baseFilecContent, [topFileName]: topFileContent }); const { requireModule } = createCjsModuleSystem({ fs: memFs }); - const stylableLSP = new VscodeStylableLanguageService( + const stylableLSP = new VSCodeStylableLanguageService( connection, new TestDocuments({ [baseTextDocument.uri]: baseTextDocument, @@ -181,7 +181,7 @@ describe('Service component test', () => { const memFs = createMemoryFs({ [baseFileName]: baseFilecContent }); const { requireModule } = createCjsModuleSystem({ fs: memFs }); - const stylableLSP = new VscodeStylableLanguageService( + const stylableLSP = new VSCodeStylableLanguageService( connection, new TestDocuments({ [baseTextDocument.uri]: baseTextDocument, @@ -207,7 +207,7 @@ describe('Service component test', () => { const memFs = createMemoryFs({ [baseFileName]: text }); const { requireModule } = createCjsModuleSystem({ fs: memFs }); - const stylableLSP = new VscodeStylableLanguageService( + const stylableLSP = new VSCodeStylableLanguageService( connection, new TestDocuments({ [textDocument.uri]: textDocument, @@ -240,7 +240,7 @@ describe('Service component test', () => { const memFs = createMemoryFs({ [baseFileName]: text }); const { requireModule } = createCjsModuleSystem({ fs: memFs }); - const stylableLSP = new VscodeStylableLanguageService( + const stylableLSP = new VSCodeStylableLanguageService( connection, new TestDocuments({ [textDocument.uri]: textDocument, @@ -303,7 +303,7 @@ describe('Service component test', () => { const memFs = createMemoryFs({ [baseFileName]: baseFilecContent, [importFileName]: importFileContent }); const { requireModule } = createCjsModuleSystem({ fs: memFs }); - const stylableLSP = new VscodeStylableLanguageService( + const stylableLSP = new VSCodeStylableLanguageService( connection, new TestDocuments({ [baseTextDocument.uri]: baseTextDocument, @@ -366,7 +366,7 @@ describe('Service component test', () => { const memFs = createMemoryFs({ [filePath]: fileText }); const { requireModule } = createCjsModuleSystem({ fs: memFs }); - const stylableLSP = new VscodeStylableLanguageService( + const stylableLSP = new VSCodeStylableLanguageService( connection, new TestDocuments({ [textDocument.uri]: textDocument, From 523625a0a103210d87f0c957a9d68d769ff2793d Mon Sep 17 00:00:00 2001 From: Barak Igal Date: Thu, 7 Jul 2022 14:21:34 +0300 Subject: [PATCH 2/5] refactor: use same listener for cleanup docs --- src/lib/server.ts | 7 +++- src/lib/vscode-service.ts | 80 +++++++++++++++++++++++---------------- test/unit/service.spec.ts | 6 +-- 3 files changed, 55 insertions(+), 38 deletions(-) diff --git a/src/lib/server.ts b/src/lib/server.ts index 583562d9..d4a58108 100644 --- a/src/lib/server.ts +++ b/src/lib/server.ts @@ -41,8 +41,11 @@ connection.onInitialize((params) => { }); docs.listen(connection); - docs.onDidChangeContent(lsp.diagnoseWithVsCodeConfig.bind(lsp)); - docs.onDidClose(lsp.onDidClose.bind(lsp)); + docs.onDidChangeContent((event) => { + lsp.cleanStylableCacheForDocument(event.document); + lsp.emitDiagnosticsForOpenDocuments(); + }); + docs.onDidClose((event) => lsp.onDidClose(event)); connection.onCompletion(lsp.onCompletion.bind(lsp)); connection.onDefinition(lsp.onDefinition.bind(lsp)); diff --git a/src/lib/vscode-service.ts b/src/lib/vscode-service.ts index d5e7847f..7f64f023 100644 --- a/src/lib/vscode-service.ts +++ b/src/lib/vscode-service.ts @@ -43,7 +43,12 @@ export class VSCodeStylableLanguageService { private connection: Connection; private clientConfig: ExtensionConfiguration = { diagnostics: { ignore: [] }, formatting: {} }; - constructor(connection: Connection, docs: TextDocuments, fs: IFileSystem, stylable: Stylable) { + constructor( + connection: Connection, + docs: TextDocuments, + fs: IFileSystem, + private stylable: Stylable + ) { this.languageService = new StylableLanguageService({ fs, stylable, @@ -127,48 +132,45 @@ export class VSCodeStylableLanguageService { return []; } - public diagnoseWithVsCodeConfig(): Diagnostic[] { - const result: Diagnostic[] = []; - this.textDocuments.keys().forEach((key) => { - const doc = this.textDocuments.get(key); - if (doc) { - if (doc.languageId === 'stylable') { - const uri = URI.parse(doc.uri); - // on windows, uri.fsPath replaces separators with '\' - // this breaks posix paths in-memory when running on windows - // take raw posix path instead - const fsPath = - uri.scheme === 'file' && - !uri.authority && // not UNC - uri.path.charCodeAt(2) !== 58 && // the colon in "c:" - path.isAbsolute(uri.path) - ? uri.path - : uri.fsPath; - let diagnostics: Diagnostic[]; - if ( - this.clientConfig.diagnostics.ignore.some((p) => { - return fsPath.startsWith(p); - }) - ) { - diagnostics = []; - } else { - diagnostics = this.languageService.diagnose(fsPath); - result.push(...diagnostics); + public cleanStylableCacheForDocument(document: TextDocument) { + if (document.languageId === 'stylable') { + const fsPath = fsPathCompatibility(URI.parse(document.uri)); + this.stylable.initCache({ + filter(_, entity) { + if ('resolvedPath' in entity) { + return entity.resolvedPath !== fsPath; } - this.connection.sendDiagnostics({ uri: doc.uri, diagnostics }); - } - } - }); + return true; + }, + }); + } + } + public emitDiagnosticsForOpenDocuments(): Diagnostic[] { + const result: Diagnostic[] = []; + for (const doc of this.textDocuments.all()) { + if (doc.languageId !== 'stylable') { + continue; + } + const fsPath = fsPathCompatibility(URI.parse(doc.uri)); + const hasIgnoredDiagnostics = this.clientConfig.diagnostics.ignore.some((p) => { + return fsPath.startsWith(p); + }); + const diagnostics: Diagnostic[] = hasIgnoredDiagnostics ? [] : this.languageService.diagnose(fsPath); + result.push(...diagnostics); + this.connection.sendDiagnostics({ uri: doc.uri, diagnostics }); + } return result; } public async onChangeConfig(): Promise { await this.loadClientConfiguration(); - this.diagnoseWithVsCodeConfig(); + this.stylable.initCache(); + this.emitDiagnosticsForOpenDocuments(); } public onDidClose(event: TextDocumentChangeEvent): void { + this.cleanStylableCacheForDocument(event.document); this.connection.sendDiagnostics({ diagnostics: [], uri: event.document.uri, @@ -194,3 +196,15 @@ export class VSCodeStylableLanguageService { return { fsPath, doc }; } } + +function fsPathCompatibility(uri: URI) { + // on windows, uri.fsPath replaces separators with '\' + // this breaks posix paths in-memory when running on windows + // take raw posix path instead + return uri.scheme === 'file' && + !uri.authority && // not UNC + uri.path.charCodeAt(2) !== 58 && // the colon in "c:" + path.isAbsolute(uri.path) + ? uri.path + : uri.fsPath; +} diff --git a/test/unit/service.spec.ts b/test/unit/service.spec.ts index ec12f7c5..6dd27b46 100644 --- a/test/unit/service.spec.ts +++ b/test/unit/service.spec.ts @@ -76,7 +76,7 @@ describe('Service component test', () => { new Stylable('/', memFs, requireModule) ); - const diagnostics = stylableLSP.diagnoseWithVsCodeConfig(); + const diagnostics = stylableLSP.emitDiagnosticsForOpenDocuments(); expect(diagnostics).to.deep.equal(expectedDiagnostics); }) ); @@ -131,7 +131,7 @@ describe('Service component test', () => { new Stylable('/', memFs, requireModule) ); - const diagnostics = stylableLSP.diagnoseWithVsCodeConfig(); + const diagnostics = stylableLSP.emitDiagnosticsForOpenDocuments(); expect(diagnostics).to.deep.equal(expectedDiagnostics); }) ); @@ -190,7 +190,7 @@ describe('Service component test', () => { new Stylable('/', memFs, requireModule) ); - const diagnostics = stylableLSP.diagnoseWithVsCodeConfig(); + const diagnostics = stylableLSP.emitDiagnosticsForOpenDocuments(); expect(diagnostics).to.deep.equal(expectedDiagnostics); }) ); From e77eb08b01a0760e42e9c68183f787c1a3ff85c1 Mon Sep 17 00:00:00 2001 From: Barak Igal Date: Thu, 7 Jul 2022 14:24:29 +0300 Subject: [PATCH 3/5] remove old code --- src/lib/server.ts | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/lib/server.ts b/src/lib/server.ts index d4a58108..513028ed 100644 --- a/src/lib/server.ts +++ b/src/lib/server.ts @@ -28,18 +28,6 @@ connection.onInitialize((params) => { }); const lsp = new VSCodeStylableLanguageService(connection, docs, wrappedFs, stylable); - docs.onDidChangeContent((event) => { - const { fsPath } = URI.parse(event.document.uri); - stylable.initCache({ - filter(_, entity) { - if ('resolvedPath' in entity) { - return entity.resolvedPath !== fsPath; - } - return true; - }, - }); - }); - docs.listen(connection); docs.onDidChangeContent((event) => { lsp.cleanStylableCacheForDocument(event.document); From 22cfabf9800ea02aa60a779000bc9ec8f25ca960 Mon Sep 17 00:00:00 2001 From: Barak Igal Date: Thu, 7 Jul 2022 14:25:11 +0300 Subject: [PATCH 4/5] remove unused import --- src/lib/server.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/lib/server.ts b/src/lib/server.ts index 513028ed..f4502fbe 100644 --- a/src/lib/server.ts +++ b/src/lib/server.ts @@ -12,7 +12,6 @@ import { TextDocument } from 'vscode-languageserver-textdocument'; import { initializeResult } from './capabilities'; import { VSCodeStylableLanguageService } from './vscode-service'; import { wrapFs } from './wrap-fs'; -import { URI } from 'vscode-uri'; const connection = createConnection(new IPCMessageReader(process), new IPCMessageWriter(process)); From a922dbd9e2096a0d42171a733f5c41b1557e9801 Mon Sep 17 00:00:00 2001 From: Barak Igal Date: Thu, 7 Jul 2022 15:16:43 +0300 Subject: [PATCH 5/5] change cache flag --- src/lib/vscode-service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/vscode-service.ts b/src/lib/vscode-service.ts index 7f64f023..52e36db0 100644 --- a/src/lib/vscode-service.ts +++ b/src/lib/vscode-service.ts @@ -140,7 +140,7 @@ export class VSCodeStylableLanguageService { if ('resolvedPath' in entity) { return entity.resolvedPath !== fsPath; } - return true; + return false; }, }); }