diff --git a/actions-languageservice/src/complete.expressions.test.ts b/actions-languageservice/src/complete.expressions.test.ts index df93c72..e646353 100644 --- a/actions-languageservice/src/complete.expressions.test.ts +++ b/actions-languageservice/src/complete.expressions.test.ts @@ -311,6 +311,35 @@ jobs: expect(result.map(x => x.label)).toEqual(["build_id"]); }); + it("env steps context only for current step and job", async () => { + const input = ` +on: push +env: + envwf: workflow_env +jobs: + a: + runs-on: ubuntu-latest + env: + envjoba: job_a_env + b: + runs-on: ubuntu-latest + env: + envjobb: job_b_env + steps: + - name: step a + run: echo "hello" + env: + envstepa: step_a_env + - name: step b + run: echo "hello \${{ env.| + env: + envstepb: step_b_env +`; + const result = await complete(...getPositionFromCursor(input), undefined, contextProviderConfig); + + expect(result.map(x => x.label)).toEqual(["envjobb", "envstepb", "envwf"]); + }); + it("inputs", async () => { const input = ` on: diff --git a/actions-languageservice/src/context-providers/default.ts b/actions-languageservice/src/context-providers/default.ts index 6762985..f554bb8 100644 --- a/actions-languageservice/src/context-providers/default.ts +++ b/actions-languageservice/src/context-providers/default.ts @@ -2,6 +2,7 @@ import {data} from "@github/actions-expressions"; import {Kind} from "@github/actions-expressions/data/expressiondata"; import {WorkflowContext} from "../context/workflow-context"; import {ContextProviderConfig} from "./config"; +import {getEnvContext} from "./env"; import {getGithubContext} from "./github"; import {getInputsContext} from "./inputs"; import {getJobContext} from "./job"; @@ -78,6 +79,9 @@ function getDefaultContext(name: string, workflowContext: WorkflowContext, mode: case "job": return getJobContext(workflowContext); + + case "env": + return getEnvContext(workflowContext); } return undefined; diff --git a/actions-languageservice/src/context-providers/env.ts b/actions-languageservice/src/context-providers/env.ts new file mode 100644 index 0000000..41eda1f --- /dev/null +++ b/actions-languageservice/src/context-providers/env.ts @@ -0,0 +1,37 @@ +import {data} from "@github/actions-expressions"; +import {isScalar, isString} from "@github/actions-workflow-parser"; +import {WorkflowContext} from "../context/workflow-context"; +import {MappingToken} from "@github/actions-workflow-parser/templates/tokens/mapping-token"; + +export function getEnvContext(workflowContext: WorkflowContext): data.Dictionary { + const d = new data.Dictionary(); + + //step env + if (workflowContext.step?.env) { + envContext(workflowContext.step.env, d); + } + + //job env + if (workflowContext.job && workflowContext.job.env) { + envContext(workflowContext.job.env, d); + } + + //workflow env + if (workflowContext.template && workflowContext.template.env) { + const wfEnv = workflowContext.template.env.assertMapping("workflow env"); + envContext(wfEnv, d); + } + + return d; +} + +function envContext(envMap: MappingToken, d: data.Dictionary) { + for (const env of envMap) { + if (!isString(env.key)) { + continue; + } + + const value = isScalar(env.value) ? new data.StringData(env.value.toDisplayString()) : new data.Null(); + d.add(env.key.value, value); + } +} diff --git a/actions-languageservice/src/validate.expressions.test.ts b/actions-languageservice/src/validate.expressions.test.ts index f9777f7..7d7c84c 100644 --- a/actions-languageservice/src/validate.expressions.test.ts +++ b/actions-languageservice/src/validate.expressions.test.ts @@ -317,6 +317,82 @@ jobs: }); }); + describe("env context", () => { + it("references env within scope", async () => { + const input = ` +on: push +jobs: + a: + runs-on: ubuntu-latest + steps: + - name: step a + env: + step_env: job_a_env + run: echo "hello \${{ env.step_env }} +`; + const result = await validate(createDocument("wf.yaml", input)); + + expect(result).toEqual([]); + }); + + it("inherits parent env", async () => { + const input = ` +on: push +env: + envwf: workflow_env +jobs: + a: + runs-on: ubuntu-latest + env: + envjoba: job_a_env + steps: + - name: step a + run: echo "hello \${{ env.envwf }} +`; + const result = await validate(createDocument("wf.yaml", input)); + + expect(result).toEqual([]); + }); + + it("references env outside of scope", async () => { + const input = ` +on: push +env: + envwf: workflow_env +jobs: + a: + runs-on: ubuntu-latest + env: + envjoba: job_a_env + steps: + - name: step a + run: echo "hello" + env: + envstepa: step_a_env + - name: step b + run: echo "hello \${{ env.envstepa }} +`; + const result = await validate(createDocument("wf.yaml", input)); + + expect(result).toEqual([ + { + message: "Context access might be invalid: envstepa", + range: { + end: { + character: 42, + line: 15 + }, + start: { + character: 23, + line: 15 + } + }, + severity: DiagnosticSeverity.Warning + } + ]); + }); + }); + describe("strategy context", () => { it("reference within a matrix job", async () => { const input = ` diff --git a/package-lock.json b/package-lock.json index a734710..68c1c58 100644 --- a/package-lock.json +++ b/package-lock.json @@ -702,9 +702,9 @@ "link": true }, "node_modules/@github/actions-workflow-parser": { - "version": "0.0.37", - "resolved": "https://npm.pkg.github.com/download/@github/actions-workflow-parser/0.0.37/4211d8ace6c443c5af3c0d3e5da031384242142c", - "integrity": "sha512-LW8Axm1Rus6sedifxDlXWfSPcgrDmYJW7jdTO27qSbwv13vu20xd3VTdlshMMV4g6IcC0u6Zw7NaV3qT2C77GQ==", + "version": "0.0.38", + "resolved": "https://npm.pkg.github.com/download/@github/actions-workflow-parser/0.0.38/e696d29d18f20c008147bc09c0d2c9b5777ba7ba", + "integrity": "sha512-FfFUS8CJOwodvmw2b/0K7W9sEd2uwuCzmO8cyjp+Ew4uzDRtjX7Q7TkMras7/U4LrpvDhpYZ2TucF3yY+seSkA==", "license": "MIT", "dependencies": { "@github/actions-expressions": "*", @@ -13565,9 +13565,9 @@ } }, "@github/actions-workflow-parser": { - "version": "0.0.37", - "resolved": "https://npm.pkg.github.com/download/@github/actions-workflow-parser/0.0.37/4211d8ace6c443c5af3c0d3e5da031384242142c", - "integrity": "sha512-LW8Axm1Rus6sedifxDlXWfSPcgrDmYJW7jdTO27qSbwv13vu20xd3VTdlshMMV4g6IcC0u6Zw7NaV3qT2C77GQ==", + "version": "0.0.38", + "resolved": "https://npm.pkg.github.com/download/@github/actions-workflow-parser/0.0.38/e696d29d18f20c008147bc09c0d2c9b5777ba7ba", + "integrity": "sha512-FfFUS8CJOwodvmw2b/0K7W9sEd2uwuCzmO8cyjp+Ew4uzDRtjX7Q7TkMras7/U4LrpvDhpYZ2TucF3yY+seSkA==", "requires": { "@github/actions-expressions": "*", "yaml": "^2.0.0-8"