diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index d302e867aeaac..0e881cec9712c 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -5503,6 +5503,10 @@ namespace ts { return symbol.declarations && find(symbol.declarations, s => !!getEffectiveTypeAnnotationNode(s) && (!enclosingDeclaration || !!findAncestor(s, n => n === enclosingDeclaration))); } + function getExistingNodeHasNoTypeParametersOrMatchingTypeParameters(existing: TypeNode, type: Type) { + return !(getObjectFlags(type) & ObjectFlags.Reference) || !isTypeReferenceNode(existing) || length(existing.typeArguments) >= getMinTypeArgumentCount((type as TypeReference).target.typeParameters); + } + /** * Unlike `typeToTypeNodeHelper`, this handles setting up the `AllowUniqueESSymbolType` flag * so a `unique symbol` is returned when appropriate for the input symbol, rather than `typeof sym` @@ -5513,7 +5517,7 @@ namespace ts { if (declWithExistingAnnotation && !isFunctionLikeDeclaration(declWithExistingAnnotation)) { // try to reuse the existing annotation const existing = getEffectiveTypeAnnotationNode(declWithExistingAnnotation)!; - if (getTypeFromTypeNode(existing) === type) { + if (getTypeFromTypeNode(existing) === type && getExistingNodeHasNoTypeParametersOrMatchingTypeParameters(existing, type)) { const result = serializeExistingTypeNode(context, existing, includePrivateSymbol, bundled); if (result) { return result; @@ -5534,7 +5538,7 @@ namespace ts { function serializeReturnTypeForSignature(context: NodeBuilderContext, type: Type, signature: Signature, includePrivateSymbol?: (s: Symbol) => void, bundled?: boolean) { if (type !== errorType && context.enclosingDeclaration) { const annotation = signature.declaration && getEffectiveReturnTypeNode(signature.declaration); - if (!!findAncestor(annotation, n => n === context.enclosingDeclaration) && annotation && instantiateType(getTypeFromTypeNode(annotation), signature.mapper) === type) { + if (!!findAncestor(annotation, n => n === context.enclosingDeclaration) && annotation && instantiateType(getTypeFromTypeNode(annotation), signature.mapper) === type && getExistingNodeHasNoTypeParametersOrMatchingTypeParameters(annotation, type)) { const result = serializeExistingTypeNode(context, annotation, includePrivateSymbol, bundled); if (result) { return result; @@ -5575,6 +5579,21 @@ namespace ts { if (isJSDocVariadicType(node)) { return createArrayTypeNode(visitNode((node as JSDocVariadicType).type, visitExistingNodeTreeSymbols)); } + if (isJSDocTypeLiteral(node)) { + return createTypeLiteralNode(map(node.jsDocPropertyTags, t => { + const name = isIdentifier(t.name) ? t.name : t.name.right; + const typeViaParent = getTypeOfPropertyOfType(getTypeFromTypeNode(node), name.escapedText); + const overrideTypeNode = typeViaParent && t.typeExpression && getTypeFromTypeNode(t.typeExpression.type) !== typeViaParent ? typeToTypeNodeHelper(typeViaParent, context) : undefined; + + return createPropertySignature( + /*modifiers*/ undefined, + name, + t.typeExpression && isJSDocOptionalType(t.typeExpression.type) ? createToken(SyntaxKind.QuestionToken) : undefined, + overrideTypeNode || (t.typeExpression && visitNode(t.typeExpression.type, visitExistingNodeTreeSymbols)) || createKeywordTypeNode(SyntaxKind.AnyKeyword), + /*initializer*/ undefined + ); + })); + } if (isTypeReferenceNode(node) && isIdentifier(node.typeName) && node.typeName.escapedText === "") { return setOriginalNode(createKeywordTypeNode(SyntaxKind.AnyKeyword), node); } diff --git a/tests/baselines/reference/jsDeclarationsMissingGenerics.js b/tests/baselines/reference/jsDeclarationsMissingGenerics.js new file mode 100644 index 0000000000000..50d260f785a4c --- /dev/null +++ b/tests/baselines/reference/jsDeclarationsMissingGenerics.js @@ -0,0 +1,30 @@ +//// [file.js] +/** + * @param {Array} x + */ +function x(x) {} +/** + * @param {Promise} x + */ +function y(x) {} + +//// [file.js] +/** + * @param {Array} x + */ +function x(x) { } +/** + * @param {Promise} x + */ +function y(x) { } + + +//// [file.d.ts] +/** + * @param {Array} x + */ +declare function x(x: any[]): void; +/** + * @param {Promise} x + */ +declare function y(x: Promise): void; diff --git a/tests/baselines/reference/jsDeclarationsMissingGenerics.symbols b/tests/baselines/reference/jsDeclarationsMissingGenerics.symbols new file mode 100644 index 0000000000000..a28d52c3dc530 --- /dev/null +++ b/tests/baselines/reference/jsDeclarationsMissingGenerics.symbols @@ -0,0 +1,15 @@ +=== tests/cases/conformance/jsdoc/declarations/file.js === +/** + * @param {Array} x + */ +function x(x) {} +>x : Symbol(x, Decl(file.js, 0, 0)) +>x : Symbol(x, Decl(file.js, 3, 11)) + +/** + * @param {Promise} x + */ +function y(x) {} +>y : Symbol(y, Decl(file.js, 3, 16)) +>x : Symbol(x, Decl(file.js, 7, 11)) + diff --git a/tests/baselines/reference/jsDeclarationsMissingGenerics.types b/tests/baselines/reference/jsDeclarationsMissingGenerics.types new file mode 100644 index 0000000000000..55045a38844b6 --- /dev/null +++ b/tests/baselines/reference/jsDeclarationsMissingGenerics.types @@ -0,0 +1,15 @@ +=== tests/cases/conformance/jsdoc/declarations/file.js === +/** + * @param {Array} x + */ +function x(x) {} +>x : (x: any[]) => void +>x : any[] + +/** + * @param {Promise} x + */ +function y(x) {} +>y : (x: Promise) => void +>x : Promise + diff --git a/tests/baselines/reference/jsDeclarationsNestedParams.js b/tests/baselines/reference/jsDeclarationsNestedParams.js new file mode 100644 index 0000000000000..f2ced1ba7cf8f --- /dev/null +++ b/tests/baselines/reference/jsDeclarationsNestedParams.js @@ -0,0 +1,96 @@ +//// [file.js] +class X { + /** + * Cancels the request, sending a cancellation to the other party + * @param {Object} error __auto_generated__ + * @param {string?} error.reason the error reason to send the cancellation with + * @param {string?} error.code the error code to send the cancellation with + * @returns {Promise.<*>} resolves when the event has been sent. + */ + async cancel({reason, code}) {} +} + +class Y { + /** + * Cancels the request, sending a cancellation to the other party + * @param {Object} error __auto_generated__ + * @param {string?} error.reason the error reason to send the cancellation with + * @param {Object} error.suberr + * @param {string?} error.suberr.reason the error reason to send the cancellation with + * @param {string?} error.suberr.code the error code to send the cancellation with + * @returns {Promise.<*>} resolves when the event has been sent. + */ + async cancel({reason, suberr}) {} +} + + +//// [file.js] +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +class X { + /** + * Cancels the request, sending a cancellation to the other party + * @param {Object} error __auto_generated__ + * @param {string?} error.reason the error reason to send the cancellation with + * @param {string?} error.code the error code to send the cancellation with + * @returns {Promise.<*>} resolves when the event has been sent. + */ + cancel({ reason, code }) { + return __awaiter(this, void 0, void 0, function* () { }); + } +} +class Y { + /** + * Cancels the request, sending a cancellation to the other party + * @param {Object} error __auto_generated__ + * @param {string?} error.reason the error reason to send the cancellation with + * @param {Object} error.suberr + * @param {string?} error.suberr.reason the error reason to send the cancellation with + * @param {string?} error.suberr.code the error code to send the cancellation with + * @returns {Promise.<*>} resolves when the event has been sent. + */ + cancel({ reason, suberr }) { + return __awaiter(this, void 0, void 0, function* () { }); + } +} + + +//// [file.d.ts] +declare class X { + /** + * Cancels the request, sending a cancellation to the other party + * @param {Object} error __auto_generated__ + * @param {string?} error.reason the error reason to send the cancellation with + * @param {string?} error.code the error code to send the cancellation with + * @returns {Promise.<*>} resolves when the event has been sent. + */ + cancel({ reason, code }: { + reason: string | null; + code: string | null; + }): Promise; +} +declare class Y { + /** + * Cancels the request, sending a cancellation to the other party + * @param {Object} error __auto_generated__ + * @param {string?} error.reason the error reason to send the cancellation with + * @param {Object} error.suberr + * @param {string?} error.suberr.reason the error reason to send the cancellation with + * @param {string?} error.suberr.code the error code to send the cancellation with + * @returns {Promise.<*>} resolves when the event has been sent. + */ + cancel({ reason, suberr }: { + reason: string | null; + suberr: { + reason: string | null; + code: string | null; + }; + }): Promise; +} diff --git a/tests/baselines/reference/jsDeclarationsNestedParams.symbols b/tests/baselines/reference/jsDeclarationsNestedParams.symbols new file mode 100644 index 0000000000000..3b567a5952c2f --- /dev/null +++ b/tests/baselines/reference/jsDeclarationsNestedParams.symbols @@ -0,0 +1,35 @@ +=== tests/cases/conformance/jsdoc/declarations/file.js === +class X { +>X : Symbol(X, Decl(file.js, 0, 0)) + + /** + * Cancels the request, sending a cancellation to the other party + * @param {Object} error __auto_generated__ + * @param {string?} error.reason the error reason to send the cancellation with + * @param {string?} error.code the error code to send the cancellation with + * @returns {Promise.<*>} resolves when the event has been sent. + */ + async cancel({reason, code}) {} +>cancel : Symbol(X.cancel, Decl(file.js, 0, 9)) +>reason : Symbol(reason, Decl(file.js, 8, 18)) +>code : Symbol(code, Decl(file.js, 8, 25)) +} + +class Y { +>Y : Symbol(Y, Decl(file.js, 9, 1)) + + /** + * Cancels the request, sending a cancellation to the other party + * @param {Object} error __auto_generated__ + * @param {string?} error.reason the error reason to send the cancellation with + * @param {Object} error.suberr + * @param {string?} error.suberr.reason the error reason to send the cancellation with + * @param {string?} error.suberr.code the error code to send the cancellation with + * @returns {Promise.<*>} resolves when the event has been sent. + */ + async cancel({reason, suberr}) {} +>cancel : Symbol(Y.cancel, Decl(file.js, 11, 9)) +>reason : Symbol(reason, Decl(file.js, 21, 18)) +>suberr : Symbol(suberr, Decl(file.js, 21, 25)) +} + diff --git a/tests/baselines/reference/jsDeclarationsNestedParams.types b/tests/baselines/reference/jsDeclarationsNestedParams.types new file mode 100644 index 0000000000000..483a707a08984 --- /dev/null +++ b/tests/baselines/reference/jsDeclarationsNestedParams.types @@ -0,0 +1,35 @@ +=== tests/cases/conformance/jsdoc/declarations/file.js === +class X { +>X : X + + /** + * Cancels the request, sending a cancellation to the other party + * @param {Object} error __auto_generated__ + * @param {string?} error.reason the error reason to send the cancellation with + * @param {string?} error.code the error code to send the cancellation with + * @returns {Promise.<*>} resolves when the event has been sent. + */ + async cancel({reason, code}) {} +>cancel : ({ reason, code }: { reason: string | null; code: string | null;}) => Promise +>reason : string +>code : string +} + +class Y { +>Y : Y + + /** + * Cancels the request, sending a cancellation to the other party + * @param {Object} error __auto_generated__ + * @param {string?} error.reason the error reason to send the cancellation with + * @param {Object} error.suberr + * @param {string?} error.suberr.reason the error reason to send the cancellation with + * @param {string?} error.suberr.code the error code to send the cancellation with + * @returns {Promise.<*>} resolves when the event has been sent. + */ + async cancel({reason, suberr}) {} +>cancel : ({ reason, suberr }: { reason: string | null; suberr: { reason: string | null; code: string | null; };}) => Promise +>reason : string +>suberr : { reason: string; code: string; } +} + diff --git a/tests/baselines/reference/jsdocArrayObjectPromiseImplicitAny.types b/tests/baselines/reference/jsdocArrayObjectPromiseImplicitAny.types index 7ea21afe4c88a..19affd29ffa37 100644 --- a/tests/baselines/reference/jsdocArrayObjectPromiseImplicitAny.types +++ b/tests/baselines/reference/jsdocArrayObjectPromiseImplicitAny.types @@ -16,7 +16,7 @@ var numberArray = [5]; * @return {Array} */ function returnAnyArray(arr) { ->returnAnyArray : (arr: Array) => Array +>returnAnyArray : (arr: any[]) => any[] >arr : any[] return arr; @@ -46,7 +46,7 @@ var numberPromise = Promise.resolve(5); * @return {Promise} */ function returnAnyPromise(pr) { ->returnAnyPromise : (pr: Promise) => Promise +>returnAnyPromise : (pr: Promise) => Promise >pr : Promise return pr; diff --git a/tests/baselines/reference/jsdocArrayObjectPromiseNoImplicitAny.types b/tests/baselines/reference/jsdocArrayObjectPromiseNoImplicitAny.types index e5dba449a09aa..82ff886f1ec68 100644 --- a/tests/baselines/reference/jsdocArrayObjectPromiseNoImplicitAny.types +++ b/tests/baselines/reference/jsdocArrayObjectPromiseNoImplicitAny.types @@ -16,7 +16,7 @@ var numberArray = [5]; * @return {Array} */ function returnNotAnyArray(arr) { ->returnNotAnyArray : (arr: Array) => Array +>returnNotAnyArray : (arr: any[]) => any[] >arr : any[] return arr; @@ -46,7 +46,7 @@ var numberPromise = Promise.resolve(5); * @return {Promise} */ function returnNotAnyPromise(pr) { ->returnNotAnyPromise : (pr: Promise) => Promise +>returnNotAnyPromise : (pr: Promise) => Promise >pr : Promise return pr; diff --git a/tests/baselines/reference/jsdocClassMissingTypeArguments.types b/tests/baselines/reference/jsdocClassMissingTypeArguments.types index 8be84d6df5df2..0c64fd3c5a7d3 100644 --- a/tests/baselines/reference/jsdocClassMissingTypeArguments.types +++ b/tests/baselines/reference/jsdocClassMissingTypeArguments.types @@ -5,6 +5,6 @@ class C {} /** @param {C} p */ function f(p) {} ->f : (p: C) => void +>f : (p: C) => void >p : C diff --git a/tests/baselines/reference/jsdocParamTag2.types b/tests/baselines/reference/jsdocParamTag2.types index bc4d0f3b10a22..e81155ccb3cd8 100644 --- a/tests/baselines/reference/jsdocParamTag2.types +++ b/tests/baselines/reference/jsdocParamTag2.types @@ -49,7 +49,7 @@ function good4({a, b}) {} * @param {string} x */ function good5({a, b}, x) {} ->good5 : ({ a, b }: * @param {string} obj.a - this is like the saddest way to specify a type * @param {string} obj.b - but it sure does allow a lot of documentation, x: string) => void +>good5 : ({ a, b }: { a: string; b: string;}, x: string) => void >a : string >b : string >x : string @@ -63,7 +63,7 @@ function good5({a, b}, x) {} * @param {string} OBJECTION.d - meh */ function good6({a, b}, {c, d}) {} ->good6 : ({ a, b }: * @param {string} obj.a * @param {string} obj.b - but it sure does allow a lot of documentation, { c, d }: * @param {string} OBJECTION.c * @param {string} OBJECTION.d - meh) => void +>good6 : ({ a, b }: { a: string; b: string;}, { c, d }: { c: string; d: string;}) => void >a : string >b : string >c : string @@ -77,7 +77,7 @@ function good6({a, b}, {c, d}) {} * @param {string} y */ function good7(x, {a, b}, y) {} ->good7 : (x: number, { a, b }: * @param {string} obj.a * @param {string} obj.b, y: string) => void +>good7 : (x: number, { a, b }: { a: string; b: string;}, y: string) => void >x : number >a : string >b : string @@ -89,7 +89,7 @@ function good7(x, {a, b}, y) {} * @param {string} obj.b */ function good8({a, b}) {} ->good8 : ({ a, b }: * @param {string} obj.a * @param {string} obj.b) => void +>good8 : ({ a, b }: { a: string; b: string;}) => void >a : string >b : string diff --git a/tests/baselines/reference/jsdocParamTagTypeLiteral.types b/tests/baselines/reference/jsdocParamTagTypeLiteral.types index a27d8b4d50c64..6a51e49ee6c15 100644 --- a/tests/baselines/reference/jsdocParamTagTypeLiteral.types +++ b/tests/baselines/reference/jsdocParamTagTypeLiteral.types @@ -23,7 +23,7 @@ normal(12); * @param {string} [opts1.w="hi"] doc5 */ function foo1(opts1) { ->foo1 : (opts1: * @param {string} opts1.x doc2 * @param {string=} opts1.y doc3 * @param {string} [opts1.z] doc4 * @param {string} [opts1.w] doc5) => void +>foo1 : (opts1: { x: string; y?: string | undefined; z: string; w: string;}) => void >opts1 : { x: string; y?: string | undefined; z?: string; w?: string; } opts1.x; @@ -45,7 +45,7 @@ foo1({x: 'abc'}); * @param {string=} opts2[].anotherY */ function foo2(/** @param opts2 bad idea theatre! */opts2) { ->foo2 : (opts2: * @param {string} opts2.anotherX * @param {string=} opts2.anotherY) => void +>foo2 : (opts2: { anotherX: string; anotherY?: string | undefined;}) => void >opts2 : { anotherX: string; anotherY?: string | undefined; }[] opts2[0].anotherX; @@ -69,7 +69,7 @@ foo2([{anotherX: "world"}]); * @param {string} opts3.x */ function foo3(opts3) { ->foo3 : (opts3: * @param {string} opts3.x) => void +>foo3 : (opts3: { x: string;}) => void >opts3 : { x: string; } opts3.x; @@ -92,7 +92,7 @@ foo3({x: 'abc'}); * @param {string} [opts4[].w="hi"] */ function foo4(opts4) { ->foo4 : (opts4: * @param {string} opts4.x * @param {string=} opts4.y * @param {string} [opts4.z] * @param {string} [opts4.w]) => void +>foo4 : (opts4: { x: string; y?: string | undefined; z: string; w: string;}) => void >opts4 : { x: string; y?: string | undefined; z?: string; w?: string; }[] opts4[0].x; @@ -122,7 +122,7 @@ foo4([{ x: 'hi' }]); * @param {number} opts5[].unnest - Here we are almost all the way back at the beginning. */ function foo5(opts5) { ->foo5 : (opts5: * @param {string} opts5.help - (This one is just normal) * @param { * @param {string} opts5.what.a - (Another normal one) * @param { * @param {string} opts5.what.bad.idea - I don't think you can get back out of this level... * @param {boolean} opts5.what.bad.oh - Oh ... that's how you do it.} opts5.what.bad - Now we're nesting inside a nested type} opts5.what - Look at us go! Here's the first nest! * @param {number} opts5.unnest - Here we are almost all the way back at the beginning.) => void +>foo5 : (opts5: { help: string; what: { a: string; bad: { idea: string; oh: boolean; }; }; unnest: number;}) => void >opts5 : { help: string; what: { a: string; bad: { idea: string; oh: boolean; }[]; }; unnest: number; }[] opts5[0].what.bad[0].idea; diff --git a/tests/baselines/reference/jsdocTemplateConstructorFunction2.types b/tests/baselines/reference/jsdocTemplateConstructorFunction2.types index 19270bb4b6731..dc70e5282c31f 100644 --- a/tests/baselines/reference/jsdocTemplateConstructorFunction2.types +++ b/tests/baselines/reference/jsdocTemplateConstructorFunction2.types @@ -26,13 +26,13 @@ function Zet(t) { * @param {T} o.nested */ Zet.prototype.add = function(v, o) { ->Zet.prototype.add = function(v, o) { this.u = v || o.nested return this.u} : (v: T, o: * @param {T} o.nested) => T +>Zet.prototype.add = function(v, o) { this.u = v || o.nested return this.u} : (v: T, o: { nested: T; }) => T >Zet.prototype.add : any >Zet.prototype : any >Zet : typeof Zet >prototype : any >add : any ->function(v, o) { this.u = v || o.nested return this.u} : (v: T, o: * @param {T} o.nested) => T +>function(v, o) { this.u = v || o.nested return this.u} : (v: T, o: { nested: T; }) => T >v : T >o : { nested: T; } diff --git a/tests/cases/conformance/jsdoc/declarations/jsDeclarationsMissingGenerics.ts b/tests/cases/conformance/jsdoc/declarations/jsDeclarationsMissingGenerics.ts new file mode 100644 index 0000000000000..ff9396ac90479 --- /dev/null +++ b/tests/cases/conformance/jsdoc/declarations/jsDeclarationsMissingGenerics.ts @@ -0,0 +1,14 @@ +// @allowJs: true +// @checkJs: true +// @target: es5 +// @outDir: ./out +// @declaration: true +// @filename: file.js +/** + * @param {Array} x + */ +function x(x) {} +/** + * @param {Promise} x + */ +function y(x) {} \ No newline at end of file diff --git a/tests/cases/conformance/jsdoc/declarations/jsDeclarationsNestedParams.ts b/tests/cases/conformance/jsdoc/declarations/jsDeclarationsNestedParams.ts new file mode 100644 index 0000000000000..2fa550f5ee9e4 --- /dev/null +++ b/tests/cases/conformance/jsdoc/declarations/jsDeclarationsNestedParams.ts @@ -0,0 +1,29 @@ +// @allowJs: true +// @checkJs: true +// @target: es6 +// @outDir: ./out +// @declaration: true +// @filename: file.js +class X { + /** + * Cancels the request, sending a cancellation to the other party + * @param {Object} error __auto_generated__ + * @param {string?} error.reason the error reason to send the cancellation with + * @param {string?} error.code the error code to send the cancellation with + * @returns {Promise.<*>} resolves when the event has been sent. + */ + async cancel({reason, code}) {} +} + +class Y { + /** + * Cancels the request, sending a cancellation to the other party + * @param {Object} error __auto_generated__ + * @param {string?} error.reason the error reason to send the cancellation with + * @param {Object} error.suberr + * @param {string?} error.suberr.reason the error reason to send the cancellation with + * @param {string?} error.suberr.code the error code to send the cancellation with + * @returns {Promise.<*>} resolves when the event has been sent. + */ + async cancel({reason, suberr}) {} +}