Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
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 src/transformation/utils/annotations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export enum AnnotationKind {
CompileMembersOnly = "compileMembersOnly",
NoResolution = "noResolution",
NoSelf = "noSelf",
CustomName = "customName",
NoSelfInFile = "noSelfInFile",
}

Expand Down
17 changes: 11 additions & 6 deletions src/transformation/visitors/call.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { transformInPrecedingStatementScope } from "../utils/preceding-statement
import { getOptionalContinuationData, transformOptionalChain } from "./optional-chaining";
import { transformImportExpression } from "./modules/import";
import { transformLanguageExtensionCallExpression } from "./language-extensions/call-extension";
import { getCustomNameFromSymbol } from "./identifier";

export function validateArguments(
context: TransformationContext,
Expand Down Expand Up @@ -135,12 +136,16 @@ export function transformContextualCallExpression(
) {
// table:name()
const table = context.transformExpression(left.expression);
return lua.createMethodCallExpression(
table,
lua.createIdentifier(left.name.text, left.name),
transformedArguments,
node
);
let name = left.name.text;

const symbol = context.checker.getSymbolAtLocation(left);
const customName = getCustomNameFromSymbol(symbol);

if (customName) {
name = customName;
}

return lua.createMethodCallExpression(table, lua.createIdentifier(name, left.name), transformedArguments, node);
} else if (ts.isElementAccessExpression(left) || ts.isPropertyAccessExpression(left)) {
if (isExpressionWithEvaluationEffect(left.expression)) {
return transformElementAccessCall(context, left, transformedArguments, argPrecedingStatements);
Expand Down
34 changes: 31 additions & 3 deletions src/transformation/visitors/identifier.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,38 @@ import { isStandardLibraryType } from "../utils/typescript";
import { getExtensionKindForNode, getExtensionKindForSymbol } from "../utils/language-extensions";
import { callExtensions } from "./language-extensions/call-extension";
import { isIdentifierExtensionValue, reportInvalidExtensionValue } from "./language-extensions/identifier";
import { Annotation, AnnotationKind, getNodeAnnotations } from "../utils/annotations";

export function transformIdentifier(context: TransformationContext, identifier: ts.Identifier): lua.Identifier {
return transformNonValueIdentifier(context, identifier, context.checker.getSymbolAtLocation(identifier));
}

export function getCustomNameFromSymbol(symbol?: ts.Symbol): undefined | string {
let retVal: undefined | string;

if (symbol) {
const declarations = symbol.getDeclarations();
if (declarations) {
let customNameAnnotation: undefined | Annotation = undefined;
for (const declaration of declarations) {
const nodeAnnotations = getNodeAnnotations(declaration);
const foundAnnotation = nodeAnnotations.get(AnnotationKind.CustomName);

if (foundAnnotation) {
customNameAnnotation = foundAnnotation;
break;
}
}

if (customNameAnnotation) {
retVal = customNameAnnotation.args[0];
}
}
}

return retVal;
}

function transformNonValueIdentifier(
context: TransformationContext,
identifier: ts.Identifier,
Expand Down Expand Up @@ -53,9 +80,10 @@ function transformNonValueIdentifier(
}
}

const text = hasUnsafeIdentifierName(context, identifier, symbol)
? createSafeName(identifier.text)
: identifier.text;
let text = hasUnsafeIdentifierName(context, identifier, symbol) ? createSafeName(identifier.text) : identifier.text;

const customName = getCustomNameFromSymbol(symbol);
if (customName) text = customName;

const symbolId = getIdentifierSymbolId(context, identifier, symbol);
return lua.createIdentifier(text, identifier, symbolId, identifier.text);
Expand Down
126 changes: 126 additions & 0 deletions test/unit/identifiers.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -845,3 +845,129 @@ test("lua built-in as in constructor assignment", () => {
export const result = new A("42").error;
`.expectToMatchJsResult();
});

test("customName rename function", () => {
const result = util.testModule`
/** @customName test2 **/
function test(this: void): number { return 3; }
export const result: number = test();
`
.expectToEqual({ result: 3 })
.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("function test2()");
expect(mainFile.lua).not.toContain("test()");
});

test("customName rename variable", () => {
const result = util.testModule`
/** @customName result2 **/
export const result: number = 3;
`
.expectToEqual({ result2: 3 })
.getLuaResult();

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("result2 =");
expect(mainFile.lua).not.toContain("result =");
});

test("customName rename classes", () => {
const testModule = util.testModule`
/** @customName Class2 **/
class Class {
test: string;

constructor(test: string) {
this.test = test;
}
}

export const result = new Class("hello world");
`;

const executionResult = testModule.getLuaExecutionResult();
expect(executionResult.result).toBeDefined();
expect(executionResult.result).toMatchObject({ test: "hello world" });

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("local Class2 =");
expect(mainFile.lua).not.toContain("local Class =");
});

test("customName rename namespace", () => {
const testModule = util.testModule`
/** @customName Test2 **/
namespace Test {
/** @customName Func2 **/
export function Func(): string {
return "hi";
}
}

export const result = Test.Func();
`;

testModule.expectToEqual({ result: "hi" });

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("Test2.Func2");
expect(mainFile.lua).not.toContain("Test =");
expect(mainFile.lua).not.toContain("Func(");
});

test("customName rename declared function", () => {
const testModule = util.testModule`
/** @customName Test2 **/
declare function Test(this: void): void;

Test();
`;

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).not.toContain("Test(");
});