diff --git a/src/transformation/visitors/access.ts b/src/transformation/visitors/access.ts index c97c0f4c7..7c96bdcd8 100644 --- a/src/transformation/visitors/access.ts +++ b/src/transformation/visitors/access.ts @@ -115,7 +115,7 @@ export function transformPropertyAccessExpressionWithCapture( let property = node.name.text; const symbol = context.checker.getSymbolAtLocation(node.name); - const customName = getCustomNameFromSymbol(symbol); + const customName = getCustomNameFromSymbol(context, symbol); if (customName) { property = customName; } diff --git a/src/transformation/visitors/call.ts b/src/transformation/visitors/call.ts index d37561910..c4a74135c 100644 --- a/src/transformation/visitors/call.ts +++ b/src/transformation/visitors/call.ts @@ -138,7 +138,7 @@ export function transformContextualCallExpression( let name = left.name.text; const symbol = context.checker.getSymbolAtLocation(left); - const customName = getCustomNameFromSymbol(symbol); + const customName = getCustomNameFromSymbol(context, symbol); if (customName) { name = customName; diff --git a/src/transformation/visitors/identifier.ts b/src/transformation/visitors/identifier.ts index e5dd90e01..3b2e016e4 100644 --- a/src/transformation/visitors/identifier.ts +++ b/src/transformation/visitors/identifier.ts @@ -18,7 +18,7 @@ export function transformIdentifier(context: TransformationContext, identifier: return transformNonValueIdentifier(context, identifier, context.checker.getSymbolAtLocation(identifier)); } -export function getCustomNameFromSymbol(symbol?: ts.Symbol): undefined | string { +export function getCustomNameFromSymbol(context: TransformationContext, symbol?: ts.Symbol): undefined | string { let retVal: undefined | string; if (symbol) { @@ -33,6 +33,18 @@ export function getCustomNameFromSymbol(symbol?: ts.Symbol): undefined | string customNameAnnotation = foundAnnotation; break; } + + // If the symbol is an imported value, check the original declaration + // beware of declaration.propertyName, this is the import name alias and should not be renamed! + if (ts.isImportSpecifier(declaration) && !declaration.propertyName) { + const importedType = context.checker.getTypeAtLocation(declaration); + if (importedType.symbol) { + const importedCustomName = getCustomNameFromSymbol(context, importedType.symbol); + if (importedCustomName) { + return importedCustomName; + } + } + } } if (customNameAnnotation) { @@ -82,7 +94,7 @@ function transformNonValueIdentifier( let text = hasUnsafeIdentifierName(context, identifier, symbol) ? createSafeName(identifier.text) : identifier.text; - const customName = getCustomNameFromSymbol(symbol); + const customName = getCustomNameFromSymbol(context, symbol); if (customName) text = customName; const symbolId = getIdentifierSymbolId(context, identifier, symbol); diff --git a/src/transformation/visitors/modules/import.ts b/src/transformation/visitors/modules/import.ts index b3a1d74a2..1b561f0fa 100644 --- a/src/transformation/visitors/modules/import.ts +++ b/src/transformation/visitors/modules/import.ts @@ -9,7 +9,7 @@ import { createHoistableVariableDeclarationStatement } from "../../utils/lua-ast import { importLuaLibFeature, LuaLibFeature } from "../../utils/lualib"; import { createSafeName } from "../../utils/safe-names"; import { peekScope } from "../../utils/scope"; -import { transformIdentifier } from "../identifier"; +import { getCustomNameFromSymbol, transformIdentifier } from "../identifier"; import { transformPropertyName } from "../literal"; function isNoResolutionPath(context: TransformationContext, moduleSpecifier: ts.Expression): boolean { @@ -46,8 +46,15 @@ function transformImportSpecifier( importSpecifier: ts.ImportSpecifier, moduleTableName: lua.Identifier ): lua.VariableDeclarationStatement { + const type = context.checker.getTypeAtLocation(importSpecifier.name); + const leftIdentifier = transformIdentifier(context, importSpecifier.name); - const propertyName = transformPropertyName(context, importSpecifier.propertyName ?? importSpecifier.name); + + // If imported value has a customName annotation use that, otherwise use regular property + const customName = getCustomNameFromSymbol(context, type.getSymbol()); + const propertyName = customName + ? lua.createStringLiteral(customName, importSpecifier.propertyName ?? importSpecifier.name) + : transformPropertyName(context, importSpecifier.propertyName ?? importSpecifier.name); return lua.createVariableDeclarationStatement( leftIdentifier, diff --git a/test/unit/identifiers.spec.ts b/test/unit/identifiers.spec.ts index 4156e83ff..f148c3c72 100644 --- a/test/unit/identifiers.spec.ts +++ b/test/unit/identifiers.spec.ts @@ -991,3 +991,67 @@ test("customName rename declared function", () => { expect(mainFile.lua).toContain("Test2("); expect(mainFile.lua).not.toContain("Test("); }); + +test("customName rename import specifier", () => { + const testModule = util.testModule` + import { Test } from "./myimport"; + import { Test as Aliased } from "./myimport"; + Test(); + Aliased(); + `.addExtraFile( + "myimport.ts", + ` + /** @customName Test2 **/ + export function Test(this: void): void {} + ` + ); + + testModule.expectToHaveNoDiagnostics(); + const result = testModule.getLuaResult(); + expect(result.transpiledFiles).not.toHaveLength(0); + + const mainFile = result.transpiledFiles.find(f => f.outPath === "main.lua"); + expect(mainFile).toBeDefined(); + + // avoid ts error "not defined", even though toBeDefined is being checked above + if (!mainFile) return; + + expect(mainFile.lua).toBeDefined(); + expect(mainFile.lua).toContain("Test2("); + expect(mainFile.lua).toContain("myimport.Test2"); + expect(mainFile.lua).not.toContain("Test("); + + testModule.expectNoExecutionError(); +}); + +test("customName import specifier from declarations", () => { + const testModule = util.testModule` + import { Test } from "./myimport"; + import { Test as Aliased } from "./myimport"; + Test(); + Aliased(); + ` + .addExtraFile( + "myimport.d.ts", + ` + /** @customName Test2 **/ + export declare function Test(this: void): void; + ` + ) + .setOptions({ noResolvePaths: ["./myimport"] }); + + testModule.expectToHaveNoDiagnostics(); + const result = testModule.getLuaResult(); + expect(result.transpiledFiles).not.toHaveLength(0); + + const mainFile = result.transpiledFiles.find(f => f.outPath === "main.lua"); + expect(mainFile).toBeDefined(); + + // avoid ts error "not defined", even though toBeDefined is being checked above + if (!mainFile) return; + + expect(mainFile.lua).toBeDefined(); + expect(mainFile.lua).toContain("Test2("); + expect(mainFile.lua).toContain("myimport.Test2"); + expect(mainFile.lua).not.toContain("Test("); +});