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

Commit 61a2eec

Browse files
committed
feat: report errors on unresolved actions (#38)
Fixes $5
1 parent 1e82159 commit 61a2eec

File tree

7 files changed

+105
-26
lines changed

7 files changed

+105
-26
lines changed

src/binding/visitors/actions-analyzer.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,25 +9,26 @@ import { MAXIMUM_SUPPORTED_ACTIONS } from "../../util/constants";
99

1010
export class ActionsAnalyzer extends BoundNodeVisitor {
1111
private exceededMaximum = false;
12-
private allActions = new Set<string>();
12+
private actions = new Set<string>();
1313

1414
private constructor(document: BoundDocument, private readonly bag: DiagnosticBag) {
1515
super();
1616
this.visit(document);
1717
}
1818

19-
public static analyze(document: BoundDocument, bag: DiagnosticBag): void {
20-
new ActionsAnalyzer(document, bag);
19+
public static analyze(document: BoundDocument, bag: DiagnosticBag): ReadonlySet<string> {
20+
const instance = new ActionsAnalyzer(document, bag);
21+
return instance.actions;
2122
}
2223

2324
protected visitAction(node: BoundAction): void {
24-
if (this.allActions.has(node.name)) {
25+
if (this.actions.has(node.name)) {
2526
this.bag.duplicateActions(node.name, node.syntax.name.range);
2627
} else {
27-
this.allActions.add(node.name);
28+
this.actions.add(node.name);
2829
}
2930

30-
if (!this.exceededMaximum && this.allActions.size > MAXIMUM_SUPPORTED_ACTIONS) {
31+
if (!this.exceededMaximum && this.actions.size > MAXIMUM_SUPPORTED_ACTIONS) {
3132
this.bag.tooManyActions(node.syntax.name.range);
3233
this.exceededMaximum = true;
3334
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*!
2+
* Copyright 2019 Omar Tawfik. Please see LICENSE file at the root of this repository.
3+
*/
4+
5+
import { BoundNodeVisitor } from "../bound-node-visitor";
6+
import { DiagnosticBag } from "../../util/diagnostics";
7+
import { BoundDocument, BoundResolves } from "../bound-nodes";
8+
9+
export class ResolvesAnalyzer extends BoundNodeVisitor {
10+
private constructor(
11+
document: BoundDocument,
12+
private readonly actions: ReadonlySet<string>,
13+
private readonly bag: DiagnosticBag,
14+
) {
15+
super();
16+
this.visit(document);
17+
}
18+
19+
public static analyze(document: BoundDocument, actions: ReadonlySet<string>, bag: DiagnosticBag): void {
20+
new ResolvesAnalyzer(document, actions, bag);
21+
}
22+
23+
protected visitResolves(node: BoundResolves): void {
24+
for (const action of node.actions) {
25+
if (!this.actions.has(action.value)) {
26+
this.bag.actionDoesNotExist(action.value, action.syntax.range);
27+
}
28+
}
29+
30+
super.visitResolves(node);
31+
}
32+
}

src/binding/visitors/secrets-analyzer.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { MAXIMUM_SUPPORTED_SECRETS } from "../../util/constants";
99

1010
export class SecretsAnalyzer extends BoundNodeVisitor {
1111
private exceededMaximum = false;
12-
private allSecrets = new Set<string>();
12+
private secrets = new Set<string>();
1313

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

3333
if (!this.exceededMaximum) {
3434
for (const secret of node.secrets) {
35-
this.allSecrets.add(secret.value);
36-
if (this.allSecrets.size > MAXIMUM_SUPPORTED_SECRETS) {
35+
this.secrets.add(secret.value);
36+
if (this.secrets.size > MAXIMUM_SUPPORTED_SECRETS) {
3737
this.bag.tooManySecrets(secret.syntax.range);
3838
this.exceededMaximum = true;
3939
break;

src/util/compilation.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { BoundDocument } from "../binding/bound-nodes";
1111
import { bindDocument } from "../binding/binder";
1212
import { SecretsAnalyzer } from "../binding/visitors/secrets-analyzer";
1313
import { ActionsAnalyzer } from "../binding/visitors/actions-analyzer";
14+
import { ResolvesAnalyzer } from "../binding/visitors/resolves-analyzer";
1415

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

27-
ActionsAnalyzer.analyze(this.document, this.bag);
28+
const actions = ActionsAnalyzer.analyze(this.document, this.bag);
29+
ResolvesAnalyzer.analyze(this.document, actions, this.bag);
2830
SecretsAnalyzer.analyze(this.document, this.bag);
2931
}
3032

src/util/diagnostics.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ export enum DiagnosticCode {
3333
DuplicateSecrets,
3434
TooManyActions,
3535
DuplicateActions,
36+
ActionDoesNotExist,
3637
}
3738

3839
export interface Diagnostic {
@@ -215,4 +216,12 @@ export class DiagnosticBag {
215216
message: `This file already defines another action with the name '${duplicate}'.`,
216217
});
217218
}
219+
220+
public actionDoesNotExist(action: string, range: TextRange): void {
221+
this.items.push({
222+
range,
223+
code: DiagnosticCode.ActionDoesNotExist,
224+
message: `The action '${action}' does not exist in the same workflow file.`,
225+
});
226+
}
218227
}

test/analysis-errors.spec.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,35 @@ ERROR: Too many actions defined. The maximum currently supported is '100'.
219219
| ^^^^^^^^^^^
220220
103 |
221221
"
222+
`);
223+
});
224+
225+
it("reports error on resolving a non-existing action", () => {
226+
expectDiagnostics(`
227+
action "a" {
228+
uses = "./ci"
229+
}
230+
action "b" {
231+
uses = "./ci"
232+
}
233+
workflow "c" {
234+
on = "fork"
235+
resolves = [
236+
"a",
237+
"b",
238+
"not_found"
239+
]
240+
}
241+
`).toMatchInlineSnapshot(`
242+
"
243+
ERROR: The action 'not_found' does not exist in the same workflow file.
244+
11 | \\"a\\",
245+
12 | \\"b\\",
246+
13 | \\"not_found\\"
247+
| ^^^^^^^^^^^
248+
14 | ]
249+
15 | }
250+
"
222251
`);
223252
});
224253
});

test/parsing-errors.spec.ts

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -89,28 +89,34 @@ ERROR: A token of kind 'string' or '{' or '[' was expected here.
8989

9090
it("reports errors on extra commas in a string array", () => {
9191
expectDiagnostics(`
92+
action "a" {
93+
uses = "./ci"
94+
}
95+
action "b" {
96+
uses = "./ci"
97+
}
9298
workflow "x" {
93-
on = "fork"
94-
resolves = [
95-
"a", "b", , , "c",
96-
]
99+
on = "fork"
100+
resolves = [
101+
, "a", , "b",
102+
]
97103
}
98104
`).toMatchInlineSnapshot(`
99105
"
100106
ERROR: A token of kind ',' was not expected here.
101-
3 | on = \\"fork\\"
102-
4 | resolves = [
103-
5 | \\"a\\", \\"b\\", , , \\"c\\",
104-
| ^
105-
6 | ]
106-
7 | }
107+
9 | on = \\"fork\\"
108+
10 | resolves = [
109+
11 | , \\"a\\", , \\"b\\",
110+
| ^
111+
12 | ]
112+
13 | }
107113
ERROR: A token of kind ',' was not expected here.
108-
3 | on = \\"fork\\"
109-
4 | resolves = [
110-
5 | \\"a\\", \\"b\\", , , \\"c\\",
111-
| ^
112-
6 | ]
113-
7 | }
114+
9 | on = \\"fork\\"
115+
10 | resolves = [
116+
11 | , \\"a\\", , \\"b\\",
117+
| ^
118+
12 | ]
119+
13 | }
114120
"
115121
`);
116122
});

0 commit comments

Comments
 (0)