Skip to content

Commit b9f6a92

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

16 files changed

+260
-3
lines changed

src/compiler/checker.ts

Lines changed: 38 additions & 3 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+
markJSDocLinkNameAsReferenced(node.name);
36206+
}
36207+
}
36208+
3620336209
function checkJSDocParameterTag(node: JSDocParameterTag) {
3620436210
checkSourceElement(node.typeExpression);
3620536211
if (!getParameterSymbolFromJSDoc(node)) {
@@ -36284,6 +36290,15 @@ namespace ts {
3628436290
}
3628536291
}
3628636292

36293+
function markJSDocLinkNameAsReferenced(name: EntityName | JSDocMemberName) {
36294+
if (isEntityName(name)) {
36295+
resolveEntityName(name, SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Value, /*ignoreErrors*/ true, /*dontResolveAlias*/ true);
36296+
}
36297+
else {
36298+
markJSDocLinkNameAsReferenced(name.left);
36299+
}
36300+
}
36301+
3628736302
function getIdentifierFromEntityNameExpression(node: Identifier | PropertyAccessExpression): Identifier | PrivateIdentifier;
3628836303
function getIdentifierFromEntityNameExpression(node: Expression): Identifier | PrivateIdentifier | undefined;
3628936304
function getIdentifierFromEntityNameExpression(node: Expression): Identifier | PrivateIdentifier | undefined {
@@ -40347,9 +40362,15 @@ namespace ts {
4034740362
}
4034840363

4034940364
function checkSourceElementWorker(node: Node): void {
40350-
if (isInJSFile(node)) {
40351-
forEach((node as JSDocContainer).jsDoc, ({ tags }) => forEach(tags, checkSourceElement));
40352-
}
40365+
forEach((node as JSDocContainer).jsDoc, ({ comment, tags }) => {
40366+
checkJSDocCommentWorker(comment);
40367+
forEach(tags, tag => {
40368+
checkJSDocCommentWorker(tag.comment);
40369+
if (isInJSFile(node)) {
40370+
checkSourceElement(tag);
40371+
}
40372+
});
40373+
});
4035340374

4035440375
const kind = node.kind;
4035540376
if (cancellationToken) {
@@ -40437,6 +40458,10 @@ namespace ts {
4043740458
return checkJSDocTemplateTag(node as JSDocTemplateTag);
4043840459
case SyntaxKind.JSDocTypeTag:
4043940460
return checkJSDocTypeTag(node as JSDocTypeTag);
40461+
case SyntaxKind.JSDocLink:
40462+
case SyntaxKind.JSDocLinkCode:
40463+
case SyntaxKind.JSDocLinkPlain:
40464+
return checkJSDocLinkLikeTag(node as JSDocLink | JSDocLinkCode | JSDocLinkPlain);
4044040465
case SyntaxKind.JSDocParameterTag:
4044140466
return checkJSDocParameterTag(node as JSDocParameterTag);
4044240467
case SyntaxKind.JSDocPropertyTag:
@@ -40532,6 +40557,16 @@ namespace ts {
4053240557
}
4053340558
}
4053440559

40560+
function checkJSDocCommentWorker(node: string | readonly JSDocComment[] | undefined) {
40561+
if (isArray(node)) {
40562+
forEach(node, tag => {
40563+
if (isJSDocLinkLike(tag)) {
40564+
checkSourceElement(tag);
40565+
}
40566+
});
40567+
}
40568+
}
40569+
4053540570
function checkJSDocTypeIsInJsFile(node: Node): void {
4053640571
if (!isInJSFile(node)) {
4053740572
grammarErrorOnNode(node, Diagnostics.JSDoc_types_can_only_be_used_inside_documentation_comments);
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: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
//// [tests/cases/conformance/jsdoc/jsdocLinkTag4.ts] ////
2+
3+
//// [a.ts]
4+
export interface A {}
5+
6+
//// [b.ts]
7+
import * as a from "./a";
8+
9+
/**
10+
* @param {number} a - see {@link a.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.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 * as a from "./a";
7+
>a : Symbol(a, Decl(b.ts, 0, 6))
8+
9+
/**
10+
* @param {number} a - see {@link a.A}
11+
*/
12+
export function foo(a: string) {}
13+
>foo : Symbol(foo, Decl(b.ts, 0, 25))
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 * as a from "./a";
6+
>a : typeof a
7+
8+
/**
9+
* @param {number} a - see {@link a.A}
10+
*/
11+
export function foo(a: string) {}
12+
>foo : (a: string) => void
13+
>a : string
14+
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// @noUnusedLocals: true
2+
// @filename: /a.ts
3+
export interface A {}
4+
5+
// @filename: /b.ts
6+
import type { A } from "./a";
7+
8+
/** {@link A} */
9+
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+
// @checkJs: true
3+
// @allowJs: true
4+
// @target: esnext
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: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// @noUnusedLocals: true
2+
// @filename: /a.ts
3+
export interface A {}
4+
5+
// @filename: /b.ts
6+
import type { A } from "./a";
7+
8+
/**
9+
* @param {number} a - see {@link A}
10+
*/
11+
export function foo(a: string) {}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// @noUnusedLocals: true
2+
// @filename: /a.ts
3+
export interface A {}
4+
5+
// @filename: /b.ts
6+
import * as a from "./a";
7+
8+
/**
9+
* @param {number} a - see {@link a.A}
10+
*/
11+
export function foo(a: string) {}

0 commit comments

Comments
 (0)