From c7ad54d9c3d1d8623acc6b5cbe8460c44140b496 Mon Sep 17 00:00:00 2001 From: Beth Brennan Date: Tue, 13 Dec 2022 15:24:54 -0500 Subject: [PATCH] Inputs and cron in github.event context --- .../src/complete.expressions.test.ts | 101 +++++++++++++++--- .../src/context-providers/default.ts | 2 +- .../src/context-providers/github.ts | 31 +++++- .../src/validate.expressions.test.ts | 82 ++++++++++++++ 4 files changed, 202 insertions(+), 14 deletions(-) diff --git a/actions-languageservice/src/complete.expressions.test.ts b/actions-languageservice/src/complete.expressions.test.ts index 6af1866..3456ec2 100644 --- a/actions-languageservice/src/complete.expressions.test.ts +++ b/actions-languageservice/src/complete.expressions.test.ts @@ -280,6 +280,10 @@ on: default: some value another-name: type: string + workflow_call: + inputs: + third-name: + type: boolean jobs: a: outputs: @@ -295,7 +299,7 @@ jobs: `; const result = await complete(...getPositionFromCursor(input), undefined, contextProviderConfig); - expect(result.map(x => x.label)).toEqual(["another-name", "name"]); + expect(result.map(x => x.label)).toEqual(["another-name", "name", "third-name"]); }); it("no inputs", async () => { @@ -320,21 +324,94 @@ jobs: expect(result).toEqual([]); }); - it("github context includes expected keys", async () => { - const input = ` -on: push + describe("github context", () => { + it("includes expected keys", async () => { + const input = ` + on: push + + jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - run: echo \${{ github.| }} + `; -jobs: - test: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - run: echo \${{ github.| }} + const result = await complete(...getPositionFromCursor(input), undefined, undefined); + + expect(result.map(x => x.label)).toContain("actor"); + }); + + it("includes event inputs", async () => { + const input = ` + on: + workflow_dispatch: + inputs: + name: + type: string + default: some value + another-name: + type: string + workflow_call: + inputs: + third-name: + type: boolean + jobs: + a: + outputs: + build_id: my-build-id + runs-on: ubuntu-latest + steps: + - run: echo hello a + b: + needs: [a] + runs-on: ubuntu-latest + steps: + - run: echo "hello \${{ github.event.inputs.| `; + const result = await complete(...getPositionFromCursor(input), undefined, undefined); - const result = await complete(...getPositionFromCursor(input), undefined, undefined); + expect(result.map(x => x.label)).toEqual(["another-name", "name", "third-name"]); + }); - expect(result.map(x => x.label)).toContain("actor"); + it("excludes event inputs and cron when no relevant events", async () => { + const input = ` + on: push + + jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - run: echo \${{ github.event.| }} + `; + + const result = await complete(...getPositionFromCursor(input), undefined, undefined); + + expect(result.map(x => x.label)).not.toContain("inputs"); + expect(result.map(x => x.label)).not.toContain("cron"); + }); + + it("includes cron schedules", async () => { + const input = ` + on: + schedule: + - cron: '0 0 * * *' + + jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - run: echo \${{ github.event.| }} + `; + + const result = await complete(...getPositionFromCursor(input), undefined, undefined); + + expect(result.map(x => x.label)).toContain("cron"); + }); + + it("excludes cron scheudle when no schedule", async () => {}); }); describe("steps context", () => { diff --git a/actions-languageservice/src/context-providers/default.ts b/actions-languageservice/src/context-providers/default.ts index 5893c49..8b53961 100644 --- a/actions-languageservice/src/context-providers/default.ts +++ b/actions-languageservice/src/context-providers/default.ts @@ -46,7 +46,7 @@ export async function getContext( function getDefaultContext(name: string, workflowContext: WorkflowContext, mode: Mode): ContextValue | undefined { switch (name) { case "github": - return getGithubContext(); + return getGithubContext(workflowContext); case "inputs": return getInputsContext(workflowContext); diff --git a/actions-languageservice/src/context-providers/github.ts b/actions-languageservice/src/context-providers/github.ts index 0422bb9..ca71be4 100644 --- a/actions-languageservice/src/context-providers/github.ts +++ b/actions-languageservice/src/context-providers/github.ts @@ -1,6 +1,9 @@ import {data} from "@github/actions-expressions"; +import {ExpressionData, Pair} from "@github/actions-expressions/data/expressiondata"; +import {WorkflowContext} from "../context/workflow-context"; +import {getInputsContext} from "./inputs"; -export function getGithubContext(): data.Dictionary { +export function getGithubContext(workflowContext: WorkflowContext): data.Dictionary { // https://docs.github.com/en/actions/learn-github-actions/contexts#github-context const keys = [ "action", @@ -41,7 +44,33 @@ export function getGithubContext(): data.Dictionary { return new data.Dictionary( ...keys.map(key => { + if (key == "event") { + return {key, value: getEventContext(workflowContext)}; + } + return {key, value: new data.Null()}; }) ); } + +function getEventContext(workflowContext: WorkflowContext): ExpressionData { + const d = new data.Dictionary(); + const events = workflowContext?.template?.events; + + if (!events) { + return d; + } + + const inputs = getInputsContext(workflowContext); + if (inputs.values().length > 0) { + d.add("inputs", inputs); + } + + const schedule = events["schedule"]; + if (schedule && schedule.length > 0) { + const default_cron = schedule[0].cron; + d.add("cron", new data.StringData(default_cron)); + } + + return d; +} diff --git a/actions-languageservice/src/validate.expressions.test.ts b/actions-languageservice/src/validate.expressions.test.ts index b914ec9..6867dcd 100644 --- a/actions-languageservice/src/validate.expressions.test.ts +++ b/actions-languageservice/src/validate.expressions.test.ts @@ -777,4 +777,86 @@ jobs: ]); }); }); + + describe("github context", () => { + it("includes only expected keys", async () => { + const input = ` +on: push + +jobs: + test: + runs-on: ubuntu-latest + steps: + - run: echo \${{ github.action }} + - run: echo \${{ github.steps }} +`; + + const result = await validate(createDocument("wf.yaml", input)); + + expect(result).toEqual([ + { + message: "Context access might be invalid: steps", + range: { + end: { + character: 37, + line: 8 + }, + start: { + character: 18, + line: 8 + } + }, + severity: DiagnosticSeverity.Warning + } + ]); + }); + it("validates event inputs", async () => { + const input = ` +on: + workflow_dispatch: + inputs: + name: + type: string + default: some value + another-name: + type: string + workflow_call: + inputs: + third-name: + type: boolean +jobs: + a: + outputs: + build_id: my-build-id + runs-on: ubuntu-latest + steps: + - run: echo hello a + b: + needs: [a] + runs-on: ubuntu-latest + steps: + - run: echo "hello \${{ github.event.inputs.name }}" + - run: echo "hello \${{ github.event.inputs.third-name }}" + - run: echo "hello \${{ github.event.inputs.random }}" +`; + const result = await validate(createDocument("wf.yaml", input)); + + expect(result).toEqual([ + { + message: "Context access might be invalid: random", + range: { + end: { + character: 56, + line: 26 + }, + start: { + character: 23, + line: 26 + } + }, + severity: DiagnosticSeverity.Warning + } + ]); + }); + }); });