Skip to content
This repository has been archived by the owner on Jun 15, 2021. It is now read-only.

feat: report errors on unresolved actions #38

Merged
merged 1 commit into from
Mar 10, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 7 additions & 6 deletions src/binding/visitors/actions-analyzer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,25 +9,26 @@ import { MAXIMUM_SUPPORTED_ACTIONS } from "../../util/constants";

export class ActionsAnalyzer extends BoundNodeVisitor {
private exceededMaximum = false;
private allActions = new Set<string>();
private actions = new Set<string>();

private constructor(document: BoundDocument, private readonly bag: DiagnosticBag) {
super();
this.visit(document);
}

public static analyze(document: BoundDocument, bag: DiagnosticBag): void {
new ActionsAnalyzer(document, bag);
public static analyze(document: BoundDocument, bag: DiagnosticBag): ReadonlySet<string> {
const instance = new ActionsAnalyzer(document, bag);
return instance.actions;
}

protected visitAction(node: BoundAction): void {
if (this.allActions.has(node.name)) {
if (this.actions.has(node.name)) {
this.bag.duplicateActions(node.name, node.syntax.name.range);
} else {
this.allActions.add(node.name);
this.actions.add(node.name);
}

if (!this.exceededMaximum && this.allActions.size > MAXIMUM_SUPPORTED_ACTIONS) {
if (!this.exceededMaximum && this.actions.size > MAXIMUM_SUPPORTED_ACTIONS) {
this.bag.tooManyActions(node.syntax.name.range);
this.exceededMaximum = true;
}
Expand Down
32 changes: 32 additions & 0 deletions src/binding/visitors/resolves-analyzer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*!
* Copyright 2019 Omar Tawfik. Please see LICENSE file at the root of this repository.
*/

import { BoundNodeVisitor } from "../bound-node-visitor";
import { DiagnosticBag } from "../../util/diagnostics";
import { BoundDocument, BoundResolves } from "../bound-nodes";

export class ResolvesAnalyzer extends BoundNodeVisitor {
private constructor(
document: BoundDocument,
private readonly actions: ReadonlySet<string>,
private readonly bag: DiagnosticBag,
) {
super();
this.visit(document);
}

public static analyze(document: BoundDocument, actions: ReadonlySet<string>, bag: DiagnosticBag): void {
new ResolvesAnalyzer(document, actions, bag);
}

protected visitResolves(node: BoundResolves): void {
for (const action of node.actions) {
if (!this.actions.has(action.value)) {
this.bag.actionDoesNotExist(action.value, action.syntax.range);
}
}

super.visitResolves(node);
}
}
6 changes: 3 additions & 3 deletions src/binding/visitors/secrets-analyzer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { MAXIMUM_SUPPORTED_SECRETS } from "../../util/constants";

export class SecretsAnalyzer extends BoundNodeVisitor {
private exceededMaximum = false;
private allSecrets = new Set<string>();
private secrets = new Set<string>();

private constructor(document: BoundDocument, private readonly bag: DiagnosticBag) {
super();
Expand All @@ -32,8 +32,8 @@ export class SecretsAnalyzer extends BoundNodeVisitor {

if (!this.exceededMaximum) {
for (const secret of node.secrets) {
this.allSecrets.add(secret.value);
if (this.allSecrets.size > MAXIMUM_SUPPORTED_SECRETS) {
this.secrets.add(secret.value);
if (this.secrets.size > MAXIMUM_SUPPORTED_SECRETS) {
this.bag.tooManySecrets(secret.syntax.range);
this.exceededMaximum = true;
break;
Expand Down
4 changes: 3 additions & 1 deletion src/util/compilation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { BoundDocument } from "../binding/bound-nodes";
import { bindDocument } from "../binding/binder";
import { SecretsAnalyzer } from "../binding/visitors/secrets-analyzer";
import { ActionsAnalyzer } from "../binding/visitors/actions-analyzer";
import { ResolvesAnalyzer } from "../binding/visitors/resolves-analyzer";

export class Compilation {
private readonly bag: DiagnosticBag;
Expand All @@ -24,7 +25,8 @@ export class Compilation {
this.syntax = parseTokens(this.tokens, this.bag);
this.document = bindDocument(this.syntax, this.bag);

ActionsAnalyzer.analyze(this.document, this.bag);
const actions = ActionsAnalyzer.analyze(this.document, this.bag);
ResolvesAnalyzer.analyze(this.document, actions, this.bag);
SecretsAnalyzer.analyze(this.document, this.bag);
}

Expand Down
9 changes: 9 additions & 0 deletions src/util/diagnostics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export enum DiagnosticCode {
DuplicateSecrets,
TooManyActions,
DuplicateActions,
ActionDoesNotExist,
}

export interface Diagnostic {
Expand Down Expand Up @@ -215,4 +216,12 @@ export class DiagnosticBag {
message: `This file already defines another action with the name '${duplicate}'.`,
});
}

public actionDoesNotExist(action: string, range: TextRange): void {
this.items.push({
range,
code: DiagnosticCode.ActionDoesNotExist,
message: `The action '${action}' does not exist in the same workflow file.`,
});
}
}
29 changes: 29 additions & 0 deletions test/analysis-errors.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,35 @@ ERROR: Too many actions defined. The maximum currently supported is '100'.
| ^^^^^^^^^^^
103 |
"
`);
});

it("reports error on resolving a non-existing action", () => {
expectDiagnostics(`
action "a" {
uses = "./ci"
}
action "b" {
uses = "./ci"
}
workflow "c" {
on = "fork"
resolves = [
"a",
"b",
"not_found"
]
}
`).toMatchInlineSnapshot(`
"
ERROR: The action 'not_found' does not exist in the same workflow file.
11 | \\"a\\",
12 | \\"b\\",
13 | \\"not_found\\"
| ^^^^^^^^^^^
14 | ]
15 | }
"
`);
});
});
38 changes: 22 additions & 16 deletions test/parsing-errors.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,28 +89,34 @@ ERROR: A token of kind 'string' or '{' or '[' was expected here.

it("reports errors on extra commas in a string array", () => {
expectDiagnostics(`
action "a" {
uses = "./ci"
}
action "b" {
uses = "./ci"
}
workflow "x" {
on = "fork"
resolves = [
"a", "b", , , "c",
]
on = "fork"
resolves = [
, "a", , "b",
]
}
`).toMatchInlineSnapshot(`
"
ERROR: A token of kind ',' was not expected here.
3 | on = \\"fork\\"
4 | resolves = [
5 | \\"a\\", \\"b\\", , , \\"c\\",
| ^
6 | ]
7 | }
9 | on = \\"fork\\"
10 | resolves = [
11 | , \\"a\\", , \\"b\\",
| ^
12 | ]
13 | }
ERROR: A token of kind ',' was not expected here.
3 | on = \\"fork\\"
4 | resolves = [
5 | \\"a\\", \\"b\\", , , \\"c\\",
| ^
6 | ]
7 | }
9 | on = \\"fork\\"
10 | resolves = [
11 | , \\"a\\", , \\"b\\",
| ^
12 | ]
13 | }
"
`);
});
Expand Down