X Tutup
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
7 changes: 5 additions & 2 deletions src/transformation/visitors/function.ts
Original file line number Diff line number Diff line change
Expand Up @@ -277,8 +277,9 @@ export function transformFunctionLikeDeclaration(

const [functionExpression, functionScope] = transformFunctionToExpression(context, node);

const isNamedFunctionExpression = ts.isFunctionExpression(node) && node.name;
// Handle named function expressions which reference themselves
if (ts.isFunctionExpression(node) && node.name && functionScope.referencedSymbols) {
if (isNamedFunctionExpression && functionScope.referencedSymbols) {
const symbol = context.checker.getSymbolAtLocation(node.name);
if (symbol) {
// TODO: Not using symbol ids because of https://github.com/microsoft/TypeScript/issues/37131
Expand All @@ -304,7 +305,9 @@ export function transformFunctionLikeDeclaration(
}
}

return functionExpression;
return isNamedFunctionExpression && isFunctionTypeWithProperties(context.checker.getTypeAtLocation(node))
? createCallableTable(functionExpression)
: functionExpression;
}

export const transformFunctionDeclaration: FunctionVisitor<ts.FunctionDeclaration> = (node, context) => {
Expand Down
18 changes: 11 additions & 7 deletions src/transformation/visitors/variable-declaration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -261,20 +261,24 @@ export function transformVariableDeclaration(

// Wrap functions being assigned to a type that contains additional properties in a callable table
// This catches 'const foo = function() {}; foo.bar = "FOOBAR";'
const wrappedValue =
value &&
// Skip named function expressions because they will have been wrapped already
!(statement.initializer && ts.isFunctionExpression(statement.initializer) && statement.initializer.name) &&
isFunctionTypeWithProperties(context.checker.getTypeAtLocation(statement.name))
? createCallableTable(value)
: value;
const wrappedValue = value && shouldWrapInitializerInCallableTable() ? createCallableTable(value) : value;

return createLocalOrExportedOrGlobalDeclaration(context, identifierName, wrappedValue, statement);
} else if (ts.isArrayBindingPattern(statement.name) || ts.isObjectBindingPattern(statement.name)) {
return transformBindingVariableDeclaration(context, statement.name, statement.initializer);
} else {
return assertNever(statement.name);
}

function shouldWrapInitializerInCallableTable() {
assert(statement.initializer);
const initializer = ts.skipOuterExpressions(statement.initializer);
// do not wrap if not a function expression
if (!ts.isFunctionExpression(initializer) && !ts.isArrowFunction(initializer)) return false;
// Skip named function expressions because they will have been wrapped already
if (ts.isFunctionExpression(initializer) && initializer.name) return false;
return isFunctionTypeWithProperties(context.checker.getTypeAtLocation(statement.name));
}
}

export function checkVariableDeclarationList(context: TransformationContext, node: ts.VariableDeclarationList): void {
Expand Down
27 changes: 27 additions & 0 deletions test/unit/functions/functionProperties.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,14 @@ test("void function with property assigned to variable", () => {
`.expectToMatchJsResult();
});

test("named function with property assigned to variable", () => {
util.testFunction`
const foo = function baz(s: string) { return s; }
foo.bar = "bar";
return foo("foo") + foo.bar;
`.expectToMatchJsResult();
});

test("recursively referenced function with property assigned to variable", () => {
util.testFunction`
const foo = function(s: string) { return s + foo.bar; };
Expand Down Expand Up @@ -173,3 +181,22 @@ test("call function with property using bind method", () => {
return foo.bind("foo", "bar")() + foo.baz;
`.expectToMatchJsResult();
});

// https://github.com/TypeScriptToLua/TypeScriptToLua/issues/1196
test("Does not wrap simple variable declaration", () => {
util.testFunction`
function foo(s: string) { return s; }
foo.bar = "bar";
const foo2 = foo;
return foo2("foo") + foo2.bar;
`.expectToMatchJsResult();
});

test("Wraps function in inner expression", () => {
util.testFunction`
type Foo = { (s: string): string; bar: string; }
const foo = (s => s)! as Foo
foo.bar = "bar";
return foo("foo") + foo.bar;
`.expectToMatchJsResult();
});
X Tutup