From 470a95deaac94e12980cd2e96657f33577f7b025 Mon Sep 17 00:00:00 2001 From: Tom <26638278+tomblind@users.noreply.github.com> Date: Sat, 1 Jun 2019 15:53:35 -0600 Subject: [PATCH 1/2] fix for parenthesis-stripping - logic moved to printer - decision to strip is now explicit based on inner expression type to avoid surprises fixes #619 --- src/LuaPrinter.ts | 19 ++++++- src/LuaTransformer.ts | 5 -- .../__snapshots__/transformation.spec.ts.snap | 4 +- test/unit/expressions.spec.ts | 54 +++++++++++++++++++ 4 files changed, 74 insertions(+), 8 deletions(-) diff --git a/src/LuaPrinter.ts b/src/LuaPrinter.ts index 0792cb7bc..5b1d8a8c4 100644 --- a/src/LuaPrinter.ts +++ b/src/LuaPrinter.ts @@ -610,8 +610,25 @@ export class LuaPrinter { return this.createSourceNode(expression, chunks); } + private canStripParenthesis(expression: tstl.Expression): boolean { + return ( + tstl.isParenthesizedExpression(expression) || + tstl.isTableIndexExpression(expression) || + tstl.isCallExpression(expression) || + tstl.isMethodCallExpression(expression) || + tstl.isIdentifier(expression) || + tstl.isNilLiteral(expression) || + tstl.isNumericLiteral(expression) || + tstl.isBooleanLiteral(expression) + ); + } + public printParenthesizedExpression(expression: tstl.ParenthesizedExpression): SourceNode { - return this.createSourceNode(expression, ["(", this.printExpression(expression.innerExpression), ")"]); + const innerExpression = this.printExpression(expression.innerExpression); + if (this.canStripParenthesis(expression.innerExpression)) { + return this.createSourceNode(expression, innerExpression); + } + return this.createSourceNode(expression, ["(", innerExpression, ")"]); } public printCallExpression(expression: tstl.CallExpression): SourceNode { diff --git a/src/LuaTransformer.ts b/src/LuaTransformer.ts index 8a5fac2ea..9f430f290 100644 --- a/src/LuaTransformer.ts +++ b/src/LuaTransformer.ts @@ -3432,11 +3432,6 @@ export class LuaTransformer { } public transformParenthesizedExpression(expression: ts.ParenthesizedExpression): ExpressionVisitResult { - if (ts.isAssertionExpression(expression.expression)) { - // Strip parenthesis from casts - return this.transformExpression(expression.expression); - } - return tstl.createParenthesizedExpression(this.transformExpression(expression.expression), expression); } diff --git a/test/translation/__snapshots__/transformation.spec.ts.snap b/test/translation/__snapshots__/transformation.spec.ts.snap index ca2e7e42b..448f18106 100644 --- a/test/translation/__snapshots__/transformation.spec.ts.snap +++ b/test/translation/__snapshots__/transformation.spec.ts.snap @@ -580,7 +580,7 @@ exports[`Transformation (tryCatch) 1`] = ` local ____TS_try, er = pcall(function() local a = 42 end) - if not (____TS_try) then + if not ____TS_try then local b = \\"fail\\" end end" @@ -591,7 +591,7 @@ exports[`Transformation (tryCatchFinally) 1`] = ` local ____TS_try, er = pcall(function() local a = 42 end) - if not (____TS_try) then + if not ____TS_try then local b = \\"fail\\" end do diff --git a/test/unit/expressions.spec.ts b/test/unit/expressions.spec.ts index 23d137f50..93d3169a8 100644 --- a/test/unit/expressions.spec.ts +++ b/test/unit/expressions.spec.ts @@ -527,3 +527,57 @@ test.each([ `; expect(util.transpileAndExecute(code)).toBe(1); }); + +test("binary expression with 'as' type assertion wrapped in parenthesis", () => { + expect(util.transpileAndExecute("return 2 * (3 - 2 as number);")).toBe(2); +}); + +test.each([ + "(x as any).foo;", + "(y.x as any).foo;", + "(y['x'] as any).foo;", + "(z() as any).foo;", + "(y.z() as any).foo;", + "(x).foo;", + "(y.x).foo;", + "(y['x']).foo;", + "(z()).foo;", + "(y.z()).foo;", + "(x as unknown as any).foo;", + "(x as any).foo;", + "((x as unknown) as any).foo;", + "((x) as any).foo;", +])("'as' type assertion should strip parenthesis (%p)", expression => { + const code = ` + declare let x: unknown; + declare let y: { x: unknown; z(this: void): unknown; }; + declare function z(this: void): unknown; + ${expression}`; + + const lua = util.transpileString(code, undefined, false); + expect(lua).not.toMatch(/\(.+\)/); +}); + +test.each([ + "(x + 1 as any).foo;", + "(!x as any).foo;", + "(x ** 2 as any).foo;", + "(x < 2 as any).foo;", + "(x in y as any).foo;", + "(x + 1).foo;", + "(!x).foo;", + "(x + 1 as unknown as any).foo;", + "((x + 1 as unknown) as any).foo;", + "(!x as unknown as any).foo;", + "((!x as unknown) as any).foo;", + "(!x as any).foo;", + "((!x) as any).foo;", +])("'as' type assertion should not strip parenthesis (%p)", expression => { + const code = ` + declare let x: number; + declare let y: {}; + ${expression}`; + + const lua = util.transpileString(code, undefined, false); + expect(lua).toMatch(/\(.+\)/); +}); From a96c4e2c29e811533765c19eb065e28dc84efa4f Mon Sep 17 00:00:00 2001 From: Tom <26638278+tomblind@users.noreply.github.com> Date: Sun, 2 Jun 2019 06:22:11 -0600 Subject: [PATCH 2/2] not operator precedence test --- test/unit/expressions.spec.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/test/unit/expressions.spec.ts b/test/unit/expressions.spec.ts index 93d3169a8..b19d029c9 100644 --- a/test/unit/expressions.spec.ts +++ b/test/unit/expressions.spec.ts @@ -581,3 +581,12 @@ test.each([ const lua = util.transpileString(code, undefined, false); expect(lua).toMatch(/\(.+\)/); }); + +test("not operator precedence (%p)", () => { + const code = ` + const a = true; + const b = false; + return !a && b;`; + + expect(util.transpileAndExecute(code)).toBe(false); +});