@@ -5,15 +5,20 @@ import { ConverterEvents } from "../converter-events";
5
5
import {
6
6
CommentDisplayPart ,
7
7
ContainerReflection ,
8
+ DeclarationReflection ,
8
9
InlineTagDisplayPart ,
9
10
ReflectionKind ,
10
11
} from "../../models" ;
11
12
import {
12
13
ComponentPath ,
13
14
DeclarationReference ,
15
+ Meaning ,
16
+ MeaningKeyword ,
14
17
parseDeclarationReference ,
15
18
} from "../comments/declarationReference" ;
16
19
import ts = require( "typescript" ) ;
20
+ import { ok } from "assert" ;
21
+ import { BindOption , filterMap , ValidationOptions } from "../../utils" ;
17
22
18
23
const urlPrefix = / ^ ( h t t p | f t p ) s ? : \/ \/ / ;
19
24
const brackets = / \[ \[ ( [ ^ \] ] + ) \] \] / g;
@@ -22,6 +27,9 @@ const brackets = /\[\[([^\]]+)\]\]/g;
22
27
*/
23
28
@Component ( { name : "link-resolver" } )
24
29
export class LinkResolverPlugin extends ConverterComponent {
30
+ @BindOption ( "validation" )
31
+ validation ! : ValidationOptions ;
32
+
25
33
/**
26
34
* Create a new LinkResolverPlugin instance.
27
35
*/
@@ -163,7 +171,11 @@ export class LinkResolverPlugin extends ConverterComponent {
163
171
part . tag === "@linkcode" ||
164
172
part . tag === "@linkplain"
165
173
) {
166
- return resolveLinkTag ( reflection , part ) ;
174
+ return resolveLinkTag ( reflection , part , ( msg : string ) => {
175
+ if ( this . validation . invalidLink ) {
176
+ this . application . logger . warn ( msg ) ;
177
+ }
178
+ } ) ;
167
179
}
168
180
}
169
181
@@ -199,7 +211,11 @@ export class LinkResolverPlugin extends ConverterComponent {
199
211
}
200
212
}
201
213
202
- function resolveLinkTag ( reflection : Reflection , part : InlineTagDisplayPart ) {
214
+ function resolveLinkTag (
215
+ reflection : Reflection ,
216
+ part : InlineTagDisplayPart ,
217
+ warn : ( message : string ) => void
218
+ ) {
203
219
let pos = 0 ;
204
220
const end = part . text . length ;
205
221
@@ -219,8 +235,15 @@ function resolveLinkTag(reflection: Reflection, part: InlineTagDisplayPart) {
219
235
}
220
236
221
237
// If resolution via a declaration reference failed, revert to the legacy "split and check"
222
- // method... should probably warn here.
223
- if ( ! target ) return legacyResolveLinkTag ( reflection , part ) ;
238
+ // method... this should go away in 0.24, once people have had a chance to migrate any failing links.
239
+ if ( ! target ) {
240
+ warn (
241
+ `Failed to resolve {@link ${
242
+ part . text
243
+ } } in ${ reflection . getFriendlyFullName ( ) } with declaration references. This link will break in v0.24.`
244
+ ) ;
245
+ return legacyResolveLinkTag ( reflection , part ) ;
246
+ }
224
247
225
248
// Remaining text after an optional pipe is the link text, so advance
226
249
// until that's consumed.
@@ -261,58 +284,201 @@ function resolveDeclarationReference(
261
284
reflection : Reflection ,
262
285
ref : DeclarationReference
263
286
) : Reflection | undefined {
264
- let refl : Reflection | undefined = reflection ;
287
+ let high : Reflection [ ] = [ ] ;
288
+ let low : Reflection [ ] = [ ] ;
265
289
266
290
if ( ref . moduleSource ) {
267
- refl = refl . project . children ?. find ( ( c ) => c . name === ref . moduleSource ) ;
291
+ high =
292
+ reflection . project . children ?. filter (
293
+ ( c ) =>
294
+ c . kindOf ( ReflectionKind . SomeModule ) &&
295
+ c . name === ref . moduleSource
296
+ ) || [ ] ;
268
297
} else if ( ref . resolutionStart === "global" ) {
269
- refl = refl . project ;
270
- }
298
+ high . push ( reflection . project ) ;
299
+ } else {
300
+ ok ( ref . resolutionStart === "local" ) ;
301
+ // TypeScript's behavior is to first try to resolve links via variable scope, and then
302
+ // if the link still hasn't been found, check either siblings (if comment belongs to a member)
303
+ // or otherwise children.
304
+ let refl : Reflection | undefined = reflection ;
305
+ if ( refl . kindOf ( ReflectionKind . ExportContainer ) ) {
306
+ high . push ( refl ) ;
307
+ }
308
+ while ( refl . parent ) {
309
+ refl = refl . parent ;
310
+ if ( refl . kindOf ( ReflectionKind . ExportContainer ) ) {
311
+ high . push ( refl ) ;
312
+ }
313
+ }
271
314
272
- if ( ! refl ) return ;
315
+ if ( reflection . kindOf ( ReflectionKind . SomeMember ) ) {
316
+ high . push ( reflection . parent ! ) ;
317
+ } else if (
318
+ reflection . kindOf ( ReflectionKind . SomeSignature ) &&
319
+ reflection . parent ! . kindOf ( ReflectionKind . SomeMember )
320
+ ) {
321
+ high . push ( reflection . parent ! . parent ! ) ;
322
+ } else if ( high [ 0 ] !== reflection ) {
323
+ high . push ( reflection ) ;
324
+ }
325
+ }
273
326
274
327
if ( ref . symbolReference ) {
275
- let targets = [ refl ] ;
276
328
for ( const part of ref . symbolReference . path || [ ] ) {
277
- targets = targets . flatMap ( ( refl ) =>
278
- resolveSymbolReferencePart ( refl , part )
279
- ) ;
329
+ const high2 = high ;
330
+ high = [ ] ;
331
+ const low2 = low ;
332
+ low = [ ] ;
333
+
334
+ for ( const refl of high2 ) {
335
+ const resolved = resolveSymbolReferencePart ( refl , part ) ;
336
+ high . push ( ...resolved . high ) ;
337
+ low . push ( ...resolved . low ) ;
338
+ }
339
+
340
+ for ( const refl of low2 ) {
341
+ const resolved = resolveSymbolReferencePart ( refl , part ) ;
342
+ low . push ( ...resolved . high ) ;
343
+ low . push ( ...resolved . low ) ;
344
+ }
280
345
}
281
346
282
- // TODO: meaning
283
- return targets [ 0 ] ;
347
+ if ( ref . symbolReference . meaning ) {
348
+ high = filterMapByMeaning ( high , ref . symbolReference . meaning ) ;
349
+ low = filterMapByMeaning ( low , ref . symbolReference . meaning ) ;
350
+ }
284
351
}
285
352
286
- return refl ;
353
+ return high [ 0 ] || low [ 0 ] ;
354
+ }
355
+
356
+ function filterMapByMeaning (
357
+ reflections : Reflection [ ] ,
358
+ meaning : Meaning
359
+ ) : Reflection [ ] {
360
+ return filterMap ( reflections , ( refl ) : Reflection | undefined => {
361
+ const kwResolved = resolveKeyword ( refl , meaning . keyword ) || [ ] ;
362
+ return kwResolved [ meaning . index || 0 ] ;
363
+ } ) ;
364
+ }
365
+
366
+ function resolveKeyword (
367
+ refl : Reflection ,
368
+ kw : MeaningKeyword | undefined
369
+ ) : Reflection [ ] | undefined {
370
+ switch ( kw ) {
371
+ case undefined :
372
+ return [ refl ] ;
373
+ case "class" :
374
+ if ( refl . kindOf ( ReflectionKind . Class ) ) return [ refl ] ;
375
+ break ;
376
+ case "interface" :
377
+ if ( refl . kindOf ( ReflectionKind . Interface ) ) return [ refl ] ;
378
+ break ;
379
+ case "type" :
380
+ if ( refl . kindOf ( ReflectionKind . SomeType ) ) return [ refl ] ;
381
+ break ;
382
+ case "enum" :
383
+ if ( refl . kindOf ( ReflectionKind . Enum ) ) return [ refl ] ;
384
+ break ;
385
+ case "namespace" :
386
+ if ( refl . kindOf ( ReflectionKind . SomeModule ) ) return [ refl ] ;
387
+ break ;
388
+ case "function" :
389
+ if ( refl . kindOf ( ReflectionKind . FunctionOrMethod ) ) {
390
+ return ( refl as DeclarationReflection ) . signatures ;
391
+ }
392
+ break ;
393
+ case "var" :
394
+ if ( refl . kindOf ( ReflectionKind . Variable ) ) return [ refl ] ;
395
+ break ;
396
+
397
+ case "new" :
398
+ case "constructor" :
399
+ if ( refl . kindOf ( ReflectionKind . Class ) ) {
400
+ const ctor = ( refl as ContainerReflection ) . children ?. find ( ( c ) =>
401
+ c . kindOf ( ReflectionKind . Constructor )
402
+ ) ;
403
+ return ( ctor as DeclarationReflection ) ?. signatures ;
404
+ }
405
+ if ( refl . kindOf ( ReflectionKind . Constructor ) ) {
406
+ return ( refl as DeclarationReflection ) . signatures ;
407
+ }
408
+ break ;
409
+
410
+ case "member" :
411
+ if ( refl . kindOf ( ReflectionKind . SomeMember ) ) return [ refl ] ;
412
+ break ;
413
+ case "event" :
414
+ if ( refl . comment ?. hasModifier ( "@event" ) ) return [ refl ] ;
415
+ break ;
416
+ case "call" :
417
+ return ( refl as DeclarationReflection ) . signatures ;
418
+
419
+ case "index" :
420
+ if ( ( refl as DeclarationReflection ) . indexSignature ) {
421
+ return [ ( refl as DeclarationReflection ) . indexSignature ! ] ;
422
+ }
423
+ break ;
424
+
425
+ case "complex" :
426
+ if ( refl . kindOf ( ReflectionKind . SomeType ) ) return [ refl ] ;
427
+ break ;
428
+ }
287
429
}
288
430
289
431
function resolveSymbolReferencePart (
290
432
refl : Reflection ,
291
433
path : ComponentPath
292
- ) : Reflection [ ] {
293
- if ( ! ( refl instanceof ContainerReflection ) || ! refl . children ) return [ ] ;
434
+ ) : { high : Reflection [ ] ; low : Reflection [ ] } {
435
+ let high : Reflection [ ] = [ ] ;
436
+ let low : Reflection [ ] = [ ] ;
437
+
438
+ if ( ! ( refl instanceof ContainerReflection ) || ! refl . children ) {
439
+ return { high, low } ;
440
+ }
294
441
295
442
switch ( path . navigation ) {
296
443
// Grammar says resolve via "exports"... as always, reality is more complicated.
297
444
// Check exports first, but also allow this as a general purpose "some child" operator
298
445
// so that resolution doesn't behave very poorly with projects using JSDoc style resolution.
299
446
// Also is more consistent with how TypeScript resolves link tags.
300
447
case "." :
301
- return [ ] ; // TODO: Finish me
448
+ high = refl . children . filter (
449
+ ( r ) =>
450
+ r . name === path . path &&
451
+ ( r . kindOf ( ReflectionKind . SomeExport ) || r . flags . isStatic )
452
+ ) ;
453
+ low = refl . children . filter (
454
+ ( r ) =>
455
+ r . name === path . path &&
456
+ ( ! r . kindOf ( ReflectionKind . SomeExport ) || ! r . flags . isStatic )
457
+ ) ;
458
+ break ;
302
459
303
460
// Resolve via "members", interface children, class instance properties/accessors/methods,
304
461
// enum members, type literal properties
305
462
case "#" :
306
- return refl . children . filter ( ( r ) => {
307
- return r . kindOf ( ReflectionKind . SomeMember ) && ! r . flags . isStatic ;
463
+ high = refl . children . filter ( ( r ) => {
464
+ return (
465
+ r . name === path . path &&
466
+ r . kindOf ( ReflectionKind . SomeMember ) &&
467
+ ! r . flags . isStatic
468
+ ) ;
308
469
} ) ;
470
+ break ;
309
471
310
472
// Resolve via "locals"... treat this as a stricter `.` which only supports traversing
311
473
// module/namespace exports since TypeDoc doesn't support documenting locals.
312
474
case "~" :
313
- if ( refl . kindOf ( ReflectionKind . SomeModule ) ) {
314
- return refl . children . filter ( ( r ) => r . name === path . path ) || [ ] ;
475
+ if (
476
+ refl . kindOf ( ReflectionKind . SomeModule | ReflectionKind . Project )
477
+ ) {
478
+ high = refl . children . filter ( ( r ) => r . name === path . path ) ;
315
479
}
316
- return [ ] ;
480
+ break ;
317
481
}
482
+
483
+ return { high, low } ;
318
484
}
0 commit comments