From f2f377c21c3d5ee92a40a3b863ab710c6d05e127 Mon Sep 17 00:00:00 2001 From: Jacob Wallraff Date: Thu, 2 Mar 2023 16:43:23 -0800 Subject: [PATCH] Simple caching for parsed workflow and template --- languageserver/src/connection.ts | 16 ++++++++++++ .../src/test-utils/workflow-context.ts | 2 +- languageservice/src/complete.ts | 2 +- languageservice/src/document-links.ts | 2 +- languageservice/src/hover.ts | 2 +- .../src/test-utils/test-workflow-context.ts | 2 +- languageservice/src/validate.ts | 2 +- workflow-parser/README.md | 2 +- workflow-parser/src/model/convert.ts | 14 ++++++++++ .../src/workflows/workflow-parser.ts | 26 +++++++++++++++---- 10 files changed, 58 insertions(+), 12 deletions(-) diff --git a/languageserver/src/connection.ts b/languageserver/src/connection.ts index 9b08b70..45258a0 100644 --- a/languageserver/src/connection.ts +++ b/languageserver/src/connection.ts @@ -91,9 +91,20 @@ export function initConnection(connection: Connection) { return result; }); + connection.onInitialized(() => { + if (hasWorkspaceFolderCapability) { + connection.workspace.onDidChangeWorkspaceFolders(_event => { + // Invalidate caches? Not sure what to do here, but we should track + connection.console.log('Workspace folder change event received.'); + }); + } + }) + // The content of a text document has changed. This event is emitted // when the text document first opened or when its content has changed. documents.onDidChangeContent(change => { + clearParsedCacheEntry(change.document.uri); + clearWorkflowTemplateCacheEntry(change.document.uri); return timeOperation("validation", async () => await validateTextDocument(change.document)); }); @@ -119,6 +130,11 @@ export function initConnection(connection: Connection) { await connection.sendDiagnostics({uri: textDocument.uri, diagnostics: result}); } + // connection.onDidChangeWatchedFiles(async change => { + // // Monitored files have change in VSCode + // connection.console.log('We received an file change event'); + // }); + connection.onCompletion(async ({position, textDocument}: TextDocumentPositionParams): Promise => { return timeOperation( "completion", diff --git a/languageserver/src/test-utils/workflow-context.ts b/languageserver/src/test-utils/workflow-context.ts index edde697..2893bda 100644 --- a/languageserver/src/test-utils/workflow-context.ts +++ b/languageserver/src/test-utils/workflow-context.ts @@ -17,7 +17,7 @@ export async function createWorkflowContext( if (!parsed.value) { throw new Error("Failed to parse workflow"); } - const template = await convertWorkflowTemplate(parsed.context, parsed.value); + const template = await convertWorkflowTemplate("test.yaml", parsed.context, parsed.value); const context: WorkflowContext = {uri: "test.yaml", template}; if (job) { diff --git a/languageservice/src/complete.ts b/languageservice/src/complete.ts index 8050570..23226b7 100644 --- a/languageservice/src/complete.ts +++ b/languageservice/src/complete.ts @@ -77,7 +77,7 @@ export async function complete( } const {token, keyToken, parent, path} = findToken(newPos, result.value); - const template = await convertWorkflowTemplate(result.context, result.value, config?.fileProvider, { + const template = await convertWorkflowTemplate(file.name, result.context, result.value, config?.fileProvider, { fetchReusableWorkflowDepth: config?.fileProvider ? 1 : 0, errorPolicy: ErrorPolicy.TryConversion }); diff --git a/languageservice/src/document-links.ts b/languageservice/src/document-links.ts index 42ec015..44ab65b 100644 --- a/languageservice/src/document-links.ts +++ b/languageservice/src/document-links.ts @@ -19,7 +19,7 @@ export async function documentLinks(document: TextDocument): Promise(); + export enum ErrorPolicy { ReturnErrorsOnly, TryConversion @@ -46,11 +48,17 @@ const defaultOptions: Required = { }; export async function convertWorkflowTemplate( + fileName: string, context: TemplateContext, root: TemplateToken, fileProvider?: FileProvider, options: WorkflowTemplateConverterOptions = defaultOptions ): Promise { + const cachedResult = workflowTemplateCache.get(fileName) + if (cachedResult) { + return cachedResult; + } + const result = {} as WorkflowTemplate; const opts = getOptionsWithDefaults(options); @@ -58,6 +66,7 @@ export async function convertWorkflowTemplate( result.errors = context.errors.getErrors().map(x => ({ Message: x.message })); + workflowTemplateCache.set(fileName, result); return result; } @@ -129,6 +138,7 @@ export async function convertWorkflowTemplate( } } + workflowTemplateCache.set(fileName, result); return result; } @@ -145,3 +155,7 @@ function getOptionsWithDefaults(options: WorkflowTemplateConverterOptions): Requ errorPolicy: options.errorPolicy !== undefined ? options.errorPolicy : defaultOptions.errorPolicy }; } + +export function clearWorkflowTemplateCacheEntry(uri: string) { + workflowTemplateCache.delete(uri); +} \ No newline at end of file diff --git a/workflow-parser/src/workflows/workflow-parser.ts b/workflow-parser/src/workflows/workflow-parser.ts index 1710a3e..aca8b72 100644 --- a/workflow-parser/src/workflows/workflow-parser.ts +++ b/workflow-parser/src/workflows/workflow-parser.ts @@ -6,6 +6,9 @@ import {File} from "./file"; import {WORKFLOW_ROOT} from "./workflow-constants"; import {getWorkflowSchema} from "./workflow-schema"; import {YamlObjectReader} from "./yaml-object-reader"; + +const parsedWorkflowCache = new Map(); + export interface ParseWorkflowResult { context: TemplateContext; value: TemplateToken | undefined; @@ -14,6 +17,11 @@ export interface ParseWorkflowResult { export function parseWorkflow(entryFile: File, trace: TraceWriter): ParseWorkflowResult; export function parseWorkflow(entryFile: File, context: TemplateContext): ParseWorkflowResult; export function parseWorkflow(entryFile: File, contextOrTrace: TraceWriter | TemplateContext): ParseWorkflowResult { + const cachedResult = parsedWorkflowCache.get(entryFile.name) + if (cachedResult) { + return cachedResult; + } + const context = contextOrTrace instanceof TemplateContext ? contextOrTrace @@ -26,15 +34,23 @@ export function parseWorkflow(entryFile: File, contextOrTrace: TraceWriter | Tem for (const err of reader.errors) { context.error(fileId, err.message, err.range); } - return { + const result = { context, value: undefined }; + parsedWorkflowCache.set(entryFile.name, result); + return result } - const result = templateReader.readTemplate(context, WORKFLOW_ROOT, reader, fileId); + const templateToken = templateReader.readTemplate(context, WORKFLOW_ROOT, reader, fileId); - return { + const result = { context, - value: result - }; + value: templateToken + } satisfies ParseWorkflowResult; + parsedWorkflowCache.set(entryFile.name, result); + return result; +} + +export function clearParsedCacheEntry(path: string) { + parsedWorkflowCache.delete(path); }