From 667365caa6711bec109c96bca3004a99bb7c0c04 Mon Sep 17 00:00:00 2001 From: GlassBricks <24237065+GlassBricks@users.noreply.github.com> Date: Sun, 2 Jan 2022 10:11:06 -0800 Subject: [PATCH] Fix function with properties assigned to variable declaration --- src/transformation/visitors/function.ts | 7 +++-- .../visitors/variable-declaration.ts | 18 ++++++++----- .../unit/functions/functionProperties.spec.ts | 27 +++++++++++++++++++ 3 files changed, 43 insertions(+), 9 deletions(-) diff --git a/src/transformation/visitors/function.ts b/src/transformation/visitors/function.ts index 12fab7d6e..9ced7089f 100644 --- a/src/transformation/visitors/function.ts +++ b/src/transformation/visitors/function.ts @@ -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 @@ -304,7 +305,9 @@ export function transformFunctionLikeDeclaration( } } - return functionExpression; + return isNamedFunctionExpression && isFunctionTypeWithProperties(context.checker.getTypeAtLocation(node)) + ? createCallableTable(functionExpression) + : functionExpression; } export const transformFunctionDeclaration: FunctionVisitor = (node, context) => { diff --git a/src/transformation/visitors/variable-declaration.ts b/src/transformation/visitors/variable-declaration.ts index e7e604404..9fd0f1f42 100644 --- a/src/transformation/visitors/variable-declaration.ts +++ b/src/transformation/visitors/variable-declaration.ts @@ -261,13 +261,7 @@ 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)) { @@ -275,6 +269,16 @@ export function transformVariableDeclaration( } 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 { diff --git a/test/unit/functions/functionProperties.spec.ts b/test/unit/functions/functionProperties.spec.ts index 85cdf9e31..16b61ab46 100644 --- a/test/unit/functions/functionProperties.spec.ts +++ b/test/unit/functions/functionProperties.spec.ts @@ -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; }; @@ -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(); +});