From f2f377c21c3d5ee92a40a3b863ab710c6d05e127 Mon Sep 17 00:00:00 2001 From: Jacob Wallraff Date: Thu, 2 Mar 2023 16:43:23 -0800 Subject: [PATCH 01/14] 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); } From 6eacdb82a526fd1731dc67522fdec441dfa04643 Mon Sep 17 00:00:00 2001 From: Jacob Wallraff Date: Thu, 2 Mar 2023 16:56:54 -0800 Subject: [PATCH 02/14] Import new methods --- languageserver/src/connection.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/languageserver/src/connection.ts b/languageserver/src/connection.ts index 45258a0..01f1036 100644 --- a/languageserver/src/connection.ts +++ b/languageserver/src/connection.ts @@ -28,6 +28,8 @@ import {fetchActionMetadata} from "./utils/action-metadata"; import {TTLCache} from "./utils/cache"; import {timeOperation} from "./utils/timer"; import {valueProviders} from "./value-providers"; +import {clearParsedCacheEntry} from "@github/actions-workflow-parser/workflows/workflow-parser"; +import {clearWorkflowTemplateCacheEntry} from "@github/actions-workflow-parser/model/convert"; export function initConnection(connection: Connection) { const documents: TextDocuments = new TextDocuments(TextDocument); From 44e3cc2d5fbfe4e20502107410228e23abf09b5d Mon Sep 17 00:00:00 2001 From: Jacob Wallraff Date: Thu, 2 Mar 2023 18:16:32 -0800 Subject: [PATCH 03/14] Cleanup --- languageserver/src/connection.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/languageserver/src/connection.ts b/languageserver/src/connection.ts index 01f1036..c527fe5 100644 --- a/languageserver/src/connection.ts +++ b/languageserver/src/connection.ts @@ -132,11 +132,6 @@ 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", From d7c19f529d711d052076ce746a47d26504d81bfa Mon Sep 17 00:00:00 2001 From: Jacob Wallraff Date: Fri, 3 Mar 2023 10:32:56 -0800 Subject: [PATCH 04/14] Clear whole cache on workspace change --- languageserver/src/connection.ts | 8 ++++---- workflow-parser/src/model/convert.ts | 12 ++++++++---- workflow-parser/src/workflows/workflow-parser.ts | 4 ++++ 3 files changed, 16 insertions(+), 8 deletions(-) diff --git a/languageserver/src/connection.ts b/languageserver/src/connection.ts index c527fe5..6b57d43 100644 --- a/languageserver/src/connection.ts +++ b/languageserver/src/connection.ts @@ -28,8 +28,8 @@ import {fetchActionMetadata} from "./utils/action-metadata"; import {TTLCache} from "./utils/cache"; import {timeOperation} from "./utils/timer"; import {valueProviders} from "./value-providers"; -import {clearParsedCacheEntry} from "@github/actions-workflow-parser/workflows/workflow-parser"; -import {clearWorkflowTemplateCacheEntry} from "@github/actions-workflow-parser/model/convert"; +import {clearParsedCache, clearParsedCacheEntry} from "@github/actions-workflow-parser/workflows/workflow-parser"; +import {clearWorkflowTemplateCache, clearWorkflowTemplateCacheEntry} from "@github/actions-workflow-parser/model/convert"; export function initConnection(connection: Connection) { const documents: TextDocuments = new TextDocuments(TextDocument); @@ -96,8 +96,8 @@ export function initConnection(connection: Connection) { 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.'); + clearParsedCache(); + clearWorkflowTemplateCache(); }); } }) diff --git a/workflow-parser/src/model/convert.ts b/workflow-parser/src/model/convert.ts index e244174..109d5d2 100644 --- a/workflow-parser/src/model/convert.ts +++ b/workflow-parser/src/model/convert.ts @@ -48,13 +48,13 @@ const defaultOptions: Required = { }; export async function convertWorkflowTemplate( - fileName: string, + uri: string, context: TemplateContext, root: TemplateToken, fileProvider?: FileProvider, options: WorkflowTemplateConverterOptions = defaultOptions ): Promise { - const cachedResult = workflowTemplateCache.get(fileName) + const cachedResult = workflowTemplateCache.get(uri) if (cachedResult) { return cachedResult; } @@ -66,7 +66,7 @@ export async function convertWorkflowTemplate( result.errors = context.errors.getErrors().map(x => ({ Message: x.message })); - workflowTemplateCache.set(fileName, result); + workflowTemplateCache.set(uri, result); return result; } @@ -138,7 +138,7 @@ export async function convertWorkflowTemplate( } } - workflowTemplateCache.set(fileName, result); + workflowTemplateCache.set(uri, result); return result; } @@ -158,4 +158,8 @@ function getOptionsWithDefaults(options: WorkflowTemplateConverterOptions): Requ export function clearWorkflowTemplateCacheEntry(uri: string) { workflowTemplateCache.delete(uri); +} + +export function clearWorkflowTemplateCache() { + workflowTemplateCache.clear(); } \ 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 aca8b72..978f12d 100644 --- a/workflow-parser/src/workflows/workflow-parser.ts +++ b/workflow-parser/src/workflows/workflow-parser.ts @@ -54,3 +54,7 @@ export function parseWorkflow(entryFile: File, contextOrTrace: TraceWriter | Tem export function clearParsedCacheEntry(path: string) { parsedWorkflowCache.delete(path); } + +export function clearParsedCache() { + parsedWorkflowCache.clear(); +} From ffcd06c8c5f0e8762fa9c2c2370c38a1daac907e Mon Sep 17 00:00:00 2001 From: Jacob Wallraff Date: Fri, 3 Mar 2023 12:28:29 -0800 Subject: [PATCH 05/14] Move caching to language service --- languageservice/src/complete.ts | 17 +++-- languageservice/src/document-links.ts | 9 ++- languageservice/src/hover.ts | 19 +++--- languageservice/src/utils/workflow-cache.ts | 63 +++++++++++++++++++ languageservice/src/validate.ts | 20 +++--- workflow-parser/src/model/convert.ts | 16 ----- .../src/workflows/workflow-parser.ts | 17 ----- 7 files changed, 95 insertions(+), 66 deletions(-) create mode 100644 languageservice/src/utils/workflow-cache.ts diff --git a/languageservice/src/complete.ts b/languageservice/src/complete.ts index 23226b7..0a8301f 100644 --- a/languageservice/src/complete.ts +++ b/languageservice/src/complete.ts @@ -1,11 +1,9 @@ import {complete as completeExpression, DescriptionDictionary} from "@github/actions-expressions"; import {CompletionItem as ExpressionCompletionItem} from "@github/actions-expressions/completion"; import { - convertWorkflowTemplate, isBasicExpression, isSequence, - isString, - parseWorkflow + isString } from "@github/actions-workflow-parser"; import {ErrorPolicy} from "@github/actions-workflow-parser/model/convert"; import {OPEN_EXPRESSION} from "@github/actions-workflow-parser/templates/template-constants"; @@ -21,7 +19,6 @@ import {getContext, Mode} from "./context-providers/default"; import {getWorkflowContext, WorkflowContext} from "./context/workflow-context"; import {validatorFunctions} from "./expression-validation/functions"; import {error} from "./log"; -import {nullTrace} from "./nulltrace"; import {isPotentiallyExpression} from "./utils/expression-detection"; import {findToken} from "./utils/find-token"; import {guessIndentation} from "./utils/indentation-guesser"; @@ -31,6 +28,7 @@ import {transform} from "./utils/transform"; import {Value, ValueProviderConfig} from "./value-providers/config"; import {defaultValueProviders} from "./value-providers/default"; import {definitionValues} from "./value-providers/definition"; +import {getParsedWorkflow, getWorkflowTemplate} from "./utils/workflow-cache"; export function getExpressionInput(input: string, pos: number): string { // Find start marker around the cursor position @@ -66,21 +64,22 @@ export async function complete( // Fix the input to work around YAML parsing issues const [newDoc, newPos] = transform(textDocument, position); - const file: File = { name: textDocument.uri, content: newDoc.getText() }; - const result = parseWorkflow(file, nullTrace); - if (!result.value) { + + const parsedWorkflow = getParsedWorkflow(file, textDocument.uri); + if (!parsedWorkflow) { return []; } - const {token, keyToken, parent, path} = findToken(newPos, result.value); - const template = await convertWorkflowTemplate(file.name, result.context, result.value, config?.fileProvider, { + const template = await getWorkflowTemplate(file, parsedWorkflow, textDocument.uri, config, { fetchReusableWorkflowDepth: config?.fileProvider ? 1 : 0, errorPolicy: ErrorPolicy.TryConversion }); + + const {token, keyToken, parent, path} = findToken(newPos, parsedWorkflow.value); const workflowContext = getWorkflowContext(textDocument.uri, template, path); // If we are inside an expression, take a different code-path. The workflow parser does not correctly create diff --git a/languageservice/src/document-links.ts b/languageservice/src/document-links.ts index 44ab65b..8cbe2a2 100644 --- a/languageservice/src/document-links.ts +++ b/languageservice/src/document-links.ts @@ -1,12 +1,11 @@ -import {convertWorkflowTemplate, parseWorkflow} from "@github/actions-workflow-parser"; import {ErrorPolicy} from "@github/actions-workflow-parser/model/convert"; import {isJob} from "@github/actions-workflow-parser/model/type-guards"; import {File} from "@github/actions-workflow-parser/workflows/file"; import {TextDocument} from "vscode-languageserver-textdocument"; import {DocumentLink} from "vscode-languageserver-types"; import {parseActionReference} from "./action"; -import {nullTrace} from "./nulltrace"; import {mapRange} from "./utils/range"; +import {getParsedWorkflow, getWorkflowTemplate} from "./utils/workflow-cache"; export async function documentLinks(document: TextDocument): Promise { const file: File = { @@ -14,12 +13,12 @@ export async function documentLinks(document: TextDocument): Promise(); +const workflowTemplateCache = new Map(); + +export function cacheParsedWorkflow(uri: string, parsedWorkflowResult: ParseWorkflowResult) { + parsedWorkflowCache.set(uri, parsedWorkflowResult); +} + +export function getParsedWorkflowCacheEntry(uri: string): ParseWorkflowResult | undefined { + return parsedWorkflowCache.get(uri); +} + +export function clearParsedCacheEntry(uri: string) { + parsedWorkflowCache.delete(uri); +} + +export function clearParsedCache() { + parsedWorkflowCache.clear(); +} + +export function cacheWorkflowTemplate(uri: string, workflowTemplate: WorkflowTemplate) { + workflowTemplateCache.set(uri, workflowTemplate); +} + +export function getWorkflowTemplateCacheEntry(uri: string): WorkflowTemplate | undefined { + return workflowTemplateCache.get(uri); +} + +export function clearWorkflowTemplateCacheEntry(uri: string) { + workflowTemplateCache.delete(uri); +} + +export function clearWorkflowTemplateCache() { + workflowTemplateCache.clear(); +} + +export function getParsedWorkflow(file: File, uri: string): ParseWorkflowResult | undefined { + let result = getParsedWorkflowCacheEntry(uri); + if (!result || !result.value) { + result = parseWorkflow(file, nullTrace); + if (!result.value) { + return undefined; + } + cacheParsedWorkflow(uri, result) + } + return result; +} + +export async function getWorkflowTemplate(file: File, parsedWorkflow: ParseWorkflowResult, uri: string, config?: CompletionConfig, options?: WorkflowTemplateConverterOptions): Promise { + let template = getWorkflowTemplateCacheEntry(uri); + if (!template) { + template = await convertWorkflowTemplate(file.name, parsedWorkflow.context, parsedWorkflow.value!, config?.fileProvider, options); + cacheWorkflowTemplate(uri, template); + } + return template; +} \ No newline at end of file diff --git a/languageservice/src/validate.ts b/languageservice/src/validate.ts index 9b4c55c..e785a4d 100644 --- a/languageservice/src/validate.ts +++ b/languageservice/src/validate.ts @@ -1,11 +1,8 @@ import {Evaluator, ExpressionEvaluationError, Lexer, Parser} from "@github/actions-expressions"; import {Expr} from "@github/actions-expressions/ast"; import { - convertWorkflowTemplate, isBasicExpression, isString, - parseWorkflow, - ParseWorkflowResult, WorkflowTemplate } from "@github/actions-workflow-parser"; import {ErrorPolicy} from "@github/actions-workflow-parser/model/convert"; @@ -18,19 +15,18 @@ import {FileProvider} from "@github/actions-workflow-parser/workflows/file-provi import {TextDocument} from "vscode-languageserver-textdocument"; import {Diagnostic, DiagnosticSeverity, URI} from "vscode-languageserver-types"; import {ActionMetadata, ActionReference} from "./action"; - import {ContextProviderConfig} from "./context-providers/config"; import {getContext, Mode} from "./context-providers/default"; import {getWorkflowContext, WorkflowContext} from "./context/workflow-context"; import {AccessError, wrapDictionary} from "./expression-validation/error-dictionary"; import {validatorFunctions} from "./expression-validation/functions"; import {error} from "./log"; -import {nullTrace} from "./nulltrace"; import {findToken} from "./utils/find-token"; import {mapRange} from "./utils/range"; import {validateAction} from "./validate-action"; import {ValueProviderConfig, ValueProviderKind} from "./value-providers/config"; import {defaultValueProviders} from "./value-providers/default"; +import { getParsedWorkflow, getWorkflowTemplate } from "./utils/workflow-cache"; export type ValidationConfig = { valueProviderConfig?: ValueProviderConfig; @@ -54,20 +50,24 @@ export async function validate(textDocument: TextDocument, config?: ValidationCo const diagnostics: Diagnostic[] = []; try { - const result: ParseWorkflowResult = parseWorkflow(file, nullTrace); - if (result.value) { + const parsedWorkflow = getParsedWorkflow(file, textDocument.uri); + if (!parsedWorkflow) { + return []; + } + + if (parsedWorkflow.value) { // Errors will be updated in the context. Attempt to do the conversion anyway in order to give the user more information - const template = await convertWorkflowTemplate(file.name, result.context, result.value, config?.fileProvider, { + const template = await getWorkflowTemplate(file, parsedWorkflow, textDocument.uri, config, { fetchReusableWorkflowDepth: config?.fileProvider ? 1 : 0, errorPolicy: ErrorPolicy.TryConversion }); // Validate expressions and value providers - await additionalValidations(diagnostics, textDocument.uri, template, result.value, config); + await additionalValidations(diagnostics, textDocument.uri, template, parsedWorkflow.value, config); } // For now map parser errors directly to diagnostics - for (const error of result.context.errors.getErrors()) { + for (const error of parsedWorkflow.context.errors.getErrors()) { let range = mapRange(error.range); diagnostics.push({ diff --git a/workflow-parser/src/model/convert.ts b/workflow-parser/src/model/convert.ts index 109d5d2..4e11dfc 100644 --- a/workflow-parser/src/model/convert.ts +++ b/workflow-parser/src/model/convert.ts @@ -48,17 +48,11 @@ const defaultOptions: Required = { }; export async function convertWorkflowTemplate( - uri: string, context: TemplateContext, root: TemplateToken, fileProvider?: FileProvider, options: WorkflowTemplateConverterOptions = defaultOptions ): Promise { - const cachedResult = workflowTemplateCache.get(uri) - if (cachedResult) { - return cachedResult; - } - const result = {} as WorkflowTemplate; const opts = getOptionsWithDefaults(options); @@ -66,7 +60,6 @@ export async function convertWorkflowTemplate( result.errors = context.errors.getErrors().map(x => ({ Message: x.message })); - workflowTemplateCache.set(uri, result); return result; } @@ -138,7 +131,6 @@ export async function convertWorkflowTemplate( } } - workflowTemplateCache.set(uri, result); return result; } @@ -155,11 +147,3 @@ function getOptionsWithDefaults(options: WorkflowTemplateConverterOptions): Requ errorPolicy: options.errorPolicy !== undefined ? options.errorPolicy : defaultOptions.errorPolicy }; } - -export function clearWorkflowTemplateCacheEntry(uri: string) { - workflowTemplateCache.delete(uri); -} - -export function clearWorkflowTemplateCache() { - workflowTemplateCache.clear(); -} \ 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 978f12d..b70d0f7 100644 --- a/workflow-parser/src/workflows/workflow-parser.ts +++ b/workflow-parser/src/workflows/workflow-parser.ts @@ -7,8 +7,6 @@ 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; @@ -17,11 +15,6 @@ 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 @@ -38,7 +31,6 @@ export function parseWorkflow(entryFile: File, contextOrTrace: TraceWriter | Tem context, value: undefined }; - parsedWorkflowCache.set(entryFile.name, result); return result } const templateToken = templateReader.readTemplate(context, WORKFLOW_ROOT, reader, fileId); @@ -47,14 +39,5 @@ export function parseWorkflow(entryFile: File, contextOrTrace: TraceWriter | Tem context, value: templateToken } satisfies ParseWorkflowResult; - parsedWorkflowCache.set(entryFile.name, result); return result; } - -export function clearParsedCacheEntry(path: string) { - parsedWorkflowCache.delete(path); -} - -export function clearParsedCache() { - parsedWorkflowCache.clear(); -} From 5a199d4c5266ddc9560cc25c2af58f15a1846d27 Mon Sep 17 00:00:00 2001 From: Jacob Wallraff Date: Fri, 3 Mar 2023 12:39:40 -0800 Subject: [PATCH 06/14] Rename methods --- languageservice/src/complete.ts | 6 ++--- languageservice/src/document-links.ts | 6 ++--- languageservice/src/hover.ts | 6 ++--- languageservice/src/utils/workflow-cache.ts | 28 +++++---------------- languageservice/src/validate.ts | 6 ++--- 5 files changed, 18 insertions(+), 34 deletions(-) diff --git a/languageservice/src/complete.ts b/languageservice/src/complete.ts index 0a8301f..bfc4e16 100644 --- a/languageservice/src/complete.ts +++ b/languageservice/src/complete.ts @@ -28,7 +28,7 @@ import {transform} from "./utils/transform"; import {Value, ValueProviderConfig} from "./value-providers/config"; import {defaultValueProviders} from "./value-providers/default"; import {definitionValues} from "./value-providers/definition"; -import {getParsedWorkflow, getWorkflowTemplate} from "./utils/workflow-cache"; +import {fetchOrParseWorkflow, fetchOrConvertWorkflowTemplate} from "./utils/workflow-cache"; export function getExpressionInput(input: string, pos: number): string { // Find start marker around the cursor position @@ -69,12 +69,12 @@ export async function complete( content: newDoc.getText() }; - const parsedWorkflow = getParsedWorkflow(file, textDocument.uri); + const parsedWorkflow = fetchOrParseWorkflow(file, textDocument.uri); if (!parsedWorkflow) { return []; } - const template = await getWorkflowTemplate(file, parsedWorkflow, textDocument.uri, config, { + const template = await fetchOrConvertWorkflowTemplate(file, parsedWorkflow, textDocument.uri, config, { fetchReusableWorkflowDepth: config?.fileProvider ? 1 : 0, errorPolicy: ErrorPolicy.TryConversion }); diff --git a/languageservice/src/document-links.ts b/languageservice/src/document-links.ts index 8cbe2a2..bd14ce1 100644 --- a/languageservice/src/document-links.ts +++ b/languageservice/src/document-links.ts @@ -5,7 +5,7 @@ import {TextDocument} from "vscode-languageserver-textdocument"; import {DocumentLink} from "vscode-languageserver-types"; import {parseActionReference} from "./action"; import {mapRange} from "./utils/range"; -import {getParsedWorkflow, getWorkflowTemplate} from "./utils/workflow-cache"; +import {fetchOrParseWorkflow, fetchOrConvertWorkflowTemplate} from "./utils/workflow-cache"; export async function documentLinks(document: TextDocument): Promise { const file: File = { @@ -13,12 +13,12 @@ export async function documentLinks(document: TextDocument): Promise(); const workflowTemplateCache = new Map(); -export function cacheParsedWorkflow(uri: string, parsedWorkflowResult: ParseWorkflowResult) { - parsedWorkflowCache.set(uri, parsedWorkflowResult); -} - -export function getParsedWorkflowCacheEntry(uri: string): ParseWorkflowResult | undefined { - return parsedWorkflowCache.get(uri); -} - export function clearParsedCacheEntry(uri: string) { parsedWorkflowCache.delete(uri); } @@ -25,14 +17,6 @@ export function clearParsedCache() { parsedWorkflowCache.clear(); } -export function cacheWorkflowTemplate(uri: string, workflowTemplate: WorkflowTemplate) { - workflowTemplateCache.set(uri, workflowTemplate); -} - -export function getWorkflowTemplateCacheEntry(uri: string): WorkflowTemplate | undefined { - return workflowTemplateCache.get(uri); -} - export function clearWorkflowTemplateCacheEntry(uri: string) { workflowTemplateCache.delete(uri); } @@ -41,23 +25,23 @@ export function clearWorkflowTemplateCache() { workflowTemplateCache.clear(); } -export function getParsedWorkflow(file: File, uri: string): ParseWorkflowResult | undefined { - let result = getParsedWorkflowCacheEntry(uri); +export function fetchOrParseWorkflow(file: File, uri: string): ParseWorkflowResult | undefined { + let result = parsedWorkflowCache.get(uri); if (!result || !result.value) { result = parseWorkflow(file, nullTrace); if (!result.value) { return undefined; } - cacheParsedWorkflow(uri, result) + parsedWorkflowCache.set(uri, result); } return result; } -export async function getWorkflowTemplate(file: File, parsedWorkflow: ParseWorkflowResult, uri: string, config?: CompletionConfig, options?: WorkflowTemplateConverterOptions): Promise { - let template = getWorkflowTemplateCacheEntry(uri); +export async function fetchOrConvertWorkflowTemplate(file: File, parsedWorkflow: ParseWorkflowResult, uri: string, config?: CompletionConfig, options?: WorkflowTemplateConverterOptions): Promise { + let template = workflowTemplateCache.get(uri); if (!template) { template = await convertWorkflowTemplate(file.name, parsedWorkflow.context, parsedWorkflow.value!, config?.fileProvider, options); - cacheWorkflowTemplate(uri, template); + workflowTemplateCache.set(uri, template); } return template; } \ No newline at end of file diff --git a/languageservice/src/validate.ts b/languageservice/src/validate.ts index e785a4d..10441c6 100644 --- a/languageservice/src/validate.ts +++ b/languageservice/src/validate.ts @@ -26,7 +26,7 @@ import {mapRange} from "./utils/range"; import {validateAction} from "./validate-action"; import {ValueProviderConfig, ValueProviderKind} from "./value-providers/config"; import {defaultValueProviders} from "./value-providers/default"; -import { getParsedWorkflow, getWorkflowTemplate } from "./utils/workflow-cache"; +import {fetchOrParseWorkflow, fetchOrConvertWorkflowTemplate} from "./utils/workflow-cache"; export type ValidationConfig = { valueProviderConfig?: ValueProviderConfig; @@ -50,14 +50,14 @@ export async function validate(textDocument: TextDocument, config?: ValidationCo const diagnostics: Diagnostic[] = []; try { - const parsedWorkflow = getParsedWorkflow(file, textDocument.uri); + const parsedWorkflow = fetchOrParseWorkflow(file, textDocument.uri); if (!parsedWorkflow) { return []; } if (parsedWorkflow.value) { // Errors will be updated in the context. Attempt to do the conversion anyway in order to give the user more information - const template = await getWorkflowTemplate(file, parsedWorkflow, textDocument.uri, config, { + const template = await fetchOrConvertWorkflowTemplate(file, parsedWorkflow, textDocument.uri, config, { fetchReusableWorkflowDepth: config?.fileProvider ? 1 : 0, errorPolicy: ErrorPolicy.TryConversion }); From 2fc36cc6a15262da9b329d09bd9ce18f56a38c13 Mon Sep 17 00:00:00 2001 From: Jacob Wallraff Date: Fri, 3 Mar 2023 12:49:25 -0800 Subject: [PATCH 07/14] Fix imports and format --- languageserver/src/connection.ts | 10 +++++++--- languageservice/src/complete.ts | 6 +----- languageservice/src/utils/workflow-cache.ts | 19 +++++++++++++++---- languageservice/src/validate.ts | 6 +----- .../src/workflows/workflow-parser.ts | 2 +- 5 files changed, 25 insertions(+), 18 deletions(-) diff --git a/languageserver/src/connection.ts b/languageserver/src/connection.ts index 6b57d43..3728110 100644 --- a/languageserver/src/connection.ts +++ b/languageserver/src/connection.ts @@ -28,8 +28,12 @@ import {fetchActionMetadata} from "./utils/action-metadata"; import {TTLCache} from "./utils/cache"; import {timeOperation} from "./utils/timer"; import {valueProviders} from "./value-providers"; -import {clearParsedCache, clearParsedCacheEntry} from "@github/actions-workflow-parser/workflows/workflow-parser"; -import {clearWorkflowTemplateCache, clearWorkflowTemplateCacheEntry} from "@github/actions-workflow-parser/model/convert"; +import { + clearParsedCache, + clearParsedCacheEntry, + clearWorkflowTemplateCache, + clearWorkflowTemplateCacheEntry +} from "@github/actions-languageservice/utils/workflow-cache"; export function initConnection(connection: Connection) { const documents: TextDocuments = new TextDocuments(TextDocument); @@ -100,7 +104,7 @@ export function initConnection(connection: Connection) { clearWorkflowTemplateCache(); }); } - }) + }); // The content of a text document has changed. This event is emitted // when the text document first opened or when its content has changed. diff --git a/languageservice/src/complete.ts b/languageservice/src/complete.ts index bfc4e16..3a0cad2 100644 --- a/languageservice/src/complete.ts +++ b/languageservice/src/complete.ts @@ -1,10 +1,6 @@ import {complete as completeExpression, DescriptionDictionary} from "@github/actions-expressions"; import {CompletionItem as ExpressionCompletionItem} from "@github/actions-expressions/completion"; -import { - isBasicExpression, - isSequence, - isString -} from "@github/actions-workflow-parser"; +import {isBasicExpression, isSequence, isString} from "@github/actions-workflow-parser"; import {ErrorPolicy} from "@github/actions-workflow-parser/model/convert"; import {OPEN_EXPRESSION} from "@github/actions-workflow-parser/templates/template-constants"; import {TemplateToken} from "@github/actions-workflow-parser/templates/tokens/index"; diff --git a/languageservice/src/utils/workflow-cache.ts b/languageservice/src/utils/workflow-cache.ts index 4a8681b..eb0c19e 100644 --- a/languageservice/src/utils/workflow-cache.ts +++ b/languageservice/src/utils/workflow-cache.ts @@ -4,7 +4,7 @@ import {File} from "@github/actions-workflow-parser/workflows/file"; import {ParseWorkflowResult} from "@github/actions-workflow-parser"; import {WorkflowTemplate} from "@github/actions-workflow-parser"; import {nullTrace} from "../nulltrace"; -import { CompletionConfig } from "../complete"; +import {CompletionConfig} from "../complete"; const parsedWorkflowCache = new Map(); const workflowTemplateCache = new Map(); @@ -37,11 +37,22 @@ export function fetchOrParseWorkflow(file: File, uri: string): ParseWorkflowResu return result; } -export async function fetchOrConvertWorkflowTemplate(file: File, parsedWorkflow: ParseWorkflowResult, uri: string, config?: CompletionConfig, options?: WorkflowTemplateConverterOptions): Promise { +export async function fetchOrConvertWorkflowTemplate( + file: File, + parsedWorkflow: ParseWorkflowResult, + uri: string, + config?: CompletionConfig, + options?: WorkflowTemplateConverterOptions +): Promise { let template = workflowTemplateCache.get(uri); if (!template) { - template = await convertWorkflowTemplate(file.name, parsedWorkflow.context, parsedWorkflow.value!, config?.fileProvider, options); + template = await convertWorkflowTemplate( + parsedWorkflow.context, + parsedWorkflow.value!, + config?.fileProvider, + options + ); workflowTemplateCache.set(uri, template); } return template; -} \ No newline at end of file +} diff --git a/languageservice/src/validate.ts b/languageservice/src/validate.ts index 10441c6..4ff223e 100644 --- a/languageservice/src/validate.ts +++ b/languageservice/src/validate.ts @@ -1,10 +1,6 @@ import {Evaluator, ExpressionEvaluationError, Lexer, Parser} from "@github/actions-expressions"; import {Expr} from "@github/actions-expressions/ast"; -import { - isBasicExpression, - isString, - WorkflowTemplate -} from "@github/actions-workflow-parser"; +import {isBasicExpression, isString, WorkflowTemplate} from "@github/actions-workflow-parser"; import {ErrorPolicy} from "@github/actions-workflow-parser/model/convert"; import {splitAllowedContext} from "@github/actions-workflow-parser/templates/allowed-context"; import {BasicExpressionToken} from "@github/actions-workflow-parser/templates/tokens/basic-expression-token"; diff --git a/workflow-parser/src/workflows/workflow-parser.ts b/workflow-parser/src/workflows/workflow-parser.ts index b70d0f7..b727653 100644 --- a/workflow-parser/src/workflows/workflow-parser.ts +++ b/workflow-parser/src/workflows/workflow-parser.ts @@ -31,7 +31,7 @@ export function parseWorkflow(entryFile: File, contextOrTrace: TraceWriter | Tem context, value: undefined }; - return result + return result; } const templateToken = templateReader.readTemplate(context, WORKFLOW_ROOT, reader, fileId); From bde26c84ea7b0466d58a5c8d42f2754a067efdd1 Mon Sep 17 00:00:00 2001 From: Jacob Wallraff Date: Mon, 6 Mar 2023 11:29:28 -0800 Subject: [PATCH 08/14] Reset cache before expressions tests --- languageservice/src/complete.expressions.test.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/languageservice/src/complete.expressions.test.ts b/languageservice/src/complete.expressions.test.ts index fc16165..5f24de1 100644 --- a/languageservice/src/complete.expressions.test.ts +++ b/languageservice/src/complete.expressions.test.ts @@ -6,6 +6,7 @@ import {registerLogger} from "./log"; import {getPositionFromCursor} from "./test-utils/cursor-position"; import {TestLogger} from "./test-utils/logger"; import {testFileProvider} from "./test-utils/test-file-provider"; +import {clearParsedCache, clearWorkflowTemplateCache} from "./utils/workflow-cache"; const contextProviderConfig: ContextProviderConfig = { getContext: async (context: string) => { @@ -24,6 +25,11 @@ const contextProviderConfig: ContextProviderConfig = { registerLogger(new TestLogger()); +beforeEach(() => { + clearWorkflowTemplateCache(); + clearParsedCache(); +}); + describe("expressions", () => { it("input extraction", () => { const test = (input: string) => { From 6aaeefb9c25da8f16467dab10670707549948125 Mon Sep 17 00:00:00 2001 From: Jacob Wallraff Date: Mon, 6 Mar 2023 11:33:28 -0800 Subject: [PATCH 09/14] Remove unused code and address feedback --- languageservice/src/complete.ts | 2 +- languageservice/src/document-links.ts | 2 +- languageservice/src/hover.ts | 2 +- languageservice/src/utils/workflow-cache.ts | 3 +-- languageservice/src/validate.ts | 2 +- workflow-parser/README.md | 2 +- workflow-parser/src/model/convert.ts | 2 -- 7 files changed, 6 insertions(+), 9 deletions(-) diff --git a/languageservice/src/complete.ts b/languageservice/src/complete.ts index 3a0cad2..8f355f7 100644 --- a/languageservice/src/complete.ts +++ b/languageservice/src/complete.ts @@ -70,7 +70,7 @@ export async function complete( return []; } - const template = await fetchOrConvertWorkflowTemplate(file, parsedWorkflow, textDocument.uri, config, { + const template = await fetchOrConvertWorkflowTemplate(parsedWorkflow, textDocument.uri, config, { fetchReusableWorkflowDepth: config?.fileProvider ? 1 : 0, errorPolicy: ErrorPolicy.TryConversion }); diff --git a/languageservice/src/document-links.ts b/languageservice/src/document-links.ts index bd14ce1..a78ff9f 100644 --- a/languageservice/src/document-links.ts +++ b/languageservice/src/document-links.ts @@ -18,7 +18,7 @@ export async function documentLinks(document: TextDocument): Promise(); - export enum ErrorPolicy { ReturnErrorsOnly, TryConversion From 70c122b0e421ecbbe310b968604d1b35f80f06da Mon Sep 17 00:00:00 2001 From: Jacob Wallraff Date: Mon, 6 Mar 2023 11:52:21 -0800 Subject: [PATCH 10/14] Reset cache for all tests --- languageserver/src/test-utils/workflow-context.ts | 2 +- languageservice/src/complete.test.ts | 6 ++++++ languageservice/src/document-links.test.ts | 6 ++++++ languageservice/src/hover.expressions.test.ts | 6 ++++++ languageservice/src/hover.reusable-workflow.test.ts | 6 ++++++ languageservice/src/hover.test.ts | 6 ++++++ languageservice/src/test-utils/test-workflow-context.ts | 2 +- languageservice/src/validate.actions.test.ts | 6 ++++++ languageservice/src/validate.expressions.test.ts | 7 +++++++ languageservice/src/validate.test.ts | 6 ++++++ languageservice/src/validate.workflow-references.test.ts | 6 ++++++ 11 files changed, 57 insertions(+), 2 deletions(-) diff --git a/languageserver/src/test-utils/workflow-context.ts b/languageserver/src/test-utils/workflow-context.ts index 2893bda..edde697 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("test.yaml", parsed.context, parsed.value); + const template = await convertWorkflowTemplate(parsed.context, parsed.value); const context: WorkflowContext = {uri: "test.yaml", template}; if (job) { diff --git a/languageservice/src/complete.test.ts b/languageservice/src/complete.test.ts index 84abd19..55ab096 100644 --- a/languageservice/src/complete.test.ts +++ b/languageservice/src/complete.test.ts @@ -5,9 +5,15 @@ import {registerLogger} from "./log"; import {getPositionFromCursor} from "./test-utils/cursor-position"; import {TestLogger} from "./test-utils/logger"; import {ValueProviderConfig, ValueProviderKind} from "./value-providers/config"; +import {clearParsedCache, clearWorkflowTemplateCache} from "./utils/workflow-cache"; registerLogger(new TestLogger()); +beforeEach(() => { + clearWorkflowTemplateCache(); + clearParsedCache(); +}); + describe("completion", () => { it("runs-on", async () => { const input = "on: push\njobs:\n build:\n runs-on: |"; diff --git a/languageservice/src/document-links.test.ts b/languageservice/src/document-links.test.ts index 576c0de..fe5fe34 100644 --- a/languageservice/src/document-links.test.ts +++ b/languageservice/src/document-links.test.ts @@ -1,5 +1,11 @@ import {documentLinks} from "./document-links"; import {createDocument} from "./test-utils/document"; +import {clearParsedCache, clearWorkflowTemplateCache} from "./utils/workflow-cache"; + +beforeEach(() => { + clearWorkflowTemplateCache(); + clearParsedCache(); +}); describe("documentLinks", () => { it("no links without actions", async () => { diff --git a/languageservice/src/hover.expressions.test.ts b/languageservice/src/hover.expressions.test.ts index 6b6bc21..c8f7235 100644 --- a/languageservice/src/hover.expressions.test.ts +++ b/languageservice/src/hover.expressions.test.ts @@ -5,6 +5,7 @@ import {hover} from "./hover"; import {registerLogger} from "./log"; import {getPositionFromCursor} from "./test-utils/cursor-position"; import {TestLogger} from "./test-utils/logger"; +import {clearParsedCache, clearWorkflowTemplateCache} from "./utils/workflow-cache"; const contextProviderConfig: ContextProviderConfig = { getContext: async (context: string) => { @@ -34,6 +35,11 @@ const contextProviderConfig: ContextProviderConfig = { registerLogger(new TestLogger()); +beforeEach(() => { + clearWorkflowTemplateCache(); + clearParsedCache(); +}); + describe("hover.expressions", () => { it("context access", async () => { const input = `on: push diff --git a/languageservice/src/hover.reusable-workflow.test.ts b/languageservice/src/hover.reusable-workflow.test.ts index 31f9154..fecbe25 100644 --- a/languageservice/src/hover.reusable-workflow.test.ts +++ b/languageservice/src/hover.reusable-workflow.test.ts @@ -1,6 +1,12 @@ import {hover} from "./hover"; import {testHoverConfig} from "./hover.test"; import {getPositionFromCursor} from "./test-utils/cursor-position"; +import {clearParsedCache, clearWorkflowTemplateCache} from "./utils/workflow-cache"; + +beforeEach(() => { + clearWorkflowTemplateCache(); + clearParsedCache(); +}); describe("hover.reusable-workflow", () => { it("hover on job input with description", async () => { diff --git a/languageservice/src/hover.test.ts b/languageservice/src/hover.test.ts index a7c4064..cc72769 100644 --- a/languageservice/src/hover.test.ts +++ b/languageservice/src/hover.test.ts @@ -3,6 +3,7 @@ import {StringToken} from "@github/actions-workflow-parser/templates/tokens/stri import {DescriptionProvider, hover, HoverConfig} from "./hover"; import {getPositionFromCursor} from "./test-utils/cursor-position"; import {testFileProvider} from "./test-utils/test-file-provider"; +import {clearParsedCache, clearWorkflowTemplateCache} from "./utils/workflow-cache"; export function testHoverConfig(tokenValue: string, tokenKey: string, description?: string) { return { @@ -22,6 +23,11 @@ export function testHoverConfig(tokenValue: string, tokenKey: string, descriptio } satisfies HoverConfig; } +beforeEach(() => { + clearWorkflowTemplateCache(); + clearParsedCache(); +}); + describe("hover", () => { it("on a key", async () => { const input = `o|n: push diff --git a/languageservice/src/test-utils/test-workflow-context.ts b/languageservice/src/test-utils/test-workflow-context.ts index 3aa8d09..86624d1 100644 --- a/languageservice/src/test-utils/test-workflow-context.ts +++ b/languageservice/src/test-utils/test-workflow-context.ts @@ -18,7 +18,7 @@ export async function testGetWorkflowContext(input: string): Promise { + clearWorkflowTemplateCache(); + clearParsedCache(); +}); + const validationConfig: ValidationConfig = { fetchActionMetadata: async (ref: ActionReference) => { let metadata: ActionMetadata | undefined = undefined; diff --git a/languageservice/src/validate.expressions.test.ts b/languageservice/src/validate.expressions.test.ts index 9d1bc3a..1b4b994 100644 --- a/languageservice/src/validate.expressions.test.ts +++ b/languageservice/src/validate.expressions.test.ts @@ -5,9 +5,16 @@ import {registerLogger} from "./log"; import {createDocument} from "./test-utils/document"; import {TestLogger} from "./test-utils/logger"; import {validate, ValidationConfig} from "./validate"; +import {clearParsedCache, clearWorkflowTemplateCache} from "./utils/workflow-cache"; registerLogger(new TestLogger()); + +beforeEach(() => { + clearWorkflowTemplateCache(); + clearParsedCache(); +}); + describe("expression validation", () => { it("access invalid context field", async () => { const result = await validate( diff --git a/languageservice/src/validate.test.ts b/languageservice/src/validate.test.ts index 05b8514..03e2be2 100644 --- a/languageservice/src/validate.test.ts +++ b/languageservice/src/validate.test.ts @@ -2,6 +2,12 @@ import {Diagnostic, DiagnosticSeverity} from "vscode-languageserver-types"; import {createDocument} from "./test-utils/document"; import {validate} from "./validate"; import {defaultValueProviders} from "./value-providers/default"; +import {clearParsedCache, clearWorkflowTemplateCache} from "./utils/workflow-cache"; + +beforeEach(() => { + clearWorkflowTemplateCache(); + clearParsedCache(); +}); describe("validation", () => { it("valid workflow", async () => { diff --git a/languageservice/src/validate.workflow-references.test.ts b/languageservice/src/validate.workflow-references.test.ts index 48614cb..12070b8 100644 --- a/languageservice/src/validate.workflow-references.test.ts +++ b/languageservice/src/validate.workflow-references.test.ts @@ -1,7 +1,13 @@ import {createDocument} from "./test-utils/document"; import {testFileProvider} from "./test-utils/test-file-provider"; +import {clearParsedCache, clearWorkflowTemplateCache} from "./utils/workflow-cache"; import {validate} from "./validate"; +beforeEach(() => { + clearWorkflowTemplateCache(); + clearParsedCache(); +}); + describe("workflow references validation", () => { it("invalid workflow reference", async () => { const input = ` From b3f22cb7024a3404de67400ece6912baa23a049b Mon Sep 17 00:00:00 2001 From: Jacob Wallraff Date: Mon, 6 Mar 2023 11:56:49 -0800 Subject: [PATCH 11/14] Format --- languageservice/src/validate.expressions.test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/languageservice/src/validate.expressions.test.ts b/languageservice/src/validate.expressions.test.ts index 1b4b994..135130e 100644 --- a/languageservice/src/validate.expressions.test.ts +++ b/languageservice/src/validate.expressions.test.ts @@ -9,7 +9,6 @@ import {clearParsedCache, clearWorkflowTemplateCache} from "./utils/workflow-cac registerLogger(new TestLogger()); - beforeEach(() => { clearWorkflowTemplateCache(); clearParsedCache(); From 9c3977d510ebc0fc2ff5790f1bec3e3c56f175e2 Mon Sep 17 00:00:00 2001 From: Jacob Wallraff Date: Mon, 6 Mar 2023 12:44:54 -0800 Subject: [PATCH 12/14] Fix a couple more tests --- languageservice/src/complete.reusable-workflows.test.ts | 6 ++++++ languageservice/src/test-utils/cursor-position.test.ts | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/languageservice/src/complete.reusable-workflows.test.ts b/languageservice/src/complete.reusable-workflows.test.ts index 98fb858..22a5d30 100644 --- a/languageservice/src/complete.reusable-workflows.test.ts +++ b/languageservice/src/complete.reusable-workflows.test.ts @@ -2,6 +2,7 @@ import {CompletionItem, MarkupContent} from "vscode-languageserver-types"; import {complete} from "./complete"; import {getPositionFromCursor} from "./test-utils/cursor-position"; import {testFileProvider} from "./test-utils/test-file-provider"; +import { clearParsedCache, clearWorkflowTemplateCache } from "./utils/workflow-cache"; function mapResult(result: CompletionItem[]) { return result.map(x => { @@ -9,6 +10,11 @@ function mapResult(result: CompletionItem[]) { }); } +beforeEach(() => { + clearWorkflowTemplateCache(); + clearParsedCache(); +}); + describe("completion with reusable workflows", () => { it("completes job inputs", async () => { const input = ` diff --git a/languageservice/src/test-utils/cursor-position.test.ts b/languageservice/src/test-utils/cursor-position.test.ts index 4cc3002..3a4e166 100644 --- a/languageservice/src/test-utils/cursor-position.test.ts +++ b/languageservice/src/test-utils/cursor-position.test.ts @@ -1,5 +1,11 @@ +import { clearParsedCache, clearWorkflowTemplateCache } from "../utils/workflow-cache"; import {getPositionFromCursor} from "./cursor-position"; +beforeEach(() => { + clearWorkflowTemplateCache(); + clearParsedCache(); +}); + describe("getPositionFromCursor", () => { it("returns the position of the cursor and the document without that cursor", () => { const input = "on: push\njobs:|"; From 0001dd045c9cab1323d2190e14d5c3a57493b857 Mon Sep 17 00:00:00 2001 From: Jacob Wallraff Date: Mon, 6 Mar 2023 12:49:07 -0800 Subject: [PATCH 13/14] Format --- languageservice/src/complete.reusable-workflows.test.ts | 2 +- languageservice/src/test-utils/cursor-position.test.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/languageservice/src/complete.reusable-workflows.test.ts b/languageservice/src/complete.reusable-workflows.test.ts index 22a5d30..cca0767 100644 --- a/languageservice/src/complete.reusable-workflows.test.ts +++ b/languageservice/src/complete.reusable-workflows.test.ts @@ -2,7 +2,7 @@ import {CompletionItem, MarkupContent} from "vscode-languageserver-types"; import {complete} from "./complete"; import {getPositionFromCursor} from "./test-utils/cursor-position"; import {testFileProvider} from "./test-utils/test-file-provider"; -import { clearParsedCache, clearWorkflowTemplateCache } from "./utils/workflow-cache"; +import {clearParsedCache, clearWorkflowTemplateCache} from "./utils/workflow-cache"; function mapResult(result: CompletionItem[]) { return result.map(x => { diff --git a/languageservice/src/test-utils/cursor-position.test.ts b/languageservice/src/test-utils/cursor-position.test.ts index 3a4e166..dd5e518 100644 --- a/languageservice/src/test-utils/cursor-position.test.ts +++ b/languageservice/src/test-utils/cursor-position.test.ts @@ -1,4 +1,4 @@ -import { clearParsedCache, clearWorkflowTemplateCache } from "../utils/workflow-cache"; +import {clearParsedCache, clearWorkflowTemplateCache} from "../utils/workflow-cache"; import {getPositionFromCursor} from "./cursor-position"; beforeEach(() => { From b272ab29be0758e78730adfff99a837949662b45 Mon Sep 17 00:00:00 2001 From: Jacob Wallraff Date: Mon, 6 Mar 2023 15:16:26 -0800 Subject: [PATCH 14/14] Combine methods --- languageserver/src/connection.ts | 13 +++---------- languageservice/src/complete.expressions.test.ts | 5 ++--- .../src/complete.reusable-workflows.test.ts | 5 ++--- languageservice/src/complete.test.ts | 5 ++--- languageservice/src/document-links.test.ts | 5 ++--- languageservice/src/hover.expressions.test.ts | 5 ++--- .../src/hover.reusable-workflow.test.ts | 5 ++--- languageservice/src/hover.test.ts | 5 ++--- .../src/test-utils/cursor-position.test.ts | 5 ++--- languageservice/src/utils/workflow-cache.ts | 12 +++--------- languageservice/src/validate.actions.test.ts | 5 ++--- languageservice/src/validate.expressions.test.ts | 5 ++--- languageservice/src/validate.test.ts | 5 ++--- languageservice/src/validate.ts | 14 +++++++------- .../src/validate.workflow-references.test.ts | 5 ++--- workflow-parser/src/workflows/workflow-parser.ts | 13 +++++-------- 16 files changed, 42 insertions(+), 70 deletions(-) diff --git a/languageserver/src/connection.ts b/languageserver/src/connection.ts index 3728110..84b69bf 100644 --- a/languageserver/src/connection.ts +++ b/languageserver/src/connection.ts @@ -28,12 +28,7 @@ import {fetchActionMetadata} from "./utils/action-metadata"; import {TTLCache} from "./utils/cache"; import {timeOperation} from "./utils/timer"; import {valueProviders} from "./value-providers"; -import { - clearParsedCache, - clearParsedCacheEntry, - clearWorkflowTemplateCache, - clearWorkflowTemplateCacheEntry -} from "@github/actions-languageservice/utils/workflow-cache"; +import {clearCacheEntry, clearCache} from "@github/actions-languageservice/utils/workflow-cache"; export function initConnection(connection: Connection) { const documents: TextDocuments = new TextDocuments(TextDocument); @@ -100,8 +95,7 @@ export function initConnection(connection: Connection) { connection.onInitialized(() => { if (hasWorkspaceFolderCapability) { connection.workspace.onDidChangeWorkspaceFolders(_event => { - clearParsedCache(); - clearWorkflowTemplateCache(); + clearCache(); }); } }); @@ -109,8 +103,7 @@ export function initConnection(connection: Connection) { // 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); + clearCacheEntry(change.document.uri); return timeOperation("validation", async () => await validateTextDocument(change.document)); }); diff --git a/languageservice/src/complete.expressions.test.ts b/languageservice/src/complete.expressions.test.ts index 5f24de1..a28e76a 100644 --- a/languageservice/src/complete.expressions.test.ts +++ b/languageservice/src/complete.expressions.test.ts @@ -6,7 +6,7 @@ import {registerLogger} from "./log"; import {getPositionFromCursor} from "./test-utils/cursor-position"; import {TestLogger} from "./test-utils/logger"; import {testFileProvider} from "./test-utils/test-file-provider"; -import {clearParsedCache, clearWorkflowTemplateCache} from "./utils/workflow-cache"; +import {clearCache} from "./utils/workflow-cache"; const contextProviderConfig: ContextProviderConfig = { getContext: async (context: string) => { @@ -26,8 +26,7 @@ const contextProviderConfig: ContextProviderConfig = { registerLogger(new TestLogger()); beforeEach(() => { - clearWorkflowTemplateCache(); - clearParsedCache(); + clearCache(); }); describe("expressions", () => { diff --git a/languageservice/src/complete.reusable-workflows.test.ts b/languageservice/src/complete.reusable-workflows.test.ts index cca0767..691f36d 100644 --- a/languageservice/src/complete.reusable-workflows.test.ts +++ b/languageservice/src/complete.reusable-workflows.test.ts @@ -2,7 +2,7 @@ import {CompletionItem, MarkupContent} from "vscode-languageserver-types"; import {complete} from "./complete"; import {getPositionFromCursor} from "./test-utils/cursor-position"; import {testFileProvider} from "./test-utils/test-file-provider"; -import {clearParsedCache, clearWorkflowTemplateCache} from "./utils/workflow-cache"; +import {clearCache} from "./utils/workflow-cache"; function mapResult(result: CompletionItem[]) { return result.map(x => { @@ -11,8 +11,7 @@ function mapResult(result: CompletionItem[]) { } beforeEach(() => { - clearWorkflowTemplateCache(); - clearParsedCache(); + clearCache(); }); describe("completion with reusable workflows", () => { diff --git a/languageservice/src/complete.test.ts b/languageservice/src/complete.test.ts index 55ab096..dc468cc 100644 --- a/languageservice/src/complete.test.ts +++ b/languageservice/src/complete.test.ts @@ -5,13 +5,12 @@ import {registerLogger} from "./log"; import {getPositionFromCursor} from "./test-utils/cursor-position"; import {TestLogger} from "./test-utils/logger"; import {ValueProviderConfig, ValueProviderKind} from "./value-providers/config"; -import {clearParsedCache, clearWorkflowTemplateCache} from "./utils/workflow-cache"; +import {clearCache} from "./utils/workflow-cache"; registerLogger(new TestLogger()); beforeEach(() => { - clearWorkflowTemplateCache(); - clearParsedCache(); + clearCache(); }); describe("completion", () => { diff --git a/languageservice/src/document-links.test.ts b/languageservice/src/document-links.test.ts index fe5fe34..65d249c 100644 --- a/languageservice/src/document-links.test.ts +++ b/languageservice/src/document-links.test.ts @@ -1,10 +1,9 @@ import {documentLinks} from "./document-links"; import {createDocument} from "./test-utils/document"; -import {clearParsedCache, clearWorkflowTemplateCache} from "./utils/workflow-cache"; +import {clearCache} from "./utils/workflow-cache"; beforeEach(() => { - clearWorkflowTemplateCache(); - clearParsedCache(); + clearCache(); }); describe("documentLinks", () => { diff --git a/languageservice/src/hover.expressions.test.ts b/languageservice/src/hover.expressions.test.ts index c8f7235..990ebe0 100644 --- a/languageservice/src/hover.expressions.test.ts +++ b/languageservice/src/hover.expressions.test.ts @@ -5,7 +5,7 @@ import {hover} from "./hover"; import {registerLogger} from "./log"; import {getPositionFromCursor} from "./test-utils/cursor-position"; import {TestLogger} from "./test-utils/logger"; -import {clearParsedCache, clearWorkflowTemplateCache} from "./utils/workflow-cache"; +import {clearCache} from "./utils/workflow-cache"; const contextProviderConfig: ContextProviderConfig = { getContext: async (context: string) => { @@ -36,8 +36,7 @@ const contextProviderConfig: ContextProviderConfig = { registerLogger(new TestLogger()); beforeEach(() => { - clearWorkflowTemplateCache(); - clearParsedCache(); + clearCache(); }); describe("hover.expressions", () => { diff --git a/languageservice/src/hover.reusable-workflow.test.ts b/languageservice/src/hover.reusable-workflow.test.ts index fecbe25..63443f6 100644 --- a/languageservice/src/hover.reusable-workflow.test.ts +++ b/languageservice/src/hover.reusable-workflow.test.ts @@ -1,11 +1,10 @@ import {hover} from "./hover"; import {testHoverConfig} from "./hover.test"; import {getPositionFromCursor} from "./test-utils/cursor-position"; -import {clearParsedCache, clearWorkflowTemplateCache} from "./utils/workflow-cache"; +import {clearCache} from "./utils/workflow-cache"; beforeEach(() => { - clearWorkflowTemplateCache(); - clearParsedCache(); + clearCache(); }); describe("hover.reusable-workflow", () => { diff --git a/languageservice/src/hover.test.ts b/languageservice/src/hover.test.ts index cc72769..e72c2bf 100644 --- a/languageservice/src/hover.test.ts +++ b/languageservice/src/hover.test.ts @@ -3,7 +3,7 @@ import {StringToken} from "@github/actions-workflow-parser/templates/tokens/stri import {DescriptionProvider, hover, HoverConfig} from "./hover"; import {getPositionFromCursor} from "./test-utils/cursor-position"; import {testFileProvider} from "./test-utils/test-file-provider"; -import {clearParsedCache, clearWorkflowTemplateCache} from "./utils/workflow-cache"; +import {clearCache} from "./utils/workflow-cache"; export function testHoverConfig(tokenValue: string, tokenKey: string, description?: string) { return { @@ -24,8 +24,7 @@ export function testHoverConfig(tokenValue: string, tokenKey: string, descriptio } beforeEach(() => { - clearWorkflowTemplateCache(); - clearParsedCache(); + clearCache(); }); describe("hover", () => { diff --git a/languageservice/src/test-utils/cursor-position.test.ts b/languageservice/src/test-utils/cursor-position.test.ts index dd5e518..277b1b6 100644 --- a/languageservice/src/test-utils/cursor-position.test.ts +++ b/languageservice/src/test-utils/cursor-position.test.ts @@ -1,9 +1,8 @@ -import {clearParsedCache, clearWorkflowTemplateCache} from "../utils/workflow-cache"; +import {clearCache} from "../utils/workflow-cache"; import {getPositionFromCursor} from "./cursor-position"; beforeEach(() => { - clearWorkflowTemplateCache(); - clearParsedCache(); + clearCache(); }); describe("getPositionFromCursor", () => { diff --git a/languageservice/src/utils/workflow-cache.ts b/languageservice/src/utils/workflow-cache.ts index 98473bf..d3fe14a 100644 --- a/languageservice/src/utils/workflow-cache.ts +++ b/languageservice/src/utils/workflow-cache.ts @@ -9,19 +9,13 @@ import {CompletionConfig} from "../complete"; const parsedWorkflowCache = new Map(); const workflowTemplateCache = new Map(); -export function clearParsedCacheEntry(uri: string) { +export function clearCacheEntry(uri: string) { parsedWorkflowCache.delete(uri); -} - -export function clearParsedCache() { - parsedWorkflowCache.clear(); -} - -export function clearWorkflowTemplateCacheEntry(uri: string) { workflowTemplateCache.delete(uri); } -export function clearWorkflowTemplateCache() { +export function clearCache() { + parsedWorkflowCache.clear(); workflowTemplateCache.clear(); } diff --git a/languageservice/src/validate.actions.test.ts b/languageservice/src/validate.actions.test.ts index 7b3330a..196da2c 100644 --- a/languageservice/src/validate.actions.test.ts +++ b/languageservice/src/validate.actions.test.ts @@ -5,13 +5,12 @@ import {createDocument} from "./test-utils/document"; import {TestLogger} from "./test-utils/logger"; import {validate, ValidationConfig} from "./validate"; import {ValueProviderKind} from "./value-providers/config"; -import {clearParsedCache, clearWorkflowTemplateCache} from "./utils/workflow-cache"; +import {clearCache} from "./utils/workflow-cache"; registerLogger(new TestLogger()); beforeEach(() => { - clearWorkflowTemplateCache(); - clearParsedCache(); + clearCache(); }); const validationConfig: ValidationConfig = { diff --git a/languageservice/src/validate.expressions.test.ts b/languageservice/src/validate.expressions.test.ts index 135130e..29b2f6f 100644 --- a/languageservice/src/validate.expressions.test.ts +++ b/languageservice/src/validate.expressions.test.ts @@ -5,13 +5,12 @@ import {registerLogger} from "./log"; import {createDocument} from "./test-utils/document"; import {TestLogger} from "./test-utils/logger"; import {validate, ValidationConfig} from "./validate"; -import {clearParsedCache, clearWorkflowTemplateCache} from "./utils/workflow-cache"; +import {clearCache} from "./utils/workflow-cache"; registerLogger(new TestLogger()); beforeEach(() => { - clearWorkflowTemplateCache(); - clearParsedCache(); + clearCache(); }); describe("expression validation", () => { diff --git a/languageservice/src/validate.test.ts b/languageservice/src/validate.test.ts index 03e2be2..ae54e4c 100644 --- a/languageservice/src/validate.test.ts +++ b/languageservice/src/validate.test.ts @@ -2,11 +2,10 @@ import {Diagnostic, DiagnosticSeverity} from "vscode-languageserver-types"; import {createDocument} from "./test-utils/document"; import {validate} from "./validate"; import {defaultValueProviders} from "./value-providers/default"; -import {clearParsedCache, clearWorkflowTemplateCache} from "./utils/workflow-cache"; +import {clearCache} from "./utils/workflow-cache"; beforeEach(() => { - clearWorkflowTemplateCache(); - clearParsedCache(); + clearCache(); }); describe("validation", () => { diff --git a/languageservice/src/validate.ts b/languageservice/src/validate.ts index 87f45d8..8161e42 100644 --- a/languageservice/src/validate.ts +++ b/languageservice/src/validate.ts @@ -1,6 +1,6 @@ import {Evaluator, ExpressionEvaluationError, Lexer, Parser} from "@github/actions-expressions"; import {Expr} from "@github/actions-expressions/ast"; -import {isBasicExpression, isString, WorkflowTemplate} from "@github/actions-workflow-parser"; +import {isBasicExpression, isString, ParseWorkflowResult, WorkflowTemplate} from "@github/actions-workflow-parser"; import {ErrorPolicy} from "@github/actions-workflow-parser/model/convert"; import {splitAllowedContext} from "@github/actions-workflow-parser/templates/allowed-context"; import {BasicExpressionToken} from "@github/actions-workflow-parser/templates/tokens/basic-expression-token"; @@ -46,24 +46,24 @@ export async function validate(textDocument: TextDocument, config?: ValidationCo const diagnostics: Diagnostic[] = []; try { - const parsedWorkflow = fetchOrParseWorkflow(file, textDocument.uri); - if (!parsedWorkflow) { + const result: ParseWorkflowResult | undefined = fetchOrParseWorkflow(file, textDocument.uri); + if (!result) { return []; } - if (parsedWorkflow.value) { + if (result.value) { // Errors will be updated in the context. Attempt to do the conversion anyway in order to give the user more information - const template = await fetchOrConvertWorkflowTemplate(parsedWorkflow, textDocument.uri, config, { + const template = await fetchOrConvertWorkflowTemplate(result, textDocument.uri, config, { fetchReusableWorkflowDepth: config?.fileProvider ? 1 : 0, errorPolicy: ErrorPolicy.TryConversion }); // Validate expressions and value providers - await additionalValidations(diagnostics, textDocument.uri, template, parsedWorkflow.value, config); + await additionalValidations(diagnostics, textDocument.uri, template, result.value, config); } // For now map parser errors directly to diagnostics - for (const error of parsedWorkflow.context.errors.getErrors()) { + for (const error of result.context.errors.getErrors()) { let range = mapRange(error.range); diagnostics.push({ diff --git a/languageservice/src/validate.workflow-references.test.ts b/languageservice/src/validate.workflow-references.test.ts index 12070b8..d57787a 100644 --- a/languageservice/src/validate.workflow-references.test.ts +++ b/languageservice/src/validate.workflow-references.test.ts @@ -1,11 +1,10 @@ import {createDocument} from "./test-utils/document"; import {testFileProvider} from "./test-utils/test-file-provider"; -import {clearParsedCache, clearWorkflowTemplateCache} from "./utils/workflow-cache"; +import {clearCache} from "./utils/workflow-cache"; import {validate} from "./validate"; beforeEach(() => { - clearWorkflowTemplateCache(); - clearParsedCache(); + clearCache(); }); describe("workflow references validation", () => { diff --git a/workflow-parser/src/workflows/workflow-parser.ts b/workflow-parser/src/workflows/workflow-parser.ts index b727653..1710a3e 100644 --- a/workflow-parser/src/workflows/workflow-parser.ts +++ b/workflow-parser/src/workflows/workflow-parser.ts @@ -6,7 +6,6 @@ import {File} from "./file"; import {WORKFLOW_ROOT} from "./workflow-constants"; import {getWorkflowSchema} from "./workflow-schema"; import {YamlObjectReader} from "./yaml-object-reader"; - export interface ParseWorkflowResult { context: TemplateContext; value: TemplateToken | undefined; @@ -27,17 +26,15 @@ export function parseWorkflow(entryFile: File, contextOrTrace: TraceWriter | Tem for (const err of reader.errors) { context.error(fileId, err.message, err.range); } - const result = { + return { context, value: undefined }; - return result; } - const templateToken = templateReader.readTemplate(context, WORKFLOW_ROOT, reader, fileId); + const result = templateReader.readTemplate(context, WORKFLOW_ROOT, reader, fileId); - const result = { + return { context, - value: templateToken - } satisfies ParseWorkflowResult; - return result; + value: result + }; }