diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 38888e5e582ed..59b19cfa913b2 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -25858,7 +25858,9 @@ namespace ts { case SyntaxKind.ParenthesizedExpression: { // Like in `checkParenthesizedExpression`, an `/** @type {xyz} */` comment before a parenthesized expression acts as a type cast. const tag = isInJSFile(parent) ? getJSDocTypeTag(parent) : undefined; - return tag ? getTypeFromTypeNode(tag.typeExpression.type) : getContextualType(parent as ParenthesizedExpression, contextFlags); + return !tag ? getContextualType(parent as ParenthesizedExpression, contextFlags) : + isJSDocTypeTag(tag) && isConstTypeReference(tag.typeExpression.type) ? tryFindWhenConstTypeReference(parent as ParenthesizedExpression) : + getTypeFromTypeNode(tag.typeExpression.type); } case SyntaxKind.NonNullExpression: return getContextualType(parent as NonNullExpression, contextFlags); diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 00390b144b590..3ffa771cb4b1a 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -2634,13 +2634,13 @@ namespace ts { let result: (JSDoc | JSDocTag)[] | undefined; // Pull parameter comments from declaring function as well if (isVariableLike(hostNode) && hasInitializer(hostNode) && hasJSDocNodes(hostNode.initializer!)) { - result = append(result, last((hostNode.initializer as HasJSDoc).jsDoc!)); + result = addRange(result, filterOwnedJSDocTags(hostNode, last((hostNode.initializer as HasJSDoc).jsDoc!))); } let node: Node | undefined = hostNode; while (node && node.parent) { if (hasJSDocNodes(node)) { - result = append(result, last(node.jsDoc!)); + result = addRange(result, filterOwnedJSDocTags(hostNode, last(node.jsDoc!))); } if (node.kind === SyntaxKind.Parameter) { @@ -2656,6 +2656,26 @@ namespace ts { return result || emptyArray; } + function filterOwnedJSDocTags(hostNode: Node, jsDoc: JSDoc | JSDocTag) { + if (isJSDoc(jsDoc)) { + const ownedTags = filter(jsDoc.tags, tag => ownsJSDocTag(hostNode, tag)); + return jsDoc.tags === ownedTags ? [jsDoc] : ownedTags; + } + return ownsJSDocTag(hostNode, jsDoc) ? [jsDoc] : undefined; + } + + /** + * Determines whether a host node owns a jsDoc tag. A `@type` tag attached to a + * a ParenthesizedExpression belongs only to the ParenthesizedExpression. + */ + function ownsJSDocTag(hostNode: Node, tag: JSDocTag) { + return !isJSDocTypeTag(tag) + || !tag.parent + || !isJSDoc(tag.parent) + || !isParenthesizedExpression(tag.parent.parent) + || tag.parent.parent === hostNode; + } + export function getNextJSDocCommentLocation(node: Node) { const parent = node.parent; if (parent.kind === SyntaxKind.PropertyAssignment || diff --git a/tests/baselines/reference/importTypeInJSDoc.types b/tests/baselines/reference/importTypeInJSDoc.types index ee28f78f97688..38216009bfe9a 100644 --- a/tests/baselines/reference/importTypeInJSDoc.types +++ b/tests/baselines/reference/importTypeInJSDoc.types @@ -37,14 +37,14 @@ export = MyClass; */ let a = /** @type {Foo} */(/** @type {*} */(undefined)); ->a : import("tests/cases/conformance/types/import/externs") +>a : any >(/** @type {*} */(undefined)) : import("tests/cases/conformance/types/import/externs") >(undefined) : any >undefined : undefined a = new Foo({doer: Foo.Bar}); >a = new Foo({doer: Foo.Bar}) : import("tests/cases/conformance/types/import/externs") ->a : import("tests/cases/conformance/types/import/externs") +>a : any >new Foo({doer: Foo.Bar}) : import("tests/cases/conformance/types/import/externs") >Foo : typeof import("tests/cases/conformance/types/import/externs") >{doer: Foo.Bar} : { doer: (x: string, y?: number) => void; } diff --git a/tests/baselines/reference/jsdocTypeTagCast.errors.txt b/tests/baselines/reference/jsdocTypeTagCast.errors.txt index 41544f603485c..f85dd92939fbe 100644 --- a/tests/baselines/reference/jsdocTypeTagCast.errors.txt +++ b/tests/baselines/reference/jsdocTypeTagCast.errors.txt @@ -123,4 +123,7 @@ tests/cases/conformance/jsdoc/b.js(67,8): error TS2454: Variable 'numOrStr' is u } - \ No newline at end of file + var asConst1 = /** @type {const} */(1); + var asConst2 = /** @type {const} */({ + x: 1 + }); \ No newline at end of file diff --git a/tests/baselines/reference/jsdocTypeTagCast.js b/tests/baselines/reference/jsdocTypeTagCast.js index fce2dca5dc293..d829ef238addd 100644 --- a/tests/baselines/reference/jsdocTypeTagCast.js +++ b/tests/baselines/reference/jsdocTypeTagCast.js @@ -74,7 +74,10 @@ if(/** @type {numOrStr is string} */(numOrStr === undefined)) { // Error } - +var asConst1 = /** @type {const} */(1); +var asConst2 = /** @type {const} */({ + x: 1 +}); //// [a.js] var W; @@ -154,3 +157,7 @@ var str; if ( /** @type {numOrStr is string} */(numOrStr === undefined)) { // Error str = numOrStr; // Error, no narrowing occurred } +var asConst1 = /** @type {const} */ (1); +var asConst2 = /** @type {const} */ ({ + x: 1 +}); diff --git a/tests/baselines/reference/jsdocTypeTagCast.symbols b/tests/baselines/reference/jsdocTypeTagCast.symbols index 5c4fbefb2c707..f719cfd9739a3 100644 --- a/tests/baselines/reference/jsdocTypeTagCast.symbols +++ b/tests/baselines/reference/jsdocTypeTagCast.symbols @@ -157,4 +157,13 @@ if(/** @type {numOrStr is string} */(numOrStr === undefined)) { // Error } +var asConst1 = /** @type {const} */(1); +>asConst1 : Symbol(asConst1, Decl(b.js, 70, 3)) +var asConst2 = /** @type {const} */({ +>asConst2 : Symbol(asConst2, Decl(b.js, 71, 3)) + + x: 1 +>x : Symbol(x, Decl(b.js, 71, 37)) + +}); diff --git a/tests/baselines/reference/jsdocTypeTagCast.types b/tests/baselines/reference/jsdocTypeTagCast.types index 5a330825aea61..d8d28fa4d7805 100644 --- a/tests/baselines/reference/jsdocTypeTagCast.types +++ b/tests/baselines/reference/jsdocTypeTagCast.types @@ -209,4 +209,18 @@ if(/** @type {numOrStr is string} */(numOrStr === undefined)) { // Error } +var asConst1 = /** @type {const} */(1); +>asConst1 : 1 +>(1) : 1 +>1 : 1 +var asConst2 = /** @type {const} */({ +>asConst2 : { x: number; } +>({ x: 1}) : { x: number; } +>{ x: 1} : { x: number; } + + x: 1 +>x : number +>1 : 1 + +}); diff --git a/tests/cases/conformance/jsdoc/jsdocTypeTagCast.ts b/tests/cases/conformance/jsdoc/jsdocTypeTagCast.ts index 60ca43b053346..b7b140b33b9bd 100644 --- a/tests/cases/conformance/jsdoc/jsdocTypeTagCast.ts +++ b/tests/cases/conformance/jsdoc/jsdocTypeTagCast.ts @@ -76,3 +76,7 @@ if(/** @type {numOrStr is string} */(numOrStr === undefined)) { // Error } +var asConst1 = /** @type {const} */(1); +var asConst2 = /** @type {const} */({ + x: 1 +}); \ No newline at end of file