Add a FileProvider to parseWorkflow

This commit is contained in:
Josh Gross
2023-02-06 11:15:15 -05:00
parent c55066fbec
commit f4dde16b61
19 changed files with 96 additions and 161 deletions
@@ -9,7 +9,7 @@ const nullTrace: TraceWriter = {
}; };
export function createWorkflowContext(workflow: string, job?: string, stepIndex?: number): WorkflowContext { export function createWorkflowContext(workflow: string, job?: string, stepIndex?: number): WorkflowContext {
const parsed = parseWorkflow("test.yaml", [{name: "test.yaml", content: workflow}], nullTrace); const parsed = parseWorkflow({name: "test.yaml", content: workflow}, nullTrace);
if (!parsed.value) { if (!parsed.value) {
throw new Error("Failed to parse workflow"); throw new Error("Failed to parse workflow");
} }
+1 -1
View File
@@ -65,7 +65,7 @@ export async function complete(
name: textDocument.uri, name: textDocument.uri,
content: newDoc.getText() content: newDoc.getText()
}; };
const result = parseWorkflow(file.name, [file], nullTrace); const result = parseWorkflow(file, nullTrace);
if (!result.value) { if (!result.value) {
return []; return [];
} }
@@ -8,13 +8,10 @@ import {getWorkflowContext, WorkflowContext} from "./workflow-context";
function testGetWorkflowContext(input: string): WorkflowContext { function testGetWorkflowContext(input: string): WorkflowContext {
const [textDocument, pos] = getPositionFromCursor(input); const [textDocument, pos] = getPositionFromCursor(input);
const result = parseWorkflow( const result = parseWorkflow(
"wf.yaml", {
[ content: textDocument.getText(),
{ name: "wf.yaml"
content: textDocument.getText(), },
name: "wf.yaml"
}
],
nullTrace nullTrace
); );
@@ -14,7 +14,7 @@ export async function documentLinks(document: TextDocument): Promise<DocumentLin
content: document.getText() content: document.getText()
}; };
const result = parseWorkflow(file.name, [file], nullTrace); const result = parseWorkflow(file, nullTrace);
if (!result.value) { if (!result.value) {
return []; return [];
} }
@@ -78,7 +78,7 @@ function testMapToExpressionPos(input: string) {
name: td.uri, name: td.uri,
content: td.getText() content: td.getText()
}; };
const result = parseWorkflow(file.name, [file], nullTrace); const result = parseWorkflow(file, nullTrace);
if (!result.value) { if (!result.value) {
throw new Error("Invalid workflow"); throw new Error("Invalid workflow");
} }
@@ -105,7 +105,7 @@ async function hoverExpression(input: string) {
name: td.uri, name: td.uri,
content: td.getText() content: td.getText()
}; };
const result = parseWorkflow(file.name, [file], nullTrace); const result = parseWorkflow(file, nullTrace);
if (!result.value) { if (!result.value) {
return undefined; return undefined;
} }
+1 -1
View File
@@ -37,7 +37,7 @@ export async function hover(document: TextDocument, position: Position, config?:
name: document.uri, name: document.uri,
content: document.getText() content: document.getText()
}; };
const result = parseWorkflow(file.name, [file], nullTrace); const result = parseWorkflow(file, nullTrace);
if (!result.value) { if (!result.value) {
return null; return null;
} }
@@ -27,13 +27,10 @@ function testFindToken(input: string): {
} { } {
const [textDocument, pos] = getPositionFromCursor(input); const [textDocument, pos] = getPositionFromCursor(input);
const result = parseWorkflow( const result = parseWorkflow(
"wf.yaml", {
[ content: textDocument.getText(),
{ name: "wf.yaml"
content: textDocument.getText(), },
name: "wf.yaml"
}
],
nullTrace nullTrace
); );
+1 -1
View File
@@ -56,7 +56,7 @@ export async function validate(
const diagnostics: Diagnostic[] = []; const diagnostics: Diagnostic[] = [];
try { try {
const result: ParseWorkflowResult = parseWorkflow(file.name, [file], nullTrace); const result: ParseWorkflowResult = parseWorkflow(file, nullTrace);
if (result.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 // Errors will be updated in the context. Attempt to do the conversion anyway in order to give the user more information
const template = convertWorkflowTemplate(result.context, result.value, ErrorPolicy.TryConversion); const template = convertWorkflowTemplate(result.context, result.value, ErrorPolicy.TryConversion);
@@ -1,5 +0,0 @@
import {File} from "@github/actions-workflow-parser/workflows/file";
export type WorkflowProvider = {
getWorkflow: (name: string) => Promise<File | undefined>;
};
+20 -35
View File
@@ -6,19 +6,16 @@ import {parseWorkflow} from "./workflows/workflow-parser";
describe("Workflow Expression Parsing", () => { describe("Workflow Expression Parsing", () => {
it("preserves original expressions when building format", () => { it("preserves original expressions when building format", () => {
const result = parseWorkflow( const result = parseWorkflow(
"test.yaml", {
[ name: "test.yaml",
{ content: `on: push
name: "test.yaml",
content: `on: push
run-name: Test \${{ github.event_name }} \${{ github.ref }} run-name: Test \${{ github.event_name }} \${{ github.ref }}
jobs: jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- run: echo 'hello'` - run: echo 'hello'`
} },
],
nullTrace nullTrace
); );
@@ -44,11 +41,9 @@ jobs:
it("preserves original expressions when building format for multi-line strings", () => { it("preserves original expressions when building format for multi-line strings", () => {
const result = parseWorkflow( const result = parseWorkflow(
"test.yaml", {
[ name: "test.yaml",
{ content: `on: push
name: "test.yaml",
content: `on: push
jobs: jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@@ -56,8 +51,7 @@ jobs:
- run: | - run: |
echo \${{ github.event_name }} echo \${{ github.event_name }}
echo 'hello' \${{github.ref }}` echo 'hello' \${{github.ref }}`
} },
],
nullTrace nullTrace
); );
@@ -109,11 +103,9 @@ jobs:
it("return errors and string token with preserved expressions for (multiple) expression errors", () => { it("return errors and string token with preserved expressions for (multiple) expression errors", () => {
const result = parseWorkflow( const result = parseWorkflow(
"test.yaml", {
[ name: "test.yaml",
{ content: `on: push
name: "test.yaml",
content: `on: push
jobs: jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@@ -121,8 +113,7 @@ jobs:
- run: | - run: |
echo \${{ abc }} echo \${{ abc }}
echo 'hello' \${{ gith }}` echo 'hello' \${{ gith }}`
} },
],
nullTrace nullTrace
); );
@@ -147,11 +138,9 @@ jobs:
it("reports all errors for multi-line expressions at the correct locations", () => { it("reports all errors for multi-line expressions at the correct locations", () => {
const result = parseWorkflow( const result = parseWorkflow(
"test.yaml", {
[ name: "test.yaml",
{ content: `on: push
name: "test.yaml",
content: `on: push
jobs: jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@@ -159,8 +148,7 @@ jobs:
- run: | - run: |
echo \${{ fromJSON2('test') }} echo \${{ fromJSON2('test') }}
echo 'hello' \${{ toJSON2(inputs.test) }}` echo 'hello' \${{ toJSON2(inputs.test) }}`
} },
],
nullTrace nullTrace
); );
@@ -186,19 +174,16 @@ jobs:
it("parses isExpression strings into expression tokens", () => { it("parses isExpression strings into expression tokens", () => {
const result = parseWorkflow( const result = parseWorkflow(
"test.yaml", {
[ name: "test.yaml",
{ content: `on: push
name: "test.yaml",
content: `on: push
jobs: jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: github.event_name == 'push' if: github.event_name == 'push'
steps: steps:
- run: echo 'hello'` - run: echo 'hello'`
} },
],
nullTrace nullTrace
); );
+17 -29
View File
@@ -5,13 +5,10 @@ import {parseWorkflow} from "./workflows/workflow-parser";
describe("parseWorkflow", () => { describe("parseWorkflow", () => {
it("parses valid workflow", () => { it("parses valid workflow", () => {
const result = parseWorkflow( const result = parseWorkflow(
"test.yaml", {
[ name: "test.yaml",
{ content: "on: push\njobs:\n build:\n runs-on: ubuntu-latest\n steps:\n - run: echo 'hello'"
name: "test.yaml", },
content: "on: push\njobs:\n build:\n runs-on: ubuntu-latest\n steps:\n - run: echo 'hello'"
}
],
nullTrace nullTrace
); );
@@ -20,13 +17,10 @@ describe("parseWorkflow", () => {
it("contains range for error", () => { it("contains range for error", () => {
const result = parseWorkflow( const result = parseWorkflow(
"test.yaml", {
[ name: "test.yaml",
{ content: "on: push\njobs:\n build:\n steps:\n - run: echo 'hello'"
name: "test.yaml", },
content: "on: push\njobs:\n build:\n steps:\n - run: echo 'hello'"
}
],
nullTrace nullTrace
); );
@@ -40,19 +34,16 @@ describe("parseWorkflow", () => {
it("error range for expression is constrained to scalar node", () => { it("error range for expression is constrained to scalar node", () => {
const result = parseWorkflow( const result = parseWorkflow(
"test.yaml", {
[ name: "test.yaml",
{ content: `on: push
name: "test.yaml",
content: `on: push
jobs: jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: \${{ github.event = 12 }} - name: \${{ github.event = 12 }}
run: echo 'hello'` run: echo 'hello'`
} },
],
nullTrace nullTrace
); );
@@ -71,14 +62,11 @@ jobs:
it("tokens contain descriptions", () => { it("tokens contain descriptions", () => {
const result = parseWorkflow( const result = parseWorkflow(
"test.yaml", {
[ name: "test.yaml",
{ content:
name: "test.yaml", "on: push\nname: hello\njobs:\n build:\n runs-on: ubuntu-latest\n steps:\n - run: echo 'hello'"
content: },
"on: push\nname: hello\njobs:\n build:\n runs-on: ubuntu-latest\n steps:\n - run: echo 'hello'"
}
],
nullTrace nullTrace
); );
@@ -9,16 +9,13 @@ function serializeTemplate(template: unknown): unknown {
describe("convertWorkflowTemplate", () => { describe("convertWorkflowTemplate", () => {
it("converts workflow with one job", () => { it("converts workflow with one job", () => {
const result = parseWorkflow( const result = parseWorkflow(
"wf.yaml", {
[ name: "wf.yaml",
{ content: `on: push
name: "wf.yaml",
content: `on: push
jobs: jobs:
build: build:
runs-on: ubuntu-latest` runs-on: ubuntu-latest`
} },
],
nullTrace nullTrace
); );
@@ -48,11 +45,9 @@ jobs:
it("converts workflow if expressions", () => { it("converts workflow if expressions", () => {
const result = parseWorkflow( const result = parseWorkflow(
"wf.yaml", {
[ name: "wf.yaml",
{ content: `on: push
name: "wf.yaml",
content: `on: push
jobs: jobs:
build: build:
if: \${{ true }} if: \${{ true }}
@@ -60,8 +55,7 @@ jobs:
deploy: deploy:
if: true if: true
runs-on: ubuntu-latest` runs-on: ubuntu-latest`
} },
],
nullTrace nullTrace
); );
@@ -100,17 +94,14 @@ jobs:
it("converts workflow with empty needs", () => { it("converts workflow with empty needs", () => {
const result = parseWorkflow( const result = parseWorkflow(
"wf.yaml", {
[ name: "wf.yaml",
{ content: `on: push
name: "wf.yaml",
content: `on: push
jobs: jobs:
build: build:
needs: # comment to preserve whitespace in test needs: # comment to preserve whitespace in test
runs-on: ubuntu-latest` runs-on: ubuntu-latest`
} },
],
nullTrace nullTrace
); );
@@ -145,11 +136,9 @@ jobs:
it("converts workflow with needs errors", () => { it("converts workflow with needs errors", () => {
const result = parseWorkflow( const result = parseWorkflow(
"wf.yaml", {
[ name: "wf.yaml",
{ content: `on: push
name: "wf.yaml",
content: `on: push
jobs: jobs:
job1: job1:
needs: [unknown-job, job3] needs: [unknown-job, job3]
@@ -159,8 +148,7 @@ jobs:
job3: job3:
needs: job1 needs: job1
runs-on: ubuntu-latest` runs-on: ubuntu-latest`
} },
],
nullTrace nullTrace
); );
@@ -225,11 +213,9 @@ jobs:
it("converts workflow with invalid on", () => { it("converts workflow with invalid on", () => {
const result = parseWorkflow( const result = parseWorkflow(
"wf.yaml", {
[ name: "wf.yaml",
{ content: `on:
name: "wf.yaml",
content: `on:
workflow_dispatch: workflow_dispatch:
inputs: inputs:
test: test:
@@ -239,8 +225,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- run: echo hello` - run: echo hello`
} },
],
nullTrace nullTrace
); );
@@ -288,15 +273,12 @@ jobs:
it("converts workflow with invalid jobs", () => { it("converts workflow with invalid jobs", () => {
const result = parseWorkflow( const result = parseWorkflow(
"wf.yaml", {
[ name: "wf.yaml",
{ content: `on: push
name: "wf.yaml",
content: `on: push
jobs: jobs:
build:` build:`
} },
],
nullTrace nullTrace
); );
@@ -6,13 +6,10 @@ import {TemplateToken} from "./template-token";
describe("traverse", () => { describe("traverse", () => {
it("returns parent token and key", () => { it("returns parent token and key", () => {
const workflow = parseWorkflow( const workflow = parseWorkflow(
"wf.yaml", {
[ name: "wf.yaml",
{ content: `on: push`
name: "wf.yaml", },
content: `on: push`
}
],
nullTrace nullTrace
); );
@@ -0,0 +1,3 @@
export interface FileProvider {
getFileContent(path: string): Promise<File | undefined>;
}
@@ -11,7 +11,7 @@ jobs:
- name: 'Hello \${{ fromJSON('test') == inputs.name }}' - name: 'Hello \${{ fromJSON('test') == inputs.name }}'
run: echo Hello, world!`; run: echo Hello, world!`;
const result = parseWorkflow("main.yaml", [{name: "main.yaml", content: content}], nullTrace); const result = parseWorkflow({name: "main.yaml", content: content}, nullTrace);
expect(result.context.errors.count).toBe(1); expect(result.context.errors.count).toBe(1);
expect(result.value).toBeUndefined(); expect(result.value).toBeUndefined();
@@ -3,6 +3,7 @@ import * as templateReader from "../templates/template-reader";
import {TemplateToken} from "../templates/tokens/template-token"; import {TemplateToken} from "../templates/tokens/template-token";
import {TraceWriter} from "../templates/trace-writer"; import {TraceWriter} from "../templates/trace-writer";
import {File} from "./file"; import {File} from "./file";
import {FileProvider} from "./file-provider";
import {WORKFLOW_ROOT} from "./workflow-constants"; import {WORKFLOW_ROOT} from "./workflow-constants";
import {getWorkflowSchema} from "./workflow-schema"; import {getWorkflowSchema} from "./workflow-schema";
import {YamlObjectReader} from "./yaml-object-reader"; import {YamlObjectReader} from "./yaml-object-reader";
@@ -11,15 +12,11 @@ export interface ParseWorkflowResult {
value: TemplateToken | undefined; value: TemplateToken | undefined;
} }
export function parseWorkflow(entryFileName: string, files: File[], trace: TraceWriter): ParseWorkflowResult { export function parseWorkflow(entryFile: File, trace: TraceWriter, fileProvider?: FileProvider): ParseWorkflowResult {
const context = new TemplateContext(new TemplateValidationErrors(), getWorkflowSchema(), trace); const context = new TemplateContext(new TemplateValidationErrors(), getWorkflowSchema(), trace);
// Add file ids const fileId = context.getFileId(entryFile.name);
files.forEach(x => context.getFileId(x.name)); const reader = new YamlObjectReader(context, fileId, entryFile.content);
const fileId = context.getFileId(entryFileName);
const fileContent = files[fileId - 1].content;
const reader = new YamlObjectReader(context, fileId, fileContent);
if (context.errors.count > 0) { if (context.errors.count > 0) {
// The file is not valid YAML, template errors could be misleading // The file is not valid YAML, template errors could be misleading
return { return {
@@ -81,13 +81,10 @@ it("YAML errors include range information", () => {
function parseAsWorkflow(content: string): TemplateToken | undefined { function parseAsWorkflow(content: string): TemplateToken | undefined {
const result = parseWorkflow( const result = parseWorkflow(
"test.yaml", {
[ name: "test.yaml",
{ content: content
name: "test.yaml", },
content: content
}
],
nullTrace nullTrace
); );
+4 -7
View File
@@ -53,13 +53,10 @@ describe("x-lang tests", () => {
} }
const parseResult = parseWorkflow( const parseResult = parseWorkflow(
testFileName, {
[ name: testFileName,
{ content: testInput
name: testFileName, },
content: testInput
}
],
nullTrace nullTrace
); );