diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 874deda580d..3ac6cc094fe 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -5505,7 +5505,7 @@ namespace ts { return symbol.declarations && find(symbol.declarations, s => !!getEffectiveTypeAnnotationNode(s) && (!enclosingDeclaration || !!findAncestor(s, n => n === enclosingDeclaration))); } - function getExistingNodeHasNoTypeParametersOrMatchingTypeParameters(existing: TypeNode, type: Type) { + function existingTypeNodeIsNotReferenceOrIsReferenceWithCompatibleTypeArgumentCount(existing: TypeNode, type: Type) { return !(getObjectFlags(type) & ObjectFlags.Reference) || !isTypeReferenceNode(existing) || length(existing.typeArguments) >= getMinTypeArgumentCount((type as TypeReference).target.typeParameters); } @@ -5519,7 +5519,7 @@ namespace ts { if (declWithExistingAnnotation && !isFunctionLikeDeclaration(declWithExistingAnnotation)) { // try to reuse the existing annotation const existing = getEffectiveTypeAnnotationNode(declWithExistingAnnotation)!; - if (getTypeFromTypeNode(existing) === type && getExistingNodeHasNoTypeParametersOrMatchingTypeParameters(existing, type)) { + if (getTypeFromTypeNode(existing) === type && existingTypeNodeIsNotReferenceOrIsReferenceWithCompatibleTypeArgumentCount(existing, type)) { const result = serializeExistingTypeNode(context, existing, includePrivateSymbol, bundled); if (result) { return result; @@ -5540,7 +5540,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 && getExistingNodeHasNoTypeParametersOrMatchingTypeParameters(annotation, type)) { + if (!!findAncestor(annotation, n => n === context.enclosingDeclaration) && annotation && instantiateType(getTypeFromTypeNode(annotation), signature.mapper) === type && existingTypeNodeIsNotReferenceOrIsReferenceWithCompatibleTypeArgumentCount(annotation, type)) { const result = serializeExistingTypeNode(context, annotation, includePrivateSymbol, bundled); if (result) { return result; @@ -5647,6 +5647,9 @@ namespace ts { ); } } + if (isTypeReferenceNode(node) && isInJSDoc(node) && (getIntendedTypeFromJSDocTypeReference(node) || unknownSymbol === resolveTypeReferenceName(getTypeReferenceName(node), SymbolFlags.Type, /*ignoreErrors*/ true))) { + return setOriginalNode(typeToTypeNodeHelper(getTypeFromTypeNode(node), context), node); + } if (isLiteralImportTypeNode(node)) { return updateImportTypeNode( node, diff --git a/tests/baselines/reference/jsDeclarationsJSDocRedirectedLookups.js b/tests/baselines/reference/jsDeclarationsJSDocRedirectedLookups.js new file mode 100644 index 00000000000..294407154c3 --- /dev/null +++ b/tests/baselines/reference/jsDeclarationsJSDocRedirectedLookups.js @@ -0,0 +1,82 @@ +//// [index.js] +// these are recognized as TS concepts by the checker +/** @type {String} */const a = ""; +/** @type {Number} */const b = 0; +/** @type {Boolean} */const c = true; +/** @type {Void} */const d = undefined; +/** @type {Undefined} */const e = undefined; +/** @type {Null} */const f = null; + +/** @type {Function} */const g = () => void 0; +/** @type {function} */const h = () => void 0; +/** @type {array} */const i = []; +/** @type {promise} */const j = Promise.resolve(0); +/** @type {Object} */const k = {x: "x"}; + + +// these are not recognized as anything and should just be lookup failures +// ignore the errors to try to ensure they're emitted as `any` in declaration emit +// @ts-ignore +/** @type {class} */const l = true; +// @ts-ignore +/** @type {bool} */const m = true; +// @ts-ignore +/** @type {int} */const n = true; +// @ts-ignore +/** @type {float} */const o = true; +// @ts-ignore +/** @type {integer} */const p = true; + +// or, in the case of `event` likely erroneously refers to the type of the global Event object +/** @type {event} */const q = undefined; + +//// [index.js] +"use strict"; +// these are recognized as TS concepts by the checker +/** @type {String} */ const a = ""; +/** @type {Number} */ const b = 0; +/** @type {Boolean} */ const c = true; +/** @type {Void} */ const d = undefined; +/** @type {Undefined} */ const e = undefined; +/** @type {Null} */ const f = null; +/** @type {Function} */ const g = () => void 0; +/** @type {function} */ const h = () => void 0; +/** @type {array} */ const i = []; +/** @type {promise} */ const j = Promise.resolve(0); +/** @type {Object} */ const k = { x: "x" }; +// these are not recognized as anything and should just be lookup failures +// ignore the errors to try to ensure they're emitted as `any` in declaration emit +// @ts-ignore +/** @type {class} */ const l = true; +// @ts-ignore +/** @type {bool} */ const m = true; +// @ts-ignore +/** @type {int} */ const n = true; +// @ts-ignore +/** @type {float} */ const o = true; +// @ts-ignore +/** @type {integer} */ const p = true; +// or, in the case of `event` likely erroneously refers to the type of the global Event object +/** @type {event} */ const q = undefined; + + +//// [index.d.ts] +/** @type {String} */ declare const a: string; +/** @type {Number} */ declare const b: number; +/** @type {Boolean} */ declare const c: boolean; +/** @type {Void} */ declare const d: void; +/** @type {Undefined} */ declare const e: undefined; +/** @type {Null} */ declare const f: null; +/** @type {Function} */ declare const g: Function; +/** @type {function} */ declare const h: Function; +/** @type {array} */ declare const i: any[]; +/** @type {promise} */ declare const j: Promise; +/** @type {Object} */ declare const k: { + [x: string]: string; +}; +/** @type {class} */ declare const l: any; +/** @type {bool} */ declare const m: any; +/** @type {int} */ declare const n: any; +/** @type {float} */ declare const o: any; +/** @type {integer} */ declare const p: any; +/** @type {event} */ declare const q: Event | undefined; diff --git a/tests/baselines/reference/jsDeclarationsJSDocRedirectedLookups.symbols b/tests/baselines/reference/jsDeclarationsJSDocRedirectedLookups.symbols new file mode 100644 index 00000000000..b24b2964a49 --- /dev/null +++ b/tests/baselines/reference/jsDeclarationsJSDocRedirectedLookups.symbols @@ -0,0 +1,69 @@ +=== tests/cases/conformance/jsdoc/declarations/index.js === +// these are recognized as TS concepts by the checker +/** @type {String} */const a = ""; +>a : Symbol(a, Decl(index.js, 1, 26)) + +/** @type {Number} */const b = 0; +>b : Symbol(b, Decl(index.js, 2, 26)) + +/** @type {Boolean} */const c = true; +>c : Symbol(c, Decl(index.js, 3, 27)) + +/** @type {Void} */const d = undefined; +>d : Symbol(d, Decl(index.js, 4, 24)) +>undefined : Symbol(undefined) + +/** @type {Undefined} */const e = undefined; +>e : Symbol(e, Decl(index.js, 5, 29)) +>undefined : Symbol(undefined) + +/** @type {Null} */const f = null; +>f : Symbol(f, Decl(index.js, 6, 24)) + +/** @type {Function} */const g = () => void 0; +>g : Symbol(g, Decl(index.js, 8, 28)) + +/** @type {function} */const h = () => void 0; +>h : Symbol(h, Decl(index.js, 9, 28)) + +/** @type {array} */const i = []; +>i : Symbol(i, Decl(index.js, 10, 25)) + +/** @type {promise} */const j = Promise.resolve(0); +>j : Symbol(j, Decl(index.js, 11, 27)) +>Promise.resolve : Symbol(PromiseConstructor.resolve, Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --)) +>Promise : Symbol(Promise, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --)) +>resolve : Symbol(PromiseConstructor.resolve, Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --)) + +/** @type {Object} */const k = {x: "x"}; +>k : Symbol(k, Decl(index.js, 12, 42)) +>x : Symbol(x, Decl(index.js, 12, 48)) + + +// these are not recognized as anything and should just be lookup failures +// ignore the errors to try to ensure they're emitted as `any` in declaration emit +// @ts-ignore +/** @type {class} */const l = true; +>l : Symbol(l, Decl(index.js, 18, 25)) + +// @ts-ignore +/** @type {bool} */const m = true; +>m : Symbol(m, Decl(index.js, 20, 24)) + +// @ts-ignore +/** @type {int} */const n = true; +>n : Symbol(n, Decl(index.js, 22, 23)) + +// @ts-ignore +/** @type {float} */const o = true; +>o : Symbol(o, Decl(index.js, 24, 25)) + +// @ts-ignore +/** @type {integer} */const p = true; +>p : Symbol(p, Decl(index.js, 26, 27)) + +// or, in the case of `event` likely erroneously refers to the type of the global Event object +/** @type {event} */const q = undefined; +>q : Symbol(q, Decl(index.js, 29, 25)) +>undefined : Symbol(undefined) + diff --git a/tests/baselines/reference/jsDeclarationsJSDocRedirectedLookups.types b/tests/baselines/reference/jsDeclarationsJSDocRedirectedLookups.types new file mode 100644 index 00000000000..43281db54a3 --- /dev/null +++ b/tests/baselines/reference/jsDeclarationsJSDocRedirectedLookups.types @@ -0,0 +1,89 @@ +=== tests/cases/conformance/jsdoc/declarations/index.js === +// these are recognized as TS concepts by the checker +/** @type {String} */const a = ""; +>a : string +>"" : "" + +/** @type {Number} */const b = 0; +>b : number +>0 : 0 + +/** @type {Boolean} */const c = true; +>c : boolean +>true : true + +/** @type {Void} */const d = undefined; +>d : void +>undefined : undefined + +/** @type {Undefined} */const e = undefined; +>e : undefined +>undefined : undefined + +/** @type {Null} */const f = null; +>f : null +>null : null + +/** @type {Function} */const g = () => void 0; +>g : Function +>() => void 0 : () => undefined +>void 0 : undefined +>0 : 0 + +/** @type {function} */const h = () => void 0; +>h : Function +>() => void 0 : () => undefined +>void 0 : undefined +>0 : 0 + +/** @type {array} */const i = []; +>i : any[] +>[] : never[] + +/** @type {promise} */const j = Promise.resolve(0); +>j : Promise +>Promise.resolve(0) : Promise +>Promise.resolve : { (value: T | PromiseLike): Promise; (): Promise; } +>Promise : PromiseConstructor +>resolve : { (value: T | PromiseLike): Promise; (): Promise; } +>0 : 0 + +/** @type {Object} */const k = {x: "x"}; +>k : { [x: string]: string; } +>{x: "x"} : { x: string; } +>x : string +>"x" : "x" + + +// these are not recognized as anything and should just be lookup failures +// ignore the errors to try to ensure they're emitted as `any` in declaration emit +// @ts-ignore +/** @type {class} */const l = true; +>l : error +>true : true + +// @ts-ignore +/** @type {bool} */const m = true; +>m : error +>true : true + +// @ts-ignore +/** @type {int} */const n = true; +>n : error +>true : true + +// @ts-ignore +/** @type {float} */const o = true; +>o : error +>true : true + +// @ts-ignore +/** @type {integer} */const p = true; +>p : error +>true : true + +// or, in the case of `event` likely erroneously refers to the type of the global Event object +/** @type {event} */const q = undefined; +>q : Event | undefined +>undefined : undefined + diff --git a/tests/cases/conformance/jsdoc/declarations/jsDeclarationsJSDocRedirectedLookups.ts b/tests/cases/conformance/jsdoc/declarations/jsDeclarationsJSDocRedirectedLookups.ts new file mode 100644 index 00000000000..cdc96048802 --- /dev/null +++ b/tests/cases/conformance/jsdoc/declarations/jsDeclarationsJSDocRedirectedLookups.ts @@ -0,0 +1,39 @@ +// @allowJs: true +// @checkJs: true +// @target: es6 +// @outDir: ./out +// @declaration: true +// @strict: true +// @noImplicitAny: false +// @filename: index.js + +// these are recognized as TS concepts by the checker +/** @type {String} */const a = ""; +/** @type {Number} */const b = 0; +/** @type {Boolean} */const c = true; +/** @type {Void} */const d = undefined; +/** @type {Undefined} */const e = undefined; +/** @type {Null} */const f = null; + +/** @type {Function} */const g = () => void 0; +/** @type {function} */const h = () => void 0; +/** @type {array} */const i = []; +/** @type {promise} */const j = Promise.resolve(0); +/** @type {Object} */const k = {x: "x"}; + + +// these are not recognized as anything and should just be lookup failures +// ignore the errors to try to ensure they're emitted as `any` in declaration emit +// @ts-ignore +/** @type {class} */const l = true; +// @ts-ignore +/** @type {bool} */const m = true; +// @ts-ignore +/** @type {int} */const n = true; +// @ts-ignore +/** @type {float} */const o = true; +// @ts-ignore +/** @type {integer} */const p = true; + +// or, in the case of `event` likely erroneously refers to the type of the global Event object +/** @type {event} */const q = undefined; \ No newline at end of file