Ensure existing context errors don't prevent parsing workflows

This commit is contained in:
Josh Gross
2023-02-09 18:50:05 -05:00
parent ff0e9ba6e2
commit e25500b8e4
4 changed files with 38 additions and 7 deletions
@@ -5,6 +5,7 @@ import {ContextProviderConfig} from "./context-providers/config";
import {registerLogger} from "./log"; import {registerLogger} from "./log";
import {getPositionFromCursor} from "./test-utils/cursor-position"; import {getPositionFromCursor} from "./test-utils/cursor-position";
import {TestLogger} from "./test-utils/logger"; import {TestLogger} from "./test-utils/logger";
import {testFileProvider} from "./test-utils/test-file-provider";
const contextProviderConfig: ContextProviderConfig = { const contextProviderConfig: ContextProviderConfig = {
getContext: async (context: string) => { getContext: async (context: string) => {
@@ -1130,4 +1131,24 @@ jobs:
expect(result.find(x => x.label === "toJson")!.insertText).toBe("toJson"); expect(result.find(x => x.label === "toJson")!.insertText).toBe("toJson");
}); });
it("Parsing errors don't prevent reusable workflows from being loaded", async () => {
const input = `
on: push
jobs:
a:
uses: ./reusable-workflow-with-outputs.yaml
b:
needs: [a]
runs-on: ubuntu-latest
steps:
- run: echo "hello \${{ needs.a.outputs.| }}"
`;
const result = await complete(...getPositionFromCursor(input), {
contextProviderConfig,
fileProvider: testFileProvider
});
expect(result.map(x => x.label)).toEqual(["build_id"]);
});
}); });
@@ -20,9 +20,12 @@ export function parseWorkflow(entryFile: File, contextOrTrace: TraceWriter | Tem
: new TemplateContext(new TemplateValidationErrors(), getWorkflowSchema(), contextOrTrace); : new TemplateContext(new TemplateValidationErrors(), getWorkflowSchema(), contextOrTrace);
const fileId = context.getFileId(entryFile.name); const fileId = context.getFileId(entryFile.name);
const reader = new YamlObjectReader(context, fileId, entryFile.content); const reader = new YamlObjectReader(fileId, entryFile.content);
if (context.errors.count > 0) { if (reader.errors.length > 0) {
// The file is not valid YAML, template errors could be misleading // The file is not valid YAML, template errors could be misleading
for (const err of reader.errors) {
context.error(fileId, err.message, err.range);
}
return { return {
context, context,
value: undefined value: undefined
@@ -68,11 +68,11 @@ it("YAML errors include range information", () => {
const context = new TemplateContext(new TemplateValidationErrors(), getWorkflowSchema(), nullTrace); const context = new TemplateContext(new TemplateValidationErrors(), getWorkflowSchema(), nullTrace);
const fileId = context.getFileId("test.yaml"); const fileId = context.getFileId("test.yaml");
new YamlObjectReader(context, fileId, content); const reader = new YamlObjectReader(fileId, content);
expect(context.errors.count).toBe(1); expect(reader.errors.length).toBe(1);
const error = context.errors.getErrors()[0]; const error = reader.errors[0];
expect(error.range).toEqual({ expect(error.range).toEqual({
start: {line: 7, column: 38}, start: {line: 7, column: 38},
end: {line: 7, column: 63} end: {line: 7, column: 63}
@@ -15,20 +15,27 @@ import {
} from "../templates/tokens/index"; } from "../templates/tokens/index";
import {Position, TokenRange} from "../templates/tokens/token-range"; import {Position, TokenRange} from "../templates/tokens/token-range";
export type YamlError = {
message: string;
range: TokenRange | undefined;
};
export class YamlObjectReader implements ObjectReader { export class YamlObjectReader implements ObjectReader {
private readonly _generator: Generator<ParseEvent>; private readonly _generator: Generator<ParseEvent>;
private _current!: IteratorResult<ParseEvent>; private _current!: IteratorResult<ParseEvent>;
private fileId?: number; private fileId?: number;
private lineCounter = new LineCounter(); private lineCounter = new LineCounter();
constructor(context: TemplateContext, fileId: number | undefined, content: string) { public errors: YamlError[] = [];
constructor(fileId: number | undefined, content: string) {
const doc = parseDocument(content, { const doc = parseDocument(content, {
lineCounter: this.lineCounter, lineCounter: this.lineCounter,
keepSourceTokens: true, keepSourceTokens: true,
uniqueKeys: false // Uniqueness is validated by the template reader uniqueKeys: false // Uniqueness is validated by the template reader
}); });
for (const err of doc.errors) { for (const err of doc.errors) {
context.error(fileId, err.message, rangeFromLinePos(err.linePos)); this.errors.push({message: err.message, range: rangeFromLinePos(err.linePos)});
} }
this._generator = this.getNodes(doc); this._generator = this.getNodes(doc);
this.fileId = fileId; this.fileId = fileId;