Merge branch 'main' into joshmgross/for-of

This commit is contained in:
Josh Gross
2022-12-09 18:00:11 -05:00
committed by GitHub
15 changed files with 705 additions and 70 deletions
@@ -0,0 +1,35 @@
name: Update @github dependencies
on:
workflow_dispatch:
jobs:
update:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 16
registry-url: 'https://npm.pkg.github.com'
scope: '@github'
- run: npm ci
env:
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- run: |
npm update @github/actions-workflow-parser
env:
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- run: |
git config user.name github-actions
git config user.email github-actions@github.com
- run: |
git add .
git commit -m "Update @github/actions-workflow-parser"
git push
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@github/actions-languageserver",
"version": "0.1.41",
"version": "0.1.47",
"description": "Language server for GitHub Actions",
"license": "MIT",
"type": "module",
@@ -38,7 +38,7 @@
"prettier-fix": "prettier --write ."
},
"dependencies": {
"@github/actions-languageservice": "^0.1.41",
"@github/actions-languageservice": "^0.1.47",
"@octokit/rest": "^19.0.5",
"vscode-languageserver": "^8.0.2",
"vscode-languageserver-textdocument": "^1.0.7"
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@github/actions-languageservice",
"version": "0.1.41",
"version": "0.1.47",
"description": "Language service for GitHub Actions",
"license": "MIT",
"type": "module",
@@ -88,6 +88,64 @@ describe("expressions", () => {
]);
});
describe("multi-line strings", () => {
it("indented |", async () => {
const input = `on: push
jobs:
build:
steps:
- run: |
first line
test \${{ github.| }}
test2`;
const result = await complete(...getPositionFromCursor(input, 1), undefined, contextProviderConfig);
expect(result.map(x => x.label)).toEqual(["event"]);
});
it("indented |+", async () => {
const input = `on: push
jobs:
build:
steps:
- run: |+
first line
test \${{ github.| }}
test2`;
const result = await complete(...getPositionFromCursor(input, 1), undefined, contextProviderConfig);
expect(result.map(x => x.label)).toEqual(["event"]);
});
it("indented >", async () => {
const input = `on: push
jobs:
build:
steps:
- run: >
first line
test \${{ github.| }}
test2`;
const result = await complete(...getPositionFromCursor(input), undefined, contextProviderConfig);
expect(result.map(x => x.label)).toEqual(["event"]);
});
it("indented >+", async () => {
const input = `on: push
jobs:
build:
steps:
- run: >+
first line
test \${{ github.| }}
test2`;
const result = await complete(...getPositionFromCursor(input), undefined, contextProviderConfig);
expect(result.map(x => x.label)).toEqual(["event"]);
});
});
it("nested auto-complete", async () => {
const input = "run-name: ${{ github.| }}";
const result = await complete(...getPositionFromCursor(input), undefined, contextProviderConfig);
@@ -532,7 +590,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 () => {
+14 -6
View File
@@ -9,9 +9,9 @@ import {MappingToken} from "@github/actions-workflow-parser/templates/tokens/map
import {TokenType} from "@github/actions-workflow-parser/templates/tokens/types";
import {File} from "@github/actions-workflow-parser/workflows/file";
import {Position, TextDocument} from "vscode-languageserver-textdocument";
import {CompletionItem, TextEdit, Range} from "vscode-languageserver-types";
import {CompletionItem, Range, TextEdit} from "vscode-languageserver-types";
import {ContextProviderConfig} from "./context-providers/config";
import {getContext} from "./context-providers/default";
import {getContext, Mode} from "./context-providers/default";
import {getWorkflowContext, WorkflowContext} from "./context/workflow-context";
import {nullTrace} from "./nulltrace";
import {getAllowedContext} from "./utils/allowed-context";
@@ -77,15 +77,23 @@ export async function complete(
token.definition?.definitionType === DefinitionType.String && (token.definition as StringDefinition).isExpression;
const containsExpression = isString(token) && token.value.indexOf(OPEN_EXPRESSION) >= 0;
if (isString(token) && (isExpression || containsExpression)) {
const currentInput = token.value;
const currentInput = token.source || token.value;
// Transform the overall position into a node relative position
const relCharPos = newPos.character - token.range!.start[1];
let relCharPos: number = 0;
const lineDiff = newPos.line - token.range!.start[0];
if (token.range!.start[0] !== token.range!.end[0]) {
const lines = currentInput.split("\n");
const linesBeforeCusor = lines.slice(0, lineDiff);
relCharPos = linesBeforeCusor.join("\n").length + newPos.character;
} else {
relCharPos = newPos.character - token.range!.start[1];
}
const expressionInput = (getExpressionInput(currentInput, relCharPos) || "").trim();
const allowedContext = getAllowedContext(token, parent!);
const context = await getContext(allowedContext, contextProviderConfig, workflowContext);
const allowedContext = getAllowedContext(token, parent);
const context = await getContext(allowedContext, contextProviderConfig, workflowContext, Mode.Completion);
return completeExpression(expressionInput, context, []);
}
@@ -1,4 +1,5 @@
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 {getInputsContext} from "./inputs";
@@ -7,16 +8,31 @@ import {getNeedsContext} from "./needs";
import {getStepsContext} from "./steps";
import {getStrategyContext} from "./strategy";
// ContextValue is the type of the value returned by a context provider
// Null indicates that the context provider doesn't have any value to provide
export type ContextValue = data.Dictionary | data.Null;
export enum Mode {
Completion,
Validation,
Hover
}
export async function getContext(
names: string[],
config: ContextProviderConfig | undefined,
workflowContext: WorkflowContext
workflowContext: WorkflowContext,
mode: Mode
): Promise<data.Dictionary> {
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, mode) || new data.Dictionary();
if (value.kind === Kind.Null) {
context.add(contextName, value);
continue;
}
value = (await config?.getContext(contextName, value)) || value;
@@ -26,7 +42,7 @@ export async function getContext(
return context;
}
async function getDefaultContext(name: string, workflowContext: WorkflowContext): Promise<data.Dictionary | undefined> {
function getDefaultContext(name: string, workflowContext: WorkflowContext, mode: Mode): ContextValue | undefined {
switch (name) {
case "runner":
return objectToDictionary({
@@ -53,7 +69,7 @@ async function getDefaultContext(name: string, workflowContext: WorkflowContext)
return getStrategyContext(workflowContext);
case "matrix":
return getMatrixContext(workflowContext);
return getMatrixContext(workflowContext, mode);
}
return undefined;
@@ -7,6 +7,7 @@ import {SequenceToken} from "@github/actions-workflow-parser/templates/tokens/se
import {StringToken} from "@github/actions-workflow-parser/templates/tokens/string-token";
import {TemplateToken} from "@github/actions-workflow-parser/templates/tokens/template-token";
import {WorkflowContext} from "../context/workflow-context";
import {Mode} from "./default";
import {getMatrixContext} from "./matrix";
type MatrixMap = {
@@ -62,7 +63,7 @@ describe("matrix context", () => {
const workflowContext = {} as WorkflowContext;
expect(workflowContext.job).toBeUndefined();
const context = getMatrixContext(workflowContext);
const context = getMatrixContext(workflowContext, Mode.Validation);
expect(context).toEqual(new data.Dictionary());
});
@@ -71,7 +72,7 @@ describe("matrix context", () => {
const workflowContext = {job} as WorkflowContext;
expect(workflowContext.job!.strategy).toBeUndefined();
const context = getMatrixContext(workflowContext);
const context = getMatrixContext(workflowContext, Mode.Validation);
expect(context).toEqual(new data.Dictionary());
});
@@ -79,7 +80,7 @@ describe("matrix context", () => {
const workflowContext = contextFromStrategy(stringToToken("hello"));
expect(workflowContext.job!.strategy).toBeDefined();
const context = getMatrixContext(workflowContext);
const context = getMatrixContext(workflowContext, Mode.Validation);
expect(context).toEqual(new data.Dictionary());
});
@@ -87,8 +88,8 @@ describe("matrix context", () => {
const strategy = new MappingToken(undefined, undefined, undefined);
const workflowContext = contextFromStrategy(strategy);
const context = getMatrixContext(workflowContext);
expect(context).toEqual(new data.Dictionary());
const context = getMatrixContext(workflowContext, Mode.Validation);
expect(context).toEqual(new data.Null());
});
it("matrix is not a mapping token", () => {
@@ -96,8 +97,8 @@ describe("matrix context", () => {
strategy.add(stringToToken("matrix"), stringToToken("hello"));
const workflowContext = contextFromStrategy(strategy);
const context = getMatrixContext(workflowContext);
expect(context).toEqual(new data.Dictionary());
const context = getMatrixContext(workflowContext, Mode.Validation);
expect(context).toEqual(new data.Null());
});
it("empty matrix", () => {
@@ -105,7 +106,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, Mode.Validation);
expect(context).toEqual(new data.Dictionary());
});
});
@@ -116,9 +117,9 @@ 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, Mode.Validation);
expect(context).toEqual(new data.Dictionary());
expect(context).toEqual(new data.Null());
});
it("matrix with include expression", () => {
@@ -136,9 +137,35 @@ describe("matrix context", () => {
strategy.add(stringToToken("matrix"), matrix);
const workflowContext = contextFromStrategy(strategy);
const context = getMatrixContext(workflowContext);
const context = getMatrixContext(workflowContext, Mode.Validation);
expect(context).toEqual(new data.Dictionary());
expect(context).toEqual(new data.Null());
});
it("matrix with include expression during completion", () => {
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, Mode.Completion);
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", () => {
@@ -151,7 +178,7 @@ describe("matrix context", () => {
strategy.add(stringToToken("matrix"), matrix);
const workflowContext = contextFromStrategy(strategy);
const context = getMatrixContext(workflowContext);
const context = getMatrixContext(workflowContext, Mode.Validation);
expect(context).toEqual(
new data.Dictionary({
@@ -166,7 +193,7 @@ describe("matrix context", () => {
it("basic matrix", () => {
const workflowContext = createMatrix({os: ["ubuntu-latest", "windows-latest"]});
const context = getMatrixContext(workflowContext);
const context = getMatrixContext(workflowContext, Mode.Validation);
expect(context).toEqual(
new data.Dictionary({
key: "os",
@@ -181,7 +208,7 @@ describe("matrix context", () => {
node: ["12", "14"]
});
const context = getMatrixContext(workflowContext);
const context = getMatrixContext(workflowContext, Mode.Validation);
expect(context).toEqual(
new data.Dictionary(
{
@@ -208,7 +235,7 @@ describe("matrix context", () => {
]
});
const context = getMatrixContext(workflowContext);
const context = getMatrixContext(workflowContext, Mode.Validation);
expect(context).toEqual(
new data.Dictionary(
@@ -242,7 +269,7 @@ describe("matrix context", () => {
]
});
const context = getMatrixContext(workflowContext);
const context = getMatrixContext(workflowContext, Mode.Validation);
expect(context).toEqual(
new data.Dictionary(
@@ -276,7 +303,7 @@ describe("matrix context", () => {
]
});
const context = getMatrixContext(workflowContext);
const context = getMatrixContext(workflowContext, Mode.Validation);
expect(context).toEqual(
new data.Dictionary(
@@ -311,7 +338,7 @@ describe("matrix context", () => {
]
});
const context = getMatrixContext(workflowContext);
const context = getMatrixContext(workflowContext, Mode.Validation);
expect(context).toEqual(new data.Dictionary());
});
@@ -4,8 +4,9 @@ import {KeyValuePair} from "@github/actions-workflow-parser/templates/tokens/key
import {MappingToken} from "@github/actions-workflow-parser/templates/tokens/mapping-token";
import {SequenceToken} from "@github/actions-workflow-parser/templates/tokens/sequence-token";
import {WorkflowContext} from "../context/workflow-context";
import {ContextValue, Mode} from "./default";
export function getMatrixContext(workflowContext: WorkflowContext): data.Dictionary {
export function getMatrixContext(workflowContext: WorkflowContext, mode: Mode): ContextValue {
// https://docs.github.com/en/actions/learn-github-actions/contexts#matrix-context
const strategy = workflowContext.job?.strategy;
if (!strategy || !isMapping(strategy)) {
@@ -15,13 +16,13 @@ export function getMatrixContext(workflowContext: WorkflowContext): data.Diction
const matrix = strategy.find("matrix");
if (!matrix || !isMapping(matrix)) {
// Matrix could be an expression, so there's no context we can provide
return new data.Dictionary();
return new data.Null();
}
const properties = matrixProperties(matrix);
const properties = matrixProperties(matrix, mode);
if (!properties) {
// Matrix included an expression, so there's no context we can provide
return new data.Dictionary();
return new data.Null();
}
const d = new data.Dictionary();
@@ -93,7 +94,7 @@ export function getMatrixContext(workflowContext: WorkflowContext): data.Diction
*
* Keys: os, version, environment
*/
function matrixProperties(matrix: MappingToken): Map<string, Set<string> | undefined> | undefined {
function matrixProperties(matrix: MappingToken, mode: Mode): Map<string, Set<string> | undefined> | undefined {
const properties = new Map<string, Set<string> | undefined>();
let include: SequenceToken | undefined;
@@ -106,9 +107,14 @@ function matrixProperties(matrix: MappingToken): Map<string, Set<string> | 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;
// Without the full properties of the matrix, we shouldn't validate anything
if (mode === Mode.Validation) {
return;
} else {
continue;
}
}
include = pair.value;
break;
@@ -1,18 +1,30 @@
import {Position, TextDocument} from "vscode-languageserver-textdocument";
import {createDocument} from "./document";
// Calculates the position of the cursor and the document without that cursor
// Cursor is represented by a `|` character
export function getPositionFromCursor(input: string): [TextDocument, Position] {
/**
* Calculates the position of the cursor and the document without that cursor
* Cursor is represented by a `|` character
* @param input Input string
* @param skip Instances of `|` to skip
*/
export function getPositionFromCursor(input: string, skip = 0): [TextDocument, Position] {
const doc = createDocument("test.yaml", input);
const cursorIndex = doc.getText().indexOf("|");
let cursorIndex = doc.getText().indexOf("|");
for (let i = 0; i < skip && cursorIndex !== -1; i++) {
cursorIndex = doc.getText().indexOf("|", cursorIndex + 1);
}
if (cursorIndex === -1) {
throw new Error("No cursor found in document");
}
// Replace only the last occurence of | in string
let newText = doc.getText();
newText = newText.substring(0, cursorIndex) + newText.substring(cursorIndex + 1);
const position = doc.positionAt(cursorIndex);
const newDoc = TextDocument.create(doc.uri, doc.languageId, doc.version, doc.getText().replace("|", ""));
const newDoc = TextDocument.create(doc.uri, doc.languageId, doc.version, newText);
return [newDoc, position];
}
@@ -1,11 +1,11 @@
import {TemplateToken} from "@github/actions-workflow-parser/templates/tokens/template-token";
export function getAllowedContext(token: TemplateToken, parent: TemplateToken): string[] {
export function getAllowedContext(token: TemplateToken, parent: TemplateToken | null | undefined): string[] {
// Workaround for https://github.com/github/c2c-actions-experience/issues/6876
// Context is inherited from the parent
const allowedContext = new Set<string>();
for (const t of [token, parent]) {
if (t.definition?.readerContext) {
if (t?.definition?.readerContext) {
for (const context of t.definition.readerContext) {
allowedContext.add(context);
}
@@ -305,4 +305,476 @@ jobs:
]);
});
});
describe("multi-line strings", () => {
it("indented |", async () => {
const input = `on: push
jobs:
build:
runs-on: ubuntu-latest
steps:
- run: |
first line
test \${{ github.does-not-exist }}
test2`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toEqual([
{
message: "Context access might be invalid: does-not-exist",
range: {
end: {
character: 43,
line: 7
},
start: {
character: 15,
line: 7
}
},
severity: DiagnosticSeverity.Warning
}
]);
});
it("indented |+", async () => {
const input = `on: push
jobs:
build:
runs-on: ubuntu-latest
steps:
- run: |+
first line
test \${{ github.does-not-exist }}
test2`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toEqual([
{
message: "Context access might be invalid: does-not-exist",
range: {
end: {
character: 43,
line: 7
},
start: {
character: 15,
line: 7
}
},
severity: DiagnosticSeverity.Warning
}
]);
});
it("indented >", async () => {
const input = `on: push
jobs:
build:
runs-on: ubuntu-latest
steps:
- run: >
first line
test \${{ github.does-not-exist }}
test2`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toEqual([
{
message: "Context access might be invalid: does-not-exist",
range: {
end: {
character: 43,
line: 7
},
start: {
character: 15,
line: 7
}
},
severity: DiagnosticSeverity.Warning
}
]);
});
it("indented >+", async () => {
const input = `on: push
jobs:
build:
runs-on: ubuntu-latest
steps:
- run: >+
first line
test \${{ github.does-not-exist }}
test2`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toEqual([
{
message: "Context access might be invalid: does-not-exist",
range: {
end: {
character: 43,
line: 7
},
start: {
character: 15,
line: 7
}
},
severity: DiagnosticSeverity.Warning
}
]);
});
});
describe("matrix context", () => {
it("reference within a matrix job", async () => {
const input = `
on: push
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
node: [14, 16]
steps:
- uses: actions/checkout@v3
- run: echo \${{ matrix.node }}
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toEqual([]);
});
it("reference outside of a matrix job", async () => {
const input = `
on: push
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- run: echo \${{ matrix.node }}
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toEqual([
{
message: "Context access might be invalid: matrix",
range: {
end: {
character: 36,
line: 8
},
start: {
character: 18,
line: 8
}
},
severity: DiagnosticSeverity.Warning
}
]);
});
it("basic matrix", async () => {
const input = `
on: push
jobs:
test:
runs-on: \${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest]
node: [14, 16]
steps:
- uses: actions/checkout@v3
- run: echo \${{ matrix.node }}
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toEqual([]);
});
it("invalid property reference", async () => {
const input = `
on: push
jobs:
test:
runs-on: \${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest]
node: [14, 16]
include:
- os: macos-latest
node: 14
exclude:
- os: windows-latest
node: 14
steps:
- uses: actions/checkout@v3
- run: echo \${{ matrix.goversion }}
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toEqual([
{
message: "Context access might be invalid: goversion",
range: {
end: {
character: 41,
line: 18
},
start: {
character: 18,
line: 18
}
},
severity: DiagnosticSeverity.Warning
}
]);
});
it("matrix with include", async () => {
const input = `
on: push
jobs:
test:
runs-on: \${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest]
node: [14, 16]
include:
- os: macos-latest
node: 14
steps:
- uses: actions/checkout@v3
- run: echo \${{ matrix.node }}
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toEqual([]);
});
it("matrix with only include", async () => {
const input = `
on: push
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
include:
- os: windows-latest
node: 14
steps:
- uses: actions/checkout@v3
- run: echo \${{ matrix.os }}
- run: echo \${{ matrix.node }}
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toEqual([]);
});
it("matrix with exclude", async () => {
const input = `
on: push
jobs:
test:
runs-on: \${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest]
node: [14, 16]
include:
- os: macos-latest
node: 14
exclude:
- os: windows-latest
node: 14
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: \${{ matrix.node }}
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toEqual([]);
});
it("matrix with only exclude", async () => {
const input = `
on: push
jobs:
test:
runs-on: \${{ matrix.os }}
strategy:
matrix:
exclude:
- os: windows-latest
node: 14
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: \${{ matrix.node }}
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toEqual([
{
message: "Context access might be invalid: os",
range: {
end: {
character: 29,
line: 5
},
start: {
character: 13,
line: 5
}
},
severity: DiagnosticSeverity.Warning
},
{
message: "Context access might be invalid: node",
range: {
end: {
character: 42,
line: 15
},
start: {
character: 24,
line: 15
}
},
severity: DiagnosticSeverity.Warning
}
]);
});
it("matrix from expression", async () => {
const input = `
on: push
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix: \${{ fromJSON('{"color":["green","blue"]}') }}
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: \${{ matrix.ANYVALUE }}
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toEqual([]);
});
it("matrix with include expression", async () => {
const input = `
on: push
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
fruit: [apple, pear]
animal: [cat, dog]
include: \${{ fromJSON('{"color":"green"}') }}
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: \${{ matrix.ANYVALUE }}
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toEqual([]);
});
it("matrix with property expression", async () => {
const input = `
on: push
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
color: \${{ fromJSON('["green","blue"]') }}
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: \${{ matrix.color }}
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toEqual([]);
});
it("matrix with property expression and invalid property reference", async () => {
const input = `
on: push
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
color: \${{ fromJSON('["green","blue"]') }}
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: \${{ matrix.shape }}
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toEqual([
{
message: "Context access might be invalid: shape",
range: {
end: {
character: 43,
line: 13
},
start: {
character: 24,
line: 13
}
},
severity: DiagnosticSeverity.Warning
}
]);
});
});
});
+8 -7
View File
@@ -10,7 +10,6 @@ import {
} 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 {Definition} from "@github/actions-workflow-parser/templates/schema/definition";
import {BasicExpressionToken} from "@github/actions-workflow-parser/templates/tokens/basic-expression-token";
import {StringToken} from "@github/actions-workflow-parser/templates/tokens/string-token";
import {TemplateToken} from "@github/actions-workflow-parser/templates/tokens/template-token";
@@ -19,11 +18,12 @@ import {TextDocument} from "vscode-languageserver-textdocument";
import {Diagnostic, DiagnosticSeverity, URI} from "vscode-languageserver-types";
import {ContextProviderConfig} from "./context-providers/config";
import {getContext} from "./context-providers/default";
import {getContext, Mode} from "./context-providers/default";
import {getWorkflowContext, WorkflowContext} from "./context/workflow-context";
import {AccessError, wrapDictionary} from "./expression-validation/error-dictionary";
import {error} from "./log";
import {nullTrace} from "./nulltrace";
import {getAllowedContext} from "./utils/allowed-context";
import {findToken} from "./utils/find-token";
import {mapRange} from "./utils/range";
import {ValueProviderConfig, ValueProviderKind} from "./value-providers/config";
@@ -95,12 +95,14 @@ async function additionalValidations(
const validationToken = key || parent || token;
const validationDefinition = validationToken.definition;
const allowedContext = getAllowedContext(validationToken, parent);
// If this is an expression, validate it
if (isBasicExpression(token)) {
await validateExpression(
diagnostics,
token,
validationDefinition,
allowedContext,
contextProviderConfig,
getProviderContext(documentUri, template, root, token)
);
@@ -171,14 +173,13 @@ function getProviderContext(
async function validateExpression(
diagnostics: Diagnostic[],
token: BasicExpressionToken,
definition: Definition | undefined,
allowedContext: string[],
contextProviderConfig: ContextProviderConfig | undefined,
workflowContext: WorkflowContext
) {
// Validate the expression
for (const expression of token.originalExpressions || [token]) {
const allowedContexts = definition?.readerContext || [];
const {namedContexts, functions} = splitAllowedContext(allowedContexts);
const {namedContexts, functions} = splitAllowedContext(allowedContext);
let expr: Expr | undefined;
@@ -194,7 +195,7 @@ async function validateExpression(
}
try {
const context = await getContext(namedContexts, contextProviderConfig, workflowContext);
const context = await getContext(namedContexts, contextProviderConfig, workflowContext, Mode.Validation);
const e = new Evaluator(expr, wrapDictionary(context));
e.evaluate();
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "browser-playground",
"version": "0.1.41",
"version": "0.1.47",
"description": "",
"private": true,
"main": "index.js",
"type": "module",
"dependencies": {
"@github/actions-languageserver": "^0.1.41",
"@github/actions-languageserver": "^0.1.47",
"monaco-editor-webpack-plugin": "^7.0.1",
"monaco-editor-workers": "^0.34.2",
"monaco-languageclient": "^4.0.3",
+1 -1
View File
@@ -1,5 +1,5 @@
{
"$schema": "node_modules/lerna/schemas/lerna-schema.json",
"useWorkspaces": true,
"version": "0.1.41"
"version": "0.1.47"
}
+13 -13
View File
@@ -16,10 +16,10 @@
},
"actions-languageserver": {
"name": "@github/actions-languageserver",
"version": "0.1.41",
"version": "0.1.47",
"license": "MIT",
"dependencies": {
"@github/actions-languageservice": "^0.1.41",
"@github/actions-languageservice": "^0.1.47",
"@octokit/rest": "^19.0.5",
"vscode-languageserver": "^8.0.2",
"vscode-languageserver-textdocument": "^1.0.7"
@@ -38,7 +38,7 @@
},
"actions-languageservice": {
"name": "@github/actions-languageservice",
"version": "0.1.41",
"version": "0.1.47",
"license": "MIT",
"dependencies": {
"@github/actions-workflow-parser": "*",
@@ -59,10 +59,10 @@
}
},
"browser-playground": {
"version": "0.1.41",
"version": "0.1.47",
"license": "MIT",
"dependencies": {
"@github/actions-languageserver": "^0.1.41",
"@github/actions-languageserver": "^0.1.47",
"monaco-editor-webpack-plugin": "^7.0.1",
"monaco-editor-workers": "^0.34.2",
"monaco-languageclient": "^4.0.3",
@@ -698,9 +698,9 @@
"link": true
},
"node_modules/@github/actions-workflow-parser": {
"version": "0.0.30",
"resolved": "https://npm.pkg.github.com/download/@github/actions-workflow-parser/0.0.30/2c95f0614784e740bc544d2a8f7b1661e9526920",
"integrity": "sha512-Gr/fDCh+dVZVCgDAo67gPVqV+cVrrhBPkVIaxnpyBVnPUtVftthY4+Klg3r37gNRSchxuORyxKQk/cPDrZwxoQ==",
"version": "0.0.31",
"resolved": "https://npm.pkg.github.com/download/@github/actions-workflow-parser/0.0.31/ebc46956b91ed1a8c45efd41a3256ae818722557",
"integrity": "sha512-3k+MBWG7Gn86aHeLdJTiYUTGm53npEelzleCbq/8Ir51xVkRINsyNe4HuGJizXxZ2WoTxAkLeZ6qpO1oCucSIg==",
"license": "MIT",
"dependencies": {
"@github/actions-expressions": "*",
@@ -13505,7 +13505,7 @@
"@github/actions-languageserver": {
"version": "file:actions-languageserver",
"requires": {
"@github/actions-languageservice": "^0.1.41",
"@github/actions-languageservice": "^0.1.47",
"@octokit/rest": "^19.0.5",
"@types/jest": "^29.0.3",
"jest": "^29.0.3",
@@ -13533,9 +13533,9 @@
}
},
"@github/actions-workflow-parser": {
"version": "0.0.30",
"resolved": "https://npm.pkg.github.com/download/@github/actions-workflow-parser/0.0.30/2c95f0614784e740bc544d2a8f7b1661e9526920",
"integrity": "sha512-Gr/fDCh+dVZVCgDAo67gPVqV+cVrrhBPkVIaxnpyBVnPUtVftthY4+Klg3r37gNRSchxuORyxKQk/cPDrZwxoQ==",
"version": "0.0.31",
"resolved": "https://npm.pkg.github.com/download/@github/actions-workflow-parser/0.0.31/ebc46956b91ed1a8c45efd41a3256ae818722557",
"integrity": "sha512-3k+MBWG7Gn86aHeLdJTiYUTGm53npEelzleCbq/8Ir51xVkRINsyNe4HuGJizXxZ2WoTxAkLeZ6qpO1oCucSIg==",
"requires": {
"@github/actions-expressions": "*",
"yaml": "^2.0.0-8"
@@ -16288,7 +16288,7 @@
"browser-playground": {
"version": "file:browser-playground",
"requires": {
"@github/actions-languageserver": "^0.1.41",
"@github/actions-languageserver": "^0.1.47",
"css-loader": "^6.7.2",
"monaco-editor-webpack-plugin": "^7.0.1",
"monaco-editor-workers": "^0.34.2",