From 49e670c223ce81049a640b6c9a3e004b5747ccc6 Mon Sep 17 00:00:00 2001 From: Colby McHenry Date: Tue, 7 Apr 2026 09:40:50 -0500 Subject: [PATCH] feat: Add resolveBody hook for JS/TS class field function extraction MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Addresses arrow function class fields like `field = () => { ... }` where the function body is nested inside field_definition nodes. Adds resolveBody method to traverse field_definition → arrow_function/function_expression → body and handles HOF wrapper patterns like `field = throttle(() => { ... })` by searching call_expression arguments. Enables proper function body extraction for class field functions in both JavaScript and TypeScript. --- src/extraction/languages/javascript.ts | 28 +++++++++++++++++++++++++ src/extraction/languages/typescript.ts | 29 ++++++++++++++++++++++++++ 2 files changed, 57 insertions(+) diff --git a/src/extraction/languages/javascript.ts b/src/extraction/languages/javascript.ts index 15b255b..0a0d678 100644 --- a/src/extraction/languages/javascript.ts +++ b/src/extraction/languages/javascript.ts @@ -14,6 +14,34 @@ export const javascriptExtractor: LanguageExtractor = { variableTypes: ['lexical_declaration', 'variable_declaration'], nameField: 'name', bodyField: 'body', + resolveBody: (node, bodyField) => { + // field_definition (arrow function class fields) nest the body inside + // an arrow_function or function_expression child: + // field_definition → arrow_function → body (statement_block) + // Also handles wrapper patterns like: field = throttle((e) => { ... }) + // field_definition → call_expression → arguments → arrow_function → body + if (node.type === 'field_definition') { + for (let i = 0; i < node.namedChildCount; i++) { + const child = node.namedChild(i); + if (!child) continue; + if (child.type === 'arrow_function' || child.type === 'function_expression') { + return getChildByField(child, bodyField); + } + if (child.type === 'call_expression') { + const args = getChildByField(child, 'arguments'); + if (args) { + for (let j = 0; j < args.namedChildCount; j++) { + const arg = args.namedChild(j); + if (arg && (arg.type === 'arrow_function' || arg.type === 'function_expression')) { + return getChildByField(arg, bodyField); + } + } + } + } + } + } + return null; + }, paramsField: 'parameters', getSignature: (node, source) => { const params = getChildByField(node, 'parameters'); diff --git a/src/extraction/languages/typescript.ts b/src/extraction/languages/typescript.ts index 230a2a3..9c91438 100644 --- a/src/extraction/languages/typescript.ts +++ b/src/extraction/languages/typescript.ts @@ -15,6 +15,35 @@ export const typescriptExtractor: LanguageExtractor = { variableTypes: ['lexical_declaration', 'variable_declaration'], nameField: 'name', bodyField: 'body', + resolveBody: (node, bodyField) => { + // public_field_definition (arrow function class fields) nest the body inside + // an arrow_function or function_expression child: + // public_field_definition → arrow_function → body (statement_block) + // Also handles wrapper patterns like: field = withBatchedUpdates((e) => { ... }) + // public_field_definition → call_expression → arguments → arrow_function → body + if (node.type === 'public_field_definition') { + for (let i = 0; i < node.namedChildCount; i++) { + const child = node.namedChild(i); + if (!child) continue; + if (child.type === 'arrow_function' || child.type === 'function_expression') { + return getChildByField(child, bodyField); + } + // Check inside call_expression arguments (HOF wrappers like throttle, debounce) + if (child.type === 'call_expression') { + const args = getChildByField(child, 'arguments'); + if (args) { + for (let j = 0; j < args.namedChildCount; j++) { + const arg = args.namedChild(j); + if (arg && (arg.type === 'arrow_function' || arg.type === 'function_expression')) { + return getChildByField(arg, bodyField); + } + } + } + } + } + } + return null; + }, paramsField: 'parameters', returnField: 'return_type', getSignature: (node, source) => {