feat(47558): check JSDoc link tags in TypeScript/JavaScript files (#47822)

This commit is contained in:
Oleksandr T
2022-06-06 19:04:18 +03:00
committed by GitHub
parent 3a8c630747
commit 62668c6c1d
20 changed files with 278 additions and 7 deletions

View File

@@ -37125,6 +37125,12 @@ namespace ts {
checkSourceElement(node.typeExpression);
}
function checkJSDocLinkLikeTag(node: JSDocLink | JSDocLinkCode | JSDocLinkPlain) {
if (node.name) {
resolveJSDocMemberName(node.name, /*ignoreErrors*/ true);
}
}
function checkJSDocParameterTag(node: JSDocParameterTag) {
checkSourceElement(node.typeExpression);
}
@@ -41350,9 +41356,15 @@ namespace ts {
}
function checkSourceElementWorker(node: Node): void {
if (isInJSFile(node)) {
forEach((node as JSDocContainer).jsDoc, ({ tags }) => forEach(tags, checkSourceElement));
}
forEach((node as JSDocContainer).jsDoc, ({ comment, tags }) => {
checkJSDocCommentWorker(comment);
forEach(tags, tag => {
checkJSDocCommentWorker(tag.comment);
if (isInJSFile(node)) {
checkSourceElement(tag);
}
});
});
const kind = node.kind;
if (cancellationToken) {
@@ -41440,6 +41452,10 @@ namespace ts {
return checkJSDocTemplateTag(node as JSDocTemplateTag);
case SyntaxKind.JSDocTypeTag:
return checkJSDocTypeTag(node as JSDocTypeTag);
case SyntaxKind.JSDocLink:
case SyntaxKind.JSDocLinkCode:
case SyntaxKind.JSDocLinkPlain:
return checkJSDocLinkLikeTag(node as JSDocLink | JSDocLinkCode | JSDocLinkPlain);
case SyntaxKind.JSDocParameterTag:
return checkJSDocParameterTag(node as JSDocParameterTag);
case SyntaxKind.JSDocPropertyTag:
@@ -41535,6 +41551,16 @@ namespace ts {
}
}
function checkJSDocCommentWorker(node: string | readonly JSDocComment[] | undefined) {
if (isArray(node)) {
forEach(node, tag => {
if (isJSDocLinkLike(tag)) {
checkSourceElement(tag);
}
});
}
}
function checkJSDocTypeIsInJsFile(node: Node): void {
if (!isInJSFile(node)) {
grammarErrorOnNode(node, Diagnostics.JSDoc_types_can_only_be_used_inside_documentation_comments);
@@ -42168,7 +42194,7 @@ namespace ts {
if (!result && isJSDoc) {
const container = findAncestor(name, or(isClassLike, isInterfaceDeclaration));
if (container) {
return resolveJSDocMemberName(name, getSymbolOfNode(container));
return resolveJSDocMemberName(name, /*ignoreErrors*/ false, getSymbolOfNode(container));
}
}
return result;
@@ -42217,11 +42243,11 @@ namespace ts {
*
* For unqualified names, a container K may be provided as a second argument.
*/
function resolveJSDocMemberName(name: EntityName | JSDocMemberName, container?: Symbol): Symbol | undefined {
function resolveJSDocMemberName(name: EntityName | JSDocMemberName, ignoreErrors?: boolean, container?: Symbol): Symbol | undefined {
if (isEntityName(name)) {
// resolve static values first
const meaning = SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Value;
let symbol = resolveEntityName(name, meaning, /*ignoreErrors*/ false, /*dontResolveAlias*/ true, getHostSignatureFromJSDoc(name));
let symbol = resolveEntityName(name, meaning, ignoreErrors, /*dontResolveAlias*/ true, getHostSignatureFromJSDoc(name));
if (!symbol && isIdentifier(name) && container) {
symbol = getMergedSymbol(getSymbol(getExportsOfSymbol(container), name.escapedText, meaning));
}
@@ -42229,7 +42255,7 @@ namespace ts {
return symbol;
}
}
const left = isIdentifier(name) ? container : resolveJSDocMemberName(name.left);
const left = isIdentifier(name) ? container : resolveJSDocMemberName(name.left, ignoreErrors, container);
const right = isIdentifier(name) ? name.escapedText : name.right.escapedText;
if (left) {
const proto = left.flags & SymbolFlags.Value && getPropertyOfType(getTypeOfSymbol(left), "prototype" as __String);

View File

@@ -0,0 +1,18 @@
//// [tests/cases/conformance/jsdoc/jsdocLinkTag1.ts] ////
//// [a.ts]
export interface A {}
//// [b.ts]
import type { A } from "./a";
/** {@link A} */
export interface B {}
//// [a.js]
"use strict";
exports.__esModule = true;
//// [b.js]
"use strict";
exports.__esModule = true;

View File

@@ -0,0 +1,12 @@
=== /a.ts ===
export interface A {}
>A : Symbol(A, Decl(a.ts, 0, 0))
=== /b.ts ===
import type { A } from "./a";
>A : Symbol(A, Decl(b.ts, 0, 13))
/** {@link A} */
export interface B {}
>B : Symbol(B, Decl(b.ts, 0, 29))

View File

@@ -0,0 +1,10 @@
=== /a.ts ===
export interface A {}
No type information for this code.
No type information for this code.=== /b.ts ===
import type { A } from "./a";
>A : A
/** {@link A} */
export interface B {}

View File

@@ -0,0 +1,13 @@
=== /a.js ===
export class A {}
>A : Symbol(A, Decl(a.js, 0, 0))
=== /b.js ===
import { A } from "./a";
>A : Symbol(A, Decl(b.js, 0, 8))
/** {@link A} */
export class B {}
>B : Symbol(B, Decl(b.js, 0, 24))

View File

@@ -0,0 +1,13 @@
=== /a.js ===
export class A {}
>A : A
=== /b.js ===
import { A } from "./a";
>A : typeof A
/** {@link A} */
export class B {}
>B : B

View File

@@ -0,0 +1,26 @@
//// [tests/cases/conformance/jsdoc/jsdocLinkTag3.ts] ////
//// [a.ts]
export interface A {}
//// [b.ts]
import type { A } from "./a";
/**
* @param {number} a - see {@link A}
*/
export function foo(a: string) {}
//// [a.js]
"use strict";
exports.__esModule = true;
//// [b.js]
"use strict";
exports.__esModule = true;
exports.foo = void 0;
/**
* @param {number} a - see {@link A}
*/
function foo(a) { }
exports.foo = foo;

View File

@@ -0,0 +1,15 @@
=== /a.ts ===
export interface A {}
>A : Symbol(A, Decl(a.ts, 0, 0))
=== /b.ts ===
import type { A } from "./a";
>A : Symbol(A, Decl(b.ts, 0, 13))
/**
* @param {number} a - see {@link A}
*/
export function foo(a: string) {}
>foo : Symbol(foo, Decl(b.ts, 0, 29))
>a : Symbol(a, Decl(b.ts, 5, 20))

View File

@@ -0,0 +1,14 @@
=== /a.ts ===
export interface A {}
No type information for this code.
No type information for this code.=== /b.ts ===
import type { A } from "./a";
>A : A
/**
* @param {number} a - see {@link A}
*/
export function foo(a: string) {}
>foo : (a: string) => void
>a : string

View File

@@ -0,0 +1,26 @@
//// [tests/cases/conformance/jsdoc/jsdocLinkTag4.ts] ////
//// [a.ts]
export interface A {}
//// [b.ts]
import * as a from "./a";
/**
* @param {number} a - see {@link a.A}
*/
export function foo(a: string) {}
//// [a.js]
"use strict";
exports.__esModule = true;
//// [b.js]
"use strict";
exports.__esModule = true;
exports.foo = void 0;
/**
* @param {number} a - see {@link a.A}
*/
function foo(a) { }
exports.foo = foo;

View File

@@ -0,0 +1,15 @@
=== /a.ts ===
export interface A {}
>A : Symbol(A, Decl(a.ts, 0, 0))
=== /b.ts ===
import * as a from "./a";
>a : Symbol(a, Decl(b.ts, 0, 6))
/**
* @param {number} a - see {@link a.A}
*/
export function foo(a: string) {}
>foo : Symbol(foo, Decl(b.ts, 0, 25))
>a : Symbol(a, Decl(b.ts, 5, 20))

View File

@@ -0,0 +1,14 @@
=== /a.ts ===
export interface A {}
No type information for this code.
No type information for this code.=== /b.ts ===
import * as a from "./a";
>a : typeof a
/**
* @param {number} a - see {@link a.A}
*/
export function foo(a: string) {}
>foo : (a: string) => void
>a : string

View File

@@ -0,0 +1,8 @@
//// [a.ts]
/** {@link UNRESOLVED_LINK} */
export interface A {}
//// [a.js]
"use strict";
exports.__esModule = true;

View File

@@ -0,0 +1,5 @@
=== /a.ts ===
/** {@link UNRESOLVED_LINK} */
export interface A {}
>A : Symbol(A, Decl(a.ts, 0, 0))

View File

@@ -0,0 +1,5 @@
=== /a.ts ===
/** {@link UNRESOLVED_LINK} */
No type information for this code.export interface A {}
No type information for this code.
No type information for this code.

View File

@@ -0,0 +1,9 @@
// @noUnusedLocals: true
// @filename: /a.ts
export interface A {}
// @filename: /b.ts
import type { A } from "./a";
/** {@link A} */
export interface B {}

View File

@@ -0,0 +1,15 @@
// @noUnusedLocals: true
// @checkJs: true
// @allowJs: true
// @target: esnext
// @noEmit: true
// @filename: /a.js
export class A {}
// @filename: /b.js
import { A } from "./a";
/** {@link A} */
export class B {}

View File

@@ -0,0 +1,11 @@
// @noUnusedLocals: true
// @filename: /a.ts
export interface A {}
// @filename: /b.ts
import type { A } from "./a";
/**
* @param {number} a - see {@link A}
*/
export function foo(a: string) {}

View File

@@ -0,0 +1,11 @@
// @noUnusedLocals: true
// @filename: /a.ts
export interface A {}
// @filename: /b.ts
import * as a from "./a";
/**
* @param {number} a - see {@link a.A}
*/
export function foo(a: string) {}

View File

@@ -0,0 +1,5 @@
// @noUnusedLocals: true
// @filename: /a.ts
/** {@link UNRESOLVED_LINK} */
export interface A {}