Skip to content

Commit c46a01d

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

20 files changed

+278
-7
lines changed

src/compiler/checker.ts

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -36955,6 +36955,12 @@ namespace ts {
3695536955
checkSourceElement(node.typeExpression);
3695636956
}
3695736957

36958+
function checkJSDocLinkLikeTag(node: JSDocLink | JSDocLinkCode | JSDocLinkPlain) {
36959+
if (node.name) {
36960+
resolveJSDocMemberName(node.name, /*ignoreErrors*/ true);
36961+
}
36962+
}
36963+
3695836964
function checkJSDocParameterTag(node: JSDocParameterTag) {
3695936965
checkSourceElement(node.typeExpression);
3696036966
}
@@ -41172,9 +41178,15 @@ namespace ts {
4117241178
}
4117341179

4117441180
function checkSourceElementWorker(node: Node): void {
41175-
if (isInJSFile(node)) {
41176-
forEach((node as JSDocContainer).jsDoc, ({ tags }) => forEach(tags, checkSourceElement));
41177-
}
41181+
forEach((node as JSDocContainer).jsDoc, ({ comment, tags }) => {
41182+
checkJSDocCommentWorker(comment);
41183+
forEach(tags, tag => {
41184+
checkJSDocCommentWorker(tag.comment);
41185+
if (isInJSFile(node)) {
41186+
checkSourceElement(tag);
41187+
}
41188+
});
41189+
});
4117841190

4117941191
const kind = node.kind;
4118041192
if (cancellationToken) {
@@ -41262,6 +41274,10 @@ namespace ts {
4126241274
return checkJSDocTemplateTag(node as JSDocTemplateTag);
4126341275
case SyntaxKind.JSDocTypeTag:
4126441276
return checkJSDocTypeTag(node as JSDocTypeTag);
41277+
case SyntaxKind.JSDocLink:
41278+
case SyntaxKind.JSDocLinkCode:
41279+
case SyntaxKind.JSDocLinkPlain:
41280+
return checkJSDocLinkLikeTag(node as JSDocLink | JSDocLinkCode | JSDocLinkPlain);
4126541281
case SyntaxKind.JSDocParameterTag:
4126641282
return checkJSDocParameterTag(node as JSDocParameterTag);
4126741283
case SyntaxKind.JSDocPropertyTag:
@@ -41357,6 +41373,16 @@ namespace ts {
4135741373
}
4135841374
}
4135941375

41376+
function checkJSDocCommentWorker(node: string | readonly JSDocComment[] | undefined) {
41377+
if (isArray(node)) {
41378+
forEach(node, tag => {
41379+
if (isJSDocLinkLike(tag)) {
41380+
checkSourceElement(tag);
41381+
}
41382+
});
41383+
}
41384+
}
41385+
4136041386
function checkJSDocTypeIsInJsFile(node: Node): void {
4136141387
if (!isInJSFile(node)) {
4136241388
grammarErrorOnNode(node, Diagnostics.JSDoc_types_can_only_be_used_inside_documentation_comments);
@@ -41990,7 +42016,7 @@ namespace ts {
4199042016
if (!result && isJSDoc) {
4199142017
const container = findAncestor(name, or(isClassLike, isInterfaceDeclaration));
4199242018
if (container) {
41993-
return resolveJSDocMemberName(name, getSymbolOfNode(container));
42019+
return resolveJSDocMemberName(name, /*ignoreErrors*/ false, getSymbolOfNode(container));
4199442020
}
4199542021
}
4199642022
return result;
@@ -42039,19 +42065,19 @@ namespace ts {
4203942065
*
4204042066
* For unqualified names, a container K may be provided as a second argument.
4204142067
*/
42042-
function resolveJSDocMemberName(name: EntityName | JSDocMemberName, container?: Symbol): Symbol | undefined {
42068+
function resolveJSDocMemberName(name: EntityName | JSDocMemberName, ignoreErrors?: boolean, container?: Symbol): Symbol | undefined {
4204342069
if (isEntityName(name)) {
4204442070
// resolve static values first
4204542071
const meaning = SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Value;
42046-
let symbol = resolveEntityName(name, meaning, /*ignoreErrors*/ false, /*dontResolveAlias*/ true, getHostSignatureFromJSDoc(name));
42072+
let symbol = resolveEntityName(name, meaning, ignoreErrors, /*dontResolveAlias*/ true, getHostSignatureFromJSDoc(name));
4204742073
if (!symbol && isIdentifier(name) && container) {
4204842074
symbol = getMergedSymbol(getSymbol(getExportsOfSymbol(container), name.escapedText, meaning));
4204942075
}
4205042076
if (symbol) {
4205142077
return symbol;
4205242078
}
4205342079
}
42054-
const left = isIdentifier(name) ? container : resolveJSDocMemberName(name.left);
42080+
const left = isIdentifier(name) ? container : resolveJSDocMemberName(name.left, ignoreErrors, container);
4205542081
const right = isIdentifier(name) ? name.escapedText : name.right.escapedText;
4205642082
if (left) {
4205742083
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: 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;

0 commit comments

Comments
 (0)