diff --git a/.prettierignore b/.prettierignore index 656f1a4d6..fb6d91319 100644 --- a/.prettierignore +++ b/.prettierignore @@ -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 diff --git a/package-lock.json b/package-lock.json index a5e8d5c45..0f6d42d97 100644 --- a/package-lock.json +++ b/package-lock.json @@ -34,17 +34,17 @@ "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" }, "engines": { "node": ">=16.10.0" }, "peerDependencies": { - "typescript": "5.5.2" + "typescript": "5.6.2" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -5693,9 +5693,9 @@ } }, "node_modules/prettier": { - "version": "2.8.4", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.4.tgz", - "integrity": "sha512-vIS4Rlc2FNh0BySk3Wkd6xmwxB0FpOndW5fisM5H8hsZSxU2VWVB5CWIkIjWvrHjIhxk2g3bfMKM87zNTrZddw==", + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", + "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", "dev": true, "bin": { "prettier": "bin-prettier.js" @@ -6575,9 +6575,9 @@ } }, "node_modules/typescript": { - "version": "5.5.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.2.tgz", - "integrity": "sha512-NcRtPEOsPFFWjobJEtfihkLCZCXZt/os3zf8nTxjVH3RvTSxjrCamJpbExGvYOF+tFHc3pA65qpdwPbzjohhew==", + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.2.tgz", + "integrity": "sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==", "dev": true, "bin": { "tsc": "bin/tsc", @@ -11295,9 +11295,9 @@ "dev": true }, "prettier": { - "version": "2.8.4", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.4.tgz", - "integrity": "sha512-vIS4Rlc2FNh0BySk3Wkd6xmwxB0FpOndW5fisM5H8hsZSxU2VWVB5CWIkIjWvrHjIhxk2g3bfMKM87zNTrZddw==", + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", + "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", "dev": true }, "pretty-format": { @@ -11881,9 +11881,9 @@ } }, "typescript": { - "version": "5.5.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.2.tgz", - "integrity": "sha512-NcRtPEOsPFFWjobJEtfihkLCZCXZt/os3zf8nTxjVH3RvTSxjrCamJpbExGvYOF+tFHc3pA65qpdwPbzjohhew==", + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.2.tgz", + "integrity": "sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==", "dev": true }, "typescript-eslint": { diff --git a/package.json b/package.json index efe35e875..231eb7a50 100644 --- a/package.json +++ b/package.json @@ -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", @@ -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" } } diff --git a/src/transformation/visitors/modules/export.ts b/src/transformation/visitors/modules/export.ts index 6908a8727..c814b8257 100644 --- a/src/transformation/visitors/modules/export.ts +++ b/src/transformation/visitors/modules/export.ts @@ -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 = (node, context) => { if (!context.resolver.isValueAliasDeclaration(node)) { @@ -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( @@ -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); } diff --git a/test/translation/__snapshots__/transformation.spec.ts.snap b/test/translation/__snapshots__/transformation.spec.ts.snap index 2832f2cd6..b0c7eea52 100644 --- a/test/translation/__snapshots__/transformation.spec.ts.snap +++ b/test/translation/__snapshots__/transformation.spec.ts.snap @@ -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" `; diff --git a/test/translation/transformation/exportStatement.ts b/test/translation/transformation/exportStatement.ts index 7aa965422..cf227da3d 100644 --- a/test/translation/transformation/exportStatement.ts +++ b/test/translation/transformation/exportStatement.ts @@ -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"; diff --git a/test/unit/conditionals.spec.ts b/test/unit/conditionals.spec.ts index 5508748bc..f862ca8a3 100644 --- a/test/unit/conditionals.spec.ts +++ b/test/unit/conditionals.spec.ts @@ -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(); }); diff --git a/test/unit/language-extensions/table.spec.ts b/test/unit/language-extensions/table.spec.ts index 40e46d61e..13af766fa 100644 --- a/test/unit/language-extensions/table.spec.ts +++ b/test/unit/language-extensions/table.spec.ts @@ -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); }); diff --git a/test/unit/nullishCoalescing.spec.ts b/test/unit/nullishCoalescing.spec.ts index 3728f2815..56db55332 100644 --- a/test/unit/nullishCoalescing.spec.ts +++ b/test/unit/nullishCoalescing.spec.ts @@ -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"])( @@ -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", () => { diff --git a/test/unit/precedingStatements.spec.ts b/test/unit/precedingStatements.spec.ts index f27d26494..1ce24da19 100644 --- a/test/unit/precedingStatements.spec.ts +++ b/test/unit/precedingStatements.spec.ts @@ -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", () => { @@ -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(); }); diff --git a/test/unit/printer/semicolons.spec.ts b/test/unit/printer/semicolons.spec.ts index addb66e53..7be461d0f 100644 --- a/test/unit/printer/semicolons.spec.ts +++ b/test/unit/printer/semicolons.spec.ts @@ -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(); }