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

Commit 5572f59

Browse files
committed
fix: report errors on duplicate actions or workflows (#44)
Fixes #41
1 parent a4e3a08 commit 5572f59

File tree

7 files changed

+145
-72
lines changed

7 files changed

+145
-72
lines changed

src/analysis/blocks-analyzer.ts

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/*!
2+
* Copyright 2019 Omar Tawfik. Please see LICENSE file at the root of this repository.
3+
*/
4+
5+
import { BoundNodeVisitor } from "../binding/bound-node-visitor";
6+
import { DiagnosticBag } from "../util/diagnostics";
7+
import { BoundDocument, BoundAction, BoundWorkflow } from "../binding/bound-nodes";
8+
import { MAXIMUM_SUPPORTED_ACTIONS } from "../util/constants";
9+
10+
export function analyzeBlocks(
11+
document: BoundDocument,
12+
bag: DiagnosticBag,
13+
): {
14+
workflows: ReadonlySet<string>;
15+
actions: ReadonlySet<string>;
16+
} {
17+
const instance = new BlocksAnalyzer(document, bag);
18+
return {
19+
workflows: instance.workflows,
20+
actions: instance.actions,
21+
};
22+
}
23+
24+
class BlocksAnalyzer extends BoundNodeVisitor {
25+
private readonly allWorkflows = new Set<string>();
26+
private readonly allActions = new Set<string>();
27+
private actionsExceededMaximum = false;
28+
29+
public constructor(document: BoundDocument, private readonly bag: DiagnosticBag) {
30+
super();
31+
this.visit(document);
32+
}
33+
34+
public get workflows(): ReadonlySet<string> {
35+
return this.allWorkflows;
36+
}
37+
38+
public get actions(): ReadonlySet<string> {
39+
return this.allActions;
40+
}
41+
42+
protected visitAction(node: BoundAction): void {
43+
if (this.allActions.has(node.name) || this.allWorkflows.has(node.name)) {
44+
this.bag.duplicateBlock(node.name, node.syntax.name.range);
45+
} else {
46+
this.allActions.add(node.name);
47+
}
48+
49+
if (!this.actionsExceededMaximum && this.allActions.size > MAXIMUM_SUPPORTED_ACTIONS) {
50+
this.bag.tooManyActions(node.syntax.name.range);
51+
this.actionsExceededMaximum = true;
52+
}
53+
54+
super.visitAction(node);
55+
}
56+
57+
protected visitWorkflow(node: BoundWorkflow): void {
58+
if (this.allActions.has(node.name) || this.allWorkflows.has(node.name)) {
59+
this.bag.duplicateBlock(node.name, node.syntax.name.range);
60+
} else {
61+
this.allWorkflows.add(node.name);
62+
}
63+
64+
super.visitWorkflow(node);
65+
}
66+
}

src/binding/visitors/resolves-analyzer.ts renamed to src/analysis/resolves-analyzer.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,16 @@
22
* Copyright 2019 Omar Tawfik. Please see LICENSE file at the root of this repository.
33
*/
44

5-
import { BoundNodeVisitor } from "../bound-node-visitor";
6-
import { DiagnosticBag } from "../../util/diagnostics";
7-
import { BoundDocument, BoundResolves } from "../bound-nodes";
5+
import { BoundNodeVisitor } from "../binding/bound-node-visitor";
6+
import { DiagnosticBag } from "../util/diagnostics";
7+
import { BoundDocument, BoundResolves } from "../binding/bound-nodes";
88

9-
export class ResolvesAnalyzer extends BoundNodeVisitor {
10-
private constructor(
9+
export function analyzeResolves(document: BoundDocument, actions: ReadonlySet<string>, bag: DiagnosticBag): void {
10+
new ResolvesAnalyzer(document, actions, bag);
11+
}
12+
13+
class ResolvesAnalyzer extends BoundNodeVisitor {
14+
public constructor(
1115
document: BoundDocument,
1216
private readonly actions: ReadonlySet<string>,
1317
private readonly bag: DiagnosticBag,
@@ -16,10 +20,6 @@ export class ResolvesAnalyzer extends BoundNodeVisitor {
1620
this.visit(document);
1721
}
1822

19-
public static analyze(document: BoundDocument, actions: ReadonlySet<string>, bag: DiagnosticBag): void {
20-
new ResolvesAnalyzer(document, actions, bag);
21-
}
22-
2323
protected visitResolves(node: BoundResolves): void {
2424
for (const action of node.actions) {
2525
if (!this.actions.has(action.value)) {

src/binding/visitors/secrets-analyzer.ts renamed to src/analysis/secrets-analyzer.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,24 @@
22
* Copyright 2019 Omar Tawfik. Please see LICENSE file at the root of this repository.
33
*/
44

5-
import { BoundNodeVisitor } from "../bound-node-visitor";
6-
import { DiagnosticBag } from "../../util/diagnostics";
7-
import { BoundSecrets, BoundDocument } from "../bound-nodes";
8-
import { MAXIMUM_SUPPORTED_SECRETS } from "../../util/constants";
5+
import { BoundNodeVisitor } from "../binding/bound-node-visitor";
6+
import { DiagnosticBag } from "../util/diagnostics";
7+
import { BoundSecrets, BoundDocument } from "../binding/bound-nodes";
8+
import { MAXIMUM_SUPPORTED_SECRETS } from "../util/constants";
99

10-
export class SecretsAnalyzer extends BoundNodeVisitor {
10+
export function analyzeSecrets(document: BoundDocument, bag: DiagnosticBag): void {
11+
new SecretsAnalyzer(document, bag);
12+
}
13+
14+
class SecretsAnalyzer extends BoundNodeVisitor {
1115
private exceededMaximum = false;
1216
private secrets = new Set<string>();
1317

14-
private constructor(document: BoundDocument, private readonly bag: DiagnosticBag) {
18+
public constructor(document: BoundDocument, private readonly bag: DiagnosticBag) {
1519
super();
1620
this.visit(document);
1721
}
1822

19-
public static analyze(document: BoundDocument, bag: DiagnosticBag): void {
20-
new SecretsAnalyzer(document, bag);
21-
}
22-
2323
protected visitSecrets(node: BoundSecrets): void {
2424
const localSecrets = new Set<string>();
2525
for (const secret of node.secrets) {

src/binding/visitors/actions-analyzer.ts

Lines changed: 0 additions & 38 deletions
This file was deleted.

src/util/compilation.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@ import { DocumentSyntax } from "../parsing/syntax-nodes";
99
import { parseTokens } from "../parsing/parser";
1010
import { BoundDocument } from "../binding/bound-nodes";
1111
import { bindDocument } from "../binding/binder";
12-
import { SecretsAnalyzer } from "../binding/visitors/secrets-analyzer";
13-
import { ActionsAnalyzer } from "../binding/visitors/actions-analyzer";
14-
import { ResolvesAnalyzer } from "../binding/visitors/resolves-analyzer";
12+
import { analyzeSecrets } from "../analysis/secrets-analyzer";
13+
import { analyzeBlocks } from "../analysis/blocks-analyzer";
14+
import { analyzeResolves } from "../analysis/resolves-analyzer";
1515

1616
export class Compilation {
1717
private readonly bag: DiagnosticBag;
@@ -21,13 +21,14 @@ export class Compilation {
2121

2222
public constructor(public readonly text: string) {
2323
this.bag = new DiagnosticBag();
24+
2425
this.tokens = scanText(text, this.bag);
2526
this.syntax = parseTokens(this.tokens, this.bag);
2627
this.document = bindDocument(this.syntax, this.bag);
2728

28-
const actions = ActionsAnalyzer.analyze(this.document, this.bag);
29-
ResolvesAnalyzer.analyze(this.document, actions, this.bag);
30-
SecretsAnalyzer.analyze(this.document, this.bag);
29+
const { actions } = analyzeBlocks(this.document, this.bag);
30+
analyzeResolves(this.document, actions, this.bag);
31+
analyzeSecrets(this.document, this.bag);
3132
}
3233

3334
public get diagnostics(): ReadonlyArray<Diagnostic> {

src/util/diagnostics.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ export enum DiagnosticCode {
3232
TooManySecrets,
3333
DuplicateSecrets,
3434
TooManyActions,
35-
DuplicateActions,
35+
DuplicateBlock,
3636
ActionDoesNotExist,
3737
}
3838

@@ -209,11 +209,11 @@ export class DiagnosticBag {
209209
});
210210
}
211211

212-
public duplicateActions(duplicate: string, range: TextRange): void {
212+
public duplicateBlock(duplicate: string, range: TextRange): void {
213213
this.items.push({
214214
range,
215-
code: DiagnosticCode.DuplicateActions,
216-
message: `This file already defines another action with the name '${duplicate}'.`,
215+
code: DiagnosticCode.DuplicateBlock,
216+
message: `This file already defines another workflow or action with the name '${duplicate}'.`,
217217
});
218218
}
219219

test/analysis-errors.spec.ts

Lines changed: 49 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ ERROR: This 'secrets' property has duplicate 'S1' secrets.
8080
`);
8181
});
8282

83-
it("reports errors on duplicate actions", () => {
83+
it("reports errors on two actions with the same name", () => {
8484
expectDiagnostics(`
8585
action "a" {
8686
uses = "./ci"
@@ -91,12 +91,9 @@ action "b" {
9191
action "a" {
9292
uses = "./ci"
9393
}
94-
action "d" {
95-
uses = "./ci"
96-
}
9794
`).toMatchInlineSnapshot(`
9895
"
99-
ERROR: This file already defines another action with the name 'a'.
96+
ERROR: This file already defines another workflow or action with the name 'a'.
10097
6 | uses = \\"./ci\\"
10198
7 | }
10299
8 | action \\"a\\" {
@@ -107,6 +104,53 @@ ERROR: This file already defines another action with the name 'a'.
107104
`);
108105
});
109106

107+
it("reports errors on two workflows with the same name", () => {
108+
expectDiagnostics(`
109+
workflow "a" {
110+
on = "fork"
111+
}
112+
workflow "b" {
113+
on = "fork"
114+
}
115+
workflow "a" {
116+
on = "fork"
117+
}
118+
`).toMatchInlineSnapshot(`
119+
"
120+
ERROR: This file already defines another workflow or action with the name 'a'.
121+
6 | on = \\"fork\\"
122+
7 | }
123+
8 | workflow \\"a\\" {
124+
| ^^^
125+
9 | on = \\"fork\\"
126+
10 | }
127+
"
128+
`);
129+
});
130+
131+
it("reports errors on a workflow and an action with the same name", () => {
132+
expectDiagnostics(`
133+
action "a" {
134+
uses = "./ci"
135+
}
136+
workflow "b" {
137+
on = "fork"
138+
}
139+
workflow "a" {
140+
on = "fork"
141+
}
142+
`).toMatchInlineSnapshot(`
143+
"
144+
ERROR: This file already defines another workflow or action with the name 'a'.
145+
1 |
146+
2 | action \\"a\\" {
147+
| ^^^
148+
3 | uses = \\"./ci\\"
149+
4 | }
150+
"
151+
`);
152+
});
153+
110154
it("reports errors on too many actions", () => {
111155
expectDiagnostics(`
112156
action "action001" { uses = "./ci" }

0 commit comments

Comments
 (0)