From 844d4bf1fe57a2192ba7d89184aab779eeea4c3d Mon Sep 17 00:00:00 2001 From: Dilip Kola Date: Tue, 19 Sep 2023 17:27:47 +0530 Subject: [PATCH 1/2] feat: add support for short key value in object expr --- src/parser.ts | 38 +++++++++++++++++++++++++----- test/scenarios/objects/template.jt | 10 ++++---- 2 files changed, 37 insertions(+), 11 deletions(-) diff --git a/src/parser.ts b/src/parser.ts index e5833f6..5990615 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -828,13 +828,31 @@ export class JsonTemplateParser { return key; } - private parseObjectPropExpr(): ObjectPropExpression { - let key: Expression | string | undefined; - if (!this.lexer.matchSpread()) { - key = this.parseObjectKeyExpr(); - this.lexer.expect(':'); + private parseShortKeyValueObjectPropExpr(): ObjectPropExpression | undefined { + if (this.lexer.matchID() && (this.lexer.match(',', 1) || this.lexer.match('}', 1))) { + const key = this.lexer.lookahead().value; + const value = this.parseBaseExpr(); + return { + type: SyntaxType.OBJECT_PROP_EXPR, + key, + value, + }; } - const value = this.parseSpreadExpr(); + } + + private parseSpreadObjectPropExpr(): ObjectPropExpression | undefined { + if (this.lexer.matchSpread()) { + return { + type: SyntaxType.OBJECT_PROP_EXPR, + value: this.parseSpreadExpr(), + }; + } + } + + private parseNormalObjectPropExpr(): ObjectPropExpression { + const key = this.parseObjectKeyExpr(); + this.lexer.expect(':'); + const value = this.parseBaseExpr(); return { type: SyntaxType.OBJECT_PROP_EXPR, key, @@ -842,6 +860,14 @@ export class JsonTemplateParser { }; } + private parseObjectPropExpr(): ObjectPropExpression { + return ( + this.parseSpreadObjectPropExpr() ?? + this.parseShortKeyValueObjectPropExpr() ?? + this.parseNormalObjectPropExpr() + ); + } + private parseObjectExpr(): ObjectExpression { this.lexer.expect('{'); const props = this.parseCommaSeparatedElements('}', () => this.parseObjectPropExpr()); diff --git a/test/scenarios/objects/template.jt b/test/scenarios/objects/template.jt index bbc3db1..35df1f3 100644 --- a/test/scenarios/objects/template.jt +++ b/test/scenarios/objects/template.jt @@ -1,14 +1,14 @@ let c = "c key"; let d = 3; +let b = 2; let a = { - // short form for "a" - a: 1, - "b": 2, + "a": 1, + b, // [c] coverts to "c key" [c]: { // this coverts to d: 3 - d: d - } + d + }, }; a.({ a: .a, From f021cb1dd198e7915645c2b0909e462744a57309 Mon Sep 17 00:00:00 2001 From: Dilip Kola Date: Sat, 23 Sep 2023 22:52:35 +0530 Subject: [PATCH 2/2] feat(INT-701): add support for return and throw --- package.json | 2 +- src/parser.ts | 22 ++++++++++++++++ src/translator.ts | 26 ++++++++++++++++++- src/types.ts | 12 +++++++++ test/scenario.test.ts | 42 +++++++++++++++++++++++++++++++ test/scenarios/return/data.ts | 12 +++++++++ test/scenarios/return/template.jt | 2 ++ test/scenarios/throw/data.ts | 12 +++++++++ test/scenarios/throw/template.jt | 1 + test/test_scenario.ts | 38 ---------------------------- 10 files changed, 129 insertions(+), 40 deletions(-) create mode 100644 test/scenario.test.ts create mode 100644 test/scenarios/return/data.ts create mode 100644 test/scenarios/return/template.jt create mode 100644 test/scenarios/throw/data.ts create mode 100644 test/scenarios/throw/template.jt delete mode 100644 test/test_scenario.ts diff --git a/package.json b/package.json index 6d0c21e..b6a627d 100644 --- a/package.json +++ b/package.json @@ -56,7 +56,7 @@ "format": "prettier --write '**/*.ts' '**/*.js' '**/*.json'", "prepare": "husky install", "jest:scenarios": "jest e2e.test.ts --verbose", - "test:scenario": "ts-node test/test_scenario.ts", + "test:scenario": "jest test/scenario.test.ts --verbose", "test:stryker": "stryker run" }, "lint-staged": { diff --git a/src/parser.ts b/src/parser.ts index 5990615..c0f4e01 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -28,6 +28,8 @@ import { Keyword, EngineOptions, PathType, + ReturnExpression, + ThrowExpression, } from './types'; import { JsonTemplateParserError } from './errors'; import { DATA_PARAM_KEY } from './constants'; @@ -997,6 +999,22 @@ export class JsonTemplateParser { }; } + private parseReturnExpr(): ReturnExpression { + this.lexer.ignoreTokens(1); + return { + type: SyntaxType.RETURN_EXPR, + value: this.parseBaseExpr(), + }; + } + + private parseThrowExpr(): ThrowExpression { + this.lexer.ignoreTokens(1); + return { + type: SyntaxType.THROW_EXPR, + value: this.parseBaseExpr(), + }; + } + private parseKeywordBasedExpr(): Expression { const token = this.lexer.lookahead(); switch (token.value) { @@ -1006,6 +1024,10 @@ export class JsonTemplateParser { return this.parseLambdaExpr(); case Keyword.ASYNC: return this.parseAsyncFunctionExpr(); + case Keyword.RETURN: + return this.parseReturnExpr(); + case Keyword.THROW: + return this.parseThrowExpr(); case Keyword.FUNCTION: return this.parseFunctionExpr(); default: diff --git a/src/translator.ts b/src/translator.ts index 8202b63..0c6152c 100644 --- a/src/translator.ts +++ b/src/translator.ts @@ -34,6 +34,8 @@ import { BlockExpression, PathOptions, PathType, + ReturnExpression, + ThrowExpression, } from './types'; import { CommonUtils } from './utils'; @@ -82,7 +84,6 @@ export class JsonTemplateTranslator { this.init(); let code: string[] = []; const exprCode = this.translateExpr(this.expr, dest, ctx); - code.push(`let ${dest};`); code.push(this.vars.map((elm) => `let ${elm};`).join('')); code.push(exprCode); @@ -159,11 +160,34 @@ export class JsonTemplateTranslator { case SyntaxType.CONDITIONAL_EXPR: return this.translateConditionalExpr(expr as ConditionalExpression, dest, ctx); + case SyntaxType.RETURN_EXPR: + return this.translateReturnExpr(expr as ReturnExpression, dest, ctx); + + case SyntaxType.THROW_EXPR: + return this.translateThrowExpr(expr as ThrowExpression, dest, ctx); default: return ''; } } + private translateThrowExpr(expr: ThrowExpression, _dest: string, ctx: string): string { + const code: string[] = []; + const value = this.acquireVar(); + code.push(this.translateExpr(expr.value, value, ctx)); + code.push(`throw ${value};`); + this.releaseVars(value); + return code.join(''); + } + + private translateReturnExpr(expr: ReturnExpression, _dest: string, ctx: string): string { + const code: string[] = []; + const value = this.acquireVar(); + code.push(this.translateExpr(expr.value, value, ctx)); + code.push(`return ${value};`); + this.releaseVars(value); + return code.join(''); + } + private translateConditionalExpr(expr: ConditionalExpression, dest: string, ctx: string): string { const code: string[] = []; const ifVar = this.acquireVar(); diff --git a/src/types.ts b/src/types.ts index 0206583..b2490da 100644 --- a/src/types.ts +++ b/src/types.ts @@ -9,6 +9,8 @@ export enum Keyword { ASYNC = 'async', IN = 'in', NOT = 'not', + RETURN = 'return', + THROW = 'throw', } export enum TokenType { @@ -74,6 +76,8 @@ export enum SyntaxType { FUNCTION_EXPR, FUNCTION_CALL_ARG, FUNCTION_CALL_EXPR, + RETURN_EXPR, + THROW_EXPR, STATEMENTS_EXPR, } @@ -211,3 +215,11 @@ export interface ConditionalExpression extends Expression { then: Expression; else: Expression; } + +export interface ReturnExpression extends Expression { + value: Expression; +} + +export interface ThrowExpression extends Expression { + value: Expression; +} diff --git a/test/scenario.test.ts b/test/scenario.test.ts new file mode 100644 index 0000000..32e9e62 --- /dev/null +++ b/test/scenario.test.ts @@ -0,0 +1,42 @@ +import { join } from 'path'; +import { Command } from 'commander'; +import { Scenario } from './types'; +import { ScenarioUtils } from './utils'; + +// Run: npm run test:scenario -- --scenario=arrays --index=1 +const command = new Command(); +command + .allowUnknownOption() + .option('-s, --scenario ', 'Enter Scenario Name') + .option('-i, --index ', 'Enter Test case index') + .parse(); + +const opts = command.opts(); +const scenarioName = opts.scenario || 'none'; +const index = +(opts.index || 0); + +describe(`${scenarioName}:`, () => { + it(`Scenario ${index}`, async () => { + if (scenarioName === 'none') { + return; + } + const scenarioDir = join(__dirname, 'scenarios', scenarioName); + const scenarios = ScenarioUtils.extractScenarios(scenarioDir); + const scenario: Scenario = scenarios[index] || scenarios[0]; + let result; + try { + console.log( + `Executing scenario: ${scenarioName}, test: ${index}, template: ${ + scenario.templatePath || 'template.jt' + }`, + ); + const templateEngine = ScenarioUtils.createTemplateEngine(scenarioDir, scenario); + result = await ScenarioUtils.evaluateScenario(templateEngine, scenario); + expect(result).toEqual(scenario.output); + } catch (error: any) { + console.log('Actual result', JSON.stringify(result, null, 2)); + console.log('Expected result', JSON.stringify(scenario.output, null, 2)); + expect(error.message).toEqual(scenario.error); + } + }); +}); diff --git a/test/scenarios/return/data.ts b/test/scenarios/return/data.ts new file mode 100644 index 0000000..a516931 --- /dev/null +++ b/test/scenarios/return/data.ts @@ -0,0 +1,12 @@ +import { Scenario } from '../../types'; + +export const data: Scenario[] = [ + { + input: 3, + output: 1, + }, + { + input: 2, + output: 1, + }, +]; diff --git a/test/scenarios/return/template.jt b/test/scenarios/return/template.jt new file mode 100644 index 0000000..ca2e91b --- /dev/null +++ b/test/scenarios/return/template.jt @@ -0,0 +1,2 @@ +(. % 2 === 0) ? return ./2; +(. - 1)/2; \ No newline at end of file diff --git a/test/scenarios/throw/data.ts b/test/scenarios/throw/data.ts new file mode 100644 index 0000000..3faeeae --- /dev/null +++ b/test/scenarios/throw/data.ts @@ -0,0 +1,12 @@ +import { Scenario } from '../../types'; + +export const data: Scenario[] = [ + { + input: 3, + error: 'num must be even', + }, + { + input: 2, + output: 1, + }, +]; diff --git a/test/scenarios/throw/template.jt b/test/scenarios/throw/template.jt new file mode 100644 index 0000000..3a773b5 --- /dev/null +++ b/test/scenarios/throw/template.jt @@ -0,0 +1 @@ +(. % 2 !== 0) ? throw new Error("num must be even") : ./2; \ No newline at end of file diff --git a/test/test_scenario.ts b/test/test_scenario.ts deleted file mode 100644 index 9d6e1ee..0000000 --- a/test/test_scenario.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { join } from 'path'; -import { deepEqual } from 'assert'; -import { Command } from 'commander'; -import { Scenario } from './types'; -import { ScenarioUtils } from './utils'; - -const command = new Command(); -command - .allowUnknownOption() - .option('-s, --scenario ', 'Enter Scenario Name') - .option('-i, --index ', 'Enter Test case index') - .parse(); - -const opts = command.opts(); -const scenarioName = opts.scenario || 'assignments'; -const index = +(opts.index || 0); - -async function createAndEvaluateTemplate() { - try { - const scenarioDir = join(__dirname, 'scenarios', scenarioName); - const scenarios: Scenario[] = ScenarioUtils.extractScenarios(scenarioDir); - const scenario: Scenario = scenarios[index] || scenarios[0]; - console.log( - `Executing scenario: ${scenarioName}, test: ${index}, template: ${ - scenario.templatePath || 'template.jt' - }`, - ); - const templateEngine = ScenarioUtils.createTemplateEngine(scenarioDir, scenario); - const result = await ScenarioUtils.evaluateScenario(templateEngine, scenario); - console.log('Actual result', JSON.stringify(result, null, 2)); - console.log('Expected result', JSON.stringify(scenario.output, null, 2)); - deepEqual(result, scenario.output, 'matching failed'); - } catch (error) { - console.error(error); - } -} - -createAndEvaluateTemplate();