diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index e47a28359d5..27e1b8117f4 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -4650,15 +4650,15 @@ namespace ts { let jsDocType: Type | undefined; for (const declaration of symbol.declarations) { let declarationInConstructor = false; - const expression = declaration.kind === SyntaxKind.BinaryExpression ? declaration : - declaration.kind === SyntaxKind.PropertyAccessExpression ? cast(declaration.parent, isBinaryExpression) : + const expression = isBinaryExpression(declaration) ? declaration : + isPropertyAccessExpression(declaration) ? isBinaryExpression(declaration.parent) ? declaration.parent : declaration : undefined; if (!expression) { return errorType; } - const special = getSpecialPropertyAssignmentKind(expression); + const special = isPropertyAccessExpression(expression) ? getSpecialPropertyAccessKind(expression) : getSpecialPropertyAssignmentKind(expression); if (special === SpecialPropertyAssignmentKind.ThisProperty) { const thisContainer = getThisContainer(expression, /*includeArrowFunctions*/ false); // Properties defined in a constructor (or base constructor, or javascript constructor function) don't get undefined added. @@ -4687,7 +4687,7 @@ namespace ts { errorNextVariableOrPropertyDeclarationMustHaveSameType(jsDocType, declaration, declarationType); } } - else if (!jsDocType) { + else if (!jsDocType && isBinaryExpression(expression)) { // If we don't have an explicit JSDoc type, get the type from the expression. let type = getWidenedLiteralType(checkExpressionCached(expression.right)); diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index c9278b55eda..9c933b44d01 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -1887,6 +1887,14 @@ namespace ts { return SpecialPropertyAssignmentKind.None; } const lhs = expr.left; + if (isEntityNameExpression(lhs.expression) && lhs.name.escapedText === "prototype" && isObjectLiteralExpression(getInitializerOfBinaryExpression(expr))) { + // F.prototype = { ... } + return SpecialPropertyAssignmentKind.Prototype; + } + return getSpecialPropertyAccessKind(lhs); + } + + export function getSpecialPropertyAccessKind(lhs: PropertyAccessExpression): SpecialPropertyAssignmentKind { if (lhs.expression.kind === SyntaxKind.ThisKeyword) { return SpecialPropertyAssignmentKind.ThisProperty; } @@ -1895,11 +1903,7 @@ namespace ts { return SpecialPropertyAssignmentKind.ModuleExports; } else if (isEntityNameExpression(lhs.expression)) { - if (lhs.name.escapedText === "prototype" && isObjectLiteralExpression(getInitializerOfBinaryExpression(expr))) { - // F.prototype = { ... } - return SpecialPropertyAssignmentKind.Prototype; - } - else if (isPrototypeAccess(lhs.expression)) { + if (isPrototypeAccess(lhs.expression)) { // F.G....prototype.x = expr return SpecialPropertyAssignmentKind.PrototypeProperty; } diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 65fae36752b..489cf717c13 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -6262,6 +6262,7 @@ declare namespace ts { function isExportsIdentifier(node: Node): boolean; function isModuleExportsPropertyAccessExpression(node: Node): boolean; function getSpecialPropertyAssignmentKind(expr: BinaryExpression): SpecialPropertyAssignmentKind; + function getSpecialPropertyAccessKind(lhs: PropertyAccessExpression): SpecialPropertyAssignmentKind; function getInitializerOfBinaryExpression(expr: BinaryExpression): Expression; function isPrototypePropertyAssignment(node: Node): boolean; function isSpecialPropertyDeclaration(expr: PropertyAccessExpression): boolean; diff --git a/tests/baselines/reference/jsdocPrototypePropertyAccessWithType.symbols b/tests/baselines/reference/jsdocPrototypePropertyAccessWithType.symbols new file mode 100644 index 00000000000..00e906ea4ad --- /dev/null +++ b/tests/baselines/reference/jsdocPrototypePropertyAccessWithType.symbols @@ -0,0 +1,16 @@ +=== /a.js === +function C() { this.x = false; }; +>C : Symbol(C, Decl(a.js, 0, 0)) +>x : Symbol(C.x, Decl(a.js, 0, 14), Decl(a.js, 0, 33)) + +/** @type {number} */ +C.prototype.x; +>C.prototype : Symbol(Function.prototype, Decl(lib.es5.d.ts, --, --)) +>C : Symbol(C, Decl(a.js, 0, 0)) +>prototype : Symbol(Function.prototype, Decl(lib.es5.d.ts, --, --)) + +new C().x; +>new C().x : Symbol(C.x, Decl(a.js, 0, 14), Decl(a.js, 0, 33)) +>C : Symbol(C, Decl(a.js, 0, 0)) +>x : Symbol(C.x, Decl(a.js, 0, 14), Decl(a.js, 0, 33)) + diff --git a/tests/baselines/reference/jsdocPrototypePropertyAccessWithType.types b/tests/baselines/reference/jsdocPrototypePropertyAccessWithType.types new file mode 100644 index 00000000000..6756c07f231 --- /dev/null +++ b/tests/baselines/reference/jsdocPrototypePropertyAccessWithType.types @@ -0,0 +1,23 @@ +=== /a.js === +function C() { this.x = false; }; +>C : typeof C +>this.x = false : false +>this.x : any +>this : any +>x : any +>false : false + +/** @type {number} */ +C.prototype.x; +>C.prototype.x : any +>C.prototype : any +>C : typeof C +>prototype : any +>x : any + +new C().x; +>new C().x : number +>new C() : C +>C : typeof C +>x : number + diff --git a/tests/cases/conformance/jsdoc/jsdocPrototypePropertyAccessWithType.ts b/tests/cases/conformance/jsdoc/jsdocPrototypePropertyAccessWithType.ts new file mode 100644 index 00000000000..b9e3c2719be --- /dev/null +++ b/tests/cases/conformance/jsdoc/jsdocPrototypePropertyAccessWithType.ts @@ -0,0 +1,9 @@ +// @allowJs: true +// @checkJs: true +// @noEmit: true + +// @Filename: /a.js +function C() { this.x = false; }; +/** @type {number} */ +C.prototype.x; +new C().x;