forked from
npmx.dev/npmx.dev
[READ-ONLY]
a fast, modern browser for the npm registry
1/**
2 * Node Processing
3 *
4 * Functions for processing deno doc output: flattening namespaces,
5 * merging overloads, and building symbol lookups.
6 *
7 * @module server/utils/docs/processing
8 */
9
10import type { DenoDocNode } from '#shared/types/deno-doc'
11import type { MergedSymbol, SymbolLookup } from './types'
12import { cleanSymbolName, createSymbolId } from './text'
13
14/**
15 * Flatten namespace elements into top-level nodes for easier display.
16 * Also filters out import/reference nodes that aren't useful for docs.
17 */
18export function flattenNamespaces(nodes: DenoDocNode[]): DenoDocNode[] {
19 const result: DenoDocNode[] = []
20
21 for (const node of nodes) {
22 // Skip internal nodes
23 if (node.kind === 'import' || node.kind === 'reference') {
24 continue
25 }
26
27 result.push(node)
28
29 // Inline namespace members with qualified names
30 if (node.kind === 'namespace' && node.namespaceDef?.elements) {
31 for (const element of node.namespaceDef.elements) {
32 result.push({
33 ...element,
34 name: `${node.name}.${element.name}`,
35 })
36 }
37 }
38 }
39
40 return result
41}
42
43/**
44 * Build a lookup table mapping symbol names to their HTML anchor IDs.
45 * Used for {@link} cross-references.
46 */
47export function buildSymbolLookup(nodes: DenoDocNode[]): SymbolLookup {
48 const lookup = new Map<string, string>()
49
50 for (const node of nodes) {
51 const cleanName = cleanSymbolName(node.name)
52 const id = createSymbolId(node.kind, cleanName)
53 lookup.set(cleanName, id)
54 }
55
56 return lookup
57}
58
59/**
60 * Merge function/method overloads into single entries.
61 *
62 * TypeScript packages often export many overloads for the same function
63 * (e.g., React's `h` has 23 overloads). This groups them together.
64 */
65export function mergeOverloads(nodes: DenoDocNode[]): MergedSymbol[] {
66 const byKey = new Map<string, DenoDocNode[]>()
67
68 for (const node of nodes) {
69 const cleanName = cleanSymbolName(node.name)
70 const key = `${node.kind}:${cleanName}`
71 const existing = byKey.get(key)
72 if (existing) {
73 existing.push(node)
74 } else {
75 byKey.set(key, [node])
76 }
77 }
78
79 const result: MergedSymbol[] = []
80
81 for (const [, groupedNodes] of byKey) {
82 const first = groupedNodes[0]
83 if (!first) continue // Should never happen, but defensive programming etc
84
85 // Use JSDoc from the best-documented overload
86 const withDoc = groupedNodes.find(n => n.jsDoc?.doc) ?? first
87
88 result.push({
89 name: cleanSymbolName(first.name),
90 kind: first.kind,
91 nodes: groupedNodes,
92 jsDoc: withDoc.jsDoc,
93 })
94 }
95
96 // Sort alphabetically
97 result.sort((a, b) => a.name.localeCompare(b.name))
98
99 return result
100}
101
102/**
103 * Group merged symbols by their kind (function, class, etc.)
104 */
105export function groupMergedByKind(symbols: MergedSymbol[]): Record<string, MergedSymbol[]> {
106 const grouped: Record<string, MergedSymbol[]> = {}
107
108 for (const sym of symbols) {
109 const kindGroup = (grouped[sym.kind] ??= [])
110 kindGroup.push(sym)
111 }
112
113 return grouped
114}