From 3df70b2491eb2c589d632d3be670d78971103d44 Mon Sep 17 00:00:00 2001 From: Josh Gross Date: Mon, 9 Jan 2023 19:01:50 -0500 Subject: [PATCH 1/2] Use a standard format configuration across packages --- .../.prettierignore => .prettierignore | 0 .../.prettierrc.json => .prettierrc.json | 0 actions-expressions/package.json | 4 +++- actions-languageserver/package.json | 8 ++++---- actions-languageservice/.prettierrc.json | 9 --------- actions-languageservice/package.json | 8 ++++---- actions-workflow-parser/.prettierignore | 3 --- actions-workflow-parser/.prettierrc.json | 3 --- actions-workflow-parser/package.json | 2 +- browser-playground/package.json | 4 +++- 10 files changed, 15 insertions(+), 26 deletions(-) rename actions-languageserver/.prettierignore => .prettierignore (100%) rename actions-languageserver/.prettierrc.json => .prettierrc.json (100%) delete mode 100644 actions-languageservice/.prettierrc.json delete mode 100644 actions-workflow-parser/.prettierignore delete mode 100644 actions-workflow-parser/.prettierrc.json diff --git a/actions-languageserver/.prettierignore b/.prettierignore similarity index 100% rename from actions-languageserver/.prettierignore rename to .prettierignore diff --git a/actions-languageserver/.prettierrc.json b/.prettierrc.json similarity index 100% rename from actions-languageserver/.prettierrc.json rename to .prettierrc.json diff --git a/actions-expressions/package.json b/actions-expressions/package.json index 9bf01e7..3c42246 100755 --- a/actions-expressions/package.json +++ b/actions-expressions/package.json @@ -29,6 +29,8 @@ "scripts": { "build": "tsc --build tsconfig.build.json", "clean": "rimraf dist", + "format": "prettier --write '**/*.ts'", + "format-check": "prettier --check '**/*.ts'", "prepublishOnly": "npm run build && npm run test", "test": "NODE_OPTIONS=\"--experimental-vm-modules\" jest", "test-watch": "NODE_OPTIONS=\"--experimental-vm-modules\" jest --watch", @@ -48,4 +50,4 @@ "ts-jest": "^29.0.3", "typescript": "^4.7.4" } -} +} \ No newline at end of file diff --git a/actions-languageserver/package.json b/actions-languageserver/package.json index 2271384..fb66594 100644 --- a/actions-languageserver/package.json +++ b/actions-languageserver/package.json @@ -30,12 +30,12 @@ "scripts": { "build": "tsc --build tsconfig.build.json", "clean": "rimraf dist", + "format": "prettier --write '**/*.ts'", + "format-check": "prettier --check '**/*.ts'", "prepublishOnly": "npm run build && npm run test", "test": "NODE_OPTIONS=\"--experimental-vm-modules\" jest", "test-watch": "NODE_OPTIONS=\"--experimental-vm-modules\" jest --watch", - "watch": "tsc --build tsconfig.build.json --watch", - "prettier": "prettier .", - "prettier-fix": "prettier --write ." + "watch": "tsc --build tsconfig.build.json --watch" }, "dependencies": { "@github/actions-languageservice": "^0.1.76", @@ -60,4 +60,4 @@ "ts-jest": "^29.0.3", "typescript": "^4.8.4" } -} +} \ No newline at end of file diff --git a/actions-languageservice/.prettierrc.json b/actions-languageservice/.prettierrc.json deleted file mode 100644 index e932aad..0000000 --- a/actions-languageservice/.prettierrc.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "tabWidth": 2, - "useTabs": false, - "printWidth": 120, - "singleQuote": false, - "bracketSpacing": false, - "trailingComma": "none", - "arrowParens": "avoid" -} diff --git a/actions-languageservice/package.json b/actions-languageservice/package.json index eeff346..5c45ea8 100644 --- a/actions-languageservice/package.json +++ b/actions-languageservice/package.json @@ -30,12 +30,12 @@ "scripts": { "build": "tsc --build tsconfig.build.json", "clean": "rimraf dist", + "format": "prettier --write '**/*.ts'", + "format-check": "prettier --check '**/*.ts'", "prepublishOnly": "npm run build && npm run test", "test": "NODE_OPTIONS=\"--experimental-vm-modules\" jest", "test-watch": "NODE_OPTIONS=\"--experimental-vm-modules\" jest --watch", - "watch": "tsc --build tsconfig.build.json --watch", - "prettier": "prettier .", - "prettier-fix": "prettier --write ." + "watch": "tsc --build tsconfig.build.json --watch" }, "dependencies": { "@github/actions-expressions": "^0.1.76", @@ -59,4 +59,4 @@ "ts-jest": "^29.0.3", "typescript": "^4.8.4" } -} +} \ No newline at end of file diff --git a/actions-workflow-parser/.prettierignore b/actions-workflow-parser/.prettierignore deleted file mode 100644 index d50eb11..0000000 --- a/actions-workflow-parser/.prettierignore +++ /dev/null @@ -1,3 +0,0 @@ -/dist -/node_modules -/src/workflows/workflow-v1.0.json diff --git a/actions-workflow-parser/.prettierrc.json b/actions-workflow-parser/.prettierrc.json deleted file mode 100644 index cce9d3c..0000000 --- a/actions-workflow-parser/.prettierrc.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "semi": false -} diff --git a/actions-workflow-parser/package.json b/actions-workflow-parser/package.json index dbe0c2f..34098ec 100644 --- a/actions-workflow-parser/package.json +++ b/actions-workflow-parser/package.json @@ -60,4 +60,4 @@ "ts-jest": "^29.0.3", "typescript": "^4.8.4" } -} +} \ No newline at end of file diff --git a/browser-playground/package.json b/browser-playground/package.json index f664383..6e9afa5 100644 --- a/browser-playground/package.json +++ b/browser-playground/package.json @@ -17,6 +17,8 @@ "scripts": { "build": "webpack --mode production", "clean": "rimraf dist", + "format": "prettier --write '**/*.ts'", + "format-check": "prettier --check '**/*.ts'", "start": "webpack-dev-server --mode development --open", "test": "exit 0", "watch": "webpack --watch --mode development --env esbuild" @@ -32,4 +34,4 @@ "webpack-cli": "^5.0.1", "webpack-dev-server": "^4.11.1" } -} +} \ No newline at end of file From c49981ec644491dbb331f32f1f54038cfce1edb2 Mon Sep 17 00:00:00 2001 From: Josh Gross Date: Mon, 9 Jan 2023 19:02:19 -0500 Subject: [PATCH 2/2] `npm run format -ws` --- actions-expressions/src/ast.ts | 4 +- actions-expressions/src/completion.test.ts | 73 +- actions-expressions/src/completion.ts | 33 +- actions-expressions/src/data/array.ts | 7 +- actions-expressions/src/data/boolean.ts | 2 +- .../src/data/dictionary.test.ts | 8 +- actions-expressions/src/data/dictionary.ts | 12 +- .../src/data/expressiondata.ts | 22 +- actions-expressions/src/data/index.ts | 18 +- actions-expressions/src/data/null.ts | 6 +- actions-expressions/src/data/number.ts | 2 +- actions-expressions/src/data/replacer.test.ts | 32 +- actions-expressions/src/data/replacer.ts | 12 +- actions-expressions/src/data/reviver.test.ts | 53 +- actions-expressions/src/data/reviver.ts | 18 +- actions-expressions/src/data/string.ts | 2 +- actions-expressions/src/errors.ts | 8 +- actions-expressions/src/evaluator.test.ts | 8 +- actions-expressions/src/evaluator.ts | 33 +- actions-expressions/src/funcs.ts | 35 +- actions-expressions/src/funcs/contains.ts | 8 +- actions-expressions/src/funcs/endswith.ts | 8 +- actions-expressions/src/funcs/format.test.ts | 8 +- actions-expressions/src/funcs/format.ts | 14 +- actions-expressions/src/funcs/fromjson.ts | 8 +- actions-expressions/src/funcs/info.ts | 2 +- actions-expressions/src/funcs/join.ts | 8 +- actions-expressions/src/funcs/startswith.ts | 8 +- actions-expressions/src/funcs/tojson.ts | 8 +- actions-expressions/src/idxHelper.ts | 2 +- actions-expressions/src/index.ts | 10 +- actions-expressions/src/lexer.test.ts | 99 ++- actions-expressions/src/lexer.ts | 57 +- actions-expressions/src/parser.ts | 75 +- actions-expressions/src/result.test.ts | 71 +- actions-expressions/src/result.ts | 15 +- actions-expressions/src/xlang.test.ts | 64 +- .../src/expressions.test.ts | 154 ++-- actions-workflow-parser/src/index.test.ts | 99 ++- actions-workflow-parser/src/index.ts | 10 +- .../src/model/convert.test.ts | 209 +++--- actions-workflow-parser/src/model/convert.ts | 74 +- .../src/model/converter/concurrency.ts | 43 +- .../src/model/converter/container.ts | 120 ++- .../src/model/converter/events.ts | 144 ++-- .../src/model/converter/handle-errors.ts | 17 +- .../src/model/converter/id-builder.test.ts | 147 ++-- .../src/model/converter/id-builder.ts | 72 +- .../src/model/converter/job/environment.ts | 32 +- .../src/model/converter/jobs.ts | 282 +++---- .../src/model/converter/steps.ts | 146 ++-- .../src/model/converter/string-list.ts | 13 +- .../src/model/converter/workflow-dispatch.ts | 103 +-- .../src/model/type-guards.ts | 6 +- .../src/model/workflow-template.ts | 212 +++--- .../src/templates/allowed-context.ts | 36 +- .../src/templates/evaluate-expression.ts | 210 ++---- .../src/templates/json-object-reader.ts | 155 ++-- .../src/templates/object-reader.ts | 16 +- .../src/templates/parse-event.ts | 12 +- .../templates/schema/boolean-definition.ts | 40 +- .../src/templates/schema/definition-info.ts | 56 +- .../src/templates/schema/definition-type.ts | 2 +- .../src/templates/schema/definition.ts | 75 +- .../src/templates/schema/index.ts | 2 +- .../templates/schema/mapping-definition.ts | 97 +-- .../src/templates/schema/null-definition.ts | 38 +- .../src/templates/schema/number-definition.ts | 40 +- .../src/templates/schema/one-of-definition.ts | 178 ++--- .../templates/schema/property-definition.ts | 45 +- .../src/templates/schema/scalar-definition.ts | 8 +- .../templates/schema/sequence-definition.ts | 50 +- .../src/templates/schema/string-definition.ts | 107 ++- .../src/templates/schema/template-schema.ts | 594 ++++++--------- .../src/templates/template-constants.ts | 96 +-- .../src/templates/template-context.ts | 156 ++-- .../src/templates/template-evaluator.ts | 362 ++++----- .../src/templates/template-reader.ts | 687 +++++++----------- .../src/templates/template-unraveler.ts | 661 +++++++---------- .../templates/template-validation-error.ts | 8 +- .../tokens/basic-expression-token.ts | 52 +- .../src/templates/tokens/boolean-token.ts | 22 +- .../src/templates/tokens/expression-token.ts | 35 +- .../src/templates/tokens/index.ts | 26 +- .../tokens/insert-expression-token.ts | 34 +- .../src/templates/tokens/key-value-pair.ts | 12 +- .../src/templates/tokens/literal-token.ts | 18 +- .../src/templates/tokens/mapping-token.ts | 51 +- .../src/templates/tokens/null-token.ts | 16 +- .../src/templates/tokens/number-token.ts | 22 +- .../src/templates/tokens/scalar-token.ts | 24 +- .../src/templates/tokens/sequence-token.ts | 38 +- .../src/templates/tokens/serialization.ts | 32 +- .../src/templates/tokens/string-token.ts | 38 +- .../templates/tokens/template-token.test.ts | 44 +- .../src/templates/tokens/template-token.ts | 132 ++-- .../src/templates/tokens/token-range.ts | 8 +- .../src/templates/tokens/traversal-state.ts | 72 +- .../src/templates/tokens/type-guards.ts | 36 +- .../src/templates/tokens/types.ts | 22 +- .../src/templates/trace-writer.ts | 6 +- .../src/test-utils/null-trace.ts | 10 +- actions-workflow-parser/src/workflows/file.ts | 4 +- .../src/workflows/workflow-constants.ts | 4 +- .../src/workflows/workflow-evaluator.ts | 45 +- .../src/workflows/workflow-parser.test.ts | 18 +- .../src/workflows/workflow-parser.ts | 58 +- .../src/workflows/workflow-schema.ts | 14 +- .../src/workflows/yaml-object-reader.test.ts | 109 ++- .../src/workflows/yaml-object-reader.ts | 205 +++--- actions-workflow-parser/src/xlang.test.ts | 101 ++- browser-playground/src/client/client.ts | 42 +- .../src/service-worker/service-worker.ts | 8 +- 113 files changed, 3252 insertions(+), 4573 deletions(-) diff --git a/actions-expressions/src/ast.ts b/actions-expressions/src/ast.ts index 84808e9..04a87b6 100644 --- a/actions-expressions/src/ast.ts +++ b/actions-expressions/src/ast.ts @@ -1,5 +1,5 @@ -import { ExpressionData } from "./data"; -import { Token } from "./lexer"; +import {ExpressionData} from "./data"; +import {Token} from "./lexer"; export interface ExprVisitor { visitLiteral(literal: Literal): R; diff --git a/actions-expressions/src/completion.test.ts b/actions-expressions/src/completion.test.ts index b95f839..810cd44 100644 --- a/actions-expressions/src/completion.test.ts +++ b/actions-expressions/src/completion.test.ts @@ -1,9 +1,9 @@ -import { complete, CompletionItem, trimTokenVector } from "./completion"; -import { BooleanData } from "./data/boolean"; -import { Dictionary } from "./data/dictionary"; -import { StringData } from "./data/string"; -import { FunctionInfo } from "./funcs/info"; -import { Lexer, TokenType } from "./lexer"; +import {complete, CompletionItem, trimTokenVector} from "./completion"; +import {BooleanData} from "./data/boolean"; +import {Dictionary} from "./data/dictionary"; +import {StringData} from "./data/string"; +import {FunctionInfo} from "./funcs/info"; +import {Lexer, TokenType} from "./lexer"; const testContext = new Dictionary( { @@ -11,24 +11,24 @@ const testContext = new Dictionary( value: new Dictionary( { key: "FOO", - value: new StringData(""), + value: new StringData("") }, { key: "BAR_TEST", - value: new StringData(""), + value: new StringData("") } - ), + ) }, { key: "secrets", value: new Dictionary({ key: "AWS_TOKEN", - value: new BooleanData(true), - }), + value: new BooleanData(true) + }) }, { key: "hashFiles(1,255)", - value: new Dictionary(), + value: new Dictionary() } ); @@ -38,17 +38,13 @@ const testComplete = (input: string): CompletionItem[] => { const pos = input.indexOf("|"); input = input.replace("|", ""); - const results = complete( - input.slice(0, pos >= 0 ? pos : input.length), - testContext, - testFunctions - ); + const results = complete(input.slice(0, pos >= 0 ? pos : input.length), testContext, testFunctions); return results; }; function completionItems(...labels: string[]): CompletionItem[] { - return labels.map((label) => ({ label, function: false })); + return labels.map(label => ({label, function: false})); } describe("auto-complete", () => { @@ -58,7 +54,7 @@ describe("auto-complete", () => { label: "toJson", description: "`toJSON(value)`\n\nReturns a pretty-print JSON representation of `value`. You can use this function to debug the information provided in contexts.", - function: true, + function: true }; expect(testComplete("to")).toContainEqual(expected); expect(testComplete("toJs")).toContainEqual(expected); @@ -69,7 +65,7 @@ describe("auto-complete", () => { it("removes parentheses from passed in function context", () => { expect(testComplete("|")).toContainEqual({ label: "hashFiles", - function: true, + function: true }); }); }); @@ -92,9 +88,7 @@ describe("auto-complete", () => { }); it("provides suggestions for contexts in function call", () => { - expect(testComplete("toJSON(env.|)")).toEqual( - completionItems("BAR_TEST", "FOO") - ); + expect(testComplete("toJSON(env.|)")).toEqual(completionItems("BAR_TEST", "FOO")); }); }); }); @@ -106,34 +100,19 @@ describe("trimTokenVector", () => { }>([ { input: "env.", - expected: [TokenType.IDENTIFIER, TokenType.DOT, TokenType.EOF], + expected: [TokenType.IDENTIFIER, TokenType.DOT, TokenType.EOF] }, { input: "github.act", - expected: [ - TokenType.IDENTIFIER, - TokenType.DOT, - TokenType.IDENTIFIER, - TokenType.EOF, - ], + expected: [TokenType.IDENTIFIER, TokenType.DOT, TokenType.IDENTIFIER, TokenType.EOF] }, { input: "1 == github.act", - expected: [ - TokenType.IDENTIFIER, - TokenType.DOT, - TokenType.IDENTIFIER, - TokenType.EOF, - ], + expected: [TokenType.IDENTIFIER, TokenType.DOT, TokenType.IDENTIFIER, TokenType.EOF] }, { input: "github.mona == github.act", - expected: [ - TokenType.IDENTIFIER, - TokenType.DOT, - TokenType.IDENTIFIER, - TokenType.EOF, - ], + expected: [TokenType.IDENTIFIER, TokenType.DOT, TokenType.IDENTIFIER, TokenType.EOF] }, { input: "github['test'].", @@ -143,14 +122,14 @@ describe("trimTokenVector", () => { TokenType.STRING, TokenType.RIGHT_BRACKET, TokenType.DOT, - TokenType.EOF, - ], - }, - ])("$input", ({ input, expected }) => { + TokenType.EOF + ] + } + ])("$input", ({input, expected}) => { const l = new Lexer(input); const lr = l.lex(); const tv = trimTokenVector(lr.tokens); - expect(tv.map((x) => x.type)).toEqual(expected); + expect(tv.map(x => x.type)).toEqual(expected); }); }); diff --git a/actions-expressions/src/completion.ts b/actions-expressions/src/completion.ts index 35b62ae..067f983 100644 --- a/actions-expressions/src/completion.ts +++ b/actions-expressions/src/completion.ts @@ -1,10 +1,10 @@ -import { Dictionary, isDictionary } from "./data/dictionary"; -import { ExpressionData } from "./data/expressiondata"; -import { Evaluator } from "./evaluator"; -import { wellKnownFunctions } from "./funcs"; -import { FunctionInfo } from "./funcs/info"; -import { Lexer, Token, TokenType } from "./lexer"; -import { Parser } from "./parser"; +import {Dictionary, isDictionary} from "./data/dictionary"; +import {ExpressionData} from "./data/expressiondata"; +import {Evaluator} from "./evaluator"; +import {wellKnownFunctions} from "./funcs"; +import {FunctionInfo} from "./funcs/info"; +import {Lexer, Token, TokenType} from "./lexer"; +import {Parser} from "./parser"; export type CompletionItem = { label: string; @@ -20,11 +20,7 @@ export type CompletionItem = { // - context.path.| or context.path['| -- auto-complete context access // - toJS| -- auto-complete function call or top-level // - | -- auto-complete function call or top-level context access -export function complete( - input: string, - context: Dictionary, - extensionFunctions: FunctionInfo[] -): CompletionItem[] { +export function complete(input: string, context: Dictionary, extensionFunctions: FunctionInfo[]): CompletionItem[] { // Lex const lexer = new Lexer(input); const lexResult = lexer.lex(); @@ -68,7 +64,7 @@ export function complete( const p = new Parser( pathTokenVector, - context.pairs().map((x) => x.key), + context.pairs().map(x => x.key), extensionFunctions ); const expr = p.parse(); @@ -82,14 +78,11 @@ export function complete( function functionItems(extensionFunctions: FunctionInfo[]): CompletionItem[] { const result: CompletionItem[] = []; - for (const fdef of [ - ...Object.values(wellKnownFunctions), - ...extensionFunctions, - ]) { + for (const fdef of [...Object.values(wellKnownFunctions), ...extensionFunctions]) { result.push({ label: fdef.name, description: fdef.description, - function: true, + function: true }); } @@ -104,7 +97,7 @@ function contextKeys(context: ExpressionData): CompletionItem[] { return ( context .pairs() - .map((x) => completionItemFromContext(x.key)) + .map(x => completionItemFromContext(x.key)) // Sort contexts .sort((a, b) => a.label.localeCompare(b.label)) ); @@ -119,7 +112,7 @@ function completionItemFromContext(context: string): CompletionItem { return { label: isFunc ? context.substring(0, parenIndex) : context, - function: isFunc, + function: isFunc }; } diff --git a/actions-expressions/src/data/array.ts b/actions-expressions/src/data/array.ts index d36fba7..91b1a7e 100644 --- a/actions-expressions/src/data/array.ts +++ b/actions-expressions/src/data/array.ts @@ -1,9 +1,4 @@ -import { - ExpressionData, - ExpressionDataInterface, - Kind, - kindStr, -} from "./expressiondata"; +import {ExpressionData, ExpressionDataInterface, Kind, kindStr} from "./expressiondata"; export class Array implements ExpressionDataInterface { private v: ExpressionData[] = []; diff --git a/actions-expressions/src/data/boolean.ts b/actions-expressions/src/data/boolean.ts index cc3db20..0a27fd6 100644 --- a/actions-expressions/src/data/boolean.ts +++ b/actions-expressions/src/data/boolean.ts @@ -1,4 +1,4 @@ -import { ExpressionDataInterface, Kind } from "./expressiondata"; +import {ExpressionDataInterface, Kind} from "./expressiondata"; export class BooleanData implements ExpressionDataInterface { constructor(public readonly value: boolean) {} diff --git a/actions-expressions/src/data/dictionary.test.ts b/actions-expressions/src/data/dictionary.test.ts index b8ae102..bd09bbb 100644 --- a/actions-expressions/src/data/dictionary.test.ts +++ b/actions-expressions/src/data/dictionary.test.ts @@ -1,12 +1,12 @@ -import { Dictionary } from "./dictionary"; -import { StringData } from "./string"; +import {Dictionary} from "./dictionary"; +import {StringData} from "./string"; describe("dictionary", () => { it("pairs contains all values", () => { const d = new Dictionary(); d.add("ABC", new StringData("val")); - expect(d.pairs()).toEqual([{ key: "ABC", value: new StringData("val") }]); + expect(d.pairs()).toEqual([{key: "ABC", value: new StringData("val")}]); }); it("does not add duplicate entries", () => { @@ -14,6 +14,6 @@ describe("dictionary", () => { d.add("ABC", new StringData("val1")); d.add("abc", new StringData("val2")); - expect(d.pairs()).toEqual([{ key: "ABC", value: new StringData("val1") }]); + expect(d.pairs()).toEqual([{key: "ABC", value: new StringData("val1")}]); }); }); diff --git a/actions-expressions/src/data/dictionary.ts b/actions-expressions/src/data/dictionary.ts index 6a69094..7a26d27 100644 --- a/actions-expressions/src/data/dictionary.ts +++ b/actions-expressions/src/data/dictionary.ts @@ -1,15 +1,9 @@ -import { - ExpressionData, - ExpressionDataInterface, - Kind, - kindStr, - Pair, -} from "./expressiondata"; +import {ExpressionData, ExpressionDataInterface, Kind, kindStr, Pair} from "./expressiondata"; export class Dictionary implements ExpressionDataInterface { private keys: string[] = []; private v: ExpressionData[] = []; - private indexMap: { [key: string]: number } = {}; + private indexMap: {[key: string]: number} = {}; constructor(...pairs: Pair[]) { for (const p of pairs) { @@ -56,7 +50,7 @@ export class Dictionary implements ExpressionDataInterface { const result: Pair[] = []; for (const key of this.keys) { - result.push({ key, value: this.v[this.indexMap[key.toLowerCase()]] }); + result.push({key, value: this.v[this.indexMap[key.toLowerCase()]]}); } return result; diff --git a/actions-expressions/src/data/expressiondata.ts b/actions-expressions/src/data/expressiondata.ts index e197acd..3f5e9e4 100644 --- a/actions-expressions/src/data/expressiondata.ts +++ b/actions-expressions/src/data/expressiondata.ts @@ -1,9 +1,9 @@ -import { Dictionary } from "./dictionary"; -import { Null } from "./null"; -import { Array } from "./array"; -import { StringData } from "./string"; -import { NumberData } from "./number"; -import { BooleanData } from "./boolean"; +import {Dictionary} from "./dictionary"; +import {Null} from "./null"; +import {Array} from "./array"; +import {StringData} from "./string"; +import {NumberData} from "./number"; +import {BooleanData} from "./boolean"; export enum Kind { String = 0, @@ -12,7 +12,7 @@ export enum Kind { Boolean, Number, CaseSensitiveDictionary, - Null, + Null } export function kindStr(k: Kind): string { @@ -43,13 +43,7 @@ export interface ExpressionDataInterface { number(): number; } -export type ExpressionData = - | Array - | Dictionary - | StringData - | BooleanData - | NumberData - | Null; +export type ExpressionData = Array | Dictionary | StringData | BooleanData | NumberData | Null; export type Pair = { key: string; diff --git a/actions-expressions/src/data/index.ts b/actions-expressions/src/data/index.ts index 9d81eca..1c9fae9 100644 --- a/actions-expressions/src/data/index.ts +++ b/actions-expressions/src/data/index.ts @@ -1,9 +1,9 @@ -export { Array } from "./array"; -export { BooleanData } from "./boolean"; -export { Dictionary } from "./dictionary"; -export { ExpressionData, Kind } from "./expressiondata"; -export { Null } from "./null"; -export { NumberData } from "./number"; -export { replacer } from "./replacer"; -export { reviver } from "./reviver"; -export { StringData } from "./string"; +export {Array} from "./array"; +export {BooleanData} from "./boolean"; +export {Dictionary} from "./dictionary"; +export {ExpressionData, Kind} from "./expressiondata"; +export {Null} from "./null"; +export {NumberData} from "./number"; +export {replacer} from "./replacer"; +export {reviver} from "./reviver"; +export {StringData} from "./string"; diff --git a/actions-expressions/src/data/null.ts b/actions-expressions/src/data/null.ts index 1430a21..78593df 100644 --- a/actions-expressions/src/data/null.ts +++ b/actions-expressions/src/data/null.ts @@ -1,8 +1,4 @@ -import { - ExpressionData, - ExpressionDataInterface, - Kind, -} from "./expressiondata"; +import {ExpressionData, ExpressionDataInterface, Kind} from "./expressiondata"; export class Null implements ExpressionDataInterface { constructor() {} diff --git a/actions-expressions/src/data/number.ts b/actions-expressions/src/data/number.ts index e809d4f..960e9c3 100644 --- a/actions-expressions/src/data/number.ts +++ b/actions-expressions/src/data/number.ts @@ -1,4 +1,4 @@ -import { ExpressionDataInterface, Kind } from "./expressiondata"; +import {ExpressionDataInterface, Kind} from "./expressiondata"; export class NumberData implements ExpressionDataInterface { constructor(public readonly value: number) {} diff --git a/actions-expressions/src/data/replacer.test.ts b/actions-expressions/src/data/replacer.test.ts index 80cf675..a7925f9 100644 --- a/actions-expressions/src/data/replacer.test.ts +++ b/actions-expressions/src/data/replacer.test.ts @@ -1,9 +1,9 @@ -import { Array } from "./array"; -import { Dictionary } from "./dictionary"; -import { Null } from "./null"; -import { NumberData } from "./number"; -import { replacer } from "./replacer"; -import { StringData } from "./string"; +import {Array} from "./array"; +import {Dictionary} from "./dictionary"; +import {Null} from "./null"; +import {NumberData} from "./number"; +import {replacer} from "./replacer"; +import {StringData} from "./string"; describe("replacer", () => { it("null", () => { @@ -11,22 +11,14 @@ describe("replacer", () => { }); it("array", () => { - expect( - JSON.stringify( - new Array(new StringData("a"), new StringData("b")), - replacer, - " " - ) - ).toEqual('[\n "a",\n "b"\n]'); + expect(JSON.stringify(new Array(new StringData("a"), new StringData("b")), replacer, " ")).toEqual( + '[\n "a",\n "b"\n]' + ); }); it("dictionary", () => { - expect( - JSON.stringify( - new Dictionary({ key: "a", value: new NumberData(42) }), - replacer, - " " - ) - ).toEqual('{\n "a": 42\n}'); + expect(JSON.stringify(new Dictionary({key: "a", value: new NumberData(42)}), replacer, " ")).toEqual( + '{\n "a": 42\n}' + ); }); }); diff --git a/actions-expressions/src/data/replacer.ts b/actions-expressions/src/data/replacer.ts index d2eb21d..4217952 100644 --- a/actions-expressions/src/data/replacer.ts +++ b/actions-expressions/src/data/replacer.ts @@ -1,9 +1,9 @@ -import { Array } from "./array"; -import { BooleanData } from "./boolean"; -import { Dictionary } from "./dictionary"; -import { Null } from "./null"; -import { NumberData } from "./number"; -import { StringData } from "./string"; +import {Array} from "./array"; +import {BooleanData} from "./boolean"; +import {Dictionary} from "./dictionary"; +import {Null} from "./null"; +import {NumberData} from "./number"; +import {StringData} from "./string"; /** * Replacer can be passed to JSON.stringify to convert an ExpressionData object into plain JSON diff --git a/actions-expressions/src/data/reviver.test.ts b/actions-expressions/src/data/reviver.test.ts index 93bd647..b69152d 100644 --- a/actions-expressions/src/data/reviver.test.ts +++ b/actions-expressions/src/data/reviver.test.ts @@ -1,11 +1,11 @@ -import { Array } from "./array"; -import { BooleanData } from "./boolean"; -import { Dictionary } from "./dictionary"; -import { ExpressionData } from "./expressiondata"; -import { Null } from "./null"; -import { NumberData } from "./number"; -import { reviver } from "./reviver"; -import { StringData } from "./string"; +import {Array} from "./array"; +import {BooleanData} from "./boolean"; +import {Dictionary} from "./dictionary"; +import {ExpressionData} from "./expressiondata"; +import {Null} from "./null"; +import {NumberData} from "./number"; +import {reviver} from "./reviver"; +import {StringData} from "./string"; describe("reviver", () => { const tests: { @@ -16,32 +16,32 @@ describe("reviver", () => { { name: "null", data: "null", - want: new Null(), + want: new Null() }, { name: "number", data: "1", - want: new NumberData(1), + want: new NumberData(1) }, { name: "string", data: `"a"`, - want: new StringData("a"), + want: new StringData("a") }, { name: "true boolean", data: "true", - want: new BooleanData(true), + want: new BooleanData(true) }, { name: "false boolean", data: "false", - want: new BooleanData(false), + want: new BooleanData(false) }, { name: "array", data: `[1,2,3]`, - want: new Array(new NumberData(1), new NumberData(2), new NumberData(3)), + want: new Array(new NumberData(1), new NumberData(2), new NumberData(3)) }, { name: "nested array", @@ -51,7 +51,7 @@ describe("reviver", () => { new NumberData(2), new Array(new NumberData(3), new NumberData(4)), new NumberData(5) - ), + ) }, { name: "complex array", @@ -59,26 +59,23 @@ describe("reviver", () => { want: new Array( new Dictionary({ key: "a", - value: new Array(new BooleanData(true), new NumberData(2)), + value: new Array(new BooleanData(true), new NumberData(2)) }), - new Dictionary({ key: "b", value: new StringData("three") }), - new Dictionary({ key: "c", value: new Null() }) - ), + new Dictionary({key: "b", value: new StringData("three")}), + new Dictionary({key: "c", value: new Null()}) + ) }, { name: "dictionary", data: `{ "object1": {} }`, want: new Dictionary({ key: "object1", - value: new Dictionary(), - }), - }, + value: new Dictionary() + }) + } ]; - test.each(tests)( - "$name", - ({ data, want }: { data: string; want: ExpressionData }) => { - expect(JSON.parse(data, reviver)).toEqual(want); - } - ); + test.each(tests)("$name", ({data, want}: {data: string; want: ExpressionData}) => { + expect(JSON.parse(data, reviver)).toEqual(want); + }); }); diff --git a/actions-expressions/src/data/reviver.ts b/actions-expressions/src/data/reviver.ts index d0e00b1..4d1168b 100644 --- a/actions-expressions/src/data/reviver.ts +++ b/actions-expressions/src/data/reviver.ts @@ -1,10 +1,10 @@ -import { Array as dArray } from "./array"; -import { BooleanData } from "./boolean"; -import { Dictionary } from "./dictionary"; -import { ExpressionData, Pair } from "./expressiondata"; -import { Null } from "./null"; -import { NumberData } from "./number"; -import { StringData } from "./string"; +import {Array as dArray} from "./array"; +import {BooleanData} from "./boolean"; +import {Dictionary} from "./dictionary"; +import {ExpressionData, Pair} from "./expressiondata"; +import {Null} from "./null"; +import {NumberData} from "./number"; +import {StringData} from "./string"; /** * Reviver can be passed to `JSON.parse` to convert plain JSON into an `ExpressionData` object. @@ -35,10 +35,10 @@ export function reviver(key: string, val: any): ExpressionData { if (typeof val === "object") { return new Dictionary( ...Object.keys(val).map( - (k) => + k => ({ key: k, - value: val[k], + value: val[k] } as Pair) ) ); diff --git a/actions-expressions/src/data/string.ts b/actions-expressions/src/data/string.ts index 2c4b457..7dd6457 100644 --- a/actions-expressions/src/data/string.ts +++ b/actions-expressions/src/data/string.ts @@ -1,4 +1,4 @@ -import { ExpressionDataInterface, Kind } from "./expressiondata"; +import {ExpressionDataInterface, Kind} from "./expressiondata"; export class StringData implements ExpressionDataInterface { constructor(public readonly value: string) {} diff --git a/actions-expressions/src/errors.ts b/actions-expressions/src/errors.ts index f3519ec..5edba0e 100644 --- a/actions-expressions/src/errors.ts +++ b/actions-expressions/src/errors.ts @@ -1,4 +1,4 @@ -import { Pos, Token, tokenString } from "./lexer"; +import {Pos, Token, tokenString} from "./lexer"; export const MAX_PARSER_DEPTH = 50; export const MAX_EXPRESSION_LENGTH = 21000; @@ -13,7 +13,7 @@ export enum ErrorType { ErrorTooFewParameters, ErrorTooManyParameters, ErrorUnrecognizedContext, - ErrorUnrecognizedFunction, + ErrorUnrecognizedFunction } export class ExpressionError extends Error { @@ -42,8 +42,8 @@ function errorDescription(typ: ErrorType): string { return "Too few parameters supplied"; case ErrorType.ErrorTooManyParameters: return "Too many parameters supplied"; - case ErrorType.ErrorUnrecognizedContext: - return "Unrecognized named-value"; + case ErrorType.ErrorUnrecognizedContext: + return "Unrecognized named-value"; case ErrorType.ErrorUnrecognizedFunction: return "Unrecognized function"; default: // Should never reach here. diff --git a/actions-expressions/src/evaluator.test.ts b/actions-expressions/src/evaluator.test.ts index c8d7e8d..cb282eb 100644 --- a/actions-expressions/src/evaluator.test.ts +++ b/actions-expressions/src/evaluator.test.ts @@ -1,7 +1,7 @@ import * as data from "./data"; -import { Evaluator } from "./evaluator"; -import { Lexer } from "./lexer"; -import { Parser } from "./parser"; +import {Evaluator} from "./evaluator"; +import {Lexer} from "./lexer"; +import {Parser} from "./parser"; test("evaluator", () => { const input = "foo['']"; @@ -18,7 +18,7 @@ test("evaluator", () => { expr, new data.Dictionary({ key: "foo", - value: new data.Dictionary({ key: "bar", value: new data.NumberData(42) }), + value: new data.Dictionary({key: "bar", value: new data.NumberData(42)}) }) ); const eresult = evaluator.evaluate(); diff --git a/actions-expressions/src/evaluator.ts b/actions-expressions/src/evaluator.ts index a20d36c..65556ff 100644 --- a/actions-expressions/src/evaluator.ts +++ b/actions-expressions/src/evaluator.ts @@ -9,14 +9,14 @@ import { Literal, Logical, Star, - Unary, + Unary } from "./ast"; import * as data from "./data"; -import { FilteredArray } from "./filtered_array"; -import { wellKnownFunctions } from "./funcs"; -import { idxHelper } from "./idxHelper"; -import { TokenType } from "./lexer"; -import { equals, falsy, greaterThan, lessThan, truthy } from "./result"; +import {FilteredArray} from "./filtered_array"; +import {wellKnownFunctions} from "./funcs"; +import {idxHelper} from "./idxHelper"; +import {TokenType} from "./lexer"; +import {equals, falsy, greaterThan, lessThan, truthy} from "./result"; export class EvaluationError extends Error {} @@ -60,9 +60,7 @@ export class Evaluator implements ExprVisitor { return new data.BooleanData(greaterThan(left, right)); case TokenType.GREATER_EQUAL: - return new data.BooleanData( - equals(left, right) || greaterThan(left, right) - ); + return new data.BooleanData(equals(left, right) || greaterThan(left, right)); case TokenType.LESS: return new data.BooleanData(lessThan(left, right)); @@ -150,25 +148,19 @@ export class Evaluator implements ExprVisitor { visitFunctionCall(functionCall: FunctionCall): data.ExpressionData { // Evaluate arguments - const args = functionCall.args.map((arg) => this.eval(arg)); + const args = functionCall.args.map(arg => this.eval(arg)); return fcall(functionCall, args); } } -function fcall( - fc: FunctionCall, - args: data.ExpressionData[] -): data.ExpressionData { +function fcall(fc: FunctionCall, args: data.ExpressionData[]): data.ExpressionData { const f = wellKnownFunctions[fc.functionName.lexeme.toLowerCase()]; return f.call(...args); } -function filteredArrayAccess( - fa: FilteredArray, - idx: idxHelper -): data.ExpressionData { +function filteredArrayAccess(fa: FilteredArray, idx: idxHelper): data.ExpressionData { const result = new FilteredArray(); for (const item of fa.values()) { @@ -225,10 +217,7 @@ function arrayAccess(a: data.Array, idx: idxHelper): data.ExpressionData { return new data.Null(); } -function objectAccess( - obj: data.Dictionary, - idx: idxHelper -): data.ExpressionData { +function objectAccess(obj: data.Dictionary, idx: idxHelper): data.ExpressionData { if (idx.star) { const fa = new FilteredArray(...obj.values()); return fa; diff --git a/actions-expressions/src/funcs.ts b/actions-expressions/src/funcs.ts index d25d7fa..082baf1 100644 --- a/actions-expressions/src/funcs.ts +++ b/actions-expressions/src/funcs.ts @@ -1,13 +1,13 @@ -import { ErrorType, ExpressionError } from "./errors"; -import { contains } from "./funcs/contains"; -import { endswith } from "./funcs/endswith"; -import { format } from "./funcs/format"; -import { fromjson } from "./funcs/fromjson"; -import { FunctionDefinition, FunctionInfo } from "./funcs/info"; -import { join } from "./funcs/join"; -import { startswith } from "./funcs/startswith"; -import { tojson } from "./funcs/tojson"; -import { Token } from "./lexer"; +import {ErrorType, ExpressionError} from "./errors"; +import {contains} from "./funcs/contains"; +import {endswith} from "./funcs/endswith"; +import {format} from "./funcs/format"; +import {fromjson} from "./funcs/fromjson"; +import {FunctionDefinition, FunctionInfo} from "./funcs/info"; +import {join} from "./funcs/join"; +import {startswith} from "./funcs/startswith"; +import {tojson} from "./funcs/tojson"; +import {Token} from "./lexer"; export type ParseContext = { allowUnknownKeywords: boolean; @@ -15,24 +15,20 @@ export type ParseContext = { extensionFunctions: Map; }; -export const wellKnownFunctions: { [name: string]: FunctionDefinition } = { +export const wellKnownFunctions: {[name: string]: FunctionDefinition} = { contains: contains, endswith: endswith, format: format, fromjson: fromjson, join: join, startswith: startswith, - tojson: tojson, + tojson: tojson }; // validateFunction returns the function definition for the given function name. // If the function does not exist or an incorrect number of arguments is provided, // an error is returned. -export function validateFunction( - context: ParseContext, - identifier: Token, - argCount: number -) { +export function validateFunction(context: ParseContext, identifier: Token, argCount: number) { // Expression function names are case-insensitive. const name = identifier.lexeme.toLowerCase(); @@ -42,10 +38,7 @@ export function validateFunction( f = context.extensionFunctions.get(name); if (!f) { if (!context.allowUnknownKeywords) { - throw new ExpressionError( - ErrorType.ErrorUnrecognizedFunction, - identifier - ); + throw new ExpressionError(ErrorType.ErrorUnrecognizedFunction, identifier); } // Skip argument validation for unknown functions diff --git a/actions-expressions/src/funcs/contains.ts b/actions-expressions/src/funcs/contains.ts index 377e447..d074d0f 100644 --- a/actions-expressions/src/funcs/contains.ts +++ b/actions-expressions/src/funcs/contains.ts @@ -1,6 +1,6 @@ -import { Array, BooleanData, ExpressionData, Kind } from "../data"; -import { equals } from "../result"; -import { FunctionDefinition } from "./info"; +import {Array, BooleanData, ExpressionData, Kind} from "../data"; +import {equals} from "../result"; +import {FunctionDefinition} from "./info"; export const contains: FunctionDefinition = { name: "contains", @@ -32,5 +32,5 @@ export const contains: FunctionDefinition = { } return new BooleanData(false); - }, + } }; diff --git a/actions-expressions/src/funcs/endswith.ts b/actions-expressions/src/funcs/endswith.ts index 23b8b98..21c095a 100644 --- a/actions-expressions/src/funcs/endswith.ts +++ b/actions-expressions/src/funcs/endswith.ts @@ -1,6 +1,6 @@ -import { BooleanData, ExpressionData } from "../data"; -import { toUpperSpecial } from "../result"; -import { FunctionDefinition } from "./info"; +import {BooleanData, ExpressionData} from "../data"; +import {toUpperSpecial} from "../result"; +import {FunctionDefinition} from "./info"; export const endswith: FunctionDefinition = { name: "endsWith", @@ -23,5 +23,5 @@ export const endswith: FunctionDefinition = { const rs = toUpperSpecial(right.coerceString()); return new BooleanData(ls.endsWith(rs)); - }, + } }; diff --git a/actions-expressions/src/funcs/format.test.ts b/actions-expressions/src/funcs/format.test.ts index 0dc3f04..d24d3fc 100644 --- a/actions-expressions/src/funcs/format.test.ts +++ b/actions-expressions/src/funcs/format.test.ts @@ -1,13 +1,11 @@ -import { Null, NumberData, StringData } from "../data"; -import { format } from "./format"; +import {Null, NumberData, StringData} from "../data"; +import {format} from "./format"; describe("format", () => { it("null", () => { expect(format.call(new StringData("{0}"), new Null())).toEqual(new StringData("")); }); it("number", () => { - expect(format.call(new StringData("{0}"), new NumberData(42))).toEqual( - new StringData("42") - ); + expect(format.call(new StringData("{0}"), new NumberData(42))).toEqual(new StringData("42")); }); }); diff --git a/actions-expressions/src/funcs/format.ts b/actions-expressions/src/funcs/format.ts index a387e64..8c5478c 100644 --- a/actions-expressions/src/funcs/format.ts +++ b/actions-expressions/src/funcs/format.ts @@ -1,5 +1,5 @@ -import { ExpressionData, StringData } from "../data"; -import { FunctionDefinition } from "./info"; +import {ExpressionData, StringData} from "../data"; +import {FunctionDefinition} from "./info"; export const format: FunctionDefinition = { name: "format", @@ -32,9 +32,7 @@ export const format: FunctionDefinition = { if (argIndex.success) { // Check parameter count if (1 + argIndex.result > args.length - 1) { - throw new Error( - `The following format string references more arguments than were supplied: ${fs}` - ); + throw new Error(`The following format string references more arguments than were supplied: ${fs}`); } // Append the portion before the left brace @@ -69,7 +67,7 @@ export const format: FunctionDefinition = { } return new StringData(result.join("")); - }, + } }; function safeCharAt(string: string, index: number): string { @@ -95,7 +93,7 @@ function readArgIndex(string: string, startIndex: number): ArgIndex { // Validate at least one digit if (length < 1) { return { - success: false, + success: false }; } @@ -105,7 +103,7 @@ function readArgIndex(string: string, startIndex: number): ArgIndex { return { success: !isNaN(result), result: result, - endIndex: endIndex, + endIndex: endIndex }; } diff --git a/actions-expressions/src/funcs/fromjson.ts b/actions-expressions/src/funcs/fromjson.ts index 2831f69..b014400 100644 --- a/actions-expressions/src/funcs/fromjson.ts +++ b/actions-expressions/src/funcs/fromjson.ts @@ -1,6 +1,6 @@ -import { ExpressionData } from "../data"; -import { reviver } from "../data/reviver"; -import { FunctionDefinition } from "./info"; +import {ExpressionData} from "../data"; +import {reviver} from "../data/reviver"; +import {FunctionDefinition} from "./info"; export const fromjson: FunctionDefinition = { name: "fromJson", @@ -18,5 +18,5 @@ export const fromjson: FunctionDefinition = { const d = JSON.parse(is, reviver); return d; - }, + } }; diff --git a/actions-expressions/src/funcs/info.ts b/actions-expressions/src/funcs/info.ts index 949ab2b..b77efbe 100644 --- a/actions-expressions/src/funcs/info.ts +++ b/actions-expressions/src/funcs/info.ts @@ -1,4 +1,4 @@ -import { ExpressionData } from "../data"; +import {ExpressionData} from "../data"; export interface FunctionInfo { name: string; diff --git a/actions-expressions/src/funcs/join.ts b/actions-expressions/src/funcs/join.ts index 7369e25..38bb92f 100644 --- a/actions-expressions/src/funcs/join.ts +++ b/actions-expressions/src/funcs/join.ts @@ -1,5 +1,5 @@ -import { Array, ExpressionData, Kind, StringData } from "../data"; -import { FunctionDefinition } from "./info"; +import {Array, ExpressionData, Kind, StringData} from "../data"; +import {FunctionDefinition} from "./info"; export const join: FunctionDefinition = { name: "join", @@ -25,11 +25,11 @@ export const join: FunctionDefinition = { return new StringData( (args[0] as Array) .values() - .map((item) => item.coerceString()) + .map(item => item.coerceString()) .join(separator) ); } return new StringData(""); - }, + } }; diff --git a/actions-expressions/src/funcs/startswith.ts b/actions-expressions/src/funcs/startswith.ts index d59058d..a45e3b4 100644 --- a/actions-expressions/src/funcs/startswith.ts +++ b/actions-expressions/src/funcs/startswith.ts @@ -1,6 +1,6 @@ -import { BooleanData, ExpressionData } from "../data"; -import { toUpperSpecial } from "../result"; -import { FunctionDefinition } from "./info"; +import {BooleanData, ExpressionData} from "../data"; +import {toUpperSpecial} from "../result"; +import {FunctionDefinition} from "./info"; export const startswith: FunctionDefinition = { name: "startsWith", @@ -23,5 +23,5 @@ export const startswith: FunctionDefinition = { const rs = toUpperSpecial(right.coerceString()); return new BooleanData(ls.startsWith(rs)); - }, + } }; diff --git a/actions-expressions/src/funcs/tojson.ts b/actions-expressions/src/funcs/tojson.ts index 75466e7..f59d52a 100644 --- a/actions-expressions/src/funcs/tojson.ts +++ b/actions-expressions/src/funcs/tojson.ts @@ -1,6 +1,6 @@ -import { ExpressionData, StringData } from "../data"; -import { replacer } from "../data/replacer"; -import { FunctionDefinition } from "./info"; +import {ExpressionData, StringData} from "../data"; +import {replacer} from "../data/replacer"; +import {FunctionDefinition} from "./info"; export const tojson: FunctionDefinition = { name: "toJson", @@ -10,5 +10,5 @@ export const tojson: FunctionDefinition = { maxArgs: 1, call: (...args: ExpressionData[]): ExpressionData => { return new StringData(JSON.stringify(args[0], replacer, " ")); - }, + } }; diff --git a/actions-expressions/src/idxHelper.ts b/actions-expressions/src/idxHelper.ts index 2ced087..a92fb9a 100644 --- a/actions-expressions/src/idxHelper.ts +++ b/actions-expressions/src/idxHelper.ts @@ -1,4 +1,4 @@ -import { ExpressionData } from "./data"; +import {ExpressionData} from "./data"; export class idxHelper { public readonly str: string | undefined; diff --git a/actions-expressions/src/index.ts b/actions-expressions/src/index.ts index 0d1e265..c49489c 100644 --- a/actions-expressions/src/index.ts +++ b/actions-expressions/src/index.ts @@ -1,6 +1,6 @@ -export { complete } from "./completion"; +export {complete} from "./completion"; export * as data from "./data"; -export { ExpressionError } from "./errors"; -export { Evaluator } from "./evaluator"; -export { Lexer } from "./lexer"; -export { Parser } from "./parser"; +export {ExpressionError} from "./errors"; +export {Evaluator} from "./evaluator"; +export {Lexer} from "./lexer"; +export {Parser} from "./parser"; diff --git a/actions-expressions/src/lexer.test.ts b/actions-expressions/src/lexer.test.ts index 4169e0f..749f721 100644 --- a/actions-expressions/src/lexer.test.ts +++ b/actions-expressions/src/lexer.test.ts @@ -1,4 +1,4 @@ -import { Lexer, Token, TokenType } from "./lexer"; +import {Lexer, Token, TokenType} from "./lexer"; describe("lexer", () => { const tests: { @@ -6,26 +6,26 @@ describe("lexer", () => { tokenType: TokenType[]; token?: Token; }[] = [ - { input: "<", tokenType: [TokenType.LESS] }, - { input: ">", tokenType: [TokenType.GREATER] }, + {input: "<", tokenType: [TokenType.LESS]}, + {input: ">", tokenType: [TokenType.GREATER]}, - { input: "!=", tokenType: [TokenType.BANG_EQUAL] }, - { input: "==", tokenType: [TokenType.EQUAL_EQUAL] }, - { input: "<=", tokenType: [TokenType.LESS_EQUAL] }, - { input: ">=", tokenType: [TokenType.GREATER_EQUAL] }, + {input: "!=", tokenType: [TokenType.BANG_EQUAL]}, + {input: "==", tokenType: [TokenType.EQUAL_EQUAL]}, + {input: "<=", tokenType: [TokenType.LESS_EQUAL]}, + {input: ">=", tokenType: [TokenType.GREATER_EQUAL]}, - { input: "&&", tokenType: [TokenType.AND] }, - { input: "||", tokenType: [TokenType.OR] }, + {input: "&&", tokenType: [TokenType.AND]}, + {input: "||", tokenType: [TokenType.OR]}, // Numbers - { input: "12", tokenType: [TokenType.NUMBER] }, - { input: "12.0", tokenType: [TokenType.NUMBER] }, - { input: "0", tokenType: [TokenType.NUMBER] }, - { input: "-0", tokenType: [TokenType.NUMBER] }, - { input: "-12.0", tokenType: [TokenType.NUMBER] }, + {input: "12", tokenType: [TokenType.NUMBER]}, + {input: "12.0", tokenType: [TokenType.NUMBER]}, + {input: "0", tokenType: [TokenType.NUMBER]}, + {input: "-0", tokenType: [TokenType.NUMBER]}, + {input: "-12.0", tokenType: [TokenType.NUMBER]}, // Strings - { input: "'It''s okay'", tokenType: [TokenType.STRING] }, + {input: "'It''s okay'", tokenType: [TokenType.STRING]}, { input: "format('{0} == ''queued''', needs)", tokenType: [ @@ -34,34 +34,28 @@ describe("lexer", () => { TokenType.STRING, TokenType.COMMA, TokenType.IDENTIFIER, - TokenType.RIGHT_PAREN, - ], + TokenType.RIGHT_PAREN + ] }, // Arrays { input: "[1,2]", - tokenType: [ - TokenType.LEFT_BRACKET, - TokenType.NUMBER, - TokenType.COMMA, - TokenType.NUMBER, - TokenType.RIGHT_BRACKET, - ], + tokenType: [TokenType.LEFT_BRACKET, TokenType.NUMBER, TokenType.COMMA, TokenType.NUMBER, TokenType.RIGHT_BRACKET] }, // Simple expressions { input: "1 == 2", - tokenType: [TokenType.NUMBER, TokenType.EQUAL_EQUAL, TokenType.NUMBER], + tokenType: [TokenType.NUMBER, TokenType.EQUAL_EQUAL, TokenType.NUMBER] }, { input: "1== 1", - tokenType: [TokenType.NUMBER, TokenType.EQUAL_EQUAL, TokenType.NUMBER], + tokenType: [TokenType.NUMBER, TokenType.EQUAL_EQUAL, TokenType.NUMBER] }, { input: "1< 1", - tokenType: [TokenType.NUMBER, TokenType.LESS, TokenType.NUMBER], + tokenType: [TokenType.NUMBER, TokenType.LESS, TokenType.NUMBER] }, // Identifiers @@ -73,9 +67,9 @@ describe("lexer", () => { lexeme: "github", pos: { line: 0, - column: 0, - }, - }, + column: 0 + } + } }, // Keywords @@ -87,9 +81,9 @@ describe("lexer", () => { lexeme: "true", pos: { line: 0, - column: 0, - }, - }, + column: 0 + } + } }, { @@ -100,9 +94,9 @@ describe("lexer", () => { lexeme: "false", pos: { line: 0, - column: 0, - }, - }, + column: 0 + } + } }, { @@ -113,32 +107,21 @@ describe("lexer", () => { lexeme: "null", pos: { line: 0, - column: 0, - }, - }, - }, + column: 0 + } + } + } ]; - test.each(tests)( - "$input", - ({ - input, - tokenType, - token, - }: { - input: string; - tokenType: TokenType[]; - token?: Token; - }) => { - const l = new Lexer(input); + test.each(tests)("$input", ({input, tokenType, token}: {input: string; tokenType: TokenType[]; token?: Token}) => { + const l = new Lexer(input); - const r = l.lex(); + const r = l.lex(); - const want = r.tokens.map((t) => t.type); + const want = r.tokens.map(t => t.type); - tokenType.push(TokenType.EOF); + tokenType.push(TokenType.EOF); - expect(want).toEqual(tokenType); - } - ); + expect(want).toEqual(tokenType); + }); }); diff --git a/actions-expressions/src/lexer.ts b/actions-expressions/src/lexer.ts index f838367..2d9c0fa 100644 --- a/actions-expressions/src/lexer.ts +++ b/actions-expressions/src/lexer.ts @@ -1,5 +1,5 @@ -import { StringData } from "./data"; -import { MAX_EXPRESSION_LENGTH } from "./errors"; +import {StringData} from "./data"; +import {MAX_EXPRESSION_LENGTH} from "./errors"; export enum TokenType { UNKNOWN, @@ -28,7 +28,7 @@ export enum TokenType { FALSE, NULL, - EOF, + EOF } export type Pos = { @@ -104,7 +104,6 @@ export class Lexer { this.previous() != TokenType.RIGHT_BRACKET && this.previous() != TokenType.RIGHT_PAREN && this.previous() != TokenType.STAR - ) { this.consumeNumber(); } else { @@ -118,9 +117,7 @@ export class Lexer { break; case "!": - this.addToken( - this.match("=") ? TokenType.BANG_EQUAL : TokenType.BANG - ); + this.addToken(this.match("=") ? TokenType.BANG_EQUAL : TokenType.BANG); break; case "=": @@ -134,15 +131,11 @@ export class Lexer { break; case "<": - this.addToken( - this.match("=") ? TokenType.LESS_EQUAL : TokenType.LESS - ); + this.addToken(this.match("=") ? TokenType.LESS_EQUAL : TokenType.LESS); break; case ">": - this.addToken( - this.match("=") ? TokenType.GREATER_EQUAL : TokenType.GREATER - ); + this.addToken(this.match("=") ? TokenType.GREATER_EQUAL : TokenType.GREATER); break; case "&": @@ -199,18 +192,18 @@ export class Lexer { this.tokens.push({ type: TokenType.EOF, lexeme: "", - pos: this.pos(), + pos: this.pos() }); return { - tokens: this.tokens, + tokens: this.tokens }; } private pos(): Pos { return { line: this.line, - column: this.start - this.lastLineOffset, + column: this.start - this.lastLineOffset }; } @@ -268,7 +261,7 @@ export class Lexer { type, lexeme: this.input.substring(this.start, this.offset), pos: this.pos(), - value, + value }); } @@ -282,9 +275,7 @@ export class Lexer { if (isNaN(value)) { throw new Error( - `Unexpected symbol: '${lexeme}'. Located at position ${ - this.start + 1 - } within expression: ${this.input}` + `Unexpected symbol: '${lexeme}'. Located at position ${this.start + 1} within expression: ${this.input}` ); } @@ -304,11 +295,9 @@ export class Lexer { if (this.atEnd()) { // Unterminated string throw new Error( - `Unexpected symbol: '${this.input.substring( - this.start - )}'. Located at position ${this.start + 1} within expression: ${ - this.input - }` + `Unexpected symbol: '${this.input.substring(this.start)}'. Located at position ${ + this.start + 1 + } within expression: ${this.input}` ); } @@ -360,9 +349,7 @@ export class Lexer { if (!isLegalIdentifier(lexeme)) { throw new Error( - `Unexpected symbol: '${lexeme}'. Located at position ${ - this.start + 1 - } within expression: ${this.input}` + `Unexpected symbol: '${lexeme}'. Located at position ${this.start + 1} within expression: ${this.input}` ); } @@ -400,19 +387,9 @@ function isLegalIdentifier(str: string): boolean { } const first = str[0]; - if ( - (first >= "a" && first <= "z") || - (first >= "A" && first <= "Z") || - first == "_" - ) { + if ((first >= "a" && first <= "z") || (first >= "A" && first <= "Z") || first == "_") { for (const c of str.substring(1).split("")) { - if ( - (c >= "a" && c <= "z") || - (c >= "A" && c <= "Z") || - (c >= "0" && c <= "9") || - c == "_" || - c == "-" - ) { + if ((c >= "a" && c <= "z") || (c >= "A" && c <= "Z") || (c >= "0" && c <= "9") || c == "_" || c == "-") { // OK } else { return false; diff --git a/actions-expressions/src/parser.ts b/actions-expressions/src/parser.ts index 8170931..ee4cf3a 100644 --- a/actions-expressions/src/parser.ts +++ b/actions-expressions/src/parser.ts @@ -1,20 +1,9 @@ -import { - Binary, - ContextAccess, - Expr, - FunctionCall, - Grouping, - IndexAccess, - Literal, - Logical, - Star, - Unary, -} from "./ast"; +import {Binary, ContextAccess, Expr, FunctionCall, Grouping, IndexAccess, Literal, Logical, Star, Unary} from "./ast"; import * as data from "./data"; -import { ErrorType, ExpressionError, MAX_PARSER_DEPTH } from "./errors"; -import { ParseContext, validateFunction } from "./funcs"; -import { FunctionInfo } from "./funcs/info"; -import { Token, TokenType } from "./lexer"; +import {ErrorType, ExpressionError, MAX_PARSER_DEPTH} from "./errors"; +import {ParseContext, validateFunction} from "./funcs"; +import {FunctionInfo} from "./funcs/info"; +import {Token, TokenType} from "./lexer"; export class Parser { private extContexts: Map; @@ -32,11 +21,7 @@ export class Parser { * @param extensionContexts Available context names * @param extensionFunctions Available functions (beyond the built-in ones) */ - constructor( - private tokens: Token[], - extensionContexts: string[], - extensionFunctions: FunctionInfo[] - ) { + constructor(private tokens: Token[], extensionContexts: string[], extensionFunctions: FunctionInfo[]) { this.extContexts = new Map(); this.extFuncs = new Map(); @@ -44,9 +29,9 @@ export class Parser { this.extContexts.set(contextName.toLowerCase(), true); } - for (const { name, func } of extensionFunctions.map((x) => ({ + for (const {name, func} of extensionFunctions.map(x => ({ name: x.name, - func: x, + func: x }))) { this.extFuncs.set(name.toLowerCase(), func); } @@ -54,7 +39,7 @@ export class Parser { this.context = { allowUnknownKeywords: false, extensionContexts: this.extContexts, - extensionFunctions: this.extFuncs, + extensionFunctions: this.extFuncs }; } @@ -65,7 +50,7 @@ export class Parser { if (this.atEnd()) { return result; } - + result = this.expression(); if (!this.atEnd()) { @@ -151,14 +136,7 @@ export class Parser { // ! is higher precedence than >, >=, <, <= let expr = this.unary(); - while ( - this.match( - TokenType.GREATER, - TokenType.GREATER_EQUAL, - TokenType.LESS, - TokenType.LESS_EQUAL - ) - ) { + while (this.match(TokenType.GREATER, TokenType.GREATER_EQUAL, TokenType.LESS, TokenType.LESS_EQUAL)) { const operator = this.previous(); const right = this.unary(); @@ -191,11 +169,7 @@ export class Parser { let depthIncreased = 0; - if ( - expr instanceof Grouping || - expr instanceof FunctionCall || - expr instanceof ContextAccess - ) { + if (expr instanceof Grouping || expr instanceof FunctionCall || expr instanceof ContextAccess) { let cont = true; while (cont) { switch (true) { @@ -207,10 +181,7 @@ export class Parser { indexExpr = this.expression(); } - this.consume( - TokenType.RIGHT_BRACKET, - ErrorType.ErrorUnexpectedSymbol - ); + this.consume(TokenType.RIGHT_BRACKET, ErrorType.ErrorUnexpectedSymbol); // Track depth this.incrDepth(); @@ -225,17 +196,11 @@ export class Parser { if (this.match(TokenType.IDENTIFIER)) { let property = this.previous(); - expr = new IndexAccess( - expr, - new Literal(new data.StringData(property.lexeme)) - ); + expr = new IndexAccess(expr, new Literal(new data.StringData(property.lexeme))); } else if (this.match(TokenType.STAR)) { expr = new IndexAccess(expr, new Star()); } else { - throw this.buildError( - ErrorType.ErrorUnexpectedSymbol, - this.peek() - ); + throw this.buildError(ErrorType.ErrorUnexpectedSymbol, this.peek()); } break; @@ -307,20 +272,14 @@ export class Parser { const expr = this.expression(); if (this.atEnd()) { - throw this.buildError( - ErrorType.ErrorUnexpectedEndOfExpression, - this.previous() - ); // Back up to get the last token before the EOF + throw this.buildError(ErrorType.ErrorUnexpectedEndOfExpression, this.previous()); // Back up to get the last token before the EOF } this.consume(TokenType.RIGHT_PAREN, ErrorType.ErrorUnexpectedSymbol); return new Grouping(expr); case this.atEnd(): - throw this.buildError( - ErrorType.ErrorUnexpectedEndOfExpression, - this.previous() - ); // Back up to get the last token before the EOF + throw this.buildError(ErrorType.ErrorUnexpectedEndOfExpression, this.previous()); // Back up to get the last token before the EOF } throw this.buildError(ErrorType.ErrorUnexpectedSymbol, this.peek()); diff --git a/actions-expressions/src/result.test.ts b/actions-expressions/src/result.test.ts index 2bc1d48..8c33eed 100644 --- a/actions-expressions/src/result.test.ts +++ b/actions-expressions/src/result.test.ts @@ -1,5 +1,5 @@ -import { BooleanData, ExpressionData, NumberData, StringData } from "./data"; -import { coerceTypes, toUpperSpecial } from "./result"; +import {BooleanData, ExpressionData, NumberData, StringData} from "./data"; +import {coerceTypes, toUpperSpecial} from "./result"; describe("coerceTypes", () => { const tests: { @@ -13,52 +13,52 @@ describe("coerceTypes", () => { }[] = [ { name: "number-bool", - data: { l: new NumberData(1), r: new BooleanData(true) }, + data: {l: new NumberData(1), r: new BooleanData(true)}, wantL: new NumberData(1), - wantR: new NumberData(1), + wantR: new NumberData(1) }, { name: "number-bool-false", - data: { l: new NumberData(1), r: new BooleanData(false) }, + data: {l: new NumberData(1), r: new BooleanData(false)}, wantL: new NumberData(1), - wantR: new NumberData(0), + wantR: new NumberData(0) }, { name: "bool-number-false", - data: { l: new BooleanData(false), r: new NumberData(1) }, + data: {l: new BooleanData(false), r: new NumberData(1)}, wantL: new NumberData(0), - wantR: new NumberData(1), + wantR: new NumberData(1) }, { name: "number-number", - data: { l: new NumberData(1), r: new NumberData(2) }, + data: {l: new NumberData(1), r: new NumberData(2)}, wantL: new NumberData(1), - wantR: new NumberData(2), + wantR: new NumberData(2) }, { name: "string-string", - data: { l: new StringData("a"), r: new StringData("b") }, + data: {l: new StringData("a"), r: new StringData("b")}, wantL: new StringData("a"), - wantR: new StringData("b"), + wantR: new StringData("b") }, { name: "string-number", - data: { l: new StringData("a"), r: new NumberData(1) }, + data: {l: new StringData("a"), r: new NumberData(1)}, wantL: new NumberData(NaN), - wantR: new NumberData(1), + wantR: new NumberData(1) }, { name: "number-string", - data: { l: new NumberData(1), r: new StringData("a") }, + data: {l: new NumberData(1), r: new StringData("a")}, wantL: new NumberData(1), - wantR: new NumberData(NaN), + wantR: new NumberData(NaN) }, { name: "bool-bool", - data: { l: new BooleanData(false), r: new BooleanData(true) }, + data: {l: new BooleanData(false), r: new BooleanData(true)}, wantL: new BooleanData(false), - wantR: new BooleanData(true), - }, + wantR: new BooleanData(true) + } ]; test.each(tests)( @@ -66,9 +66,9 @@ describe("coerceTypes", () => { ({ data, wantL, - wantR, + wantR }: { - data: { l: ExpressionData; r: ExpressionData }; + data: {l: ExpressionData; r: ExpressionData}; wantL: ExpressionData; wantR: ExpressionData; }) => { @@ -80,22 +80,19 @@ describe("coerceTypes", () => { }); describe("toUpperSpecial", () => { - const tests: { input: string; want: string }[] = [ - { input: "", want: "" }, - { input: "abc", want: "ABC" }, - { input: "ıabc", want: "ıABC" }, - { input: "ııabc", want: "ııABC" }, - { input: "abcı", want: "ABCı" }, - { input: "abcıı", want: "ABCıı" }, - { input: "abcıdef", want: "ABCıDEF" }, - { input: "abcııdef", want: "ABCııDEF" }, - { input: "abcıdefıghi", want: "ABCıDEFıGHI" }, + const tests: {input: string; want: string}[] = [ + {input: "", want: ""}, + {input: "abc", want: "ABC"}, + {input: "ıabc", want: "ıABC"}, + {input: "ııabc", want: "ııABC"}, + {input: "abcı", want: "ABCı"}, + {input: "abcıı", want: "ABCıı"}, + {input: "abcıdef", want: "ABCıDEF"}, + {input: "abcııdef", want: "ABCııDEF"}, + {input: "abcıdefıghi", want: "ABCıDEFıGHI"} ]; - test.each(tests)( - "$input", - ({ input, want }: { input: string; want: string }) => { - expect(toUpperSpecial(input)).toEqual(want); - } - ); + test.each(tests)("$input", ({input, want}: {input: string; want: string}) => { + expect(toUpperSpecial(input)).toEqual(want); + }); }); diff --git a/actions-expressions/src/result.ts b/actions-expressions/src/result.ts index 06b65ef..4c5d1c7 100644 --- a/actions-expressions/src/result.ts +++ b/actions-expressions/src/result.ts @@ -76,10 +76,7 @@ export function coerceTypes( // Similar to the Javascript abstract equality comparison algorithm http://www.ecma-international.org/ecma-262/5.1/#sec-11.9.3. // Except string comparison is OrdinalIgnoreCase, and objects are not coerced to primitives. -export function equals( - lhs: data.ExpressionData, - rhs: data.ExpressionData -): boolean { +export function equals(lhs: data.ExpressionData, rhs: data.ExpressionData): boolean { let [lv, rv] = coerceTypes(lhs, rhs); if (lv.kind != rv.kind) { @@ -125,10 +122,7 @@ export function equals( // Similar to the Javascript abstract equality comparison algorithm http://www.ecma-international.org/ecma-262/5.1/#sec-11.9.3. // Except string comparison is OrdinalIgnoreCase, and objects are not coerced to primitives. -export function greaterThan( - lhs: data.ExpressionData, - rhs: data.ExpressionData -): boolean { +export function greaterThan(lhs: data.ExpressionData, rhs: data.ExpressionData): boolean { const [lv, rv] = coerceTypes(lhs, rhs); if (lv.kind != rv.kind) { @@ -166,10 +160,7 @@ export function greaterThan( // Similar to the Javascript abstract equality comparison algorithm http://www.ecma-international.org/ecma-262/5.1/#sec-11.9.3. // Except string comparison is OrdinalIgnoreCase, and objects are not coerced to primitives. -export function lessThan( - lhs: data.ExpressionData, - rhs: data.ExpressionData -): boolean { +export function lessThan(lhs: data.ExpressionData, rhs: data.ExpressionData): boolean { const [lv, rv] = coerceTypes(lhs, rhs); if (lv.kind != rv.kind) { diff --git a/actions-expressions/src/xlang.test.ts b/actions-expressions/src/xlang.test.ts index 6d89651..4d67be7 100644 --- a/actions-expressions/src/xlang.test.ts +++ b/actions-expressions/src/xlang.test.ts @@ -1,14 +1,14 @@ import * as fs from "fs"; import * as path from "path"; -import { Expr } from "./ast"; +import {Expr} from "./ast"; import * as data from "./data"; -import { kindStr } from "./data/expressiondata"; -import { replacer } from "./data/replacer"; -import { reviver } from "./data/reviver"; -import { ExpressionError } from "./errors"; -import { Evaluator } from "./evaluator"; -import { Lexer, Result } from "./lexer"; -import { Parser } from "./parser"; +import {kindStr} from "./data/expressiondata"; +import {replacer} from "./data/replacer"; +import {reviver} from "./data/reviver"; +import {ExpressionError} from "./errors"; +import {Evaluator} from "./evaluator"; +import {Lexer, Result} from "./lexer"; +import {Parser} from "./parser"; interface TestResult { value: data.ExpressionData; @@ -18,11 +18,11 @@ interface TestResult { enum TestErrorKind { Lexing = "lexing", Parsing = "parsing", - Evaluation = "evaluation", + Evaluation = "evaluation" } enum SkipOptionKind { - Typescript = "typescript", + Typescript = "typescript" } interface TestOptions { @@ -83,9 +83,7 @@ describe("x-lang tests", () => { let tests = testCases[testCaseName]; // Filter out tests that are not supported by typescript - tests = tests.filter( - (t) => !t.options?.skip?.includes(SkipOptionKind.Typescript) - ); + tests = tests.filter(t => !t.options?.skip?.includes(SkipOptionKind.Typescript)); const testName = path.basename(file, ".json"); @@ -94,7 +92,7 @@ describe("x-lang tests", () => { } describe(testName, () => { - test.each(tests)(`${testName}: ${testCaseName} ($expr)`, (testCase) => { + test.each(tests)(`${testName}: ${testCaseName} ($expr)`, testCase => { if (!testCase.contexts) { testCase.contexts = new data.Dictionary(); } @@ -114,37 +112,29 @@ describe("x-lang tests", () => { return; } - throw new Error( - `unexpected error lexing expression: ${e.message} ${e.stack}` - ); + throw new Error(`unexpected error lexing expression: ${e.message} ${e.stack}`); } // Parse - const contextNames = testCase.contexts.pairs().map((x) => x.key); + const contextNames = testCase.contexts.pairs().map(x => x.key); const parser = new Parser(result.tokens, contextNames, []); let expr: Expr; try { expr = parser.parse(); if (testCase.err?.kind === TestErrorKind.Parsing) { - throw new Error( - "expected error parsing expression, but got none" - ); + throw new Error("expected error parsing expression, but got none"); } } catch (e: any) { // Did test expect parsing error? if (testCase.err?.kind === TestErrorKind.Parsing) { // Test expects parsing error const pe = e as ExpressionError; - expect(errorWithExpression(pe, testCase.expr)).toContain( - testCase.err.value - ); + expect(errorWithExpression(pe, testCase.expr)).toContain(testCase.err.value); return; } - throw new Error( - `unexpected error parsing expression: ${e.message} ${e.stack}` - ); + throw new Error(`unexpected error parsing expression: ${e.message} ${e.stack}`); } // Evaluate expression @@ -158,28 +148,20 @@ describe("x-lang tests", () => { } if (testCase.err?.kind === TestErrorKind.Evaluation) { - throw new Error( - "expected error evaluating expression, but got none" - ); + throw new Error("expected error evaluating expression, but got none"); } expect(kindStr(result.kind)).toBe(testCase.result.kind); - expect(JSON.stringify(result, replacer)).toEqual( - JSON.stringify(testCase.result.value, replacer) - ); + expect(JSON.stringify(result, replacer)).toEqual(JSON.stringify(testCase.result.value, replacer)); } catch (e: any) { if (testCase.err?.kind === TestErrorKind.Evaluation) { const pe = e as ExpressionError; - expect(errorWithExpression(pe, testCase.expr)).toContain( - testCase.err.value - ); + expect(errorWithExpression(pe, testCase.expr)).toContain(testCase.err.value); return; } - throw new Error( - `unexpected error evaluating expression: ${e.message} ${e.stack}` - ); + throw new Error(`unexpected error evaluating expression: ${e.message} ${e.stack}`); } }); }); @@ -189,9 +171,7 @@ describe("x-lang tests", () => { function errorWithExpression(e: ExpressionError, expr: string): string { if (e.pos !== undefined) { - return `${e.message}. Located at position ${ - e.pos.column + 1 - } within expression: ${expr}`; + return `${e.message}. Located at position ${e.pos.column + 1} within expression: ${expr}`; } return `${e.message}. Located within expression: ${expr}`; diff --git a/actions-workflow-parser/src/expressions.test.ts b/actions-workflow-parser/src/expressions.test.ts index e306d99..f45eaea 100644 --- a/actions-workflow-parser/src/expressions.test.ts +++ b/actions-workflow-parser/src/expressions.test.ts @@ -1,7 +1,7 @@ -import { StringToken } from "./templates/tokens" -import { isBasicExpression, isString } from "./templates/tokens/type-guards" -import { nullTrace } from "./test-utils/null-trace" -import { parseWorkflow } from "./workflows/workflow-parser" +import {StringToken} from "./templates/tokens"; +import {isBasicExpression, isString} from "./templates/tokens/type-guards"; +import {nullTrace} from "./test-utils/null-trace"; +import {parseWorkflow} from "./workflows/workflow-parser"; describe("Workflow Expression Parsing", () => { it("preserves original expressions when building format", () => { @@ -16,33 +16,31 @@ jobs: build: runs-on: ubuntu-latest steps: - - run: echo 'hello'`, - }, + - run: echo 'hello'` + } ], nullTrace - ) + ); - expect(result.context.errors.getErrors()).toHaveLength(0) + expect(result.context.errors.getErrors()).toHaveLength(0); - const run = result.value!.assertMapping("run")! - const runNameMapping = run.get(1)! - expect(runNameMapping?.key?.assertString("run-name key").value).toBe( - "run-name" - ) + const run = result.value!.assertMapping("run")!; + const runNameMapping = run.get(1)!; + expect(runNameMapping?.key?.assertString("run-name key").value).toBe("run-name"); - const v = runNameMapping.value! - expect(v).not.toBeUndefined() + const v = runNameMapping.value!; + expect(v).not.toBeUndefined(); if (!isBasicExpression(v)) { - throw new Error("expected run-name to be a basic expression") + throw new Error("expected run-name to be a basic expression"); } - expect(v.originalExpressions).toHaveLength(2) - expect(v.originalExpressions!.map((x) => x.toDisplayString())).toEqual([ + expect(v.originalExpressions).toHaveLength(2); + expect(v.originalExpressions!.map(x => x.toDisplayString())).toEqual([ "${{ github.event_name }}", - "${{ github.ref }}", - ]) - }) + "${{ github.ref }}" + ]); + }); it("preserves original expressions when building format for multi-line strings", () => { const result = parseWorkflow( @@ -57,54 +55,49 @@ jobs: steps: - run: | echo \${{ github.event_name }} - echo 'hello' \${{ github.ref }}`, - }, + echo 'hello' \${{ github.ref }}` + } ], nullTrace - ) + ); - expect(result.context.errors.getErrors()).toHaveLength(0) + expect(result.context.errors.getErrors()).toHaveLength(0); - const run = result.value!.assertMapping("run")! - const jobsMapping = run.get(1)! - expect(jobsMapping?.key?.assertString("jobs").value).toBe("jobs") + const run = result.value!.assertMapping("run")!; + const jobsMapping = run.get(1)!; + expect(jobsMapping?.key?.assertString("jobs").value).toBe("jobs"); - const job = jobsMapping - .value!.assertMapping("jobs")! - .get(0)! - .value!.assertMapping("job") + const job = jobsMapping.value!.assertMapping("jobs")!.get(0)!.value!.assertMapping("job"); const stepRun = job .get(1) .value!.assertSequence("steps") .get(0) .assertMapping("step") .get(0) - .value!.assertScalar("step-run") + .value!.assertScalar("step-run"); if (!isBasicExpression(stepRun)) { - throw new Error("expected run-name to be a basic expression") + throw new Error("expected run-name to be a basic expression"); } - expect(stepRun.originalExpressions).toHaveLength(2) - expect( - stepRun.originalExpressions!.map((x) => [x.toDisplayString(), x.range]) - ).toEqual([ + expect(stepRun.originalExpressions).toHaveLength(2); + expect(stepRun.originalExpressions!.map(x => [x.toDisplayString(), x.range])).toEqual([ [ "${{ github.event_name }}", { start: [7, 16], - end: [7, 40], - }, + end: [7, 40] + } ], [ "${{ github.ref }}", { start: [8, 24], - end: [8, 41], - }, - ], - ]) - }) + end: [8, 41] + } + ] + ]); + }); it("return errors and string token with preserved expressions for (multiple) expression errors", () => { const result = parseWorkflow( @@ -119,33 +112,30 @@ jobs: steps: - run: | echo \${{ abc }} - echo 'hello' \${{ gith }}`, - }, + echo 'hello' \${{ gith }}` + } ], nullTrace - ) + ); - expect(result.context.errors.getErrors()).toHaveLength(2) + expect(result.context.errors.getErrors()).toHaveLength(2); - const run = result.value!.assertMapping("run")! - const jobsMapping = run.get(1)! - expect(jobsMapping?.key?.assertString("jobs").value).toBe("jobs") + const run = result.value!.assertMapping("run")!; + const jobsMapping = run.get(1)!; + expect(jobsMapping?.key?.assertString("jobs").value).toBe("jobs"); - const job = jobsMapping - .value!.assertMapping("jobs")! - .get(0)! - .value!.assertMapping("job") + const job = jobsMapping.value!.assertMapping("jobs")!.get(0)!.value!.assertMapping("job"); const stepRun = job .get(1) .value!.assertSequence("steps") .get(0) .assertMapping("step") .get(0) - .value!.assertScalar("step-run") + .value!.assertScalar("step-run"); - expect(isString(stepRun)).toBe(true) - expect((stepRun as StringToken).value).toContain("${{") - }) + expect(isString(stepRun)).toBe(true); + expect((stepRun as StringToken).value).toContain("${{"); + }); it("reports all errors for multi-line expressions at the correct locations", () => { const result = parseWorkflow( @@ -160,31 +150,31 @@ jobs: steps: - run: | echo \${{ fromJSON2('test') }} - echo 'hello' \${{ toJSON2(inputs.test) }}`, - }, + echo 'hello' \${{ toJSON2(inputs.test) }}` + } ], nullTrace - ) + ); expect(result.context.errors.getErrors()).toEqual([ { prefix: "test.yaml (Line: 6, Col: 14)", range: { start: [7, 16], - end: [7, 40], + end: [7, 40] }, - rawMessage: "Unrecognized function: 'fromJSON2'", + rawMessage: "Unrecognized function: 'fromJSON2'" }, { prefix: "test.yaml (Line: 6, Col: 14)", range: { start: [8, 24], - end: [8, 51], + end: [8, 51] }, - rawMessage: "Unrecognized function: 'toJSON2'", - }, - ]) - }) + rawMessage: "Unrecognized function: 'toJSON2'" + } + ]); + }); it("parses isExpression strings into expression tokens", () => { const result = parseWorkflow( @@ -198,22 +188,22 @@ jobs: runs-on: ubuntu-latest if: github.event_name == 'push' steps: - - run: echo 'hello'`, - }, + - run: echo 'hello'` + } ], nullTrace - ) + ); - expect(result.context.errors.getErrors()).toHaveLength(0) + expect(result.context.errors.getErrors()).toHaveLength(0); - const workflowRoot = result.value!.assertMapping("root")! - const jobs = workflowRoot.get(1).value.assertMapping("jobs") - const build = jobs.get(0).value.assertMapping("job") - const ifToken = build.get(1).value - expect(ifToken.toString()).toEqual("${{ github.event_name == 'push' }}") + const workflowRoot = result.value!.assertMapping("root")!; + const jobs = workflowRoot.get(1).value.assertMapping("jobs"); + const build = jobs.get(0).value.assertMapping("job"); + const ifToken = build.get(1).value; + expect(ifToken.toString()).toEqual("${{ github.event_name == 'push' }}"); if (!isBasicExpression(ifToken)) { - throw new Error("expected if to be a basic expression") + throw new Error("expected if to be a basic expression"); } - }) -}) + }); +}); diff --git a/actions-workflow-parser/src/index.test.ts b/actions-workflow-parser/src/index.test.ts index 4443f41..d94034c 100644 --- a/actions-workflow-parser/src/index.test.ts +++ b/actions-workflow-parser/src/index.test.ts @@ -1,6 +1,6 @@ -import { TemplateValidationError } from "./templates/template-validation-error" -import { nullTrace } from "./test-utils/null-trace" -import { parseWorkflow } from "./workflows/workflow-parser" +import {TemplateValidationError} from "./templates/template-validation-error"; +import {nullTrace} from "./test-utils/null-trace"; +import {parseWorkflow} from "./workflows/workflow-parser"; describe("parseWorkflow", () => { it("parses valid workflow", () => { @@ -9,15 +9,14 @@ describe("parseWorkflow", () => { [ { name: "test.yaml", - content: - "on: push\njobs:\n build:\n runs-on: ubuntu-latest\n steps:\n - run: echo 'hello'", - }, + content: "on: push\njobs:\n build:\n runs-on: ubuntu-latest\n steps:\n - run: echo 'hello'" + } ], nullTrace - ) + ); - expect(result.context.errors.getErrors()).toHaveLength(0) - }) + expect(result.context.errors.getErrors()).toHaveLength(0); + }); it("contains range for error", () => { const result = parseWorkflow( @@ -25,25 +24,19 @@ describe("parseWorkflow", () => { [ { name: "test.yaml", - content: - "on: push\njobs:\n build:\n steps:\n - run: echo 'hello'", - }, + content: "on: push\njobs:\n build:\n steps:\n - run: echo 'hello'" + } ], nullTrace - ) + ); expect(result.context.errors.getErrors()).toEqual([ - new TemplateValidationError( - "Required property is missing: runs-on", - "test.yaml (Line: 4, Col: 5)", - undefined, - { - start: [4, 5], - end: [5, 24], - } - ), - ]) - }) + new TemplateValidationError("Required property is missing: runs-on", "test.yaml (Line: 4, Col: 5)", undefined, { + start: [4, 5], + end: [5, 24] + }) + ]); + }); it("error range for expression is constrained to scalar node", () => { const result = parseWorkflow( @@ -57,11 +50,11 @@ jobs: runs-on: ubuntu-latest steps: - name: \${{ github.event = 12 }} - run: echo 'hello'`, - }, + run: echo 'hello'` + } ], nullTrace - ) + ); expect(result.context.errors.getErrors()).toEqual([ new TemplateValidationError( @@ -70,11 +63,11 @@ jobs: undefined, { start: [6, 13], - end: [6, 37], + end: [6, 37] } - ), - ]) - }) + ) + ]); + }); it("tokens contain descriptions", () => { const result = parseWorkflow( @@ -83,39 +76,37 @@ jobs: { name: "test.yaml", content: - "on: push\nname: hello\njobs:\n build:\n runs-on: ubuntu-latest\n steps:\n - run: echo 'hello'", - }, + "on: push\nname: hello\njobs:\n build:\n runs-on: ubuntu-latest\n steps:\n - run: echo 'hello'" + } ], nullTrace - ) + ); - expect(result.context.errors.getErrors()).toHaveLength(0) - expect(result.value).not.toBeUndefined() - const root = result.value!.assertMapping("root") - expect(root.description).toBe("Workflow file with strict validation") + expect(result.context.errors.getErrors()).toHaveLength(0); + expect(result.value).not.toBeUndefined(); + const root = result.value!.assertMapping("root"); + expect(root.description).toBe("Workflow file with strict validation"); for (const pair of root) { - const key = pair.key.assertString("key").value + const key = pair.key.assertString("key").value; switch (key) { case "name": { - const nameKey = pair.key.assertString("name") - expect(nameKey.definition).not.toBeUndefined() - expect(nameKey.description).toBe("The name of the workflow.") - break + const nameKey = pair.key.assertString("name"); + expect(nameKey.definition).not.toBeUndefined(); + expect(nameKey.description).toBe("The name of the workflow."); + break; } case "on": { - const onKey = pair.key.assertString("on") - const onValue = pair.value.assertString("push") - expect(onKey.definition).not.toBeUndefined() + const onKey = pair.key.assertString("on"); + const onValue = pair.value.assertString("push"); + expect(onKey.definition).not.toBeUndefined(); expect(onKey.description).toBe( "The name of the GitHub event that triggers the workflow. You can provide a single event string, array of events, array of event types, or an event configuration map that schedules a workflow or restricts the execution of a workflow to specific files, tags, or branch changes. For a list of available events, see https://help.github.com/en/github/automating-your-workflow-with-github-actions/events-that-trigger-workflows." - ) - expect(onValue.definition).not.toBeUndefined() - expect(onValue.description).toBe( - "Runs your workflow when you push a commit or tag." - ) - break + ); + expect(onValue.definition).not.toBeUndefined(); + expect(onValue.description).toBe("Runs your workflow when you push a commit or tag."); + break; } } } - }) -}) + }); +}); diff --git a/actions-workflow-parser/src/index.ts b/actions-workflow-parser/src/index.ts index e73faec..6dcfc86 100644 --- a/actions-workflow-parser/src/index.ts +++ b/actions-workflow-parser/src/index.ts @@ -1,5 +1,5 @@ -export { convertWorkflowTemplate } from "./model/convert" -export { WorkflowTemplate } from "./model/workflow-template" -export * from "./templates/tokens/type-guards" -export { NoOperationTraceWriter, TraceWriter } from "./templates/trace-writer" -export { parseWorkflow, ParseWorkflowResult } from "./workflows/workflow-parser" +export {convertWorkflowTemplate} from "./model/convert"; +export {WorkflowTemplate} from "./model/workflow-template"; +export * from "./templates/tokens/type-guards"; +export {NoOperationTraceWriter, TraceWriter} from "./templates/trace-writer"; +export {parseWorkflow, ParseWorkflowResult} from "./workflows/workflow-parser"; diff --git a/actions-workflow-parser/src/model/convert.test.ts b/actions-workflow-parser/src/model/convert.test.ts index 84e68e5..fa75ae8 100644 --- a/actions-workflow-parser/src/model/convert.test.ts +++ b/actions-workflow-parser/src/model/convert.test.ts @@ -1,9 +1,9 @@ -import { nullTrace } from "../test-utils/null-trace" -import { parseWorkflow } from "../workflows/workflow-parser" -import { convertWorkflowTemplate, ErrorPolicy } from "./convert" +import {nullTrace} from "../test-utils/null-trace"; +import {parseWorkflow} from "../workflows/workflow-parser"; +import {convertWorkflowTemplate, ErrorPolicy} from "./convert"; function serializeTemplate(template: unknown): unknown { - return JSON.parse(JSON.stringify(template)) + return JSON.parse(JSON.stringify(template)); } describe("convertWorkflowTemplate", () => { @@ -16,39 +16,35 @@ describe("convertWorkflowTemplate", () => { content: `on: push jobs: build: - runs-on: ubuntu-latest`, - }, + runs-on: ubuntu-latest` + } ], nullTrace - ) + ); - const template = convertWorkflowTemplate( - result.context, - result.value!, - ErrorPolicy.TryConversion - ) + const template = convertWorkflowTemplate(result.context, result.value!, ErrorPolicy.TryConversion); expect(serializeTemplate(template)).toEqual({ events: { - push: {}, + push: {} }, jobs: [ { id: "build", if: { expr: "success()", - type: 3, + type: 3 }, name: "build", needs: undefined, outputs: undefined, "runs-on": "ubuntu-latest", steps: [], - type: "job", - }, - ], - }) - }) + type: "job" + } + ] + }); + }); it("converts workflow if expressions", () => { const result = parseWorkflow( @@ -63,48 +59,44 @@ jobs: runs-on: ubuntu-latest deploy: if: true - runs-on: ubuntu-latest`, - }, + runs-on: ubuntu-latest` + } ], nullTrace - ) + ); - const template = convertWorkflowTemplate( - result.context, - result.value!, - ErrorPolicy.TryConversion - ) + const template = convertWorkflowTemplate(result.context, result.value!, ErrorPolicy.TryConversion); expect(serializeTemplate(template)).toEqual({ events: { - push: {}, + push: {} }, jobs: [ { id: "build", if: { expr: "success()", - type: 3, + type: 3 }, name: "build", "runs-on": "ubuntu-latest", steps: [], - type: "job", + type: "job" }, { id: "deploy", if: { expr: "success()", - type: 3, + type: 3 }, name: "deploy", "runs-on": "ubuntu-latest", steps: [], - type: "job", - }, - ], - }) - }) + type: "job" + } + ] + }); + }); it("converts workflow with empty needs", () => { const result = parseWorkflow( @@ -116,44 +108,40 @@ jobs: jobs: build: needs: # comment to preserve whitespace in test - runs-on: ubuntu-latest`, - }, + runs-on: ubuntu-latest` + } ], nullTrace - ) + ); - const template = convertWorkflowTemplate( - result.context, - result.value!, - ErrorPolicy.TryConversion - ) + const template = convertWorkflowTemplate(result.context, result.value!, ErrorPolicy.TryConversion); expect(serializeTemplate(template)).toEqual({ errors: [ { - Message: "wf.yaml (Line: 4, Col: 12): Unexpected value ''", - }, + Message: "wf.yaml (Line: 4, Col: 12): Unexpected value ''" + } ], events: { - push: {}, + push: {} }, jobs: [ { id: "build", if: { expr: "success()", - type: 3, + type: 3 }, name: "build", needs: [], outputs: undefined, "runs-on": "ubuntu-latest", steps: [], - type: "job", - }, - ], - }) - }) + type: "job" + } + ] + }); + }); it("converts workflow with needs errors", () => { const result = parseWorkflow( @@ -170,75 +158,70 @@ jobs: runs-on: ubuntu-latest job3: needs: job1 - runs-on: ubuntu-latest`, - }, + runs-on: ubuntu-latest` + } ], nullTrace - ) + ); - const template = convertWorkflowTemplate( - result.context, - result.value!, - ErrorPolicy.TryConversion - ) + const template = convertWorkflowTemplate(result.context, result.value!, ErrorPolicy.TryConversion); expect(serializeTemplate(template)).toEqual({ errors: [ { - Message: - "wf.yaml (Line: 4, Col: 13): Job 'job1' depends on unknown job 'unknown-job'.", + Message: "wf.yaml (Line: 4, Col: 13): Job 'job1' depends on unknown job 'unknown-job'." }, { Message: - "wf.yaml (Line: 4, Col: 26): Job 'job1' depends on job 'job3' which creates a cycle in the dependency graph.", + "wf.yaml (Line: 4, Col: 26): Job 'job1' depends on job 'job3' which creates a cycle in the dependency graph." }, { Message: - "wf.yaml (Line: 9, Col: 12): Job 'job3' depends on job 'job1' which creates a cycle in the dependency graph.", - }, + "wf.yaml (Line: 9, Col: 12): Job 'job3' depends on job 'job1' which creates a cycle in the dependency graph." + } ], events: { - push: {}, + push: {} }, jobs: [ { id: "job1", if: { expr: "success()", - type: 3, + type: 3 }, name: "job1", needs: ["unknown-job", "job3"], "runs-on": "ubuntu-latest", steps: [], - type: "job", + type: "job" }, { id: "job2", if: { expr: "success()", - type: 3, + type: 3 }, name: "job2", "runs-on": "ubuntu-latest", steps: [], - type: "job", + type: "job" }, { id: "job3", if: { expr: "success()", - type: 3, + type: 3 }, name: "job3", needs: ["job1"], "runs-on": "ubuntu-latest", steps: [], - type: "job", - }, - ], - }) - }) + type: "job" + } + ] + }); + }); it("converts workflow with invalid on", () => { const result = parseWorkflow( @@ -255,29 +238,25 @@ jobs: build: runs-on: ubuntu-latest steps: - - run: echo hello`, - }, + - run: echo hello` + } ], nullTrace - ) + ); - const template = convertWorkflowTemplate( - result.context, - result.value!, - ErrorPolicy.TryConversion - ) + const template = convertWorkflowTemplate(result.context, result.value!, ErrorPolicy.TryConversion); - expect(template.jobs).not.toBeUndefined() - expect(template.jobs).toHaveLength(1) + expect(template.jobs).not.toBeUndefined(); + expect(template.jobs).toHaveLength(1); expect(serializeTemplate(template)).toEqual({ errors: [ { - Message: "wf.yaml (Line: 5, Col: 18): Unexpected value '123'", + Message: "wf.yaml (Line: 5, Col: 18): Unexpected value '123'" }, { Message: - "wf.yaml (Line: 5, Col: 18): Unexpected type 'NumberToken' encountered while reading 'input options'. The type 'SequenceToken' was expected.", - }, + "wf.yaml (Line: 5, Col: 18): Unexpected type 'NumberToken' encountered while reading 'input options'. The type 'SequenceToken' was expected." + } ], events: {}, jobs: [ @@ -285,7 +264,7 @@ jobs: id: "build", if: { expr: "success()", - type: 3, + type: 3 }, name: "build", needs: undefined, @@ -296,16 +275,16 @@ jobs: id: "__run", if: { expr: "success()", - type: 3, + type: 3 }, - run: "echo hello", - }, + run: "echo hello" + } ], - type: "job", - }, - ], - }) - }) + type: "job" + } + ] + }); + }); it("converts workflow with invalid jobs", () => { const result = parseWorkflow( @@ -315,34 +294,30 @@ jobs: name: "wf.yaml", content: `on: push jobs: - build:`, - }, + build:` + } ], nullTrace - ) + ); - const template = convertWorkflowTemplate( - result.context, - result.value!, - ErrorPolicy.TryConversion - ) + const template = convertWorkflowTemplate(result.context, result.value!, ErrorPolicy.TryConversion); - expect(template.jobs).not.toBeUndefined() - expect(template.jobs).toHaveLength(0) + expect(template.jobs).not.toBeUndefined(); + expect(template.jobs).toHaveLength(0); expect(serializeTemplate(template)).toEqual({ errors: [ { - Message: "wf.yaml (Line: 3, Col: 9): Unexpected value ''", + Message: "wf.yaml (Line: 3, Col: 9): Unexpected value ''" }, { Message: - "wf.yaml (Line: 3, Col: 9): Unexpected type 'NullToken' encountered while reading 'job build'. The type 'MappingToken' was expected.", - }, + "wf.yaml (Line: 3, Col: 9): Unexpected type 'NullToken' encountered while reading 'job build'. The type 'MappingToken' was expected." + } ], events: { - push: {}, + push: {} }, - jobs: [], - }) - }) -}) + jobs: [] + }); + }); +}); diff --git a/actions-workflow-parser/src/model/convert.ts b/actions-workflow-parser/src/model/convert.ts index afab05d..3bd9350 100644 --- a/actions-workflow-parser/src/model/convert.ts +++ b/actions-workflow-parser/src/model/convert.ts @@ -1,17 +1,14 @@ -import { TemplateContext } from "../templates/template-context" -import { - TemplateToken, - TemplateTokenError, -} from "../templates/tokens/template-token" -import { convertConcurrency } from "./converter/concurrency" -import { convertOn } from "./converter/events" -import { handleTemplateTokenErrors } from "./converter/handle-errors" -import { convertJobs } from "./converter/jobs" -import { WorkflowTemplate } from "./workflow-template" +import {TemplateContext} from "../templates/template-context"; +import {TemplateToken, TemplateTokenError} from "../templates/tokens/template-token"; +import {convertConcurrency} from "./converter/concurrency"; +import {convertOn} from "./converter/events"; +import {handleTemplateTokenErrors} from "./converter/handle-errors"; +import {convertJobs} from "./converter/jobs"; +import {WorkflowTemplate} from "./workflow-template"; export enum ErrorPolicy { ReturnErrorsOnly, - TryConversion, + TryConversion } export function convertWorkflowTemplate( @@ -19,62 +16,53 @@ export function convertWorkflowTemplate( root: TemplateToken, errorPolicy: ErrorPolicy = ErrorPolicy.ReturnErrorsOnly ): WorkflowTemplate { - const result = {} as WorkflowTemplate + const result = {} as WorkflowTemplate; - if ( - context.errors.getErrors().length > 0 && - errorPolicy === ErrorPolicy.ReturnErrorsOnly - ) { - result.errors = context.errors.getErrors().map((x) => ({ - Message: x.message, - })) - return result + if (context.errors.getErrors().length > 0 && errorPolicy === ErrorPolicy.ReturnErrorsOnly) { + result.errors = context.errors.getErrors().map(x => ({ + Message: x.message + })); + return result; } try { - const rootMapping = root.assertMapping("root") + const rootMapping = root.assertMapping("root"); for (const item of rootMapping) { - const key = item.key.assertString("root key") + const key = item.key.assertString("root key"); switch (key.value) { case "on": - result.events = handleTemplateTokenErrors(root, context, {}, () => - convertOn(context, item.value) - ) - break + result.events = handleTemplateTokenErrors(root, context, {}, () => convertOn(context, item.value)); + break; case "jobs": - result.jobs = handleTemplateTokenErrors(root, context, [], () => - convertJobs(context, item.value) - ) - break + result.jobs = handleTemplateTokenErrors(root, context, [], () => convertJobs(context, item.value)); + break; case "concurrency": - handleTemplateTokenErrors(root, context, {}, () => - convertConcurrency(context, item.value) - ) - result.concurrency = item.value - break + handleTemplateTokenErrors(root, context, {}, () => convertConcurrency(context, item.value)); + result.concurrency = item.value; + break; case "env": - result.env = item.value - break + result.env = item.value; + break; } } } catch (err) { if (err instanceof TemplateTokenError) { - context.error(err.token, err) + context.error(err.token, err); } else { // Report error for the root node - context.error(root, err) + context.error(root, err); } } finally { if (context.errors.getErrors().length > 0) { - result.errors = context.errors.getErrors().map((x) => ({ - Message: x.message, - })) + result.errors = context.errors.getErrors().map(x => ({ + Message: x.message + })); } } - return result + return result; } diff --git a/actions-workflow-parser/src/model/converter/concurrency.ts b/actions-workflow-parser/src/model/converter/concurrency.ts index 544bbf3..d240dda 100644 --- a/actions-workflow-parser/src/model/converter/concurrency.ts +++ b/actions-workflow-parser/src/model/converter/concurrency.ts @@ -1,42 +1,35 @@ -import { TemplateContext } from "../../templates/template-context" -import { TemplateToken } from "../../templates/tokens/template-token" -import { isString } from "../../templates/tokens/type-guards" -import { ConcurrencySetting } from "../workflow-template" +import {TemplateContext} from "../../templates/template-context"; +import {TemplateToken} from "../../templates/tokens/template-token"; +import {isString} from "../../templates/tokens/type-guards"; +import {ConcurrencySetting} from "../workflow-template"; -export function convertConcurrency( - context: TemplateContext, - token: TemplateToken -): ConcurrencySetting { - const result: ConcurrencySetting = {} +export function convertConcurrency(context: TemplateContext, token: TemplateToken): ConcurrencySetting { + const result: ConcurrencySetting = {}; if (token.isExpression) { - return result + return result; } if (isString(token)) { - result.group = token - return result + result.group = token; + return result; } - const concurrencyProperty = token.assertMapping("concurrency group") + const concurrencyProperty = token.assertMapping("concurrency group"); for (const property of concurrencyProperty) { - const propertyName = property.key.assertString("concurrency group key") + const propertyName = property.key.assertString("concurrency group key"); if (property.key.isExpression || property.value.isExpression) { - continue + continue; } switch (propertyName.value) { case "group": - result.group = property.value.assertString("concurrency group") - break + result.group = property.value.assertString("concurrency group"); + break; case "cancel-in-progress": - result.cancelInProgress = - property.value.assertBoolean("cancel-in-progress").value - break + result.cancelInProgress = property.value.assertBoolean("cancel-in-progress").value; + break; default: - context.error( - propertyName, - `Invalid property name: ${propertyName.value}` - ) + context.error(propertyName, `Invalid property name: ${propertyName.value}`); } } - return result + return result; } diff --git a/actions-workflow-parser/src/model/converter/container.ts b/actions-workflow-parser/src/model/converter/container.ts index 58edb75..a17a85c 100644 --- a/actions-workflow-parser/src/model/converter/container.ts +++ b/actions-workflow-parser/src/model/converter/container.ts @@ -1,124 +1,110 @@ -import { TemplateContext } from "../../templates/template-context" -import { - MappingToken, - SequenceToken, - StringToken, - TemplateToken, -} from "../../templates/tokens" -import { isString } from "../../templates/tokens/type-guards" -import { Container, Credential } from "../workflow-template" +import {TemplateContext} from "../../templates/template-context"; +import {MappingToken, SequenceToken, StringToken, TemplateToken} from "../../templates/tokens"; +import {isString} from "../../templates/tokens/type-guards"; +import {Container, Credential} from "../workflow-template"; -export function convertToJobContainer( - context: TemplateContext, - container: TemplateToken -): Container | undefined { - let image: StringToken | undefined - let env: MappingToken | undefined - let ports: SequenceToken | undefined - let volumes: SequenceToken | undefined - let options: StringToken | undefined +export function convertToJobContainer(context: TemplateContext, container: TemplateToken): Container | undefined { + let image: StringToken | undefined; + let env: MappingToken | undefined; + let ports: SequenceToken | undefined; + let volumes: SequenceToken | undefined; + let options: StringToken | undefined; // Skip validation for expressions for now to match // behavior of the other parsers for (const [_, token, __] of TemplateToken.traverse(container)) { if (token.isExpression) { - return + return; } } if (isString(container)) { // Workflow uses shorthand syntax `container: image-name` - image = container.assertString("container item") - return { image: image } + image = container.assertString("container item"); + return {image: image}; } - const mapping = container.assertMapping("container item") + const mapping = container.assertMapping("container item"); if (mapping) for (const item of mapping) { - const key = item.key.assertString("container item key") - const value = item.value + const key = item.key.assertString("container item key"); + const value = item.value; switch (key.value) { case "image": - image = value.assertString("container image") - break + image = value.assertString("container image"); + break; case "credentials": - convertToJobCredentials(context, value) - break + convertToJobCredentials(context, value); + break; case "env": - env = value.assertMapping("container env") + env = value.assertMapping("container env"); for (const envItem of env) { - envItem.key.assertString("container env value") + envItem.key.assertString("container env value"); } - break + break; case "ports": - ports = value.assertSequence("container ports") + ports = value.assertSequence("container ports"); for (const port of ports) { - port.assertString("container port") + port.assertString("container port"); } - break + break; case "volumes": - volumes = value.assertSequence("container volumes") + volumes = value.assertSequence("container volumes"); for (const volume of volumes) { - volume.assertString("container volume") + volume.assertString("container volume"); } - break + break; case "options": - options = value.assertString("container options") - break + options = value.assertString("container options"); + break; default: - context.error(key, `Unexpected container item key: ${key.value}`) + context.error(key, `Unexpected container item key: ${key.value}`); } } if (!image) { - context.error(container, "Container image cannot be empty") + context.error(container, "Container image cannot be empty"); } else { - return { image, env, ports, volumes, options } + return {image, env, ports, volumes, options}; } } -export function convertToJobServices( - context: TemplateContext, - services: TemplateToken -): Container[] | undefined { - const serviceList: Container[] = [] +export function convertToJobServices(context: TemplateContext, services: TemplateToken): Container[] | undefined { + const serviceList: Container[] = []; - const mapping = services.assertMapping("services") + const mapping = services.assertMapping("services"); for (const service of mapping) { - service.key.assertString("service key") - const container = convertToJobContainer(context, service.value) + service.key.assertString("service key"); + const container = convertToJobContainer(context, service.value); if (container) { - serviceList.push(container) + serviceList.push(container); } } - return serviceList + return serviceList; } -function convertToJobCredentials( - context: TemplateContext, - value: TemplateToken -): Credential | undefined { - const mapping = value.assertMapping("credentials") +function convertToJobCredentials(context: TemplateContext, value: TemplateToken): Credential | undefined { + const mapping = value.assertMapping("credentials"); - let username: StringToken | undefined - let password: StringToken | undefined + let username: StringToken | undefined; + let password: StringToken | undefined; for (const item of mapping) { - const key = item.key.assertString("credentials item") - const value = item.value + const key = item.key.assertString("credentials item"); + const value = item.value; switch (key.value) { case "username": - username = value.assertString("credentials username") - break + username = value.assertString("credentials username"); + break; case "password": - password = value.assertString("credentials password") - break + password = value.assertString("credentials password"); + break; default: - context.error(key, `credentials key ${key.value}`) + context.error(key, `credentials key ${key.value}`); } } - return { username, password } + return {username, password}; } diff --git a/actions-workflow-parser/src/model/converter/events.ts b/actions-workflow-parser/src/model/converter/events.ts index 4504e2d..32f2131 100644 --- a/actions-workflow-parser/src/model/converter/events.ts +++ b/actions-workflow-parser/src/model/converter/events.ts @@ -1,14 +1,9 @@ -import { TemplateContext } from "../../templates/template-context" -import { MappingToken } from "../../templates/tokens/mapping-token" -import { SequenceToken } from "../../templates/tokens/sequence-token" -import { TemplateToken } from "../../templates/tokens/template-token" -import { - isLiteral, - isMapping, - isSequence, - isString, -} from "../../templates/tokens/type-guards" -import { TokenType } from "../../templates/tokens/types" +import {TemplateContext} from "../../templates/template-context"; +import {MappingToken} from "../../templates/tokens/mapping-token"; +import {SequenceToken} from "../../templates/tokens/sequence-token"; +import {TemplateToken} from "../../templates/tokens/template-token"; +import {isLiteral, isMapping, isSequence, isString} from "../../templates/tokens/type-guards"; +import {TokenType} from "../../templates/tokens/types"; import { BranchFilterConfig, EventsConfig, @@ -16,56 +11,53 @@ import { ScheduleConfig, TagFilterConfig, TypesFilterConfig, - WorkflowFilterConfig, -} from "../workflow-template" -import { convertStringList } from "./string-list" -import { convertEventWorkflowDispatchInputs } from "./workflow-dispatch" + WorkflowFilterConfig +} from "../workflow-template"; +import {convertStringList} from "./string-list"; +import {convertEventWorkflowDispatchInputs} from "./workflow-dispatch"; -export function convertOn( - context: TemplateContext, - token: TemplateToken -): EventsConfig { +export function convertOn(context: TemplateContext, token: TemplateToken): EventsConfig { if (isLiteral(token)) { - const event = token.assertString("on") + const event = token.assertString("on"); return { - [event.value]: {}, - } as EventsConfig + [event.value]: {} + } as EventsConfig; } if (isSequence(token)) { - const result = {} as EventsConfig + const result = {} as EventsConfig; for (const item of token) { - const event = item.assertString("on") - result[event.value] = {} + const event = item.assertString("on"); + result[event.value] = {}; } - return result + return result; } if (isMapping(token)) { - const result = {} as EventsConfig + const result = {} as EventsConfig; for (const item of token) { - const eventKey = item.key.assertString("event name") - const eventName = eventKey.value + const eventKey = item.key.assertString("event name"); + const eventName = eventKey.value; if (item.value.templateTokenType === TokenType.Null) { - result[eventName] = {} - continue + result[eventName] = {}; + continue; } // Schedule is the only event that can be a sequence, handle that separately if (eventName === "schedule") { - const scheduleToken = item.value.assertSequence(`event ${eventName}`) - result.schedule = convertSchedule(context, scheduleToken) - continue + const scheduleToken = item.value.assertSequence(`event ${eventName}`); + result.schedule = convertSchedule(context, scheduleToken); + continue; } // All other events are defined as mappings. During schema validation we already ensure that events // receive only known keys, so here we can focus on the values and whether they are valid. - const eventToken = item.value.assertMapping(`event ${eventName}`) + const eventToken = item.value.assertMapping(`event ${eventName}`); result[eventName] = { ...convertPatternFilter("branches", eventToken), @@ -74,105 +66,95 @@ export function convertOn( ...convertFilter("types", eventToken), ...convertFilter("workflows", eventToken), // TODO - share input parsing for now, but workflow_call also needs outputs and secrets - ...convertEventWorkflowDispatchInputs(context, eventToken), - } + ...convertEventWorkflowDispatchInputs(context, eventToken) + }; } - return result + return result; } - context.error(token, "Invalid format for 'on'") - return {} + context.error(token, "Invalid format for 'on'"); + return {}; } -function convertPatternFilter< - T extends BranchFilterConfig & TagFilterConfig & PathFilterConfig ->(name: "branches" | "tags" | "paths", token: MappingToken): T { - const result = {} as T +function convertPatternFilter( + name: "branches" | "tags" | "paths", + token: MappingToken +): T { + const result = {} as T; for (const item of token) { - const key = item.key.assertString(`${name} filter key`) + const key = item.key.assertString(`${name} filter key`); switch (key.value) { case name: if (isString(item.value)) { - result[name] = [item.value.value] + result[name] = [item.value.value]; } else { - result[name] = convertStringList( - name, - item.value.assertSequence(`${name} list`) - ) + result[name] = convertStringList(name, item.value.assertSequence(`${name} list`)); } - break + break; case `${name}-ignore`: if (isString(item.value)) { - result[`${name}-ignore`] = [item.value.value] + result[`${name}-ignore`] = [item.value.value]; } else { result[`${name}-ignore`] = convertStringList( `${name}-ignore`, item.value.assertSequence(`${name}-ignore list`) - ) + ); } - break + break; } } - return result + return result; } function convertFilter( name: "types" | "workflows", token: MappingToken ): T { - const result = {} as T + const result = {} as T; for (const item of token) { - const key = item.key.assertString(`${name} filter key`) + const key = item.key.assertString(`${name} filter key`); switch (key.value) { case name: if (isString(item.value)) { - result[name] = [item.value.value] + result[name] = [item.value.value]; } else { - result[name] = convertStringList( - name, - item.value.assertSequence(`${name} list`) - ) + result[name] = convertStringList(name, item.value.assertSequence(`${name} list`)); } - break + break; } } - return result + return result; } -function convertSchedule( - context: TemplateContext, - token: SequenceToken -): ScheduleConfig[] | undefined { - const result = [] as ScheduleConfig[] +function convertSchedule(context: TemplateContext, token: SequenceToken): ScheduleConfig[] | undefined { + const result = [] as ScheduleConfig[]; for (const item of token) { - const mappingToken = item.assertMapping(`event schedule`) + const mappingToken = item.assertMapping(`event schedule`); if (mappingToken.count == 1) { - const schedule = mappingToken.get(0) - const scheduleKey = schedule.key.assertString(`schedule key`) + const schedule = mappingToken.get(0); + const scheduleKey = schedule.key.assertString(`schedule key`); if (scheduleKey.value == "cron") { - const cron = schedule.value.assertString(`schedule cron`) + const cron = schedule.value.assertString(`schedule cron`); // Validate the cron string - if ( - !cron.value.match(/((((\d+,)+\d+|(\d+(\/|-)\d+)|\d+|\*) ?){5,7})/) - ) { - context.error(cron, "Invalid cron string") + if (!cron.value.match(/((((\d+,)+\d+|(\d+(\/|-)\d+)|\d+|\*) ?){5,7})/)) { + context.error(cron, "Invalid cron string"); } - result.push({ cron: cron.value }) + result.push({cron: cron.value}); } else { - context.error(scheduleKey, `Invalid schedule key`) + context.error(scheduleKey, `Invalid schedule key`); } } else { - context.error(mappingToken, "Invalid format for 'schedule'") + context.error(mappingToken, "Invalid format for 'schedule'"); } } - return result + return result; } diff --git a/actions-workflow-parser/src/model/converter/handle-errors.ts b/actions-workflow-parser/src/model/converter/handle-errors.ts index f8c8400..a1f2a3b 100644 --- a/actions-workflow-parser/src/model/converter/handle-errors.ts +++ b/actions-workflow-parser/src/model/converter/handle-errors.ts @@ -1,8 +1,5 @@ -import { TemplateContext } from "../../templates/template-context" -import { - TemplateToken, - TemplateTokenError, -} from "../../templates/tokens/template-token" +import {TemplateContext} from "../../templates/template-context"; +import {TemplateToken, TemplateTokenError} from "../../templates/tokens/template-token"; export function handleTemplateTokenErrors( root: TemplateToken, @@ -10,18 +7,18 @@ export function handleTemplateTokenErrors( defaultValue: TResult, f: () => TResult ): TResult { - let r: TResult = defaultValue + let r: TResult = defaultValue; try { - r = f() + r = f(); } catch (err) { if (err instanceof TemplateTokenError) { - context.error(err.token, err) + context.error(err.token, err); } else { // Report error for the root node - context.error(root, err) + context.error(root, err); } } - return r + return r; } diff --git a/actions-workflow-parser/src/model/converter/id-builder.test.ts b/actions-workflow-parser/src/model/converter/id-builder.test.ts index 97ec3d1..96b73ce 100644 --- a/actions-workflow-parser/src/model/converter/id-builder.test.ts +++ b/actions-workflow-parser/src/model/converter/id-builder.test.ts @@ -1,147 +1,146 @@ -import { IdBuilder } from "./id-builder" +import {IdBuilder} from "./id-builder"; function build(...segments: string[]): string { - const builder = new IdBuilder() + const builder = new IdBuilder(); for (const segment of segments) { - builder.appendSegment(segment) + builder.appendSegment(segment); } - return builder.build() + return builder.build(); } describe("ID Builder", () => { it("builds IDs", () => { - expect(build("one")).toEqual("one") + expect(build("one")).toEqual("one"); - expect(build("one", "two")).toEqual("one_two") + expect(build("one", "two")).toEqual("one_two"); - expect(build("one", "two", "three")).toEqual("one_two_three") - }) + expect(build("one", "two", "three")).toEqual("one_two_three"); + }); it("empty builder", () => { - const builder = new IdBuilder() - expect(builder.build()).toEqual("job") - }) + const builder = new IdBuilder(); + expect(builder.build()).toEqual("job"); + }); it("ignores empty segments", () => { - expect(build("", "one")).toEqual("one") + expect(build("", "one")).toEqual("one"); - expect(build("one", "", "two", "")).toEqual("one_two") - }) + expect(build("one", "", "two", "")).toEqual("one_two"); + }); it("handles illegal characters", () => { - const builder = new IdBuilder() - builder.appendSegment("hello world!") - expect(builder.build()).toEqual("hello_world_") - }) + const builder = new IdBuilder(); + builder.appendSegment("hello world!"); + expect(builder.build()).toEqual("hello_world_"); + }); it("handles illegal leading characters", () => { - expect(build("!hello")).toEqual("_hello") + expect(build("!hello")).toEqual("_hello"); - expect(build("!hello", "!world")).toEqual("_hello__world") + expect(build("!hello", "!world")).toEqual("_hello__world"); - expect(build("!@world", "!@world")).toEqual("__world___world") + expect(build("!@world", "!@world")).toEqual("__world___world"); - expect(build("123")).toEqual("_123") + expect(build("123")).toEqual("_123"); - expect(build("123", "456")).toEqual("_123_456") + expect(build("123", "456")).toEqual("_123_456"); - expect(build("-abc")).toEqual("_-abc") + expect(build("-abc")).toEqual("_-abc"); - expect(build("-abc", "-def")).toEqual("_-abc_-def") - }) + expect(build("-abc", "-def")).toEqual("_-abc_-def"); + }); it("allows legal characters", () => { - expect(build("abyzABYZ0189_-")).toEqual("abyzABYZ0189_-") - }) + expect(build("abyzABYZ0189_-")).toEqual("abyzABYZ0189_-"); + }); it("allows legal leading characters", () => { - expect(build("abc")).toEqual("abc") + expect(build("abc")).toEqual("abc"); - expect(build("bcd")).toEqual("bcd") + expect(build("bcd")).toEqual("bcd"); - expect(build("zyx")).toEqual("zyx") + expect(build("zyx")).toEqual("zyx"); - expect(build("yxw")).toEqual("yxw") + expect(build("yxw")).toEqual("yxw"); - expect(build("ABCD")).toEqual("ABCD") + expect(build("ABCD")).toEqual("ABCD"); - expect(build("BCDE")).toEqual("BCDE") + expect(build("BCDE")).toEqual("BCDE"); - expect(build("ZYXW")).toEqual("ZYXW") + expect(build("ZYXW")).toEqual("ZYXW"); - expect(build("YXWV")).toEqual("YXWV") + expect(build("YXWV")).toEqual("YXWV"); - expect(build("_abc")).toEqual("_abc") - }) + expect(build("_abc")).toEqual("_abc"); + }); it("errors for max collisions", () => { - const builder = new IdBuilder() - builder.appendSegment("abc") - builder.appendSegment("def") - expect(builder.build()).toEqual("abc_def") + const builder = new IdBuilder(); + builder.appendSegment("abc"); + builder.appendSegment("def"); + expect(builder.build()).toEqual("abc_def"); for (let i = 2; i < 1000; i++) { - builder.appendSegment("abc") - builder.appendSegment("def") - expect(builder.build()).toEqual(`abc_def_${i}`) + builder.appendSegment("abc"); + builder.appendSegment("def"); + expect(builder.build()).toEqual(`abc_def_${i}`); } - builder.appendSegment("abc") - builder.appendSegment("def") - expect(() => builder.build()).toThrowError("Unable to create a unique name") - }) + builder.appendSegment("abc"); + builder.appendSegment("def"); + expect(() => builder.build()).toThrowError("Unable to create a unique name"); + }); it("takes suffix into account for max length", () => { - const builder = new IdBuilder() + const builder = new IdBuilder(); - const name = - "_234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890" - builder.appendSegment(name) - expect(builder.build()).toEqual(name) + const name = "_234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890"; + builder.appendSegment(name); + expect(builder.build()).toEqual(name); - builder.appendSegment(name) + builder.appendSegment(name); expect(builder.build()).toEqual( "_2345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678_2" - ) + ); - builder.appendSegment(name) + builder.appendSegment(name); expect(builder.build()).toEqual( "_2345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678_3" - ) + ); - builder.appendSegment(name) + builder.appendSegment(name); expect(builder.build()).toEqual( "_2345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678_4" - ) + ); - builder.appendSegment(name) + builder.appendSegment(name); expect(builder.build()).toEqual( "_2345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678_5" - ) + ); - builder.appendSegment(name) + builder.appendSegment(name); expect(builder.build()).toEqual( "_2345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678_6" - ) + ); - builder.appendSegment(name) + builder.appendSegment(name); expect(builder.build()).toEqual( "_2345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678_7" - ) + ); - builder.appendSegment(name) + builder.appendSegment(name); expect(builder.build()).toEqual( "_2345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678_8" - ) + ); - builder.appendSegment(name) + builder.appendSegment(name); expect(builder.build()).toEqual( "_2345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678_9" - ) + ); - builder.appendSegment(name) + builder.appendSegment(name); expect(builder.build()).toEqual( "_234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567_10" - ) - }) -}) + ); + }); +}); diff --git a/actions-workflow-parser/src/model/converter/id-builder.ts b/actions-workflow-parser/src/model/converter/id-builder.ts index b99bd08..c59559c 100644 --- a/actions-workflow-parser/src/model/converter/id-builder.ts +++ b/actions-workflow-parser/src/model/converter/id-builder.ts @@ -1,69 +1,65 @@ -const SEPARATOR = "_" -const MAX_ATTEMPTS = 1000 -const MAX_LENGTH = 100 +const SEPARATOR = "_"; +const MAX_ATTEMPTS = 1000; +const MAX_LENGTH = 100; export class IdBuilder { - private name: string[] = [] - private readonly distinctNames: Set = new Set() + private name: string[] = []; + private readonly distinctNames: Set = new Set(); public appendSegment(value: string) { if (value.length === 0) { - return + return; } if (this.name.length == 0) { - const first = value[0] + const first = value[0]; if (this.isAlpha(first) || first == "_") { // Legal first char } else if (this.isNumeric(first) || first == "-") { // Illegal first char, but legal char. // Prepend "_". - this.name.push("_") + this.name.push("_"); } else { // Illegal char } } else { // Separator - this.name.push(SEPARATOR) + this.name.push(SEPARATOR); } for (const c of value) { { if (this.isAlphaNumeric(c) || c == "_" || c == "-") { // Legal - this.name.push(c) + this.name.push(c); } else { // Illegal - this.name.push(SEPARATOR) + this.name.push(SEPARATOR); } } } } public build(): string { - const original = this.name.length > 0 ? this.name.join("") : "job" - let suffix = "" + const original = this.name.length > 0 ? this.name.join("") : "job"; + let suffix = ""; for (let attempt = 1; attempt < MAX_ATTEMPTS; attempt++) { if (attempt === 1) { - suffix = "" + suffix = ""; } else { - suffix = "_" + attempt + suffix = "_" + attempt; } - const candidate = - original.substring( - 0, - Math.min(original.length, MAX_LENGTH - suffix.length) - ) + suffix + const candidate = original.substring(0, Math.min(original.length, MAX_LENGTH - suffix.length)) + suffix; if (!this.distinctNames.has(candidate)) { - this.distinctNames.add(candidate) - this.name = [] - return candidate + this.distinctNames.add(candidate); + this.name = []; + return candidate; } } - throw new Error("Unable to create a unique name") + throw new Error("Unable to create a unique name"); } /** @@ -73,19 +69,19 @@ export class IdBuilder { */ public tryAddKnownId(value: string): string | undefined { if (!value || !this.isValid(value) || value.length >= MAX_LENGTH) { - return `The identifier '${value}' is invalid. IDs may only contain alphanumeric characters, '_', and '-'. IDs must start with a letter or '_' and and must be less than ${MAX_LENGTH} characters.` + return `The identifier '${value}' is invalid. IDs may only contain alphanumeric characters, '_', and '-'. IDs must start with a letter or '_' and and must be less than ${MAX_LENGTH} characters.`; } if (value.startsWith(SEPARATOR + SEPARATOR)) { - return `The identifier '${value}' is invalid. IDs starting with '__' are reserved.` + return `The identifier '${value}' is invalid. IDs starting with '__' are reserved.`; } if (this.distinctNames.has(value)) { - return `The identifier '${value}' may not be used more than once within the same scope.` + return `The identifier '${value}' may not be used more than once within the same scope.`; } - this.distinctNames.add(value) - return + this.distinctNames.add(value); + return; } /** @@ -95,32 +91,32 @@ export class IdBuilder { * @returns Whether the name is valid */ private isValid(name: string): boolean { - let first = true + let first = true; for (const c of name) { if (first) { - first = false + first = false; if (!this.isAlpha(c) && c != "_") { - return false + return false; } - continue + continue; } if (!this.isAlphaNumeric(c) && c != "_" && c != "-") { - return false + return false; } } - return true + return true; } private isAlphaNumeric(c: string): boolean { - return this.isAlpha(c) || this.isNumeric(c) + return this.isAlpha(c) || this.isNumeric(c); } private isNumeric(c: string): boolean { - return c >= "0" && c <= "9" + return c >= "0" && c <= "9"; } private isAlpha(c: string): boolean { - return (c >= "a" && c <= "z") || (c >= "A" && c <= "Z") + return (c >= "a" && c <= "z") || (c >= "A" && c <= "Z"); } } diff --git a/actions-workflow-parser/src/model/converter/job/environment.ts b/actions-workflow-parser/src/model/converter/job/environment.ts index 5e9558f..5153245 100644 --- a/actions-workflow-parser/src/model/converter/job/environment.ts +++ b/actions-workflow-parser/src/model/converter/job/environment.ts @@ -1,41 +1,41 @@ -import { TemplateContext } from "../../../templates/template-context" -import { TemplateToken } from "../../../templates/tokens/template-token" -import { isScalar } from "../../../templates/tokens/type-guards" -import { ActionsEnvironmentReference } from "../../workflow-template" +import {TemplateContext} from "../../../templates/template-context"; +import {TemplateToken} from "../../../templates/tokens/template-token"; +import {isScalar} from "../../../templates/tokens/type-guards"; +import {ActionsEnvironmentReference} from "../../workflow-template"; export function convertToActionsEnvironmentRef( context: TemplateContext, token: TemplateToken ): ActionsEnvironmentReference { - const result: ActionsEnvironmentReference = {} + const result: ActionsEnvironmentReference = {}; if (token.isExpression) { - return result + return result; } if (isScalar(token)) { - result.name = token - return result + result.name = token; + return result; } - const environmentMapping = token.assertMapping("job environment") + const environmentMapping = token.assertMapping("job environment"); for (const property of environmentMapping) { - const propertyName = property.key.assertString("job environment key") + const propertyName = property.key.assertString("job environment key"); if (property.key.isExpression || property.value.isExpression) { - continue + continue; } switch (propertyName.value) { case "name": - result.name = property.value.assertScalar("job environment name key") - break + result.name = property.value.assertScalar("job environment name key"); + break; case "url": - result.url = property.value - break + result.url = property.value; + break; } } - return result + return result; } diff --git a/actions-workflow-parser/src/model/converter/jobs.ts b/actions-workflow-parser/src/model/converter/jobs.ts index 6b709c6..17aa165 100644 --- a/actions-workflow-parser/src/model/converter/jobs.ts +++ b/actions-workflow-parser/src/model/converter/jobs.ts @@ -1,72 +1,53 @@ -import { TemplateContext } from "../../templates/template-context" -import { - BasicExpressionToken, - MappingToken, - StringToken, -} from "../../templates/tokens" -import { TemplateToken } from "../../templates/tokens/template-token" -import { - isMapping, - isSequence, - isString, -} from "../../templates/tokens/type-guards" -import { Job } from "../workflow-template" -import { convertConcurrency } from "./concurrency" -import { convertToJobContainer, convertToJobServices } from "./container" -import { handleTemplateTokenErrors } from "./handle-errors" -import { IdBuilder } from "./id-builder" -import { convertToActionsEnvironmentRef } from "./job/environment" -import { convertSteps } from "./steps" +import {TemplateContext} from "../../templates/template-context"; +import {BasicExpressionToken, MappingToken, StringToken} from "../../templates/tokens"; +import {TemplateToken} from "../../templates/tokens/template-token"; +import {isMapping, isSequence, isString} from "../../templates/tokens/type-guards"; +import {Job} from "../workflow-template"; +import {convertConcurrency} from "./concurrency"; +import {convertToJobContainer, convertToJobServices} from "./container"; +import {handleTemplateTokenErrors} from "./handle-errors"; +import {IdBuilder} from "./id-builder"; +import {convertToActionsEnvironmentRef} from "./job/environment"; +import {convertSteps} from "./steps"; type nodeInfo = { - name: string - needs: StringToken[] -} + name: string; + needs: StringToken[]; +}; -export function convertJobs( - context: TemplateContext, - token: TemplateToken -): Job[] { +export function convertJobs(context: TemplateContext, token: TemplateToken): Job[] { if (isMapping(token)) { - const result: Job[] = [] - const jobsWithSatisfiedNeeds: nodeInfo[] = [] - const alljobsWithUnsatisfiedNeeds: nodeInfo[] = [] + const result: Job[] = []; + const jobsWithSatisfiedNeeds: nodeInfo[] = []; + const alljobsWithUnsatisfiedNeeds: nodeInfo[] = []; for (const item of token) { - const jobKey = item.key.assertString("job name") - const jobDef = item.value.assertMapping(`job ${jobKey.value}`) + const jobKey = item.key.assertString("job name"); + const jobDef = item.value.assertMapping(`job ${jobKey.value}`); - const job = handleTemplateTokenErrors(token, context, undefined, () => - convertJob(context, jobKey, jobDef) - ) + const job = handleTemplateTokenErrors(token, context, undefined, () => convertJob(context, jobKey, jobDef)); if (job) { - result.push(job) + result.push(job); const node = { name: job.id.value, - needs: Object.assign([], job.needs), - } + needs: Object.assign([], job.needs) + }; if (node.needs.length > 0) { - alljobsWithUnsatisfiedNeeds.push(node) + alljobsWithUnsatisfiedNeeds.push(node); } else { - jobsWithSatisfiedNeeds.push(node) + jobsWithSatisfiedNeeds.push(node); } } } //validate job needs - validateNeeds( - token, - context, - result, - jobsWithSatisfiedNeeds, - alljobsWithUnsatisfiedNeeds - ) + validateNeeds(token, context, result, jobsWithSatisfiedNeeds, alljobsWithUnsatisfiedNeeds); - return result + return result; } - context.error(token, "Invalid format for jobs") - return [] + context.error(token, "Invalid format for jobs"); + return []; } function validateNeeds( @@ -77,28 +58,25 @@ function validateNeeds( alljobsWithUnsatisfiedNeeds: nodeInfo[] ) { if (jobsWithSatisfiedNeeds.length == 0) { - context.error( - token, - "The workflow must contain at least one job with no dependencies." - ) - return + context.error(token, "The workflow must contain at least one job with no dependencies."); + return; } // Figure out which nodes would start after current completes while (jobsWithSatisfiedNeeds.length > 0) { - const currentJob = jobsWithSatisfiedNeeds.shift() + const currentJob = jobsWithSatisfiedNeeds.shift(); if (currentJob == undefined) { - break + break; } for (let i = alljobsWithUnsatisfiedNeeds.length - 1; i >= 0; i--) { - const unsatisfiedJob = alljobsWithUnsatisfiedNeeds[i] + const unsatisfiedJob = alljobsWithUnsatisfiedNeeds[i]; for (let j = unsatisfiedJob.needs.length - 1; j >= 0; j--) { - const need = unsatisfiedJob.needs[j] + const need = unsatisfiedJob.needs[j]; if (need.value == currentJob.name) { - unsatisfiedJob.needs.splice(j, 1) + unsatisfiedJob.needs.splice(j, 1); if (unsatisfiedJob.needs.length == 0) { - jobsWithSatisfiedNeeds.push(unsatisfiedJob) - alljobsWithUnsatisfiedNeeds.splice(i, 1) + jobsWithSatisfiedNeeds.push(unsatisfiedJob); + alljobsWithUnsatisfiedNeeds.splice(i, 1); } } } @@ -107,46 +85,33 @@ function validateNeeds( // Check whether some jobs will never execute if (alljobsWithUnsatisfiedNeeds.length > 0) { - const jobNames = result.map((x) => x.id.value) + const jobNames = result.map(x => x.id.value); for (const unsatisfiedJob of alljobsWithUnsatisfiedNeeds) { for (const need of unsatisfiedJob.needs) { if (jobNames.includes(need.value)) { context.error( need, `Job '${unsatisfiedJob.name}' depends on job '${need.value}' which creates a cycle in the dependency graph.` - ) + ); } else { - context.error( - need, - `Job '${unsatisfiedJob.name}' depends on unknown job '${need.value}'.` - ) + context.error(need, `Job '${unsatisfiedJob.name}' depends on unknown job '${need.value}'.`); } } } } } -function convertJob( - context: TemplateContext, - jobKey: StringToken, - token: MappingToken -): Job { - const error = new IdBuilder().tryAddKnownId(jobKey.value) +function convertJob(context: TemplateContext, jobKey: StringToken, token: MappingToken): Job { + const error = new IdBuilder().tryAddKnownId(jobKey.value); if (error) { - context.error(jobKey, error) + context.error(jobKey, error); } const result: Job = { type: "job", id: jobKey, name: undefined, needs: undefined, - if: new BasicExpressionToken( - undefined, - undefined, - "success()", - undefined, - undefined - ), + if: new BasicExpressionToken(undefined, undefined, "success()", undefined, undefined), env: undefined, concurrency: undefined, environment: undefined, @@ -155,191 +120,180 @@ function convertJob( container: undefined, services: undefined, outputs: undefined, - steps: [], - } + steps: [] + }; for (const item of token) { - const propertyName = item.key.assertString("job property name") + const propertyName = item.key.assertString("job property name"); switch (propertyName.value) { case "concurrency": - handleTemplateTokenErrors(item.value, context, undefined, () => - convertConcurrency(context, item.value) - ) - result.concurrency = item.value - break + handleTemplateTokenErrors(item.value, context, undefined, () => convertConcurrency(context, item.value)); + result.concurrency = item.value; + break; case "container": // Do early validation, but don't convert - convertToJobContainer(context, item.value) - result.container = item.value - break + convertToJobContainer(context, item.value); + result.container = item.value; + break; case "env": - result.env = item.value.assertMapping("job env") - break + result.env = item.value.assertMapping("job env"); + break; case "environment": handleTemplateTokenErrors(item.value, context, undefined, () => convertToActionsEnvironmentRef(context, item.value) - ) - result.environment = item.value - break + ); + result.environment = item.value; + break; case "name": - result.name = item.value.assertScalar("job name") - break + result.name = item.value.assertScalar("job name"); + break; case "needs": - result.needs = [] + result.needs = []; if (isString(item.value)) { - const jobNeeds = item.value.assertString("job needs id") - result.needs.push(jobNeeds) + const jobNeeds = item.value.assertString("job needs id"); + result.needs.push(jobNeeds); } if (isSequence(item.value)) { for (const seqItem of item.value) { - const jobNeeds = seqItem.assertString("job needs id") - result.needs.push(jobNeeds) + const jobNeeds = seqItem.assertString("job needs id"); + result.needs.push(jobNeeds); } } - break + break; case "outputs": - result.outputs = item.value.assertMapping("job outputs") - break + result.outputs = item.value.assertMapping("job outputs"); + break; case "runs-on": - handleTemplateTokenErrors(item.value, context, undefined, () => - convertRunsOn(context, item.value) - ) - result["runs-on"] = item.value - break + handleTemplateTokenErrors(item.value, context, undefined, () => convertRunsOn(context, item.value)); + result["runs-on"] = item.value; + break; case "services": // Do early validation, but don't convert - convertToJobServices(context, item.value) - result.services = item.value - break + convertToJobServices(context, item.value); + result.services = item.value; + break; case "steps": - result.steps = convertSteps(context, item.value) - break + result.steps = convertSteps(context, item.value); + break; case "strategy": - result.strategy = item.value - break + result.strategy = item.value; + break; } } if (!result.name) { - result.name = result.id + result.name = result.id; } - return result + return result; } type RunsOn = { - labels: Set - group: string -} + labels: Set; + group: string; +}; function convertRunsOn(context: TemplateContext, token: TemplateToken): RunsOn { - const labels = convertRunsOnLabels(token) + const labels = convertRunsOnLabels(token); if (!isMapping(token)) { return { labels, - group: "", - } + group: "" + }; } - let group = "" + let group = ""; for (const item of token) { - const key = item.key.assertString("job runs-on property name") + const key = item.key.assertString("job runs-on property name"); switch (key.value) { case "group": { if (item.value.isExpression) { - continue + continue; } - const groupName = item.value.assertString( - "job runs-on group name" - ).value - const names = groupName.split("/") + const groupName = item.value.assertString("job runs-on group name").value; + const names = groupName.split("/"); switch (names.length) { case 1: { - group = groupName - break + group = groupName; + break; } case 2: { - if ( - !["org", "organization", "ent", "enterprise"].includes(names[0]) - ) { + if (!["org", "organization", "ent", "enterprise"].includes(names[0])) { context.error( item.value, `Invalid runs-on group name '${groupName}. Please use 'organization/' or 'enterprise/' prefix to target a single runner group.'` - ) - continue + ); + continue; } if (!names[1]) { - context.error( - item.value, - `Invalid runs-on group name '${groupName}'.` - ) - continue + context.error(item.value, `Invalid runs-on group name '${groupName}'.`); + continue; } - group = groupName - break + group = groupName; + break; } default: { context.error( item.value, `Invalid runs-on group name '${groupName}. Please use 'organization/' or 'enterprise/' prefix to target a single runner group.'` - ) - break + ); + break; } } - break + break; } case "labels": { - const mapLabels = convertRunsOnLabels(item.value) + const mapLabels = convertRunsOnLabels(item.value); for (const label of mapLabels) { - labels.add(label) + labels.add(label); } - break + break; } } } return { labels, - group, - } + group + }; } function convertRunsOnLabels(token: TemplateToken): Set { - const labels = new Set() + const labels = new Set(); if (token.isExpression) { - return labels + return labels; } if (isString(token)) { - labels.add(token.value) - return labels + labels.add(token.value); + return labels; } if (isSequence(token)) { for (const item of token) { if (item.isExpression) { - continue + continue; } - const label = item.assertString("job runs-on label sequence item") - labels.add(label.value) + const label = item.assertString("job runs-on label sequence item"); + labels.add(label.value); } } - return labels + return labels; } diff --git a/actions-workflow-parser/src/model/converter/steps.ts b/actions-workflow-parser/src/model/converter/steps.ts index 1aad070..4adca20 100644 --- a/actions-workflow-parser/src/model/converter/steps.ts +++ b/actions-workflow-parser/src/model/converter/steps.ts @@ -1,107 +1,84 @@ -import { TemplateContext } from "../../templates/template-context" -import { - BasicExpressionToken, - MappingToken, - ScalarToken, - StringToken, - TemplateToken, -} from "../../templates/tokens" -import { isSequence } from "../../templates/tokens/type-guards" -import { isActionStep } from "../type-guards" -import { ActionStep, Step } from "../workflow-template" -import { handleTemplateTokenErrors } from "./handle-errors" -import { IdBuilder } from "./id-builder" +import {TemplateContext} from "../../templates/template-context"; +import {BasicExpressionToken, MappingToken, ScalarToken, StringToken, TemplateToken} from "../../templates/tokens"; +import {isSequence} from "../../templates/tokens/type-guards"; +import {isActionStep} from "../type-guards"; +import {ActionStep, Step} from "../workflow-template"; +import {handleTemplateTokenErrors} from "./handle-errors"; +import {IdBuilder} from "./id-builder"; -export function convertSteps( - context: TemplateContext, - steps: TemplateToken -): Step[] { +export function convertSteps(context: TemplateContext, steps: TemplateToken): Step[] { if (!isSequence(steps)) { - context.error(steps, "Invalid format for steps") - return [] + context.error(steps, "Invalid format for steps"); + return []; } - const idBuilder = new IdBuilder() + const idBuilder = new IdBuilder(); - const result: Step[] = [] + const result: Step[] = []; for (const item of steps) { - const step = handleTemplateTokenErrors(steps, context, undefined, () => - convertStep(context, idBuilder, item) - ) + const step = handleTemplateTokenErrors(steps, context, undefined, () => convertStep(context, idBuilder, item)); if (step) { - result.push(step) + result.push(step); } } for (const step of result) { if (step.id) { - continue + continue; } - let id = "" + let id = ""; if (isActionStep(step)) { - id = createActionStepId(step) + id = createActionStepId(step); } if (!id) { - id = "run" + id = "run"; } - idBuilder.appendSegment(`__${id}`) - step.id = idBuilder.build() + idBuilder.appendSegment(`__${id}`); + step.id = idBuilder.build(); } - return result + return result; } -function convertStep( - context: TemplateContext, - idBuilder: IdBuilder, - step: TemplateToken -): Step | undefined { - const mapping = step.assertMapping("steps item") +function convertStep(context: TemplateContext, idBuilder: IdBuilder, step: TemplateToken): Step | undefined { + const mapping = step.assertMapping("steps item"); - let run: ScalarToken | undefined - let id: StringToken | undefined - let name: ScalarToken | undefined - let uses: StringToken | undefined - let continueOnError: boolean | undefined - let env: MappingToken | undefined - const ifCondition = new BasicExpressionToken( - undefined, - undefined, - "success()", - undefined, - undefined - ) + let run: ScalarToken | undefined; + let id: StringToken | undefined; + let name: ScalarToken | undefined; + let uses: StringToken | undefined; + let continueOnError: boolean | undefined; + let env: MappingToken | undefined; + const ifCondition = new BasicExpressionToken(undefined, undefined, "success()", undefined, undefined); for (const item of mapping) { - const key = item.key.assertString("steps item key") + const key = item.key.assertString("steps item key"); switch (key.value) { case "id": - id = item.value.assertString("steps item id") + id = item.value.assertString("steps item id"); if (id) { - const error = idBuilder.tryAddKnownId(id.value) + const error = idBuilder.tryAddKnownId(id.value); if (error) { - context.error(id, error) + context.error(id, error); } } - break + break; case "name": - name = item.value.assertScalar("steps item name") - break + name = item.value.assertScalar("steps item name"); + break; case "run": - run = item.value.assertScalar("steps item run") - break + run = item.value.assertScalar("steps item run"); + break; case "uses": - uses = item.value.assertString("steps item uses") - break + uses = item.value.assertString("steps item uses"); + break; case "env": - env = item.value.assertMapping("step env") - break + env = item.value.assertMapping("step env"); + break; case "continue-on-error": - continueOnError = item.value.assertBoolean( - "steps item continue-on-error" - ).value + continueOnError = item.value.assertBoolean("steps item continue-on-error").value; } } @@ -112,8 +89,8 @@ function convertStep( if: ifCondition, "continue-on-error": continueOnError, env, - run, - } + run + }; } if (uses) { @@ -123,38 +100,33 @@ function convertStep( if: ifCondition, "continue-on-error": continueOnError, env, - uses, - } + uses + }; } - context.error(step, "Expected uses or run to be defined") + context.error(step, "Expected uses or run to be defined"); } function createActionStepId(step: ActionStep): string { - const uses = step.uses.value + const uses = step.uses.value; if (uses.startsWith("docker://")) { - return uses.substring("docker://".length) + return uses.substring("docker://".length); } if (uses.startsWith("./") || uses.startsWith(".\\")) { - return "self" + return "self"; } - const segments = uses.split("@") + const segments = uses.split("@"); if (segments.length != 2) { - return "" + return ""; } - const pathSegments = segments[0].split(/[\\/]/).filter((s) => s.length > 0) - const gitRef = segments[1] + const pathSegments = segments[0].split(/[\\/]/).filter(s => s.length > 0); + const gitRef = segments[1]; - if ( - pathSegments.length >= 2 && - pathSegments[0] && - pathSegments[1] && - gitRef - ) { - return `${pathSegments[0]}/${pathSegments[1]}` + if (pathSegments.length >= 2 && pathSegments[0] && pathSegments[1] && gitRef) { + return `${pathSegments[0]}/${pathSegments[1]}`; } - return "" + return ""; } diff --git a/actions-workflow-parser/src/model/converter/string-list.ts b/actions-workflow-parser/src/model/converter/string-list.ts index 3de4584..b0c0026 100644 --- a/actions-workflow-parser/src/model/converter/string-list.ts +++ b/actions-workflow-parser/src/model/converter/string-list.ts @@ -1,14 +1,11 @@ -import { SequenceToken } from "../../templates/tokens/sequence-token" +import {SequenceToken} from "../../templates/tokens/sequence-token"; -export function convertStringList( - name: string, - token: SequenceToken -): string[] { - const result = [] as string[] +export function convertStringList(name: string, token: SequenceToken): string[] { + const result = [] as string[]; for (const item of token) { - result.push(item.assertString(`${name} item`).value) + result.push(item.assertString(`${name} item`).value); } - return result + return result; } diff --git a/actions-workflow-parser/src/model/converter/workflow-dispatch.ts b/actions-workflow-parser/src/model/converter/workflow-dispatch.ts index a64cd2e..922b191 100644 --- a/actions-workflow-parser/src/model/converter/workflow-dispatch.ts +++ b/actions-workflow-parser/src/model/converter/workflow-dispatch.ts @@ -1,99 +1,79 @@ -import { TemplateContext } from "../../templates/template-context" -import { MappingToken } from "../../templates/tokens/mapping-token" -import { ScalarToken } from "../../templates/tokens/scalar-token" -import { - InputConfig, - InputType, - WorkflowDispatchConfig, -} from "../workflow-template" -import { convertStringList } from "./string-list" +import {TemplateContext} from "../../templates/template-context"; +import {MappingToken} from "../../templates/tokens/mapping-token"; +import {ScalarToken} from "../../templates/tokens/scalar-token"; +import {InputConfig, InputType, WorkflowDispatchConfig} from "../workflow-template"; +import {convertStringList} from "./string-list"; export function convertEventWorkflowDispatchInputs( context: TemplateContext, token: MappingToken ): WorkflowDispatchConfig { - const result: WorkflowDispatchConfig = {} + const result: WorkflowDispatchConfig = {}; for (const item of token) { - const key = item.key.assertString("workflow dispatch input key") + const key = item.key.assertString("workflow dispatch input key"); switch (key.value) { case "inputs": - result.inputs = convertWorkflowDispatchInputs( - context, - item.value.assertMapping("workflow dispatch inputs") - ) - break + result.inputs = convertWorkflowDispatchInputs(context, item.value.assertMapping("workflow dispatch inputs")); + break; } } - return result + return result; } export function convertWorkflowDispatchInputs( context: TemplateContext, token: MappingToken ): { - [inputName: string]: InputConfig + [inputName: string]: InputConfig; } { - const result: { [inputName: string]: InputConfig } = {} + const result: {[inputName: string]: InputConfig} = {}; for (const item of token) { - const inputName = item.key.assertString("input name") - const inputMapping = item.value.assertMapping("input configuration") + const inputName = item.key.assertString("input name"); + const inputMapping = item.value.assertMapping("input configuration"); - result[inputName.value] = convertWorkflowDispatchInput( - context, - inputMapping - ) + result[inputName.value] = convertWorkflowDispatchInput(context, inputMapping); } - return result + return result; } -export function convertWorkflowDispatchInput( - context: TemplateContext, - token: MappingToken -): InputConfig { +export function convertWorkflowDispatchInput(context: TemplateContext, token: MappingToken): InputConfig { const result: InputConfig = { - type: InputType.string, // Default to string - } + type: InputType.string // Default to string + }; - let defaultValue: undefined | ScalarToken + let defaultValue: undefined | ScalarToken; for (const item of token) { - const key = item.key.assertString("workflow dispatch input key") + const key = item.key.assertString("workflow dispatch input key"); switch (key.value) { case "description": - result.description = item.value.assertString("input description").value - break + result.description = item.value.assertString("input description").value; + break; case "required": - result.required = item.value.assertBoolean("input required").value - break + result.required = item.value.assertBoolean("input required").value; + break; case "default": - defaultValue = item.value.assertScalar("input default") - break + defaultValue = item.value.assertScalar("input default"); + break; case "type": - result.type = - InputType[ - item.value.assertString("input type") - .value as keyof typeof InputType - ] - break + result.type = InputType[item.value.assertString("input type").value as keyof typeof InputType]; + break; case "options": - result.options = convertStringList( - "input options", - item.value.assertSequence("input options") - ) - break + result.options = convertStringList("input options", item.value.assertSequence("input options")); + break; default: - context.error(item.key, `Invalid key '${key.value}'`) + context.error(item.key, `Invalid key '${key.value}'`); } } @@ -102,34 +82,31 @@ export function convertWorkflowDispatchInput( try { switch (result.type) { case InputType.boolean: - result.default = defaultValue.assertBoolean("input default").value + result.default = defaultValue.assertBoolean("input default").value; - break + break; case InputType.string: case InputType.choice: case InputType.environment: - result.default = defaultValue.assertString("input default").value - break + result.default = defaultValue.assertString("input default").value; + break; } } catch (e) { - context.error(defaultValue, e) + context.error(defaultValue, e); } } // Validate `options` for `choice` type if (result.type === InputType.choice) { if (result.options === undefined || result.options.length === 0) { - context.error(token, "Missing 'options' for choice input") + context.error(token, "Missing 'options' for choice input"); } } else { if (result.options !== undefined) { - context.error( - token, - "Input type is not 'choice', but 'options' is defined" - ) + context.error(token, "Input type is not 'choice', but 'options' is defined"); } } - return result + return result; } diff --git a/actions-workflow-parser/src/model/type-guards.ts b/actions-workflow-parser/src/model/type-guards.ts index 23467c9..a1e09c0 100644 --- a/actions-workflow-parser/src/model/type-guards.ts +++ b/actions-workflow-parser/src/model/type-guards.ts @@ -1,9 +1,9 @@ -import { ActionStep, RunStep, Step } from "./workflow-template" +import {ActionStep, RunStep, Step} from "./workflow-template"; export function isRunStep(step: Step): step is RunStep { - return (step as RunStep).run !== undefined + return (step as RunStep).run !== undefined; } export function isActionStep(step: Step): step is ActionStep { - return (step as ActionStep).uses !== undefined + return (step as ActionStep).uses !== undefined; } diff --git a/actions-workflow-parser/src/model/workflow-template.ts b/actions-workflow-parser/src/model/workflow-template.ts index dc9ec16..2ede8f5 100644 --- a/actions-workflow-parser/src/model/workflow-template.ts +++ b/actions-workflow-parser/src/model/workflow-template.ts @@ -4,166 +4,164 @@ import { ScalarToken, SequenceToken, StringToken, - TemplateToken, -} from "../templates/tokens" + TemplateToken +} from "../templates/tokens"; export type WorkflowTemplate = { - events: EventsConfig - jobs: Job[] - concurrency: TemplateToken - env: TemplateToken + events: EventsConfig; + jobs: Job[]; + concurrency: TemplateToken; + env: TemplateToken; errors?: { - Message: string - }[] -} + Message: string; + }[]; +}; export type ConcurrencySetting = { - group?: StringToken - cancelInProgress?: boolean -} + group?: StringToken; + cancelInProgress?: boolean; +}; export type ActionsEnvironmentReference = { - name?: TemplateToken - url?: TemplateToken -} + name?: TemplateToken; + url?: TemplateToken; +}; export type Job = { - type: string - id: StringToken - name?: ScalarToken - needs?: StringToken[] - if: BasicExpressionToken - env?: MappingToken - concurrency?: TemplateToken - environment?: TemplateToken - strategy?: TemplateToken - "runs-on"?: TemplateToken - container?: TemplateToken - services?: TemplateToken - outputs?: MappingToken - steps: Step[] -} + type: string; + id: StringToken; + name?: ScalarToken; + needs?: StringToken[]; + if: BasicExpressionToken; + env?: MappingToken; + concurrency?: TemplateToken; + environment?: TemplateToken; + strategy?: TemplateToken; + "runs-on"?: TemplateToken; + container?: TemplateToken; + services?: TemplateToken; + outputs?: MappingToken; + steps: Step[]; +}; export type Container = { - image: StringToken - credentials?: Credential - env?: MappingToken - ports?: SequenceToken - volumes?: SequenceToken - options?: StringToken -} + image: StringToken; + credentials?: Credential; + env?: MappingToken; + ports?: SequenceToken; + volumes?: SequenceToken; + options?: StringToken; +}; export type Credential = { - username: StringToken | undefined - password: StringToken | undefined -} + username: StringToken | undefined; + password: StringToken | undefined; +}; -export type Step = ActionStep | RunStep +export type Step = ActionStep | RunStep; type BaseStep = { - id: string - name?: ScalarToken - if: BasicExpressionToken - "continue-on-error"?: boolean - env?: MappingToken -} + id: string; + name?: ScalarToken; + if: BasicExpressionToken; + "continue-on-error"?: boolean; + env?: MappingToken; +}; export type RunStep = BaseStep & { - run: ScalarToken -} + run: ScalarToken; +}; export type ActionStep = BaseStep & { - uses: StringToken -} + uses: StringToken; +}; export type EventsConfig = { - schedule?: ScheduleConfig[] - workflow_dispatch?: WorkflowDispatchConfig - workflow_call?: WorkflowCallConfig + schedule?: ScheduleConfig[]; + workflow_dispatch?: WorkflowDispatchConfig; + workflow_call?: WorkflowCallConfig; // Events that support filters - pull_request?: BranchFilterConfig & PathFilterConfig & TypesFilterConfig - pull_request_target?: BranchFilterConfig & - PathFilterConfig & - TypesFilterConfig - push?: BranchFilterConfig & TagFilterConfig & PathFilterConfig - workflow_run?: WorkflowFilterConfig & BranchFilterConfig & TypesFilterConfig + pull_request?: BranchFilterConfig & PathFilterConfig & TypesFilterConfig; + pull_request_target?: BranchFilterConfig & PathFilterConfig & TypesFilterConfig; + push?: BranchFilterConfig & TagFilterConfig & PathFilterConfig; + workflow_run?: WorkflowFilterConfig & BranchFilterConfig & TypesFilterConfig; // Events that only support activity types - branch_protection_rule?: TypesFilterConfig - check_run?: TypesFilterConfig - check_suite?: TypesFilterConfig - disccusion?: TypesFilterConfig - disccusion_comment?: TypesFilterConfig - issue_comment?: TypesFilterConfig - issues?: TypesFilterConfig - label?: TypesFilterConfig - merge_group?: TypesFilterConfig - milestone?: TypesFilterConfig - project?: TypesFilterConfig - project_card?: TypesFilterConfig - project_column?: TypesFilterConfig - pull_request_review?: TypesFilterConfig - pull_request_review_comment?: TypesFilterConfig - registry_package?: TypesFilterConfig - repository_dispatch?: TypesFilterConfig - release?: TypesFilterConfig - watch?: TypesFilterConfig + branch_protection_rule?: TypesFilterConfig; + check_run?: TypesFilterConfig; + check_suite?: TypesFilterConfig; + disccusion?: TypesFilterConfig; + disccusion_comment?: TypesFilterConfig; + issue_comment?: TypesFilterConfig; + issues?: TypesFilterConfig; + label?: TypesFilterConfig; + merge_group?: TypesFilterConfig; + milestone?: TypesFilterConfig; + project?: TypesFilterConfig; + project_card?: TypesFilterConfig; + project_column?: TypesFilterConfig; + pull_request_review?: TypesFilterConfig; + pull_request_review_comment?: TypesFilterConfig; + registry_package?: TypesFilterConfig; + repository_dispatch?: TypesFilterConfig; + release?: TypesFilterConfig; + watch?: TypesFilterConfig; // Index signature to allow easier lookup - [eventName: string]: unknown -} + [eventName: string]: unknown; +}; export type TypesFilterConfig = { - types?: string[] -} + types?: string[]; +}; export type BranchFilterConfig = { - branches?: string[] - "branches-ignore"?: string[] -} + branches?: string[]; + "branches-ignore"?: string[]; +}; export type TagFilterConfig = { - tags?: string[] - "tags-ignore"?: string[] -} + tags?: string[]; + "tags-ignore"?: string[]; +}; export type PathFilterConfig = { - paths?: string[] - "paths-ignore"?: string[] -} + paths?: string[]; + "paths-ignore"?: string[]; +}; export type WorkflowDispatchConfig = { - inputs?: { [inputName: string]: InputConfig } -} + inputs?: {[inputName: string]: InputConfig}; +}; export type WorkflowCallConfig = { - inputs: { [inputName: string]: InputConfig } + inputs: {[inputName: string]: InputConfig}; // TODO - these are supported in C# and Go but not in TS yet // outputs: { [outputName: string]: OutputConfig } // secrets: { [secretName: string]: SecretConfig } -} +}; export enum InputType { string = "string", choice = "choice", boolean = "boolean", - environment = "environment", + environment = "environment" } export type InputConfig = { - type: InputType - description?: string - required?: boolean - default?: string | boolean | number - options?: string[] -} + type: InputType; + description?: string; + required?: boolean; + default?: string | boolean | number; + options?: string[]; +}; export type ScheduleConfig = { - cron: string -} + cron: string; +}; export type WorkflowFilterConfig = { - workflows?: string[] -} + workflows?: string[]; +}; diff --git a/actions-workflow-parser/src/templates/allowed-context.ts b/actions-workflow-parser/src/templates/allowed-context.ts index 1c5cd61..16b8878 100644 --- a/actions-workflow-parser/src/templates/allowed-context.ts +++ b/actions-workflow-parser/src/templates/allowed-context.ts @@ -1,38 +1,36 @@ -import { FunctionInfo } from "@github/actions-expressions/funcs/info" -import { MAX_CONSTANT } from "./template-constants" +import {FunctionInfo} from "@github/actions-expressions/funcs/info"; +import {MAX_CONSTANT} from "./template-constants"; export function splitAllowedContext(allowedContext: string[]): { - namedContexts: string[] - functions: FunctionInfo[] + namedContexts: string[]; + functions: FunctionInfo[]; } { - const FUNCTION_REGEXP = /^([a-zA-Z0-9_]+)\(([0-9]+),([0-9]+|MAX)\)$/ + const FUNCTION_REGEXP = /^([a-zA-Z0-9_]+)\(([0-9]+),([0-9]+|MAX)\)$/; - const namedContexts: string[] = [] - const functions: FunctionInfo[] = [] + const namedContexts: string[] = []; + const functions: FunctionInfo[] = []; if (allowedContext.length > 0) { for (const contextItem of allowedContext) { - const match = contextItem.match(FUNCTION_REGEXP) + const match = contextItem.match(FUNCTION_REGEXP); if (match) { - const functionName = match[1] - const minParameters = Number.parseInt(match[2]) - const maxParametersRaw = match[3] + const functionName = match[1]; + const minParameters = Number.parseInt(match[2]); + const maxParametersRaw = match[3]; const maxParameters = - maxParametersRaw === MAX_CONSTANT - ? Number.MAX_SAFE_INTEGER - : Number.parseInt(maxParametersRaw) + maxParametersRaw === MAX_CONSTANT ? Number.MAX_SAFE_INTEGER : Number.parseInt(maxParametersRaw); functions.push({ name: functionName, minArgs: minParameters, - maxArgs: maxParameters, - }) + maxArgs: maxParameters + }); } else { - namedContexts.push(contextItem) + namedContexts.push(contextItem); } } } return { namedContexts: namedContexts, - functions: functions, - } + functions: functions + }; } diff --git a/actions-workflow-parser/src/templates/evaluate-expression.ts b/actions-workflow-parser/src/templates/evaluate-expression.ts index 13b92b3..7f43f52 100644 --- a/actions-workflow-parser/src/templates/evaluate-expression.ts +++ b/actions-workflow-parser/src/templates/evaluate-expression.ts @@ -1,12 +1,8 @@ -import { Evaluator, Lexer, Parser } from "@github/actions-expressions" -import { Expr } from "@github/actions-expressions/ast" -import { - Dictionary, - ExpressionData, - Kind, -} from "@github/actions-expressions/data/index" -import { FunctionInfo } from "@github/actions-expressions/funcs/info" -import { TemplateContext } from "./template-context" +import {Evaluator, Lexer, Parser} from "@github/actions-expressions"; +import {Expr} from "@github/actions-expressions/ast"; +import {Dictionary, ExpressionData, Kind} from "@github/actions-expressions/data/index"; +import {FunctionInfo} from "@github/actions-expressions/funcs/info"; +import {TemplateContext} from "./template-context"; import { BasicExpressionToken, BooleanToken, @@ -16,115 +12,74 @@ import { NumberToken, SequenceToken, StringToken, - TemplateToken, -} from "./tokens" -import { TokenType } from "./tokens/types" + TemplateToken +} from "./tokens"; +import {TokenType} from "./tokens/types"; -export function evaluateStringToken( - token: BasicExpressionToken, - context: TemplateContext -): TemplateToken { - const expr = parseExpression( - token.expression, - context.expressionNamedContexts, - context.expressionFunctions - ) +export function evaluateStringToken(token: BasicExpressionToken, context: TemplateContext): TemplateToken { + const expr = parseExpression(token.expression, context.expressionNamedContexts, context.expressionFunctions); if (!expr) { - throw new Error("Unexpected empty expression") + throw new Error("Unexpected empty expression"); } // TODO: Pass in context - const evaluator = new Evaluator(expr, new Dictionary()) - const result = evaluator.evaluate() + const evaluator = new Evaluator(expr, new Dictionary()); + const result = evaluator.evaluate(); if (!result.primitive) { - context.error(token, "Expected a string") - return new StringToken( - token.file, - token.range, - token.expression, - token.definitionInfo - ) + context.error(token, "Expected a string"); + return new StringToken(token.file, token.range, token.expression, token.definitionInfo); } - return new StringToken( - token.file, - token.range, - result.coerceString(), - token.definitionInfo - ) + return new StringToken(token.file, token.range, result.coerceString(), token.definitionInfo); } -export function evaluateSequenceToken( - token: BasicExpressionToken, - context: TemplateContext -): TemplateToken { - const expr = parseExpression( - token.expression, - context.expressionNamedContexts, - context.expressionFunctions - ) +export function evaluateSequenceToken(token: BasicExpressionToken, context: TemplateContext): TemplateToken { + const expr = parseExpression(token.expression, context.expressionNamedContexts, context.expressionFunctions); if (!expr) { - throw new Error("Unexpected empty expression") + throw new Error("Unexpected empty expression"); } // TODO: Pass in context - const evaluator = new Evaluator(expr, new Dictionary()) - const result = evaluator.evaluate() - const value = convertToTemplateToken(token, result) + const evaluator = new Evaluator(expr, new Dictionary()); + const result = evaluator.evaluate(); + const value = convertToTemplateToken(token, result); if (value.templateTokenType !== TokenType.Sequence) { - context.error(token, "Expected a sequence") - return new SequenceToken(token.file, token.range, token.definitionInfo) + context.error(token, "Expected a sequence"); + return new SequenceToken(token.file, token.range, token.definitionInfo); } - return value + return value; } -export function evaluateMappingToken( - token: BasicExpressionToken, - context: TemplateContext -): MappingToken { - const expr = parseExpression( - token.expression, - context.expressionNamedContexts, - context.expressionFunctions - ) +export function evaluateMappingToken(token: BasicExpressionToken, context: TemplateContext): MappingToken { + const expr = parseExpression(token.expression, context.expressionNamedContexts, context.expressionFunctions); if (!expr) { - throw new Error("Unexpected empty expression") + throw new Error("Unexpected empty expression"); } // TODO: Pass in context - const evaluator = new Evaluator(expr, new Dictionary()) - const result = evaluator.evaluate() - const value = convertToTemplateToken(token, result) + const evaluator = new Evaluator(expr, new Dictionary()); + const result = evaluator.evaluate(); + const value = convertToTemplateToken(token, result); if (value.templateTokenType !== TokenType.Mapping) { - context.error(token, "Expected a mapping") - return new MappingToken(token.file, token.range, token.definitionInfo) + context.error(token, "Expected a mapping"); + return new MappingToken(token.file, token.range, token.definitionInfo); } - return value as MappingToken + return value as MappingToken; } -export function evaluateTemplateToken( - token: BasicExpressionToken, - context: TemplateContext -): TemplateToken { - const expr = parseExpression( - token.expression, - context.expressionNamedContexts, - context.expressionFunctions - ) +export function evaluateTemplateToken(token: BasicExpressionToken, context: TemplateContext): TemplateToken { + const expr = parseExpression(token.expression, context.expressionNamedContexts, context.expressionFunctions); if (!expr) { - throw new Error("Unexpected empty expression") + throw new Error("Unexpected empty expression"); } // TODO: Pass in context - const evaluator = new Evaluator(expr, new Dictionary()) - const result = evaluator.evaluate() - return convertToTemplateToken(token, result) + const evaluator = new Evaluator(expr, new Dictionary()); + const result = evaluator.evaluate(); + return convertToTemplateToken(token, result); } -function convertToTemplateToken( - token: BasicExpressionToken, - result: ExpressionData -): TemplateToken { +function convertToTemplateToken(token: BasicExpressionToken, result: ExpressionData): TemplateToken { // Literal - const literal = convertToLiteralToken(token, result) + const literal = convertToLiteralToken(token, result); if (literal) { - return literal + return literal; } // TODO: Support expressions that return a sequence or mapping token @@ -132,80 +87,45 @@ function convertToTemplateToken( // Leverage the expression SDK to traverse the object switch (result.kind) { case Kind.Dictionary: { - const mapping = new MappingToken( - token.file, - token.range, - token.definitionInfo - ) - for (const { key, value } of result.pairs()) { - const keyToken = new StringToken( - token.file, - token.range, - key, - token.definitionInfo - ) - const valueToken = convertToTemplateToken(token, value) - mapping.add(keyToken, valueToken) + const mapping = new MappingToken(token.file, token.range, token.definitionInfo); + for (const {key, value} of result.pairs()) { + const keyToken = new StringToken(token.file, token.range, key, token.definitionInfo); + const valueToken = convertToTemplateToken(token, value); + mapping.add(keyToken, valueToken); } - return mapping + return mapping; } case Kind.Array: { - const sequence = new SequenceToken( - token.file, - token.range, - token.definitionInfo - ) + const sequence = new SequenceToken(token.file, token.range, token.definitionInfo); for (const value of result.values()) { - const itemToken = convertToTemplateToken(token, value) - sequence.add(itemToken) + const itemToken = convertToTemplateToken(token, value); + sequence.add(itemToken); } - return sequence + return sequence; } default: - throw new Error("Unable to convert the object to a template token") + throw new Error("Unable to convert the object to a template token"); } } -function convertToLiteralToken( - token: BasicExpressionToken, - result: ExpressionData -): LiteralToken | undefined { +function convertToLiteralToken(token: BasicExpressionToken, result: ExpressionData): LiteralToken | undefined { switch (result.kind) { case Kind.Null: - return new NullToken(token.file, token.range, token.definitionInfo) + return new NullToken(token.file, token.range, token.definitionInfo); case Kind.Boolean: - return new BooleanToken( - token.file, - token.range, - result.value, - token.definitionInfo - ) + return new BooleanToken(token.file, token.range, result.value, token.definitionInfo); case Kind.Number: - return new NumberToken( - token.file, - token.range, - result.value, - token.definitionInfo - ) + return new NumberToken(token.file, token.range, result.value, token.definitionInfo); case Kind.String: - return new StringToken( - token.file, - token.range, - result.value, - token.definitionInfo - ) + return new StringToken(token.file, token.range, result.value, token.definitionInfo); } - return undefined + return undefined; } -function parseExpression( - expression: string, - contexts: string[], - functions: FunctionInfo[] -): Expr { - const lexer = new Lexer(expression) - const result = lexer.lex() - const p = new Parser(result.tokens, contexts, functions) - return p.parse() +function parseExpression(expression: string, contexts: string[], functions: FunctionInfo[]): Expr { + const lexer = new Lexer(expression); + const result = lexer.lex(); + const p = new Parser(result.tokens, contexts, functions); + return p.parse(); } diff --git a/actions-workflow-parser/src/templates/json-object-reader.ts b/actions-workflow-parser/src/templates/json-object-reader.ts index b93f466..ab37780 100644 --- a/actions-workflow-parser/src/templates/json-object-reader.ts +++ b/actions-workflow-parser/src/templates/json-object-reader.ts @@ -1,193 +1,156 @@ -import { ObjectReader } from "./object-reader" -import { EventType, ParseEvent } from "./parse-event" -import { - LiteralToken, - SequenceToken, - MappingToken, - NullToken, - BooleanToken, - NumberToken, - StringToken, -} from "./tokens" +import {ObjectReader} from "./object-reader"; +import {EventType, ParseEvent} from "./parse-event"; +import {LiteralToken, SequenceToken, MappingToken, NullToken, BooleanToken, NumberToken, StringToken} from "./tokens"; export class JSONObjectReader implements ObjectReader { - private readonly _fileId: number | undefined - private readonly _generator: Generator - private _current: IteratorResult + private readonly _fileId: number | undefined; + private readonly _generator: Generator; + private _current: IteratorResult; public constructor(fileId: number | undefined, input: string) { - this._fileId = fileId - const value = JSON.parse(input) - this._generator = this.getParseEvents(value, true) - this._current = this._generator.next() + this._fileId = fileId; + const value = JSON.parse(input); + this._generator = this.getParseEvents(value, true); + this._current = this._generator.next(); } public allowLiteral(): LiteralToken | undefined { if (!this._current.done) { - const parseEvent = this._current.value + const parseEvent = this._current.value; if (parseEvent.type === EventType.Literal) { - this._current = this._generator.next() - return parseEvent.token as LiteralToken + this._current = this._generator.next(); + return parseEvent.token as LiteralToken; } } - return undefined + return undefined; } public allowSequenceStart(): SequenceToken | undefined { if (!this._current.done) { - const parseEvent = this._current.value + const parseEvent = this._current.value; if (parseEvent.type === EventType.SequenceStart) { - this._current = this._generator.next() - return parseEvent.token as SequenceToken + this._current = this._generator.next(); + return parseEvent.token as SequenceToken; } } - return undefined + return undefined; } public allowSequenceEnd(): boolean { if (!this._current.done) { - const parseEvent = this._current.value + const parseEvent = this._current.value; if (parseEvent.type === EventType.SequenceEnd) { - this._current = this._generator.next() - return true + this._current = this._generator.next(); + return true; } } - return false + return false; } public allowMappingStart(): MappingToken | undefined { if (!this._current.done) { - const parseEvent = this._current.value + const parseEvent = this._current.value; if (parseEvent.type === EventType.MappingStart) { - this._current = this._generator.next() - return parseEvent.token as MappingToken + this._current = this._generator.next(); + return parseEvent.token as MappingToken; } } - return undefined + return undefined; } public allowMappingEnd(): boolean { if (!this._current.done) { - const parseEvent = this._current.value + const parseEvent = this._current.value; if (parseEvent.type === EventType.MappingEnd) { - this._current = this._generator.next() - return true + this._current = this._generator.next(); + return true; } } - return false + return false; } public validateEnd(): void { if (!this._current.done) { - const parseEvent = this._current.value as ParseEvent + const parseEvent = this._current.value as ParseEvent; if (parseEvent.type === EventType.DocumentEnd) { - this._current = this._generator.next() - return + this._current = this._generator.next(); + return; } } - throw new Error("Expected end of reader") + throw new Error("Expected end of reader"); } public validateStart(): void { if (!this._current.done) { - const parseEvent = this._current.value as ParseEvent + const parseEvent = this._current.value as ParseEvent; if (parseEvent.type === EventType.DocumentStart) { - this._current = this._generator.next() - return + this._current = this._generator.next(); + return; } } - throw new Error("Expected start of reader") + throw new Error("Expected start of reader"); } /** * Returns all tokens (depth first) */ - private *getParseEvents( - value: any, - root?: boolean - ): Generator { + private *getParseEvents(value: any, root?: boolean): Generator { if (root) { - yield new ParseEvent(EventType.DocumentStart, undefined) + yield new ParseEvent(EventType.DocumentStart, undefined); } switch (typeof value) { case "undefined": - yield new ParseEvent( - EventType.Literal, - new NullToken(this._fileId, undefined, undefined) - ) - break + yield new ParseEvent(EventType.Literal, new NullToken(this._fileId, undefined, undefined)); + break; case "boolean": - yield new ParseEvent( - EventType.Literal, - new BooleanToken(this._fileId, undefined, value, undefined) - ) - break + yield new ParseEvent(EventType.Literal, new BooleanToken(this._fileId, undefined, value, undefined)); + break; case "number": - yield new ParseEvent( - EventType.Literal, - new NumberToken(this._fileId, undefined, value, undefined) - ) - break + yield new ParseEvent(EventType.Literal, new NumberToken(this._fileId, undefined, value, undefined)); + break; case "string": - yield new ParseEvent( - EventType.Literal, - new StringToken(this._fileId, undefined, value, undefined) - ) - break + yield new ParseEvent(EventType.Literal, new StringToken(this._fileId, undefined, value, undefined)); + break; case "object": // null if (value === null) { - yield new ParseEvent( - EventType.Literal, - new NullToken(this._fileId, undefined, undefined) - ) + yield new ParseEvent(EventType.Literal, new NullToken(this._fileId, undefined, undefined)); } // array else if (Object.prototype.hasOwnProperty.call(value, "length")) { - yield new ParseEvent( - EventType.SequenceStart, - new SequenceToken(this._fileId, undefined, undefined) - ) + yield new ParseEvent(EventType.SequenceStart, new SequenceToken(this._fileId, undefined, undefined)); for (const item of value as []) { for (const e of this.getParseEvents(item)) { - yield e + yield e; } } - yield new ParseEvent(EventType.SequenceEnd, undefined) + yield new ParseEvent(EventType.SequenceEnd, undefined); } // object else { - yield new ParseEvent( - EventType.MappingStart, - new MappingToken(this._fileId, undefined, undefined) - ) + yield new ParseEvent(EventType.MappingStart, new MappingToken(this._fileId, undefined, undefined)); for (const key of Object.keys(value)) { - yield new ParseEvent( - EventType.Literal, - new StringToken(this._fileId, undefined, key, undefined) - ) + yield new ParseEvent(EventType.Literal, new StringToken(this._fileId, undefined, key, undefined)); for (const e of this.getParseEvents(value[key])) { - yield e + yield e; } } - yield new ParseEvent(EventType.MappingEnd, undefined) + yield new ParseEvent(EventType.MappingEnd, undefined); } - break + break; default: - throw new Error( - `Unexpected value type '${typeof value}' when reading object` - ) + throw new Error(`Unexpected value type '${typeof value}' when reading object`); } if (root) { - yield new ParseEvent(EventType.DocumentEnd, undefined) + yield new ParseEvent(EventType.DocumentEnd, undefined); } } } diff --git a/actions-workflow-parser/src/templates/object-reader.ts b/actions-workflow-parser/src/templates/object-reader.ts index e89bbb8..1b40439 100644 --- a/actions-workflow-parser/src/templates/object-reader.ts +++ b/actions-workflow-parser/src/templates/object-reader.ts @@ -1,22 +1,22 @@ -import { LiteralToken, SequenceToken, MappingToken } from "./tokens" +import {LiteralToken, SequenceToken, MappingToken} from "./tokens"; /** * Interface for reading a source object (or file). * This interface is used by TemplateReader to build a TemplateToken DOM. */ export interface ObjectReader { - allowLiteral(): LiteralToken | undefined + allowLiteral(): LiteralToken | undefined; // maybe rename these since we don't have out params - allowSequenceStart(): SequenceToken | undefined + allowSequenceStart(): SequenceToken | undefined; - allowSequenceEnd(): boolean + allowSequenceEnd(): boolean; - allowMappingStart(): MappingToken | undefined + allowMappingStart(): MappingToken | undefined; - allowMappingEnd(): boolean + allowMappingEnd(): boolean; - validateStart(): void + validateStart(): void; - validateEnd(): void + validateEnd(): void; } diff --git a/actions-workflow-parser/src/templates/parse-event.ts b/actions-workflow-parser/src/templates/parse-event.ts index 36215a2..0808f71 100644 --- a/actions-workflow-parser/src/templates/parse-event.ts +++ b/actions-workflow-parser/src/templates/parse-event.ts @@ -1,11 +1,11 @@ -import { TemplateToken } from "./tokens" +import {TemplateToken} from "./tokens"; export class ParseEvent { - public readonly type: EventType - public readonly token: TemplateToken | undefined + public readonly type: EventType; + public readonly token: TemplateToken | undefined; public constructor(type: EventType, token?: TemplateToken | undefined) { - this.type = type - this.token = token + this.type = type; + this.token = token; } } @@ -16,5 +16,5 @@ export enum EventType { MappingStart, MappingEnd, DocumentStart, - DocumentEnd, + DocumentEnd } diff --git a/actions-workflow-parser/src/templates/schema/boolean-definition.ts b/actions-workflow-parser/src/templates/schema/boolean-definition.ts index e4cdd92..aa59859 100644 --- a/actions-workflow-parser/src/templates/schema/boolean-definition.ts +++ b/actions-workflow-parser/src/templates/schema/boolean-definition.ts @@ -1,51 +1,43 @@ -import { TemplateSchema } from "." -import { DEFINITION, BOOLEAN } from "../template-constants" -import { MappingToken, LiteralToken } from "../tokens" -import { TokenType } from "../tokens/types" -import { DefinitionType } from "./definition-type" -import { ScalarDefinition } from "./scalar-definition" +import {TemplateSchema} from "."; +import {DEFINITION, BOOLEAN} from "../template-constants"; +import {MappingToken, LiteralToken} from "../tokens"; +import {TokenType} from "../tokens/types"; +import {DefinitionType} from "./definition-type"; +import {ScalarDefinition} from "./scalar-definition"; export class BooleanDefinition extends ScalarDefinition { public constructor(key: string, definition?: MappingToken) { - super(key, definition) + super(key, definition); if (definition) { for (const definitionPair of definition) { - const definitionKey = definitionPair.key.assertString( - `${DEFINITION} key` - ) + const definitionKey = definitionPair.key.assertString(`${DEFINITION} key`); switch (definitionKey.value) { case BOOLEAN: { - const mapping = definitionPair.value.assertMapping( - `${DEFINITION} ${BOOLEAN}` - ) + const mapping = definitionPair.value.assertMapping(`${DEFINITION} ${BOOLEAN}`); for (const mappingPair of mapping) { - const mappingKey = mappingPair.key.assertString( - `${DEFINITION} ${BOOLEAN} key` - ) + const mappingKey = mappingPair.key.assertString(`${DEFINITION} ${BOOLEAN} key`); switch (mappingKey.value) { default: // throws - mappingKey.assertUnexpectedValue( - `${DEFINITION} ${BOOLEAN} key` - ) - break + mappingKey.assertUnexpectedValue(`${DEFINITION} ${BOOLEAN} key`); + break; } } - break + break; } default: - definitionKey.assertUnexpectedValue(`${DEFINITION} key`) // throws + definitionKey.assertUnexpectedValue(`${DEFINITION} key`); // throws } } } } public override get definitionType(): DefinitionType { - return DefinitionType.Boolean + return DefinitionType.Boolean; } public override isMatch(literal: LiteralToken): boolean { - return literal.templateTokenType === TokenType.Boolean + return literal.templateTokenType === TokenType.Boolean; } public override validate(schema: TemplateSchema, name: string): void {} diff --git a/actions-workflow-parser/src/templates/schema/definition-info.ts b/actions-workflow-parser/src/templates/schema/definition-info.ts index b8e5e76..61b290a 100644 --- a/actions-workflow-parser/src/templates/schema/definition-info.ts +++ b/actions-workflow-parser/src/templates/schema/definition-info.ts @@ -1,63 +1,57 @@ -import { TemplateSchema } from "." -import { Definition } from "./definition" -import { DefinitionType } from "./definition-type" -import { ScalarDefinition } from "./scalar-definition" +import {TemplateSchema} from "."; +import {Definition} from "./definition"; +import {DefinitionType} from "./definition-type"; +import {ScalarDefinition} from "./scalar-definition"; export class DefinitionInfo { - private readonly _schema: TemplateSchema - public readonly isDefinitionInfo = true - public readonly definition: Definition - public readonly allowedContext: string[] + private readonly _schema: TemplateSchema; + public readonly isDefinitionInfo = true; + public readonly definition: Definition; + public readonly allowedContext: string[]; - public constructor(schema: TemplateSchema, name: string) - public constructor(parent: DefinitionInfo, name: string) - public constructor(parent: DefinitionInfo, definition: Definition) - public constructor( - schemaOrParent: TemplateSchema | DefinitionInfo, - nameOrDefinition: string | Definition - ) { + public constructor(schema: TemplateSchema, name: string); + public constructor(parent: DefinitionInfo, name: string); + public constructor(parent: DefinitionInfo, definition: Definition); + public constructor(schemaOrParent: TemplateSchema | DefinitionInfo, nameOrDefinition: string | Definition) { const parent: DefinitionInfo | undefined = (schemaOrParent as DefinitionInfo | undefined)?.isDefinitionInfo === true ? (schemaOrParent as DefinitionInfo) - : undefined - this._schema = - parent === undefined ? (schemaOrParent as TemplateSchema) : parent._schema + : undefined; + this._schema = parent === undefined ? (schemaOrParent as TemplateSchema) : parent._schema; // Lookup the definition if a key was passed in this.definition = - typeof nameOrDefinition === "string" - ? this._schema.getDefinition(nameOrDefinition) - : nameOrDefinition + typeof nameOrDefinition === "string" ? this._schema.getDefinition(nameOrDefinition) : nameOrDefinition; // Record allowed context if (this.definition.readerContext.length > 0) { - this.allowedContext = [] + this.allowedContext = []; // Copy parent allowed context - const upperSeen: { [upper: string]: boolean } = {} + const upperSeen: {[upper: string]: boolean} = {}; for (const context of parent?.allowedContext ?? []) { - this.allowedContext.push(context) - upperSeen[context.toUpperCase()] = true + this.allowedContext.push(context); + upperSeen[context.toUpperCase()] = true; } // Append context if unseen for (const context of this.definition.readerContext) { - const upper = context.toUpperCase() + const upper = context.toUpperCase(); if (!upperSeen[upper]) { - this.allowedContext.push(context) - upperSeen[upper] = true + this.allowedContext.push(context); + upperSeen[upper] = true; } } } else { - this.allowedContext = parent?.allowedContext ?? [] + this.allowedContext = parent?.allowedContext ?? []; } } public getScalarDefinitions(): ScalarDefinition[] { - return this._schema.getScalarDefinitions(this.definition) + return this._schema.getScalarDefinitions(this.definition); } public getDefinitionsOfType(type: DefinitionType): Definition[] { - return this._schema.getDefinitionsOfType(this.definition, type) + return this._schema.getDefinitionsOfType(this.definition, type); } } diff --git a/actions-workflow-parser/src/templates/schema/definition-type.ts b/actions-workflow-parser/src/templates/schema/definition-type.ts index e7194ce..624f118 100644 --- a/actions-workflow-parser/src/templates/schema/definition-type.ts +++ b/actions-workflow-parser/src/templates/schema/definition-type.ts @@ -6,5 +6,5 @@ export enum DefinitionType { Sequence, Mapping, OneOf, - AllowedValues, + AllowedValues } diff --git a/actions-workflow-parser/src/templates/schema/definition.ts b/actions-workflow-parser/src/templates/schema/definition.ts index febc341..bef24dd 100644 --- a/actions-workflow-parser/src/templates/schema/definition.ts +++ b/actions-workflow-parser/src/templates/schema/definition.ts @@ -1,7 +1,7 @@ -import { CONTEXT, DEFINITION, DESCRIPTION } from "../template-constants" -import { MappingToken } from "../tokens" -import { DefinitionType } from "./definition-type" -import { TemplateSchema } from "./template-schema" +import {CONTEXT, DEFINITION, DESCRIPTION} from "../template-constants"; +import {MappingToken} from "../tokens"; +import {DefinitionType} from "./definition-type"; +import {TemplateSchema} from "./template-schema"; /** * Defines the allowable schema for a user defined type @@ -11,75 +11,68 @@ export abstract class Definition { * Used by the template reader to determine allowed expression values and functions. * Also used by the template reader to validate function min/max parameters. */ - public readonly readerContext: string[] = [] + public readonly readerContext: string[] = []; /** * Used by the template evaluator to determine allowed expression values and functions. * The min/max parameter info is omitted. */ - public readonly evaluatorContext: string[] = [] + public readonly evaluatorContext: string[] = []; // A key to uniquely identify a definition - public readonly key: string + public readonly key: string; - public readonly description: string | undefined + public readonly description: string | undefined; public constructor(key: string, definition?: MappingToken) { - this.key = key + this.key = key; if (definition) { for (let i = 0; i < definition.count; ) { - const definitionKey = definition - .get(i) - .key.assertString(`${DEFINITION} key`) + const definitionKey = definition.get(i).key.assertString(`${DEFINITION} key`); switch (definitionKey.value) { case CONTEXT: { - const context = definition - .get(i) - .value.assertSequence(`${DEFINITION} ${CONTEXT}`) - definition.remove(i) - const seenReaderContext: { [key: string]: boolean } = {} - const seenEvaluatorContext: { [key: string]: boolean } = {} + const context = definition.get(i).value.assertSequence(`${DEFINITION} ${CONTEXT}`); + definition.remove(i); + const seenReaderContext: {[key: string]: boolean} = {}; + const seenEvaluatorContext: {[key: string]: boolean} = {}; for (const item of context) { - const itemStr = item.assertString(`${CONTEXT} item`).value - const upperItemStr = itemStr.toUpperCase() + const itemStr = item.assertString(`${CONTEXT} item`).value; + const upperItemStr = itemStr.toUpperCase(); if (seenReaderContext[upperItemStr]) { - throw new Error(`Duplicate context item '${itemStr}'`) + throw new Error(`Duplicate context item '${itemStr}'`); } - seenReaderContext[upperItemStr] = true - this.readerContext.push(itemStr) + seenReaderContext[upperItemStr] = true; + this.readerContext.push(itemStr); // Remove min/max parameter info - const paramIndex = itemStr.indexOf("(") - const modifiedItemStr = - paramIndex > 0 - ? itemStr.substr(0, paramIndex + 1) + ")" - : itemStr - const upperModifiedItemStr = modifiedItemStr.toUpperCase() + const paramIndex = itemStr.indexOf("("); + const modifiedItemStr = paramIndex > 0 ? itemStr.substr(0, paramIndex + 1) + ")" : itemStr; + const upperModifiedItemStr = modifiedItemStr.toUpperCase(); if (seenEvaluatorContext[upperModifiedItemStr]) { - throw new Error(`Duplicate context item '${modifiedItemStr}'`) + throw new Error(`Duplicate context item '${modifiedItemStr}'`); } - seenEvaluatorContext[upperModifiedItemStr] = true - this.evaluatorContext.push(modifiedItemStr) + seenEvaluatorContext[upperModifiedItemStr] = true; + this.evaluatorContext.push(modifiedItemStr); } - break + break; } case DESCRIPTION: { - const value = definition.get(i).value - this.description = value.assertString(DESCRIPTION).value - definition.remove(i) - break + const value = definition.get(i).value; + this.description = value.assertString(DESCRIPTION).value; + definition.remove(i); + break; } default: { - i++ - break + i++; + break; } } } } } - public abstract get definitionType(): DefinitionType + public abstract get definitionType(): DefinitionType; - public abstract validate(schema: TemplateSchema, name: string): void + public abstract validate(schema: TemplateSchema, name: string): void; } diff --git a/actions-workflow-parser/src/templates/schema/index.ts b/actions-workflow-parser/src/templates/schema/index.ts index 2b09e24..2460333 100644 --- a/actions-workflow-parser/src/templates/schema/index.ts +++ b/actions-workflow-parser/src/templates/schema/index.ts @@ -1 +1 @@ -export { TemplateSchema } from "./template-schema" +export {TemplateSchema} from "./template-schema"; diff --git a/actions-workflow-parser/src/templates/schema/mapping-definition.ts b/actions-workflow-parser/src/templates/schema/mapping-definition.ts index 90ee21a..e364b0e 100644 --- a/actions-workflow-parser/src/templates/schema/mapping-definition.ts +++ b/actions-workflow-parser/src/templates/schema/mapping-definition.ts @@ -1,117 +1,90 @@ -import { TemplateSchema } from "./template-schema" -import { - DEFINITION, - MAPPING, - PROPERTIES, - LOOSE_KEY_TYPE, - LOOSE_VALUE_TYPE, -} from "../template-constants" -import { MappingToken } from "../tokens" -import { Definition } from "./definition" -import { DefinitionType } from "./definition-type" -import { PropertyDefinition } from "./property-definition" +import {TemplateSchema} from "./template-schema"; +import {DEFINITION, MAPPING, PROPERTIES, LOOSE_KEY_TYPE, LOOSE_VALUE_TYPE} from "../template-constants"; +import {MappingToken} from "../tokens"; +import {Definition} from "./definition"; +import {DefinitionType} from "./definition-type"; +import {PropertyDefinition} from "./property-definition"; export class MappingDefinition extends Definition { - public readonly properties: { [key: string]: PropertyDefinition } = {} - public looseKeyType = "" - public looseValueType = "" + public readonly properties: {[key: string]: PropertyDefinition} = {}; + public looseKeyType = ""; + public looseValueType = ""; public constructor(key: string, definition?: MappingToken) { - super(key, definition) + super(key, definition); if (definition) { for (const definitionPair of definition) { - const definitionKey = definitionPair.key.assertString( - `${DEFINITION} key` - ) + const definitionKey = definitionPair.key.assertString(`${DEFINITION} key`); switch (definitionKey.value) { case MAPPING: { - const mapping = definitionPair.value.assertMapping( - `${DEFINITION} ${MAPPING}` - ) + const mapping = definitionPair.value.assertMapping(`${DEFINITION} ${MAPPING}`); for (const mappingPair of mapping) { - const mappingKey = mappingPair.key.assertString( - `${DEFINITION} ${MAPPING} key` - ) + const mappingKey = mappingPair.key.assertString(`${DEFINITION} ${MAPPING} key`); switch (mappingKey.value) { case PROPERTIES: { - const properties = mappingPair.value.assertMapping( - `${DEFINITION} ${MAPPING} ${PROPERTIES}` - ) + const properties = mappingPair.value.assertMapping(`${DEFINITION} ${MAPPING} ${PROPERTIES}`); for (const propertiesPair of properties) { - const propertyName = propertiesPair.key.assertString( - `${DEFINITION} ${MAPPING} ${PROPERTIES} key` - ) - this.properties[propertyName.value] = - new PropertyDefinition(propertiesPair.value) + const propertyName = propertiesPair.key.assertString(`${DEFINITION} ${MAPPING} ${PROPERTIES} key`); + this.properties[propertyName.value] = new PropertyDefinition(propertiesPair.value); } - break + break; } case LOOSE_KEY_TYPE: { - const looseKeyType = mappingPair.value.assertString( - `${DEFINITION} ${MAPPING} ${LOOSE_KEY_TYPE}` - ) - this.looseKeyType = looseKeyType.value - break + const looseKeyType = mappingPair.value.assertString(`${DEFINITION} ${MAPPING} ${LOOSE_KEY_TYPE}`); + this.looseKeyType = looseKeyType.value; + break; } case LOOSE_VALUE_TYPE: { - const looseValueType = mappingPair.value.assertString( - `${DEFINITION} ${MAPPING} ${LOOSE_VALUE_TYPE}` - ) - this.looseValueType = looseValueType.value - break + const looseValueType = mappingPair.value.assertString(`${DEFINITION} ${MAPPING} ${LOOSE_VALUE_TYPE}`); + this.looseValueType = looseValueType.value; + break; } default: // throws - mappingKey.assertUnexpectedValue( - `${DEFINITION} ${MAPPING} key` - ) - break + mappingKey.assertUnexpectedValue(`${DEFINITION} ${MAPPING} key`); + break; } } - break + break; } default: - definitionKey.assertUnexpectedValue(`${DEFINITION} key`) // throws + definitionKey.assertUnexpectedValue(`${DEFINITION} key`); // throws } } } } public override get definitionType(): DefinitionType { - return DefinitionType.Mapping + return DefinitionType.Mapping; } public override validate(schema: TemplateSchema, name: string): void { // Lookup loose key type if (this.looseKeyType) { - schema.getDefinition(this.looseKeyType) + schema.getDefinition(this.looseKeyType); // Lookup loose value type if (this.looseValueType) { - schema.getDefinition(this.looseValueType) + schema.getDefinition(this.looseValueType); } else { throw new Error( `Property '${LOOSE_KEY_TYPE}' is defined but '${LOOSE_VALUE_TYPE}' is not defined on '${name}'` - ) + ); } } // Otherwise validate loose value type not be defined else if (this.looseValueType) { - throw new Error( - `Property '${LOOSE_VALUE_TYPE}' is defined but '${LOOSE_KEY_TYPE}' is not defined on '${name}'` - ) + throw new Error(`Property '${LOOSE_VALUE_TYPE}' is defined but '${LOOSE_KEY_TYPE}' is not defined on '${name}'`); } // Lookup each property for (const propertyName of Object.keys(this.properties)) { - const propertyDef = this.properties[propertyName] + const propertyDef = this.properties[propertyName]; if (!propertyDef.type) { - throw new Error( - `Type not specified for the property '${propertyName}' on '${name}'` - ) + throw new Error(`Type not specified for the property '${propertyName}' on '${name}'`); } - schema.getDefinition(propertyDef.type) + schema.getDefinition(propertyDef.type); } } } diff --git a/actions-workflow-parser/src/templates/schema/null-definition.ts b/actions-workflow-parser/src/templates/schema/null-definition.ts index 6f23bfa..8b4389c 100644 --- a/actions-workflow-parser/src/templates/schema/null-definition.ts +++ b/actions-workflow-parser/src/templates/schema/null-definition.ts @@ -1,49 +1,43 @@ -import { TemplateSchema } from "./template-schema" -import { DEFINITION, NULL } from "../template-constants" -import { MappingToken, LiteralToken } from "../tokens" -import { DefinitionType } from "./definition-type" -import { ScalarDefinition } from "./scalar-definition" -import { TokenType } from "../tokens/types" +import {TemplateSchema} from "./template-schema"; +import {DEFINITION, NULL} from "../template-constants"; +import {MappingToken, LiteralToken} from "../tokens"; +import {DefinitionType} from "./definition-type"; +import {ScalarDefinition} from "./scalar-definition"; +import {TokenType} from "../tokens/types"; export class NullDefinition extends ScalarDefinition { public constructor(key: string, definition?: MappingToken) { - super(key, definition) + super(key, definition); if (definition) { for (const definitionPair of definition) { - const definitionKey = definitionPair.key.assertString( - `${DEFINITION} key` - ) + const definitionKey = definitionPair.key.assertString(`${DEFINITION} key`); switch (definitionKey.value) { case NULL: { - const mapping = definitionPair.value.assertMapping( - `${DEFINITION} ${NULL}` - ) + const mapping = definitionPair.value.assertMapping(`${DEFINITION} ${NULL}`); for (const mappingPair of mapping) { - const mappingKey = mappingPair.key.assertString( - `${DEFINITION} ${NULL} key` - ) + const mappingKey = mappingPair.key.assertString(`${DEFINITION} ${NULL} key`); switch (mappingKey.value) { default: // throws - mappingKey.assertUnexpectedValue(`${DEFINITION} ${NULL} key`) - break + mappingKey.assertUnexpectedValue(`${DEFINITION} ${NULL} key`); + break; } } - break + break; } default: - definitionKey.assertUnexpectedValue(`${DEFINITION} key`) // throws + definitionKey.assertUnexpectedValue(`${DEFINITION} key`); // throws } } } } public override get definitionType(): DefinitionType { - return DefinitionType.Null + return DefinitionType.Null; } public override isMatch(literal: LiteralToken): boolean { - return literal.templateTokenType === TokenType.Null + return literal.templateTokenType === TokenType.Null; } public override validate(schema: TemplateSchema, name: string): void {} diff --git a/actions-workflow-parser/src/templates/schema/number-definition.ts b/actions-workflow-parser/src/templates/schema/number-definition.ts index 74dc982..a7e90b0 100644 --- a/actions-workflow-parser/src/templates/schema/number-definition.ts +++ b/actions-workflow-parser/src/templates/schema/number-definition.ts @@ -1,51 +1,43 @@ -import { TemplateSchema } from "./template-schema" -import { DEFINITION, NUMBER } from "../template-constants" -import { MappingToken, LiteralToken } from "../tokens" -import { DefinitionType } from "./definition-type" -import { ScalarDefinition } from "./scalar-definition" -import { TokenType } from "../tokens/types" +import {TemplateSchema} from "./template-schema"; +import {DEFINITION, NUMBER} from "../template-constants"; +import {MappingToken, LiteralToken} from "../tokens"; +import {DefinitionType} from "./definition-type"; +import {ScalarDefinition} from "./scalar-definition"; +import {TokenType} from "../tokens/types"; export class NumberDefinition extends ScalarDefinition { public constructor(key: string, definition?: MappingToken) { - super(key, definition) + super(key, definition); if (definition) { for (const definitionPair of definition) { - const definitionKey = definitionPair.key.assertString( - `${DEFINITION} key` - ) + const definitionKey = definitionPair.key.assertString(`${DEFINITION} key`); switch (definitionKey.value) { case NUMBER: { - const mapping = definitionPair.value.assertMapping( - `${DEFINITION} ${NUMBER}` - ) + const mapping = definitionPair.value.assertMapping(`${DEFINITION} ${NUMBER}`); for (const mappingPair of mapping) { - const mappingKey = mappingPair.key.assertString( - `${DEFINITION} ${NUMBER} key` - ) + const mappingKey = mappingPair.key.assertString(`${DEFINITION} ${NUMBER} key`); switch (mappingKey.value) { default: // throws - mappingKey.assertUnexpectedValue( - `${DEFINITION} ${NUMBER} key` - ) - break + mappingKey.assertUnexpectedValue(`${DEFINITION} ${NUMBER} key`); + break; } } - break + break; } default: - definitionKey.assertUnexpectedValue(`${DEFINITION} key`) // throws + definitionKey.assertUnexpectedValue(`${DEFINITION} key`); // throws } } } } public override get definitionType(): DefinitionType { - return DefinitionType.Number + return DefinitionType.Number; } public override isMatch(literal: LiteralToken): boolean { - return literal.templateTokenType === TokenType.Number + return literal.templateTokenType === TokenType.Number; } public override validate(schema: TemplateSchema, name: string): void {} diff --git a/actions-workflow-parser/src/templates/schema/one-of-definition.ts b/actions-workflow-parser/src/templates/schema/one-of-definition.ts index ec1e4bb..ab2fd99 100644 --- a/actions-workflow-parser/src/templates/schema/one-of-definition.ts +++ b/actions-workflow-parser/src/templates/schema/one-of-definition.ts @@ -1,4 +1,4 @@ -import { TemplateSchema } from "./template-schema" +import {TemplateSchema} from "./template-schema"; import { DEFINITION, ONE_OF, @@ -9,180 +9,151 @@ import { SCALAR, CONSTANT, LOOSE_KEY_TYPE, - ALLOWED_VALUES, -} from "../template-constants" -import { MappingToken } from "../tokens" -import { BooleanDefinition } from "./boolean-definition" -import { Definition } from "./definition" -import { DefinitionType } from "./definition-type" -import { MappingDefinition } from "./mapping-definition" -import { NullDefinition } from "./null-definition" -import { NumberDefinition } from "./number-definition" -import { SequenceDefinition } from "./sequence-definition" -import { StringDefinition } from "./string-definition" -import { PropertyDefinition } from "./property-definition" + ALLOWED_VALUES +} from "../template-constants"; +import {MappingToken} from "../tokens"; +import {BooleanDefinition} from "./boolean-definition"; +import {Definition} from "./definition"; +import {DefinitionType} from "./definition-type"; +import {MappingDefinition} from "./mapping-definition"; +import {NullDefinition} from "./null-definition"; +import {NumberDefinition} from "./number-definition"; +import {SequenceDefinition} from "./sequence-definition"; +import {StringDefinition} from "./string-definition"; +import {PropertyDefinition} from "./property-definition"; /** * Must resolve to exactly one of the referenced definitions */ export class OneOfDefinition extends Definition { - public readonly oneOf: string[] = [] - public readonly oneOfPrefix: string[] = [] + public readonly oneOf: string[] = []; + public readonly oneOfPrefix: string[] = []; public constructor(key: string, definition?: MappingToken) { - super(key, definition) + super(key, definition); if (definition) { for (const definitionPair of definition) { - const definitionKey = definitionPair.key.assertString( - `${DEFINITION} key` - ) + const definitionKey = definitionPair.key.assertString(`${DEFINITION} key`); switch (definitionKey.value) { case ONE_OF: { - const oneOf = definitionPair.value.assertSequence( - `${DEFINITION} ${ONE_OF}` - ) + const oneOf = definitionPair.value.assertSequence(`${DEFINITION} ${ONE_OF}`); for (const item of oneOf) { - const oneOfItem = item.assertString( - `${DEFINITION} ${ONE_OF} item` - ) - this.oneOf.push(oneOfItem.value) + const oneOfItem = item.assertString(`${DEFINITION} ${ONE_OF} item`); + this.oneOf.push(oneOfItem.value); } - break + break; } case ALLOWED_VALUES: { - const oneOf = definitionPair.value.assertSequence( - `${DEFINITION} ${ALLOWED_VALUES}` - ) + const oneOf = definitionPair.value.assertSequence(`${DEFINITION} ${ALLOWED_VALUES}`); for (const item of oneOf) { - const oneOfItem = item.assertString( - `${DEFINITION} ${ONE_OF} item` - ) - this.oneOf.push(this.key + "-" + oneOfItem.value) + const oneOfItem = item.assertString(`${DEFINITION} ${ONE_OF} item`); + this.oneOf.push(this.key + "-" + oneOfItem.value); } - break + break; } default: // throws - definitionKey.assertUnexpectedValue(`${DEFINITION} key`) - break + definitionKey.assertUnexpectedValue(`${DEFINITION} key`); + break; } } } } public override get definitionType(): DefinitionType { - return DefinitionType.OneOf + return DefinitionType.OneOf; } public override validate(schema: TemplateSchema, name: string): void { if (this.oneOf.length === 0) { - throw new Error(`'${name}' does not contain any references`) + throw new Error(`'${name}' does not contain any references`); } - let foundLooseKeyType = false - const mappingDefinitions: MappingDefinition[] = [] - let allowedValuesDefinition: OneOfDefinition | undefined - let sequenceDefinition: SequenceDefinition | undefined - let nullDefinition: NullDefinition | undefined - let booleanDefinition: BooleanDefinition | undefined - let numberDefinition: NumberDefinition | undefined - const stringDefinitions: StringDefinition[] = [] - const seenNestedTypes: { [key: string]: boolean } = {} + let foundLooseKeyType = false; + const mappingDefinitions: MappingDefinition[] = []; + let allowedValuesDefinition: OneOfDefinition | undefined; + let sequenceDefinition: SequenceDefinition | undefined; + let nullDefinition: NullDefinition | undefined; + let booleanDefinition: BooleanDefinition | undefined; + let numberDefinition: NumberDefinition | undefined; + const stringDefinitions: StringDefinition[] = []; + const seenNestedTypes: {[key: string]: boolean} = {}; for (const nestedType of this.oneOf) { if (seenNestedTypes[nestedType]) { - throw new Error( - `'${name}' contains duplicate nested type '${nestedType}'` - ) + throw new Error(`'${name}' contains duplicate nested type '${nestedType}'`); } - seenNestedTypes[nestedType] = true + seenNestedTypes[nestedType] = true; - const nestedDefinition = schema.getDefinition(nestedType) + const nestedDefinition = schema.getDefinition(nestedType); if (nestedDefinition.readerContext.length > 0) { throw new Error( `'${name}' is a one-of definition and references another definition that defines context. This is currently not supported.` - ) + ); } switch (nestedDefinition.definitionType) { case DefinitionType.Mapping: { - const mappingDefinition = nestedDefinition as MappingDefinition - mappingDefinitions.push(mappingDefinition) + const mappingDefinition = nestedDefinition as MappingDefinition; + mappingDefinitions.push(mappingDefinition); if (mappingDefinition.looseKeyType) { - foundLooseKeyType = true + foundLooseKeyType = true; } - break + break; } case DefinitionType.Sequence: { // Multiple sequence definitions not allowed if (sequenceDefinition) { - throw new Error( - `'${name}' refers to more than one definition of type '${SEQUENCE}'` - ) + throw new Error(`'${name}' refers to more than one definition of type '${SEQUENCE}'`); } - sequenceDefinition = nestedDefinition as SequenceDefinition - break + sequenceDefinition = nestedDefinition as SequenceDefinition; + break; } case DefinitionType.Null: { // Multiple null definitions not allowed if (nullDefinition) { - throw new Error( - `'${name}' refers to more than one definition of type '${NULL}'` - ) + throw new Error(`'${name}' refers to more than one definition of type '${NULL}'`); } - nullDefinition = nestedDefinition as NullDefinition - break + nullDefinition = nestedDefinition as NullDefinition; + break; } case DefinitionType.Boolean: { // Multiple boolean definitions not allowed if (booleanDefinition) { - throw new Error( - `'${name}' refers to more than one definition of type '${BOOLEAN}'` - ) + throw new Error(`'${name}' refers to more than one definition of type '${BOOLEAN}'`); } - booleanDefinition = nestedDefinition as BooleanDefinition - break + booleanDefinition = nestedDefinition as BooleanDefinition; + break; } case DefinitionType.Number: { // Multiple number definitions not allowed if (numberDefinition) { - throw new Error( - `'${name}' refers to more than one definition of type '${NUMBER}'` - ) + throw new Error(`'${name}' refers to more than one definition of type '${NUMBER}'`); } - numberDefinition = nestedDefinition as NumberDefinition - break + numberDefinition = nestedDefinition as NumberDefinition; + break; } case DefinitionType.String: { - const stringDefinition = nestedDefinition as StringDefinition + const stringDefinition = nestedDefinition as StringDefinition; // Multiple string definitions - if ( - stringDefinitions.length > 0 && - (!stringDefinitions[0].constant || !stringDefinition.constant) - ) { - throw new Error( - `'${name}' refers to more than one '${SCALAR}', but some do not set '${CONSTANT}'` - ) + if (stringDefinitions.length > 0 && (!stringDefinitions[0].constant || !stringDefinition.constant)) { + throw new Error(`'${name}' refers to more than one '${SCALAR}', but some do not set '${CONSTANT}'`); } - stringDefinitions.push(stringDefinition) - break + stringDefinitions.push(stringDefinition); + break; } case DefinitionType.OneOf: { // Multiple allowed-values definitions not allowed if (allowedValuesDefinition) { - throw new Error( - `'${name}' contains multiple allowed-values definitions` - ) + throw new Error(`'${name}' contains multiple allowed-values definitions`); } - allowedValuesDefinition = nestedDefinition as OneOfDefinition - break + allowedValuesDefinition = nestedDefinition as OneOfDefinition; + break; } default: - throw new Error( - `'${name}' refers to a definition with type '${nestedDefinition.definitionType}'` - ) + throw new Error(`'${name}' refers to a definition with type '${nestedDefinition.definitionType}'`); } } @@ -190,31 +161,30 @@ export class OneOfDefinition extends Definition { if (foundLooseKeyType) { throw new Error( `'${name}' refers to two mappings and at least one sets '${LOOSE_KEY_TYPE}'. This is not currently supported.` - ) + ); } - const seenProperties: { [key: string]: PropertyDefinition } = {} + const seenProperties: {[key: string]: PropertyDefinition} = {}; for (const mappingDefinition of mappingDefinitions) { for (const propertyName of Object.keys(mappingDefinition.properties)) { - const newPropertyDef = mappingDefinition.properties[propertyName] + const newPropertyDef = mappingDefinition.properties[propertyName]; // Already seen - const existingPropertyDef: PropertyDefinition | undefined = - seenProperties[propertyName] + const existingPropertyDef: PropertyDefinition | undefined = seenProperties[propertyName]; if (existingPropertyDef) { // Types match if (existingPropertyDef.type === newPropertyDef.type) { - continue + continue; } // Collision throw new Error( `'${name}' contains two mappings with the same property, but each refers to a different type. All matching properties must refer to the same type.` - ) + ); } // New else { - seenProperties[propertyName] = newPropertyDef + seenProperties[propertyName] = newPropertyDef; } } } diff --git a/actions-workflow-parser/src/templates/schema/property-definition.ts b/actions-workflow-parser/src/templates/schema/property-definition.ts index 886a8e7..0d8bf0a 100644 --- a/actions-workflow-parser/src/templates/schema/property-definition.ts +++ b/actions-workflow-parser/src/templates/schema/property-definition.ts @@ -1,44 +1,31 @@ -import { - MAPPING_PROPERTY_VALUE, - TYPE, - REQUIRED, - DESCRIPTION, -} from "../template-constants" -import { TemplateToken, StringToken } from "../tokens" -import { TokenType } from "../tokens/types" +import {MAPPING_PROPERTY_VALUE, TYPE, REQUIRED, DESCRIPTION} from "../template-constants"; +import {TemplateToken, StringToken} from "../tokens"; +import {TokenType} from "../tokens/types"; export class PropertyDefinition { - public readonly type: string = "" - public readonly required: boolean = false - public readonly description: string | undefined + public readonly type: string = ""; + public readonly required: boolean = false; + public readonly description: string | undefined; public constructor(token: TemplateToken) { if (token.templateTokenType === TokenType.String) { - this.type = (token as StringToken).value + this.type = (token as StringToken).value; } else { - const mapping = token.assertMapping(MAPPING_PROPERTY_VALUE) + const mapping = token.assertMapping(MAPPING_PROPERTY_VALUE); for (const mappingPair of mapping) { - const mappingKey = mappingPair.key.assertString( - `${MAPPING_PROPERTY_VALUE} key` - ) + const mappingKey = mappingPair.key.assertString(`${MAPPING_PROPERTY_VALUE} key`); switch (mappingKey.value) { case TYPE: - this.type = mappingPair.value.assertString( - `${MAPPING_PROPERTY_VALUE} ${TYPE}` - ).value - break + this.type = mappingPair.value.assertString(`${MAPPING_PROPERTY_VALUE} ${TYPE}`).value; + break; case REQUIRED: - this.required = mappingPair.value.assertBoolean( - `${MAPPING_PROPERTY_VALUE} ${REQUIRED}` - ).value - break + this.required = mappingPair.value.assertBoolean(`${MAPPING_PROPERTY_VALUE} ${REQUIRED}`).value; + break; case DESCRIPTION: - this.description = mappingPair.value.assertString( - `${MAPPING_PROPERTY_VALUE} ${DESCRIPTION}` - ).value - break + this.description = mappingPair.value.assertString(`${MAPPING_PROPERTY_VALUE} ${DESCRIPTION}`).value; + break; default: - mappingKey.assertUnexpectedValue(`${MAPPING_PROPERTY_VALUE} key`) // throws + mappingKey.assertUnexpectedValue(`${MAPPING_PROPERTY_VALUE} key`); // throws } } } diff --git a/actions-workflow-parser/src/templates/schema/scalar-definition.ts b/actions-workflow-parser/src/templates/schema/scalar-definition.ts index 900ef71..bacb2f3 100644 --- a/actions-workflow-parser/src/templates/schema/scalar-definition.ts +++ b/actions-workflow-parser/src/templates/schema/scalar-definition.ts @@ -1,10 +1,10 @@ -import { LiteralToken, MappingToken } from "../tokens" -import { Definition } from "./definition" +import {LiteralToken, MappingToken} from "../tokens"; +import {Definition} from "./definition"; export abstract class ScalarDefinition extends Definition { public constructor(key: string, definition?: MappingToken) { - super(key, definition) + super(key, definition); } - public abstract isMatch(literal: LiteralToken): boolean + public abstract isMatch(literal: LiteralToken): boolean; } diff --git a/actions-workflow-parser/src/templates/schema/sequence-definition.ts b/actions-workflow-parser/src/templates/schema/sequence-definition.ts index 59d2e2f..448aec8 100644 --- a/actions-workflow-parser/src/templates/schema/sequence-definition.ts +++ b/actions-workflow-parser/src/templates/schema/sequence-definition.ts @@ -1,63 +1,53 @@ -import { DEFINITION, SEQUENCE, ITEM_TYPE } from "../template-constants" -import { MappingToken } from "../tokens" -import { Definition } from "./definition" -import { DefinitionType } from "./definition-type" -import { TemplateSchema } from "./template-schema" +import {DEFINITION, SEQUENCE, ITEM_TYPE} from "../template-constants"; +import {MappingToken} from "../tokens"; +import {Definition} from "./definition"; +import {DefinitionType} from "./definition-type"; +import {TemplateSchema} from "./template-schema"; export class SequenceDefinition extends Definition { - public itemType = "" + public itemType = ""; public constructor(key: string, definition?: MappingToken) { - super(key, definition) + super(key, definition); if (definition) { for (const definitionPair of definition) { - const definitionKey = definitionPair.key.assertString( - `${DEFINITION} key` - ) + const definitionKey = definitionPair.key.assertString(`${DEFINITION} key`); switch (definitionKey.value) { case SEQUENCE: { - const mapping = definitionPair.value.assertMapping( - `${DEFINITION} ${SEQUENCE}` - ) + const mapping = definitionPair.value.assertMapping(`${DEFINITION} ${SEQUENCE}`); for (const mappingPair of mapping) { - const mappingKey = mappingPair.key.assertString( - `${DEFINITION} ${SEQUENCE} key` - ) + const mappingKey = mappingPair.key.assertString(`${DEFINITION} ${SEQUENCE} key`); switch (mappingKey.value) { case ITEM_TYPE: { - const itemType = mappingPair.value.assertString( - `${DEFINITION} ${SEQUENCE} ${ITEM_TYPE}` - ) - this.itemType = itemType.value - break + const itemType = mappingPair.value.assertString(`${DEFINITION} ${SEQUENCE} ${ITEM_TYPE}`); + this.itemType = itemType.value; + break; } default: // throws - mappingKey.assertUnexpectedValue( - `${DEFINITION} ${SEQUENCE} key` - ) - break + mappingKey.assertUnexpectedValue(`${DEFINITION} ${SEQUENCE} key`); + break; } } - break + break; } default: - definitionKey.assertUnexpectedValue(`${DEFINITION} key`) // throws + definitionKey.assertUnexpectedValue(`${DEFINITION} key`); // throws } } } } public override get definitionType(): DefinitionType { - return DefinitionType.Sequence + return DefinitionType.Sequence; } public override validate(schema: TemplateSchema, name: string): void { if (!this.itemType) { - throw new Error(`'${name}' does not defined '${ITEM_TYPE}'`) + throw new Error(`'${name}' does not defined '${ITEM_TYPE}'`); } // Lookup item type - schema.getDefinition(this.itemType) + schema.getDefinition(this.itemType); } } diff --git a/actions-workflow-parser/src/templates/schema/string-definition.ts b/actions-workflow-parser/src/templates/schema/string-definition.ts index 0a80b1f..f1812ad 100644 --- a/actions-workflow-parser/src/templates/schema/string-definition.ts +++ b/actions-workflow-parser/src/templates/schema/string-definition.ts @@ -1,113 +1,88 @@ -import { - CONSTANT, - DEFINITION, - IGNORE_CASE, - IS_EXPRESSION, - REQUIRE_NON_EMPTY, - STRING, -} from "../template-constants" -import { LiteralToken, MappingToken, StringToken } from "../tokens" -import { TokenType } from "../tokens/types" -import { DefinitionType } from "./definition-type" -import { ScalarDefinition } from "./scalar-definition" -import { TemplateSchema } from "./template-schema" +import {CONSTANT, DEFINITION, IGNORE_CASE, IS_EXPRESSION, REQUIRE_NON_EMPTY, STRING} from "../template-constants"; +import {LiteralToken, MappingToken, StringToken} from "../tokens"; +import {TokenType} from "../tokens/types"; +import {DefinitionType} from "./definition-type"; +import {ScalarDefinition} from "./scalar-definition"; +import {TemplateSchema} from "./template-schema"; export class StringDefinition extends ScalarDefinition { - public constant = "" - public ignoreCase = false - public requireNonEmpty = false - public isExpression = false + public constant = ""; + public ignoreCase = false; + public requireNonEmpty = false; + public isExpression = false; public constructor(key: string, definition?: MappingToken) { - super(key, definition) + super(key, definition); if (definition) { for (const definitionPair of definition) { - const definitionKey = definitionPair.key.assertString( - `${DEFINITION} key` - ) + const definitionKey = definitionPair.key.assertString(`${DEFINITION} key`); switch (definitionKey.value) { case STRING: { - const mapping = definitionPair.value.assertMapping( - `${DEFINITION} ${STRING}` - ) + const mapping = definitionPair.value.assertMapping(`${DEFINITION} ${STRING}`); for (const mappingPair of mapping) { - const mappingKey = mappingPair.key.assertString( - `${DEFINITION} ${STRING} key` - ) + const mappingKey = mappingPair.key.assertString(`${DEFINITION} ${STRING} key`); switch (mappingKey.value) { case CONSTANT: { - const constantStringToken = mappingPair.value.assertString( - `${DEFINITION} ${STRING} ${CONSTANT}` - ) - this.constant = constantStringToken.value - break + const constantStringToken = mappingPair.value.assertString(`${DEFINITION} ${STRING} ${CONSTANT}`); + this.constant = constantStringToken.value; + break; } case IGNORE_CASE: { - const ignoreCaseBooleanToken = - mappingPair.value.assertBoolean( - `${DEFINITION} ${STRING} ${IGNORE_CASE}` - ) - this.ignoreCase = ignoreCaseBooleanToken.value - break + const ignoreCaseBooleanToken = mappingPair.value.assertBoolean( + `${DEFINITION} ${STRING} ${IGNORE_CASE}` + ); + this.ignoreCase = ignoreCaseBooleanToken.value; + break; } case REQUIRE_NON_EMPTY: { - const requireNonEmptyBooleanToken = - mappingPair.value.assertBoolean( - `${DEFINITION} ${STRING} ${REQUIRE_NON_EMPTY}` - ) - this.requireNonEmpty = requireNonEmptyBooleanToken.value - break + const requireNonEmptyBooleanToken = mappingPair.value.assertBoolean( + `${DEFINITION} ${STRING} ${REQUIRE_NON_EMPTY}` + ); + this.requireNonEmpty = requireNonEmptyBooleanToken.value; + break; } case IS_EXPRESSION: { - const isExpressionToken = mappingPair.value.assertBoolean( - `${DEFINITION} ${STRING} ${IS_EXPRESSION}` - ) - this.isExpression = isExpressionToken.value - break + const isExpressionToken = mappingPair.value.assertBoolean(`${DEFINITION} ${STRING} ${IS_EXPRESSION}`); + this.isExpression = isExpressionToken.value; + break; } default: // throws - mappingKey.assertUnexpectedValue( - `${DEFINITION} ${STRING} key` - ) - break + mappingKey.assertUnexpectedValue(`${DEFINITION} ${STRING} key`); + break; } } - break + break; } default: - definitionKey.assertUnexpectedValue(`${DEFINITION} key`) // throws + definitionKey.assertUnexpectedValue(`${DEFINITION} key`); // throws } } } } public override get definitionType(): DefinitionType { - return DefinitionType.String + return DefinitionType.String; } public override isMatch(literal: LiteralToken): boolean { if (literal.templateTokenType === TokenType.String) { - const value = (literal as StringToken).value + const value = (literal as StringToken).value; if (this.constant) { - return this.ignoreCase - ? this.constant.toUpperCase() === value.toUpperCase() - : this.constant === value + return this.ignoreCase ? this.constant.toUpperCase() === value.toUpperCase() : this.constant === value; } else if (this.requireNonEmpty) { - return !!value + return !!value; } else { - return true + return true; } } - return false + return false; } public override validate(schema: TemplateSchema, name: string): void { if (this.constant && this.requireNonEmpty) { - throw new Error( - `Properties '${CONSTANT}' and '${REQUIRE_NON_EMPTY}' cannot both be set` - ) + throw new Error(`Properties '${CONSTANT}' and '${REQUIRE_NON_EMPTY}' cannot both be set`); } } } diff --git a/actions-workflow-parser/src/templates/schema/template-schema.ts b/actions-workflow-parser/src/templates/schema/template-schema.ts index 6c5222c..5255b07 100644 --- a/actions-workflow-parser/src/templates/schema/template-schema.ts +++ b/actions-workflow-parser/src/templates/schema/template-schema.ts @@ -1,5 +1,5 @@ -import { TokenType } from "../../templates/tokens/types" -import { ObjectReader } from "../object-reader" +import {TokenType} from "../../templates/tokens/types"; +import {ObjectReader} from "../object-reader"; import { ALLOWED_VALUES, ANY, @@ -42,192 +42,149 @@ import { STRING_DEFINITION_PROPERTIES, TEMPLATE_SCHEMA, TYPE, - VERSION, -} from "../template-constants" -import { TemplateContext, TemplateValidationErrors } from "../template-context" -import { readTemplate } from "../template-reader" -import { MappingToken, SequenceToken, StringToken } from "../tokens" -import { NoOperationTraceWriter } from "../trace-writer" -import { BooleanDefinition } from "./boolean-definition" -import { Definition } from "./definition" -import { DefinitionType } from "./definition-type" -import { MappingDefinition } from "./mapping-definition" -import { NullDefinition } from "./null-definition" -import { NumberDefinition } from "./number-definition" -import { OneOfDefinition } from "./one-of-definition" -import { PropertyDefinition } from "./property-definition" -import { ScalarDefinition } from "./scalar-definition" -import { SequenceDefinition } from "./sequence-definition" -import { StringDefinition } from "./string-definition" + VERSION +} from "../template-constants"; +import {TemplateContext, TemplateValidationErrors} from "../template-context"; +import {readTemplate} from "../template-reader"; +import {MappingToken, SequenceToken, StringToken} from "../tokens"; +import {NoOperationTraceWriter} from "../trace-writer"; +import {BooleanDefinition} from "./boolean-definition"; +import {Definition} from "./definition"; +import {DefinitionType} from "./definition-type"; +import {MappingDefinition} from "./mapping-definition"; +import {NullDefinition} from "./null-definition"; +import {NumberDefinition} from "./number-definition"; +import {OneOfDefinition} from "./one-of-definition"; +import {PropertyDefinition} from "./property-definition"; +import {ScalarDefinition} from "./scalar-definition"; +import {SequenceDefinition} from "./sequence-definition"; +import {StringDefinition} from "./string-definition"; /** * This models the root schema object and contains definitions */ export class TemplateSchema { - private static readonly _definitionNamePattern = /^[a-zA-Z_][a-zA-Z0-9_-]*$/ - private static _internalSchema: TemplateSchema | undefined - public readonly definitions: { [key: string]: Definition } = {} - public readonly version: string = "" + private static readonly _definitionNamePattern = /^[a-zA-Z_][a-zA-Z0-9_-]*$/; + private static _internalSchema: TemplateSchema | undefined; + public readonly definitions: {[key: string]: Definition} = {}; + public readonly version: string = ""; public constructor(mapping?: MappingToken) { // Add built-in type: null - this.definitions[NULL] = new NullDefinition(NULL) + this.definitions[NULL] = new NullDefinition(NULL); // Add built-in type: boolean - this.definitions[BOOLEAN] = new BooleanDefinition(BOOLEAN) + this.definitions[BOOLEAN] = new BooleanDefinition(BOOLEAN); // Add built-in type: number - this.definitions[NUMBER] = new NumberDefinition(NUMBER) + this.definitions[NUMBER] = new NumberDefinition(NUMBER); // Add built-in type: string - this.definitions[STRING] = new StringDefinition(STRING) + this.definitions[STRING] = new StringDefinition(STRING); // Add built-in type: sequence - const sequenceDefinition = new SequenceDefinition(SEQUENCE) - sequenceDefinition.itemType = ANY - this.definitions[sequenceDefinition.key] = sequenceDefinition + const sequenceDefinition = new SequenceDefinition(SEQUENCE); + sequenceDefinition.itemType = ANY; + this.definitions[sequenceDefinition.key] = sequenceDefinition; // Add built-in type: mapping - const mappingDefinition = new MappingDefinition(MAPPING) - mappingDefinition.looseKeyType = STRING - mappingDefinition.looseValueType = ANY - this.definitions[mappingDefinition.key] = mappingDefinition + const mappingDefinition = new MappingDefinition(MAPPING); + mappingDefinition.looseKeyType = STRING; + mappingDefinition.looseValueType = ANY; + this.definitions[mappingDefinition.key] = mappingDefinition; // Add built-in type: any - const anyDefinition = new OneOfDefinition(ANY) - anyDefinition.oneOf.push(NULL) - anyDefinition.oneOf.push(BOOLEAN) - anyDefinition.oneOf.push(NUMBER) - anyDefinition.oneOf.push(STRING) - anyDefinition.oneOf.push(SEQUENCE) - anyDefinition.oneOf.push(MAPPING) - this.definitions[anyDefinition.key] = anyDefinition + const anyDefinition = new OneOfDefinition(ANY); + anyDefinition.oneOf.push(NULL); + anyDefinition.oneOf.push(BOOLEAN); + anyDefinition.oneOf.push(NUMBER); + anyDefinition.oneOf.push(STRING); + anyDefinition.oneOf.push(SEQUENCE); + anyDefinition.oneOf.push(MAPPING); + this.definitions[anyDefinition.key] = anyDefinition; if (mapping) { for (const pair of mapping) { - const key = pair.key.assertString(`${TEMPLATE_SCHEMA} key`) + const key = pair.key.assertString(`${TEMPLATE_SCHEMA} key`); switch (key.value) { case VERSION: { - this.version = pair.value.assertString( - `${TEMPLATE_SCHEMA} ${VERSION}` - ).value - break + this.version = pair.value.assertString(`${TEMPLATE_SCHEMA} ${VERSION}`).value; + break; } case DEFINITIONS: { - const definitions = pair.value.assertMapping( - `${TEMPLATE_SCHEMA} ${DEFINITIONS}` - ) + const definitions = pair.value.assertMapping(`${TEMPLATE_SCHEMA} ${DEFINITIONS}`); for (const definitionsPair of definitions) { - const definitionsKey = definitionsPair.key.assertString( - `${TEMPLATE_SCHEMA} ${DEFINITIONS} key` - ) - const definitionsValue = definitionsPair.value.assertMapping( - `${TEMPLATE_SCHEMA} ${DEFINITIONS} value` - ) - let definition: Definition | undefined + const definitionsKey = definitionsPair.key.assertString(`${TEMPLATE_SCHEMA} ${DEFINITIONS} key`); + const definitionsValue = definitionsPair.value.assertMapping(`${TEMPLATE_SCHEMA} ${DEFINITIONS} value`); + let definition: Definition | undefined; for (const definitionPair of definitionsValue) { - const definitionKey = definitionPair.key.assertString( - `${DEFINITION} key` - ) - const mappingToken = definitionsPair.value as MappingToken + const definitionKey = definitionPair.key.assertString(`${DEFINITION} key`); + const mappingToken = definitionsPair.value as MappingToken; switch (definitionKey.value) { case NULL: - definition = new NullDefinition( - definitionsKey.value, - definitionsValue - ) - break + definition = new NullDefinition(definitionsKey.value, definitionsValue); + break; case BOOLEAN: - definition = new BooleanDefinition( - definitionsKey.value, - definitionsValue - ) - break + definition = new BooleanDefinition(definitionsKey.value, definitionsValue); + break; case NUMBER: - definition = new NumberDefinition( - definitionsKey.value, - definitionsValue - ) - break + definition = new NumberDefinition(definitionsKey.value, definitionsValue); + break; case STRING: - definition = new StringDefinition( - definitionsKey.value, - definitionsValue - ) - break + definition = new StringDefinition(definitionsKey.value, definitionsValue); + break; case SEQUENCE: - definition = new SequenceDefinition( - definitionsKey.value, - definitionsValue - ) - break + definition = new SequenceDefinition(definitionsKey.value, definitionsValue); + break; case MAPPING: - definition = new MappingDefinition( - definitionsKey.value, - definitionsValue - ) - break + definition = new MappingDefinition(definitionsKey.value, definitionsValue); + break; case ONE_OF: - definition = new OneOfDefinition( - definitionsKey.value, - definitionsValue - ) - break + definition = new OneOfDefinition(definitionsKey.value, definitionsValue); + break; case ALLOWED_VALUES: // Change the allowed-values definition into a one-of definition and its corresponding string definitions for (const item of mappingToken) { if (item.value.templateTokenType === TokenType.Sequence) { // Create a new string definition for each StringToken in the sequence - const sequenceToken = item.value as SequenceToken + const sequenceToken = item.value as SequenceToken; for (const activity of sequenceToken) { if (activity.templateTokenType === TokenType.String) { - const stringToken = activity as StringToken - const allowedValuesKey = - definitionsKey.value + "-" + stringToken.value - const allowedValuesDef = new StringDefinition( - allowedValuesKey - ) - allowedValuesDef.constant = - stringToken.toDisplayString() - this.definitions[allowedValuesKey] = - allowedValuesDef + const stringToken = activity as StringToken; + const allowedValuesKey = definitionsKey.value + "-" + stringToken.value; + const allowedValuesDef = new StringDefinition(allowedValuesKey); + allowedValuesDef.constant = stringToken.toDisplayString(); + this.definitions[allowedValuesKey] = allowedValuesDef; } } } } - definition = new OneOfDefinition( - definitionsKey.value, - definitionsValue - ) - break + definition = new OneOfDefinition(definitionsKey.value, definitionsValue); + break; case CONTEXT: case DESCRIPTION: - continue + continue; default: // throws - definitionKey.assertUnexpectedValue( - `${DEFINITION} mapping key` - ) - break + definitionKey.assertUnexpectedValue(`${DEFINITION} mapping key`); + break; } - break + break; } if (!definition) { - throw new Error( - `Not enough information to construct definition '${definitionsKey.value}'` - ) + throw new Error(`Not enough information to construct definition '${definitionsKey.value}'`); } - this.definitions[definitionsKey.value] = definition + this.definitions[definitionsKey.value] = definition; } - break + break; } default: // throws - key.assertUnexpectedValue(`${TEMPLATE_SCHEMA} key`) - break + key.assertUnexpectedValue(`${TEMPLATE_SCHEMA} key`); + break; } } } @@ -237,62 +194,59 @@ export class TemplateSchema { * Looks up a definition by name */ public getDefinition(name: string): Definition { - const result = this.definitions[name] + const result = this.definitions[name]; if (result) { - return result + return result; } - throw new Error(`Schema definition '${name}' not found`) + throw new Error(`Schema definition '${name}' not found`); } /** * Expands one-of definitions and returns all scalar definitions */ public getScalarDefinitions(definition: Definition): ScalarDefinition[] { - const result: ScalarDefinition[] = [] + const result: ScalarDefinition[] = []; switch (definition.definitionType) { case DefinitionType.Null: case DefinitionType.Boolean: case DefinitionType.Number: case DefinitionType.String: - result.push(definition as ScalarDefinition) - break + result.push(definition as ScalarDefinition); + break; case DefinitionType.OneOf: { - const oneOf = definition as OneOfDefinition + const oneOf = definition as OneOfDefinition; // Expand nested one-of definitions for (const nestedName of oneOf.oneOf) { - const nestedDefinition = this.getDefinition(nestedName) - result.push(...this.getScalarDefinitions(nestedDefinition)) + const nestedDefinition = this.getDefinition(nestedName); + result.push(...this.getScalarDefinitions(nestedDefinition)); } - break + break; } } - return result + return result; } /** * Expands one-of definitions and returns all matching definitions by type */ - public getDefinitionsOfType( - definition: Definition, - type: DefinitionType - ): Definition[] { - const result: Definition[] = [] + public getDefinitionsOfType(definition: Definition, type: DefinitionType): Definition[] { + const result: Definition[] = []; if (definition.definitionType === type) { - result.push(definition) + result.push(definition); } else if (definition.definitionType === DefinitionType.OneOf) { - const oneOf = definition as OneOfDefinition + const oneOf = definition as OneOfDefinition; for (const nestedName of oneOf.oneOf) { - const nestedDefinition = this.getDefinition(nestedName) + const nestedDefinition = this.getDefinition(nestedName); if (nestedDefinition.definitionType === type) { - result.push(nestedDefinition) + result.push(nestedDefinition); } } } - return result + return result; } /** @@ -304,16 +258,16 @@ export class TemplateSchema { definitions: MappingDefinition[], propertyName: string ): PropertyDefinition | undefined { - let result: PropertyDefinition | undefined + let result: PropertyDefinition | undefined; // Check for a matching well-known property - let notFoundInSome = false + let notFoundInSome = false; for (const definition of definitions) { - const propertyDef = definition.properties[propertyName] + const propertyDef = definition.properties[propertyName]; if (propertyDef) { - result = propertyDef + result = propertyDef; } else { - notFoundInSome = true + notFoundInSome = true; } } @@ -321,40 +275,40 @@ export class TemplateSchema { if (result && notFoundInSome) { for (let i = 0; i < definitions.length; ) { if (definitions[i].properties[propertyName]) { - i++ + i++; } else { - definitions.splice(i, 1) + definitions.splice(i, 1); } } } - return result + return result; } private validate(): void { - const oneOfDefinitions: { [key: string]: OneOfDefinition } = {} + const oneOfDefinitions: {[key: string]: OneOfDefinition} = {}; for (const name of Object.keys(this.definitions)) { if (!name.match(TemplateSchema._definitionNamePattern)) { - throw new Error(`Invalid definition name '${name}'`) + throw new Error(`Invalid definition name '${name}'`); } - const definition = this.definitions[name] + const definition = this.definitions[name]; // Delay validation for 'one-of' definitions if (definition.definitionType === DefinitionType.OneOf) { - oneOfDefinitions[name] = definition as OneOfDefinition + oneOfDefinitions[name] = definition as OneOfDefinition; } // Otherwise validate now else { - definition.validate(this, name) + definition.validate(this, name); } } // Validate 'one-of' definitions for (const name of Object.keys(oneOfDefinitions)) { - const oneOf = oneOfDefinitions[name] - oneOf.validate(this, name) + const oneOf = oneOfDefinitions[name]; + oneOf.validate(this, name); } } @@ -366,19 +320,14 @@ export class TemplateSchema { new TemplateValidationErrors(10, 500), TemplateSchema.getInternalSchema(), new NoOperationTraceWriter() - ) - const template = readTemplate( - context, - TEMPLATE_SCHEMA, - objectReader, - undefined - ) - context.errors.check() + ); + const template = readTemplate(context, TEMPLATE_SCHEMA, objectReader, undefined); + context.errors.check(); - const mapping = template!.assertMapping(TEMPLATE_SCHEMA) - const schema = new TemplateSchema(mapping) - schema.validate() - return schema + const mapping = template!.assertMapping(TEMPLATE_SCHEMA); + const schema = new TemplateSchema(mapping); + schema.validate(); + return schema; } /** @@ -386,294 +335,217 @@ export class TemplateSchema { */ private static getInternalSchema(): TemplateSchema { if (TemplateSchema._internalSchema === undefined) { - const schema = new TemplateSchema() + const schema = new TemplateSchema(); // template-schema - let mappingDefinition = new MappingDefinition(TEMPLATE_SCHEMA) + let mappingDefinition = new MappingDefinition(TEMPLATE_SCHEMA); mappingDefinition.properties[VERSION] = new PropertyDefinition( new StringToken(undefined, undefined, NON_EMPTY_STRING, undefined) - ) + ); mappingDefinition.properties[DEFINITIONS] = new PropertyDefinition( new StringToken(undefined, undefined, DEFINITIONS, undefined) - ) - schema.definitions[mappingDefinition.key] = mappingDefinition + ); + schema.definitions[mappingDefinition.key] = mappingDefinition; // definitions - mappingDefinition = new MappingDefinition(DEFINITIONS) - mappingDefinition.looseKeyType = NON_EMPTY_STRING - mappingDefinition.looseValueType = DEFINITION - schema.definitions[mappingDefinition.key] = mappingDefinition + mappingDefinition = new MappingDefinition(DEFINITIONS); + mappingDefinition.looseKeyType = NON_EMPTY_STRING; + mappingDefinition.looseValueType = DEFINITION; + schema.definitions[mappingDefinition.key] = mappingDefinition; // definition - let oneOfDefinition = new OneOfDefinition(DEFINITION) - oneOfDefinition.oneOf.push(NULL_DEFINITION) - oneOfDefinition.oneOf.push(BOOLEAN_DEFINITION) - oneOfDefinition.oneOf.push(NUMBER_DEFINITION) - oneOfDefinition.oneOf.push(STRING_DEFINITION) - oneOfDefinition.oneOf.push(SEQUENCE_DEFINITION) - oneOfDefinition.oneOf.push(MAPPING_DEFINITION) - oneOfDefinition.oneOf.push(ONE_OF_DEFINITION) - schema.definitions[oneOfDefinition.key] = oneOfDefinition + let oneOfDefinition = new OneOfDefinition(DEFINITION); + oneOfDefinition.oneOf.push(NULL_DEFINITION); + oneOfDefinition.oneOf.push(BOOLEAN_DEFINITION); + oneOfDefinition.oneOf.push(NUMBER_DEFINITION); + oneOfDefinition.oneOf.push(STRING_DEFINITION); + oneOfDefinition.oneOf.push(SEQUENCE_DEFINITION); + oneOfDefinition.oneOf.push(MAPPING_DEFINITION); + oneOfDefinition.oneOf.push(ONE_OF_DEFINITION); + schema.definitions[oneOfDefinition.key] = oneOfDefinition; // null-definition - mappingDefinition = new MappingDefinition(NULL_DEFINITION) + mappingDefinition = new MappingDefinition(NULL_DEFINITION); mappingDefinition.properties[DESCRIPTION] = new PropertyDefinition( new StringToken(undefined, undefined, STRING, undefined) - ) + ); mappingDefinition.properties[CONTEXT] = new PropertyDefinition( - new StringToken( - undefined, - undefined, - SEQUENCE_OF_NON_EMPTY_STRING, - undefined - ) - ) + new StringToken(undefined, undefined, SEQUENCE_OF_NON_EMPTY_STRING, undefined) + ); mappingDefinition.properties[NULL] = new PropertyDefinition( - new StringToken( - undefined, - undefined, - NULL_DEFINITION_PROPERTIES, - undefined - ) - ) - schema.definitions[mappingDefinition.key] = mappingDefinition + new StringToken(undefined, undefined, NULL_DEFINITION_PROPERTIES, undefined) + ); + schema.definitions[mappingDefinition.key] = mappingDefinition; // null-definition-properties - mappingDefinition = new MappingDefinition(NULL_DEFINITION_PROPERTIES) - schema.definitions[mappingDefinition.key] = mappingDefinition + mappingDefinition = new MappingDefinition(NULL_DEFINITION_PROPERTIES); + schema.definitions[mappingDefinition.key] = mappingDefinition; // boolean-definition - mappingDefinition = new MappingDefinition(BOOLEAN_DEFINITION) + mappingDefinition = new MappingDefinition(BOOLEAN_DEFINITION); mappingDefinition.properties[DESCRIPTION] = new PropertyDefinition( new StringToken(undefined, undefined, STRING, undefined) - ) + ); mappingDefinition.properties[CONTEXT] = new PropertyDefinition( - new StringToken( - undefined, - undefined, - SEQUENCE_OF_NON_EMPTY_STRING, - undefined - ) - ) + new StringToken(undefined, undefined, SEQUENCE_OF_NON_EMPTY_STRING, undefined) + ); mappingDefinition.properties[BOOLEAN] = new PropertyDefinition( - new StringToken( - undefined, - undefined, - BOOLEAN_DEFINITION_PROPERTIES, - undefined - ) - ) - schema.definitions[mappingDefinition.key] = mappingDefinition + new StringToken(undefined, undefined, BOOLEAN_DEFINITION_PROPERTIES, undefined) + ); + schema.definitions[mappingDefinition.key] = mappingDefinition; // boolean-definition-properties - mappingDefinition = new MappingDefinition(BOOLEAN_DEFINITION_PROPERTIES) - schema.definitions[mappingDefinition.key] = mappingDefinition + mappingDefinition = new MappingDefinition(BOOLEAN_DEFINITION_PROPERTIES); + schema.definitions[mappingDefinition.key] = mappingDefinition; // number-definition - mappingDefinition = new MappingDefinition(NUMBER_DEFINITION) + mappingDefinition = new MappingDefinition(NUMBER_DEFINITION); mappingDefinition.properties[DESCRIPTION] = new PropertyDefinition( new StringToken(undefined, undefined, STRING, undefined) - ) + ); mappingDefinition.properties[CONTEXT] = new PropertyDefinition( - new StringToken( - undefined, - undefined, - SEQUENCE_OF_NON_EMPTY_STRING, - undefined - ) - ) + new StringToken(undefined, undefined, SEQUENCE_OF_NON_EMPTY_STRING, undefined) + ); mappingDefinition.properties[NUMBER] = new PropertyDefinition( - new StringToken( - undefined, - undefined, - NUMBER_DEFINITION_PROPERTIES, - undefined - ) - ) - schema.definitions[mappingDefinition.key] = mappingDefinition + new StringToken(undefined, undefined, NUMBER_DEFINITION_PROPERTIES, undefined) + ); + schema.definitions[mappingDefinition.key] = mappingDefinition; // number-definition-properties - mappingDefinition = new MappingDefinition(NUMBER_DEFINITION_PROPERTIES) - schema.definitions[mappingDefinition.key] = mappingDefinition + mappingDefinition = new MappingDefinition(NUMBER_DEFINITION_PROPERTIES); + schema.definitions[mappingDefinition.key] = mappingDefinition; // string-definition - mappingDefinition = new MappingDefinition(STRING_DEFINITION) + mappingDefinition = new MappingDefinition(STRING_DEFINITION); mappingDefinition.properties[DESCRIPTION] = new PropertyDefinition( new StringToken(undefined, undefined, STRING, undefined) - ) + ); mappingDefinition.properties[CONTEXT] = new PropertyDefinition( - new StringToken( - undefined, - undefined, - SEQUENCE_OF_NON_EMPTY_STRING, - undefined - ) - ) + new StringToken(undefined, undefined, SEQUENCE_OF_NON_EMPTY_STRING, undefined) + ); mappingDefinition.properties[STRING] = new PropertyDefinition( - new StringToken( - undefined, - undefined, - STRING_DEFINITION_PROPERTIES, - undefined - ) - ) - schema.definitions[mappingDefinition.key] = mappingDefinition + new StringToken(undefined, undefined, STRING_DEFINITION_PROPERTIES, undefined) + ); + schema.definitions[mappingDefinition.key] = mappingDefinition; // string-definition-properties - mappingDefinition = new MappingDefinition(STRING_DEFINITION_PROPERTIES) + mappingDefinition = new MappingDefinition(STRING_DEFINITION_PROPERTIES); mappingDefinition.properties[CONSTANT] = new PropertyDefinition( new StringToken(undefined, undefined, NON_EMPTY_STRING, undefined) - ) + ); mappingDefinition.properties[IGNORE_CASE] = new PropertyDefinition( new StringToken(undefined, undefined, BOOLEAN, undefined) - ) + ); mappingDefinition.properties[REQUIRE_NON_EMPTY] = new PropertyDefinition( new StringToken(undefined, undefined, BOOLEAN, undefined) - ) + ); mappingDefinition.properties[IS_EXPRESSION] = new PropertyDefinition( new StringToken(undefined, undefined, BOOLEAN, undefined) - ) - schema.definitions[mappingDefinition.key] = mappingDefinition + ); + schema.definitions[mappingDefinition.key] = mappingDefinition; // sequence-definition - mappingDefinition = new MappingDefinition(SEQUENCE_DEFINITION) + mappingDefinition = new MappingDefinition(SEQUENCE_DEFINITION); mappingDefinition.properties[DESCRIPTION] = new PropertyDefinition( new StringToken(undefined, undefined, STRING, undefined) - ) + ); mappingDefinition.properties[CONTEXT] = new PropertyDefinition( - new StringToken( - undefined, - undefined, - SEQUENCE_OF_NON_EMPTY_STRING, - undefined - ) - ) + new StringToken(undefined, undefined, SEQUENCE_OF_NON_EMPTY_STRING, undefined) + ); mappingDefinition.properties[SEQUENCE] = new PropertyDefinition( - new StringToken( - undefined, - undefined, - SEQUENCE_DEFINITION_PROPERTIES, - undefined - ) - ) - schema.definitions[mappingDefinition.key] = mappingDefinition + new StringToken(undefined, undefined, SEQUENCE_DEFINITION_PROPERTIES, undefined) + ); + schema.definitions[mappingDefinition.key] = mappingDefinition; // sequence-definition-properties - mappingDefinition = new MappingDefinition(SEQUENCE_DEFINITION_PROPERTIES) + mappingDefinition = new MappingDefinition(SEQUENCE_DEFINITION_PROPERTIES); mappingDefinition.properties[ITEM_TYPE] = new PropertyDefinition( new StringToken(undefined, undefined, NON_EMPTY_STRING, undefined) - ) - schema.definitions[mappingDefinition.key] = mappingDefinition + ); + schema.definitions[mappingDefinition.key] = mappingDefinition; // mapping-definition - mappingDefinition = new MappingDefinition(MAPPING_DEFINITION) + mappingDefinition = new MappingDefinition(MAPPING_DEFINITION); mappingDefinition.properties[DESCRIPTION] = new PropertyDefinition( new StringToken(undefined, undefined, STRING, undefined) - ) + ); mappingDefinition.properties[CONTEXT] = new PropertyDefinition( - new StringToken( - undefined, - undefined, - SEQUENCE_OF_NON_EMPTY_STRING, - undefined - ) - ) + new StringToken(undefined, undefined, SEQUENCE_OF_NON_EMPTY_STRING, undefined) + ); mappingDefinition.properties[MAPPING] = new PropertyDefinition( - new StringToken( - undefined, - undefined, - MAPPING_DEFINITION_PROPERTIES, - undefined - ) - ) - schema.definitions[mappingDefinition.key] = mappingDefinition + new StringToken(undefined, undefined, MAPPING_DEFINITION_PROPERTIES, undefined) + ); + schema.definitions[mappingDefinition.key] = mappingDefinition; // mapping-definition-properties - mappingDefinition = new MappingDefinition(MAPPING_DEFINITION_PROPERTIES) + mappingDefinition = new MappingDefinition(MAPPING_DEFINITION_PROPERTIES); mappingDefinition.properties[PROPERTIES] = new PropertyDefinition( new StringToken(undefined, undefined, PROPERTIES, undefined) - ) + ); mappingDefinition.properties[LOOSE_KEY_TYPE] = new PropertyDefinition( new StringToken(undefined, undefined, NON_EMPTY_STRING, undefined) - ) + ); mappingDefinition.properties[LOOSE_VALUE_TYPE] = new PropertyDefinition( new StringToken(undefined, undefined, NON_EMPTY_STRING, undefined) - ) - schema.definitions[mappingDefinition.key] = mappingDefinition + ); + schema.definitions[mappingDefinition.key] = mappingDefinition; // properties - mappingDefinition = new MappingDefinition(PROPERTIES) - mappingDefinition.looseKeyType = NON_EMPTY_STRING - mappingDefinition.looseValueType = PROPERTY_VALUE - schema.definitions[mappingDefinition.key] = mappingDefinition + mappingDefinition = new MappingDefinition(PROPERTIES); + mappingDefinition.looseKeyType = NON_EMPTY_STRING; + mappingDefinition.looseValueType = PROPERTY_VALUE; + schema.definitions[mappingDefinition.key] = mappingDefinition; // property-value - oneOfDefinition = new OneOfDefinition(PROPERTY_VALUE) - oneOfDefinition.oneOf.push(NON_EMPTY_STRING) - oneOfDefinition.oneOf.push(MAPPING_PROPERTY_VALUE) - schema.definitions[oneOfDefinition.key] = oneOfDefinition + oneOfDefinition = new OneOfDefinition(PROPERTY_VALUE); + oneOfDefinition.oneOf.push(NON_EMPTY_STRING); + oneOfDefinition.oneOf.push(MAPPING_PROPERTY_VALUE); + schema.definitions[oneOfDefinition.key] = oneOfDefinition; // mapping-property-value - mappingDefinition = new MappingDefinition(MAPPING_PROPERTY_VALUE) + mappingDefinition = new MappingDefinition(MAPPING_PROPERTY_VALUE); mappingDefinition.properties[TYPE] = new PropertyDefinition( new StringToken(undefined, undefined, NON_EMPTY_STRING, undefined) - ) + ); mappingDefinition.properties[REQUIRED] = new PropertyDefinition( new StringToken(undefined, undefined, BOOLEAN, undefined) - ) + ); mappingDefinition.properties[DESCRIPTION] = new PropertyDefinition( new StringToken(undefined, undefined, STRING, undefined) - ) - schema.definitions[mappingDefinition.key] = mappingDefinition + ); + schema.definitions[mappingDefinition.key] = mappingDefinition; // one-of-definition - mappingDefinition = new MappingDefinition(ONE_OF_DEFINITION) + mappingDefinition = new MappingDefinition(ONE_OF_DEFINITION); mappingDefinition.properties[DESCRIPTION] = new PropertyDefinition( new StringToken(undefined, undefined, STRING, undefined) - ) + ); mappingDefinition.properties[CONTEXT] = new PropertyDefinition( - new StringToken( - undefined, - undefined, - SEQUENCE_OF_NON_EMPTY_STRING, - undefined - ) - ) + new StringToken(undefined, undefined, SEQUENCE_OF_NON_EMPTY_STRING, undefined) + ); mappingDefinition.properties[ONE_OF] = new PropertyDefinition( - new StringToken( - undefined, - undefined, - SEQUENCE_OF_NON_EMPTY_STRING, - undefined - ) - ) + new StringToken(undefined, undefined, SEQUENCE_OF_NON_EMPTY_STRING, undefined) + ); mappingDefinition.properties[ALLOWED_VALUES] = new PropertyDefinition( - new StringToken( - undefined, - undefined, - SEQUENCE_OF_NON_EMPTY_STRING, - undefined - ) - ) - schema.definitions[mappingDefinition.key] = mappingDefinition + new StringToken(undefined, undefined, SEQUENCE_OF_NON_EMPTY_STRING, undefined) + ); + schema.definitions[mappingDefinition.key] = mappingDefinition; // non-empty-string - const stringDefinition = new StringDefinition(NON_EMPTY_STRING) - stringDefinition.requireNonEmpty = true - schema.definitions[stringDefinition.key] = stringDefinition + const stringDefinition = new StringDefinition(NON_EMPTY_STRING); + stringDefinition.requireNonEmpty = true; + schema.definitions[stringDefinition.key] = stringDefinition; // sequence-of-non-empty-string - const sequenceDefinition = new SequenceDefinition( - SEQUENCE_OF_NON_EMPTY_STRING - ) - sequenceDefinition.itemType = NON_EMPTY_STRING - schema.definitions[sequenceDefinition.key] = sequenceDefinition + const sequenceDefinition = new SequenceDefinition(SEQUENCE_OF_NON_EMPTY_STRING); + sequenceDefinition.itemType = NON_EMPTY_STRING; + schema.definitions[sequenceDefinition.key] = sequenceDefinition; - schema.validate() + schema.validate(); - TemplateSchema._internalSchema = schema + TemplateSchema._internalSchema = schema; } - return TemplateSchema._internalSchema + return TemplateSchema._internalSchema; } } diff --git a/actions-workflow-parser/src/templates/template-constants.ts b/actions-workflow-parser/src/templates/template-constants.ts index 7162050..13b0cef 100644 --- a/actions-workflow-parser/src/templates/template-constants.ts +++ b/actions-workflow-parser/src/templates/template-constants.ts @@ -1,48 +1,48 @@ -export const ALLOWED_VALUES = "allowed-values" -export const ANY = "any" -export const BOOLEAN = "boolean" -export const BOOLEAN_DEFINITION = "boolean-definition" -export const BOOLEAN_DEFINITION_PROPERTIES = "boolean-definition-properties" -export const CLOSE_EXPRESSION = "}}" -export const CONSTANT = "constant" -export const CONTEXT = "context" -export const DEFINITION = "definition" -export const DEFINITIONS = "definitions" -export const DESCRIPTION = "description" -export const IGNORE_CASE = "ignore-case" -export const INSERT_DIRECTIVE = "insert" -export const IS_EXPRESSION = "is-expression" -export const ITEM_TYPE = "item-type" -export const LOOSE_KEY_TYPE = "loose-key-type" -export const LOOSE_VALUE_TYPE = "loose-value-type" -export const MAX_CONSTANT = "MAX" -export const MAPPING = "mapping" -export const MAPPING_DEFINITION = "mapping-definition" -export const MAPPING_DEFINITION_PROPERTIES = "mapping-definition-properties" -export const MAPPING_PROPERTY_VALUE = "mapping-property-value" -export const NON_EMPTY_STRING = "non-empty-string" -export const NULL = "null" -export const NULL_DEFINITION = "null-definition" -export const NULL_DEFINITION_PROPERTIES = "null-definition-properties" -export const NUMBER = "number" -export const NUMBER_DEFINITION = "number-definition" -export const NUMBER_DEFINITION_PROPERTIES = "number-definition-properties" -export const ONE_OF = "one-of" -export const ONE_OF_DEFINITION = "one-of-definition" -export const OPEN_EXPRESSION = "${{" -export const PROPERTY_VALUE = "property-value" -export const PROPERTIES = "properties" -export const REQUIRED = "required" -export const REQUIRE_NON_EMPTY = "require-non-empty" -export const SCALAR = "scalar" -export const SEQUENCE = "sequence" -export const SEQUENCE_DEFINITION = "sequence-definition" -export const SEQUENCE_DEFINITION_PROPERTIES = "sequence-definition-properties" -export const TYPE = "type" -export const SEQUENCE_OF_NON_EMPTY_STRING = "sequence-of-non-empty-string" -export const STRING = "string" -export const STRING_DEFINITION = "string-definition" -export const STRING_DEFINITION_PROPERTIES = "string-definition-properties" -export const STRUCTURE = "structure" -export const TEMPLATE_SCHEMA = "template-schema" -export const VERSION = "version" +export const ALLOWED_VALUES = "allowed-values"; +export const ANY = "any"; +export const BOOLEAN = "boolean"; +export const BOOLEAN_DEFINITION = "boolean-definition"; +export const BOOLEAN_DEFINITION_PROPERTIES = "boolean-definition-properties"; +export const CLOSE_EXPRESSION = "}}"; +export const CONSTANT = "constant"; +export const CONTEXT = "context"; +export const DEFINITION = "definition"; +export const DEFINITIONS = "definitions"; +export const DESCRIPTION = "description"; +export const IGNORE_CASE = "ignore-case"; +export const INSERT_DIRECTIVE = "insert"; +export const IS_EXPRESSION = "is-expression"; +export const ITEM_TYPE = "item-type"; +export const LOOSE_KEY_TYPE = "loose-key-type"; +export const LOOSE_VALUE_TYPE = "loose-value-type"; +export const MAX_CONSTANT = "MAX"; +export const MAPPING = "mapping"; +export const MAPPING_DEFINITION = "mapping-definition"; +export const MAPPING_DEFINITION_PROPERTIES = "mapping-definition-properties"; +export const MAPPING_PROPERTY_VALUE = "mapping-property-value"; +export const NON_EMPTY_STRING = "non-empty-string"; +export const NULL = "null"; +export const NULL_DEFINITION = "null-definition"; +export const NULL_DEFINITION_PROPERTIES = "null-definition-properties"; +export const NUMBER = "number"; +export const NUMBER_DEFINITION = "number-definition"; +export const NUMBER_DEFINITION_PROPERTIES = "number-definition-properties"; +export const ONE_OF = "one-of"; +export const ONE_OF_DEFINITION = "one-of-definition"; +export const OPEN_EXPRESSION = "${{"; +export const PROPERTY_VALUE = "property-value"; +export const PROPERTIES = "properties"; +export const REQUIRED = "required"; +export const REQUIRE_NON_EMPTY = "require-non-empty"; +export const SCALAR = "scalar"; +export const SEQUENCE = "sequence"; +export const SEQUENCE_DEFINITION = "sequence-definition"; +export const SEQUENCE_DEFINITION_PROPERTIES = "sequence-definition-properties"; +export const TYPE = "type"; +export const SEQUENCE_OF_NON_EMPTY_STRING = "sequence-of-non-empty-string"; +export const STRING = "string"; +export const STRING_DEFINITION = "string-definition"; +export const STRING_DEFINITION_PROPERTIES = "string-definition-properties"; +export const STRUCTURE = "structure"; +export const TEMPLATE_SCHEMA = "template-schema"; +export const VERSION = "version"; diff --git a/actions-workflow-parser/src/templates/template-context.ts b/actions-workflow-parser/src/templates/template-context.ts index dda7786..0b02c25 100644 --- a/actions-workflow-parser/src/templates/template-context.ts +++ b/actions-workflow-parser/src/templates/template-context.ts @@ -1,135 +1,100 @@ -import { FunctionInfo } from "@github/actions-expressions/funcs/info" +import {FunctionInfo} from "@github/actions-expressions/funcs/info"; -import { TemplateSchema } from "./schema/template-schema" -import { TemplateValidationError } from "./template-validation-error" -import { TemplateToken } from "./tokens" -import { TokenRange } from "./tokens/token-range" -import { TraceWriter } from "./trace-writer" +import {TemplateSchema} from "./schema/template-schema"; +import {TemplateValidationError} from "./template-validation-error"; +import {TemplateToken} from "./tokens"; +import {TokenRange} from "./tokens/token-range"; +import {TraceWriter} from "./trace-writer"; /** * Context object that is flowed through while loading and evaluating object templates */ export class TemplateContext { - private readonly _fileIds: { [name: string]: number } = {} - private readonly _fileNames: string[] = [] + private readonly _fileIds: {[name: string]: number} = {}; + private readonly _fileNames: string[] = []; /** * Available functions within expression contexts */ - public readonly expressionFunctions: FunctionInfo[] = [] + public readonly expressionFunctions: FunctionInfo[] = []; /** * Available values within expression contexts */ - public readonly expressionNamedContexts: string[] = [] + public readonly expressionNamedContexts: string[] = []; - public readonly errors: TemplateValidationErrors - public readonly schema: TemplateSchema - public readonly trace: TraceWriter - public readonly state: { [key: string]: any } = {} + public readonly errors: TemplateValidationErrors; + public readonly schema: TemplateSchema; + public readonly trace: TraceWriter; + public readonly state: {[key: string]: any} = {}; - public constructor( - errors: TemplateValidationErrors, - schema: TemplateSchema, - trace: TraceWriter - ) { - this.errors = errors - this.schema = schema - this.trace = trace + public constructor(errors: TemplateValidationErrors, schema: TemplateSchema, trace: TraceWriter) { + this.errors = errors; + this.schema = schema; + this.trace = trace; } - public error( - token: TemplateToken | undefined, - err: string, - tokenRange?: TokenRange - ): void - public error( - token: TemplateToken | undefined, - err: Error, - tokenRange?: TokenRange - ): void - public error(token: TemplateToken | undefined, err: unknown): void - public error( - fileId: number | undefined, - err: string, - tokenRange?: TokenRange - ): void - public error( - fileId: number | undefined, - err: Error, - tokenRange?: TokenRange - ): void - public error( - fileId: number | undefined, - err: unknown, - tokenRange?: TokenRange - ): void + public error(token: TemplateToken | undefined, err: string, tokenRange?: TokenRange): void; + public error(token: TemplateToken | undefined, err: Error, tokenRange?: TokenRange): void; + public error(token: TemplateToken | undefined, err: unknown): void; + public error(fileId: number | undefined, err: string, tokenRange?: TokenRange): void; + public error(fileId: number | undefined, err: Error, tokenRange?: TokenRange): void; + public error(fileId: number | undefined, err: unknown, tokenRange?: TokenRange): void; public error( tokenOrFileId: TemplateToken | number | undefined, err: string | Error | unknown, tokenRange?: TokenRange ): void { - const token = tokenOrFileId as TemplateToken | undefined - const range = tokenRange || token?.range - const prefix = this.getErrorPrefix( - token?.file ?? (tokenOrFileId as number | undefined), - token?.line, - token?.col - ) - const message = (err as Error | undefined)?.message ?? `${err}` + const token = tokenOrFileId as TemplateToken | undefined; + const range = tokenRange || token?.range; + const prefix = this.getErrorPrefix(token?.file ?? (tokenOrFileId as number | undefined), token?.line, token?.col); + const message = (err as Error | undefined)?.message ?? `${err}`; - const e = new TemplateValidationError(message, prefix, undefined, range) - this.errors.add(e) - this.trace.error(e.message) + const e = new TemplateValidationError(message, prefix, undefined, range); + this.errors.add(e); + this.trace.error(e.message); } /** * Gets or adds the file ID */ public getFileId(file: string) { - const key = file.toUpperCase() - let id: number | undefined = this._fileIds[key] + const key = file.toUpperCase(); + let id: number | undefined = this._fileIds[key]; if (id === undefined) { - id = this._fileNames.length + 1 - this._fileIds[key] = id - this._fileNames.push(file) + id = this._fileNames.length + 1; + this._fileIds[key] = id; + this._fileNames.push(file); } - return id + return id; } /** * Looks up a file name by ID. Returns undefined if not found. */ public getFileName(fileId: number): string | undefined { - return this._fileNames.length >= fileId - ? this._fileNames[fileId - 1] - : undefined + return this._fileNames.length >= fileId ? this._fileNames[fileId - 1] : undefined; } /** * Gets a copy of the file table */ public getFileTable(): string[] { - return this._fileNames.slice() + return this._fileNames.slice(); } - private getErrorPrefix( - fileId?: number, - line?: number, - column?: number - ): string { - const fileName = - fileId !== undefined ? this.getFileName(fileId as number) : undefined + private getErrorPrefix(fileId?: number, line?: number, column?: number): string { + const fileName = fileId !== undefined ? this.getFileName(fileId as number) : undefined; if (fileName) { if (line !== undefined && column !== undefined) { - return `${fileName} (Line: ${line}, Col: ${column})` + return `${fileName} (Line: ${line}, Col: ${column})`; } else { - return fileName + return fileName; } } else if (line !== undefined && column !== undefined) { - return `(Line: ${line}, Col: ${column})` + return `(Line: ${line}, Col: ${column})`; } else { - return "" + return ""; } } } @@ -138,17 +103,17 @@ export class TemplateContext { * Provides information about errors which occurred during validation */ export class TemplateValidationErrors { - private readonly _maxErrors: number - private readonly _maxMessageLength: number - private _errors: TemplateValidationError[] = [] + private readonly _maxErrors: number; + private readonly _maxMessageLength: number; + private _errors: TemplateValidationError[] = []; public constructor(maxErrors?: number, maxMessageLength?: number) { - this._maxErrors = maxErrors ?? 0 - this._maxMessageLength = maxMessageLength ?? 0 + this._maxErrors = maxErrors ?? 0; + this._maxMessageLength = maxMessageLength ?? 0; } public get count(): number { - return this._errors.length + return this._errors.length; } public add(err: TemplateValidationError | TemplateValidationError[]): void { @@ -156,19 +121,16 @@ export class TemplateValidationErrors { // Check max errors if (this._maxErrors <= 0 || this._errors.length < this._maxErrors) { // Check max message length - if ( - this._maxMessageLength > 0 && - e.message.length > this._maxMessageLength - ) { + if (this._maxMessageLength > 0 && e.message.length > this._maxMessageLength) { e = new TemplateValidationError( e.message.substring(0, this._maxMessageLength) + "[...]", e.prefix, e.code, e.range - ) + ); } - this._errors.push(e) + this._errors.push(e); } } } @@ -179,21 +141,21 @@ export class TemplateValidationErrors { */ public check(prefix?: string): void { if (this._errors.length <= 0) { - return + return; } if (!prefix) { - prefix = "The template is not valid." + prefix = "The template is not valid."; } - throw new Error(`${prefix} ${this._errors.map((x) => x.message).join(",")}`) + throw new Error(`${prefix} ${this._errors.map(x => x.message).join(",")}`); } public clear(): void { - this._errors = [] + this._errors = []; } public getErrors(): TemplateValidationError[] { - return this._errors.slice() + return this._errors.slice(); } } diff --git a/actions-workflow-parser/src/templates/template-evaluator.ts b/actions-workflow-parser/src/templates/template-evaluator.ts index 717c98d..eba3f35 100644 --- a/actions-workflow-parser/src/templates/template-evaluator.ts +++ b/actions-workflow-parser/src/templates/template-evaluator.ts @@ -1,22 +1,16 @@ // called at runtime. expands expressions and re-validates the schema result after expansion -import { TemplateSchema } from "./schema" -import { Definition } from "./schema/definition" -import { DefinitionType } from "./schema/definition-type" -import { MappingDefinition } from "./schema/mapping-definition" -import { ScalarDefinition } from "./schema/scalar-definition" -import { SequenceDefinition } from "./schema/sequence-definition" -import { ANY } from "./template-constants" -import { TemplateContext } from "./template-context" -import { TemplateUnraveler } from "./template-unraveler" -import { - LiteralToken, - MappingToken, - ScalarToken, - StringToken, - TemplateToken, -} from "./tokens" -import { TokenType } from "./tokens/types" +import {TemplateSchema} from "./schema"; +import {Definition} from "./schema/definition"; +import {DefinitionType} from "./schema/definition-type"; +import {MappingDefinition} from "./schema/mapping-definition"; +import {ScalarDefinition} from "./schema/scalar-definition"; +import {SequenceDefinition} from "./schema/sequence-definition"; +import {ANY} from "./template-constants"; +import {TemplateContext} from "./template-context"; +import {TemplateUnraveler} from "./template-unraveler"; +import {LiteralToken, MappingToken, ScalarToken, StringToken, TemplateToken} from "./tokens"; +import {TokenType} from "./tokens/types"; export function evaluateTemplate( context: TemplateContext, @@ -25,90 +19,81 @@ export function evaluateTemplate( removeBytes: number, fileId: number | undefined ): TemplateToken | undefined { - let result: TemplateToken | undefined + let result: TemplateToken | undefined; - const evaluator = new TemplateEvaluator(context, template, removeBytes) + const evaluator = new TemplateEvaluator(context, template, removeBytes); try { - const definitionInfo = new DefinitionInfo(context, type) - result = evaluator.evaluate(definitionInfo) + const definitionInfo = new DefinitionInfo(context, type); + result = evaluator.evaluate(definitionInfo); if (result) { - evaluator.unraveler.readEnd() + evaluator.unraveler.readEnd(); } } catch (err) { - context.error(fileId, err) - result = undefined + context.error(fileId, err); + result = undefined; } - return result + return result; } class TemplateEvaluator { - public readonly context: TemplateContext - public readonly schema: TemplateSchema - public readonly unraveler: TemplateUnraveler + public readonly context: TemplateContext; + public readonly schema: TemplateSchema; + public readonly unraveler: TemplateUnraveler; - public constructor( - context: TemplateContext, - template: TemplateToken, - removeBytes: number - ) { - this.context = context - this.schema = context.schema - this.unraveler = new TemplateUnraveler(context, template, removeBytes) + public constructor(context: TemplateContext, template: TemplateToken, removeBytes: number) { + this.context = context; + this.schema = context.schema; + this.unraveler = new TemplateUnraveler(context, template, removeBytes); } public evaluate(definition: DefinitionInfo): TemplateToken { // Scalar - const scalar = this.unraveler.allowScalar(definition.expand) + const scalar = this.unraveler.allowScalar(definition.expand); if (scalar) { if (scalar.isLiteral) { - return this.validate(scalar as LiteralToken, definition) + return this.validate(scalar as LiteralToken, definition); } else { - return scalar + return scalar; } } // Sequence - const sequence = this.unraveler.allowSequenceStart(definition.expand) + const sequence = this.unraveler.allowSequenceStart(definition.expand); if (sequence) { - const sequenceDefinition = definition.getDefinitionsOfType( - DefinitionType.Sequence - )[0] as SequenceDefinition | undefined + const sequenceDefinition = definition.getDefinitionsOfType(DefinitionType.Sequence)[0] as + | SequenceDefinition + | undefined; // Legal if (sequenceDefinition) { - const itemDefinition = new DefinitionInfo( - definition, - sequenceDefinition.itemType - ) + const itemDefinition = new DefinitionInfo(definition, sequenceDefinition.itemType); // Add each item while (!this.unraveler.allowSequenceEnd(definition.expand)) { - const item = this.evaluate(itemDefinition) - sequence.add(item) + const item = this.evaluate(itemDefinition); + sequence.add(item); } } // Illegal else { // Error - this.context.error(sequence, "A sequence was not expected") + this.context.error(sequence, "A sequence was not expected"); // Skip each item while (!this.unraveler.allowSequenceEnd(false)) { - this.unraveler.skipSequenceItem() + this.unraveler.skipSequenceItem(); } } - return sequence + return sequence; } // Mapping - const mapping = this.unraveler.allowMappingStart(definition.expand) + const mapping = this.unraveler.allowMappingStart(definition.expand); if (mapping) { - const mappingDefinitions = definition.getDefinitionsOfType( - DefinitionType.Mapping - ) as MappingDefinition[] + const mappingDefinitions = definition.getDefinitionsOfType(DefinitionType.Mapping) as MappingDefinition[]; // Legal if (mappingDefinitions.length > 0) { @@ -117,42 +102,27 @@ class TemplateEvaluator { Object.keys(mappingDefinitions[0].properties).length > 0 || !mappingDefinitions[0].looseKeyType ) { - this.handleMappingWithWellKnownProperties( - definition, - mappingDefinitions, - mapping - ) + this.handleMappingWithWellKnownProperties(definition, mappingDefinitions, mapping); } else { - const keyDefinition = new DefinitionInfo( - definition, - mappingDefinitions[0].looseKeyType - ) - const valueDefinition = new DefinitionInfo( - definition, - mappingDefinitions[0].looseValueType - ) - this.handleMappingWithAllLooseProperties( - definition, - keyDefinition, - valueDefinition, - mapping - ) + const keyDefinition = new DefinitionInfo(definition, mappingDefinitions[0].looseKeyType); + const valueDefinition = new DefinitionInfo(definition, mappingDefinitions[0].looseValueType); + this.handleMappingWithAllLooseProperties(definition, keyDefinition, valueDefinition, mapping); } } // Illegal else { - this.context.error(mapping, "A mapping was not expected") + this.context.error(mapping, "A mapping was not expected"); while (!this.unraveler.allowMappingEnd(false)) { - this.unraveler.skipMappingKey() - this.unraveler.skipMappingValue() + this.unraveler.skipMappingKey(); + this.unraveler.skipMappingValue(); } } - return mapping + return mapping; } - throw new Error("Expected a scalar value, a sequence, or a mapping") + throw new Error("Expected a scalar value, a sequence, or a mapping"); } private handleMappingWithWellKnownProperties( @@ -161,26 +131,26 @@ class TemplateEvaluator { mapping: MappingToken ) { // Check if loose properties are allowed - let looseKeyType: string | undefined - let looseValueType: string | undefined - let looseKeyDefinition: DefinitionInfo | undefined - let looseValueDefinition: DefinitionInfo | undefined + let looseKeyType: string | undefined; + let looseValueType: string | undefined; + let looseKeyDefinition: DefinitionInfo | undefined; + let looseValueDefinition: DefinitionInfo | undefined; if (mappingDefinitions[0].looseKeyType) { - looseKeyType = mappingDefinitions[0].looseKeyType - looseValueType = mappingDefinitions[0].looseValueType + looseKeyType = mappingDefinitions[0].looseKeyType; + looseValueType = mappingDefinitions[0].looseValueType; } - const upperKeys: { [upperKey: string]: boolean } = {} - let hasExpressionKey = false + const upperKeys: {[upperKey: string]: boolean} = {}; + let hasExpressionKey = false; - let nextKeyScalar: ScalarToken | undefined + let nextKeyScalar: ScalarToken | undefined; while ((nextKeyScalar = this.unraveler.allowScalar(definition.expand))) { // Expression if (nextKeyScalar.isExpression) { - hasExpressionKey = true - const anyDefinition = new DefinitionInfo(definition, ANY) - mapping.add(nextKeyScalar, this.evaluate(anyDefinition)) - continue + hasExpressionKey = true; + const anyDefinition = new DefinitionInfo(definition, ANY); + mapping.add(nextKeyScalar, this.evaluate(anyDefinition)); + continue; } // Convert to StringToken if required @@ -192,64 +162,58 @@ class TemplateEvaluator { nextKeyScalar.range, nextKeyScalar.toString(), nextKeyScalar.definitionInfo - ) + ); // Duplicate - const upperKey = nextKey.value.toUpperCase() + const upperKey = nextKey.value.toUpperCase(); if (upperKeys[upperKey]) { - this.context.error(nextKey, `'${nextKey.value}' is already defined`) - this.unraveler.skipMappingValue() - continue + this.context.error(nextKey, `'${nextKey.value}' is already defined`); + this.unraveler.skipMappingValue(); + continue; } - upperKeys[upperKey] = true + upperKeys[upperKey] = true; // Well known - const nextValuePropertyDef = this.schema.matchPropertyAndFilter( - mappingDefinitions, - nextKey.value - ) + const nextValuePropertyDef = this.schema.matchPropertyAndFilter(mappingDefinitions, nextKey.value); if (nextValuePropertyDef?.type) { - const nextValueDefinition = new DefinitionInfo( - definition, - nextValuePropertyDef.type - ) - const nextValue = this.evaluate(nextValueDefinition) - nextValue.propertyDefinition = nextValuePropertyDef - mapping.add(nextKey, nextValue) - continue + const nextValueDefinition = new DefinitionInfo(definition, nextValuePropertyDef.type); + const nextValue = this.evaluate(nextValueDefinition); + nextValue.propertyDefinition = nextValuePropertyDef; + mapping.add(nextKey, nextValue); + continue; } // Loose if (looseKeyType) { if (!looseKeyDefinition) { - looseKeyDefinition = new DefinitionInfo(definition, looseKeyType) - looseValueDefinition = new DefinitionInfo(definition, looseValueType!) + looseKeyDefinition = new DefinitionInfo(definition, looseKeyType); + looseValueDefinition = new DefinitionInfo(definition, looseValueType!); } - this.validate(nextKey, looseKeyDefinition) - const nextValue = this.evaluate(looseValueDefinition!) - mapping.add(nextKey, nextValue) - continue + this.validate(nextKey, looseKeyDefinition); + const nextValue = this.evaluate(looseValueDefinition!); + mapping.add(nextKey, nextValue); + continue; } // Error - this.context.error(nextKey, `Unexpected value '${nextKey.value}'`) - this.unraveler.skipMappingValue() + this.context.error(nextKey, `Unexpected value '${nextKey.value}'`); + this.unraveler.skipMappingValue(); } // Unable to filter to one definition if (mappingDefinitions.length > 1) { - const hitCount: { [key: string]: number } = {} + const hitCount: {[key: string]: number} = {}; for (const mappingDefinition of mappingDefinitions) { for (const key of Object.keys(mappingDefinition.properties)) { - hitCount[key] = (hitCount[key] ?? 0) + 1 + hitCount[key] = (hitCount[key] ?? 0) + 1; } } - const nonDuplicates: string[] = [] + const nonDuplicates: string[] = []; for (const key of Object.keys(hitCount)) { if (hitCount[key] === 1) { - nonDuplicates.push(key) + nonDuplicates.push(key); } } @@ -258,24 +222,19 @@ class TemplateEvaluator { `There's not enough info to determine what you meant. Add one of these properties: ${nonDuplicates .sort() .join(", ")}` - ) + ); } // Check required properties else if (mappingDefinitions.length === 1 && !hasExpressionKey) { - for (const propertyName of Object.keys( - mappingDefinitions[0].properties - )) { - const propertyDef = mappingDefinitions[0].properties[propertyName] + for (const propertyName of Object.keys(mappingDefinitions[0].properties)) { + const propertyDef = mappingDefinitions[0].properties[propertyName]; if (propertyDef.required && !upperKeys[propertyName.toUpperCase()]) { - this.context.error( - mapping, - `Required property is missing: ${propertyName}` - ) + this.context.error(mapping, `Required property is missing: ${propertyName}`); } } } - this.unraveler.readMappingEnd() + this.unraveler.readMappingEnd(); } private handleMappingWithAllLooseProperties( @@ -284,22 +243,20 @@ class TemplateEvaluator { valueDefinition: DefinitionInfo, mapping: MappingToken ): void { - const upperKeys: { [key: string]: boolean } = {} + const upperKeys: {[key: string]: boolean} = {}; - let nextKeyScalar: ScalarToken | undefined - while ( - (nextKeyScalar = this.unraveler.allowScalar(mappingDefinition.expand)) - ) { + let nextKeyScalar: ScalarToken | undefined; + while ((nextKeyScalar = this.unraveler.allowScalar(mappingDefinition.expand))) { // Expression if (nextKeyScalar.isExpression) { if (nextKeyScalar.templateTokenType === TokenType.BasicExpression) { - mapping.add(nextKeyScalar, this.evaluate(valueDefinition)) + mapping.add(nextKeyScalar, this.evaluate(valueDefinition)); } else { - const anyDefinition = new DefinitionInfo(mappingDefinition, ANY) - mapping.add(nextKeyScalar, this.evaluate(anyDefinition)) + const anyDefinition = new DefinitionInfo(mappingDefinition, ANY); + mapping.add(nextKeyScalar, this.evaluate(anyDefinition)); } - continue + continue; } // Convert to StringToken if required @@ -311,56 +268,48 @@ class TemplateEvaluator { nextKeyScalar.range, nextKeyScalar.toString(), nextKeyScalar.definitionInfo - ) + ); // Duplicate - const upperKey = nextKey.value.toUpperCase() + const upperKey = nextKey.value.toUpperCase(); if (upperKeys[upperKey]) { - this.context.error(nextKey, `'${nextKey.value}' is already defined`) - this.unraveler.skipMappingValue() - continue + this.context.error(nextKey, `'${nextKey.value}' is already defined`); + this.unraveler.skipMappingValue(); + continue; } - upperKeys[upperKey] = true + upperKeys[upperKey] = true; // Validate - this.validate(nextKey, keyDefinition) + this.validate(nextKey, keyDefinition); // Add the pair - const nextValue = this.evaluate(valueDefinition) - mapping.add(nextKey, nextValue) + const nextValue = this.evaluate(valueDefinition); + mapping.add(nextKey, nextValue); } - this.unraveler.readMappingEnd() + this.unraveler.readMappingEnd(); } - private validate( - literal: LiteralToken, - definition: DefinitionInfo - ): LiteralToken { + private validate(literal: LiteralToken, definition: DefinitionInfo): LiteralToken { // Legal - const scalarDefinitions = definition.getScalarDefinitions() - if (scalarDefinitions.some((x) => x.isMatch(literal))) { - return literal + const scalarDefinitions = definition.getScalarDefinitions(); + if (scalarDefinitions.some(x => x.isMatch(literal))) { + return literal; } // Not a string, convert if (literal.templateTokenType !== TokenType.String) { - const stringLiteral = new StringToken( - literal.file, - literal.range, - literal.toString(), - literal.definitionInfo - ) + const stringLiteral = new StringToken(literal.file, literal.range, literal.toString(), literal.definitionInfo); // Legal - if (scalarDefinitions.some((x) => x.isMatch(stringLiteral))) { - return stringLiteral + if (scalarDefinitions.some(x => x.isMatch(stringLiteral))) { + return stringLiteral; } } // Illegal - this.context.error(literal, `Unexpected value '${literal.toString()}'`) - return literal + this.context.error(literal, `Unexpected value '${literal.toString()}'`); + return literal; } } @@ -368,83 +317,78 @@ class DefinitionInfo { /** * Hashtable of available contexts */ - private readonly _upperAvailable: { [context: string]: boolean } + private readonly _upperAvailable: {[context: string]: boolean}; /** * Allowed context */ - private readonly _allowed: string[] - private readonly _schema: TemplateSchema - public readonly isDefinitionInfo = true - public readonly definition: Definition - public readonly expand: boolean + private readonly _allowed: string[]; + private readonly _schema: TemplateSchema; + public readonly isDefinitionInfo = true; + public readonly definition: Definition; + public readonly expand: boolean; - public constructor(context: TemplateContext, name: string) - public constructor(parent: DefinitionInfo, name: string) - public constructor( - contextOrParent: TemplateContext | DefinitionInfo, - name: string - ) { + public constructor(context: TemplateContext, name: string); + public constructor(parent: DefinitionInfo, name: string); + public constructor(contextOrParent: TemplateContext | DefinitionInfo, name: string) { // "parent" overload - let parent: DefinitionInfo | undefined - if ( - (contextOrParent as DefinitionInfo | undefined)?.isDefinitionInfo === true - ) { - parent = contextOrParent as DefinitionInfo - this._schema = parent._schema - this._upperAvailable = parent._upperAvailable + let parent: DefinitionInfo | undefined; + if ((contextOrParent as DefinitionInfo | undefined)?.isDefinitionInfo === true) { + parent = contextOrParent as DefinitionInfo; + this._schema = parent._schema; + this._upperAvailable = parent._upperAvailable; } // "context" overload else { - const context = contextOrParent as TemplateContext - this._schema = context.schema - this._upperAvailable = {} + const context = contextOrParent as TemplateContext; + this._schema = context.schema; + this._upperAvailable = {}; for (const namedContext of context.expressionNamedContexts) { - this._upperAvailable[namedContext.toUpperCase()] = true + this._upperAvailable[namedContext.toUpperCase()] = true; } for (const func of context.expressionFunctions) { - this._upperAvailable[`${func.name}()`.toUpperCase()] = true + this._upperAvailable[`${func.name}()`.toUpperCase()] = true; } } // Lookup the definition - this.definition = this._schema.getDefinition(name) + this.definition = this._schema.getDefinition(name); // Record allowed context if (this.definition.evaluatorContext.length > 0) { - this._allowed = [] - this.expand = true + this._allowed = []; + this.expand = true; // Copy parent allowed context - const upperSeen: { [upper: string]: boolean } = {} + const upperSeen: {[upper: string]: boolean} = {}; for (const context of parent?._allowed ?? []) { - this._allowed.push(context) - const upper = context.toUpperCase() - upperSeen[upper] = true + this._allowed.push(context); + const upper = context.toUpperCase(); + upperSeen[upper] = true; if (!this._upperAvailable[upper]) { - this.expand = false + this.expand = false; } } // Append context if unseen for (const context of this.definition.evaluatorContext) { - const upper = context.toUpperCase() + const upper = context.toUpperCase(); if (!upperSeen[upper]) { - this._allowed.push(context) - upperSeen[upper] = true - if (!this._upperAvailable[upper]) [(this.expand = false)] + this._allowed.push(context); + upperSeen[upper] = true; + if (!this._upperAvailable[upper]) [(this.expand = false)]; } } } else { - this._allowed = parent?._allowed ?? [] - this.expand = parent?.expand ?? false + this._allowed = parent?._allowed ?? []; + this.expand = parent?.expand ?? false; } } public getScalarDefinitions(): ScalarDefinition[] { - return this._schema.getScalarDefinitions(this.definition) + return this._schema.getScalarDefinitions(this.definition); } public getDefinitionsOfType(type: DefinitionType): Definition[] { - return this._schema.getDefinitionsOfType(this.definition, type) + return this._schema.getDefinitionsOfType(this.definition, type); } } diff --git a/actions-workflow-parser/src/templates/template-reader.ts b/actions-workflow-parser/src/templates/template-reader.ts index 0c5e26b..e7f4d87 100644 --- a/actions-workflow-parser/src/templates/template-reader.ts +++ b/actions-workflow-parser/src/templates/template-reader.ts @@ -1,20 +1,15 @@ // template-reader *just* does schema validation -import { ObjectReader } from "./object-reader" -import { TemplateSchema } from "./schema" -import { DefinitionInfo } from "./schema/definition-info" -import { DefinitionType } from "./schema/definition-type" -import { MappingDefinition } from "./schema/mapping-definition" -import { ScalarDefinition } from "./schema/scalar-definition" -import { SequenceDefinition } from "./schema/sequence-definition" -import { StringDefinition } from "./schema/string-definition" -import { - ANY, - CLOSE_EXPRESSION, - INSERT_DIRECTIVE, - OPEN_EXPRESSION, -} from "./template-constants" -import { TemplateContext } from "./template-context" +import {ObjectReader} from "./object-reader"; +import {TemplateSchema} from "./schema"; +import {DefinitionInfo} from "./schema/definition-info"; +import {DefinitionType} from "./schema/definition-type"; +import {MappingDefinition} from "./schema/mapping-definition"; +import {ScalarDefinition} from "./schema/scalar-definition"; +import {SequenceDefinition} from "./schema/sequence-definition"; +import {StringDefinition} from "./schema/string-definition"; +import {ANY, CLOSE_EXPRESSION, INSERT_DIRECTIVE, OPEN_EXPRESSION} from "./template-constants"; +import {TemplateContext} from "./template-context"; import { BasicExpressionToken, ExpressionToken, @@ -23,13 +18,13 @@ import { MappingToken, ScalarToken, StringToken, - TemplateToken, -} from "./tokens" -import { TokenRange } from "./tokens/token-range" -import { isString } from "./tokens/type-guards" -import { TokenType } from "./tokens/types" + TemplateToken +} from "./tokens"; +import {TokenRange} from "./tokens/token-range"; +import {isString} from "./tokens/type-guards"; +import {TokenType} from "./tokens/types"; -const WHITESPACE_PATTERN = /\s/ +const WHITESPACE_PATTERN = /\s/; export function readTemplate( context: TemplateContext, @@ -37,91 +32,82 @@ export function readTemplate( objectReader: ObjectReader, fileId: number | undefined ): TemplateToken | undefined { - const reader = new TemplateReader(context, objectReader, fileId) - let value: TemplateToken | undefined + const reader = new TemplateReader(context, objectReader, fileId); + let value: TemplateToken | undefined; try { - objectReader.validateStart() - const definition = new DefinitionInfo(context.schema, type) - value = reader.readValue(definition) - objectReader.validateEnd() + objectReader.validateStart(); + const definition = new DefinitionInfo(context.schema, type); + value = reader.readValue(definition); + objectReader.validateEnd(); } catch (err) { - context.error(fileId, err) + context.error(fileId, err); } - return value + return value; } export interface ReadTemplateResult { - value: TemplateToken - bytes: number + value: TemplateToken; + bytes: number; } class TemplateReader { - private readonly _context: TemplateContext - private readonly _schema: TemplateSchema - private readonly _objectReader: ObjectReader - private readonly _fileId: number | undefined + private readonly _context: TemplateContext; + private readonly _schema: TemplateSchema; + private readonly _objectReader: ObjectReader; + private readonly _fileId: number | undefined; - public constructor( - context: TemplateContext, - objectReader: ObjectReader, - fileId: number | undefined - ) { - this._context = context - this._schema = context.schema - this._objectReader = objectReader - this._fileId = fileId + public constructor(context: TemplateContext, objectReader: ObjectReader, fileId: number | undefined) { + this._context = context; + this._schema = context.schema; + this._objectReader = objectReader; + this._fileId = fileId; } public readValue(definition: DefinitionInfo): TemplateToken { // Scalar - const literal = this._objectReader.allowLiteral() + const literal = this._objectReader.allowLiteral(); if (literal) { - let scalar = this.parseScalar(literal, definition) - scalar = this.validate(scalar, definition) - return scalar + let scalar = this.parseScalar(literal, definition); + scalar = this.validate(scalar, definition); + return scalar; } // Sequence - const sequence = this._objectReader.allowSequenceStart() + const sequence = this._objectReader.allowSequenceStart(); if (sequence) { - const sequenceDefinition = definition.getDefinitionsOfType( - DefinitionType.Sequence - )[0] as SequenceDefinition | undefined + const sequenceDefinition = definition.getDefinitionsOfType(DefinitionType.Sequence)[0] as + | SequenceDefinition + | undefined; // Legal if (sequenceDefinition) { - const itemDefinition = new DefinitionInfo( - definition, - sequenceDefinition.itemType - ) + const itemDefinition = new DefinitionInfo(definition, sequenceDefinition.itemType); // Add each item while (!this._objectReader.allowSequenceEnd()) { - const item = this.readValue(itemDefinition) - sequence.add(item) + const item = this.readValue(itemDefinition); + sequence.add(item); } } // Illegal else { // Error - this._context.error(sequence, "A sequence was not expected") + this._context.error(sequence, "A sequence was not expected"); // Skip each item while (!this._objectReader.allowSequenceEnd()) { - this.skipValue() + this.skipValue(); } } - sequence.definitionInfo = definition - return sequence + sequence.definitionInfo = definition; + return sequence; } // Mapping - const mapping = this._objectReader.allowMappingStart() + const mapping = this._objectReader.allowMappingStart(); if (mapping) { - const mappingDefinitions = definition.getDefinitionsOfType( - DefinitionType.Mapping - ) as MappingDefinition[] + const mappingDefinitions = definition.getDefinitionsOfType(DefinitionType.Mapping) as MappingDefinition[]; // Legal if (mappingDefinitions.length > 0) { @@ -130,49 +116,39 @@ class TemplateReader { Object.keys(mappingDefinitions[0].properties).length > 0 || !mappingDefinitions[0].looseKeyType ) { - this.handleMappingWithWellKnownProperties( - definition, - mappingDefinitions, - mapping - ) + this.handleMappingWithWellKnownProperties(definition, mappingDefinitions, mapping); } else { - const keyDefinition = new DefinitionInfo( - definition, - mappingDefinitions[0].looseKeyType - ) - const valueDefinition = new DefinitionInfo( - definition, - mappingDefinitions[0].looseValueType - ) + const keyDefinition = new DefinitionInfo(definition, mappingDefinitions[0].looseKeyType); + const valueDefinition = new DefinitionInfo(definition, mappingDefinitions[0].looseValueType); this.handleMappingWithAllLooseProperties( definition, keyDefinition, valueDefinition, mappingDefinitions[0], mapping - ) + ); } } // Illegal else { - this._context.error(mapping, "A mapping was not expected") + this._context.error(mapping, "A mapping was not expected"); while (this._objectReader.allowMappingEnd()) { - this.skipValue() - this.skipValue() + this.skipValue(); + this.skipValue(); } } // handleMappingWithWellKnownProperties will only set a definition // if it can identify a single matching definition if (!mapping.definitionInfo) { - mapping.definitionInfo = definition + mapping.definitionInfo = definition; } - return mapping + return mapping; } - throw new Error("Expected a scalar value, a sequence, or a mapping") + throw new Error("Expected a scalar value, a sequence, or a mapping"); } private handleMappingWithWellKnownProperties( @@ -181,41 +157,38 @@ class TemplateReader { mapping: MappingToken ): void { // Check if loose properties are allowed - let looseKeyType: string | undefined - let looseValueType: string | undefined - let looseKeyDefinition: DefinitionInfo | undefined - let looseValueDefinition: DefinitionInfo | undefined + let looseKeyType: string | undefined; + let looseValueType: string | undefined; + let looseKeyDefinition: DefinitionInfo | undefined; + let looseValueDefinition: DefinitionInfo | undefined; if (mappingDefinitions[0].looseKeyType) { - looseKeyType = mappingDefinitions[0].looseKeyType - looseValueType = mappingDefinitions[0].looseValueType + looseKeyType = mappingDefinitions[0].looseKeyType; + looseValueType = mappingDefinitions[0].looseValueType; } - const upperKeys: { [upperKey: string]: boolean } = {} - let hasExpressionKey = false + const upperKeys: {[upperKey: string]: boolean} = {}; + let hasExpressionKey = false; - let rawLiteral: LiteralToken | undefined + let rawLiteral: LiteralToken | undefined; while ((rawLiteral = this._objectReader.allowLiteral())) { - const nextKeyScalar = this.parseScalar(rawLiteral, definition) + const nextKeyScalar = this.parseScalar(rawLiteral, definition); // Expression if (nextKeyScalar.isExpression) { - hasExpressionKey = true + hasExpressionKey = true; // Legal if (definition.allowedContext.length > 0) { - const anyDefinition = new DefinitionInfo(definition, ANY) - mapping.add(nextKeyScalar, this.readValue(anyDefinition)) + const anyDefinition = new DefinitionInfo(definition, ANY); + mapping.add(nextKeyScalar, this.readValue(anyDefinition)); } // Illegal else { - this._context.error( - nextKeyScalar, - "A template expression is not allowed in this context" - ) - this.skipValue() + this._context.error(nextKeyScalar, "A template expression is not allowed in this context"); + this.skipValue(); } - continue + continue; } // Convert to StringToken if required @@ -227,92 +200,80 @@ class TemplateReader { nextKeyScalar.range, nextKeyScalar.toString(), nextKeyScalar.definitionInfo - ) + ); // Duplicate - const upperKey = nextKey.value.toUpperCase() + const upperKey = nextKey.value.toUpperCase(); if (upperKeys[upperKey]) { - this._context.error(nextKey, `'${nextKey.value}' is already defined`) - this.skipValue() - continue + this._context.error(nextKey, `'${nextKey.value}' is already defined`); + this.skipValue(); + continue; } - upperKeys[upperKey] = true + upperKeys[upperKey] = true; // Well known - const nextPropertyDef = this._schema.matchPropertyAndFilter( - mappingDefinitions, - nextKey.value - ) + const nextPropertyDef = this._schema.matchPropertyAndFilter(mappingDefinitions, nextKey.value); if (nextPropertyDef) { - const nextDefinition = new DefinitionInfo( - definition, - nextPropertyDef.type - ) + const nextDefinition = new DefinitionInfo(definition, nextPropertyDef.type); // Store the definition on the key, the value may have its own definition - nextKey.definitionInfo = nextDefinition + nextKey.definitionInfo = nextDefinition; // If the property has a description, it's a parameter that uses a shared type // and we need to make sure its description is set if there is one if (nextPropertyDef.description) { - nextKey.description = nextPropertyDef.description + nextKey.description = nextPropertyDef.description; } - const nextValue = this.readValue(nextDefinition) + const nextValue = this.readValue(nextDefinition); - mapping.add(nextKey, nextValue) - continue + mapping.add(nextKey, nextValue); + continue; } // Loose if (looseKeyType) { if (!looseKeyDefinition) { - looseKeyDefinition = new DefinitionInfo(definition, looseKeyType) - looseValueDefinition = new DefinitionInfo(definition, looseValueType!) + looseKeyDefinition = new DefinitionInfo(definition, looseKeyType); + looseValueDefinition = new DefinitionInfo(definition, looseValueType!); } - this.validate(nextKey, looseKeyDefinition) + this.validate(nextKey, looseKeyDefinition); // Store the definition on the key, the value may have its own definition - const nextDefinition = new DefinitionInfo( - definition, - mappingDefinitions[0].looseValueType - ) - nextKey.definitionInfo = nextDefinition + const nextDefinition = new DefinitionInfo(definition, mappingDefinitions[0].looseValueType); + nextKey.definitionInfo = nextDefinition; - const nextValue = this.readValue(looseValueDefinition!) - mapping.add(nextKey, nextValue) - continue + const nextValue = this.readValue(looseValueDefinition!); + mapping.add(nextKey, nextValue); + continue; } // Error - this._context.error(nextKey, `Unexpected value '${nextKey.value}'`) - this.skipValue() + this._context.error(nextKey, `Unexpected value '${nextKey.value}'`); + this.skipValue(); } // If we matched a single definition from multiple, // update the token's definition to enable more specific editor // completion and validation if (mappingDefinitions.length === 1) { - mapping.definitionInfo = new DefinitionInfo( - definition, - mappingDefinitions[0] - ) + mapping.definitionInfo = new DefinitionInfo(definition, mappingDefinitions[0]); } // Unable to filter to one definition if (mappingDefinitions.length > 1) { - const hitCount: { [key: string]: number } = {} + const hitCount: {[key: string]: number} = {}; for (const mappingDefinition of mappingDefinitions) { for (const key of Object.keys(mappingDefinition.properties)) { - hitCount[key] = (hitCount[key] ?? 0) + 1 + hitCount[key] = (hitCount[key] ?? 0) + 1; } } - const nonDuplicates: string[] = [] + const nonDuplicates: string[] = []; for (const key of Object.keys(hitCount)) { if (hitCount[key] === 1) { - nonDuplicates.push(key) + nonDuplicates.push(key); } } @@ -321,24 +282,19 @@ class TemplateReader { `There's not enough info to determine what you meant. Add one of these properties: ${nonDuplicates .sort() .join(", ")}` - ) + ); } // Check required properties else if (mappingDefinitions.length === 1 && !hasExpressionKey) { - for (const propertyName of Object.keys( - mappingDefinitions[0].properties - )) { - const propertyDef = mappingDefinitions[0].properties[propertyName] + for (const propertyName of Object.keys(mappingDefinitions[0].properties)) { + const propertyDef = mappingDefinitions[0].properties[propertyName]; if (propertyDef.required && !upperKeys[propertyName.toUpperCase()]) { - this._context.error( - mapping, - `Required property is missing: ${propertyName}` - ) + this._context.error(mapping, `Required property is missing: ${propertyName}`); } } } - this.expectMappingEnd() + this.expectMappingEnd(); } private handleMappingWithAllLooseProperties( @@ -348,31 +304,28 @@ class TemplateReader { mappingDefinition: MappingDefinition, mapping: MappingToken ): void { - let nextValue: TemplateToken - const upperKeys: { [key: string]: boolean } = {} + let nextValue: TemplateToken; + const upperKeys: {[key: string]: boolean} = {}; - let rawLiteral: LiteralToken | undefined + let rawLiteral: LiteralToken | undefined; while ((rawLiteral = this._objectReader.allowLiteral())) { - const nextKeyScalar = this.parseScalar(rawLiteral, definition) - nextKeyScalar.definitionInfo = keyDefinition + const nextKeyScalar = this.parseScalar(rawLiteral, definition); + nextKeyScalar.definitionInfo = keyDefinition; // Expression if (nextKeyScalar.isExpression) { // Legal if (definition.allowedContext.length > 0) { - nextValue = this.readValue(valueDefinition) - mapping.add(nextKeyScalar, nextValue) + nextValue = this.readValue(valueDefinition); + mapping.add(nextKeyScalar, nextValue); } // Illegal else { - this._context.error( - nextKeyScalar, - "A template expression is not allowed in this context" - ) - this.skipValue() + this._context.error(nextKeyScalar, "A template expression is not allowed in this context"); + this.skipValue(); } - continue + continue; } // Convert to StringToken if required @@ -384,38 +337,35 @@ class TemplateReader { nextKeyScalar.range, nextKeyScalar.toString(), nextKeyScalar.definitionInfo - ) + ); // Duplicate - const upperKey = nextKey.value.toUpperCase() + const upperKey = nextKey.value.toUpperCase(); if (upperKeys[upperKey]) { - this._context.error(nextKey, `'${nextKey.value}' is already defined`) - this.skipValue() - continue + this._context.error(nextKey, `'${nextKey.value}' is already defined`); + this.skipValue(); + continue; } - upperKeys[upperKey] = true + upperKeys[upperKey] = true; // Validate - this.validate(nextKey, keyDefinition) + this.validate(nextKey, keyDefinition); // Store the definition on the key, the value may have its own definition - const nextDefinition = new DefinitionInfo( - definition, - mappingDefinition.looseValueType - ) - nextKey.definitionInfo = nextDefinition + const nextDefinition = new DefinitionInfo(definition, mappingDefinition.looseValueType); + nextKey.definitionInfo = nextDefinition; // Add the pair - nextValue = this.readValue(valueDefinition) - mapping.add(nextKey, nextValue) + nextValue = this.readValue(valueDefinition); + mapping.add(nextKey, nextValue); } - this.expectMappingEnd() + this.expectMappingEnd(); } private expectMappingEnd(): void { if (!this._objectReader.allowMappingEnd()) { - throw new Error("Expected mapping end") // Should never happen + throw new Error("Expected mapping end"); // Should never happen } } @@ -427,46 +377,36 @@ class TemplateReader { // Sequence else if (this._objectReader.allowSequenceStart()) { while (!this._objectReader.allowSequenceEnd()) { - this.skipValue() + this.skipValue(); } } // Mapping else if (this._objectReader.allowMappingStart()) { while (!this._objectReader.allowMappingEnd()) { - this.skipValue() - this.skipValue() + this.skipValue(); + this.skipValue(); } } // Unexpected else { - throw new Error("Expected a scalar value, a sequence, or a mapping") + throw new Error("Expected a scalar value, a sequence, or a mapping"); } } - private validate( - scalar: ScalarToken, - definition: DefinitionInfo - ): ScalarToken { + private validate(scalar: ScalarToken, definition: DefinitionInfo): ScalarToken { switch (scalar.templateTokenType) { case TokenType.Null: case TokenType.Boolean: case TokenType.Number: case TokenType.String: { - const literal = scalar as LiteralToken + const literal = scalar as LiteralToken; // Legal - const scalarDefinitions = definition.getScalarDefinitions() - let relevantDefinition: ScalarDefinition | undefined - if ( - (relevantDefinition = scalarDefinitions.find((x) => - x.isMatch(literal) - )) - ) { - scalar.definitionInfo = new DefinitionInfo( - definition, - relevantDefinition - ) - return scalar + const scalarDefinitions = definition.getScalarDefinitions(); + let relevantDefinition: ScalarDefinition | undefined; + if ((relevantDefinition = scalarDefinitions.find(x => x.isMatch(literal)))) { + scalar.definitionInfo = new DefinitionInfo(definition, relevantDefinition); + return scalar; } // Not a string, convert @@ -476,94 +416,73 @@ class TemplateReader { literal.range, literal.toString(), literal.definitionInfo - ) + ); // Legal - if ( - (relevantDefinition = scalarDefinitions.find((x) => - x.isMatch(stringLiteral) - )) - ) { - stringLiteral.definitionInfo = new DefinitionInfo( - definition, - relevantDefinition - ) - return stringLiteral + if ((relevantDefinition = scalarDefinitions.find(x => x.isMatch(stringLiteral)))) { + stringLiteral.definitionInfo = new DefinitionInfo(definition, relevantDefinition); + return stringLiteral; } } // Illegal - this._context.error(literal, `Unexpected value '${literal.toString()}'`) - return scalar + this._context.error(literal, `Unexpected value '${literal.toString()}'`); + return scalar; } case TokenType.BasicExpression: // Illegal if (definition.allowedContext.length === 0) { - this._context.error( - scalar, - "A template expression is not allowed in this context" - ) + this._context.error(scalar, "A template expression is not allowed in this context"); } - return scalar + return scalar; default: - this._context.error(scalar, `Unexpected value '${scalar.toString()}'`) - return scalar + this._context.error(scalar, `Unexpected value '${scalar.toString()}'`); + return scalar; } } - private parseScalar( - token: LiteralToken, - definitionInfo: DefinitionInfo - ): ScalarToken { + private parseScalar(token: LiteralToken, definitionInfo: DefinitionInfo): ScalarToken { // Not a string if (!isString(token)) { - return token + return token; } - const allowedContext = definitionInfo.allowedContext - const raw = token.source || token.value + const allowedContext = definitionInfo.allowedContext; + const raw = token.source || token.value; // Check if the value is definitely a literal - let startExpression: number = raw.indexOf(OPEN_EXPRESSION) + let startExpression: number = raw.indexOf(OPEN_EXPRESSION); if (startExpression < 0) { // Doesn't contain "${{" // Check if value should still be evaluated as an expression - if ( - definitionInfo.definition instanceof StringDefinition && - definitionInfo.definition.isExpression - ) { - const expression = this.parseIntoExpressionToken( - token.range!, - raw, - allowedContext, - token - ) + if (definitionInfo.definition instanceof StringDefinition && definitionInfo.definition.isExpression) { + const expression = this.parseIntoExpressionToken(token.range!, raw, allowedContext, token); if (expression) { - return expression + return expression; } } - return token + return token; } // Break the value into segments of LiteralToken and ExpressionToken - let encounteredError = false - const segments: ScalarToken[] = [] - let i = 0 + let encounteredError = false; + const segments: ScalarToken[] = []; + let i = 0; while (i < raw.length) { // An expression starts here if (i === startExpression) { // Find the end of the expression - i.e. "}}" - startExpression = i - let endExpression = -1 - let inString = false + startExpression = i; + let endExpression = -1; + let inString = false; for (i += OPEN_EXPRESSION.length; i < raw.length; i++) { if (raw[i] === "'") { - inString = !inString // Note, this handles escaped single quotes gracefully. E.x. 'foo''bar' + inString = !inString; // Note, this handles escaped single quotes gracefully. E.x. 'foo''bar' } else if (!inString && raw[i] === "}" && raw[i - 1] === "}") { - endExpression = i - i++ - break + endExpression = i; + i++; + break; } } @@ -572,146 +491,116 @@ class TemplateReader { this._context.error( token, "The expression is not closed. An unescaped ${{ sequence was found, but the closing }} sequence was not found." - ) - return token + ); + return token; } // Parse the expression const rawExpression = raw.substr( startExpression + OPEN_EXPRESSION.length, - endExpression - - startExpression + - 1 - - OPEN_EXPRESSION.length - - CLOSE_EXPRESSION.length - ) + endExpression - startExpression + 1 - OPEN_EXPRESSION.length - CLOSE_EXPRESSION.length + ); - let tr = token.range! + let tr = token.range!; if (tr.start[0] === tr.end[0]) { // If it's a single line expression, adjust the range to only cover the sub-expression tr = { start: [tr.start[0], tr.start[1] + startExpression], - end: [tr.end[0], tr.start[1] + endExpression + 1], - } + end: [tr.end[0], tr.start[1] + endExpression + 1] + }; } else { // Adjust the range to only cover the expression for multi-line strings - const startRaw = raw.substring(0, startExpression) - const adjustedStartLine = startRaw.split("\n").length - const beginningOfLine = startRaw.lastIndexOf("\n") - const adjustedStart = startExpression - beginningOfLine - const adjustedEnd = endExpression - beginningOfLine + 1 + const startRaw = raw.substring(0, startExpression); + const adjustedStartLine = startRaw.split("\n").length; + const beginningOfLine = startRaw.lastIndexOf("\n"); + const adjustedStart = startExpression - beginningOfLine; + const adjustedEnd = endExpression - beginningOfLine + 1; tr = { start: [tr.start[0] + adjustedStartLine, adjustedStart], - end: [tr.start[0] + adjustedStartLine, adjustedEnd], - } + end: [tr.start[0] + adjustedStartLine, adjustedEnd] + }; } - const expression = this.parseIntoExpressionToken( - tr, - rawExpression, - allowedContext, - token - ) + const expression = this.parseIntoExpressionToken(tr, rawExpression, allowedContext, token); if (!expression) { // Record that we've hit an error but continue to validate any other expressions // that might be in the string - encounteredError = true + encounteredError = true; } else { // Check if a directive was used when not allowed - if ( - expression.directive && - (startExpression !== 0 || i < raw.length) - ) { + if (expression.directive && (startExpression !== 0 || i < raw.length)) { this._context.error( token, `The directive '${expression.directive}' is not allowed in this context. Directives are not supported for expressions that are embedded within a string. Directives are only supported when the entire value is an expression.` - ) - return token + ); + return token; } // Add the segment - segments.push(expression) + segments.push(expression); } // Look for the next expression - startExpression = raw.indexOf(OPEN_EXPRESSION, i) + startExpression = raw.indexOf(OPEN_EXPRESSION, i); } // The next expression is further ahead else if (i < startExpression) { // Append the segment - this.addString( - segments, - token.range, - raw.substr(i, startExpression - i), - token.definitionInfo - ) + this.addString(segments, token.range, raw.substr(i, startExpression - i), token.definitionInfo); // Adjust the position - i = startExpression + i = startExpression; } // No remaining expressions else { - this.addString( - segments, - token.range, - raw.substr(i), - token.definitionInfo - ) - break + this.addString(segments, token.range, raw.substr(i), token.definitionInfo); + break; } } // If we've hit any error during parsing, return the original token if (encounteredError) { - return token + return token; } // Check if can convert to a literal // For example, the escaped expression: ${{ '{{ this is a literal }}' }} - if ( - segments.length === 1 && - segments[0].templateTokenType === TokenType.BasicExpression - ) { - const basicExpression = segments[0] as BasicExpressionToken - const str = this.getExpressionString(basicExpression.expression) + if (segments.length === 1 && segments[0].templateTokenType === TokenType.BasicExpression) { + const basicExpression = segments[0] as BasicExpressionToken; + const str = this.getExpressionString(basicExpression.expression); if (str !== undefined) { - return new StringToken( - this._fileId, - token.range, - str, - token.definitionInfo - ) + return new StringToken(this._fileId, token.range, str, token.definitionInfo); } } // Check if only one segment if (segments.length === 1) { - return segments[0] + return segments[0]; } // Build the new expression, using the format function - const format: string[] = [] - const args: string[] = [] - const expressionTokens: BasicExpressionToken[] = [] - let argIndex = 0 + const format: string[] = []; + const args: string[] = []; + const expressionTokens: BasicExpressionToken[] = []; + let argIndex = 0; for (const segment of segments) { if (isString(segment)) { const text = segment.value .replace(/'/g, "''") // Escape quotes .replace(/\{/g, "{{") // Escape braces - .replace(/\}/g, "}}") - format.push(text) + .replace(/\}/g, "}}"); + format.push(text); } else { - format.push(`{${argIndex}}`) // Append format arg - argIndex++ + format.push(`{${argIndex}}`); // Append format arg + argIndex++; - const expression = segment as BasicExpressionToken - args.push(", ") - args.push(expression.expression) + const expression = segment as BasicExpressionToken; + args.push(", "); + args.push(expression.expression); - expressionTokens.push(expression) + expressionTokens.push(expression); } } @@ -721,7 +610,7 @@ class TemplateReader { `format('${format.join("")}'${args.join("")})`, token.definitionInfo, expressionTokens - ) + ); } private parseIntoExpressionToken( @@ -730,20 +619,15 @@ class TemplateReader { allowedContext: string[], token: TemplateToken ): ExpressionToken | undefined { - const parseExpressionResult = this.parseExpression( - tr, - rawExpression, - allowedContext, - token.definitionInfo - ) + const parseExpressionResult = this.parseExpression(tr, rawExpression, allowedContext, token.definitionInfo); // Check for error if (parseExpressionResult.error) { - this._context.error(token, parseExpressionResult.error, tr) - return undefined + this._context.error(token, parseExpressionResult.error, tr); + return undefined; } - return parseExpressionResult.expression! + return parseExpressionResult.expression!; } private parseExpression( @@ -752,55 +636,41 @@ class TemplateReader { allowedContext: string[], definitionInfo: DefinitionInfo | undefined ): ParseExpressionResult { - const trimmed = value.trim() + const trimmed = value.trim(); // Check if the value is empty if (!trimmed) { return { - error: new Error("An expression was expected"), - } + error: new Error("An expression was expected") + }; } // Try to find a matching directive - const matchDirectiveResult = this.matchDirective( - trimmed, - INSERT_DIRECTIVE, - 0 - ) + const matchDirectiveResult = this.matchDirective(trimmed, INSERT_DIRECTIVE, 0); if (matchDirectiveResult.isMatch) { return { - expression: new InsertExpressionToken( - this._fileId, - range, - definitionInfo - ), - } + expression: new InsertExpressionToken(this._fileId, range, definitionInfo) + }; } else if (matchDirectiveResult.error) { return { - error: matchDirectiveResult.error, - } + error: matchDirectiveResult.error + }; } // Check if valid expression try { - ExpressionToken.validateExpression(trimmed, allowedContext) + ExpressionToken.validateExpression(trimmed, allowedContext); } catch (err) { return { - error: err, - } + error: err + }; } // Return the expression return { - expression: new BasicExpressionToken( - this._fileId, - range, - trimmed, - definitionInfo, - undefined - ), - error: undefined, - } + expression: new BasicExpressionToken(this._fileId, range, trimmed, definitionInfo, undefined), + error: undefined + }; } private addString( @@ -810,57 +680,44 @@ class TemplateReader { definition: DefinitionInfo | undefined ): void { // If the last segment was a LiteralToken, then append to the last segment - if ( - segments.length > 0 && - segments[segments.length - 1].templateTokenType === TokenType.String - ) { - const lastSegment = segments[segments.length - 1] as StringToken - segments[segments.length - 1] = new StringToken( - this._fileId, - range, - `${lastSegment.value}${value}`, - definition - ) + if (segments.length > 0 && segments[segments.length - 1].templateTokenType === TokenType.String) { + const lastSegment = segments[segments.length - 1] as StringToken; + segments[segments.length - 1] = new StringToken(this._fileId, range, `${lastSegment.value}${value}`, definition); } // Otherwise add a new LiteralToken else { - segments.push(new StringToken(this._fileId, range, value, definition)) + segments.push(new StringToken(this._fileId, range, value, definition)); } } - private matchDirective( - trimmed: string, - directive: string, - expectedParameters: number - ): MatchDirectiveResult { - const parameters: string[] = [] + private matchDirective(trimmed: string, directive: string, expectedParameters: number): MatchDirectiveResult { + const parameters: string[] = []; if ( trimmed.startsWith(directive) && - (trimmed.length === directive.length || - WHITESPACE_PATTERN.test(trimmed[directive.length])) + (trimmed.length === directive.length || WHITESPACE_PATTERN.test(trimmed[directive.length])) ) { - let startIndex = directive.length - let inString = false - let parens = 0 + let startIndex = directive.length; + let inString = false; + let parens = 0; for (let i = startIndex; i < trimmed.length; i++) { - const c = trimmed[i] + const c = trimmed[i]; if (WHITESPACE_PATTERN.test(c) && !inString && parens == 0) { if (startIndex < 1) { - parameters.push(trimmed.substr(startIndex, i - startIndex)) + parameters.push(trimmed.substr(startIndex, i - startIndex)); } - startIndex = i + 1 + startIndex = i + 1; } else if (c === "'") { - inString = !inString + inString = !inString; } else if (c === "(" && !inString) { - parens++ + parens++; } else if (c === ")" && !inString) { - parens-- + parens--; } } if (startIndex < trimmed.length) { - parameters.push(trimmed.substr(startIndex)) + parameters.push(trimmed.substr(startIndex)); } if (expectedParameters != parameters.length) { @@ -869,51 +726,51 @@ class TemplateReader { parameters: [], error: new Error( `Exactly ${expectedParameters} parameter(s) were expected following the directive '${directive}'. Actual parameter count: ${parameters.length}` - ), - } + ) + }; } return { isMatch: true, - parameters: parameters, - } + parameters: parameters + }; } return { isMatch: false, - parameters: parameters, - } + parameters: parameters + }; } private getExpressionString(trimmed: string): string | undefined { - const result: string[] = [] + const result: string[] = []; - let inString = false + let inString = false; for (let i = 0; i < trimmed.length; i++) { - const c = trimmed[i] + const c = trimmed[i]; if (c === "'") { - inString = !inString + inString = !inString; if (inString && i !== 0) { - result.push(c) + result.push(c); } } else if (!inString) { - return undefined + return undefined; } else { - result.push(c) + result.push(c); } } - return result.join("") + return result.join(""); } } interface ParseExpressionResult { - expression: ExpressionToken | undefined - error: Error | undefined + expression: ExpressionToken | undefined; + error: Error | undefined; } interface MatchDirectiveResult { - isMatch: boolean - parameters: string[] - error: Error | undefined + isMatch: boolean; + parameters: string[]; + error: Error | undefined; } diff --git a/actions-workflow-parser/src/templates/template-unraveler.ts b/actions-workflow-parser/src/templates/template-unraveler.ts index 781d18c..88d799a 100644 --- a/actions-workflow-parser/src/templates/template-unraveler.ts +++ b/actions-workflow-parser/src/templates/template-unraveler.ts @@ -1,12 +1,8 @@ // Does just-in-time expression expansion -import { - evaluateMappingToken, - evaluateStringToken, - evaluateTemplateToken, -} from "./evaluate-expression" -import { CLOSE_EXPRESSION, OPEN_EXPRESSION } from "./template-constants" -import { TemplateContext } from "./template-context" +import {evaluateMappingToken, evaluateStringToken, evaluateTemplateToken} from "./evaluate-expression"; +import {CLOSE_EXPRESSION, OPEN_EXPRESSION} from "./template-constants"; +import {TemplateContext} from "./template-context"; import { BasicExpressionToken, InsertExpressionToken, @@ -15,9 +11,9 @@ import { ScalarToken, SequenceToken, StringToken, - TemplateToken, -} from "./tokens" -import { TokenType } from "./tokens/types" + TemplateToken +} from "./tokens"; +import {TokenType} from "./tokens/types"; /** * This class allows callers to easily traverse a template object. @@ -25,137 +21,113 @@ import { TokenType } from "./tokens/types" * and memory tracking. */ export class TemplateUnraveler { - private readonly _context: TemplateContext - private _current: ReaderState | undefined - private _expanded = false + private readonly _context: TemplateContext; + private _current: ReaderState | undefined; + private _expanded = false; - public constructor( - context: TemplateContext, - template: TemplateToken, - removeBytes: number - ) { - this._context = context + public constructor(context: TemplateContext, template: TemplateToken, removeBytes: number) { + this._context = context; // Initialize the reader state - this.moveFirst(template, removeBytes) + this.moveFirst(template, removeBytes); } public allowScalar(expand: boolean): ScalarToken | undefined { if (expand) { - this.unravel(true) + this.unravel(true); } if (this._current?.value.isScalar) { - const scalar = this._current.value as ScalarToken - this.moveNext() - return scalar + const scalar = this._current.value as ScalarToken; + this.moveNext(); + return scalar; } - return undefined + return undefined; } public allowSequenceStart(expand: boolean): SequenceToken | undefined { if (expand) { - this.unravel(true) + this.unravel(true); } - if ( - this._current?.value.templateTokenType === TokenType.Sequence && - (this._current as SequenceState).isStart - ) { + if (this._current?.value.templateTokenType === TokenType.Sequence && (this._current as SequenceState).isStart) { const sequence = new SequenceToken( this._current.value.file, this._current.value.range, this._current.value.definitionInfo - ) - this.moveNext() - return sequence + ); + this.moveNext(); + return sequence; } - return undefined + return undefined; } public allowSequenceEnd(expand: boolean): boolean { if (expand) { - this.unravel(true) + this.unravel(true); } - if ( - this._current?.value.templateTokenType === TokenType.Sequence && - (this._current as SequenceState).isEnd - ) { - this.moveNext() - return true + if (this._current?.value.templateTokenType === TokenType.Sequence && (this._current as SequenceState).isEnd) { + this.moveNext(); + return true; } - return false + return false; } public allowMappingStart(expand: boolean): MappingToken | undefined { if (expand) { - this.unravel(true) + this.unravel(true); } - if ( - this._current?.value.templateTokenType === TokenType.Mapping && - (this._current as MappingState).isStart - ) { + if (this._current?.value.templateTokenType === TokenType.Mapping && (this._current as MappingState).isStart) { const mapping = new MappingToken( this._current.value.file, this._current.value.range, this._current.value.definitionInfo - ) - this.moveNext() - return mapping + ); + this.moveNext(); + return mapping; } - return undefined + return undefined; } public allowMappingEnd(expand: boolean): boolean { if (expand) { - this.unravel(true) + this.unravel(true); } - if ( - this._current?.value.templateTokenType === TokenType.Mapping && - (this._current as MappingState).isEnd - ) { - this.moveNext() - return true + if (this._current?.value.templateTokenType === TokenType.Mapping && (this._current as MappingState).isEnd) { + this.moveNext(); + return true; } - return false + return false; } public readEnd(): void { if (this._current !== undefined) { - throw new Error( - `Expected end of template object. ${this.dumpState("readEnd")}` - ) + throw new Error(`Expected end of template object. ${this.dumpState("readEnd")}`); } } public readMappingEnd(): void { if (!this.allowMappingEnd(false)) { - throw new Error( - `Unexpected state while attempting to read the mapping end. ${this.dumpState( - "readMappingEnd" - )}` - ) + throw new Error(`Unexpected state while attempting to read the mapping end. ${this.dumpState("readMappingEnd")}`); } } public skipSequenceItem(): void { if (this._current?.parent?.value.templateTokenType !== TokenType.Sequence) { throw new Error( - `Unexpected state while attempting to skip the current sequence item. ${this.dumpState( - "skipSequenceItem" - )}` - ) + `Unexpected state while attempting to skip the current sequence item. ${this.dumpState("skipSequenceItem")}` + ); } - this.moveNext(true) + this.moveNext(true); } public skipMappingKey(): void { @@ -164,13 +136,11 @@ export class TemplateUnraveler { !(this._current.parent as MappingState).isKey ) { throw new Error( - `Unexpected state while attempting to skip the current mapping key. ${this.dumpState( - "skipMappingKey" - )}` - ) + `Unexpected state while attempting to skip the current mapping key. ${this.dumpState("skipMappingKey")}` + ); } - this.moveNext(true) + this.moveNext(true); } public skipMappingValue(): void { @@ -179,38 +149,36 @@ export class TemplateUnraveler { (this._current.parent as MappingState).isKey ) { throw new Error( - `Unexpected state while attempting to skip the current mapping value. ${this.dumpState( - "skipMappingValue" - )}` - ) + `Unexpected state while attempting to skip the current mapping value. ${this.dumpState("skipMappingValue")}` + ); } - this.moveNext(true) + this.moveNext(true); } private dumpState(operation: string): string { - const result: string[] = [] + const result: string[] = []; if (operation) { - result.push(`Operation: ${operation}`) + result.push(`Operation: ${operation}`); } if (this._current === undefined) { - result.push(`State: (null)`) + result.push(`State: (null)`); } else { - result.push(`State:`) - result.push("") + result.push(`State:`); + result.push(""); // Push state hierarchy - const stack: ReaderState[] = [] - let curr: ReaderState | undefined = this._current + const stack: ReaderState[] = []; + let curr: ReaderState | undefined = this._current; while (curr) { - result.push(curr.toString()) - curr = curr.parent + result.push(curr.toString()); + curr = curr.parent; } } - return result.join("\n") + return result.join("\n"); } private moveFirst(value: TemplateToken, removeBytes: number): void { @@ -222,19 +190,17 @@ export class TemplateUnraveler { case TokenType.Sequence: case TokenType.Mapping: case TokenType.BasicExpression: - break + break; default: - throw new Error( - `Unexpected type '${value.typeName()}' when initializing object reader state` - ) + throw new Error(`Unexpected type '${value.typeName()}' when initializing object reader state`); } - this._current = ReaderState.createState(undefined, value, this._context) + this._current = ReaderState.createState(undefined, value, this._context); } private moveNext(skipNestedEvents?: boolean): void { if (this._current === undefined) { - return + return; } // Sequence start @@ -244,8 +210,8 @@ export class TemplateUnraveler { !skipNestedEvents ) { // Move to the first item or sequence end - const sequenceState = this._current as SequenceState - this._current = sequenceState.next() + const sequenceState = this._current as SequenceState; + this._current = sequenceState.next(); } // Mapping state else if ( @@ -254,56 +220,50 @@ export class TemplateUnraveler { !skipNestedEvents ) { // Move to the first item key or mapping end - const mappingState = this._current as MappingState - this._current = mappingState.next() + const mappingState = this._current as MappingState; + this._current = mappingState.next(); } // Parent is a sequence - else if ( - this._current.parent?.value.templateTokenType === TokenType.Sequence - ) { + else if (this._current.parent?.value.templateTokenType === TokenType.Sequence) { // Move to the next item or sequence end - const parentSequenceState = this._current.parent as SequenceState - this._current = parentSequenceState.next() + const parentSequenceState = this._current.parent as SequenceState; + this._current = parentSequenceState.next(); } // Parent is a mapping - else if ( - this._current.parent?.value.templateTokenType === TokenType.Mapping - ) { + else if (this._current.parent?.value.templateTokenType === TokenType.Mapping) { // Move to the next item value, item key, or mapping end - const parentMappingState = this._current.parent as MappingState - this._current = parentMappingState.next() + const parentMappingState = this._current.parent as MappingState; + this._current = parentMappingState.next(); } // Parent is an expression end else if (this._current.parent !== undefined) { - this._current = this._current.parent + this._current = this._current.parent; } // Parent is undefined else { - this._current = undefined + this._current = undefined; } - this._expanded = false - this.unravel(false) + this._expanded = false; + this.unravel(false); } private unravel(expand: boolean): void { if (this._expanded) { - return + return; } for (;;) { if (this._current === undefined) { - break + break; } // Literal else if (this._current.value.isLiteral) { - break + break; } // Basic expression - else if ( - this._current.value.templateTokenType === TokenType.BasicExpression - ) { - const basicExpressionState = this._current as BasicExpressionState + else if (this._current.value.templateTokenType === TokenType.BasicExpression) { + const basicExpressionState = this._current as BasicExpressionState; // Sequence item is a basic expression start // For example: @@ -311,14 +271,11 @@ export class TemplateUnraveler { // - script: credscan // - ${{ parameters.preBuild }} // - script: build - if ( - basicExpressionState.isStart && - this._current.parent?.value.templateTokenType === TokenType.Sequence - ) { + if (basicExpressionState.isStart && this._current.parent?.value.templateTokenType === TokenType.Sequence) { if (expand) { - this.sequenceItemBasicExpression() + this.sequenceItemBasicExpression(); } else { - break + break; } } // Mapping key is a basic expression start @@ -331,9 +288,9 @@ export class TemplateUnraveler { (this._current.parent as MappingState).isKey ) { if (expand) { - this.mappingKeyBasicExpression() + this.mappingKeyBasicExpression(); } else { - break + break; } } // Mapping value is a basic expression start @@ -347,82 +304,71 @@ export class TemplateUnraveler { !(this._current.parent as MappingState).isKey ) { if (expand) { - this.mappingValueBasicExpression() + this.mappingValueBasicExpression(); } else { - break + break; } } // Root basic expression start - else if ( - basicExpressionState.isStart && - this._current.parent === undefined - ) { + else if (basicExpressionState.isStart && this._current.parent === undefined) { if (expand) { - this.rootBasicExpression() + this.rootBasicExpression(); } else { - break + break; } } // Basic expression end else if (basicExpressionState.isEnd) { - this.endExpression() + this.endExpression(); } else { - this.unexpectedState("unravel basic expression") + this.unexpectedState("unravel basic expression"); } } // Mapping else if (this._current.value.templateTokenType === TokenType.Mapping) { - const mappingState = this._current as MappingState + const mappingState = this._current as MappingState; // Mapping end, closing an "insert" mapping insertion - if ( - mappingState.isEnd && - this._current.parent?.value.templateTokenType === - TokenType.InsertExpression - ) { - this._current = this._current.parent // Skip to the expression end + if (mappingState.isEnd && this._current.parent?.value.templateTokenType === TokenType.InsertExpression) { + this._current = this._current.parent; // Skip to the expression end } // Normal mapping start else if (mappingState.isStart) { - break + break; } // Normal mapping end else if (mappingState.isEnd) { - break + break; } else { - this.unexpectedState("unravel mapping") + this.unexpectedState("unravel mapping"); } } // Sequence else if (this._current.value.templateTokenType === TokenType.Sequence) { - const sequenceState = this._current as SequenceState + const sequenceState = this._current as SequenceState; // Sequence end, closing a sequence insertion if ( sequenceState.isEnd && - this._current.parent?.value.templateTokenType === - TokenType.BasicExpression && - this._current.parent.parent?.value.templateTokenType === - TokenType.Sequence + this._current.parent?.value.templateTokenType === TokenType.BasicExpression && + this._current.parent.parent?.value.templateTokenType === TokenType.Sequence ) { - this._current = this._current.parent // Skip to the expression end + this._current = this._current.parent; // Skip to the expression end } // Normal sequence start else if (sequenceState.isStart) { - break + break; } // Normal sequence end else if (sequenceState.isEnd) { - break + break; } else { - this.unexpectedState("unravel sequence") + this.unexpectedState("unravel sequence"); } } // Insert expression - else if ( - this._current.value.templateTokenType === TokenType.InsertExpression - ) { - const insertExpressionState = this._current as InsertExpressionState + else if (this._current.value.templateTokenType === TokenType.InsertExpression) { + const insertExpressionState = this._current as InsertExpressionState; // Mapping key, beginning an "insert" mapping insertion // For example: @@ -435,31 +381,31 @@ export class TemplateUnraveler { (this._current.parent as MappingState).isKey ) { if (expand) { - this.startMappingInsertion() + this.startMappingInsertion(); } else { - break + break; } } // Expression end else if (insertExpressionState.isEnd) { - this.endExpression() + this.endExpression(); } // Not allowed else if (insertExpressionState.isStart) { this._context.error( insertExpressionState.value, `The expression directive '${insertExpressionState.expression.directive}' is not supported in this context` - ) - this._current = insertExpressionState.toStringToken() + ); + this._current = insertExpressionState.toStringToken(); } else { - this.unexpectedState("unravel insert expression") + this.unexpectedState("unravel insert expression"); } } else { - this.unexpectedState("unravel") + this.unexpectedState("unravel"); } } - this._expanded = expand + this._expanded = expand; } private sequenceItemBasicExpression() { @@ -477,26 +423,26 @@ export class TemplateUnraveler { // // BasicExpressionState // m_current - const expressionState = this._current as BasicExpressionState - const expression = expressionState.value as BasicExpressionToken - let value: TemplateToken | undefined + const expressionState = this._current as BasicExpressionState; + const expression = expressionState.value as BasicExpressionToken; + let value: TemplateToken | undefined; try { - value = evaluateTemplateToken(expression, expressionState.context) + value = evaluateTemplateToken(expression, expressionState.context); } catch (err) { - this._context.error(expression, err) + this._context.error(expression, err); } // Move to the nested sequence, skip the sequence start if (value?.templateTokenType === TokenType.Sequence) { - this._current = expressionState.next(value, true) + this._current = expressionState.next(value, true); } // Move to the new value else if (value !== undefined) { - this._current = expressionState.next(value, false) + this._current = expressionState.next(value, false); } // Move to the expression end else if (value === undefined) { - expressionState.end() + expressionState.end(); } } @@ -517,24 +463,24 @@ export class TemplateUnraveler { // BasicExpressionState // m_current // The expression should evaluate to a string - const expressionState = this._current as BasicExpressionState - const expression = expressionState.value as BasicExpressionToken - let stringToken: StringToken | undefined + const expressionState = this._current as BasicExpressionState; + const expression = expressionState.value as BasicExpressionToken; + let stringToken: StringToken | undefined; try { - const result = evaluateStringToken(expression, expressionState.context) - stringToken = result as StringToken + const result = evaluateStringToken(expression, expressionState.context); + stringToken = result as StringToken; } catch (err) { - this._context.error(expression, err) + this._context.error(expression, err); } // Move to the stringToken if (stringToken !== undefined) { - this._current = expressionState.next(stringToken, false) + this._current = expressionState.next(stringToken, false); } // Move to the next key or mapping end else { - const parentMappingState = this._current!.parent as MappingState - this._current = parentMappingState.next() // Next key or mapping end + const parentMappingState = this._current!.parent as MappingState; + this._current = parentMappingState.next(); // Next key or mapping end } } @@ -555,23 +501,18 @@ export class TemplateUnraveler { // // BasicExpressionState // m_current - const expressionState = this._current as BasicExpressionState - const expression = expressionState.value as BasicExpressionToken - let value: TemplateToken + const expressionState = this._current as BasicExpressionState; + const expression = expressionState.value as BasicExpressionToken; + let value: TemplateToken; try { - value = evaluateTemplateToken(expression, expressionState.context) + value = evaluateTemplateToken(expression, expressionState.context); } catch (err) { - this._context.error(expression, err) - value = new StringToken( - expression.file, - expression.range, - "", - expression.definitionInfo - ) + this._context.error(expression, err); + value = new StringToken(expression.file, expression.range, "", expression.definitionInfo); } // Move to the new value - this._current = expressionState.next(value, false) + this._current = expressionState.next(value, false); } private rootBasicExpression() { @@ -583,23 +524,18 @@ export class TemplateUnraveler { // // BasicExpressionState // m_current - const expressionState = this._current as BasicExpressionState - const expression = expressionState.value as BasicExpressionToken - let value: TemplateToken + const expressionState = this._current as BasicExpressionState; + const expression = expressionState.value as BasicExpressionToken; + let value: TemplateToken; try { - value = evaluateTemplateToken(expression, expressionState.context) + value = evaluateTemplateToken(expression, expressionState.context); } catch (err) { - this._context.error(expression, err) - value = new StringToken( - expression.file, - expression.range, - "", - expression.definitionInfo - ) + this._context.error(expression, err); + value = new StringToken(expression.file, expression.range, "", expression.definitionInfo); } // Move to the new value - this._current = expressionState.next(value, false) + this._current = expressionState.next(value, false); } private startMappingInsertion() { @@ -622,401 +558,336 @@ export class TemplateUnraveler { // // InsertExpressionState // m_current - const expressionState = this._current as InsertExpressionState - const parentMappingState = expressionState.parent as MappingState - const nestedValue = parentMappingState.mapping.get( - parentMappingState.index - ).value - let nestedMapping: MappingToken | undefined + const expressionState = this._current as InsertExpressionState; + const parentMappingState = expressionState.parent as MappingState; + const nestedValue = parentMappingState.mapping.get(parentMappingState.index).value; + let nestedMapping: MappingToken | undefined; if (nestedValue.templateTokenType === TokenType.Mapping) { - nestedMapping = nestedValue as MappingToken + nestedMapping = nestedValue as MappingToken; } else if (nestedValue.templateTokenType === TokenType.BasicExpression) { - const basicExpression = nestedValue as BasicExpressionToken + const basicExpression = nestedValue as BasicExpressionToken; // The expression should evaluate to a mapping try { - nestedMapping = evaluateMappingToken( - basicExpression, - expressionState.context - ) + nestedMapping = evaluateMappingToken(basicExpression, expressionState.context); } catch (err) { - this._context.error(basicExpression, err) + this._context.error(basicExpression, err); } } else { - this._context.error(nestedValue, "Expected a mapping") + this._context.error(nestedValue, "Expected a mapping"); } // Move to the nested first key if ((nestedMapping?.count ?? 0) > 0) { - this._current = expressionState.next(nestedMapping!) + this._current = expressionState.next(nestedMapping!); } // Move to the expression end else { - expressionState.end() + expressionState.end(); } } private endExpression(): void { if (!this._current) { - throw new Error("_current should not be null") + throw new Error("_current should not be null"); } // End of document if (this._current.parent === undefined) { - this._current = undefined + this._current = undefined; } // End basic expression - else if ( - this._current.value.templateTokenType === TokenType.BasicExpression - ) { + else if (this._current.value.templateTokenType === TokenType.BasicExpression) { // Move to the next item or sequence end if (this._current.parent.value.templateTokenType === TokenType.Sequence) { - const parentSequenceState = this._current.parent as SequenceState - this._current = parentSequenceState.next() + const parentSequenceState = this._current.parent as SequenceState; + this._current = parentSequenceState.next(); } // Move to the next key, next value, or mapping end else { - const parentMappingState = this._current.parent as MappingState - this._current = parentMappingState.next() + const parentMappingState = this._current.parent as MappingState; + this._current = parentMappingState.next(); } } // End "insert" mapping insertion else { // Move to the next key or mapping end - const parentMappingState = this._current.parent as MappingState - this._current = parentMappingState.next() + const parentMappingState = this._current.parent as MappingState; + this._current = parentMappingState.next(); } } private unexpectedState(operation: string): void { - throw new Error( - `Unexpected state while unraveling expressions. ${this.dumpState( - operation - )}` - ) + throw new Error(`Unexpected state while unraveling expressions. ${this.dumpState(operation)}`); } } abstract class ReaderState { - public readonly parent: ReaderState | undefined - public readonly value: TemplateToken - public readonly context: TemplateContext + public readonly parent: ReaderState | undefined; + public readonly value: TemplateToken; + public readonly context: TemplateContext; - public constructor( - parent: ReaderState | undefined, - value: TemplateToken, - context: TemplateContext - ) { - this.parent = parent - this.value = value - this.context = context + public constructor(parent: ReaderState | undefined, value: TemplateToken, context: TemplateContext) { + this.parent = parent; + this.value = value; + this.context = context; } - public abstract toString(): string + public abstract toString(): string; - public static createState( - parent: ReaderState | undefined, - value: TemplateToken, - context: TemplateContext - ) { + public static createState(parent: ReaderState | undefined, value: TemplateToken, context: TemplateContext) { switch (value.templateTokenType) { case TokenType.Null: case TokenType.Boolean: case TokenType.Number: case TokenType.String: - return new LiteralState(parent, value as LiteralToken, context) + return new LiteralState(parent, value as LiteralToken, context); case TokenType.Sequence: - return new SequenceState(parent, value as SequenceToken, context) + return new SequenceState(parent, value as SequenceToken, context); case TokenType.Mapping: - return new MappingState(parent, value as MappingToken, context) + return new MappingState(parent, value as MappingToken, context); case TokenType.BasicExpression: - return new BasicExpressionState( - parent, - value as BasicExpressionToken, - context - ) + return new BasicExpressionState(parent, value as BasicExpressionToken, context); case TokenType.InsertExpression: - return new InsertExpressionState( - parent, - value as InsertExpressionToken, - context - ) + return new InsertExpressionState(parent, value as InsertExpressionToken, context); default: - throw new Error( - `Unexpected type '${value.typeName()}' when constructing reader state` - ) + throw new Error(`Unexpected type '${value.typeName()}' when constructing reader state`); } } } class LiteralState extends ReaderState { - public constructor( - parent: ReaderState | undefined, - literal: LiteralToken, - context: TemplateContext - ) { - super(parent, literal, context) + public constructor(parent: ReaderState | undefined, literal: LiteralToken, context: TemplateContext) { + super(parent, literal, context); } public override toString(): string { - const result: string[] = [] - result.push("LiteralState") - return `${result.join("\n")}\n` + const result: string[] = []; + result.push("LiteralState"); + return `${result.join("\n")}\n`; } } class SequenceState extends ReaderState { - private _isStart = true - private _index = 0 + private _isStart = true; + private _index = 0; - public constructor( - parent: ReaderState | undefined, - sequence: SequenceToken, - context: TemplateContext - ) { - super(parent, sequence, context) + public constructor(parent: ReaderState | undefined, sequence: SequenceToken, context: TemplateContext) { + super(parent, sequence, context); } /** * Indicates whether the state represents the sequence-start event */ public get isStart(): boolean { - return this._isStart + return this._isStart; } /** * The current index within the sequence */ public get index(): number { - return this._index + return this._index; } /** * Indicates whether the state represents the sequence-end event */ public get isEnd(): boolean { - return !this.isStart && this.index >= this.sequence.count + return !this.isStart && this.index >= this.sequence.count; } public get sequence(): SequenceToken { - return this.value as SequenceToken + return this.value as SequenceToken; } public next(): ReaderState { // Adjust the state if (this._isStart) { - this._isStart = false + this._isStart = false; } else { - this._index++ + this._index++; } // Return the next event if (!this.isEnd) { - return ReaderState.createState( - this, - this.sequence.get(this._index), - this.context - ) + return ReaderState.createState(this, this.sequence.get(this._index), this.context); } else { - return this + return this; } } public override toString(): string { - const result: string[] = [] - result.push("SequenceState:") - result.push(` isStart: ${this._isStart}`) - result.push(` index: ${this._index}`) - result.push(` isEnd: ${this.isEnd}`) - return `${result.join("\n")}\n` + const result: string[] = []; + result.push("SequenceState:"); + result.push(` isStart: ${this._isStart}`); + result.push(` index: ${this._index}`); + result.push(` isEnd: ${this.isEnd}`); + return `${result.join("\n")}\n`; } } class MappingState extends ReaderState { - private _isStart = true - private _index = 0 - private _isKey = false + private _isStart = true; + private _index = 0; + private _isKey = false; - public constructor( - parent: ReaderState | undefined, - mapping: MappingToken, - context: TemplateContext - ) { - super(parent, mapping, context) + public constructor(parent: ReaderState | undefined, mapping: MappingToken, context: TemplateContext) { + super(parent, mapping, context); } /** * Indicates whether the state represents the mapping-start event */ public get isStart(): boolean { - return this._isStart + return this._isStart; } /** * The current index within the mapping */ public get index(): number { - return this._index + return this._index; } /** * Indicates whether the state represents a mapping-key position */ public get isKey(): boolean { - return this._isKey + return this._isKey; } /** * Indicates whether the state represents the mapping-end event */ public get isEnd(): boolean { - return !this._isStart && this._index >= this.mapping.count + return !this._isStart && this._index >= this.mapping.count; } public get mapping(): MappingToken { - return this.value as MappingToken + return this.value as MappingToken; } public next(): ReaderState { // Adjust the state if (this._isStart) { - this._isStart = false - this._isKey = true + this._isStart = false; + this._isKey = true; } else if (this._isKey) { - this._isKey = false + this._isKey = false; } else { - this._index++ - this._isKey = true + this._index++; + this._isKey = true; } // Return the next event if (!this.isEnd) { if (this._isKey) { - return ReaderState.createState( - this, - this.mapping.get(this._index).key, - this.context - ) + return ReaderState.createState(this, this.mapping.get(this._index).key, this.context); } else { - return ReaderState.createState( - this, - this.mapping.get(this._index).value, - this.context - ) + return ReaderState.createState(this, this.mapping.get(this._index).value, this.context); } } else { - return this + return this; } } public override toString(): string { - const result: string[] = [] - result.push("MappingState:") - result.push(` isStart: ${this._isStart}`) - result.push(` index: ${this._index}`) - result.push(` isKey: ${this._isKey}`) - result.push(` isEnd: ${this.isEnd}`) - return `${result.join("\n")}\n` + const result: string[] = []; + result.push("MappingState:"); + result.push(` isStart: ${this._isStart}`); + result.push(` index: ${this._index}`); + result.push(` isKey: ${this._isKey}`); + result.push(` isEnd: ${this.isEnd}`); + return `${result.join("\n")}\n`; } } class BasicExpressionState extends ReaderState { - private _isStart = true + private _isStart = true; - public constructor( - parent: ReaderState | undefined, - expression: BasicExpressionToken, - context: TemplateContext - ) { - super(parent, expression, context) + public constructor(parent: ReaderState | undefined, expression: BasicExpressionToken, context: TemplateContext) { + super(parent, expression, context); } /** * Indicates whether entering the expression */ public get isStart(): boolean { - return this._isStart + return this._isStart; } /** * Indicates whether leaving the expression */ public get isEnd(): boolean { - return !this._isStart + return !this._isStart; } public next(value: TemplateToken, isSequenceInsertion: boolean): ReaderState { // Adjust the state - this._isStart = false + this._isStart = false; // Create the nested state - const nestedState = ReaderState.createState(this, value, this.context) + const nestedState = ReaderState.createState(this, value, this.context); if (isSequenceInsertion) { - const nestedSequenceState = nestedState as SequenceState - return nestedSequenceState.next() // Skip the sequence start + const nestedSequenceState = nestedState as SequenceState; + return nestedSequenceState.next(); // Skip the sequence start } else { - return nestedState + return nestedState; } } public end(): ReaderState { - this._isStart = false - return this + this._isStart = false; + return this; } public override toString(): string { - const result: string[] = [] - result.push("BasicExpressionState:") - result.push(` isStart: ${this._isStart}`) - return `${result.join("\n")}\n` + const result: string[] = []; + result.push("BasicExpressionState:"); + result.push(` isStart: ${this._isStart}`); + return `${result.join("\n")}\n`; } } class InsertExpressionState extends ReaderState { - private _isStart = true + private _isStart = true; - public constructor( - parent: ReaderState | undefined, - expression: InsertExpressionToken, - context: TemplateContext - ) { - super(parent, expression, context) + public constructor(parent: ReaderState | undefined, expression: InsertExpressionToken, context: TemplateContext) { + super(parent, expression, context); } /** * Indicates whether entering or leaving the expression */ public get isStart(): boolean { - return this._isStart + return this._isStart; } /** * Indicates whether leaving the expression */ public get isEnd(): boolean { - return !this._isStart + return !this._isStart; } public get expression(): InsertExpressionToken { - return this.value as InsertExpressionToken + return this.value as InsertExpressionToken; } public next(value: MappingToken): ReaderState { // Adjust the state - this._isStart = false + this._isStart = false; // Create the nested state - const nestedState = ReaderState.createState( - this, - value, - this.context - ) as MappingState - return nestedState.next() // Skip the mapping start + const nestedState = ReaderState.createState(this, value, this.context) as MappingState; + return nestedState.next(); // Skip the mapping start } public end(): ReaderState { - this._isStart = false - return this + this._isStart = false; + return this; } /** @@ -1028,14 +899,14 @@ class InsertExpressionState extends ReaderState { this.value.range, `${OPEN_EXPRESSION} ${this.expression.directive} ${CLOSE_EXPRESSION}`, this.value.definitionInfo - ) - return ReaderState.createState(this.parent, literal, this.context) + ); + return ReaderState.createState(this.parent, literal, this.context); } public override toString(): string { - const result: string[] = [] - result.push("InsertExpressionState:") - result.push(` isStart: ${this._isStart}`) - return `${result.join("\n")}\n` + const result: string[] = []; + result.push("InsertExpressionState:"); + result.push(` isStart: ${this._isStart}`); + return `${result.join("\n")}\n`; } } diff --git a/actions-workflow-parser/src/templates/template-validation-error.ts b/actions-workflow-parser/src/templates/template-validation-error.ts index bcf01e6..06a35c4 100644 --- a/actions-workflow-parser/src/templates/template-validation-error.ts +++ b/actions-workflow-parser/src/templates/template-validation-error.ts @@ -1,4 +1,4 @@ -import { TokenRange } from "./tokens/token-range" +import {TokenRange} from "./tokens/token-range"; /** * Provides information about an error which occurred during validation @@ -13,13 +13,13 @@ export class TemplateValidationError { public get message(): string { if (this.prefix) { - return `${this.prefix}: ${this.rawMessage}` + return `${this.prefix}: ${this.rawMessage}`; } - return this.rawMessage + return this.rawMessage; } public toString(): string { - return this.message + return this.message; } } diff --git a/actions-workflow-parser/src/templates/tokens/basic-expression-token.ts b/actions-workflow-parser/src/templates/tokens/basic-expression-token.ts index 4b1d3ff..6d5912d 100644 --- a/actions-workflow-parser/src/templates/tokens/basic-expression-token.ts +++ b/actions-workflow-parser/src/templates/tokens/basic-expression-token.ts @@ -1,17 +1,17 @@ -import { DefinitionInfo } from "../schema/definition-info" +import {DefinitionInfo} from "../schema/definition-info"; -import { CLOSE_EXPRESSION, OPEN_EXPRESSION } from "../template-constants" -import { ExpressionToken } from "./expression-token" -import { ScalarToken } from "./scalar-token" -import { SerializedExpressionToken } from "./serialization" -import { TemplateToken } from "./template-token" -import { TokenRange } from "./token-range" -import { TokenType } from "./types" +import {CLOSE_EXPRESSION, OPEN_EXPRESSION} from "../template-constants"; +import {ExpressionToken} from "./expression-token"; +import {ScalarToken} from "./scalar-token"; +import {SerializedExpressionToken} from "./serialization"; +import {TemplateToken} from "./template-token"; +import {TokenRange} from "./token-range"; +import {TokenType} from "./types"; export class BasicExpressionToken extends ExpressionToken { - private readonly expr: string + private readonly expr: string; - public readonly originalExpressions: BasicExpressionToken[] | undefined + public readonly originalExpressions: BasicExpressionToken[] | undefined; /** * @param originalExpressions If the basic expression was transformed from individual expressions, these will be the original ones @@ -23,46 +23,34 @@ export class BasicExpressionToken extends ExpressionToken { definitionInfo: DefinitionInfo | undefined, originalExpressions: BasicExpressionToken[] | undefined ) { - super(TokenType.BasicExpression, file, range, undefined, definitionInfo) - this.expr = expression - this.originalExpressions = originalExpressions + super(TokenType.BasicExpression, file, range, undefined, definitionInfo); + this.expr = expression; + this.originalExpressions = originalExpressions; } public get expression(): string { - return this.expr + return this.expr; } public override clone(omitSource?: boolean): TemplateToken { return omitSource - ? new BasicExpressionToken( - undefined, - undefined, - this.expr, - this.definitionInfo, - this.originalExpressions - ) - : new BasicExpressionToken( - this.file, - this.range, - this.expr, - this.definitionInfo, - this.originalExpressions - ) + ? new BasicExpressionToken(undefined, undefined, this.expr, this.definitionInfo, this.originalExpressions) + : new BasicExpressionToken(this.file, this.range, this.expr, this.definitionInfo, this.originalExpressions); } public override toString(): string { - return `${OPEN_EXPRESSION} ${this.expr} ${CLOSE_EXPRESSION}` + return `${OPEN_EXPRESSION} ${this.expr} ${CLOSE_EXPRESSION}`; } public override toDisplayString(): string { // TODO: Implement expression display string to match `BasicExpressionToken#ToDisplayString()` in the C# parser - return ScalarToken.trimDisplayString(this.toString()) + return ScalarToken.trimDisplayString(this.toString()); } public override toJSON(): SerializedExpressionToken { return { type: TokenType.BasicExpression, - expr: this.expr, - } + expr: this.expr + }; } } diff --git a/actions-workflow-parser/src/templates/tokens/boolean-token.ts b/actions-workflow-parser/src/templates/tokens/boolean-token.ts index 4488d6a..8edc342 100644 --- a/actions-workflow-parser/src/templates/tokens/boolean-token.ts +++ b/actions-workflow-parser/src/templates/tokens/boolean-token.ts @@ -1,10 +1,10 @@ -import { LiteralToken, TemplateToken } from "." -import { DefinitionInfo } from "../schema/definition-info" -import { TokenRange } from "./token-range" -import { TokenType } from "./types" +import {LiteralToken, TemplateToken} from "."; +import {DefinitionInfo} from "../schema/definition-info"; +import {TokenRange} from "./token-range"; +import {TokenType} from "./types"; export class BooleanToken extends LiteralToken { - private readonly bool: boolean + private readonly bool: boolean; public constructor( file: number | undefined, @@ -12,25 +12,25 @@ export class BooleanToken extends LiteralToken { value: boolean, definitionInfo: DefinitionInfo | undefined ) { - super(TokenType.Boolean, file, range, definitionInfo) - this.bool = value + super(TokenType.Boolean, file, range, definitionInfo); + this.bool = value; } public get value(): boolean { - return this.bool + return this.bool; } public override clone(omitSource?: boolean): TemplateToken { return omitSource ? new BooleanToken(undefined, undefined, this.bool, this.definitionInfo) - : new BooleanToken(this.file, this.range, this.bool, this.definitionInfo) + : new BooleanToken(this.file, this.range, this.bool, this.definitionInfo); } public override toString(): string { - return this.bool ? "true" : "false" + return this.bool ? "true" : "false"; } public override toJSON() { - return this.bool + return this.bool; } } diff --git a/actions-workflow-parser/src/templates/tokens/expression-token.ts b/actions-workflow-parser/src/templates/tokens/expression-token.ts index 336052e..70072aa 100644 --- a/actions-workflow-parser/src/templates/tokens/expression-token.ts +++ b/actions-workflow-parser/src/templates/tokens/expression-token.ts @@ -1,11 +1,11 @@ -import { Lexer, Parser } from "@github/actions-expressions" -import { splitAllowedContext } from "../allowed-context" -import { DefinitionInfo } from "../schema/definition-info" -import { ScalarToken } from "./scalar-token" -import { TokenRange } from "./token-range" +import {Lexer, Parser} from "@github/actions-expressions"; +import {splitAllowedContext} from "../allowed-context"; +import {DefinitionInfo} from "../schema/definition-info"; +import {ScalarToken} from "./scalar-token"; +import {TokenRange} from "./token-range"; export abstract class ExpressionToken extends ScalarToken { - public readonly directive: string | undefined + public readonly directive: string | undefined; public constructor( type: number, @@ -14,28 +14,25 @@ export abstract class ExpressionToken extends ScalarToken { directive: string | undefined, definitionInfo: DefinitionInfo | undefined ) { - super(type, file, range, definitionInfo) - this.directive = directive + super(type, file, range, definitionInfo); + this.directive = directive; } public override get isLiteral(): boolean { - return false + return false; } public override get isExpression(): boolean { - return true + return true; } - public static validateExpression( - expression: string, - allowedContext: string[] - ): void { - const { namedContexts, functions } = splitAllowedContext(allowedContext) + public static validateExpression(expression: string, allowedContext: string[]): void { + const {namedContexts, functions} = splitAllowedContext(allowedContext); // Parse - const lexer = new Lexer(expression) - const result = lexer.lex() - const p = new Parser(result.tokens, namedContexts, functions) - p.parse() + const lexer = new Lexer(expression); + const result = lexer.lex(); + const p = new Parser(result.tokens, namedContexts, functions); + p.parse(); } } diff --git a/actions-workflow-parser/src/templates/tokens/index.ts b/actions-workflow-parser/src/templates/tokens/index.ts index 1da1088..b69fd13 100644 --- a/actions-workflow-parser/src/templates/tokens/index.ts +++ b/actions-workflow-parser/src/templates/tokens/index.ts @@ -1,13 +1,13 @@ -export { TemplateToken } from "./template-token" -export { ScalarToken } from "./scalar-token" -export { LiteralToken } from "./literal-token" -export { StringToken } from "./string-token" -export { NumberToken } from "./number-token" -export { BooleanToken } from "./boolean-token" -export { NullToken } from "./null-token" -export { KeyValuePair } from "./key-value-pair" -export { SequenceToken } from "./sequence-token" -export { MappingToken } from "./mapping-token" -export { ExpressionToken } from "./expression-token" -export { BasicExpressionToken } from "./basic-expression-token" -export { InsertExpressionToken } from "./insert-expression-token" +export {TemplateToken} from "./template-token"; +export {ScalarToken} from "./scalar-token"; +export {LiteralToken} from "./literal-token"; +export {StringToken} from "./string-token"; +export {NumberToken} from "./number-token"; +export {BooleanToken} from "./boolean-token"; +export {NullToken} from "./null-token"; +export {KeyValuePair} from "./key-value-pair"; +export {SequenceToken} from "./sequence-token"; +export {MappingToken} from "./mapping-token"; +export {ExpressionToken} from "./expression-token"; +export {BasicExpressionToken} from "./basic-expression-token"; +export {InsertExpressionToken} from "./insert-expression-token"; diff --git a/actions-workflow-parser/src/templates/tokens/insert-expression-token.ts b/actions-workflow-parser/src/templates/tokens/insert-expression-token.ts index 270e40d..281ce72 100644 --- a/actions-workflow-parser/src/templates/tokens/insert-expression-token.ts +++ b/actions-workflow-parser/src/templates/tokens/insert-expression-token.ts @@ -1,13 +1,9 @@ -import { TemplateToken, ScalarToken, ExpressionToken } from "." -import { DefinitionInfo } from "../schema/definition-info" -import { - INSERT_DIRECTIVE, - OPEN_EXPRESSION, - CLOSE_EXPRESSION, -} from "../template-constants" -import { SerializedExpressionToken } from "./serialization" -import { TokenRange } from "./token-range" -import { TokenType } from "./types" +import {TemplateToken, ScalarToken, ExpressionToken} from "."; +import {DefinitionInfo} from "../schema/definition-info"; +import {INSERT_DIRECTIVE, OPEN_EXPRESSION, CLOSE_EXPRESSION} from "../template-constants"; +import {SerializedExpressionToken} from "./serialization"; +import {TokenRange} from "./token-range"; +import {TokenType} from "./types"; export class InsertExpressionToken extends ExpressionToken { public constructor( @@ -15,33 +11,27 @@ export class InsertExpressionToken extends ExpressionToken { range: TokenRange | undefined, definitionInfo: DefinitionInfo | undefined ) { - super( - TokenType.InsertExpression, - file, - range, - INSERT_DIRECTIVE, - definitionInfo - ) + super(TokenType.InsertExpression, file, range, INSERT_DIRECTIVE, definitionInfo); } public override clone(omitSource?: boolean): TemplateToken { return omitSource ? new InsertExpressionToken(undefined, undefined, this.definitionInfo) - : new InsertExpressionToken(this.file, this.range, this.definitionInfo) + : new InsertExpressionToken(this.file, this.range, this.definitionInfo); } public override toString(): string { - return `${OPEN_EXPRESSION} ${INSERT_DIRECTIVE} ${CLOSE_EXPRESSION}` + return `${OPEN_EXPRESSION} ${INSERT_DIRECTIVE} ${CLOSE_EXPRESSION}`; } public override toDisplayString(): string { - return ScalarToken.trimDisplayString(this.toString()) + return ScalarToken.trimDisplayString(this.toString()); } public override toJSON(): SerializedExpressionToken { return { type: TokenType.InsertExpression, - expr: "insert", - } + expr: "insert" + }; } } diff --git a/actions-workflow-parser/src/templates/tokens/key-value-pair.ts b/actions-workflow-parser/src/templates/tokens/key-value-pair.ts index 2ba6227..82a8de4 100644 --- a/actions-workflow-parser/src/templates/tokens/key-value-pair.ts +++ b/actions-workflow-parser/src/templates/tokens/key-value-pair.ts @@ -1,11 +1,11 @@ -import { ScalarToken } from "./scalar-token" -import { TemplateToken } from "./template-token" +import {ScalarToken} from "./scalar-token"; +import {TemplateToken} from "./template-token"; export class KeyValuePair { - public readonly key: ScalarToken - public readonly value: TemplateToken + public readonly key: ScalarToken; + public readonly value: TemplateToken; public constructor(key: ScalarToken, value: TemplateToken) { - this.key = key - this.value = value + this.key = key; + this.value = value; } } diff --git a/actions-workflow-parser/src/templates/tokens/literal-token.ts b/actions-workflow-parser/src/templates/tokens/literal-token.ts index aed8a67..9ad03c0 100644 --- a/actions-workflow-parser/src/templates/tokens/literal-token.ts +++ b/actions-workflow-parser/src/templates/tokens/literal-token.ts @@ -1,6 +1,6 @@ -import { DefinitionInfo } from "../schema/definition-info" -import { ScalarToken } from "./scalar-token" -import { TokenRange } from "./token-range" +import {DefinitionInfo} from "../schema/definition-info"; +import {ScalarToken} from "./scalar-token"; +import {TokenRange} from "./token-range"; export abstract class LiteralToken extends ScalarToken { public constructor( @@ -9,27 +9,25 @@ export abstract class LiteralToken extends ScalarToken { range: TokenRange | undefined, definitionInfo: DefinitionInfo | undefined ) { - super(type, file, range, definitionInfo) + super(type, file, range, definitionInfo); } public override get isLiteral(): boolean { - return true + return true; } public override get isExpression(): boolean { - return false + return false; } public override toDisplayString(): string { - return ScalarToken.trimDisplayString(this.toString()) + return ScalarToken.trimDisplayString(this.toString()); } /** * Throws a good debug message when an unexpected literal value is encountered */ public assertUnexpectedValue(objectDescription: string): void { - throw new Error( - `Error while reading '${objectDescription}'. Unexpected value '${this.toString()}'` - ) + throw new Error(`Error while reading '${objectDescription}'. Unexpected value '${this.toString()}'`); } } diff --git a/actions-workflow-parser/src/templates/tokens/mapping-token.ts b/actions-workflow-parser/src/templates/tokens/mapping-token.ts index a0f934f..6f1d411 100644 --- a/actions-workflow-parser/src/templates/tokens/mapping-token.ts +++ b/actions-workflow-parser/src/templates/tokens/mapping-token.ts @@ -1,80 +1,77 @@ -import { TemplateToken, KeyValuePair, ScalarToken } from "." -import { DefinitionInfo } from "../schema/definition-info" -import { MapItem, SerializedToken } from "./serialization" -import { TokenRange } from "./token-range" -import { TokenType } from "./types" +import {TemplateToken, KeyValuePair, ScalarToken} from "."; +import {DefinitionInfo} from "../schema/definition-info"; +import {MapItem, SerializedToken} from "./serialization"; +import {TokenRange} from "./token-range"; +import {TokenType} from "./types"; export class MappingToken extends TemplateToken { - private readonly map: KeyValuePair[] = [] + private readonly map: KeyValuePair[] = []; public constructor( file: number | undefined, range: TokenRange | undefined, definitionInfo: DefinitionInfo | undefined ) { - super(TokenType.Mapping, file, range, definitionInfo) + super(TokenType.Mapping, file, range, definitionInfo); } public get count(): number { - return this.map.length + return this.map.length; } public override get isScalar(): boolean { - return false + return false; } public override get isLiteral(): boolean { - return false + return false; } public override get isExpression(): boolean { - return false + return false; } public add(key: ScalarToken, value: TemplateToken): void { - this.map.push(new KeyValuePair(key, value)) + this.map.push(new KeyValuePair(key, value)); } public get(index: number): KeyValuePair { - return this.map[index] + return this.map[index]; } public find(key: string): TemplateToken | undefined { - const pair = this.map.find((pair) => pair.key.toString() === key) - return pair?.value + const pair = this.map.find(pair => pair.key.toString() === key); + return pair?.value; } public remove(index: number): void { - this.map.splice(index, 1) + this.map.splice(index, 1); } public override clone(omitSource?: boolean): TemplateToken { const result = omitSource ? new MappingToken(undefined, undefined, this.definitionInfo) - : new MappingToken(this.file, this.range, this.definitionInfo) + : new MappingToken(this.file, this.range, this.definitionInfo); for (const item of this.map) { - result.add( - item.key.clone(omitSource) as ScalarToken, - item.value.clone(omitSource) - ) + result.add(item.key.clone(omitSource) as ScalarToken, item.value.clone(omitSource)); } - return result + return result; } public override toJSON(): SerializedToken { - const items: MapItem[] = [] + const items: MapItem[] = []; for (const item of this.map) { - items.push({ Key: item.key, Value: item.value }) + items.push({Key: item.key, Value: item.value}); } return { type: TokenType.Mapping, - map: items, - } + map: items + }; } public *[Symbol.iterator](): Iterator { for (const item of this.map) { - yield item + yield item; } } } diff --git a/actions-workflow-parser/src/templates/tokens/null-token.ts b/actions-workflow-parser/src/templates/tokens/null-token.ts index 313dd7c..5474c10 100644 --- a/actions-workflow-parser/src/templates/tokens/null-token.ts +++ b/actions-workflow-parser/src/templates/tokens/null-token.ts @@ -1,7 +1,7 @@ -import { LiteralToken, TemplateToken } from "." -import { DefinitionInfo } from "../schema/definition-info" -import { TokenRange } from "./token-range" -import { TokenType } from "./types" +import {LiteralToken, TemplateToken} from "."; +import {DefinitionInfo} from "../schema/definition-info"; +import {TokenRange} from "./token-range"; +import {TokenType} from "./types"; export class NullToken extends LiteralToken { public constructor( @@ -9,20 +9,20 @@ export class NullToken extends LiteralToken { range: TokenRange | undefined, definitionInfo: DefinitionInfo | undefined ) { - super(TokenType.Null, file, range, definitionInfo) + super(TokenType.Null, file, range, definitionInfo); } public override clone(omitSource?: boolean): TemplateToken { return omitSource ? new NullToken(undefined, undefined, this.definitionInfo) - : new NullToken(this.file, this.range, this.definitionInfo) + : new NullToken(this.file, this.range, this.definitionInfo); } public override toString(): string { - return "" + return ""; } public override toJSON() { - return "null" + return "null"; } } diff --git a/actions-workflow-parser/src/templates/tokens/number-token.ts b/actions-workflow-parser/src/templates/tokens/number-token.ts index fbfeb99..1f0c8b2 100644 --- a/actions-workflow-parser/src/templates/tokens/number-token.ts +++ b/actions-workflow-parser/src/templates/tokens/number-token.ts @@ -1,10 +1,10 @@ -import { LiteralToken, TemplateToken } from "." -import { DefinitionInfo } from "../schema/definition-info" -import { TokenRange } from "./token-range" -import { TokenType } from "./types" +import {LiteralToken, TemplateToken} from "."; +import {DefinitionInfo} from "../schema/definition-info"; +import {TokenRange} from "./token-range"; +import {TokenType} from "./types"; export class NumberToken extends LiteralToken { - private readonly num: number + private readonly num: number; public constructor( file: number | undefined, @@ -12,25 +12,25 @@ export class NumberToken extends LiteralToken { value: number, definitionInfo: DefinitionInfo | undefined ) { - super(TokenType.Number, file, range, definitionInfo) - this.num = value + super(TokenType.Number, file, range, definitionInfo); + this.num = value; } public get value(): number { - return this.num + return this.num; } public override clone(omitSource?: boolean): TemplateToken { return omitSource ? new NumberToken(undefined, undefined, this.num, this.definitionInfo) - : new NumberToken(this.file, this.range, this.num, this.definitionInfo) + : new NumberToken(this.file, this.range, this.num, this.definitionInfo); } public override toString(): string { - return `${this.num}` + return `${this.num}`; } public override toJSON() { - return this.num + return this.num; } } diff --git a/actions-workflow-parser/src/templates/tokens/scalar-token.ts b/actions-workflow-parser/src/templates/tokens/scalar-token.ts index 66f66f6..e8ab08e 100644 --- a/actions-workflow-parser/src/templates/tokens/scalar-token.ts +++ b/actions-workflow-parser/src/templates/tokens/scalar-token.ts @@ -1,6 +1,6 @@ -import { DefinitionInfo } from "../schema/definition-info" -import { TemplateToken } from "./template-token" -import { TokenRange } from "./token-range" +import {DefinitionInfo} from "../schema/definition-info"; +import {TemplateToken} from "./template-token"; +import {TokenRange} from "./token-range"; /** * Base class for everything that is not a mapping or sequence @@ -12,21 +12,21 @@ export abstract class ScalarToken extends TemplateToken { range: TokenRange | undefined, definitionInfo: DefinitionInfo | undefined ) { - super(type, file, range, definitionInfo) + super(type, file, range, definitionInfo); } - public abstract toString(): string + public abstract toString(): string; - public abstract toDisplayString(): string + public abstract toDisplayString(): string; public override get isScalar(): boolean { - return true + return true; } protected static trimDisplayString(displayString: string): string { - let firstLine = displayString.trimStart() - const firstNewLine = firstLine.indexOf("\n") - const firstCarriageReturn = firstLine.indexOf("\r") + let firstLine = displayString.trimStart(); + const firstNewLine = firstLine.indexOf("\n"); + const firstCarriageReturn = firstLine.indexOf("\r"); if (firstNewLine >= 0 || firstCarriageReturn >= 0) { firstLine = firstLine.substr( 0, @@ -34,8 +34,8 @@ export abstract class ScalarToken extends TemplateToken { firstNewLine >= 0 ? firstNewLine : Number.MAX_VALUE, firstCarriageReturn >= 0 ? firstCarriageReturn : Number.MAX_VALUE ) - ) + ); } - return firstLine + return firstLine; } } diff --git a/actions-workflow-parser/src/templates/tokens/sequence-token.ts b/actions-workflow-parser/src/templates/tokens/sequence-token.ts index ae353b4..38bcdd6 100644 --- a/actions-workflow-parser/src/templates/tokens/sequence-token.ts +++ b/actions-workflow-parser/src/templates/tokens/sequence-token.ts @@ -1,64 +1,64 @@ -import { TemplateToken } from "." -import { DefinitionInfo } from "../schema/definition-info" -import { SerializedSequenceToken } from "./serialization" -import { TokenRange } from "./token-range" -import { TokenType } from "./types" +import {TemplateToken} from "."; +import {DefinitionInfo} from "../schema/definition-info"; +import {SerializedSequenceToken} from "./serialization"; +import {TokenRange} from "./token-range"; +import {TokenType} from "./types"; export class SequenceToken extends TemplateToken { - private readonly seq: TemplateToken[] = [] + private readonly seq: TemplateToken[] = []; public constructor( file: number | undefined, range: TokenRange | undefined, definitionInfo: DefinitionInfo | undefined ) { - super(TokenType.Sequence, file, range, definitionInfo) + super(TokenType.Sequence, file, range, definitionInfo); } public get count(): number { - return this.seq.length + return this.seq.length; } public override get isScalar(): boolean { - return false + return false; } public override get isLiteral(): boolean { - return false + return false; } public override get isExpression(): boolean { - return false + return false; } public add(value: TemplateToken): void { - this.seq.push(value) + this.seq.push(value); } public get(index: number): TemplateToken { - return this.seq[index] + return this.seq[index]; } public override clone(omitSource?: boolean): TemplateToken { const result = omitSource ? new SequenceToken(undefined, undefined, this.definitionInfo) - : new SequenceToken(this.file, this.range, this.definitionInfo) + : new SequenceToken(this.file, this.range, this.definitionInfo); for (const item of this.seq) { - result.add(item.clone(omitSource)) + result.add(item.clone(omitSource)); } - return result + return result; } public override toJSON(): SerializedSequenceToken { return { type: TokenType.Sequence, - seq: this.seq, - } + seq: this.seq + }; } public *[Symbol.iterator](): Iterator { for (const item of this.seq) { - yield item + yield item; } } } diff --git a/actions-workflow-parser/src/templates/tokens/serialization.ts b/actions-workflow-parser/src/templates/tokens/serialization.ts index 5ccc448..5dd6f9d 100644 --- a/actions-workflow-parser/src/templates/tokens/serialization.ts +++ b/actions-workflow-parser/src/templates/tokens/serialization.ts @@ -1,26 +1,26 @@ -import { ScalarToken } from "./scalar-token" -import { TemplateToken } from "./template-token" -import { TokenType } from "./types" +import {ScalarToken} from "./scalar-token"; +import {TemplateToken} from "./template-token"; +import {TokenType} from "./types"; export type MapItem = { - Key: ScalarToken - Value: TemplateToken -} + Key: ScalarToken; + Value: TemplateToken; +}; export type SerializedMappingToken = { - type: TokenType.Mapping - map: MapItem[] -} + type: TokenType.Mapping; + map: MapItem[]; +}; export type SerializedSequenceToken = { - type: TokenType.Sequence - seq: TemplateToken[] -} + type: TokenType.Sequence; + seq: TemplateToken[]; +}; export type SerializedExpressionToken = { - type: TokenType.BasicExpression | TokenType.InsertExpression - expr: string -} + type: TokenType.BasicExpression | TokenType.InsertExpression; + expr: string; +}; export type SerializedToken = | SerializedMappingToken @@ -29,4 +29,4 @@ export type SerializedToken = | string | number | boolean - | undefined + | undefined; diff --git a/actions-workflow-parser/src/templates/tokens/string-token.ts b/actions-workflow-parser/src/templates/tokens/string-token.ts index 7a434cf..bc32d65 100644 --- a/actions-workflow-parser/src/templates/tokens/string-token.ts +++ b/actions-workflow-parser/src/templates/tokens/string-token.ts @@ -1,11 +1,11 @@ -import { LiteralToken, TemplateToken } from "." -import { DefinitionInfo } from "../schema/definition-info" -import { TokenRange } from "./token-range" -import { TokenType } from "./types" +import {LiteralToken, TemplateToken} from "."; +import {DefinitionInfo} from "../schema/definition-info"; +import {TokenRange} from "./token-range"; +import {TokenType} from "./types"; export class StringToken extends LiteralToken { - public readonly value: string - public readonly source: string | undefined + public readonly value: string; + public readonly source: string | undefined; public constructor( file: number | undefined, @@ -14,34 +14,22 @@ export class StringToken extends LiteralToken { definitionInfo: DefinitionInfo | undefined, source?: string ) { - super(TokenType.String, file, range, definitionInfo) - this.value = value - this.source = source + super(TokenType.String, file, range, definitionInfo); + this.value = value; + this.source = source; } public override clone(omitSource?: boolean): TemplateToken { return omitSource - ? new StringToken( - undefined, - undefined, - this.value, - this.definitionInfo, - this.source - ) - : new StringToken( - this.file, - this.range, - this.value, - this.definitionInfo, - this.source - ) + ? new StringToken(undefined, undefined, this.value, this.definitionInfo, this.source) + : new StringToken(this.file, this.range, this.value, this.definitionInfo, this.source); } public override toString(): string { - return this.value + return this.value; } public override toJSON() { - return this.value + return this.value; } } diff --git a/actions-workflow-parser/src/templates/tokens/template-token.test.ts b/actions-workflow-parser/src/templates/tokens/template-token.test.ts index e6c0808..520eb28 100644 --- a/actions-workflow-parser/src/templates/tokens/template-token.test.ts +++ b/actions-workflow-parser/src/templates/tokens/template-token.test.ts @@ -1,7 +1,7 @@ -import { nullTrace } from "../../test-utils/null-trace" -import { parseWorkflow } from "../../workflows/workflow-parser" -import { StringToken } from "./string-token" -import { TemplateToken } from "./template-token" +import {nullTrace} from "../../test-utils/null-trace"; +import {parseWorkflow} from "../../workflows/workflow-parser"; +import {StringToken} from "./string-token"; +import {TemplateToken} from "./template-token"; describe("traverse", () => { it("returns parent token and key", () => { @@ -10,36 +10,36 @@ describe("traverse", () => { [ { name: "wf.yaml", - content: `on: push`, - }, + content: `on: push` + } ], nullTrace - ) + ); - const root = workflow.value! - const traverser = TemplateToken.traverse(root) + const root = workflow.value!; + const traverser = TemplateToken.traverse(root); // Root - expect(traverser.next()!.value).toEqual([undefined, root, undefined]) + expect(traverser.next()!.value).toEqual([undefined, root, undefined]); // On - const onResult = traverser.next()!.value! - expect(onResult[0]).toBe(root) - expect(getValue(onResult[1])).toEqual("on") - expect(onResult[2]).toBeUndefined() + const onResult = traverser.next()!.value!; + expect(onResult[0]).toBe(root); + expect(getValue(onResult[1])).toEqual("on"); + expect(onResult[2]).toBeUndefined(); // Push - const pushResult = traverser.next()!.value! - expect(pushResult[0]).toBe(root) - expect(getValue(pushResult[1])).toEqual("push") - expect(getValue(pushResult[2])).toEqual("on") - }) -}) + const pushResult = traverser.next()!.value!; + expect(pushResult[0]).toBe(root); + expect(getValue(pushResult[1])).toEqual("push"); + expect(getValue(pushResult[2])).toEqual("on"); + }); +}); function getValue(token: TemplateToken | undefined): string { if (token instanceof StringToken) { - return token.value + return token.value; } - return "" + return ""; } diff --git a/actions-workflow-parser/src/templates/tokens/template-token.ts b/actions-workflow-parser/src/templates/tokens/template-token.ts index baae830..5257b3c 100644 --- a/actions-workflow-parser/src/templates/tokens/template-token.ts +++ b/actions-workflow-parser/src/templates/tokens/template-token.ts @@ -1,34 +1,26 @@ -import { - BooleanToken, - MappingToken, - NullToken, - NumberToken, - ScalarToken, - SequenceToken, - StringToken, -} from "." -import { Definition } from "../schema/definition" -import { DefinitionInfo } from "../schema/definition-info" -import { PropertyDefinition } from "../schema/property-definition" -import { SerializedToken } from "./serialization" -import { TokenRange } from "./token-range" -import { TraversalState } from "./traversal-state" -import { TokenType, tokenTypeName } from "./types" +import {BooleanToken, MappingToken, NullToken, NumberToken, ScalarToken, SequenceToken, StringToken} from "."; +import {Definition} from "../schema/definition"; +import {DefinitionInfo} from "../schema/definition-info"; +import {PropertyDefinition} from "../schema/property-definition"; +import {SerializedToken} from "./serialization"; +import {TokenRange} from "./token-range"; +import {TraversalState} from "./traversal-state"; +import {TokenType, tokenTypeName} from "./types"; export class TemplateTokenError extends Error { constructor(message: string, public readonly token?: TemplateToken) { - super(message) + super(message); } } export abstract class TemplateToken { // Fields for serialization - private readonly type: TokenType - private _description: string | undefined - public readonly file: number | undefined - public readonly range: TokenRange | undefined - public definitionInfo: DefinitionInfo | undefined - public propertyDefinition: PropertyDefinition | undefined + private readonly type: TokenType; + private _description: string | undefined; + public readonly file: number | undefined; + public readonly range: TokenRange | undefined; + public definitionInfo: DefinitionInfo | undefined; + public propertyDefinition: PropertyDefinition | undefined; /** * Base class for all template tokens @@ -39,50 +31,46 @@ export abstract class TemplateToken { range: TokenRange | undefined, definitionInfo: DefinitionInfo | undefined ) { - this.type = type - this.file = file - this.range = range - this.definitionInfo = definitionInfo + this.type = type; + this.file = file; + this.range = range; + this.definitionInfo = definitionInfo; } public get templateTokenType(): number { - return this.type + return this.type; } public get line(): number | undefined { - return this.range?.start[0] + return this.range?.start[0]; } public get col(): number | undefined { - return this.range?.start[1] + return this.range?.start[1]; } public get definition(): Definition | undefined { - return this.definitionInfo?.definition + return this.definitionInfo?.definition; } get description(): string | undefined { - return ( - this._description || - this.propertyDefinition?.description || - this.definition?.description - ) + return this._description || this.propertyDefinition?.description || this.definition?.description; } set description(description: string | undefined) { - this._description = description + this._description = description; } - public abstract get isScalar(): boolean + public abstract get isScalar(): boolean; - public abstract get isLiteral(): boolean + public abstract get isLiteral(): boolean; - public abstract get isExpression(): boolean + public abstract get isExpression(): boolean; - public abstract clone(omitSource?: boolean): TemplateToken + public abstract clone(omitSource?: boolean): TemplateToken; public typeName(): string { - return tokenTypeName(this.type) + return tokenTypeName(this.type); } /** @@ -90,7 +78,7 @@ export abstract class TemplateToken { */ public assertNull(objectDescription: string): NullToken { if (this.type === TokenType.Null) { - return this as unknown as NullToken + return this as unknown as NullToken; } throw new TemplateTokenError( @@ -98,7 +86,7 @@ export abstract class TemplateToken { TokenType.Null )}' was expected.`, this - ) + ); } /** @@ -106,7 +94,7 @@ export abstract class TemplateToken { */ public assertBoolean(objectDescription: string): BooleanToken { if (this.type === TokenType.Boolean) { - return this as unknown as BooleanToken + return this as unknown as BooleanToken; } throw new TemplateTokenError( @@ -114,7 +102,7 @@ export abstract class TemplateToken { TokenType.Boolean )}' was expected.`, this - ) + ); } /** @@ -122,7 +110,7 @@ export abstract class TemplateToken { */ public assertNumber(objectDescription: string): NumberToken { if (this.type === TokenType.Number) { - return this as unknown as NumberToken + return this as unknown as NumberToken; } throw new TemplateTokenError( @@ -130,7 +118,7 @@ export abstract class TemplateToken { TokenType.Number )}' was expected.`, this - ) + ); } /** @@ -138,7 +126,7 @@ export abstract class TemplateToken { */ public assertString(objectDescription: string): StringToken { if (this.type === TokenType.String) { - return this as unknown as StringToken + return this as unknown as StringToken; } throw new TemplateTokenError( @@ -146,7 +134,7 @@ export abstract class TemplateToken { TokenType.String )}' was expected.`, this - ) + ); } /** @@ -154,13 +142,13 @@ export abstract class TemplateToken { */ public assertScalar(objectDescription: string): ScalarToken { if ((this as unknown as ScalarToken | undefined)?.isScalar === true) { - return this as unknown as ScalarToken + return this as unknown as ScalarToken; } throw new TemplateTokenError( `Unexpected type '${this.typeName()}' encountered while reading '${objectDescription}'. The type 'ScalarToken' was expected.`, this - ) + ); } /** @@ -168,7 +156,7 @@ export abstract class TemplateToken { */ public assertSequence(objectDescription: string): SequenceToken { if (this.type === TokenType.Sequence) { - return this as unknown as SequenceToken + return this as unknown as SequenceToken; } throw new TemplateTokenError( @@ -176,7 +164,7 @@ export abstract class TemplateToken { TokenType.Sequence )}' was expected.`, this - ) + ); } /** @@ -184,7 +172,7 @@ export abstract class TemplateToken { */ public assertMapping(objectDescription: string): MappingToken { if (this.type === TokenType.Mapping) { - return this as unknown as MappingToken + return this as unknown as MappingToken; } throw new TemplateTokenError( @@ -192,7 +180,7 @@ export abstract class TemplateToken { TokenType.Mapping )}' was expected.`, this - ) + ); } /** @@ -203,45 +191,35 @@ export abstract class TemplateToken { public static *traverse( value: TemplateToken, omitKeys?: boolean - ): Generator< - [ - parent: TemplateToken | undefined, - token: TemplateToken, - keyToken: TemplateToken | undefined - ], - void - > { - yield [undefined, value, undefined] + ): Generator<[parent: TemplateToken | undefined, token: TemplateToken, keyToken: TemplateToken | undefined], void> { + yield [undefined, value, undefined]; switch (value.templateTokenType) { case TokenType.Sequence: case TokenType.Mapping: { - let state: TraversalState | undefined = new TraversalState( - undefined, - value - ) - state = new TraversalState(state, value) + let state: TraversalState | undefined = new TraversalState(undefined, value); + state = new TraversalState(state, value); while (state.parent) { if (state.moveNext(omitKeys ?? false)) { - value = state.current as TemplateToken - yield [state.parent?.current, value, state.currentKey] + value = state.current as TemplateToken; + yield [state.parent?.current, value, state.currentKey]; switch (value.type) { case TokenType.Sequence: case TokenType.Mapping: - state = new TraversalState(state, value) - break + state = new TraversalState(state, value); + break; } } else { - state = state.parent + state = state.parent; } } - break + break; } } } public toJSON(): SerializedToken { - return undefined + return undefined; } } diff --git a/actions-workflow-parser/src/templates/tokens/token-range.ts b/actions-workflow-parser/src/templates/tokens/token-range.ts index 137e983..34b1f90 100644 --- a/actions-workflow-parser/src/templates/tokens/token-range.ts +++ b/actions-workflow-parser/src/templates/tokens/token-range.ts @@ -1,6 +1,6 @@ -export type Position = [line: number, character: number] +export type Position = [line: number, character: number]; export type TokenRange = { - start: Position - end: Position -} + start: Position; + end: Position; +}; diff --git a/actions-workflow-parser/src/templates/tokens/traversal-state.ts b/actions-workflow-parser/src/templates/tokens/traversal-state.ts index cf03729..0057303 100644 --- a/actions-workflow-parser/src/templates/tokens/traversal-state.ts +++ b/actions-workflow-parser/src/templates/tokens/traversal-state.ts @@ -1,71 +1,69 @@ -import { TemplateToken } from "." -import { MappingToken } from "./mapping-token" -import { SequenceToken } from "./sequence-token" -import { TokenType } from "./types" +import {TemplateToken} from "."; +import {MappingToken} from "./mapping-token"; +import {SequenceToken} from "./sequence-token"; +import {TokenType} from "./types"; export class TraversalState { - private readonly _token: TemplateToken - private index = -1 - private isKey = false - public readonly parent: TraversalState | undefined - public current: TemplateToken | undefined - public currentKey: TemplateToken | undefined + private readonly _token: TemplateToken; + private index = -1; + private isKey = false; + public readonly parent: TraversalState | undefined; + public current: TemplateToken | undefined; + public currentKey: TemplateToken | undefined; public constructor(parent: TraversalState | undefined, token: TemplateToken) { - this.parent = parent - this._token = token - this.current = token + this.parent = parent; + this._token = token; + this.current = token; } public moveNext(omitKeys: boolean): boolean { switch (this._token.templateTokenType) { case TokenType.Sequence: { - const sequence = this._token as SequenceToken + const sequence = this._token as SequenceToken; if (++this.index < sequence.count) { - this.current = sequence.get(this.index) - return true + this.current = sequence.get(this.index); + return true; } - this.current = undefined - return false + this.current = undefined; + return false; } case TokenType.Mapping: { - const mapping = this._token as MappingToken + const mapping = this._token as MappingToken; // Already returned the key, now return the value if (this.isKey) { - this.isKey = false - this.currentKey = this.current - this.current = mapping.get(this.index).value - return true + this.isKey = false; + this.currentKey = this.current; + this.current = mapping.get(this.index).value; + return true; } // Move next if (++this.index < mapping.count) { // Skip the key, return the value if (omitKeys) { - this.isKey = false - this.currentKey = mapping.get(this.index).key - this.current = mapping.get(this.index).value - return true + this.isKey = false; + this.currentKey = mapping.get(this.index).key; + this.current = mapping.get(this.index).value; + return true; } // Return the key - this.isKey = true - this.currentKey = undefined - this.current = mapping.get(this.index).key - return true + this.isKey = true; + this.currentKey = undefined; + this.current = mapping.get(this.index).key; + return true; } - this.currentKey = undefined - this.current = undefined - return false + this.currentKey = undefined; + this.current = undefined; + return false; } default: - throw new Error( - `Unexpected token type '${this._token.templateTokenType}' when traversing state` - ) + throw new Error(`Unexpected token type '${this._token.templateTokenType}' when traversing state`); } } } diff --git a/actions-workflow-parser/src/templates/tokens/type-guards.ts b/actions-workflow-parser/src/templates/tokens/type-guards.ts index 51603fc..cf2b435 100644 --- a/actions-workflow-parser/src/templates/tokens/type-guards.ts +++ b/actions-workflow-parser/src/templates/tokens/type-guards.ts @@ -1,42 +1,42 @@ -import { BasicExpressionToken } from "./basic-expression-token" -import { BooleanToken } from "./boolean-token" -import { LiteralToken } from "./literal-token" -import { MappingToken } from "./mapping-token" -import { NumberToken } from "./number-token" -import { ScalarToken } from "./scalar-token" -import { SequenceToken } from "./sequence-token" -import { StringToken } from "./string-token" -import { TemplateToken } from "./template-token" -import { TokenType } from "./types" +import {BasicExpressionToken} from "./basic-expression-token"; +import {BooleanToken} from "./boolean-token"; +import {LiteralToken} from "./literal-token"; +import {MappingToken} from "./mapping-token"; +import {NumberToken} from "./number-token"; +import {ScalarToken} from "./scalar-token"; +import {SequenceToken} from "./sequence-token"; +import {StringToken} from "./string-token"; +import {TemplateToken} from "./template-token"; +import {TokenType} from "./types"; export function isLiteral(t: TemplateToken): t is LiteralToken { - return t.isLiteral + return t.isLiteral; } export function isScalar(t: TemplateToken): t is ScalarToken { - return t.isScalar + return t.isScalar; } export function isString(t: TemplateToken): t is StringToken { - return isLiteral(t) && t.templateTokenType === TokenType.String + return isLiteral(t) && t.templateTokenType === TokenType.String; } export function isNumber(t: TemplateToken): t is NumberToken { - return isLiteral(t) && t.templateTokenType === TokenType.Number + return isLiteral(t) && t.templateTokenType === TokenType.Number; } export function isBoolean(t: TemplateToken): t is BooleanToken { - return isLiteral(t) && t.templateTokenType === TokenType.Boolean + return isLiteral(t) && t.templateTokenType === TokenType.Boolean; } export function isBasicExpression(t: TemplateToken): t is BasicExpressionToken { - return isScalar(t) && t.templateTokenType === TokenType.BasicExpression + return isScalar(t) && t.templateTokenType === TokenType.BasicExpression; } export function isSequence(t: TemplateToken): t is SequenceToken { - return t instanceof SequenceToken + return t instanceof SequenceToken; } export function isMapping(t: TemplateToken): t is MappingToken { - return t instanceof MappingToken + return t instanceof MappingToken; } diff --git a/actions-workflow-parser/src/templates/tokens/types.ts b/actions-workflow-parser/src/templates/tokens/types.ts index 7b3b34e..50d4be2 100644 --- a/actions-workflow-parser/src/templates/tokens/types.ts +++ b/actions-workflow-parser/src/templates/tokens/types.ts @@ -6,31 +6,31 @@ export enum TokenType { InsertExpression, Boolean, Number, - Null, + Null } export function tokenTypeName(type: TokenType): string { switch (type) { case TokenType.String: - return "StringToken" + return "StringToken"; case TokenType.Sequence: - return "SequenceToken" + return "SequenceToken"; case TokenType.Mapping: - return "MappingToken" + return "MappingToken"; case TokenType.BasicExpression: - return "BasicExpressionToken" + return "BasicExpressionToken"; case TokenType.InsertExpression: - return "InsertExpressionToken" + return "InsertExpressionToken"; case TokenType.Boolean: - return "BooleanToken" + return "BooleanToken"; case TokenType.Number: - return "NumberToken" + return "NumberToken"; case TokenType.Null: - return "NullToken" + return "NullToken"; default: { // Use never to ensure exhaustiveness - const exhaustiveCheck: never = type - throw new Error(`Unhandled token type: ${type} ${exhaustiveCheck}}`) + const exhaustiveCheck: never = type; + throw new Error(`Unhandled token type: ${type} ${exhaustiveCheck}}`); } } } diff --git a/actions-workflow-parser/src/templates/trace-writer.ts b/actions-workflow-parser/src/templates/trace-writer.ts index 42f3801..2ac9a0c 100644 --- a/actions-workflow-parser/src/templates/trace-writer.ts +++ b/actions-workflow-parser/src/templates/trace-writer.ts @@ -1,9 +1,9 @@ export interface TraceWriter { - error(message: string): void + error(message: string): void; - info(message: string): void + info(message: string): void; - verbose(message: string): void + verbose(message: string): void; } export class NoOperationTraceWriter implements TraceWriter { diff --git a/actions-workflow-parser/src/test-utils/null-trace.ts b/actions-workflow-parser/src/test-utils/null-trace.ts index 0af9999..08aba62 100644 --- a/actions-workflow-parser/src/test-utils/null-trace.ts +++ b/actions-workflow-parser/src/test-utils/null-trace.ts @@ -1,7 +1,7 @@ -import { TraceWriter } from "../templates/trace-writer" +import {TraceWriter} from "../templates/trace-writer"; export const nullTrace: TraceWriter = { - info: (x) => {}, - verbose: (x) => {}, - error: (x) => {}, -} + info: x => {}, + verbose: x => {}, + error: x => {} +}; diff --git a/actions-workflow-parser/src/workflows/file.ts b/actions-workflow-parser/src/workflows/file.ts index b3c98bb..6e3ada5 100644 --- a/actions-workflow-parser/src/workflows/file.ts +++ b/actions-workflow-parser/src/workflows/file.ts @@ -1,4 +1,4 @@ export interface File { - name: string - content: string + name: string; + content: string; } diff --git a/actions-workflow-parser/src/workflows/workflow-constants.ts b/actions-workflow-parser/src/workflows/workflow-constants.ts index d9156c8..2abbb26 100644 --- a/actions-workflow-parser/src/workflows/workflow-constants.ts +++ b/actions-workflow-parser/src/workflows/workflow-constants.ts @@ -1,2 +1,2 @@ -export const WORKFLOW_ROOT = "workflow-root-strict" -export const STRATEGY = "strategy" +export const WORKFLOW_ROOT = "workflow-root-strict"; +export const STRATEGY = "strategy"; diff --git a/actions-workflow-parser/src/workflows/workflow-evaluator.ts b/actions-workflow-parser/src/workflows/workflow-evaluator.ts index e36a7d9..16419d7 100644 --- a/actions-workflow-parser/src/workflows/workflow-evaluator.ts +++ b/actions-workflow-parser/src/workflows/workflow-evaluator.ts @@ -1,18 +1,15 @@ -import { Dictionary } from "@github/actions-expressions/data/dictionary" -import { - TemplateContext, - TemplateValidationErrors, -} from "../templates/template-context" -import * as templateEvaluator from "../templates/template-evaluator" -import { TemplateValidationError } from "../templates/template-validation-error" -import { TemplateToken } from "../templates/tokens/template-token" -import { TraceWriter } from "../templates/trace-writer" -import { STRATEGY } from "./workflow-constants" -import { getWorkflowSchema } from "./workflow-schema" +import {Dictionary} from "@github/actions-expressions/data/dictionary"; +import {TemplateContext, TemplateValidationErrors} from "../templates/template-context"; +import * as templateEvaluator from "../templates/template-evaluator"; +import {TemplateValidationError} from "../templates/template-validation-error"; +import {TemplateToken} from "../templates/tokens/template-token"; +import {TraceWriter} from "../templates/trace-writer"; +import {STRATEGY} from "./workflow-constants"; +import {getWorkflowSchema} from "./workflow-schema"; export interface EvaluateWorkflowResult { - value: TemplateToken | undefined - errors: TemplateValidationError[] + value: TemplateToken | undefined; + errors: TemplateValidationError[]; } export function evaluateStrategy( @@ -21,31 +18,21 @@ export function evaluateStrategy( token: TemplateToken, trace: TraceWriter ): EvaluateWorkflowResult { - const templateContext = new TemplateContext( - new TemplateValidationErrors(), - getWorkflowSchema(), - trace - ) + const templateContext = new TemplateContext(new TemplateValidationErrors(), getWorkflowSchema(), trace); // Add each file name for (const fileName of fileTable) { - templateContext.getFileId(fileName) + templateContext.getFileId(fileName); } // Add expression named contexts for (const pair of context.pairs()) { - templateContext.expressionNamedContexts.push(pair.key) + templateContext.expressionNamedContexts.push(pair.key); } - const value = templateEvaluator.evaluateTemplate( - templateContext, - STRATEGY, - token, - 0, - undefined - ) + const value = templateEvaluator.evaluateTemplate(templateContext, STRATEGY, token, 0, undefined); return { value: value, - errors: templateContext.errors.getErrors(), - } + errors: templateContext.errors.getErrors() + }; } diff --git a/actions-workflow-parser/src/workflows/workflow-parser.test.ts b/actions-workflow-parser/src/workflows/workflow-parser.test.ts index ef0d950..021ac2b 100644 --- a/actions-workflow-parser/src/workflows/workflow-parser.test.ts +++ b/actions-workflow-parser/src/workflows/workflow-parser.test.ts @@ -1,5 +1,5 @@ -import { parseWorkflow } from "./workflow-parser" -import { nullTrace } from "../test-utils/null-trace" +import {parseWorkflow} from "./workflow-parser"; +import {nullTrace} from "../test-utils/null-trace"; it("The template is not read when there are YAML errors", () => { const content = ` @@ -9,14 +9,10 @@ jobs: runs-on: ubuntu-latest steps: - name: 'Hello \${{ fromJSON('test') == inputs.name }}' - run: echo Hello, world!` + run: echo Hello, world!`; - const result = parseWorkflow( - "main.yaml", - [{ name: "main.yaml", content: content }], - nullTrace - ) + const result = parseWorkflow("main.yaml", [{name: "main.yaml", content: content}], nullTrace); - expect(result.context.errors.count).toBe(1) - expect(result.value).toBeUndefined() -}) + expect(result.context.errors.count).toBe(1); + expect(result.value).toBeUndefined(); +}); diff --git a/actions-workflow-parser/src/workflows/workflow-parser.ts b/actions-workflow-parser/src/workflows/workflow-parser.ts index d25fecf..a49c903 100644 --- a/actions-workflow-parser/src/workflows/workflow-parser.ts +++ b/actions-workflow-parser/src/workflows/workflow-parser.ts @@ -1,52 +1,36 @@ -import { - TemplateContext, - TemplateValidationErrors, -} from "../templates/template-context" -import * as templateReader from "../templates/template-reader" -import { TemplateToken } from "../templates/tokens/template-token" -import { TraceWriter } from "../templates/trace-writer" -import { File } from "./file" -import { WORKFLOW_ROOT } from "./workflow-constants" -import { getWorkflowSchema } from "./workflow-schema" -import { YamlObjectReader } from "./yaml-object-reader" +import {TemplateContext, TemplateValidationErrors} from "../templates/template-context"; +import * as templateReader from "../templates/template-reader"; +import {TemplateToken} from "../templates/tokens/template-token"; +import {TraceWriter} from "../templates/trace-writer"; +import {File} from "./file"; +import {WORKFLOW_ROOT} from "./workflow-constants"; +import {getWorkflowSchema} from "./workflow-schema"; +import {YamlObjectReader} from "./yaml-object-reader"; export interface ParseWorkflowResult { - context: TemplateContext - value: TemplateToken | undefined + context: TemplateContext; + value: TemplateToken | undefined; } -export function parseWorkflow( - entryFileName: string, - files: File[], - trace: TraceWriter -): ParseWorkflowResult { - const context = new TemplateContext( - new TemplateValidationErrors(), - getWorkflowSchema(), - trace - ) +export function parseWorkflow(entryFileName: string, files: File[], trace: TraceWriter): ParseWorkflowResult { + const context = new TemplateContext(new TemplateValidationErrors(), getWorkflowSchema(), trace); // Add file ids - files.forEach((x) => context.getFileId(x.name)) + files.forEach(x => context.getFileId(x.name)); - const fileId = context.getFileId(entryFileName) - const fileContent = files[fileId - 1].content - const reader = new YamlObjectReader(context, fileId, fileContent) + const fileId = context.getFileId(entryFileName); + const fileContent = files[fileId - 1].content; + const reader = new YamlObjectReader(context, fileId, fileContent); if (context.errors.count > 0) { // The file is not valid YAML, template errors could be misleading return { context, - value: undefined, - } + value: undefined + }; } - const result = templateReader.readTemplate( - context, - WORKFLOW_ROOT, - reader, - fileId - ) + const result = templateReader.readTemplate(context, WORKFLOW_ROOT, reader, fileId); return { context, - value: result, - } + value: result + }; } diff --git a/actions-workflow-parser/src/workflows/workflow-schema.ts b/actions-workflow-parser/src/workflows/workflow-schema.ts index c45384e..d2b7dc2 100644 --- a/actions-workflow-parser/src/workflows/workflow-schema.ts +++ b/actions-workflow-parser/src/workflows/workflow-schema.ts @@ -1,13 +1,13 @@ -import { JSONObjectReader } from "../templates/json-object-reader" -import { TemplateSchema } from "../templates/schema" -import WorkflowSchema from "../workflow-v1.0.json" +import {JSONObjectReader} from "../templates/json-object-reader"; +import {TemplateSchema} from "../templates/schema"; +import WorkflowSchema from "../workflow-v1.0.json"; -let schema: TemplateSchema +let schema: TemplateSchema; export function getWorkflowSchema(): TemplateSchema { if (schema === undefined) { - const json = JSON.stringify(WorkflowSchema) - schema = TemplateSchema.load(new JSONObjectReader(undefined, json)) + const json = JSON.stringify(WorkflowSchema); + schema = TemplateSchema.load(new JSONObjectReader(undefined, json)); } - return schema + return schema; } diff --git a/actions-workflow-parser/src/workflows/yaml-object-reader.test.ts b/actions-workflow-parser/src/workflows/yaml-object-reader.test.ts index 34c6fa0..9813dff 100644 --- a/actions-workflow-parser/src/workflows/yaml-object-reader.test.ts +++ b/actions-workflow-parser/src/workflows/yaml-object-reader.test.ts @@ -1,63 +1,60 @@ -import { - TemplateContext, - TemplateValidationErrors, -} from "../templates/template-context" -import { TemplateToken } from "../templates/tokens" -import { TokenType } from "../templates/tokens/types" -import { nullTrace } from "../test-utils/null-trace" -import { parseWorkflow } from "./workflow-parser" -import { getWorkflowSchema } from "./workflow-schema" -import { YamlObjectReader } from "./yaml-object-reader" +import {TemplateContext, TemplateValidationErrors} from "../templates/template-context"; +import {TemplateToken} from "../templates/tokens"; +import {TokenType} from "../templates/tokens/types"; +import {nullTrace} from "../test-utils/null-trace"; +import {parseWorkflow} from "./workflow-parser"; +import {getWorkflowSchema} from "./workflow-schema"; +import {YamlObjectReader} from "./yaml-object-reader"; describe("getLiteralToken", () => { it("non-zero number", () => { - const result = parseAsWorkflow("1") + const result = parseAsWorkflow("1"); - expect(result).not.toBeUndefined() - expect(result?.templateTokenType).toEqual(TokenType.Number) - expect(result?.toString()).toEqual("1") - }) + expect(result).not.toBeUndefined(); + expect(result?.templateTokenType).toEqual(TokenType.Number); + expect(result?.toString()).toEqual("1"); + }); it("zero", () => { - const result = parseAsWorkflow("0") + const result = parseAsWorkflow("0"); - expect(result).not.toBeUndefined() - expect(result?.templateTokenType).toEqual(TokenType.Number) - expect(result?.toString()).toEqual("0") - }) + expect(result).not.toBeUndefined(); + expect(result?.templateTokenType).toEqual(TokenType.Number); + expect(result?.toString()).toEqual("0"); + }); it("true", () => { - const result = parseAsWorkflow("true") + const result = parseAsWorkflow("true"); - expect(result).not.toBeUndefined() - expect(result?.templateTokenType).toEqual(TokenType.Boolean) - expect(result?.toString()).toEqual("true") - }) + expect(result).not.toBeUndefined(); + expect(result?.templateTokenType).toEqual(TokenType.Boolean); + expect(result?.toString()).toEqual("true"); + }); it("false", () => { - const result = parseAsWorkflow("false") + const result = parseAsWorkflow("false"); - expect(result).not.toBeUndefined() - expect(result?.templateTokenType).toEqual(TokenType.Boolean) - expect(result?.toString()).toEqual("false") - }) + expect(result).not.toBeUndefined(); + expect(result?.templateTokenType).toEqual(TokenType.Boolean); + expect(result?.toString()).toEqual("false"); + }); it("string", () => { - const result = parseAsWorkflow("test") + const result = parseAsWorkflow("test"); - expect(result).not.toBeUndefined() - expect(result?.templateTokenType).toEqual(TokenType.String) - expect(result?.toString()).toEqual("test") - }) + expect(result).not.toBeUndefined(); + expect(result?.templateTokenType).toEqual(TokenType.String); + expect(result?.toString()).toEqual("test"); + }); it("null", () => { - const result = parseAsWorkflow("null") + const result = parseAsWorkflow("null"); - expect(result).not.toBeUndefined() - expect(result?.templateTokenType).toEqual(TokenType.Null) - expect(result?.toString()).toEqual("") - }) -}) + expect(result).not.toBeUndefined(); + expect(result?.templateTokenType).toEqual(TokenType.Null); + expect(result?.toString()).toEqual(""); + }); +}); it("YAML errors include range information", () => { const content = ` @@ -67,24 +64,20 @@ it("YAML errors include range information", () => { runs-on: ubuntu-latest steps: - name: 'Hello \${{ fromJSON('test') == inputs.name }}' - run: echo Hello, world!` + run: echo Hello, world!`; - const context = new TemplateContext( - new TemplateValidationErrors(), - getWorkflowSchema(), - nullTrace - ) - const fileId = context.getFileId("test.yaml") - new YamlObjectReader(context, fileId, content) + const context = new TemplateContext(new TemplateValidationErrors(), getWorkflowSchema(), nullTrace); + const fileId = context.getFileId("test.yaml"); + new YamlObjectReader(context, fileId, content); - expect(context.errors.count).toBe(1) + expect(context.errors.count).toBe(1); - const error = context.errors.getErrors()[0] + const error = context.errors.getErrors()[0]; expect(error.range).toEqual({ start: [7, 38], - end: [7, 63], - }) -}) + end: [7, 63] + }); +}); function parseAsWorkflow(content: string): TemplateToken | undefined { const result = parseWorkflow( @@ -92,11 +85,11 @@ function parseAsWorkflow(content: string): TemplateToken | undefined { [ { name: "test.yaml", - content: content, - }, + content: content + } ], nullTrace - ) + ); - return result.value + return result.value; } diff --git a/actions-workflow-parser/src/workflows/yaml-object-reader.ts b/actions-workflow-parser/src/workflows/yaml-object-reader.ts index 7122aab..ce21e07 100644 --- a/actions-workflow-parser/src/workflows/yaml-object-reader.ts +++ b/actions-workflow-parser/src/workflows/yaml-object-reader.ts @@ -1,19 +1,9 @@ -import { - isCollection, - isDocument, - isMap, - isPair, - isScalar, - isSeq, - LineCounter, - parseDocument, - Scalar, -} from "yaml" -import { LinePos } from "yaml/dist/errors" -import { NodeBase } from "yaml/dist/nodes/Node" -import { ObjectReader } from "../templates/object-reader" -import { EventType, ParseEvent } from "../templates/parse-event" -import { TemplateContext } from "../templates/template-context" +import {isCollection, isDocument, isMap, isPair, isScalar, isSeq, LineCounter, parseDocument, Scalar} from "yaml"; +import {LinePos} from "yaml/dist/errors"; +import {NodeBase} from "yaml/dist/nodes/Node"; +import {ObjectReader} from "../templates/object-reader"; +import {EventType, ParseEvent} from "../templates/parse-event"; +import {TemplateContext} from "../templates/template-context"; import { BooleanToken, LiteralToken, @@ -21,243 +11,218 @@ import { NullToken, NumberToken, SequenceToken, - StringToken, -} from "../templates/tokens/index" -import { Position, TokenRange } from "../templates/tokens/token-range" + StringToken +} from "../templates/tokens/index"; +import {Position, TokenRange} from "../templates/tokens/token-range"; export class YamlObjectReader implements ObjectReader { - private readonly _generator: Generator - private _current!: IteratorResult - private fileId?: number - private lineCounter = new LineCounter() + private readonly _generator: Generator; + private _current!: IteratorResult; + private fileId?: number; + private lineCounter = new LineCounter(); - constructor( - context: TemplateContext, - fileId: number | undefined, - content: string - ) { + constructor(context: TemplateContext, fileId: number | undefined, content: string) { const doc = parseDocument(content, { lineCounter: this.lineCounter, keepSourceTokens: true, - uniqueKeys: false, // Uniqueness is validated by the template reader - }) + uniqueKeys: false // Uniqueness is validated by the template reader + }); for (const err of doc.errors) { - context.error(fileId, err.message, rangeFromLinePos(err.linePos)) + context.error(fileId, err.message, rangeFromLinePos(err.linePos)); } - this._generator = this.getNodes(doc) - this.fileId = fileId + this._generator = this.getNodes(doc); + this.fileId = fileId; } private *getNodes(node: unknown): Generator { - let range = this.getRange(node as NodeBase | undefined) + let range = this.getRange(node as NodeBase | undefined); if (isDocument(node)) { - yield new ParseEvent(EventType.DocumentStart) + yield new ParseEvent(EventType.DocumentStart); for (const item of this.getNodes(node.contents)) { - yield item + yield item; } - yield new ParseEvent(EventType.DocumentEnd) + yield new ParseEvent(EventType.DocumentEnd); } if (isCollection(node)) { if (isSeq(node)) { - yield new ParseEvent( - EventType.SequenceStart, - new SequenceToken(this.fileId, range, undefined) - ) + yield new ParseEvent(EventType.SequenceStart, new SequenceToken(this.fileId, range, undefined)); } else if (isMap(node)) { - yield new ParseEvent( - EventType.MappingStart, - new MappingToken(this.fileId, range, undefined) - ) + yield new ParseEvent(EventType.MappingStart, new MappingToken(this.fileId, range, undefined)); } for (const item of node.items) { for (const child of this.getNodes(item)) { - yield child + yield child; } } if (isSeq(node)) { - yield new ParseEvent(EventType.SequenceEnd) + yield new ParseEvent(EventType.SequenceEnd); } else if (isMap(node)) { - yield new ParseEvent(EventType.MappingEnd) + yield new ParseEvent(EventType.MappingEnd); } } if (isScalar(node)) { - yield new ParseEvent( - EventType.Literal, - YamlObjectReader.getLiteralToken(this.fileId, range, node as Scalar) - ) + yield new ParseEvent(EventType.Literal, YamlObjectReader.getLiteralToken(this.fileId, range, node as Scalar)); } if (isPair(node)) { - const scalarKey = node.key as Scalar - range = this.getRange(scalarKey) - const key = scalarKey.value as string - yield new ParseEvent( - EventType.Literal, - new StringToken(this.fileId, range, key, undefined) - ) + const scalarKey = node.key as Scalar; + range = this.getRange(scalarKey); + const key = scalarKey.value as string; + yield new ParseEvent(EventType.Literal, new StringToken(this.fileId, range, key, undefined)); for (const child of this.getNodes(node.value)) { - yield child + yield child; } } } private getRange(node: NodeBase | undefined): TokenRange | undefined { - const range = node?.range ?? [] - const startPos = range[0] - const endPos = range[1] + const range = node?.range ?? []; + const startPos = range[0]; + const endPos = range[1]; if (startPos !== undefined && endPos !== undefined) { - const slp = this.lineCounter.linePos(startPos) - const elp = this.lineCounter.linePos(endPos) + const slp = this.lineCounter.linePos(startPos); + const elp = this.lineCounter.linePos(endPos); return { start: [slp.line, slp.col], - end: [elp.line, elp.col], - } + end: [elp.line, elp.col] + }; } - return undefined + return undefined; } - private static getLiteralToken( - fileId: number | undefined, - range: TokenRange | undefined, - token: Scalar - ) { - const value = token.value + private static getLiteralToken(fileId: number | undefined, range: TokenRange | undefined, token: Scalar) { + const value = token.value; if (value === null || value === undefined) { - return new NullToken(fileId, range, undefined) + return new NullToken(fileId, range, undefined); } switch (typeof value) { case "number": - return new NumberToken(fileId, range, value, undefined) + return new NumberToken(fileId, range, value, undefined); case "boolean": - return new BooleanToken(fileId, range, value, undefined) + return new BooleanToken(fileId, range, value, undefined); case "string": { // If the string is a YAML block string, include the original source - let source: string | undefined + let source: string | undefined; if ( (token.type === "BLOCK_LITERAL" || // | multi-line strings token.type === "BLOCK_FOLDED") && // > multi-line strings token.srcToken && token.srcToken.type === "block-scalar" ) { - source = token.srcToken.source + source = token.srcToken.source; } - return new StringToken(fileId, range, value, undefined, source) + return new StringToken(fileId, range, value, undefined, source); } default: - throw new Error( - `Unexpected value type '${typeof value}' when reading object` - ) + throw new Error(`Unexpected value type '${typeof value}' when reading object`); } } public allowLiteral(): LiteralToken | undefined { if (!this._current.done) { - const parseEvent = this._current.value + const parseEvent = this._current.value; if (parseEvent.type === EventType.Literal) { - this._current = this._generator.next() - return parseEvent.token as LiteralToken + this._current = this._generator.next(); + return parseEvent.token as LiteralToken; } } - return undefined + return undefined; } public allowSequenceStart(): SequenceToken | undefined { if (!this._current.done) { - const parseEvent = this._current.value + const parseEvent = this._current.value; if (parseEvent.type === EventType.SequenceStart) { - this._current = this._generator.next() - return parseEvent.token as SequenceToken + this._current = this._generator.next(); + return parseEvent.token as SequenceToken; } } - return undefined + return undefined; } public allowSequenceEnd(): boolean { if (!this._current.done) { - const parseEvent = this._current.value + const parseEvent = this._current.value; if (parseEvent.type === EventType.SequenceEnd) { - this._current = this._generator.next() - return true + this._current = this._generator.next(); + return true; } } - return false + return false; } public allowMappingStart(): MappingToken | undefined { if (!this._current.done) { - const parseEvent = this._current.value + const parseEvent = this._current.value; if (parseEvent.type === EventType.MappingStart) { - this._current = this._generator.next() - return parseEvent.token as MappingToken + this._current = this._generator.next(); + return parseEvent.token as MappingToken; } } - return undefined + return undefined; } public allowMappingEnd(): boolean { if (!this._current.done) { - const parseEvent = this._current.value + const parseEvent = this._current.value; if (parseEvent.type === EventType.MappingEnd) { - this._current = this._generator.next() - return true + this._current = this._generator.next(); + return true; } } - return false + return false; } public validateEnd(): void { if (!this._current.done) { - const parseEvent = this._current.value as ParseEvent + const parseEvent = this._current.value as ParseEvent; if (parseEvent.type === EventType.DocumentEnd) { - this._current = this._generator.next() - return + this._current = this._generator.next(); + return; } } - throw new Error("Expected end of reader") + throw new Error("Expected end of reader"); } public validateStart(): void { if (!this._current) { - this._current = this._generator.next() + this._current = this._generator.next(); } if (!this._current.done) { - const parseEvent = this._current.value as ParseEvent + const parseEvent = this._current.value as ParseEvent; if (parseEvent.type === EventType.DocumentStart) { - this._current = this._generator.next() - return + this._current = this._generator.next(); + return; } } - throw new Error("Expected start of reader") + throw new Error("Expected start of reader"); } } -function rangeFromLinePos( - linePos: [LinePos] | [LinePos, LinePos] | undefined -): TokenRange | undefined { +function rangeFromLinePos(linePos: [LinePos] | [LinePos, LinePos] | undefined): TokenRange | undefined { if (linePos === undefined) { - return + return; } // TokenRange and linePos are both 1-based - const start: Position = [linePos[0].line, linePos[0].col] - const end: Position = - linePos.length == 2 ? [linePos[1].line, linePos[1].col] : start - return { start, end } + const start: Position = [linePos[0].line, linePos[0].col]; + const end: Position = linePos.length == 2 ? [linePos[1].line, linePos[1].col] : start; + return {start, end}; } diff --git a/actions-workflow-parser/src/xlang.test.ts b/actions-workflow-parser/src/xlang.test.ts index 16a0c1b..906ed63 100644 --- a/actions-workflow-parser/src/xlang.test.ts +++ b/actions-workflow-parser/src/xlang.test.ts @@ -1,59 +1,55 @@ -import * as fs from "fs" -import * as path from "path" -import * as YAML from "yaml" -import { convertWorkflowTemplate } from "./model/convert" -import { TraceWriter } from "./templates/trace-writer" -import { parseWorkflow } from "./workflows/workflow-parser" +import * as fs from "fs"; +import * as path from "path"; +import * as YAML from "yaml"; +import {convertWorkflowTemplate} from "./model/convert"; +import {TraceWriter} from "./templates/trace-writer"; +import {parseWorkflow} from "./workflows/workflow-parser"; interface TestOptions { - "include-source"?: boolean - skip?: string[] + "include-source"?: boolean; + skip?: string[]; } const nullTrace: TraceWriter = { - info: (x) => {}, - verbose: (x) => {}, - error: (x) => {}, -} + info: x => {}, + verbose: x => {}, + error: x => {} +}; -const testFiles = "./testdata/reader" +const testFiles = "./testdata/reader"; describe("x-lang tests", () => { - const files = fs.readdirSync(testFiles) + const files = fs.readdirSync(testFiles); for (const file of files) { - const fileName = path.join(testFiles, file) + const fileName = path.join(testFiles, file); - const fileStat = fs.statSync(fileName) + const fileStat = fs.statSync(fileName); if (fileStat.isDirectory()) { - throw new Error("sub-directories are not supported") + throw new Error("sub-directories are not supported"); } - if ( - path.extname(fileName) !== ".yml" && - path.extname(fileName) !== ".yaml" - ) { - throw new Error("only y(a)ml files are supported " + file) + if (path.extname(fileName) !== ".yml" && path.extname(fileName) !== ".yaml") { + throw new Error("only y(a)ml files are supported " + file); } - const inputFile = fs.readFileSync(fileName, "utf8") + const inputFile = fs.readFileSync(fileName, "utf8"); - const testDocs: string[] = inputFile.split(/\r?\n---\r?\n/) - expect(testDocs.length).toBeGreaterThanOrEqual(3) + const testDocs: string[] = inputFile.split(/\r?\n---\r?\n/); + expect(testDocs.length).toBeGreaterThanOrEqual(3); - const testOptions: TestOptions = YAML.parse(testDocs[0]) - const unsupportedTest = contains(testOptions.skip, "TypeScript") + const testOptions: TestOptions = YAML.parse(testDocs[0]); + const unsupportedTest = contains(testOptions.skip, "TypeScript"); const test = () => { - let testFileName = - ".github/workflows" + fileName.substring(fileName.lastIndexOf("/")) - let testInput = testDocs[1] - let expectedTemplate = testDocs[2].trim() + let testFileName = ".github/workflows" + fileName.substring(fileName.lastIndexOf("/")); + let testInput = testDocs[1]; + let expectedTemplate = testDocs[2].trim(); // TODO: when reusable workflows are implemented, implement correctly if (fileName.indexOf("reusable") !== -1) { - testFileName = testDocs[1] - testInput = testDocs[2] - expectedTemplate = testDocs[3].trim() + testFileName = testDocs[1]; + testInput = testDocs[2]; + expectedTemplate = testDocs[3].trim(); } const parseResult = parseWorkflow( @@ -61,50 +57,45 @@ describe("x-lang tests", () => { [ { name: testFileName, - content: testInput, - }, + content: testInput + } ], nullTrace - ) + ); - expect(parseResult.value).not.toBeUndefined() + expect(parseResult.value).not.toBeUndefined(); - const workflowTemplate = convertWorkflowTemplate( - parseResult.context, - parseResult.value! - ) + const workflowTemplate = convertWorkflowTemplate(parseResult.context, parseResult.value!); // Unless this tests is only used by TypeScript, remove the events for now. // TODO: Remove this once we parse events everywhere const includeEvents = - testOptions.skip !== undefined && - contains(testOptions.skip, "Go") && - contains(testOptions.skip, "C#") + testOptions.skip !== undefined && contains(testOptions.skip, "Go") && contains(testOptions.skip, "C#"); if (!includeEvents) { - delete (workflowTemplate as any).events + delete (workflowTemplate as any).events; } // Other parsers don't have a partial template when there are errors if (workflowTemplate.errors) { - delete (workflowTemplate as any).jobs + delete (workflowTemplate as any).jobs; } - const result = JSON.stringify(workflowTemplate, null, " ") - expect(result).toBe(expectedTemplate) - } + const result = JSON.stringify(workflowTemplate, null, " "); + expect(result).toBe(expectedTemplate); + }; if (unsupportedTest) { - it.failing(fileName, test) + it.failing(fileName, test); } else { - it(fileName, test) + it(fileName, test); } } -}) +}); // Case-insensitive contains function contains(arr: string[] | undefined, term: string): boolean { if (!arr) { - return false + return false; } - return arr.map((x) => x.toLowerCase()).indexOf(term.toLowerCase()) !== -1 + return arr.map(x => x.toLowerCase()).indexOf(term.toLowerCase()) !== -1; } diff --git a/browser-playground/src/client/client.ts b/browser-playground/src/client/client.ts index e31935c..a18d1f6 100644 --- a/browser-playground/src/client/client.ts +++ b/browser-playground/src/client/client.ts @@ -1,16 +1,7 @@ import * as monaco from "monaco-editor"; -import { - CloseAction, - ErrorAction, - MessageTransports, - MonacoLanguageClient, - MonacoServices, -} from "monaco-languageclient"; -import { - BrowserMessageReader, - BrowserMessageWriter, -} from "vscode-languageserver-protocol/browser.js"; +import {CloseAction, ErrorAction, MessageTransports, MonacoLanguageClient, MonacoServices} from "monaco-languageclient"; +import {BrowserMessageReader, BrowserMessageWriter} from "vscode-languageserver-protocol/browser.js"; monaco.editor.create(document.getElementById("container")!, { value: `name: Demo workflow @@ -30,42 +21,37 @@ jobs: - uses: actions/checkout@v3 - run: echo "Hello \${{ github.event.inputs.name }}"`, language: "yaml", - wordBasedSuggestions: false, + wordBasedSuggestions: false }); -function createLanguageClient( - transports: MessageTransports -): MonacoLanguageClient { +function createLanguageClient(transports: MessageTransports): MonacoLanguageClient { return new MonacoLanguageClient({ name: "GitHub Actions Language Client", clientOptions: { // Handle all yaml files as workflows - documentSelector: [{ language: "yaml" }], + documentSelector: [{language: "yaml"}], errorHandler: { - error: () => ({ action: ErrorAction.Continue }), - closed: () => ({ action: CloseAction.DoNotRestart }), + error: () => ({action: ErrorAction.Continue}), + closed: () => ({action: CloseAction.DoNotRestart}) }, // Custom options for the GitHub Actions language server - initializationOptions: {}, + initializationOptions: {} }, connectionProvider: { - get: () => Promise.resolve(transports), - }, + get: () => Promise.resolve(transports) + } }); } MonacoServices.install(); -const worker = new Worker( - new URL("../service-worker/service-worker.ts", import.meta.url), - { - type: "module", - } -); +const worker = new Worker(new URL("../service-worker/service-worker.ts", import.meta.url), { + type: "module" +}); const reader = new BrowserMessageReader(worker); const writer = new BrowserMessageWriter(worker); -const languageClient = createLanguageClient({ reader, writer }); +const languageClient = createLanguageClient({reader, writer}); languageClient.start(); reader.onClose(() => languageClient.stop()); diff --git a/browser-playground/src/service-worker/service-worker.ts b/browser-playground/src/service-worker/service-worker.ts index 08136cf..202ce2d 100644 --- a/browser-playground/src/service-worker/service-worker.ts +++ b/browser-playground/src/service-worker/service-worker.ts @@ -1,10 +1,6 @@ -import { - BrowserMessageReader, - BrowserMessageWriter, - createConnection, -} from "vscode-languageserver/browser.js"; +import {BrowserMessageReader, BrowserMessageWriter, createConnection} from "vscode-languageserver/browser.js"; -import { initConnection } from "@github/actions-languageserver/connection"; +import {initConnection} from "@github/actions-languageserver/connection"; const messageReader = new BrowserMessageReader(self); const messageWriter = new BrowserMessageWriter(self);