Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion src/CompilerOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ export type CompilerOptions = OmitIndexSignature<ts.CompilerOptions> & {
luaBundleEntry?: string;
luaTarget?: LuaTarget;
luaLibImport?: LuaLibImportKind;
noHoisting?: boolean;
sourceMapTraceback?: boolean;
plugins?: Array<ts.PluginImport | TransformerImport>;
[option: string]: ts.CompilerOptions[string] | Array<ts.PluginImport | TransformerImport>;
Expand Down
5 changes: 0 additions & 5 deletions src/cli/parse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,6 @@ export const optionDeclarations: CommandLineOption[] = [
description: "Specify if a header will be added to compiled files.",
type: "boolean",
},
{
name: "noHoisting",
description: "Disables hoisting.",
type: "boolean",
},
{
name: "sourceMapTraceback",
description: "Applies the source map to show source TS files and lines in error tracebacks.",
Expand Down
7 changes: 0 additions & 7 deletions src/transformation/utils/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,13 +130,6 @@ export const UnsupportedNonDestructuringLuaIterator = (node: ts.Node) =>
export const UnresolvableRequirePath = (node: ts.Node, reason: string, path?: string) =>
new TranspileError(`${reason}. TypeScript path: ${path}.`, node);

export const ReferencedBeforeDeclaration = (node: ts.Identifier) =>
new TranspileError(
`Identifier "${node.text}" was referenced before it was declared. The declaration ` +
"must be moved before the identifier's use, or hoisting must be enabled.",
node
);

export const UnsupportedObjectDestructuringInForOf = (node: ts.Node) =>
new TranspileError(`Unsupported object destructuring in for...of statement.`, node);

Expand Down
20 changes: 9 additions & 11 deletions src/transformation/utils/lua-ast.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ export function createHoistableVariableDeclarationStatement(
tsOriginal?: ts.Node
): lua.AssignmentStatement | lua.VariableDeclarationStatement {
const declaration = lua.createVariableDeclarationStatement(identifier, initializer, tsOriginal);
if (!context.options.noHoisting && identifier.symbolId) {
if (identifier.symbolId !== undefined) {
const scope = peekScope(context);
assert(scope.type !== ScopeType.Switch);

Expand Down Expand Up @@ -158,17 +158,15 @@ export function createLocalOrExportedOrGlobalDeclaration(
declaration = lua.createVariableDeclarationStatement(lhs, rhs, tsOriginal);
}

if (!context.options.noHoisting) {
// Remember local variable declarations for hoisting later
if (!scope.variableDeclarations) {
scope.variableDeclarations = [];
}
// Remember local variable declarations for hoisting later
if (!scope.variableDeclarations) {
scope.variableDeclarations = [];
}

scope.variableDeclarations.push(declaration);
scope.variableDeclarations.push(declaration);

if (scope.type === ScopeType.Switch) {
declaration = undefined;
}
if (scope.type === ScopeType.Switch) {
declaration = undefined;
}
} else if (rhs) {
// global
Expand All @@ -178,7 +176,7 @@ export function createLocalOrExportedOrGlobalDeclaration(
}
}

if (!context.options.noHoisting && isFunctionDeclaration) {
if (isFunctionDeclaration) {
// Remember function definitions for hoisting later
const functionSymbolId = (lhs as lua.Identifier).symbolId;
const scope = peekScope(context);
Expand Down
4 changes: 0 additions & 4 deletions src/transformation/utils/scope.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,10 +98,6 @@ export function popScope(context: TransformationContext): Scope {
}

export function performHoisting(context: TransformationContext, statements: lua.Statement[]): lua.Statement[] {
if (context.options.noHoisting) {
return statements;
}

const scope = peekScope(context);
let result = statements;
result = hoistFunctionDefinitions(context, scope, result);
Expand Down
10 changes: 0 additions & 10 deletions src/transformation/utils/symbols.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@ import * as ts from "typescript";
import * as lua from "../../LuaAST";
import { getOrUpdate } from "../../utils";
import { TransformationContext } from "../context";
import { ReferencedBeforeDeclaration } from "./errors";
import { markSymbolAsReferencedInCurrentScopes } from "./scope";
import { getFirstDeclarationInFile } from "./typescript";

const symbolIdCounters = new WeakMap<TransformationContext, number>();
function nextSymbolId(context: TransformationContext): lua.SymbolId {
Expand Down Expand Up @@ -46,14 +44,6 @@ export function trackSymbolReference(
symbolInfo.set(symbolId, { symbol, firstSeenAtPos: identifier.pos });
}

if (context.options.noHoisting) {
// Check for reference-before-declaration
const declaration = getFirstDeclarationInFile(symbol, context.sourceFile);
if (declaration && identifier.pos < declaration.pos) {
throw ReferencedBeforeDeclaration(identifier);
}
}

markSymbolAsReferencedInCurrentScopes(context, symbolId, identifier);

return symbolId;
Expand Down
2 changes: 1 addition & 1 deletion src/transformation/visitors/function.ts
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,7 @@ export const transformFunctionDeclaration: FunctionVisitor<ts.FunctionDeclaratio
}

// Remember symbols referenced in this function for hoisting later
if (!context.options.noHoisting && name.symbolId !== undefined) {
if (name.symbolId !== undefined) {
const scope = peekScope(context);
if (!scope.functionDefinitions) {
scope.functionDefinitions = new Map();
Expand Down
2 changes: 1 addition & 1 deletion src/transformation/visitors/modules/import.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ function transformImportSpecifier(
export const transformImportDeclaration: FunctionVisitor<ts.ImportDeclaration> = (statement, context) => {
const scope = peekScope(context);

if (!context.options.noHoisting && !scope.importStatements) {
if (!scope.importStatements) {
scope.importStatements = [];
}

Expand Down
8 changes: 2 additions & 6 deletions test/cli/parse.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,11 +82,11 @@ describe("command line", () => {
});

test("shouldn't parse following arguments as values", () => {
const result = tstl.parseCommandLine(["--noHeader", "--noHoisting"]);
const result = tstl.parseCommandLine(["--noHeader", "--noImplicitSelf"]);

expect(result.errors).not.toHaveDiagnostics();
expect(result.options.noHeader).toBe(true);
expect(result.options.noHoisting).toBe(true);
expect(result.options.noImplicitSelf).toBe(true);
});

test("shouldn't parse following files as values", () => {
Expand All @@ -101,8 +101,6 @@ describe("command line", () => {
test.each<[string, string, tstl.CompilerOptions]>([
["noHeader", "false", { noHeader: false }],
["noHeader", "true", { noHeader: true }],
["noHoisting", "false", { noHoisting: false }],
["noHoisting", "true", { noHoisting: true }],
["sourceMapTraceback", "false", { sourceMapTraceback: false }],
["sourceMapTraceback", "true", { sourceMapTraceback: true }],

Expand Down Expand Up @@ -209,8 +207,6 @@ describe("tsconfig", () => {
test.each<[string, any, tstl.CompilerOptions]>([
["noHeader", false, { noHeader: false }],
["noHeader", true, { noHeader: true }],
["noHoisting", false, { noHoisting: false }],
["noHoisting", true, { noHoisting: true }],
["sourceMapTraceback", false, { sourceMapTraceback: false }],
["sourceMapTraceback", true, { sourceMapTraceback: true }],

Expand Down
58 changes: 24 additions & 34 deletions test/unit/annotations/vararg.spec.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
import * as util from "../../util";

test.each([{}, { noHoisting: true }])("@vararg", compilerOptions => {
const code = `
/** @vararg */ type LuaVarArg<A extends unknown[]> = A & { __luaVarArg?: never };
const varargDeclaration = `
/** @vararg */
type LuaVarArg<A extends unknown[]> = A & { __luaVararg?: never };
`;

test("@vararg", () => {
util.testFunction`
${varargDeclaration}
function foo(a: unknown, ...b: LuaVarArg<unknown[]>) {
const c = [...b];
return c.join("");
Expand All @@ -11,45 +16,30 @@ test.each([{}, { noHoisting: true }])("@vararg", compilerOptions => {
return foo(a, ...b);
}
return bar("A", "B", "C", "D");
`;

const lua = util.transpileString(code, compilerOptions);
expect(lua).not.toMatch("b = ({...})");
expect(lua).not.toMatch("unpack");
expect(util.transpileAndExecute(code, compilerOptions)).toBe("BCD");
`
.tap(builder => expect(builder.getMainLuaCodeChunk()).not.toMatch("b = "))
.tap(builder => expect(builder.getMainLuaCodeChunk()).not.toMatch("unpack"))
.expectToMatchJsResult();
});

test.each([{}, { noHoisting: true }])("@vararg array access", compilerOptions => {
const code = `
/** @vararg */ type LuaVarArg<A extends unknown[]> = A & { __luaVarArg?: never };
test("@vararg array access", () => {
util.testFunction`
${varargDeclaration}
function foo(a: unknown, ...b: LuaVarArg<unknown[]>) {
const c = [...b];
return c.join("") + b[0];
}
return foo("A", "B", "C", "D");
`;

expect(util.transpileAndExecute(code, compilerOptions)).toBe("BCDB");
`.expectToMatchJsResult();
});

test.each([{}, { noHoisting: true }])("@vararg global", compilerOptions => {
const code = `
/** @vararg */ type LuaVarArg<A extends unknown[]> = A & { __luaVarArg?: never };
test("@vararg global", () => {
util.testModule`
${varargDeclaration}
declare const arg: LuaVarArg<string[]>;
const arr = [...arg];
const result = arr.join("");
`;

const luaBody = util.transpileString(code, compilerOptions, false);
expect(luaBody).not.toMatch("unpack");

const lua = `
function test(...)
${luaBody}
return result
end
return test("A", "B", "C", "D")
`;

expect(util.executeLua(lua)).toBe("ABCD");
export const result = [...arg].join("");
`
.setLuaFactory(code => `return (function(...) ${code} end)("A", "B", "C", "D")`)
.tap(builder => expect(builder.getMainLuaCodeChunk()).not.toMatch("unpack"))
.expectToEqual({ result: "ABCD" });
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we not just expectToMatchJsResult()? I think this setLuaFactory pattern is confusing, and don't really see the need for it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test tests module-level arguments, which is a lua-only concept

});
54 changes: 23 additions & 31 deletions test/unit/functions/functions.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -432,33 +432,29 @@ test("Function rest binding pattern", () => {
`.expectToMatchJsResult();
});

test.each([{}, { noHoisting: true }])("Function rest parameter", compilerOptions => {
const code = `
test("Function rest parameter", () => {
util.testFunction`
function foo(a: unknown, ...b: string[]) {
return b.join("");
}
return foo("A", "B", "C", "D");
`;

expect(util.transpileAndExecute(code, compilerOptions)).toBe("BCD");
`.expectToMatchJsResult();
});

test.each([{}, { noHoisting: true }])("Function nested rest parameter", compilerOptions => {
const code = `
test("Function nested rest parameter", () => {
util.testFunction`
function foo(a: unknown, ...b: string[]) {
function bar() {
return b.join("");
}
return bar();
}
return foo("A", "B", "C", "D");
`;

expect(util.transpileAndExecute(code, compilerOptions)).toBe("BCD");
`.expectToMatchJsResult();
});

test.each([{}, { noHoisting: true }])("Function nested rest spread", compilerOptions => {
const code = `
test("Function nested rest spread", () => {
util.testFunction`
function foo(a: unknown, ...b: string[]) {
function bar() {
const c = [...b];
Expand All @@ -467,40 +463,36 @@ test.each([{}, { noHoisting: true }])("Function nested rest spread", compilerOpt
return bar();
}
return foo("A", "B", "C", "D");
`;

expect(util.transpileAndExecute(code, compilerOptions)).toBe("BCD");
`.expectToMatchJsResult();
});

test.each([{}, { noHoisting: true }])("Function rest parameter (unreferenced)", compilerOptions => {
const code = `
test("Function rest parameter (unreferenced)", () => {
util.testFunction`
function foo(a: unknown, ...b: string[]) {
return "foobar";
}
return foo("A", "B", "C", "D");
`;

expect(util.transpileString(code, compilerOptions)).not.toMatch("b = ({...})");
expect(util.transpileAndExecute(code, compilerOptions)).toBe("foobar");
`
.tap(builder => expect(builder.getMainLuaCodeChunk()).not.toMatch("{...}"))
.expectToMatchJsResult();
});

test.each([{}, { noHoisting: true }])("Function rest parameter (referenced in property shorthand)", compilerOptions => {
const code = `
test("Function rest parameter (referenced in property shorthand)", () => {
util.testFunction`
function foo(a: unknown, ...b: string[]) {
const c = { b };
return c.b.join("");
}
return foo("A", "B", "C", "D");
`;

expect(util.transpileAndExecute(code, compilerOptions)).toBe("BCD");
`.expectToMatchJsResult();
});

test("named function expression reference", () => {
const code = `
const y = function x(inp: string) {
return inp + typeof x;
util.testFunction`
const y = function x() {
return { x: typeof x, y: typeof y };
};
return y("foo-");`;
expect(util.transpileAndExecute(code)).toBe("foo-function");

return y();
`.expectToMatchJsResult();
});
25 changes: 0 additions & 25 deletions test/unit/hoisting.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import * as ts from "typescript";
import { ReferencedBeforeDeclaration } from "../../src/transformation/utils/errors";
import * as util from "../util";

test.each(["let", "const"])("Let/Const Hoisting (%p)", varType => {
Expand Down Expand Up @@ -204,30 +203,6 @@ test("Enum Hoisting", () => {
expect(result).toBe("foo");
});

test.each([
{ code: `foo = "foo"; var foo;`, identifier: "foo" },
{ code: `foo = "foo"; export var foo;`, identifier: "foo" },
{ code: `function setBar() { const bar = foo; } let foo = "foo";`, identifier: "foo" },
{ code: `function setBar() { const bar = foo; } const foo = "foo";`, identifier: "foo" },
{ code: `function setBar() { const bar = foo; } export let foo = "foo";`, identifier: "foo" },
{ code: `function setBar() { const bar = foo; } export const foo = "foo";`, identifier: "foo" },
{ code: `const foo = bar(); function bar() { return "bar"; }`, identifier: "bar" },
{ code: `export const foo = bar(); function bar() { return "bar"; }`, identifier: "bar" },
{ code: `const foo = bar(); export function bar() { return "bar"; }`, identifier: "bar" },
{ code: `function bar() { return NS.foo; } namespace NS { export let foo = "foo"; }`, identifier: "NS" },
{
code: `export namespace O { export function f() { return I.foo; } namespace I { export let foo = "foo"; } }`,
identifier: "I",
},
{ code: `function makeFoo() { return new Foo(); } class Foo {}`, identifier: "Foo" },
{ code: `function bar() { return E.A; } enum E { A = "foo" }`, identifier: "E" },
{ code: `function setBar() { const bar = { foo }; } let foo = "foo";`, identifier: "foo" },
])("No Hoisting (%p)", ({ code, identifier }) => {
expect(() => util.transpileString(code, { noHoisting: true })).toThrowExactError(
ReferencedBeforeDeclaration(ts.createIdentifier(identifier))
);
});

test("Import hoisting (named)", () => {
util.testBundle`
export const result = foo;
Expand Down
Loading