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

Commit

Permalink
fix: report errors on duplicate actions or workflows (#44)
Browse files Browse the repository at this point in the history
Fixes #41
  • Loading branch information
OmarTawfik committed Mar 12, 2019
1 parent a4e3a08 commit 5572f59
Show file tree
Hide file tree
Showing 7 changed files with 145 additions and 72 deletions.
66 changes: 66 additions & 0 deletions src/analysis/blocks-analyzer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*!
* Copyright 2019 Omar Tawfik. Please see LICENSE file at the root of this repository.
*/

import { BoundNodeVisitor } from "../binding/bound-node-visitor";
import { DiagnosticBag } from "../util/diagnostics";
import { BoundDocument, BoundAction, BoundWorkflow } from "../binding/bound-nodes";
import { MAXIMUM_SUPPORTED_ACTIONS } from "../util/constants";

export function analyzeBlocks(
document: BoundDocument,
bag: DiagnosticBag,
): {
workflows: ReadonlySet<string>;
actions: ReadonlySet<string>;
} {
const instance = new BlocksAnalyzer(document, bag);
return {
workflows: instance.workflows,
actions: instance.actions,
};
}

class BlocksAnalyzer extends BoundNodeVisitor {
private readonly allWorkflows = new Set<string>();
private readonly allActions = new Set<string>();
private actionsExceededMaximum = false;

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

public get workflows(): ReadonlySet<string> {
return this.allWorkflows;
}

public get actions(): ReadonlySet<string> {
return this.allActions;
}

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

if (!this.actionsExceededMaximum && this.allActions.size > MAXIMUM_SUPPORTED_ACTIONS) {
this.bag.tooManyActions(node.syntax.name.range);
this.actionsExceededMaximum = true;
}

super.visitAction(node);
}

protected visitWorkflow(node: BoundWorkflow): void {
if (this.allActions.has(node.name) || this.allWorkflows.has(node.name)) {
this.bag.duplicateBlock(node.name, node.syntax.name.range);
} else {
this.allWorkflows.add(node.name);
}

super.visitWorkflow(node);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,16 @@
* 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";
import { BoundNodeVisitor } from "../binding/bound-node-visitor";
import { DiagnosticBag } from "../util/diagnostics";
import { BoundDocument, BoundResolves } from "../binding/bound-nodes";

export class ResolvesAnalyzer extends BoundNodeVisitor {
private constructor(
export function analyzeResolves(document: BoundDocument, actions: ReadonlySet<string>, bag: DiagnosticBag): void {
new ResolvesAnalyzer(document, actions, bag);
}

class ResolvesAnalyzer extends BoundNodeVisitor {
public constructor(
document: BoundDocument,
private readonly actions: ReadonlySet<string>,
private readonly bag: DiagnosticBag,
Expand All @@ -16,10 +20,6 @@ export class ResolvesAnalyzer extends BoundNodeVisitor {
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)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,24 @@
* 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 { BoundSecrets, BoundDocument } from "../bound-nodes";
import { MAXIMUM_SUPPORTED_SECRETS } from "../../util/constants";
import { BoundNodeVisitor } from "../binding/bound-node-visitor";
import { DiagnosticBag } from "../util/diagnostics";
import { BoundSecrets, BoundDocument } from "../binding/bound-nodes";
import { MAXIMUM_SUPPORTED_SECRETS } from "../util/constants";

export class SecretsAnalyzer extends BoundNodeVisitor {
export function analyzeSecrets(document: BoundDocument, bag: DiagnosticBag): void {
new SecretsAnalyzer(document, bag);
}

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

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

public static analyze(document: BoundDocument, bag: DiagnosticBag): void {
new SecretsAnalyzer(document, bag);
}

protected visitSecrets(node: BoundSecrets): void {
const localSecrets = new Set<string>();
for (const secret of node.secrets) {
Expand Down
38 changes: 0 additions & 38 deletions src/binding/visitors/actions-analyzer.ts

This file was deleted.

13 changes: 7 additions & 6 deletions src/util/compilation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ import { DocumentSyntax } from "../parsing/syntax-nodes";
import { parseTokens } from "../parsing/parser";
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";
import { analyzeSecrets } from "../analysis/secrets-analyzer";
import { analyzeBlocks } from "../analysis/blocks-analyzer";
import { analyzeResolves } from "../analysis/resolves-analyzer";

export class Compilation {
private readonly bag: DiagnosticBag;
Expand All @@ -21,13 +21,14 @@ export class Compilation {

public constructor(public readonly text: string) {
this.bag = new DiagnosticBag();

this.tokens = scanText(text, this.bag);
this.syntax = parseTokens(this.tokens, this.bag);
this.document = bindDocument(this.syntax, this.bag);

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

public get diagnostics(): ReadonlyArray<Diagnostic> {
Expand Down
8 changes: 4 additions & 4 deletions src/util/diagnostics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export enum DiagnosticCode {
TooManySecrets,
DuplicateSecrets,
TooManyActions,
DuplicateActions,
DuplicateBlock,
ActionDoesNotExist,
}

Expand Down Expand Up @@ -209,11 +209,11 @@ export class DiagnosticBag {
});
}

public duplicateActions(duplicate: string, range: TextRange): void {
public duplicateBlock(duplicate: string, range: TextRange): void {
this.items.push({
range,
code: DiagnosticCode.DuplicateActions,
message: `This file already defines another action with the name '${duplicate}'.`,
code: DiagnosticCode.DuplicateBlock,
message: `This file already defines another workflow or action with the name '${duplicate}'.`,
});
}

Expand Down
54 changes: 49 additions & 5 deletions test/analysis-errors.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ ERROR: This 'secrets' property has duplicate 'S1' secrets.
`);
});

it("reports errors on duplicate actions", () => {
it("reports errors on two actions with the same name", () => {
expectDiagnostics(`
action "a" {
uses = "./ci"
Expand All @@ -91,12 +91,9 @@ action "b" {
action "a" {
uses = "./ci"
}
action "d" {
uses = "./ci"
}
`).toMatchInlineSnapshot(`
"
ERROR: This file already defines another action with the name 'a'.
ERROR: This file already defines another workflow or action with the name 'a'.
6 | uses = \\"./ci\\"
7 | }
8 | action \\"a\\" {
Expand All @@ -107,6 +104,53 @@ ERROR: This file already defines another action with the name 'a'.
`);
});

it("reports errors on two workflows with the same name", () => {
expectDiagnostics(`
workflow "a" {
on = "fork"
}
workflow "b" {
on = "fork"
}
workflow "a" {
on = "fork"
}
`).toMatchInlineSnapshot(`
"
ERROR: This file already defines another workflow or action with the name 'a'.
6 | on = \\"fork\\"
7 | }
8 | workflow \\"a\\" {
| ^^^
9 | on = \\"fork\\"
10 | }
"
`);
});

it("reports errors on a workflow and an action with the same name", () => {
expectDiagnostics(`
action "a" {
uses = "./ci"
}
workflow "b" {
on = "fork"
}
workflow "a" {
on = "fork"
}
`).toMatchInlineSnapshot(`
"
ERROR: This file already defines another workflow or action with the name 'a'.
1 |
2 | action \\"a\\" {
| ^^^
3 | uses = \\"./ci\\"
4 | }
"
`);
});

it("reports errors on too many actions", () => {
expectDiagnostics(`
action "action001" { uses = "./ci" }
Expand Down

0 comments on commit 5572f59

Please sign in to comment.