diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 3490ca961c5..cfd83efeb31 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -2560,6 +2560,22 @@ namespace ts { return initializer || decl; } + /** + * Get the real symbol of a declaration with an expando initializer. + * + * Normally, declarations have an associated symbol, but when a declaration has an expando + * initializer, the expando's symbol is the one that has all the members merged into it. + */ + function getExpandoSymbol(symbol: Symbol): Symbol | undefined { + const decl = symbol.valueDeclaration; + if (!decl || !isInJSFile(decl) || symbol.flags & SymbolFlags.TypeAlias) { + return undefined; + } + const init = isVariableDeclaration(decl) ? getDeclaredExpandoInitializer(decl) : getAssignedExpandoInitializer(decl); + return init && getSymbolOfNode(init) || undefined; + } + + function resolveExternalModuleName(location: Node, moduleReferenceExpression: Expression, ignoreErrors?: boolean): Symbol | undefined { return resolveExternalModuleNameWorker(location, moduleReferenceExpression, ignoreErrors ? undefined : Diagnostics.Cannot_find_module_0); } @@ -9195,10 +9211,13 @@ namespace ts { if (symbol === unknownSymbol) { return errorType; } + symbol = getExpandoSymbol(symbol) || symbol; - const type = getTypeReferenceTypeWorker(node, symbol, typeArguments); - if (type) { - return type; + if (symbol.flags & (SymbolFlags.Class | SymbolFlags.Interface)) { + return getTypeFromClassOrInterfaceReference(node, symbol, typeArguments); + } + if (symbol.flags & SymbolFlags.TypeAlias) { + return getTypeFromTypeAliasReference(node, symbol, typeArguments); } // Get type from reference to named type that cannot be generic (enum or type parameter) @@ -9209,62 +9228,34 @@ namespace ts { errorType; } - if (!(symbol.flags & SymbolFlags.Value && isJSDocTypeReference(node))) { - return errorType; + if (symbol.flags & SymbolFlags.Value && isJSDocTypeReference(node)) { + const jsdocType = getTypeFromJSAlias(node, symbol); + if (jsdocType) { + return jsdocType; + } + else { + // Resolve the type reference as a Type for the purpose of reporting errors. + resolveTypeReferenceName(getTypeReferenceName(node), SymbolFlags.Type); + return getTypeOfSymbol(symbol); + } } - const jsdocType = getJSDocTypeReference(node, symbol, typeArguments); - if (jsdocType) { - return jsdocType; - } - - // Resolve the type reference as a Type for the purpose of reporting errors. - resolveTypeReferenceName(getTypeReferenceName(node), SymbolFlags.Type); - return getTypeOfSymbol(symbol); + return errorType; } /** - * A jsdoc TypeReference may have resolved to a value (as opposed to a type). If - * the symbol is a constructor function, return the inferred class type; otherwise, - * the type of this reference is just the type of the value we resolved to. + * A JSdoc TypeReference may be to a value imported from commonjs. + * These should really be aliases, but this special-case code fakes alias resolution + * by producing a type from a value. */ - function getJSDocTypeReference(node: NodeWithTypeArguments, symbol: Symbol, typeArguments: Type[] | undefined): Type | undefined { - // In the case of an assignment of a function expression (binary expressions, variable declarations, etc.), we will get the - // correct instance type for the symbol on the LHS by finding the type for RHS. For example if we want to get the type of the symbol `foo`: - // var foo = function() {} - // We will find the static type of the assigned anonymous function. - const staticType = getTypeOfSymbol(symbol); - const instanceType = - staticType.symbol && - staticType.symbol !== symbol && // Make sure this is an assignment like expression by checking that symbol -> type -> symbol doesn't roundtrips. - getTypeReferenceTypeWorker(node, staticType.symbol, typeArguments); // Get the instance type of the RHS symbol. - if (instanceType) { - return getSymbolLinks(symbol).resolvedJSDocType = instanceType; - } - } - - function getTypeReferenceTypeWorker(node: NodeWithTypeArguments, symbol: Symbol, typeArguments: Type[] | undefined): Type | undefined { - if (symbol.flags & (SymbolFlags.Class | SymbolFlags.Interface)) { - if (symbol.valueDeclaration && symbol.valueDeclaration.parent && isBinaryExpression(symbol.valueDeclaration.parent)) { - const jsdocType = getJSDocTypeReference(node, symbol, typeArguments); - if (jsdocType) { - return jsdocType; - } - } - return getTypeFromClassOrInterfaceReference(node, symbol, typeArguments); - } - - if (symbol.flags & SymbolFlags.TypeAlias) { - return getTypeFromTypeAliasReference(node, symbol, typeArguments); - } - - if (symbol.flags & SymbolFlags.Function && - isJSDocTypeReference(node) && - isJSConstructor(symbol.valueDeclaration)) { - const resolved = resolveStructuredTypeMembers(getTypeOfSymbol(symbol)); - if (resolved.callSignatures.length === 1) { - return getReturnTypeOfSignature(resolved.callSignatures[0]); - } + function getTypeFromJSAlias(node: NodeWithTypeArguments, symbol: Symbol): Type | undefined { + const valueType = getTypeOfSymbol(symbol); + const typeType = + valueType.symbol && + valueType.symbol !== symbol && // Make sure this is a commonjs export by checking that symbol -> type -> symbol doesn't roundtrip. + getTypeReferenceType(node, valueType.symbol); + if (typeType) { + return getSymbolLinks(symbol).resolvedJSDocType = typeType; } } diff --git a/tests/baselines/reference/jsdocConstructorFunctionTypeReference.symbols b/tests/baselines/reference/jsdocConstructorFunctionTypeReference.symbols new file mode 100644 index 00000000000..f4e58505c2e --- /dev/null +++ b/tests/baselines/reference/jsdocConstructorFunctionTypeReference.symbols @@ -0,0 +1,31 @@ +=== tests/cases/conformance/salsa/jsdocConstructorFunctionTypeReference.js === +var Validator = function VFunc() { +>Validator : Symbol(Validator, Decl(jsdocConstructorFunctionTypeReference.js, 0, 3)) +>VFunc : Symbol(VFunc, Decl(jsdocConstructorFunctionTypeReference.js, 0, 15)) + + this.flags = "gim" +>flags : Symbol(VFunc.flags, Decl(jsdocConstructorFunctionTypeReference.js, 0, 34)) + +}; + +Validator.prototype.num = 12 +>Validator.prototype : Symbol(Validator.num, Decl(jsdocConstructorFunctionTypeReference.js, 2, 2)) +>Validator : Symbol(Validator, Decl(jsdocConstructorFunctionTypeReference.js, 0, 3)) +>prototype : Symbol(Function.prototype, Decl(lib.es5.d.ts, --, --)) +>num : Symbol(Validator.num, Decl(jsdocConstructorFunctionTypeReference.js, 2, 2)) + +/** + * @param {Validator} state + */ +var validateRegExpFlags = function(state) { +>validateRegExpFlags : Symbol(validateRegExpFlags, Decl(jsdocConstructorFunctionTypeReference.js, 9, 3)) +>state : Symbol(state, Decl(jsdocConstructorFunctionTypeReference.js, 9, 35)) + + return state.flags +>state.flags : Symbol(VFunc.flags, Decl(jsdocConstructorFunctionTypeReference.js, 0, 34)) +>state : Symbol(state, Decl(jsdocConstructorFunctionTypeReference.js, 9, 35)) +>flags : Symbol(VFunc.flags, Decl(jsdocConstructorFunctionTypeReference.js, 0, 34)) + +}; + + diff --git a/tests/baselines/reference/jsdocConstructorFunctionTypeReference.types b/tests/baselines/reference/jsdocConstructorFunctionTypeReference.types new file mode 100644 index 00000000000..b536ad26ded --- /dev/null +++ b/tests/baselines/reference/jsdocConstructorFunctionTypeReference.types @@ -0,0 +1,40 @@ +=== tests/cases/conformance/salsa/jsdocConstructorFunctionTypeReference.js === +var Validator = function VFunc() { +>Validator : typeof VFunc +>function VFunc() { this.flags = "gim"} : typeof VFunc +>VFunc : typeof VFunc + + this.flags = "gim" +>this.flags = "gim" : "gim" +>this.flags : any +>this : any +>flags : any +>"gim" : "gim" + +}; + +Validator.prototype.num = 12 +>Validator.prototype.num = 12 : 12 +>Validator.prototype.num : any +>Validator.prototype : any +>Validator : typeof VFunc +>prototype : any +>num : any +>12 : 12 + +/** + * @param {Validator} state + */ +var validateRegExpFlags = function(state) { +>validateRegExpFlags : (state: VFunc) => string +>function(state) { return state.flags} : (state: VFunc) => string +>state : VFunc + + return state.flags +>state.flags : string +>state : VFunc +>flags : string + +}; + + diff --git a/tests/cases/conformance/salsa/jsdocConstructorFunctionTypeReference.ts b/tests/cases/conformance/salsa/jsdocConstructorFunctionTypeReference.ts new file mode 100644 index 00000000000..46f2667c6e1 --- /dev/null +++ b/tests/cases/conformance/salsa/jsdocConstructorFunctionTypeReference.ts @@ -0,0 +1,19 @@ +// @noEmit: true +// @allowJs: true +// @checkJs: true +// @noImplicitAny: true +// @Filename: jsdocConstructorFunctionTypeReference.js + +var Validator = function VFunc() { + this.flags = "gim" +}; + +Validator.prototype.num = 12 + +/** + * @param {Validator} state + */ +var validateRegExpFlags = function(state) { + return state.flags +}; +