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

Commit

Permalink
feat: report errors on unresolved actions (#38)
Browse files Browse the repository at this point in the history
Fixes $5
  • Loading branch information
OmarTawfik committed Mar 12, 2019
1 parent 1e82159 commit 61a2eec
Show file tree
Hide file tree
Showing 7 changed files with 105 additions and 26 deletions.
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

0 comments on commit 61a2eec

Please sign in to comment.