@@ -5,10 +5,6 @@ import * as typedoc from 'typedoc'
55import packageJson from '../packages/remix/package.json' with { type : 'json' }
66import * as prettier from 'prettier'
77
8- // TODO:
9- // - Handle sub-modules: `import { createCookie } from 'remix/cookie'`
10- // - Handle alias re-exports: `export { openFile as getFile } `
11-
128//#region Types
139
1410// Function parameter or Class property
@@ -104,10 +100,15 @@ async function main() {
104100 let { comments, apisToDocument } = createLookupMaps ( project )
105101
106102 // Prefer `remix` package exports over other package exports
107- getDuplicateAPIS ( apisToDocument ) . forEach ( ( dup ) => apisToDocument . delete ( dup ) )
103+ getDuplicateAPIs ( apisToDocument ) . forEach ( ( name ) => apisToDocument . delete ( name ) )
104+
105+ // Remove aliased APIs and only document the canonicals
106+ getAliasedAPIs ( comments ) . forEach ( ( name ) => apisToDocument . delete ( name ) )
108107
109108 // Parse JSDocs into DocumentedAPI instances we can write out to markdown
110- let documentedAPIs = [ ...apisToDocument ] . map ( ( name ) => getDocumentedAPI ( comments . get ( name ) ! ) )
109+ let documentedAPIs = [ ...apisToDocument ] . map ( ( name ) =>
110+ getDocumentedAPI ( name , comments . get ( name ) ! ) ,
111+ )
111112
112113 // Write out docs
113114 await writeMarkdownFiles ( documentedAPIs )
@@ -135,6 +136,9 @@ async function loadTypedocJson(): Promise<typedoc.ProjectReflection> {
135136 name : packageJson . name ,
136137 entryPoints : [ './packages/*' ] ,
137138 entryPointStrategy : 'packages' ,
139+ packageOptions : {
140+ blockTags : [ ...typedoc . OptionDefaults . blockTags , '@alias' ] ,
141+ } ,
138142 } )
139143 let reflection = await app . convert ( )
140144 invariant ( reflection , 'Failed to generate TypeDoc reflection from source code' )
@@ -155,7 +159,6 @@ async function loadTypedocJson(): Promise<typedoc.ProjectReflection> {
155159function createLookupMaps ( reflection : typedoc . ProjectReflection ) : Maps {
156160 let comments = new Map < string , typedoc . Reflection > ( )
157161 let apisToDocument = new Set < string > ( )
158- let referenceTargetMap = new Map < string , number > ( )
159162
160163 // Reflections we want to traverse through to find documented APIs
161164 let traverseKinds = new Set < typedoc . ReflectionKind > ( [
@@ -172,7 +175,7 @@ function createLookupMaps(reflection: typedoc.ProjectReflection): Maps {
172175
173176 return { comments, apisToDocument }
174177
175- function recurse ( node : typedoc . Reflection ) {
178+ function recurse ( node : typedoc . Reflection , alias ?: string ) {
176179 node . traverse ( ( child ) => {
177180 if (
178181 cliArgs . module &&
@@ -183,27 +186,25 @@ function createLookupMaps(reflection: typedoc.ProjectReflection): Maps {
183186 return
184187 }
185188
186- comments . set ( child . getFriendlyFullName ( ) , child )
189+ let apiName = alias || child . getFriendlyFullName ( )
190+ comments . set ( apiName , child )
187191
188- let indent = ' ' . repeat ( child . getFriendlyFullName ( ) . split ( '.' ) . length - 1 )
192+ let indent = ' ' . repeat ( apiName . split ( '.' ) . length - 1 )
189193 let logApi = ( suffix : string ) =>
190194 log (
191195 [
192196 `${ indent } [${ typedoc . ReflectionKind [ child . kind ] } ]` ,
193- child . getFriendlyFullName ( ) ,
197+ apiName ,
194198 `(${ child . id } )` ,
195199 `(${ suffix } )` ,
196200 ] . join ( ' ' ) ,
197201 )
198202
199203 // Reference types are aliases - stick them off into a separate map for post-processing
200- if (
201- child . kind === typedoc . ReflectionKind . Reference &&
202- '_target' in child &&
203- typeof child . _target === 'number'
204- ) {
205- logApi ( `reference to ${ child . _target } ` )
206- referenceTargetMap . set ( child . getFriendlyFullName ( ) , child . _target )
204+ if ( child . isReference ( ) ) {
205+ logApi ( `reference to ${ child . getTargetReflectionDeep ( ) . getFriendlyFullName ( ) } ` )
206+ let ref = child . getTargetReflection ( )
207+ recurse ( ref , child . getFriendlyFullName ( ) )
207208 return
208209 }
209210
@@ -215,7 +216,7 @@ function createLookupMaps(reflection: typedoc.ProjectReflection): Maps {
215216
216217 // Grab APIs with JSDoc comments that we should generate docs for
217218 if ( child . comment && ( ! cliArgs . api || child . name === cliArgs . api ) ) {
218- apisToDocument . add ( child . getFriendlyFullName ( ) )
219+ apisToDocument . add ( apiName )
219220 logApi ( `commenting` )
220221 }
221222
@@ -228,13 +229,13 @@ function createLookupMaps(reflection: typedoc.ProjectReflection): Maps {
228229}
229230
230231// Deduplicate APIs that are exported from multiple packages, preferring the remix package
231- function getDuplicateAPIS ( apisToDocument : Set < string > ) : Set < string > {
232+ function getDuplicateAPIs ( apisToDocument : Set < string > ) : Set < string > {
232233 let apisByName = new Map < string , string [ ] > ( )
233234 let duplicates = new Set < string > ( )
234235
235236 // Group APIs by short name
236237 for ( let fullName of apisToDocument ) {
237- let apiName = fullName . split ( '.' ) . slice ( 0 , - 1 ) [ 0 ]
238+ let apiName = getApiNameFromFullName ( fullName )
238239 apisByName . set ( apiName , [ ...( apisByName . get ( apiName ) || [ ] ) , fullName ] )
239240 }
240241
@@ -255,24 +256,50 @@ function getDuplicateAPIS(apisToDocument: Set<string>): Set<string> {
255256 }
256257 } else if ( ! remixAPI && fullNames . length > 1 ) {
257258 // Multiple non-remix packages export this API
258- warn ( `Multiple packages export ${ apiName } but none is remix : ${ fullNames . join ( ', ' ) } ` )
259+ warn ( `Multiple packages export ${ apiName } : ${ fullNames . join ( ', ' ) } ` )
259260 }
260261 }
261262
262263 return duplicates
263264}
264265
266+ function getAliasedAPIs ( comments : Map < string , typedoc . Reflection > ) : Set < string > {
267+ let aliasedAPIs = new Set < string > ( )
268+
269+ comments . forEach ( ( reflection , name ) => {
270+ let parts = name . split ( '.' )
271+ let apiName = parts . pop ( )
272+ let alias = reflection . comment ?. blockTags . find ( ( tag ) => tag . tag === '@alias' )
273+ if ( alias ) {
274+ // The canonical API should include `@alias`
275+ // We will generate a markdown doc for the canonical API, and not the aliases
276+ // The canonical doc will list the aliases names
277+ let aliasName = alias . content . reduce ( ( acc , part ) => {
278+ invariant ( part . kind === 'text' )
279+ return acc + part . text
280+ } , '' )
281+ if ( apiName !== aliasName ) {
282+ let aliasFullName = [ ...parts , aliasName ] . join ( '.' )
283+ log ( `Preferring canonical API \`${ name } \` over alias \`${ aliasFullName } \`` )
284+ aliasedAPIs . add ( aliasFullName )
285+ }
286+ }
287+ } )
288+
289+ return aliasedAPIs
290+ }
291+
265292//#region DocumentedAPI
266293
267294// Convert a typedoc reflection for a given node into a documentable instance
268- function getDocumentedAPI ( node : typedoc . Reflection ) : DocumentedAPI {
295+ function getDocumentedAPI ( fullName : string , node : typedoc . Reflection ) : DocumentedAPI {
269296 try {
270297 if ( node . isSignature ( ) ) {
271- return getDocumentedFunction ( node )
298+ return getDocumentedFunction ( fullName , node )
272299 }
273300
274301 if ( node . isDeclaration ( ) && node . kind === typedoc . ReflectionKind . Class ) {
275- return getDocumentedClass ( node )
302+ return getDocumentedClass ( fullName , node )
276303 }
277304
278305 throw new Error ( `Unsupported documented API kind: ${ typedoc . ReflectionKind [ node . kind ] } ` )
@@ -286,21 +313,27 @@ function getDocumentedAPI(node: typedoc.Reflection): DocumentedAPI {
286313 }
287314}
288315
289- function getDocumentedFunction ( node : typedoc . SignatureReflection ) : DocumentedFunction {
290- let method = getMethod ( node )
316+ function getDocumentedFunction (
317+ fullName : string ,
318+ node : typedoc . SignatureReflection ,
319+ ) : DocumentedFunction {
320+ let method = getMethod ( fullName , node )
291321 invariant ( method , `Failed to get method for function: ${ node . getFriendlyFullName ( ) } ` )
292322 return {
293323 type : 'function' ,
294- path : getDocumentedApiPath ( node ) ,
295- aliases : undefined ,
324+ path : getDocumentedApiPath ( fullName ) ,
325+ aliases : getDocumentedApiAliases ( node . comment ! ) ,
296326 example : node . comment ?. getTag ( '@example' ) ?. content
297327 ? processComment ( node . comment . getTag ( '@example' ) ! . content )
298328 : undefined ,
299329 ...method ,
300330 } satisfies DocumentedFunction
301331}
302332
303- function getDocumentedClass ( node : typedoc . DeclarationReflection ) : DocumentedClass {
333+ function getDocumentedClass (
334+ fullName : string ,
335+ node : typedoc . DeclarationReflection ,
336+ ) : DocumentedClass {
304337 let constructor : Method | undefined
305338 let properties : ParameterOrProperty [ ] = [ ]
306339 let methods : Method [ ] = [ ]
@@ -312,7 +345,7 @@ function getDocumentedClass(node: typedoc.DeclarationReflection): DocumentedClas
312345 signature ,
313346 `Missing constructor signature for class: ${ node . getFriendlyFullName ( ) } ` ,
314347 )
315- constructor = getMethod ( signature )
348+ constructor = getMethod ( fullName , signature )
316349 } else if ( child . kind === typedoc . ReflectionKind . Property ) {
317350 let property = getParameterOrProperty ( child )
318351 if ( property ) {
@@ -326,7 +359,7 @@ function getDocumentedClass(node: typedoc.DeclarationReflection): DocumentedClas
326359 } else if ( child . kind === typedoc . ReflectionKind . Method ) {
327360 let signature = child . getAllSignatures ( ) [ 0 ]
328361 invariant ( `Missing method signature for class: ${ child . getFriendlyFullName ( ) } ` )
329- let method = getMethod ( signature )
362+ let method = getMethod ( fullName , signature )
330363 if ( method ) {
331364 methods . push ( method )
332365 }
@@ -340,19 +373,35 @@ function getDocumentedClass(node: typedoc.DeclarationReflection): DocumentedClas
340373
341374 return {
342375 type : 'class' ,
343- aliases : undefined ,
376+ aliases : getDocumentedApiAliases ( node . comment ! ) ,
344377 example : undefined ,
345- path : getDocumentedApiPath ( node ) ,
346- name : node . name ,
378+ path : getDocumentedApiPath ( fullName ) ,
379+ name : getApiNameFromFullName ( fullName ) ,
347380 description : getDocumentedApiDescription ( node . comment ! ) ,
348381 constructor,
349382 properties,
350383 methods,
351384 }
352385}
353386
354- function getDocumentedApiPath ( node : typedoc . Reflection ) : string {
355- let nameParts = node . getFriendlyFullName ( ) . split ( '.' )
387+ function getDocumentedApiAliases ( typedocComment : typedoc . Comment ) : string [ ] | undefined {
388+ let tags = typedocComment . getTags ( '@alias' )
389+ if ( ! tags || tags . length === 0 ) {
390+ return undefined
391+ }
392+ return tags . map ( ( tag ) => {
393+ return tag . content . reduce ( ( acc , part ) => {
394+ invariant (
395+ part . kind === 'text' ,
396+ `Invalid @alias tag content: ${ typedocComment . getTags ( '@alias' ) . join ( ', ' ) } ` ,
397+ )
398+ return acc + part . text
399+ } , '' )
400+ } )
401+ }
402+
403+ function getDocumentedApiPath ( fullName : string ) : string {
404+ let nameParts = fullName . split ( '.' )
356405 return (
357406 nameParts
358407 . map ( ( s ) => s . replace ( / ^ @ r e m i x - r u n \/ / g, '' ) )
@@ -369,7 +418,7 @@ function getDocumentedApiDescription(typedocComment: typedoc.Comment): string {
369418 return description
370419}
371420
372- function getMethod ( node : typedoc . SignatureReflection ) : Method | undefined {
421+ function getMethod ( fullName : string , node : typedoc . SignatureReflection ) : Method | undefined {
373422 let parameters : ParameterOrProperty [ ] = [ ]
374423 node . traverse ( ( child ) => {
375424 // Only process params, not type params (generics)
@@ -400,7 +449,7 @@ function getMethod(node: typedoc.SignatureReflection): Method | undefined {
400449 }
401450
402451 return {
403- name : node . name ,
452+ name : getApiNameFromFullName ( fullName ) ,
404453 signature,
405454 description : node . comment ?. summary ? processComment ( node . comment . summary ) : '' ,
406455 parameters,
@@ -489,14 +538,19 @@ function processComment(parts: typedoc.CommentDisplayPart[]): string {
489538 target && target instanceof typedoc . Reflection ,
490539 `Missing/invalid target for @link content: ${ part . text } ` ,
491540 )
492- let path = getDocumentedApiPath ( target ) . replace ( / \. m d $ / , '' )
541+ let path = getDocumentedApiPath ( target . getFriendlyFullName ( ) ) . replace ( / \. m d $ / , '' )
493542 let href = `${ cliArgs . websiteDocsPath } /${ path } `
494543 text = `[\`${ part . text } \`](${ href } )`
495544 }
496545 return acc + text
497546 } , '' )
498547}
499548
549+ function getApiNameFromFullName ( fullName : string ) : string {
550+ return fullName . split ( '.' ) . slice ( - 1 ) [ 0 ]
551+ }
552+
553+ //#endregion
500554//#region Markdown
501555
502556async function writeMarkdownFiles ( comments : DocumentedAPI [ ] ) {
@@ -534,9 +588,7 @@ const pre = async (content: string, lang = 'ts') => {
534588
535589async function getFunctionMarkdown ( comment : DocumentedFunction ) : Promise < string > {
536590 return [
537- `---\ntitle: ${ comment . name } \n---` ,
538- h1 ( comment . name ) ,
539- h2 ( 'Summary' , comment . description ) ,
591+ ...getCommonMarkdown ( comment ) ,
540592 h2 ( 'Signature' , await pre ( comment . signature ) ) ,
541593 comment . example
542594 ? h2 (
@@ -556,9 +608,7 @@ async function getFunctionMarkdown(comment: DocumentedFunction): Promise<string>
556608
557609async function getClassMarkdown ( comment : DocumentedClass ) : Promise < string > {
558610 return [
559- `---\ntitle: ${ comment . name } \n---` ,
560- h1 ( comment . name ) ,
561- h2 ( 'Summary' , comment . description ) ,
611+ ...getCommonMarkdown ( comment ) ,
562612 comment . example ? h2 ( 'Example' , comment . example ) : undefined ,
563613 comment . constructor
564614 ? h2 (
@@ -595,6 +645,15 @@ async function getClassMarkdown(comment: DocumentedClass): Promise<string> {
595645 . join ( '\n\n' )
596646}
597647
648+ function getCommonMarkdown ( comment : DocumentedFunction | DocumentedClass ) : ( string | undefined ) [ ] {
649+ return [
650+ `---\ntitle: ${ comment . name } \n---` ,
651+ h1 ( comment . name ) ,
652+ h2 ( 'Summary' , comment . description ) ,
653+ comment . aliases ? h2 ( 'Aliases' , comment . aliases . join ( ', ' ) ) : undefined ,
654+ ] . filter ( Boolean )
655+ }
656+
598657//#region utils
599658
600659function log ( ...args : unknown [ ] ) {
0 commit comments