From eeb39de9d5f99ae05e37587e48852ce61e067933 Mon Sep 17 00:00:00 2001 From: Josh Gross Date: Thu, 8 Dec 2022 14:34:11 -0500 Subject: [PATCH] Support partial matrix context completion --- .../src/complete.expressions.test.ts | 2 +- actions-languageservice/src/complete.ts | 2 +- .../src/context-providers/default.ts | 13 +++-- .../src/context-providers/matrix.test.ts | 56 ++++++++++++++----- .../src/context-providers/matrix.ts | 17 ++++-- 5 files changed, 64 insertions(+), 26 deletions(-) diff --git a/actions-languageservice/src/complete.expressions.test.ts b/actions-languageservice/src/complete.expressions.test.ts index e8a4715..7a2d345 100644 --- a/actions-languageservice/src/complete.expressions.test.ts +++ b/actions-languageservice/src/complete.expressions.test.ts @@ -532,7 +532,7 @@ jobs: const result = await complete(...getPositionFromCursor(input), undefined, contextProviderConfig); - expect(result.map(x => x.label)).toEqual([]); + expect(result.map(x => x.label)).toEqual(["animal", "fruit"]); }); it("matrix with expression in property", async () => { diff --git a/actions-languageservice/src/complete.ts b/actions-languageservice/src/complete.ts index db85d6c..0def058 100644 --- a/actions-languageservice/src/complete.ts +++ b/actions-languageservice/src/complete.ts @@ -85,7 +85,7 @@ export async function complete( const expressionInput = (getExpressionInput(currentInput, relCharPos) || "").trim(); const allowedContext = getAllowedContext(token, parent); - const context = await getContext(allowedContext, contextProviderConfig, workflowContext); + const context = await getContext(allowedContext, contextProviderConfig, workflowContext, true); return completeExpression(expressionInput, context, []); } diff --git a/actions-languageservice/src/context-providers/default.ts b/actions-languageservice/src/context-providers/default.ts index 784c693..2c0f35f 100644 --- a/actions-languageservice/src/context-providers/default.ts +++ b/actions-languageservice/src/context-providers/default.ts @@ -15,13 +15,14 @@ export type ContextValue = data.Dictionary | data.Null; export async function getContext( names: string[], config: ContextProviderConfig | undefined, - workflowContext: WorkflowContext + workflowContext: WorkflowContext, + allowPartialContext: boolean = false ): Promise { const context = new data.Dictionary(); const filteredNames = filterContextNames(names, workflowContext); for (const contextName of filteredNames) { - let value = (await getDefaultContext(contextName, workflowContext)) || new data.Dictionary(); + let value = getDefaultContext(contextName, workflowContext, allowPartialContext) || new data.Dictionary(); if (value.kind === Kind.Null) { context.add(contextName, value); continue; @@ -35,7 +36,11 @@ export async function getContext( return context; } -async function getDefaultContext(name: string, workflowContext: WorkflowContext): Promise { +function getDefaultContext( + name: string, + workflowContext: WorkflowContext, + allowPartialContext: boolean +): ContextValue | undefined { switch (name) { case "runner": return objectToDictionary({ @@ -62,7 +67,7 @@ async function getDefaultContext(name: string, workflowContext: WorkflowContext) return getStrategyContext(workflowContext); case "matrix": - return getMatrixContext(workflowContext); + return getMatrixContext(workflowContext, allowPartialContext); } return undefined; diff --git a/actions-languageservice/src/context-providers/matrix.test.ts b/actions-languageservice/src/context-providers/matrix.test.ts index a1e473c..5f00477 100644 --- a/actions-languageservice/src/context-providers/matrix.test.ts +++ b/actions-languageservice/src/context-providers/matrix.test.ts @@ -62,7 +62,7 @@ describe("matrix context", () => { const workflowContext = {} as WorkflowContext; expect(workflowContext.job).toBeUndefined(); - const context = getMatrixContext(workflowContext); + const context = getMatrixContext(workflowContext, false); expect(context).toEqual(new data.Dictionary()); }); @@ -71,7 +71,7 @@ describe("matrix context", () => { const workflowContext = {job} as WorkflowContext; expect(workflowContext.job!.strategy).toBeUndefined(); - const context = getMatrixContext(workflowContext); + const context = getMatrixContext(workflowContext, false); expect(context).toEqual(new data.Dictionary()); }); @@ -79,7 +79,7 @@ describe("matrix context", () => { const workflowContext = contextFromStrategy(stringToToken("hello")); expect(workflowContext.job!.strategy).toBeDefined(); - const context = getMatrixContext(workflowContext); + const context = getMatrixContext(workflowContext, false); expect(context).toEqual(new data.Dictionary()); }); @@ -87,7 +87,7 @@ describe("matrix context", () => { const strategy = new MappingToken(undefined, undefined, undefined); const workflowContext = contextFromStrategy(strategy); - const context = getMatrixContext(workflowContext); + const context = getMatrixContext(workflowContext, false); expect(context).toEqual(new data.Null()); }); @@ -96,7 +96,7 @@ describe("matrix context", () => { strategy.add(stringToToken("matrix"), stringToToken("hello")); const workflowContext = contextFromStrategy(strategy); - const context = getMatrixContext(workflowContext); + const context = getMatrixContext(workflowContext, false); expect(context).toEqual(new data.Null()); }); @@ -105,7 +105,7 @@ describe("matrix context", () => { strategy.add(stringToToken("matrix"), new MappingToken(undefined, undefined, undefined)); const workflowContext = contextFromStrategy(strategy); - const context = getMatrixContext(workflowContext); + const context = getMatrixContext(workflowContext, false); expect(context).toEqual(new data.Dictionary()); }); }); @@ -116,7 +116,7 @@ describe("matrix context", () => { strategy.add(stringToToken("matrix"), expressionToToken("${{ fromJSON(needs.job1.outputs.matrix) }}")); const workflowContext = contextFromStrategy(strategy); - const context = getMatrixContext(workflowContext); + const context = getMatrixContext(workflowContext, false); expect(context).toEqual(new data.Null()); }); @@ -136,11 +136,37 @@ describe("matrix context", () => { strategy.add(stringToToken("matrix"), matrix); const workflowContext = contextFromStrategy(strategy); - const context = getMatrixContext(workflowContext); + const context = getMatrixContext(workflowContext, false); expect(context).toEqual(new data.Null()); }); + it("matrix with include expression and partial context allowed", () => { + const include = expressionToToken("${{ fromJSON(needs.job1.outputs.matrix) }}"); + + const nodeSequence = new SequenceToken(undefined, undefined, undefined); + nodeSequence.add(stringToToken("12")); + nodeSequence.add(stringToToken("14")); + + const matrix = new MappingToken(undefined, undefined, undefined); + matrix.add(stringToToken("node"), nodeSequence); + matrix.add(stringToToken("include"), include); + + const strategy = new MappingToken(undefined, undefined, undefined); + strategy.add(stringToToken("matrix"), matrix); + + const workflowContext = contextFromStrategy(strategy); + + const context = getMatrixContext(workflowContext, true); + + expect(context).toEqual( + new data.Dictionary({ + key: "node", + value: new data.Array(new data.StringData("12"), new data.StringData("14")) + }) + ); + }); + it("matrix with expression within property", () => { const version = expressionToToken("${{ github.event.client_payload.versions }}"); @@ -151,7 +177,7 @@ describe("matrix context", () => { strategy.add(stringToToken("matrix"), matrix); const workflowContext = contextFromStrategy(strategy); - const context = getMatrixContext(workflowContext); + const context = getMatrixContext(workflowContext, false); expect(context).toEqual( new data.Dictionary({ @@ -166,7 +192,7 @@ describe("matrix context", () => { it("basic matrix", () => { const workflowContext = createMatrix({os: ["ubuntu-latest", "windows-latest"]}); - const context = getMatrixContext(workflowContext); + const context = getMatrixContext(workflowContext, false); expect(context).toEqual( new data.Dictionary({ key: "os", @@ -181,7 +207,7 @@ describe("matrix context", () => { node: ["12", "14"] }); - const context = getMatrixContext(workflowContext); + const context = getMatrixContext(workflowContext, false); expect(context).toEqual( new data.Dictionary( { @@ -208,7 +234,7 @@ describe("matrix context", () => { ] }); - const context = getMatrixContext(workflowContext); + const context = getMatrixContext(workflowContext, false); expect(context).toEqual( new data.Dictionary( @@ -242,7 +268,7 @@ describe("matrix context", () => { ] }); - const context = getMatrixContext(workflowContext); + const context = getMatrixContext(workflowContext, false); expect(context).toEqual( new data.Dictionary( @@ -276,7 +302,7 @@ describe("matrix context", () => { ] }); - const context = getMatrixContext(workflowContext); + const context = getMatrixContext(workflowContext, false); expect(context).toEqual( new data.Dictionary( @@ -311,7 +337,7 @@ describe("matrix context", () => { ] }); - const context = getMatrixContext(workflowContext); + const context = getMatrixContext(workflowContext, false); expect(context).toEqual(new data.Dictionary()); }); diff --git a/actions-languageservice/src/context-providers/matrix.ts b/actions-languageservice/src/context-providers/matrix.ts index f3d8b4c..5b65b10 100644 --- a/actions-languageservice/src/context-providers/matrix.ts +++ b/actions-languageservice/src/context-providers/matrix.ts @@ -6,7 +6,7 @@ import {SequenceToken} from "@github/actions-workflow-parser/templates/tokens/se import {WorkflowContext} from "../context/workflow-context"; import {ContextValue} from "./default"; -export function getMatrixContext(workflowContext: WorkflowContext): ContextValue { +export function getMatrixContext(workflowContext: WorkflowContext, allowPartialContext: boolean): ContextValue { // https://docs.github.com/en/actions/learn-github-actions/contexts#matrix-context const strategy = workflowContext.job?.strategy; if (!strategy || !isMapping(strategy)) { @@ -19,7 +19,7 @@ export function getMatrixContext(workflowContext: WorkflowContext): ContextValue return new data.Null(); } - const properties = matrixProperties(matrix); + const properties = matrixProperties(matrix, allowPartialContext); if (!properties) { // Matrix included an expression, so there's no context we can provide return new data.Null(); @@ -94,7 +94,10 @@ export function getMatrixContext(workflowContext: WorkflowContext): ContextValue * * Keys: os, version, environment */ -function matrixProperties(matrix: MappingToken): Map | undefined> | undefined { +function matrixProperties( + matrix: MappingToken, + allowPartialContext: boolean +): Map | undefined> | undefined { const properties = new Map | undefined>(); let include: SequenceToken | undefined; @@ -108,9 +111,13 @@ function matrixProperties(matrix: MappingToken): Map | undef const key = pair.key.value; switch (key) { case "include": - // If "include" is an expression, we can't know the properties of the matrix + // If "include" is an expression, we can't know the full properties of the matrix if (isBasicExpression(pair.value) || !isSequence(pair.value)) { - return; + if (!allowPartialContext) { + return; + } else { + continue; + } } include = pair.value; break;