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
2 changes: 1 addition & 1 deletion src/compiler/binder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down
8 changes: 6 additions & 2 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
12 changes: 12 additions & 0 deletions src/compiler/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
17 changes: 17 additions & 0 deletions tests/cases/fourslash/salsaMethodsOnAssignedFunctionExpressions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/// <reference path="fourslash.ts" />
// @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.');