From 642583771d5f3a3175b5bfd93b9b79ce70dc0938 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Fri, 10 Jun 2016 14:10:30 -0700 Subject: [PATCH 1/2] Salsa: get members of variables whose initialisers are functions --- src/compiler/binder.ts | 2 +- src/compiler/checker.ts | 8 ++++++-- src/compiler/utilities.ts | 12 ++++++++++++ 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index db956cca647bc..bfe7ee31eb248 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -1945,7 +1945,7 @@ namespace ts { classPrototype.parent = leftSideOfAssignment; const funcSymbol = container.locals[constructorFunction.text]; - if (!funcSymbol || !(funcSymbol.flags & SymbolFlags.Function)) { + if (!funcSymbol || !(funcSymbol.flags & SymbolFlags.Function || isDeclarationOfFunctionExpression(funcSymbol))) { return; } diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 044f42414b75e..e3a29cce258f6 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -11481,8 +11481,12 @@ namespace ts { // When resolved signature is a call signature (and not a construct signature) the result type is any, unless // the declaring function had members created through 'x.prototype.y = expr' or 'this.y = expr' psuedodeclarations // in a JS file - const funcSymbol = checkExpression(node.expression).symbol; - if (funcSymbol && funcSymbol.members && (funcSymbol.flags & SymbolFlags.Function)) { + // Note:JS inferred classes might come from a variable declaration instead of a function declaration. + // In this case, using getResolvedSymbol directly is required to avoid losing the members from the declaration. + const funcSymbol = node.expression.kind === SyntaxKind.Identifier ? + getResolvedSymbol(node.expression as Identifier) : + checkExpression(node.expression).symbol; + if (funcSymbol && funcSymbol.members && (funcSymbol.flags & SymbolFlags.Function || isDeclarationOfFunctionExpression(funcSymbol))) { return getInferredClassType(funcSymbol); } else if (compilerOptions.noImplicitAny) { diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index c54775ad2af4a..04ea50f4e0c99 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -1257,6 +1257,18 @@ namespace ts { return charCode === CharacterCodes.singleQuote || charCode === CharacterCodes.doubleQuote; } + /** + * Returns true if the node is a variable declaration whose initializer is a function expression. + * This function does not test if the node is in a JavaScript file or not. + */ + export function isDeclarationOfFunctionExpression(s: Symbol) { + if (s.valueDeclaration && s.valueDeclaration.kind === SyntaxKind.VariableDeclaration) { + const declaration = s.valueDeclaration as VariableDeclaration; + return declaration.initializer && declaration.initializer.kind === SyntaxKind.FunctionExpression; + } + return false; + } + /// Given a BinaryExpression, returns SpecialPropertyAssignmentKind for the various kinds of property /// assignments we treat as special in the binder export function getSpecialPropertyAssignmentKind(expression: Node): SpecialPropertyAssignmentKind { From 4a9b1209aeffb33ddbc6c7358e2a0e901cb8eee9 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Fri, 10 Jun 2016 14:11:31 -0700 Subject: [PATCH 2/2] Test adding members to JS variables whose initialisers are functions --- ...salsaMethodsOnAssignedFunctionExpressions.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 tests/cases/fourslash/salsaMethodsOnAssignedFunctionExpressions.ts diff --git a/tests/cases/fourslash/salsaMethodsOnAssignedFunctionExpressions.ts b/tests/cases/fourslash/salsaMethodsOnAssignedFunctionExpressions.ts new file mode 100644 index 0000000000000..a73207c01d22e --- /dev/null +++ b/tests/cases/fourslash/salsaMethodsOnAssignedFunctionExpressions.ts @@ -0,0 +1,17 @@ +/// +// @allowJs: true +// @Filename: something.js +////var C = function () { } +/////** +//// * The prototype method. +//// * @param {string} a Parameter definition. +//// */ +////function f(a) {} +////C.prototype.m = f; +//// +////var x = new C(); +////x/*1*/./*2*/m(); +goTo.marker('1'); +verify.quickInfoIs('var x: {\n m: (a: string) => void;\n}'); +goTo.marker('2'); +verify.completionListContains('m', '(property) C.m: (a: string) => void', 'The prototype method.');