Skip to content

Commit 828cdbf

Browse files
committed
feat(47558): check JSDoc link tags in TypeScript/JavaScript files
1 parent c06849a commit 828cdbf

File tree

12 files changed

+190
-6
lines changed

12 files changed

+190
-6
lines changed

src/compiler/checker.ts

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36200,6 +36200,12 @@ namespace ts {
3620036200
checkSourceElement(node.typeExpression);
3620136201
}
3620236202

36203+
function checkJSDocLinkLikeTag(node: JSDocLink | JSDocLinkCode | JSDocLinkPlain) {
36204+
if (node.name) {
36205+
resolveJSDocMemberName(node.name, /*container*/ undefined, /*ignoreErrors*/ true);
36206+
}
36207+
}
36208+
3620336209
function checkJSDocParameterTag(node: JSDocParameterTag) {
3620436210
checkSourceElement(node.typeExpression);
3620536211
if (!getParameterSymbolFromJSDoc(node)) {
@@ -40347,9 +40353,15 @@ namespace ts {
4034740353
}
4034840354

4034940355
function checkSourceElementWorker(node: Node): void {
40350-
if (isInJSFile(node)) {
40351-
forEach((node as JSDocContainer).jsDoc, ({ tags }) => forEach(tags, checkSourceElement));
40352-
}
40356+
forEach((node as JSDocContainer).jsDoc, ({ comment, tags }) => {
40357+
checkJSDocCommentWorker(comment);
40358+
forEach(tags, tag => {
40359+
checkJSDocCommentWorker(tag.comment);
40360+
if (isInJSFile(node)) {
40361+
checkSourceElement(tag);
40362+
}
40363+
});
40364+
});
4035340365

4035440366
const kind = node.kind;
4035540367
if (cancellationToken) {
@@ -40437,6 +40449,10 @@ namespace ts {
4043740449
return checkJSDocTemplateTag(node as JSDocTemplateTag);
4043840450
case SyntaxKind.JSDocTypeTag:
4043940451
return checkJSDocTypeTag(node as JSDocTypeTag);
40452+
case SyntaxKind.JSDocLink:
40453+
case SyntaxKind.JSDocLinkCode:
40454+
case SyntaxKind.JSDocLinkPlain:
40455+
return checkJSDocLinkLikeTag(node as JSDocLink | JSDocLinkCode | JSDocLinkPlain);
4044040456
case SyntaxKind.JSDocParameterTag:
4044140457
return checkJSDocParameterTag(node as JSDocParameterTag);
4044240458
case SyntaxKind.JSDocPropertyTag:
@@ -40532,6 +40548,16 @@ namespace ts {
4053240548
}
4053340549
}
4053440550

40551+
function checkJSDocCommentWorker(node: string | readonly JSDocComment[] | undefined) {
40552+
if (isArray(node)) {
40553+
forEach(node, tag => {
40554+
if (isJSDocLinkLike(tag)) {
40555+
checkSourceElement(tag);
40556+
}
40557+
});
40558+
}
40559+
}
40560+
4053540561
function checkJSDocTypeIsInJsFile(node: Node): void {
4053640562
if (!isInJSFile(node)) {
4053740563
grammarErrorOnNode(node, Diagnostics.JSDoc_types_can_only_be_used_inside_documentation_comments);
@@ -41193,19 +41219,19 @@ namespace ts {
4119341219
*
4119441220
* For unqualified names, a container K may be provided as a second argument.
4119541221
*/
41196-
function resolveJSDocMemberName(name: EntityName | JSDocMemberName, container?: Symbol): Symbol | undefined {
41222+
function resolveJSDocMemberName(name: EntityName | JSDocMemberName, container?: Symbol, ignoreErrors?: boolean): Symbol | undefined {
4119741223
if (isEntityName(name)) {
4119841224
// resolve static values first
4119941225
const meaning = SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Value;
41200-
let symbol = resolveEntityName(name, meaning, /*ignoreErrors*/ false, /*dontResolveAlias*/ true, getHostSignatureFromJSDoc(name));
41226+
let symbol = resolveEntityName(name, meaning, ignoreErrors, /*dontResolveAlias*/ true, getHostSignatureFromJSDoc(name));
4120141227
if (!symbol && isIdentifier(name) && container) {
4120241228
symbol = getMergedSymbol(getSymbol(getExportsOfSymbol(container), name.escapedText, meaning));
4120341229
}
4120441230
if (symbol) {
4120541231
return symbol;
4120641232
}
4120741233
}
41208-
const left = isIdentifier(name) ? container : resolveJSDocMemberName(name.left);
41234+
const left = isIdentifier(name) ? container : resolveJSDocMemberName(name.left, /*container*/ undefined, ignoreErrors);
4120941235
const right = isIdentifier(name) ? name.escapedText : name.right.escapedText;
4121041236
if (left) {
4121141237
const proto = left.flags & SymbolFlags.Value && getPropertyOfType(getTypeOfSymbol(left), "prototype" as __String);
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
//// [tests/cases/conformance/jsdoc/jsdocLinkTag1.ts] ////
2+
3+
//// [a.ts]
4+
export interface A {}
5+
6+
//// [b.ts]
7+
import type { A } from "./a";
8+
9+
/** {@link A} */
10+
export interface B {}
11+
12+
13+
//// [a.js]
14+
"use strict";
15+
exports.__esModule = true;
16+
//// [b.js]
17+
"use strict";
18+
exports.__esModule = true;
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
=== /a.ts ===
2+
export interface A {}
3+
>A : Symbol(A, Decl(a.ts, 0, 0))
4+
5+
=== /b.ts ===
6+
import type { A } from "./a";
7+
>A : Symbol(A, Decl(b.ts, 0, 13))
8+
9+
/** {@link A} */
10+
export interface B {}
11+
>B : Symbol(B, Decl(b.ts, 0, 29))
12+
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
=== /a.ts ===
2+
export interface A {}
3+
No type information for this code.
4+
No type information for this code.=== /b.ts ===
5+
import type { A } from "./a";
6+
>A : A
7+
8+
/** {@link A} */
9+
export interface B {}
10+
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
=== /a.js ===
2+
export class A {}
3+
>A : Symbol(A, Decl(a.js, 0, 0))
4+
5+
=== /b.js ===
6+
import { A } from "./a";
7+
>A : Symbol(A, Decl(b.js, 0, 8))
8+
9+
/** {@link A} */
10+
export class B {}
11+
>B : Symbol(B, Decl(b.js, 0, 24))
12+
13+
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
=== /a.js ===
2+
export class A {}
3+
>A : A
4+
5+
=== /b.js ===
6+
import { A } from "./a";
7+
>A : typeof A
8+
9+
/** {@link A} */
10+
export class B {}
11+
>B : B
12+
13+
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
//// [tests/cases/conformance/jsdoc/jsdocLinkTag3.ts] ////
2+
3+
//// [a.ts]
4+
export interface A {}
5+
6+
//// [b.ts]
7+
import type { A } from "./a";
8+
9+
/**
10+
* @param {number} a - see {@link A}
11+
*/
12+
export function foo(a: string) {}
13+
14+
15+
//// [a.js]
16+
"use strict";
17+
exports.__esModule = true;
18+
//// [b.js]
19+
"use strict";
20+
exports.__esModule = true;
21+
exports.foo = void 0;
22+
/**
23+
* @param {number} a - see {@link A}
24+
*/
25+
function foo(a) { }
26+
exports.foo = foo;
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
=== /a.ts ===
2+
export interface A {}
3+
>A : Symbol(A, Decl(a.ts, 0, 0))
4+
5+
=== /b.ts ===
6+
import type { A } from "./a";
7+
>A : Symbol(A, Decl(b.ts, 0, 13))
8+
9+
/**
10+
* @param {number} a - see {@link A}
11+
*/
12+
export function foo(a: string) {}
13+
>foo : Symbol(foo, Decl(b.ts, 0, 29))
14+
>a : Symbol(a, Decl(b.ts, 5, 20))
15+
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
=== /a.ts ===
2+
export interface A {}
3+
No type information for this code.
4+
No type information for this code.=== /b.ts ===
5+
import type { A } from "./a";
6+
>A : A
7+
8+
/**
9+
* @param {number} a - see {@link A}
10+
*/
11+
export function foo(a: string) {}
12+
>foo : (a: string) => void
13+
>a : string
14+
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// @noUnusedLocals: true
2+
3+
// @filename: /a.ts
4+
export interface A {}
5+
6+
// @filename: /b.ts
7+
import type { A } from "./a";
8+
9+
/** {@link A} */
10+
export interface B {}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// @noUnusedLocals: true
2+
// @target: esnext
3+
// @allowJs: true
4+
// @checkJs: true
5+
// @noEmit: true
6+
7+
// @filename: /a.js
8+
export class A {}
9+
10+
// @filename: /b.js
11+
import { A } from "./a";
12+
13+
/** {@link A} */
14+
export class B {}
15+
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// @noUnusedLocals: true
2+
3+
// @filename: /a.ts
4+
export interface A {}
5+
6+
// @filename: /b.ts
7+
import type { A } from "./a";
8+
9+
/**
10+
* @param {number} a - see {@link A}
11+
*/
12+
export function foo(a: string) {}

0 commit comments

Comments
 (0)