@@ -12,7 +12,16 @@ import {
1212import { lexLineComments } from "./lineLexer.js" ;
1313import { parseComment } from "./parser.js" ;
1414import 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" ;
1625import type { Context } from "../context.js" ;
1726
1827export 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+
104181function getCommentWithCache (
105182 discovered : DiscoveredComment | undefined ,
106183 context : CommentContextOptionalChecker ,
@@ -128,13 +205,15 @@ function getCommentWithCache(
128205function 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