@saluzi/codegraph 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (189) hide show
  1. package/dist/bin/codegraph.d.ts +25 -0
  2. package/dist/bin/node-version-check.d.ts +37 -0
  3. package/dist/bin/uninstall.d.ts +14 -0
  4. package/dist/context/formatter.d.ts +33 -0
  5. package/dist/context/formatter.js +244 -0
  6. package/dist/context/index.d.ts +117 -0
  7. package/dist/context/index.js +1050 -0
  8. package/dist/db/index.d.ts +101 -0
  9. package/dist/db/index.js +250 -0
  10. package/dist/db/migrations.d.ts +47 -0
  11. package/dist/db/migrations.js +131 -0
  12. package/dist/db/queries.d.ts +291 -0
  13. package/dist/db/queries.js +1349 -0
  14. package/dist/db/schema.sql +151 -0
  15. package/dist/db/sqlite-adapter.d.ts +49 -0
  16. package/dist/db/sqlite-adapter.js +141 -0
  17. package/dist/directory.d.ts +62 -0
  18. package/dist/directory.js +264 -0
  19. package/dist/errors.d.ts +149 -0
  20. package/dist/errors.js +219 -0
  21. package/dist/extraction/dfm-extractor.d.ts +31 -0
  22. package/dist/extraction/dfm-extractor.js +151 -0
  23. package/dist/extraction/grammars.d.ts +94 -0
  24. package/dist/extraction/grammars.js +357 -0
  25. package/dist/extraction/index.d.ts +148 -0
  26. package/dist/extraction/index.js +1286 -0
  27. package/dist/extraction/languages/c-cpp.d.ts +4 -0
  28. package/dist/extraction/languages/c-cpp.js +126 -0
  29. package/dist/extraction/languages/csharp.d.ts +3 -0
  30. package/dist/extraction/languages/csharp.js +72 -0
  31. package/dist/extraction/languages/dart.d.ts +3 -0
  32. package/dist/extraction/languages/dart.js +192 -0
  33. package/dist/extraction/languages/go.d.ts +3 -0
  34. package/dist/extraction/languages/go.js +58 -0
  35. package/dist/extraction/languages/index.d.ts +10 -0
  36. package/dist/extraction/languages/index.js +49 -0
  37. package/dist/extraction/languages/java.d.ts +3 -0
  38. package/dist/extraction/languages/java.js +64 -0
  39. package/dist/extraction/languages/javascript.d.ts +3 -0
  40. package/dist/extraction/languages/javascript.js +90 -0
  41. package/dist/extraction/languages/kotlin.d.ts +3 -0
  42. package/dist/extraction/languages/kotlin.js +253 -0
  43. package/dist/extraction/languages/lua.d.ts +3 -0
  44. package/dist/extraction/languages/lua.js +150 -0
  45. package/dist/extraction/languages/luau.d.ts +3 -0
  46. package/dist/extraction/languages/luau.js +37 -0
  47. package/dist/extraction/languages/pascal.d.ts +3 -0
  48. package/dist/extraction/languages/pascal.js +66 -0
  49. package/dist/extraction/languages/php.d.ts +3 -0
  50. package/dist/extraction/languages/php.js +107 -0
  51. package/dist/extraction/languages/python.d.ts +3 -0
  52. package/dist/extraction/languages/python.js +56 -0
  53. package/dist/extraction/languages/ruby.d.ts +3 -0
  54. package/dist/extraction/languages/ruby.js +114 -0
  55. package/dist/extraction/languages/rust.d.ts +3 -0
  56. package/dist/extraction/languages/rust.js +109 -0
  57. package/dist/extraction/languages/scala.d.ts +3 -0
  58. package/dist/extraction/languages/scala.js +139 -0
  59. package/dist/extraction/languages/swift.d.ts +3 -0
  60. package/dist/extraction/languages/swift.js +91 -0
  61. package/dist/extraction/languages/typescript.d.ts +3 -0
  62. package/dist/extraction/languages/typescript.js +129 -0
  63. package/dist/extraction/liquid-extractor.d.ts +52 -0
  64. package/dist/extraction/liquid-extractor.js +313 -0
  65. package/dist/extraction/parse-worker.d.ts +8 -0
  66. package/dist/extraction/parse-worker.js +94 -0
  67. package/dist/extraction/svelte-extractor.d.ts +56 -0
  68. package/dist/extraction/svelte-extractor.js +272 -0
  69. package/dist/extraction/tree-sitter-helpers.d.ts +39 -0
  70. package/dist/extraction/tree-sitter-helpers.js +103 -0
  71. package/dist/extraction/tree-sitter-types.d.ts +191 -0
  72. package/dist/extraction/tree-sitter-types.js +10 -0
  73. package/dist/extraction/tree-sitter.d.ts +238 -0
  74. package/dist/extraction/tree-sitter.js +2430 -0
  75. package/dist/extraction/vue-extractor.d.ts +36 -0
  76. package/dist/extraction/vue-extractor.js +163 -0
  77. package/dist/extraction/wasm/tree-sitter-c.wasm +0 -0
  78. package/dist/extraction/wasm/tree-sitter-c_sharp.wasm +0 -0
  79. package/dist/extraction/wasm/tree-sitter-cpp.wasm +0 -0
  80. package/dist/extraction/wasm/tree-sitter-dart.wasm +0 -0
  81. package/dist/extraction/wasm/tree-sitter-go.wasm +0 -0
  82. package/dist/extraction/wasm/tree-sitter-java.wasm +0 -0
  83. package/dist/extraction/wasm/tree-sitter-javascript.wasm +0 -0
  84. package/dist/extraction/wasm/tree-sitter-kotlin.wasm +0 -0
  85. package/dist/extraction/wasm/tree-sitter-lua.wasm +0 -0
  86. package/dist/extraction/wasm/tree-sitter-luau.wasm +0 -0
  87. package/dist/extraction/wasm/tree-sitter-pascal.wasm +0 -0
  88. package/dist/extraction/wasm/tree-sitter-php.wasm +0 -0
  89. package/dist/extraction/wasm/tree-sitter-python.wasm +0 -0
  90. package/dist/extraction/wasm/tree-sitter-ruby.wasm +0 -0
  91. package/dist/extraction/wasm/tree-sitter-rust.wasm +0 -0
  92. package/dist/extraction/wasm/tree-sitter-scala.wasm +0 -0
  93. package/dist/extraction/wasm/tree-sitter-swift.wasm +0 -0
  94. package/dist/extraction/wasm/tree-sitter-tsx.wasm +0 -0
  95. package/dist/extraction/wasm/tree-sitter-typescript.wasm +0 -0
  96. package/dist/extraction/wasm-runtime-flags.d.ts +46 -0
  97. package/dist/extraction/wasm-runtime-flags.js +105 -0
  98. package/dist/graph/index.d.ts +8 -0
  99. package/dist/graph/index.js +13 -0
  100. package/dist/graph/queries.d.ts +109 -0
  101. package/dist/graph/queries.js +366 -0
  102. package/dist/graph/traversal.d.ts +137 -0
  103. package/dist/graph/traversal.js +528 -0
  104. package/dist/index.d.ts +509 -0
  105. package/dist/index.js +800 -0
  106. package/dist/installer/claude-md-template.d.ts +19 -0
  107. package/dist/installer/config-writer.d.ts +29 -0
  108. package/dist/installer/index.d.ts +140 -0
  109. package/dist/installer/instructions-template.d.ts +30 -0
  110. package/dist/installer/targets/claude.d.ts +55 -0
  111. package/dist/installer/targets/codex.d.ts +18 -0
  112. package/dist/installer/targets/cursor.d.ts +35 -0
  113. package/dist/installer/targets/hermes.d.ts +18 -0
  114. package/dist/installer/targets/opencode.d.ts +30 -0
  115. package/dist/installer/targets/registry.d.ts +38 -0
  116. package/dist/installer/targets/shared.d.ts +92 -0
  117. package/dist/installer/targets/toml.d.ts +64 -0
  118. package/dist/installer/targets/types.d.ts +122 -0
  119. package/dist/mcp/index.d.ts +98 -0
  120. package/dist/mcp/server-instructions.d.ts +20 -0
  121. package/dist/mcp/tools.d.ts +269 -0
  122. package/dist/mcp/transport.d.ts +117 -0
  123. package/dist/resolution/frameworks/cargo-workspace.d.ts +20 -0
  124. package/dist/resolution/frameworks/cargo-workspace.js +225 -0
  125. package/dist/resolution/frameworks/csharp.d.ts +8 -0
  126. package/dist/resolution/frameworks/csharp.js +213 -0
  127. package/dist/resolution/frameworks/drupal.d.ts +51 -0
  128. package/dist/resolution/frameworks/drupal.js +335 -0
  129. package/dist/resolution/frameworks/express.d.ts +8 -0
  130. package/dist/resolution/frameworks/express.js +225 -0
  131. package/dist/resolution/frameworks/go.d.ts +8 -0
  132. package/dist/resolution/frameworks/go.js +158 -0
  133. package/dist/resolution/frameworks/index.d.ts +52 -0
  134. package/dist/resolution/frameworks/index.js +137 -0
  135. package/dist/resolution/frameworks/java.d.ts +8 -0
  136. package/dist/resolution/frameworks/java.js +177 -0
  137. package/dist/resolution/frameworks/laravel.d.ts +13 -0
  138. package/dist/resolution/frameworks/laravel.js +248 -0
  139. package/dist/resolution/frameworks/nestjs.d.ts +26 -0
  140. package/dist/resolution/frameworks/nestjs.js +374 -0
  141. package/dist/resolution/frameworks/python.d.ts +10 -0
  142. package/dist/resolution/frameworks/python.js +278 -0
  143. package/dist/resolution/frameworks/react.d.ts +8 -0
  144. package/dist/resolution/frameworks/react.js +272 -0
  145. package/dist/resolution/frameworks/ruby.d.ts +8 -0
  146. package/dist/resolution/frameworks/ruby.js +198 -0
  147. package/dist/resolution/frameworks/rust.d.ts +8 -0
  148. package/dist/resolution/frameworks/rust.js +207 -0
  149. package/dist/resolution/frameworks/svelte.d.ts +9 -0
  150. package/dist/resolution/frameworks/svelte.js +249 -0
  151. package/dist/resolution/frameworks/swift.d.ts +10 -0
  152. package/dist/resolution/frameworks/swift.js +376 -0
  153. package/dist/resolution/frameworks/vue.d.ts +9 -0
  154. package/dist/resolution/frameworks/vue.js +306 -0
  155. package/dist/resolution/import-resolver.d.ts +61 -0
  156. package/dist/resolution/import-resolver.js +663 -0
  157. package/dist/resolution/index.d.ts +118 -0
  158. package/dist/resolution/index.js +744 -0
  159. package/dist/resolution/lru-cache.d.ts +24 -0
  160. package/dist/resolution/lru-cache.js +62 -0
  161. package/dist/resolution/name-matcher.d.ts +50 -0
  162. package/dist/resolution/name-matcher.js +384 -0
  163. package/dist/resolution/path-aliases.d.ts +72 -0
  164. package/dist/resolution/path-aliases.js +238 -0
  165. package/dist/resolution/strip-comments.d.ts +40 -0
  166. package/dist/resolution/strip-comments.js +441 -0
  167. package/dist/resolution/types.d.ts +181 -0
  168. package/dist/resolution/types.js +8 -0
  169. package/dist/search/query-parser.d.ts +61 -0
  170. package/dist/search/query-parser.js +177 -0
  171. package/dist/search/query-utils.d.ts +59 -0
  172. package/dist/search/query-utils.js +383 -0
  173. package/dist/sync/git-hooks.d.ts +54 -0
  174. package/dist/sync/git-hooks.js +223 -0
  175. package/dist/sync/index.d.ts +25 -0
  176. package/dist/sync/index.js +28 -0
  177. package/dist/sync/watch-policy.d.ts +51 -0
  178. package/dist/sync/watch-policy.js +124 -0
  179. package/dist/sync/watcher.d.ts +83 -0
  180. package/dist/sync/watcher.js +192 -0
  181. package/dist/types.d.ts +433 -0
  182. package/dist/types.js +75 -0
  183. package/dist/ui/glyphs.d.ts +42 -0
  184. package/dist/ui/shimmer-progress.d.ts +11 -0
  185. package/dist/ui/shimmer-worker.d.ts +2 -0
  186. package/dist/ui/types.d.ts +20 -0
  187. package/dist/utils.d.ts +231 -0
  188. package/dist/utils.js +549 -0
  189. package/package.json +24 -0
@@ -0,0 +1,335 @@
1
+ "use strict";
2
+ /**
3
+ * Drupal Framework Resolver
4
+ *
5
+ * Supports Drupal 8/9/10/11 (Composer-based projects). Drupal 7 is not supported.
6
+ *
7
+ * ## What this resolver does
8
+ *
9
+ * 1. **Detection** — reads composer.json and checks for any `drupal/*` dependency in
10
+ * `require` or `require-dev`.
11
+ *
12
+ * 2. **Route extraction** — parses `*.routing.yml` files and emits `route` nodes for each
13
+ * Drupal route, with `references` edges to the `_controller`, `_form`, or entity handler
14
+ * class/method.
15
+ *
16
+ * 3. **Hook detection** — scans `.module`, `.install`, `.theme`, and `.inc` files for Drupal
17
+ * hook implementations. Two strategies are used:
18
+ * a. Docblock: `@Implements hook_X()` → precise, no false positives.
19
+ * b. Name pattern: function `{moduleName}_{hookSuffix}()` → catches hooks without
20
+ * docblocks but may produce false positives on helper functions.
21
+ * Detected hooks emit an `UnresolvedRef` from the implementing function node to the
22
+ * canonical `hook_X` name, linking implementations to the hook when `codegraph_callers`
23
+ * is invoked.
24
+ *
25
+ * ## Design decisions (review in future iterations)
26
+ *
27
+ * - Hook graph resolution (v1): hook references are stored as UnresolvedRef pointing to the
28
+ * canonical `hook_X` name. If Drupal core is indexed, these will resolve to core hook
29
+ * definitions. Without core, they remain unresolved but are still searchable via
30
+ * `codegraph_search("form_alter")`. Full hook-node creation (virtual nodes for every hook)
31
+ * is deferred to a future iteration.
32
+ *
33
+ * - Services / plugins (out of scope for v1): `*.services.yml` service definitions and plugin
34
+ * annotations (`@Block`, `@FormElement`, etc.) are not extracted. Add a TODO below when
35
+ * ready to implement.
36
+ *
37
+ * - Twig templates (out of scope for v1): `.twig` files are tracked as file nodes but no
38
+ * symbol extraction is performed (no tree-sitter Twig grammar). Implement when a Twig
39
+ * grammar WASM is available.
40
+ *
41
+ * ## TODOs for future iterations
42
+ *
43
+ * - TODO: Extract service definitions from `*.services.yml` files (class → service-id edges).
44
+ * - TODO: Extract plugin annotations (`@Block`, `@FormElement`, `@Field`, etc.) from PHP
45
+ * docblocks and emit plugin nodes with references to the annotated class.
46
+ * - TODO: Add Twig symbol extraction when a tree-sitter Twig grammar becomes available.
47
+ * - TODO: Improve hook resolution: create virtual `hook_*` nodes so `codegraph_callers`
48
+ * returns all implementations even when Drupal core is not indexed.
49
+ */
50
+ Object.defineProperty(exports, "__esModule", { value: true });
51
+ exports.drupalResolver = void 0;
52
+ const tree_sitter_helpers_1 = require("../../extraction/tree-sitter-helpers");
53
+ // ---------------------------------------------------------------------------
54
+ // Helpers
55
+ // ---------------------------------------------------------------------------
56
+ /**
57
+ * Parse the last PHP namespace segment from a FQCN like `\Drupal\mymodule\Controller\Foo`.
58
+ * Returns `null` for strings that don't look like a FQCN.
59
+ */
60
+ function lastSegment(fqcn) {
61
+ const clean = fqcn.replace(/^\\+/, '').trim();
62
+ if (!clean.includes('\\'))
63
+ return null;
64
+ const parts = clean.split('\\');
65
+ return parts[parts.length - 1] ?? null;
66
+ }
67
+ /**
68
+ * Derive the Drupal module name from a file path.
69
+ * e.g. `web/modules/custom/my_module/my_module.module` → `my_module`
70
+ */
71
+ function moduleNameFromPath(filePath) {
72
+ const match = filePath.match(/\/([^/]+)\.[^./]+$/);
73
+ return match ? match[1] : null;
74
+ }
75
+ // ---------------------------------------------------------------------------
76
+ // Route extraction helpers
77
+ // ---------------------------------------------------------------------------
78
+ /**
79
+ * Extract route nodes and handler references from a Drupal `*.routing.yml` file.
80
+ *
81
+ * Drupal routing YAML format:
82
+ *
83
+ * route.name:
84
+ * path: '/some/path'
85
+ * defaults:
86
+ * _controller: '\Drupal\module\Controller\MyController::method'
87
+ * _form: '\Drupal\module\Form\MyForm'
88
+ * _title: 'Page title'
89
+ * requirements:
90
+ * _permission: 'access content'
91
+ * methods: [GET, POST] # optional
92
+ */
93
+ function extractDrupalRoutes(filePath, content) {
94
+ const nodes = [];
95
+ const references = [];
96
+ const now = Date.now();
97
+ const lines = content.split('\n');
98
+ let pending = null;
99
+ let currentPath = null;
100
+ let handlerRefs = [];
101
+ let methods = [];
102
+ const flushRoute = () => {
103
+ if (!pending || !currentPath)
104
+ return;
105
+ const methodTag = methods.length > 0 ? ` [${methods.join(',')}]` : '';
106
+ const routeNode = {
107
+ id: `route:${filePath}:${pending.lineNum}:${currentPath}`,
108
+ kind: 'route',
109
+ name: `${currentPath}${methodTag}`,
110
+ qualifiedName: `${filePath}::${pending.name}`,
111
+ filePath,
112
+ startLine: pending.lineNum,
113
+ endLine: pending.lineNum,
114
+ startColumn: 0,
115
+ endColumn: 0,
116
+ language: 'yaml',
117
+ updatedAt: now,
118
+ };
119
+ nodes.push(routeNode);
120
+ for (const handler of handlerRefs) {
121
+ references.push({
122
+ fromNodeId: routeNode.id,
123
+ referenceName: handler,
124
+ referenceKind: 'references',
125
+ line: pending.lineNum,
126
+ column: 0,
127
+ filePath,
128
+ language: 'yaml',
129
+ });
130
+ }
131
+ };
132
+ for (let i = 0; i < lines.length; i++) {
133
+ const line = lines[i];
134
+ const trimmed = line.trim();
135
+ if (!trimmed || trimmed.startsWith('#'))
136
+ continue;
137
+ // Top-level route name: no leading whitespace, ends with a colon (no value after)
138
+ if (/^\S.*:\s*$/.test(line) && !/^\s/.test(line)) {
139
+ flushRoute();
140
+ pending = { name: trimmed.slice(0, -1).trim(), lineNum: i + 1 };
141
+ currentPath = null;
142
+ handlerRefs = [];
143
+ methods = [];
144
+ continue;
145
+ }
146
+ // path: '/some/path'
147
+ const pathMatch = trimmed.match(/^path:\s*['"]?([^'"#\n]+?)['"]?\s*(?:#.*)?$/);
148
+ if (pathMatch) {
149
+ currentPath = pathMatch[1].trim();
150
+ continue;
151
+ }
152
+ // _controller: '\Drupal\...\Class::method'
153
+ const controllerMatch = trimmed.match(/^_controller:\s*['"]?([^'"#\n]+?)['"]?\s*(?:#.*)?$/);
154
+ if (controllerMatch) {
155
+ handlerRefs.push(controllerMatch[1].trim());
156
+ continue;
157
+ }
158
+ // _form: '\Drupal\...\Form\MyForm'
159
+ const formMatch = trimmed.match(/^_form:\s*['"]?([^'"#\n]+?)['"]?\s*(?:#.*)?$/);
160
+ if (formMatch) {
161
+ handlerRefs.push(formMatch[1].trim());
162
+ continue;
163
+ }
164
+ // _entity_form / _entity_list / _entity_view: entity.type
165
+ const entityMatch = trimmed.match(/^_(entity_form|entity_list|entity_view):\s*['"]?([^'"#\n]+?)['"]?\s*(?:#.*)?$/);
166
+ if (entityMatch) {
167
+ handlerRefs.push(entityMatch[2].trim());
168
+ continue;
169
+ }
170
+ // methods: [GET, POST] or methods: [GET]
171
+ const methodsMatch = trimmed.match(/^methods:\s*\[([^\]]+)\]/);
172
+ if (methodsMatch) {
173
+ methods = methodsMatch[1].split(',').map((m) => m.trim().toUpperCase()).filter(Boolean);
174
+ continue;
175
+ }
176
+ }
177
+ flushRoute();
178
+ return { nodes, references };
179
+ }
180
+ // ---------------------------------------------------------------------------
181
+ // Hook detection helpers
182
+ // ---------------------------------------------------------------------------
183
+ const HOOK_FILE_EXTENSIONS = ['.module', '.install', '.theme', '.inc'];
184
+ function isDrupalHookFile(filePath) {
185
+ return HOOK_FILE_EXTENSIONS.some((ext) => filePath.endsWith(ext));
186
+ }
187
+ /**
188
+ * Extract hook implementation references from a Drupal PHP file.
189
+ *
190
+ * Strategy A (primary): look for docblocks containing `Implements hook_X().`
191
+ * followed immediately by the function definition. This is the Drupal coding
192
+ * standard and is precise.
193
+ *
194
+ * Strategy B (fallback): for functions whose name starts with `{moduleName}_`,
195
+ * treat the suffix as the hook name. Catches hooks without docblocks but may
196
+ * produce false positives on non-hook helper functions.
197
+ *
198
+ * Each detected hook emits an UnresolvedRef from the implementing function node
199
+ * (identified by computing the same ID tree-sitter would generate) to the
200
+ * canonical hook name, e.g. `hook_form_alter`.
201
+ */
202
+ function extractDrupalHooks(filePath, content) {
203
+ const references = [];
204
+ // Build a map of function name → 1-indexed line number for all top-level functions.
205
+ // This mirrors tree-sitter's line numbering so we can reconstruct node IDs.
206
+ const funcLineMap = new Map();
207
+ const funcDef = /^function\s+(\w+)\s*\(/gm;
208
+ let fm;
209
+ while ((fm = funcDef.exec(content)) !== null) {
210
+ const name = fm[1];
211
+ if (!funcLineMap.has(name)) {
212
+ // line = number of newlines before match start + 1
213
+ funcLineMap.set(name, content.slice(0, fm.index).split('\n').length);
214
+ }
215
+ }
216
+ const emitHookRef = (hookName, funcName) => {
217
+ const lineNum = funcLineMap.get(funcName);
218
+ if (lineNum === undefined)
219
+ return;
220
+ const nodeId = (0, tree_sitter_helpers_1.generateNodeId)(filePath, 'function', funcName, lineNum);
221
+ references.push({
222
+ fromNodeId: nodeId,
223
+ referenceName: hookName,
224
+ referenceKind: 'references',
225
+ line: lineNum,
226
+ column: 0,
227
+ filePath,
228
+ language: 'php',
229
+ });
230
+ };
231
+ // Strategy A: docblock `Implements hook_X().` followed by function definition.
232
+ // The docblock and function may be separated by blank lines.
233
+ const docblockPattern = /\/\*\*[\s\S]*?(?:@|\*\s+)Implements\s+(hook_\w+)\s*\(\)[\s\S]*?\*\/\s*\n(?:\s*\n)*function\s+(\w+)\s*\(/g;
234
+ const docblockMatched = new Set();
235
+ let match;
236
+ while ((match = docblockPattern.exec(content)) !== null) {
237
+ const [, hookName, funcName] = match;
238
+ emitHookRef(hookName, funcName);
239
+ docblockMatched.add(funcName);
240
+ }
241
+ // Strategy B: fallback name-pattern matching for functions without docblocks.
242
+ // Only applies to functions whose name starts with {moduleName}_ and that were
243
+ // not already matched by Strategy A.
244
+ const moduleName = moduleNameFromPath(filePath);
245
+ if (moduleName) {
246
+ const prefix = moduleName + '_';
247
+ for (const [funcName] of funcLineMap) {
248
+ if (docblockMatched.has(funcName))
249
+ continue;
250
+ if (!funcName.startsWith(prefix))
251
+ continue;
252
+ const hookSuffix = funcName.slice(prefix.length);
253
+ if (!hookSuffix)
254
+ continue;
255
+ // Emit a reference to hook_{suffix} — the resolver will link it if the
256
+ // hook is defined somewhere in the indexed graph (e.g. Drupal core).
257
+ emitHookRef(`hook_${hookSuffix}`, funcName);
258
+ }
259
+ }
260
+ return { nodes: [], references };
261
+ }
262
+ // ---------------------------------------------------------------------------
263
+ // Resolver
264
+ // ---------------------------------------------------------------------------
265
+ exports.drupalResolver = {
266
+ name: 'drupal',
267
+ languages: ['php', 'yaml'],
268
+ detect(context) {
269
+ const composer = context.readFile('composer.json');
270
+ if (!composer)
271
+ return false;
272
+ try {
273
+ const json = JSON.parse(composer);
274
+ const deps = { ...json.require, ...(json['require-dev'] ?? {}) };
275
+ return Object.keys(deps).some((k) => k.startsWith('drupal/'));
276
+ }
277
+ catch {
278
+ return false;
279
+ }
280
+ },
281
+ resolve(ref, context) {
282
+ const name = ref.referenceName;
283
+ // _controller: '\Drupal\module\...\ClassName::methodName'
284
+ const controllerMatch = name.match(/^\\?(?:Drupal\\[^:]+\\)?([^\\:]+)::(\w+)$/);
285
+ if (controllerMatch) {
286
+ const [, className, methodName] = controllerMatch;
287
+ const classNodes = context.getNodesByName(className);
288
+ for (const cls of classNodes) {
289
+ if (cls.kind !== 'class')
290
+ continue;
291
+ const fileNodes = context.getNodesInFile(cls.filePath);
292
+ const method = fileNodes.find((n) => n.kind === 'method' && n.name === methodName);
293
+ if (method) {
294
+ return { original: ref, targetNodeId: method.id, confidence: 0.9, resolvedBy: 'framework' };
295
+ }
296
+ return { original: ref, targetNodeId: cls.id, confidence: 0.7, resolvedBy: 'framework' };
297
+ }
298
+ }
299
+ // _form / _entity_form: '\Drupal\module\...\ClassName' (no ::method)
300
+ if (name.includes('\\') && !name.includes('::')) {
301
+ const className = lastSegment(name);
302
+ if (className) {
303
+ const classNodes = context.getNodesByName(className);
304
+ const cls = classNodes.find((n) => n.kind === 'class');
305
+ if (cls) {
306
+ return { original: ref, targetNodeId: cls.id, confidence: 0.85, resolvedBy: 'framework' };
307
+ }
308
+ }
309
+ }
310
+ // hook_X — find any function whose name ends in _{hookSuffix} in a hook file
311
+ if (name.startsWith('hook_')) {
312
+ const hookSuffix = name.slice(5); // strip 'hook_'
313
+ const candidates = context.getNodesByKind('function').filter((n) => n.name.endsWith(`_${hookSuffix}`) && isDrupalHookFile(n.filePath));
314
+ if (candidates.length > 0) {
315
+ return {
316
+ original: ref,
317
+ targetNodeId: candidates[0].id,
318
+ confidence: 0.75,
319
+ resolvedBy: 'framework',
320
+ };
321
+ }
322
+ }
323
+ return null;
324
+ },
325
+ extract(filePath, content) {
326
+ if (filePath.endsWith('.routing.yml')) {
327
+ return extractDrupalRoutes(filePath, content);
328
+ }
329
+ if (isDrupalHookFile(filePath) || filePath.endsWith('.php')) {
330
+ return extractDrupalHooks(filePath, content);
331
+ }
332
+ return { nodes: [], references: [] };
333
+ },
334
+ };
335
+ //# sourceMappingURL=drupal.js.map
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Express/Node.js Framework Resolver
3
+ *
4
+ * Handles Express and general Node.js patterns.
5
+ */
6
+ import { FrameworkResolver } from '../types'
7
+ export declare const expressResolver: FrameworkResolver
8
+ //# sourceMappingURL=express.d.ts.map
@@ -0,0 +1,225 @@
1
+ "use strict";
2
+ /**
3
+ * Express/Node.js Framework Resolver
4
+ *
5
+ * Handles Express and general Node.js patterns.
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.expressResolver = void 0;
9
+ const strip_comments_1 = require("../strip-comments");
10
+ function extractTailIdent(expr) {
11
+ const cleaned = expr.replace(/\s+/g, '').replace(/\(\)$/, '');
12
+ const m = cleaned.match(/(?:\.|^)([A-Za-z_][A-Za-z0-9_]*)$/);
13
+ return m ? m[1] : null;
14
+ }
15
+ exports.expressResolver = {
16
+ name: 'express',
17
+ languages: ['javascript', 'typescript'],
18
+ detect(context) {
19
+ // Check for Express in package.json
20
+ const packageJson = context.readFile('package.json');
21
+ if (packageJson) {
22
+ try {
23
+ const pkg = JSON.parse(packageJson);
24
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies };
25
+ if (deps.express || deps.fastify || deps.koa || deps.hapi) {
26
+ return true;
27
+ }
28
+ }
29
+ catch {
30
+ // Invalid JSON
31
+ }
32
+ }
33
+ // Check for common Express patterns
34
+ const allFiles = context.getAllFiles();
35
+ for (const file of allFiles) {
36
+ if (file.includes('routes') ||
37
+ file.includes('controllers') ||
38
+ file.includes('middleware')) {
39
+ const content = context.readFile(file);
40
+ if (content && (content.includes('express') || content.includes('app.get') || content.includes('router.get'))) {
41
+ return true;
42
+ }
43
+ }
44
+ }
45
+ return false;
46
+ },
47
+ resolve(ref, context) {
48
+ // Pattern 1: Middleware references
49
+ if (isMiddlewareName(ref.referenceName)) {
50
+ const result = resolveMiddleware(ref.referenceName, context);
51
+ if (result) {
52
+ return {
53
+ original: ref,
54
+ targetNodeId: result,
55
+ confidence: 0.8,
56
+ resolvedBy: 'framework',
57
+ };
58
+ }
59
+ }
60
+ // Pattern 2: Controller method references
61
+ const controllerMatch = ref.referenceName.match(/^(\w+)Controller\.(\w+)$/);
62
+ if (controllerMatch) {
63
+ const [, controller, method] = controllerMatch;
64
+ const result = resolveControllerMethod(controller, method, context);
65
+ if (result) {
66
+ return {
67
+ original: ref,
68
+ targetNodeId: result,
69
+ confidence: 0.85,
70
+ resolvedBy: 'framework',
71
+ };
72
+ }
73
+ }
74
+ // Pattern 3: Service/helper references
75
+ const serviceMatch = ref.referenceName.match(/^(\w+)(Service|Helper|Utils?)\.(\w+)$/);
76
+ if (serviceMatch) {
77
+ const [, name, suffix, method] = serviceMatch;
78
+ const result = resolveServiceMethod(name + suffix, method, context);
79
+ if (result) {
80
+ return {
81
+ original: ref,
82
+ targetNodeId: result,
83
+ confidence: 0.8,
84
+ resolvedBy: 'framework',
85
+ };
86
+ }
87
+ }
88
+ return null;
89
+ },
90
+ extract(filePath, content) {
91
+ if (!/\.(m?js|tsx?|cjs)$/.test(filePath))
92
+ return { nodes: [], references: [] };
93
+ const nodes = [];
94
+ const references = [];
95
+ const now = Date.now();
96
+ const lang = detectLanguage(filePath);
97
+ const safe = (0, strip_comments_1.stripCommentsForRegex)(content, lang);
98
+ // (app|router).METHOD('/path', handler-expr)
99
+ const regex = /\b(app|router)\.(get|post|put|patch|delete|all|use)\s*\(\s*['"]([^'"]+)['"]\s*,\s*([^)]+)\)/g;
100
+ let match;
101
+ while ((match = regex.exec(safe)) !== null) {
102
+ const [, _obj, method, routePath, handlers] = match;
103
+ if (method === 'use' && !routePath.startsWith('/'))
104
+ continue;
105
+ const line = safe.slice(0, match.index).split('\n').length;
106
+ const routeNode = {
107
+ id: `route:${filePath}:${line}:${method.toUpperCase()}:${routePath}`,
108
+ kind: 'route',
109
+ name: `${method.toUpperCase()} ${routePath}`,
110
+ qualifiedName: `${filePath}::${method.toUpperCase()}:${routePath}`,
111
+ filePath,
112
+ startLine: line,
113
+ endLine: line,
114
+ startColumn: 0,
115
+ endColumn: match[0].length,
116
+ language: detectLanguage(filePath),
117
+ updatedAt: now,
118
+ };
119
+ nodes.push(routeNode);
120
+ // Handler is the LAST comma-separated argument; earlier ones are middleware.
121
+ const parts = handlers.split(',').map((s) => s.trim()).filter(Boolean);
122
+ const last = parts[parts.length - 1];
123
+ const handlerName = last ? extractTailIdent(last) : null;
124
+ if (handlerName) {
125
+ references.push({
126
+ fromNodeId: routeNode.id,
127
+ referenceName: handlerName,
128
+ referenceKind: 'references',
129
+ line,
130
+ column: 0,
131
+ filePath,
132
+ language: detectLanguage(filePath),
133
+ });
134
+ }
135
+ }
136
+ return { nodes, references };
137
+ },
138
+ };
139
+ /**
140
+ * Check if a name looks like middleware
141
+ */
142
+ function isMiddlewareName(name) {
143
+ const middlewarePatterns = [
144
+ /^auth$/i,
145
+ /^authenticate$/i,
146
+ /^authorization$/i,
147
+ /^validate/i,
148
+ /^sanitize/i,
149
+ /^rateLimit/i,
150
+ /^cors$/i,
151
+ /^helmet$/i,
152
+ /^logger$/i,
153
+ /^errorHandler$/i,
154
+ /^notFound$/i,
155
+ /Middleware$/i,
156
+ ];
157
+ return middlewarePatterns.some((p) => p.test(name));
158
+ }
159
+ /**
160
+ * Resolve middleware reference using name-based lookup
161
+ */
162
+ function resolveMiddleware(name, context) {
163
+ // Try exact name first
164
+ const candidates = context.getNodesByName(name);
165
+ const match = candidates.find((n) => n.name.toLowerCase() === name.toLowerCase() ||
166
+ n.name.toLowerCase() === name.replace(/Middleware$/i, '').toLowerCase());
167
+ if (match)
168
+ return match.id;
169
+ // Try without Middleware suffix
170
+ const baseName = name.replace(/Middleware$/i, '');
171
+ if (baseName !== name) {
172
+ const baseCandidates = context.getNodesByName(baseName);
173
+ const MIDDLEWARE_DIRS = ['/middleware/', '/middlewares/'];
174
+ const preferred = baseCandidates.filter((n) => MIDDLEWARE_DIRS.some((d) => n.filePath.includes(d)));
175
+ if (preferred.length > 0)
176
+ return preferred[0].id;
177
+ if (baseCandidates.length > 0)
178
+ return baseCandidates[0].id;
179
+ }
180
+ return null;
181
+ }
182
+ /**
183
+ * Resolve controller method using name-based lookup
184
+ */
185
+ function resolveControllerMethod(controller, method, context) {
186
+ // Look for the method name directly
187
+ const methodCandidates = context.getNodesByName(method);
188
+ const methodNodes = methodCandidates.filter((n) => (n.kind === 'method' || n.kind === 'function') &&
189
+ n.filePath.toLowerCase().includes(controller.toLowerCase()));
190
+ if (methodNodes.length > 0)
191
+ return methodNodes[0].id;
192
+ // Fall back: look for controller class, then find the method in its file
193
+ const controllerName = controller + 'Controller';
194
+ const controllerCandidates = context.getNodesByName(controllerName);
195
+ for (const ctrl of controllerCandidates) {
196
+ const nodesInFile = context.getNodesInFile(ctrl.filePath);
197
+ const methodNode = nodesInFile.find((n) => (n.kind === 'method' || n.kind === 'function') && n.name === method);
198
+ if (methodNode)
199
+ return methodNode.id;
200
+ }
201
+ return null;
202
+ }
203
+ /**
204
+ * Resolve service/helper method using name-based lookup
205
+ */
206
+ function resolveServiceMethod(serviceName, method, context) {
207
+ // Look for the method in files matching the service name
208
+ const methodCandidates = context.getNodesByName(method);
209
+ const stripped = serviceName.replace(/(Service|Helper|Utils?)$/i, '').toLowerCase();
210
+ const methodNodes = methodCandidates.filter((n) => (n.kind === 'method' || n.kind === 'function') &&
211
+ n.filePath.toLowerCase().includes(stripped));
212
+ if (methodNodes.length > 0)
213
+ return methodNodes[0].id;
214
+ return null;
215
+ }
216
+ /**
217
+ * Detect language from file extension
218
+ */
219
+ function detectLanguage(filePath) {
220
+ if (filePath.endsWith('.ts') || filePath.endsWith('.tsx')) {
221
+ return 'typescript';
222
+ }
223
+ return 'javascript';
224
+ }
225
+ //# sourceMappingURL=express.js.map
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Go Framework Resolver
3
+ *
4
+ * Handles Gin, Echo, Fiber, Chi, and standard library patterns.
5
+ */
6
+ import { FrameworkResolver } from '../types'
7
+ export declare const goResolver: FrameworkResolver
8
+ //# sourceMappingURL=go.d.ts.map