diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index a35d7a59d048c..95500b8bc5b7b 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -4588,13 +4588,13 @@ export function getJSDocCommentsAndTags(hostNode: Node, noCache?: boolean): read let result: (JSDoc | JSDocTag)[] | undefined; // Pull parameter comments from declaring function as well if (isVariableLike(hostNode) && hasInitializer(hostNode) && hasJSDocNodes(hostNode.initializer!)) { - result = addRange(result, filterOwnedJSDocTags(hostNode, hostNode.initializer.jsDoc!)); + result = addRange(result, filterOwnedJSDocTags(hostNode, hostNode.initializer.jsDoc!, result)); } let node: Node | undefined = hostNode; while (node && node.parent) { if (hasJSDocNodes(node)) { - result = addRange(result, filterOwnedJSDocTags(hostNode, node.jsDoc!)); + result = addRange(result, filterOwnedJSDocTags(hostNode, node.jsDoc!, result)); } if (node.kind === SyntaxKind.Parameter) { @@ -4610,11 +4610,11 @@ export function getJSDocCommentsAndTags(hostNode: Node, noCache?: boolean): read return result || emptyArray; } -function filterOwnedJSDocTags(hostNode: Node, comments: JSDocArray) { +function filterOwnedJSDocTags(hostNode: Node, comments: JSDocArray, result: (JSDoc | JSDocTag)[] | undefined) { const lastJsDoc = last(comments); return flatMap(comments, jsDoc => { if (jsDoc === lastJsDoc) { - const ownedTags = filter(jsDoc.tags, tag => ownsJSDocTag(hostNode, tag)); + const ownedTags = filter(jsDoc.tags, tag => ownsJSDocTag(hostNode, tag, result)); return jsDoc.tags === ownedTags ? [jsDoc] : ownedTags; } else { @@ -4627,14 +4627,24 @@ function filterOwnedJSDocTags(hostNode: Node, comments: JSDocArray) { * Determines whether a host node owns a jsDoc tag. A `@type`/`@satisfies` tag attached to a * a ParenthesizedExpression belongs only to the ParenthesizedExpression. */ -function ownsJSDocTag(hostNode: Node, tag: JSDocTag) { - return !(isJSDocTypeTag(tag) || isJSDocSatisfiesTag(tag)) - || !tag.parent +function ownsJSDocTag(hostNode: Node, tag: JSDocTag, result: (JSDoc | JSDocTag)[] | undefined) { + if ( + (isJSDocTypeOrSatisfiesTag(tag)) && some(result, t => { + return isJSDoc(t) ? some(t.tags, isJSDocTypeOrSatisfiesTag) : isJSDocTypeOrSatisfiesTag(t); + }) + ) { + return false; + } + return !tag.parent || !isJSDoc(tag.parent) || !isParenthesizedExpression(tag.parent.parent) || tag.parent.parent === hostNode; } +function isJSDocTypeOrSatisfiesTag(tag: JSDocTag) { + return isJSDocTypeTag(tag) || isJSDocSatisfiesTag(tag); +} + /** @internal */ export function getNextJSDocCommentLocation(node: Node): Node | undefined { const parent = node.parent; diff --git a/tests/baselines/reference/checkJsdocSatisfiesTag1.types b/tests/baselines/reference/checkJsdocSatisfiesTag1.types index 93b3d1df2781c..5f749d2c75712 100644 --- a/tests/baselines/reference/checkJsdocSatisfiesTag1.types +++ b/tests/baselines/reference/checkJsdocSatisfiesTag1.types @@ -60,8 +60,8 @@ const t3 = /** @satisfies {T1} */ ({}); const t4 = /** @satisfies {T2} */ ({ a: "a" }); >t4 : T2 > : ^^ ->({ a: "a" }) : T2 -> : ^^ +>({ a: "a" }) : { a: "a"; } +> : ^^^^^^^^^^^ >{ a: "a" } : { a: "a"; } > : ^^^^^^^^^^^ >a : "a" @@ -74,7 +74,7 @@ const t5 = /** @satisfies {T3} */((m) => m.substring(0)); >t5 : (m: string) => string > : ^ ^^ ^^^^^ >((m) => m.substring(0)) : (m: string) => string -> : ^ ^^ ^^^^^ +> : ^ ^^^^^^^^^^^^^^^^^^^ >(m) => m.substring(0) : (m: string) => string > : ^ ^^^^^^^^^^^^^^^^^^^ >m : string diff --git a/tests/baselines/reference/checkJsdocSatisfiesTag11.errors.txt b/tests/baselines/reference/checkJsdocSatisfiesTag11.errors.txt index 62e873fdcf1c7..8cede58f23ac2 100644 --- a/tests/baselines/reference/checkJsdocSatisfiesTag11.errors.txt +++ b/tests/baselines/reference/checkJsdocSatisfiesTag11.errors.txt @@ -1,8 +1,7 @@ /a.js(13,5): error TS1223: 'satisfies' tag already specified. -/a.js(18,5): error TS1223: 'satisfies' tag already specified. -==== /a.js (2 errors) ==== +==== /a.js (1 errors) ==== /** * @typedef {Object} T1 * @property {number} a @@ -23,8 +22,6 @@ /** * @satisfies {number} - ~~~~~~~~~ -!!! error TS1223: 'satisfies' tag already specified. */ const t2 = /** @satisfies {number} */ (1); \ No newline at end of file diff --git a/tests/baselines/reference/checkJsdocSatisfiesTag16.errors.txt b/tests/baselines/reference/checkJsdocSatisfiesTag16.errors.txt new file mode 100644 index 0000000000000..5b0808d314f08 --- /dev/null +++ b/tests/baselines/reference/checkJsdocSatisfiesTag16.errors.txt @@ -0,0 +1,35 @@ +/a.js(9,17): error TS1360: Type 'number' does not satisfy the expected type 'string'. +/a.js(12,5): error TS1360: Type 'number' does not satisfy the expected type 'string'. +/a.js(17,5): error TS1360: Type 'number' does not satisfy the expected type 'string'. +/a.js(19,17): error TS1360: Type 'number' does not satisfy the expected type 'string'. + + +==== /a.js (4 errors) ==== + /** + * @satisfies {number} + */ + const t1 = /** @satisfies {number} */ (1); + + /** + * @satisfies {number} + */ + const t2 = /** @satisfies {string} */ (1); + ~~~~~~~~~ +!!! error TS1360: Type 'number' does not satisfy the expected type 'string'. + + /** + * @satisfies {string} + ~~~~~~~~~ +!!! error TS1360: Type 'number' does not satisfy the expected type 'string'. + */ + const t3 = /** @satisfies {number} */ (1); + + /** + * @satisfies {string} + ~~~~~~~~~ +!!! error TS1360: Type 'number' does not satisfy the expected type 'string'. + */ + const t4 = /** @satisfies {string} */ (1); + ~~~~~~~~~ +!!! error TS1360: Type 'number' does not satisfy the expected type 'string'. + \ No newline at end of file diff --git a/tests/baselines/reference/checkJsdocSatisfiesTag16.symbols b/tests/baselines/reference/checkJsdocSatisfiesTag16.symbols new file mode 100644 index 0000000000000..ae79952be111e --- /dev/null +++ b/tests/baselines/reference/checkJsdocSatisfiesTag16.symbols @@ -0,0 +1,27 @@ +//// [tests/cases/conformance/jsdoc/checkJsdocSatisfiesTag16.ts] //// + +=== /a.js === +/** + * @satisfies {number} + */ +const t1 = /** @satisfies {number} */ (1); +>t1 : Symbol(t1, Decl(a.js, 3, 5)) + +/** + * @satisfies {number} + */ +const t2 = /** @satisfies {string} */ (1); +>t2 : Symbol(t2, Decl(a.js, 8, 5)) + +/** + * @satisfies {string} + */ +const t3 = /** @satisfies {number} */ (1); +>t3 : Symbol(t3, Decl(a.js, 13, 5)) + +/** + * @satisfies {string} + */ +const t4 = /** @satisfies {string} */ (1); +>t4 : Symbol(t4, Decl(a.js, 18, 5)) + diff --git a/tests/baselines/reference/checkJsdocSatisfiesTag16.types b/tests/baselines/reference/checkJsdocSatisfiesTag16.types new file mode 100644 index 0000000000000..017d30265b6a8 --- /dev/null +++ b/tests/baselines/reference/checkJsdocSatisfiesTag16.types @@ -0,0 +1,47 @@ +//// [tests/cases/conformance/jsdoc/checkJsdocSatisfiesTag16.ts] //// + +=== /a.js === +/** + * @satisfies {number} + */ +const t1 = /** @satisfies {number} */ (1); +>t1 : 1 +> : ^ +>(1) : 1 +> : ^ +>1 : 1 +> : ^ + +/** + * @satisfies {number} + */ +const t2 = /** @satisfies {string} */ (1); +>t2 : 1 +> : ^ +>(1) : 1 +> : ^ +>1 : 1 +> : ^ + +/** + * @satisfies {string} + */ +const t3 = /** @satisfies {number} */ (1); +>t3 : 1 +> : ^ +>(1) : 1 +> : ^ +>1 : 1 +> : ^ + +/** + * @satisfies {string} + */ +const t4 = /** @satisfies {string} */ (1); +>t4 : 1 +> : ^ +>(1) : 1 +> : ^ +>1 : 1 +> : ^ + diff --git a/tests/baselines/reference/checkJsdocSatisfiesTag3.types b/tests/baselines/reference/checkJsdocSatisfiesTag3.types index 73b094927a096..f00185c87c294 100644 --- a/tests/baselines/reference/checkJsdocSatisfiesTag3.types +++ b/tests/baselines/reference/checkJsdocSatisfiesTag3.types @@ -5,8 +5,8 @@ let obj = /** @satisfies {{ g(s: string): void } & Record} */ ({ >obj : { f(s: string): void; } & Record > : ^^^^ ^^ ^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ->({ f(s) { }, // "incorrect" implicit any on 's' g(s) { }}) : { f(s: string): void; } & Record -> : ^^^^ ^^ ^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>({ f(s) { }, // "incorrect" implicit any on 's' g(s) { }}) : { f(s: any): void; g(s: string): void; } +> : ^^^^ ^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^ >{ f(s) { }, // "incorrect" implicit any on 's' g(s) { }} : { f(s: any): void; g(s: string): void; } > : ^^^^ ^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^ diff --git a/tests/cases/conformance/jsdoc/checkJsdocSatisfiesTag16.ts b/tests/cases/conformance/jsdoc/checkJsdocSatisfiesTag16.ts new file mode 100644 index 0000000000000..970555bd80777 --- /dev/null +++ b/tests/cases/conformance/jsdoc/checkJsdocSatisfiesTag16.ts @@ -0,0 +1,26 @@ +// @strict: true +// @allowJS: true +// @checkJs: true +// @noEmit: true + +// @filename: /a.js + +/** + * @satisfies {number} + */ +const t1 = /** @satisfies {number} */ (1); + +/** + * @satisfies {number} + */ +const t2 = /** @satisfies {string} */ (1); + +/** + * @satisfies {string} + */ +const t3 = /** @satisfies {number} */ (1); + +/** + * @satisfies {string} + */ +const t4 = /** @satisfies {string} */ (1);