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: 1 addition & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/dist
/coverage
/test/translation/transformation/characterEscapeSequence.ts
/test/translation/transformation/exportStatement.ts
/benchmark/dist
/test/transpile/module-resolution/**/node_modules
/test/transpile/module-resolution/**/dist
30 changes: 15 additions & 15 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
"node": ">=16.10.0"
},
"peerDependencies": {
"typescript": "5.5.2"
"typescript": "5.6.2"
},
"dependencies": {
"@typescript-to-lua/language-extensions": "1.19.0",
Expand All @@ -67,10 +67,10 @@
"jest-circus": "^29.5.0",
"lua-types": "^2.13.0",
"lua-wasm-bindings": "^0.3.1",
"prettier": "^2.8.4",
"prettier": "^2.8.8",
"ts-jest": "^29.1.0",
"ts-node": "^10.9.1",
"typescript": "^5.5.2",
"typescript": "^5.6.2",
"typescript-eslint": "^7.13.1"
}
}
88 changes: 50 additions & 38 deletions src/transformation/visitors/modules/export.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,12 @@ import * as ts from "typescript";
import * as lua from "../../../LuaAST";
import { assert } from "../../../utils";
import { FunctionVisitor, TransformationContext } from "../../context";
import {
createDefaultExportExpression,
createDefaultExportStringLiteral,
createExportedIdentifier,
} from "../../utils/export";
import { createDefaultExportExpression, createDefaultExportStringLiteral } from "../../utils/export";
import { createExportsIdentifier } from "../../utils/lua-ast";
import { ScopeType } from "../../utils/scope";
import { transformScopeBlock } from "../block";
import { transformIdentifier } from "../identifier";
import { createShorthandIdentifier } from "../literal";
import { createShorthandIdentifier, transformPropertyName } from "../literal";
import { createModuleRequire } from "./import";
import { createSafeName } from "../../utils/safe-names";
import * as path from "path";

export const transformExportAssignment: FunctionVisitor<ts.ExportAssignment> = (node, context) => {
if (!context.resolver.isValueAliasDeclaration(node)) {
Expand Down Expand Up @@ -101,20 +96,37 @@ function transformExportAll(context: TransformationContext, node: ts.ExportDecla
}

const isDefaultExportSpecifier = (node: ts.ExportSpecifier) =>
(node.name && ts.identifierToKeywordKind(node.name) === ts.SyntaxKind.DefaultKeyword) ||
(node.propertyName && ts.identifierToKeywordKind(node.propertyName) === ts.SyntaxKind.DefaultKeyword);
(node.name &&
ts.isIdentifier(node.name) &&
ts.identifierToKeywordKind(node.name) === ts.SyntaxKind.DefaultKeyword) ||
(node.propertyName &&
ts.isIdentifier(node.propertyName) &&
ts.identifierToKeywordKind(node.propertyName) === ts.SyntaxKind.DefaultKeyword);

function transformExportSpecifier(context: TransformationContext, node: ts.ExportSpecifier): lua.AssignmentStatement {
const exportedSymbol = context.checker.getExportSpecifierLocalTargetSymbol(node);
const exportedIdentifier = node.propertyName ? node.propertyName : node.name;
const exportedExpression = createShorthandIdentifier(context, exportedSymbol, exportedIdentifier);
const exportedName = node.name;
const exportedValue = node.propertyName ?? node.name;
let rhs: lua.Expression;
if (ts.isIdentifier(exportedValue)) {
const exportedSymbol = context.checker.getExportSpecifierLocalTargetSymbol(node);
rhs = createShorthandIdentifier(context, exportedSymbol, exportedValue);
} else {
rhs = lua.createStringLiteral(exportedName.text, exportedValue);
}

const isDefault = isDefaultExportSpecifier(node);
const exportAssignmentLeftHandSide = isDefault
? createDefaultExportExpression(node)
: createExportedIdentifier(context, transformIdentifier(context, node.name));
if (isDefaultExportSpecifier(node)) {
const lhs = createDefaultExportExpression(node);
return lua.createAssignmentStatement(lhs, rhs, node);
} else {
const exportsTable = createExportsIdentifier();
const lhs = lua.createTableIndexExpression(
exportsTable,
lua.createStringLiteral(exportedName.text),
exportedName
);

return lua.createAssignmentStatement(exportAssignmentLeftHandSide, exportedExpression, node);
return lua.createAssignmentStatement(lhs, rhs, node);
}
}

function transformExportSpecifiersFrom(
Expand All @@ -123,32 +135,32 @@ function transformExportSpecifiersFrom(
moduleSpecifier: ts.Expression,
exportSpecifiers: ts.ExportSpecifier[]
): lua.Statement {
// First transpile as import clause
const importClause = ts.factory.createImportClause(
false,
undefined,
ts.factory.createNamedImports(
exportSpecifiers.map(s => ts.factory.createImportSpecifier(statement.isTypeOnly, s.propertyName, s.name))
)
);
const result: lua.Statement[] = [];

const importDeclaration = ts.factory.createImportDeclaration(statement.modifiers, importClause, moduleSpecifier);
const importPath = ts.isStringLiteral(moduleSpecifier) ? moduleSpecifier.text.replace(/"/g, "") : "module";

// Wrap in block to prevent imports from hoisting out of `do` statement
const [block] = transformScopeBlock(context, ts.factory.createBlock([importDeclaration]), ScopeType.Block);
const result = block.statements;
// Create the require statement to extract values.
// local ____module = require("module")
const importUniqueName = lua.createIdentifier(createSafeName(path.basename(importPath)));
const requireCall = createModuleRequire(context, moduleSpecifier);
result.push(lua.createVariableDeclarationStatement(importUniqueName, requireCall, statement));

// Now the module is imported, add the imports to the export table
for (const specifier of exportSpecifiers) {
result.push(
lua.createAssignmentStatement(
createExportedIdentifier(context, transformIdentifier(context, specifier.name)),
transformIdentifier(context, specifier.name)
)
// Assign to exports table
const exportsTable = createExportsIdentifier();
const exportedName = specifier.name;
const exportedNameTransformed = transformPropertyName(context, exportedName);
const lhs = lua.createTableIndexExpression(exportsTable, exportedNameTransformed, exportedName);

const exportedValue = specifier.propertyName ?? specifier.name;
const rhs = lua.createTableIndexExpression(
lua.cloneIdentifier(importUniqueName),
transformPropertyName(context, exportedValue),
specifier
);
result.push(lua.createAssignmentStatement(lhs, rhs, specifier));
}

// Wrap this in a DoStatement to prevent polluting the scope.
return lua.createDoStatement(result, statement);
}

Expand Down
17 changes: 11 additions & 6 deletions test/translation/__snapshots__/transformation.spec.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -41,15 +41,20 @@ do
end
do
local ____xyz = require("xyz")
local abc = ____xyz.abc
local def = ____xyz.def
____exports.abc = abc
____exports.def = def
____exports.abc = ____xyz.abc
____exports.def = ____xyz.def
end
do
local ____xyz = require("xyz")
local def = ____xyz.abc
____exports.def = def
____exports.def = ____xyz.abc
end
do
local ____bla = require("bla")
____exports.bar = ____bla["123"]
end
do
local ____bla = require("bla")
____exports["123"] = ____bla.foo
end
return ____exports"
`;
Expand Down
2 changes: 2 additions & 0 deletions test/translation/transformation/exportStatement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,5 @@ export { xyz as uwv };
export * from "xyz";
export { abc, def } from "xyz";
export { abc as def } from "xyz";
export { "123" as bar } from "bla";
export { foo as "123" } from "bla";
5 changes: 4 additions & 1 deletion test/unit/conditionals.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,10 @@ test.each([
{ condition: 3, lhs: 4, rhs: 5 },
])("Ternary Conditional (%p)", ({ condition, lhs, rhs }) => {
util.testExpressionTemplate`${condition} ? ${lhs} : ${rhs}`
.ignoreDiagnostics([truthyOnlyConditionalValue.code])
.ignoreDiagnostics([
truthyOnlyConditionalValue.code,
2872 /* TS2872: This kind of expression is always truthy. */,
])
.expectToMatchJsResult();
});

Expand Down
1 change: 1 addition & 0 deletions test/unit/language-extensions/table.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,7 @@ describe("Table extensions use as expression", () => {
`
.withLanguageExtensions()
.setReturnExport("result")
.ignoreDiagnostics([2872 /* TS2872: This kind of expression is always truthy. */])
.expectToEqual(value);
});

Expand Down
18 changes: 14 additions & 4 deletions test/unit/nullishCoalescing.spec.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,26 @@
import * as util from "../util";

test.each(["null", "undefined"])("nullish-coalesing operator returns rhs", nullishValue => {
util.testExpression`${nullishValue} ?? "Hello, World!"`.expectToMatchJsResult();
util.testExpression`${nullishValue} ?? "Hello, World!"`
.ignoreDiagnostics([2871 /* TS2871: This expression is always nullish. */])
.expectToMatchJsResult();
});

test.each([3, "foo", {}, [], true, false])("nullish-coalesing operator returns lhs", value => {
util.testExpression`${util.formatCode(value)} ?? "Hello, World!"`.expectToMatchJsResult();
util.testExpression`${util.formatCode(value)} ?? "Hello, World!"`
.ignoreDiagnostics([
2869 /* TS2869: Right operand of ?? is unreachable because the left operand is never nullish. */,
])
.expectToMatchJsResult();
});

test.each(["any", "unknown"])("nullish-coalesing operator with any/unknown type", type => {
util.testFunction`
const unknownType = false as ${type};
return unknownType ?? "This should not be returned!";
`.expectToMatchJsResult();
`
.ignoreDiagnostics([2871 /* TS2871: This expression is always nullish. */])
.expectToMatchJsResult();
});

test.each(["boolean | string", "number | false", "undefined | true"])(
Expand All @@ -38,7 +46,9 @@ test("nullish-coalescing operator with side effect rhs", () => {
let i = 0;
const incI = () => ++i;
return [i, undefined ?? incI(), i];
`.expectToMatchJsResult();
`
.ignoreDiagnostics([2871 /* TS2871: This expression is always nullish. */])
.expectToMatchJsResult();
});

test("nullish-coalescing operator with vararg", () => {
Expand Down
12 changes: 10 additions & 2 deletions test/unit/precedingStatements.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -633,7 +633,11 @@ test("class member initializers", () => {
}
const inst = new MyClass();
return [inst.myField, inst.foo];
`.expectToMatchJsResult();
`
.ignoreDiagnostics([
2869 /* TS2869: Right operand of ?? is unreachable because the left operand is never nullish. */,
])
.expectToMatchJsResult();
});

test("class member initializers in extended class", () => {
Expand All @@ -644,5 +648,9 @@ test("class member initializers in extended class", () => {
}
const inst = new MyClass();
return inst.myField;
`.expectToMatchJsResult();
`
.ignoreDiagnostics([
2869 /* TS2869: Right operand of ?? is unreachable because the left operand is never nullish. */,
])
.expectToMatchJsResult();
});
1 change: 1 addition & 0 deletions test/unit/printer/semicolons.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ test.each(["const a = 1; const b = a;", "const a = 1; let b: number; b = a;", "{
(undefined || foo)();
return result;
`
.ignoreDiagnostics([2873 /* TS2873: This kind of expression is always falsy. */])
.expectToMatchJsResult()
.expectLuaToMatchSnapshot();
}
Expand Down