* Extract route nodes and handler references from a Drupal `*.routing.yml` file. * * Drupal routing YAML format: * * route.name: * path: '/some/path' * defaults: * _controller: '\Drupal\module\Controller\MyController::method' * _form: '\Drupal\module\Form\MyForm' *
( filePath: string, content: string )
| 95 | * methods: [GET, POST] # optional |
| 96 | */ |
| 97 | function extractDrupalRoutes( |
| 98 | filePath: string, |
| 99 | content: string |
| 100 | ): { nodes: Node[]; references: UnresolvedRef[] } { |
| 101 | const nodes: Node[] = []; |
| 102 | const references: UnresolvedRef[] = []; |
| 103 | const now = Date.now(); |
| 104 | |
| 105 | const lines = content.split('\n'); |
| 106 | |
| 107 | type PendingRoute = { name: string; lineNum: number }; |
| 108 | let pending: PendingRoute | null = null; |
| 109 | let currentPath: string | null = null; |
| 110 | let handlerRefs: string[] = []; |
| 111 | let methods: string[] = []; |
| 112 | |
| 113 | const flushRoute = () => { |
| 114 | if (!pending || !currentPath) return; |
| 115 | |
| 116 | const methodTag = methods.length > 0 ? ` [${methods.join(',')}]` : ''; |
| 117 | const routeNode: Node = { |
| 118 | id: `route:${filePath}:${pending.lineNum}:${currentPath}`, |
| 119 | kind: 'route', |
| 120 | name: `${currentPath}${methodTag}`, |
| 121 | qualifiedName: `${filePath}::${pending.name}`, |
| 122 | filePath, |
| 123 | startLine: pending.lineNum, |
| 124 | endLine: pending.lineNum, |
| 125 | startColumn: 0, |
| 126 | endColumn: 0, |
| 127 | language: 'yaml', |
| 128 | updatedAt: now, |
| 129 | }; |
| 130 | nodes.push(routeNode); |
| 131 | |
| 132 | for (const handler of handlerRefs) { |
| 133 | references.push({ |
| 134 | fromNodeId: routeNode.id, |
| 135 | referenceName: handler, |
| 136 | referenceKind: 'references', |
| 137 | line: pending.lineNum, |
| 138 | column: 0, |
| 139 | filePath, |
| 140 | language: 'yaml', |
| 141 | }); |
| 142 | } |
| 143 | }; |
| 144 | |
| 145 | for (let i = 0; i < lines.length; i++) { |
| 146 | const line = lines[i]!; |
| 147 | const trimmed = line.trim(); |
| 148 | |
| 149 | if (!trimmed || trimmed.startsWith('#')) continue; |
| 150 | |
| 151 | // Top-level route name: no leading whitespace, ends with a colon (no value after) |
| 152 | if (/^\S.*:\s*$/.test(line) && !/^\s/.test(line)) { |
| 153 | flushRoute(); |
| 154 | pending = { name: trimmed.slice(0, -1).trim(), lineNum: i + 1 }; |