|
| 1 | +/** |
| 2 | + * Contract Registry System |
| 3 | + * |
| 4 | + * Tracks all contracts (modules and facets) for relationship detection |
| 5 | + * and cross-reference generation in documentation. |
| 6 | + * |
| 7 | + * Features: |
| 8 | + * - Register contracts with metadata (name, type, category, path) |
| 9 | + * - Find related contracts (module/facet pairs, same category, extensions) |
| 10 | + * - Enrich documentation data with relationship information |
| 11 | + */ |
| 12 | + |
| 13 | +// ============================================================================ |
| 14 | +// Registry State |
| 15 | +// ============================================================================ |
| 16 | + |
| 17 | +/** |
| 18 | + * Global registry to track all contracts for relationship detection |
| 19 | + * This allows us to find related contracts and generate cross-references |
| 20 | + */ |
| 21 | +const contractRegistry = { |
| 22 | + byName: new Map(), |
| 23 | + byCategory: new Map(), |
| 24 | + byType: { modules: [], facets: [] } |
| 25 | +}; |
| 26 | + |
| 27 | +// ============================================================================ |
| 28 | +// Registry Management |
| 29 | +// ============================================================================ |
| 30 | + |
| 31 | +/** |
| 32 | + * Register a contract in the global registry |
| 33 | + * @param {object} contractData - Contract documentation data |
| 34 | + * @param {object} outputPath - Output path information from getOutputPath |
| 35 | + * @returns {object} Registered contract entry |
| 36 | + */ |
| 37 | +function registerContract(contractData, outputPath) { |
| 38 | + // Construct full path including filename (without .mdx extension) |
| 39 | + // This ensures RelatedDocs links point to the actual page, not the category index |
| 40 | + const fullPath = outputPath.relativePath |
| 41 | + ? `${outputPath.relativePath}/${outputPath.fileName}` |
| 42 | + : outputPath.fileName; |
| 43 | + |
| 44 | + const entry = { |
| 45 | + name: contractData.title, |
| 46 | + type: contractData.contractType, // 'module' or 'facet' |
| 47 | + category: outputPath.category, |
| 48 | + path: fullPath, |
| 49 | + sourcePath: contractData.sourceFilePath, |
| 50 | + functions: contractData.functions || [], |
| 51 | + storagePosition: contractData.storageInfo?.storagePosition |
| 52 | + }; |
| 53 | + |
| 54 | + contractRegistry.byName.set(contractData.title, entry); |
| 55 | + |
| 56 | + if (!contractRegistry.byCategory.has(outputPath.category)) { |
| 57 | + contractRegistry.byCategory.set(outputPath.category, []); |
| 58 | + } |
| 59 | + contractRegistry.byCategory.get(outputPath.category).push(entry); |
| 60 | + |
| 61 | + if (contractData.contractType === 'module') { |
| 62 | + contractRegistry.byType.modules.push(entry); |
| 63 | + } else { |
| 64 | + contractRegistry.byType.facets.push(entry); |
| 65 | + } |
| 66 | + |
| 67 | + return entry; |
| 68 | +} |
| 69 | + |
| 70 | +/** |
| 71 | + * Get the contract registry |
| 72 | + * @returns {object} The contract registry |
| 73 | + */ |
| 74 | +function getContractRegistry() { |
| 75 | + return contractRegistry; |
| 76 | +} |
| 77 | + |
| 78 | +/** |
| 79 | + * Clear the contract registry (useful for testing or reset) |
| 80 | + */ |
| 81 | +function clearContractRegistry() { |
| 82 | + contractRegistry.byName.clear(); |
| 83 | + contractRegistry.byCategory.clear(); |
| 84 | + contractRegistry.byType.modules = []; |
| 85 | + contractRegistry.byType.facets = []; |
| 86 | +} |
| 87 | + |
| 88 | +// ============================================================================ |
| 89 | +// Relationship Detection |
| 90 | +// ============================================================================ |
| 91 | + |
| 92 | +/** |
| 93 | + * Find related contracts for a given contract |
| 94 | + * @param {string} contractName - Name of the contract |
| 95 | + * @param {string} contractType - Type of contract ('module' or 'facet') |
| 96 | + * @param {string} category - Category of the contract |
| 97 | + * @param {object} registry - Contract registry (optional, uses global if not provided) |
| 98 | + * @returns {Array} Array of related contract objects with title, href, description, icon |
| 99 | + */ |
| 100 | +function findRelatedContracts(contractName, contractType, category, registry = null) { |
| 101 | + const reg = registry || contractRegistry; |
| 102 | + const related = []; |
| 103 | + const contract = reg.byName.get(contractName); |
| 104 | + if (!contract) return related; |
| 105 | + |
| 106 | + // 1. Find corresponding module/facet pair |
| 107 | + if (contractType === 'facet') { |
| 108 | + const moduleName = contractName.replace('Facet', 'Mod'); |
| 109 | + const module = reg.byName.get(moduleName); |
| 110 | + if (module) { |
| 111 | + related.push({ |
| 112 | + title: moduleName, |
| 113 | + href: `/docs/library/${module.path}`, |
| 114 | + description: `Module used by ${contractName}`, |
| 115 | + icon: '📦' |
| 116 | + }); |
| 117 | + } |
| 118 | + } else if (contractType === 'module') { |
| 119 | + const facetName = contractName.replace('Mod', 'Facet'); |
| 120 | + const facet = reg.byName.get(facetName); |
| 121 | + if (facet) { |
| 122 | + related.push({ |
| 123 | + title: facetName, |
| 124 | + href: `/docs/library/${facet.path}`, |
| 125 | + description: `Facet using ${contractName}`, |
| 126 | + icon: '💎' |
| 127 | + }); |
| 128 | + } |
| 129 | + } |
| 130 | + |
| 131 | + // 2. Find related contracts in same category (excluding self) |
| 132 | + const sameCategory = reg.byCategory.get(category) || []; |
| 133 | + sameCategory.forEach(c => { |
| 134 | + if (c.name !== contractName && c.type === contractType) { |
| 135 | + related.push({ |
| 136 | + title: c.name, |
| 137 | + href: `/docs/library/${c.path}`, |
| 138 | + description: `Related ${contractType} in ${category}`, |
| 139 | + icon: contractType === 'module' ? '📦' : '💎' |
| 140 | + }); |
| 141 | + } |
| 142 | + }); |
| 143 | + |
| 144 | + // 3. Find extension contracts (e.g., ERC20Facet → ERC20BurnFacet) |
| 145 | + if (contractType === 'facet') { |
| 146 | + const baseName = contractName.replace(/BurnFacet$|PermitFacet$|BridgeableFacet$|EnumerableFacet$/, 'Facet'); |
| 147 | + if (baseName !== contractName) { |
| 148 | + const base = reg.byName.get(baseName); |
| 149 | + if (base) { |
| 150 | + related.push({ |
| 151 | + title: baseName, |
| 152 | + href: `/docs/library/${base.path}`, |
| 153 | + description: `Base facet for ${contractName}`, |
| 154 | + icon: '💎' |
| 155 | + }); |
| 156 | + } |
| 157 | + } |
| 158 | + } |
| 159 | + |
| 160 | + // 4. Find core dependencies (e.g., all facets depend on DiamondCutFacet) |
| 161 | + if (contractType === 'facet' && contractName !== 'DiamondCutFacet') { |
| 162 | + const diamondCut = reg.byName.get('DiamondCutFacet'); |
| 163 | + if (diamondCut) { |
| 164 | + related.push({ |
| 165 | + title: 'DiamondCutFacet', |
| 166 | + href: `/docs/library/${diamondCut.path}`, |
| 167 | + description: 'Required for adding facets to diamonds', |
| 168 | + icon: '🔧' |
| 169 | + }); |
| 170 | + } |
| 171 | + } |
| 172 | + |
| 173 | + return related.slice(0, 6); // Limit to 6 related items |
| 174 | +} |
| 175 | + |
| 176 | +/** |
| 177 | + * Enrich contract data with relationship information |
| 178 | + * @param {object} data - Contract documentation data |
| 179 | + * @param {object} pathInfo - Output path information |
| 180 | + * @param {object} registry - Contract registry (optional, uses global if not provided) |
| 181 | + * @returns {object} Enriched data with relatedDocs property |
| 182 | + */ |
| 183 | +function enrichWithRelationships(data, pathInfo, registry = null) { |
| 184 | + const relatedDocs = findRelatedContracts( |
| 185 | + data.title, |
| 186 | + data.contractType, |
| 187 | + pathInfo.category, |
| 188 | + registry |
| 189 | + ); |
| 190 | + |
| 191 | + return { |
| 192 | + ...data, |
| 193 | + relatedDocs: relatedDocs.length > 0 ? relatedDocs : null |
| 194 | + }; |
| 195 | +} |
| 196 | + |
| 197 | +// ============================================================================ |
| 198 | +// Exports |
| 199 | +// ============================================================================ |
| 200 | + |
| 201 | +module.exports = { |
| 202 | + // Registry management |
| 203 | + registerContract, |
| 204 | + getContractRegistry, |
| 205 | + clearContractRegistry, |
| 206 | + |
| 207 | + // Relationship detection |
| 208 | + findRelatedContracts, |
| 209 | + enrichWithRelationships, |
| 210 | +}; |
| 211 | + |
0 commit comments