Skip to content

Commit d719d09

Browse files
committed
WIP Resolve local links via TypeChecker
Issue #3113
1 parent e118cfd commit d719d09

1 file changed

Lines changed: 83 additions & 1 deletion

File tree

src/lib/converter/comments/index.ts

Lines changed: 83 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,16 @@ import {
1212
import { lexLineComments } from "./lineLexer.js";
1313
import { parseComment } from "./parser.js";
1414
import type { FileRegistry } from "../../models/FileRegistry.js";
15-
import { assertNever, i18n, Logger, setUnion } from "#utils";
15+
import {
16+
assertNever,
17+
i18n,
18+
Logger,
19+
type MeaningKeyword,
20+
parseDeclarationReference,
21+
setUnion,
22+
type SymbolReference,
23+
} from "#utils";
24+
import { resolveAliasedSymbol } from "../utils/symbols.js";
1625
import type { Context } from "../context.js";
1726

1827
export interface CommentParserConfig {
@@ -39,6 +48,7 @@ export interface CommentContextOptionalChecker {
3948
config: CommentParserConfig;
4049
logger: Logger;
4150
checker?: ts.TypeChecker | undefined;
51+
node?: ts.Node | undefined;
4252
files: FileRegistry;
4353
createSymbolId: Context["createSymbolId"];
4454
}
@@ -85,13 +95,15 @@ function getCommentIgnoringCacheNoDiscoveryId(
8595
file,
8696
context,
8797
);
98+
if (!jsDoc) resolveLocalLinks(comment, context);
8899
break;
89100
case ts.SyntaxKind.SingleLineCommentTrivia:
90101
comment = parseComment(
91102
lexLineComments(file.text, ranges),
92103
file,
93104
context,
94105
);
106+
resolveLocalLinks(comment, context);
95107
break;
96108
default:
97109
assertNever(ranges[0].kind);
@@ -101,6 +113,71 @@ function getCommentIgnoringCacheNoDiscoveryId(
101113
return comment;
102114
}
103115

116+
function resolveLocalLinks(comment: Comment, context: CommentContextOptionalChecker) {
117+
const { checker, node } = context;
118+
if (!checker || !node) return;
119+
for (const elt of comment.summary) {
120+
if (elt.kind === "inline-tag" && elt.tag === "@link" && !elt.target) {
121+
let pos = 0;
122+
while (pos < elt.text.length && ts.isWhiteSpaceLike(elt.text.charCodeAt(pos))) {
123+
pos++;
124+
}
125+
const parsed = parseDeclarationReference(elt.text, pos, elt.text.length);
126+
if (parsed && parsed[0].resolutionStart === "local" && parsed[0].symbolReference) {
127+
const resolved = resolveLocalLink(parsed[0].symbolReference, node, checker);
128+
if (resolved) {
129+
elt.target = context.createSymbolId(resolveAliasedSymbol(resolved, checker));
130+
}
131+
}
132+
}
133+
}
134+
}
135+
136+
function resolveLocalLink(ref: SymbolReference, node: ts.Node, checker: ts.TypeChecker): ts.Symbol | undefined {
137+
if (!ref.path || ref.meaning?.label) return undefined;
138+
let symbols: ts.Symbol[] = [];
139+
const add = (sym: ts.Symbol | undefined) => {
140+
if (sym) {
141+
sym = resolveAliasedSymbol(sym, checker);
142+
if (!symbols.includes(sym)) symbols.push(sym);
143+
}
144+
};
145+
add(checker.resolveName(ref.path[0].path, node, ts.SymbolFlags.Value, true));
146+
add(checker.resolveName(ref.path[0].path, node, ts.SymbolFlags.Type, true));
147+
add(checker.resolveName(ref.path[0].path, node, ts.SymbolFlags.Namespace, true));
148+
for (let i = 1; i < ref.path.length && symbols.length; i++) {
149+
const { path, navigation } = ref.path[i];
150+
const prev = symbols;
151+
symbols = [];
152+
for (const sym of prev) {
153+
if (navigation !== "~") {
154+
add(sym.members?.get(path as ts.__String));
155+
}
156+
if (navigation !== "#") {
157+
add(sym.exports?.get(path as ts.__String));
158+
}
159+
}
160+
}
161+
const filter = ref.meaning?.keyword && meaningSymbolFlags[ref.meaning.keyword];
162+
if (filter != null) {
163+
symbols = symbols.filter(sym => sym.flags & filter);
164+
}
165+
return symbols.length ? symbols[0] : undefined;
166+
}
167+
168+
const meaningSymbolFlags: { [meaning in MeaningKeyword]?: ts.SymbolFlags } = {
169+
class: ts.SymbolFlags.Class,
170+
interface: ts.SymbolFlags.Interface,
171+
type: ts.SymbolFlags.Type,
172+
enum: ts.SymbolFlags.Enum,
173+
namespace: ts.SymbolFlags.Namespace,
174+
function: ts.SymbolFlags.Function,
175+
var: ts.SymbolFlags.Variable,
176+
constructor: ts.SymbolFlags.Constructor,
177+
member: ts.SymbolFlags.ClassMember | ts.SymbolFlags.EnumMember,
178+
call: ts.SymbolFlags.Signature,
179+
};
180+
104181
function getCommentWithCache(
105182
discovered: DiscoveredComment | undefined,
106183
context: CommentContextOptionalChecker,
@@ -128,13 +205,15 @@ function getCommentWithCache(
128205
function getCommentImpl(
129206
commentSource: DiscoveredComment | undefined,
130207
moduleComment: boolean,
208+
node: ts.Node | undefined,
131209
context: CommentContext,
132210
) {
133211
const comment = getCommentWithCache(
134212
commentSource,
135213
{
136214
...context,
137215
checker: context.config.useTsLinkResolution ? context.checker : undefined,
216+
node,
138217
},
139218
);
140219

@@ -205,6 +284,7 @@ export function getComment(
205284
!context.config.suppressCommentWarningsInDeclarationFiles,
206285
),
207286
isModule,
287+
declarations.length ? declarations[0] : undefined,
208288
context,
209289
);
210290

@@ -226,6 +306,7 @@ export function getNodeComment(
226306
return getCommentImpl(
227307
discoverNodeComment(node, context.config.commentStyle),
228308
moduleComment,
309+
node,
229310
context,
230311
);
231312
}
@@ -295,6 +376,7 @@ export function getSignatureComment(
295376
return getCommentImpl(
296377
discoverSignatureComment(declaration, context.checker, context.config.commentStyle),
297378
false,
379+
declaration,
298380
context,
299381
);
300382
}

0 commit comments

Comments
 (0)