Skip to content

Fix arrow function and function expression export indexing#18

Merged
colbymchenry merged 2 commits into
colbymchenry:mainfrom
GeneralClaw:fix/arrow-function-export-indexing
Feb 10, 2026
Merged

Fix arrow function and function expression export indexing#18
colbymchenry merged 2 commits into
colbymchenry:mainfrom
GeneralClaw:fix/arrow-function-export-indexing

Conversation

@GeneralClaw

@GeneralClaw GeneralClaw commented Feb 9, 2026

Copy link
Copy Markdown
Contributor

Problem

CodeGraph v0.3.1 fails to index three common TypeScript/JavaScript patterns:

  1. Arrow function exports: export const useAuth = () => { ... } — the arrow_function AST node has no name field, so extractName() returns '<anonymous>' and bails.
  2. Type aliases: export type AuthContextValue = { ... }type_alias_declaration isn't in any extraction list.
  3. Non-function variable exports: export const store = create(...), export const schema = z.object(...), export const config = { ... } — these are call_expression, object, or primitive values, not functions, so they're never visited.

Additionally, isExported() for TS/JS uses a 10-char substring lookback that misses export for deeply nested nodes.

Real-World Impact

Tested on a production monorepo (Expo + FastAPI, 238 files):

Metric Before After Change
Nodes 779 1,172 +50%
Edges 2,599 3,489 +34%
packages/ nodes 0 166 Was completely invisible
variable nodes 0 109 New kind
type_alias nodes 0 105 New kind

7 shared packages (auth, db, cv, i18n, ui, analytics, config) had zero nodes. After this fix, all have full coverage. Only 4 files remain at 0 nodes — all are re-export barrels or ambient declarations with no extractable symbols.

Changes (2 commits)

Commit 1: Arrow function + isExported fix

extractFunction(): When arrow_function or function_expression resolves to '<anonymous>', check the parent variable_declarator for the name:

let name = extractName(node, this.source, this.extractor);
if (name === '<anonymous>' &&
    (node.type === 'arrow_function' || node.type === 'function_expression')) {
  const parent = node.parent;
  if (parent?.type === 'variable_declarator') {
    const varName = getChildByField(parent, 'name');
    if (varName) name = getNodeText(varName, this.source);
  }
}

isExported() (TS + JS): Walk the parent chain instead of 10-char lookback:

isExported: (node, _source) => {
  let current = node.parent;
  while (current) {
    if (current.type === 'export_statement') return true;
    current = current.parent;
  }
  return false;
},

Commit 2: Type alias + exported variable extraction

typeAliasTypes: New field on LanguageExtractor interface. Populated for TypeScript (type_alias_declaration), Go (type_spec), Rust (type_item), C (type_definition), C++ (type_definition, alias_declaration), Swift (typealias_declaration), Kotlin (type_alias). Empty for languages without type aliases.

extractTypeAlias(): New method that creates type_alias kind nodes.

extractExportedVariables(): New method called when visiting export_statement nodes. Finds lexical_declaration > variable_declarator children whose values are NOT already handled by functionTypes (avoids duplicating arrow functions). Creates variable kind nodes for:

  • Zustand stores: export const useX = create(...)
  • XState machines: export const xMachine = createMachine(...)
  • Zod schemas: export const schema = z.object(...)
  • Config objects: export const config = { ... }
  • Constants: export const MAX = 3
  • Arrays: export const NAMES = [...] as const

Tests

Added 17 new test cases in __tests__/extraction.test.ts:

Arrow function tests (6):

  • Exported arrow functions, function expressions, non-exported, anonymous, multiple exports, JavaScript files

Type alias tests (3):

  • Exported type aliases, non-exported, multiple in same file

Exported variable tests (8):

  • Zustand stores, object literals, arrays, primitives, Zod schemas, XState machines
  • No duplication with arrow functions
  • Non-exported const not treated as exported

All 215 tests pass (59 extraction + 156 others). Zero regressions in evaluation benchmarks.

AST Context

For export const useAuth = () => { ... }, tree-sitter produces:

export_statement
  lexical_declaration
    variable_declarator
      name: identifier "useAuth"
      value: arrow_function

For export const config = { ... }:

export_statement
  lexical_declaration
    variable_declarator
      name: identifier "config"
      value: object

The arrow_function/object nodes are 3 levels deep under export_statement, which is why the 10-char lookback failed and why parent-chain walking is needed.

Arrow functions and function expressions assigned to variables
(e.g. `export const useAuth = () => { ... }`) were not being indexed
because the arrow_function AST node has no `name` field — the name
lives on the parent variable_declarator node.

Additionally, `isExported()` for TypeScript and JavaScript extractors
only checked 10 characters back from the node's start position, which
missed `export` for deeply nested nodes like arrow functions inside
variable declarations inside export statements.

Changes:
- extractFunction(): When an arrow_function or function_expression
  resolves to '<anonymous>', look up the parent variable_declarator
  for the name before skipping.
- isExported() (TS + JS): Walk the parent chain to find an
  export_statement ancestor instead of substring matching.
- Add 6 test cases covering arrow function exports, function
  expression exports, non-exported arrow functions, anonymous
  arrow functions, multiple exports, and JavaScript files.

Tested on a real monorepo (238 files): node count increased from
779 to 958 (+23%), with 94 new nodes in packages/ that previously
had 0 coverage.
Extend extraction to index two additional categories of symbols
that were previously invisible:

1. Type aliases (e.g. `export type X = ...` in TypeScript,
   `type X` in Go, `type X = ...` in Rust, `typealias X` in Swift,
   `type_alias` in Kotlin). Adds `typeAliasTypes` to the
   LanguageExtractor interface with values for all 13 languages.

2. Exported variable declarations that aren't functions, including:
   - Zustand stores: `export const useX = create(...)`
   - XState machines: `export const xMachine = createMachine(...)`
   - Zod schemas: `export const schema = z.object(...)`
   - Config objects: `export const config = { ... }`
   - Constants: `export const MAX = 3`
   - Arrays: `export const NAMES = [...] as const`

   The extractExportedVariables() method is called when visiting
   export_statement nodes. It skips variable_declarator values that
   are already handled by functionTypes (arrow_function,
   function_expression) to avoid duplicate extraction.

Adds 11 new test cases (59 total extraction tests, 215 total).

Tested on production monorepo: nodes increased from 958 to 1,172
(+22%), with 109 new variable nodes and 105 new type_alias nodes.
Only 4 files remain at 0 nodes — all are re-export barrels or
ambient declaration files with no extractable symbols.
@colbymchenry colbymchenry merged commit 38fac1f into colbymchenry:main Feb 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants