MCPcopy Create free account
hub / github.com/colbymchenry/codegraph / extractDrupalHooks

Function extractDrupalHooks

src/resolution/frameworks/drupal.ts:226–290  ·  view source on GitHub ↗

* Extract hook implementation references from a Drupal PHP file. * * Strategy A (primary): look for docblocks containing `Implements hook_X().` * followed immediately by the function definition. This is the Drupal coding * standard and is precise. * * Strategy B (fallback): for functions whose

(
  filePath: string,
  content: string
)

Source from the content-addressed store, hash-verified

224 * canonical hook name, e.g. `hook_form_alter`.
225 */
226function extractDrupalHooks(
227 filePath: string,
228 content: string
229): { nodes: Node[]; references: UnresolvedRef[] } {
230 const references: UnresolvedRef[] = [];
231
232 // Build a map of function name → 1-indexed line number for all top-level functions.
233 // This mirrors tree-sitter's line numbering so we can reconstruct node IDs.
234 const funcLineMap = new Map<string, number>();
235 const funcDef = /^function\s+(\w+)\s*\(/gm;
236 let fm: RegExpExecArray | null;
237 while ((fm = funcDef.exec(content)) !== null) {
238 const name = fm[1]!;
239 if (!funcLineMap.has(name)) {
240 // line = number of newlines before match start + 1
241 funcLineMap.set(name, content.slice(0, fm.index).split('\n').length);
242 }
243 }
244
245 const emitHookRef = (hookName: string, funcName: string) => {
246 const lineNum = funcLineMap.get(funcName);
247 if (lineNum === undefined) return;
248 const nodeId = generateNodeId(filePath, 'function', funcName, lineNum);
249 references.push({
250 fromNodeId: nodeId,
251 referenceName: hookName,
252 referenceKind: 'references',
253 line: lineNum,
254 column: 0,
255 filePath,
256 language: 'php',
257 });
258 };
259
260 // Strategy A: docblock `Implements hook_X().` followed by function definition.
261 // The docblock and function may be separated by blank lines.
262 const docblockPattern =
263 /\/\*\*[\s\S]*?(?:@|\*\s+)Implements\s+(hook_\w+)\s*\(\)[\s\S]*?\*\/\s*\n(?:\s*\n)*function\s+(\w+)\s*\(/g;
264 const docblockMatched = new Set<string>();
265 let match: RegExpExecArray | null;
266 while ((match = docblockPattern.exec(content)) !== null) {
267 const [, hookName, funcName] = match;
268 emitHookRef(hookName!, funcName!);
269 docblockMatched.add(funcName!);
270 }
271
272 // Strategy B: fallback name-pattern matching for functions without docblocks.
273 // Only applies to functions whose name starts with {moduleName}_ and that were
274 // not already matched by Strategy A.
275 const moduleName = moduleNameFromPath(filePath);
276 if (moduleName) {
277 const prefix = moduleName + '_';
278 for (const [funcName] of funcLineMap) {
279 if (docblockMatched.has(funcName)) continue;
280 if (!funcName.startsWith(prefix)) continue;
281 const hookSuffix = funcName.slice(prefix.length);
282 if (!hookSuffix) continue;
283 // Emit a reference to hook_{suffix} — the resolver will link it if the

Callers 1

extractFunction · 0.85

Calls 5

emitHookRefFunction · 0.85
moduleNameFromPathFunction · 0.85
hasMethod · 0.80
execMethod · 0.65
setMethod · 0.45

Tested by

no test coverage detected