diff --git a/src/transformation/utils/lua-ast.ts b/src/transformation/utils/lua-ast.ts index a04c69035..effc18814 100644 --- a/src/transformation/utils/lua-ast.ts +++ b/src/transformation/utils/lua-ast.ts @@ -1,11 +1,10 @@ -import * as ts from "typescript"; import * as assert from "assert"; +import * as ts from "typescript"; import { LuaTarget } from "../../CompilerOptions"; import * as lua from "../../LuaAST"; import { TransformationContext } from "../context"; -import { getCurrentNamespace } from "../visitors/namespace"; import { createExportedIdentifier, getIdentifierExportScope } from "./export"; -import { findScope, peekScope, ScopeType } from "./scope"; +import { peekScope, ScopeType } from "./scope"; import { isFunctionType } from "./typescript"; export type OneToManyVisitorResult = T | T[] | undefined; @@ -122,7 +121,6 @@ export function createLocalOrExportedOrGlobalDeclaration( let declaration: lua.VariableDeclarationStatement | undefined; let assignment: lua.AssignmentStatement | undefined; - const isVariableDeclaration = tsOriginal !== undefined && ts.isVariableDeclaration(tsOriginal); const isFunctionDeclaration = tsOriginal !== undefined && ts.isFunctionDeclaration(tsOriginal); const identifiers = Array.isArray(lhs) ? lhs : [lhs]; @@ -143,11 +141,10 @@ export function createLocalOrExportedOrGlobalDeclaration( ); } } else { - const insideFunction = findScope(context, ScopeType.Function) !== undefined; - - if (context.isModule || getCurrentNamespace(context) || insideFunction || isVariableDeclaration) { - const scope = peekScope(context); + const scope = peekScope(context); + const isTopLevelVariable = scope.type === ScopeType.File; + if (context.isModule || !isTopLevelVariable) { const isPossibleWrappedFunction = !isFunctionDeclaration && tsOriginal && diff --git a/src/transformation/visitors/namespace.ts b/src/transformation/visitors/namespace.ts index 5835f9112..fc269f5e7 100644 --- a/src/transformation/visitors/namespace.ts +++ b/src/transformation/visitors/namespace.ts @@ -46,8 +46,8 @@ function moduleHasEmittedBody( return false; } +// Static context -> namespace dictionary keeping the current namespace for each transformation context const currentNamespaces = new WeakMap(); -export const getCurrentNamespace = (context: TransformationContext) => currentNamespaces.get(context); export const transformModuleDeclaration: FunctionVisitor = (node, context) => { const annotations = getTypeAnnotations(context, context.checker.getTypeAtLocation(node)); diff --git a/test/translation/__snapshots__/transformation.spec.ts.snap b/test/translation/__snapshots__/transformation.spec.ts.snap index 6d64751eb..f4e2ce661 100644 --- a/test/translation/__snapshots__/transformation.spec.ts.snap +++ b/test/translation/__snapshots__/transformation.spec.ts.snap @@ -1,18 +1,27 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`Transformation (blockScopeVariables) 1`] = ` +"do + local a = 1 + local b = 1 + local ____ = {c = 1} + local c = ____.c +end" +`; + exports[`Transformation (characterEscapeSequence) 1`] = ` -"local quoteInDoubleQuotes = \\"' ' '\\" -local quoteInTemplateString = \\"' ' '\\" -local doubleQuoteInQuotes = \\"\\\\\\" \\\\\\" \\\\\\"\\" -local doubleQuoteInDoubleQuotes = \\"\\\\\\" \\\\\\" \\\\\\"\\" -local doubleQuoteInTemplateString = \\"\\\\\\" \\\\\\" \\\\\\"\\" -local backQuoteInQuotes = \\"\` \` \`\\" -local backQuoteInDoubleQuotes = \\"\` \` \`\\" -local backQuoteInTemplateString = \\"\` \` \`\\" -local escapedCharsInQuotes = \\"\\\\\\\\ \\\\0 \\\\b \\\\t \\\\n \\\\v \\\\f \\\\\\" ' \`\\" -local escapedCharsInDoubleQuotes = \\"\\\\\\\\ \\\\0 \\\\b \\\\t \\\\n \\\\v \\\\f \\\\\\" ' \`\\" -local escapedCharsInTemplateString = \\"\\\\\\\\ \\\\0 \\\\b \\\\t \\\\n \\\\v \\\\f \\\\\\" ' \`\\" -local nonEmptyTemplateString = \\"Level 0: \\\\n\\\\t \\" .. \\"Level 1: \\\\n\\\\t\\\\t \\" .. \\"Level 3: \\\\n\\\\t\\\\t\\\\t \\" .. \\"Last level \\\\n --\\" .. \\" \\\\n --\\" .. \\" \\\\n --\\" .. \\" \\\\n --\\"" +"quoteInDoubleQuotes = \\"' ' '\\" +quoteInTemplateString = \\"' ' '\\" +doubleQuoteInQuotes = \\"\\\\\\" \\\\\\" \\\\\\"\\" +doubleQuoteInDoubleQuotes = \\"\\\\\\" \\\\\\" \\\\\\"\\" +doubleQuoteInTemplateString = \\"\\\\\\" \\\\\\" \\\\\\"\\" +backQuoteInQuotes = \\"\` \` \`\\" +backQuoteInDoubleQuotes = \\"\` \` \`\\" +backQuoteInTemplateString = \\"\` \` \`\\" +escapedCharsInQuotes = \\"\\\\\\\\ \\\\0 \\\\b \\\\t \\\\n \\\\v \\\\f \\\\\\" ' \`\\" +escapedCharsInDoubleQuotes = \\"\\\\\\\\ \\\\0 \\\\b \\\\t \\\\n \\\\v \\\\f \\\\\\" ' \`\\" +escapedCharsInTemplateString = \\"\\\\\\\\ \\\\0 \\\\b \\\\t \\\\n \\\\v \\\\f \\\\\\" ' \`\\" +nonEmptyTemplateString = \\"Level 0: \\\\n\\\\t \\" .. \\"Level 1: \\\\n\\\\t\\\\t \\" .. \\"Level 3: \\\\n\\\\t\\\\t\\\\t \\" .. \\"Last level \\\\n --\\" .. \\" \\\\n --\\" .. \\" \\\\n --\\" .. \\" \\\\n --\\"" `; exports[`Transformation (classExtension1) 1`] = ` @@ -244,7 +253,7 @@ ____exports.foo = \\"bar\\" return ____exports" `; -exports[`Transformation (modulesVariableNoExport) 1`] = `"local foo = \\"bar\\""`; +exports[`Transformation (modulesVariableNoExport) 1`] = `"foo = \\"bar\\""`; exports[`Transformation (namespacePhantom) 1`] = ` "function nsMember(self) @@ -257,6 +266,19 @@ exports[`Transformation (returnDefault) 1`] = ` end" `; +exports[`Transformation (topLevelVariables) 1`] = ` +"obj = {value1 = 1, value2 = 2} +value1 = obj.value1 +value2 = obj.value2 +obj2 = {value3 = 1, value4 = 2} +value3 = obj2.value3 +value4 = obj2.value4 +function fun1(self) +end +fun2 = function() +end" +`; + exports[`Transformation (unusedDefaultWithNamespaceImport) 1`] = ` "local ____exports = {} local x = require(\\"module\\") diff --git a/test/translation/transformation/blockScopeVariables.ts b/test/translation/transformation/blockScopeVariables.ts new file mode 100644 index 000000000..7623f0efa --- /dev/null +++ b/test/translation/transformation/blockScopeVariables.ts @@ -0,0 +1,5 @@ +{ + const a = 1; + const [b] = [1]; + const { c } = { c: 1 }; +} diff --git a/test/translation/transformation/topLevelVariables.ts b/test/translation/transformation/topLevelVariables.ts new file mode 100644 index 000000000..5782c4cf4 --- /dev/null +++ b/test/translation/transformation/topLevelVariables.ts @@ -0,0 +1,11 @@ +const obj = { value1: 1, value2: 2 }; +const value1 = obj.value1; +const { value2 } = obj; + +let noValueLet; +let obj2 = { value3: 1, value4: 2 }; +let value3 = obj2.value3; +let { value4 } = obj2; + +function fun1(): void {} +const fun2 = () => {}; diff --git a/test/unit/assignments.spec.ts b/test/unit/assignments.spec.ts index c559690d0..ce7abd622 100644 --- a/test/unit/assignments.spec.ts +++ b/test/unit/assignments.spec.ts @@ -1,13 +1,21 @@ import * as util from "../util"; -test("const declaration", () => { - const lua = util.transpileString(`const foo = true;`); - expect(lua).toBe(`local foo = true`); +test.each(["const", "let"])("%s declaration not top-level is not global", declarationKind => { + util.testModule` + { + ${declarationKind} foo = true; + } + // @ts-ignore + return (globalThis as any).foo; + `.expectToEqual(undefined); }); -test("let declaration", () => { - const lua = util.transpileString(`let foo = true;`); - expect(lua).toBe(`local foo = true`); +test.each(["const", "let"])("%s declaration top-level is global", declarationKind => { + util.testModule` + ${declarationKind} foo = true; + // @ts-ignore + return (globalThis as any).foo; + `.expectToEqual(true); }); test("var declaration is disallowed", () => { diff --git a/test/unit/objectLiteral.spec.ts b/test/unit/objectLiteral.spec.ts index b4bcb3d4e..203b05780 100644 --- a/test/unit/objectLiteral.spec.ts +++ b/test/unit/objectLiteral.spec.ts @@ -1,66 +1,65 @@ import * as util from "../util"; -test.each([ - { inp: `{a:3,b:"4"}`, out: '{a = 3, b = "4"}' }, - { inp: `{"a":3,b:"4"}`, out: '{a = 3, b = "4"}' }, - { inp: `{["a"]:3,b:"4"}`, out: '{a = 3, b = "4"}' }, - { inp: `{["a"+123]:3,b:"4"}`, out: '{["a" .. 123] = 3, b = "4"}' }, - { inp: `{[myFunc()]:3,b:"4"}`, out: '{\n [myFunc(_G)] = 3,\n b = "4"\n}' }, - { inp: `{x}`, out: `{x = x}` }, -])("Object Literal (%p)", ({ inp, out }) => { - const lua = util.transpileString(`const myvar = ${inp};`); - expect(lua).toBe(`local myvar = ${out}`); +test.each([`{ a: 3, b: "4" }`, `{ "a": 3, b: "4" }`, `{ ["a"]: 3, b: "4" }`, `{ ["a" + 123]: 3, b: "4" }`])( + "Object Literal (%p)", + inp => { + util.testExpression(inp).expectToMatchJsResult(); + } +); + +test("object literal with function call to get key", () => { + util.testFunction` + const myFunc = () => "a"; + return { [myFunc() + "b"]: 3 }; + `.expectToMatchJsResult(); +}); + +test("object literal with shorthand property", () => { + util.testFunction` + const x = 5; + return { x }; + `.expectToMatchJsResult(); }); describe("property shorthand", () => { test("should support property shorthand", () => { - const result = util.transpileAndExecute(` + util.testFunction` const x = 1; const o = { x }; return o.x; - `); - - expect(result).toBe(1); + `.expectToMatchJsResult(); }); test.each([NaN, Infinity])("should support %p shorthand", identifier => { - const result = util.transpileAndExecute(`return ({ ${identifier} }).${identifier}`); - - expect(result).toBe(identifier); + util.testExpression`({ ${identifier} }).${identifier}`.expectToMatchJsResult(); }); test("should support _G shorthand", () => { - const result = util.transpileAndExecute( - `return ({ _G })._G.foobar;`, - undefined, - `foobar = "foobar"`, - "declare const _G: any;" - ); - - expect(result).toBe("foobar"); + util.testExpression`({ _G })._G.foobar` + .setTsHeader(`declare const _G: any;`) + .setLuaHeader(`foobar = "foobar"`) + .expectToEqual("foobar"); }); test("should support export property shorthand", () => { - const code = ` + util.testModule` export const x = 1; const o = { x }; export const y = o.x; - `; - expect(util.transpileExecuteAndReturnExport(code, "y")).toBe(1); + `.expectToMatchJsResult(); }); }); test("undefined as object key", () => { - const code = `const foo = {undefined: "foo"}; - return foo.undefined;`; - expect(util.transpileAndExecute(code)).toBe("foo"); + util.testFunction` + const foo = {undefined: "foo"}; + return foo.undefined; + `.expectToMatchJsResult(); }); test.each([`({x: "foobar"}.x)`, `({x: "foobar"}["x"])`, `({x: () => "foobar"}.x())`, `({x: () => "foobar"}["x"]())`])( "object literal property access (%p)", expression => { - const code = `return ${expression}`; - const expectResult = eval(expression); - expect(util.transpileAndExecute(code)).toBe(expectResult); + util.testExpression(expression).expectToMatchJsResult(); } );