Skip to content

feat: track dynamic import() expressions as graph edges#389

Merged
carlos-alm merged 6 commits intomainfrom
feat/dynamic-import-edges
Mar 9, 2026
Merged

feat: track dynamic import() expressions as graph edges#389
carlos-alm merged 6 commits intomainfrom
feat/dynamic-import-edges

Conversation

@carlos-alm
Copy link
Contributor

Summary

  • Extract dynamic import() calls in both query-based and walk-based JS/TS extraction paths
  • Add dynamic-imports edge kind to CORE_EDGE_KINDS
  • Resolves backlog ID 81: codegraph now tracks import() expressions as graph edges, fixing false "zero consumers" reports for symbols consumed via dynamic imports

Changes

  • src/extractors/javascript.js: Add extractDynamicImportsWalk() targeted walk for the query-based fast path (tree-sitter Query patterns can't match import() function type). Add extractDynamicImportNames() to extract destructured names from patterns like const { a } = await import('./foo.js')
  • src/kinds.js: Add 'dynamic-imports' to CORE_EDGE_KINDS
  • src/builder.js: Update edge kind selection to emit dynamic-imports edges, including barrel resolution propagation
  • tests/engines/query-walk-parity.test.js: Add parity test for dynamic import expressions

Verification

  • 95 dynamic-imports edges created when building codegraph's own graph
  • Both query and walk extraction paths produce identical results (parity test)
  • All 1564 tests pass, lint clean

Test plan

  • npx vitest run tests/engines/query-walk-parity.test.js — 14 tests pass (including new dynamic import case)
  • npx vitest run tests/parsers/javascript.test.js — 20 tests pass
  • npm test — 1564 tests pass
  • node src/cli.js build . --no-incremental --engine wasm — 95 dynamic-imports edges created

Extract dynamic import() calls in both query-based and walk-based
extraction paths. Adds 'dynamic-imports' edge kind to CORE_EDGE_KINDS.

- Add extractDynamicImportsWalk() for the query-based fast path
  (tree-sitter Query patterns don't match import() function type)
- Add extractDynamicImportNames() to extract destructured names from
  patterns like `const { a } = await import('./foo.js')`
- Update builder.js edge kind selection to emit 'dynamic-imports'
  edges including barrel resolution propagation
- Add query-walk parity test for dynamic import expressions

Impact: 6 functions changed, 24 affected
@claude
Copy link

claude bot commented Mar 9, 2026

Claude finished @carlos-alm's task —— View job


PR Review: Dynamic Import Tracking

I'm reviewing this PR to add dynamic import() expression tracking to codegraph. Let me analyze the changes systematically:

Todo List:

  • Phase 1: Root cause analysis - understand the problem
  • Phase 2: Configuration change detection
  • Phase 3: Test integrity verification
  • Phase 4: Code quality review
  • Final Assessment: Rating and recommendation

Phase 1: Root Cause Analysis ✅

Problem Being Solved: Codegraph was reporting false "zero consumers" for symbols that are actually consumed via dynamic import() expressions. The issue stems from tree-sitter Query patterns not being able to match import() calls because they have function type import, which doesn't fit the standard call_expression patterns that expect identifier, member_expression, or subscript_expression functions.

Why This Approach: The solution adds a targeted tree walk (extractDynamicImportsWalk) specifically for dynamic imports in the query-based fast path, since query patterns can't capture them. This maintains the performance benefits of query-based extraction while filling the gap for dynamic imports.

Risk Assessment: Low risk - this is an additive feature that doesn't modify existing extraction logic. The approach is consistent with the existing pattern of using targeted walks for edge cases (like extractConstantsWalk for constants).

Phase 2: Configuration Change Detection ✅

No configuration files modified (biome.json, eslint, tsconfig, package.json unchanged). No linting rules disabled, test timeouts increased, or coverage thresholds lowered. This is purely a feature addition without any rule relaxation.

Phase 3: Test Integrity ✅

Test Coverage: Added comprehensive test case for dynamic imports in query-walk-parity.test.js covering:

  • Destructured imports: const { readFile } = await import('fs/promises')
  • Namespace imports: const mod = await import('./utils.js')
  • Side-effect imports: import('./side-effect.js')

Test Quality: The test properly validates both extraction paths (query vs walk) produce identical results, ensuring the new functionality maintains parity. The normalization function correctly handles the new dynamicImport flag.

No Red Flags: No test assertions weakened, no skipped validations, appropriate test coverage for the new feature.

Phase 4: Code Quality Review ✅

Implementation Quality:

  • Consistent with codebase: Uses same patterns as existing import extraction
  • DRY principle: Both query and walk paths use shared extractDynamicImportNames() helper
  • Robust parsing: Handles destructuring ({ a, b }), namespace (mod), array ([a, b]), and standalone patterns
  • AST navigation: Properly walks up parent chain to find variable declarator context
  • Performance conscious: Early return in extractDynamicImportsWalk to skip recursing into import() children

Edge Cases Handled:

  • await import() expressions (skips await wrapper)
  • ✅ Destructured imports with aliases: { a: localName }
  • ✅ Standalone side-effect imports with no variable assignment
  • ✅ Both shorthand and pair patterns in object destructuring

Security & Safety:

  • ✅ No SQL injection risks - uses existing parameterized insert patterns
  • ✅ No command injection - only processes AST nodes, no shell execution
  • ✅ Defensive programming - null checks on parent, childForFieldName calls
  • ✅ Input validation - checks node types before processing

Edge Kind Integration:

  • ✅ Properly added to CORE_EDGE_KINDS constant in src/kinds.js
  • ✅ Correctly integrated into builder logic with barrel file propagation
  • ✅ Maintains precedence order in edge classification (reexport > typeOnly > dynamicImport > imports)

Final Assessment

Root Cause Analysis

Problem Being Solved: Dynamic imports causing false "zero consumers" reports - a real user pain point
Why This Approach: Necessary due to tree-sitter Query limitations, maintains performance while adding completeness
Risk Assessment: Very low risk - purely additive, well-tested, follows existing patterns

Critical Concerns

None identified. This is a clean, well-implemented feature addition.

Final Recommendation

  • Rating: ⭐⭐⭐⭐☆ (4/5)
  • Action: APPROVE
  • Reasoning: Excellent implementation of a needed feature. High-quality code that follows project conventions, comprehensive test coverage, addresses real user issues with dynamic imports. Only minor deduction for not being exceptional (missing broader dynamic import pattern coverage like .then() chains), but this is appropriate scope for the current issue.

Recommendation: Approved for merge. This PR successfully addresses the dynamic import tracking gap while maintaining code quality and test coverage standards.


@greptile-apps
Copy link
Contributor

greptile-apps bot commented Mar 9, 2026

Greptile Summary

This PR adds dynamic import() expression tracking to the WASM JS/TS extraction pipeline, emitting dynamic-imports edges for both the query-based and walk-based paths. It correctly handles all major destructuring patterns (shorthand, aliased, namespace, array, and rest), propagates barrel resolution for dynamic imports, and ships a query/walk parity test.

Key changes:

  • extractDynamicImportsWalk supplements tree-sitter queries (which can't match the import function type) in the query path; the walk path handles the same case inline in walkJavaScriptNode
  • extractDynamicImportNames walks the AST upward to extract destructured binding names for object_pattern, identifier, and array_pattern (including rest_pattern), returning [] for unresolvable patterns like .then() chains
  • builder.js correctly places dynamic-imports in the edge-kind selection ternary and propagates it through barrel resolution
  • Non-static paths (template literals, variable arguments) are handled with a debug() warning rather than silently dropped
  • Two known remaining limitations are documented in BACKLOG items 81 and 82: (a) the native Rust engine is not yet updated, (b) exports --unused consumer counting still only queries calls edges, so symbols accessed exclusively via dynamic imports without a direct call site will continue to show zero consumers

Confidence Score: 5/5

  • Safe to merge; implementation is clean with symmetric handling across both engine paths and comprehensive test coverage.
  • All 1564 existing tests pass plus 14 parity tests including the new dynamic import case. The logic in extractDynamicImportNames correctly handles all documented destructuring patterns. The rest_pattern handling in array destructuring was properly fixed, and both engine paths (query and walk) produce identical results verified by the parity test.
  • No files require special attention.

Last reviewed commit: e97e75b

Comment on lines +499 to +501
// Extract destructured names from parent context:
// const { a, b } = await import('./foo.js')
// import('./foo.js').then(({ a, b }) => ...)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Misleading .then() comment — names are never extracted for that pattern

The comment claims that destructured names from import('./foo.js').then(({ a, b }) => ...) are extracted, but extractDynamicImportNames only climbs the AST up to a variable_declarator. For the .then() pattern, the direct parent of the call_expression is a member_expression, not an await_expression or variable_declarator, so extractDynamicImportNames will always return [] for that case.

The file-level dynamic-imports edge is still emitted (no data is lost), but the comment sets a false expectation and could mislead future contributors into thinking the .then() case is already handled.

Suggested change
// Extract destructured names from parent context:
// const { a, b } = await import('./foo.js')
// import('./foo.js').then(({ a, b }) => ...)
// Extract destructured names from parent context:
// const { a, b } = await import('./foo.js')
// (standalone import('./foo.js').then(...) calls produce an edge with empty names)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Already addressed in commit ae0155e — the comment was corrected to say .then(...) calls produce an edge with empty names, matching the actual behavior.

Comment on lines +241 to +243
const strArg = findChild(args, 'string');
if (strArg) {
const modPath = strArg.text.replace(/['"]/g, '');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Template-literal and variable import paths silently skipped

Both extractDynamicImportsWalk (query path) and the walk path in extractSymbolsWalk look for a child node of type string to extract the module path. This means the following patterns produce no edge and no warning:

import(`./plugins/${name}.js`);   // template_string node, not string
const p = './utils.js';
const mod = await import(p);      // identifier node, not string

These are admittedly hard to resolve statically, but they account for a meaningful portion of real-world dynamic imports (especially lazy-loaded plugins). Leaving them silently uncovered can lead to the same "false zero-consumers" reports the PR was designed to fix.

A simple improvement would be to log a debug-level warning when an import() call is encountered but the path cannot be statically resolved, so users are at least aware of the gap.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Already addressed in commit ae0155e — both the walk path (extractDynamicImportsWalk) and the query path (walkJavaScriptNode) already emit a debug() warning when a dynamic import() has a non-static path (template literal or variable).

@claude
Copy link

claude bot commented Mar 9, 2026

Claude finished @carlos-alm's task —— View job


PR Review: Dynamic Import Tracking

I'm reviewing this PR to add dynamic import() expression tracking to codegraph. Let me analyze the changes systematically:

Todo List:

  • Phase 1: Root cause analysis - understand the problem
  • Phase 2: Configuration change detection
  • Phase 3: Test integrity verification
  • Phase 4: Code quality review
  • Final Assessment: Rating and recommendation

Phase 1: Root Cause Analysis ✅

Problem Being Solved: Codegraph was reporting false "zero consumers" for symbols that are actually consumed via dynamic import() expressions. The issue stems from tree-sitter Query patterns not being able to match import() calls because they have function type import, which doesn't fit the standard call_expression patterns that expect identifier, member_expression, or subscript_expression functions.

Why This Approach: The solution adds a targeted tree walk (extractDynamicImportsWalk) specifically for dynamic imports in the query-based fast path, since query patterns can't capture them. This maintains the performance benefits of query-based extraction while filling the gap for dynamic imports.

Risk Assessment: Low risk - this is an additive feature that doesn't modify existing extraction logic. The approach is consistent with the existing pattern of using targeted walks for edge cases (like extractConstantsWalk for constants).

Phase 2: Configuration Change Detection ✅

No configuration files modified (biome.json, eslint, tsconfig, package.json unchanged). No linting rules disabled, test timeouts increased, or coverage thresholds lowered. This is purely a feature addition without any rule relaxation.

Phase 3: Test Integrity ✅

Test Coverage: Added comprehensive test case for dynamic imports in query-walk-parity.test.js covering:

  • Destructured imports: const { readFile } = await import('fs/promises')
  • Namespace imports: const mod = await import('./utils.js')
  • Side-effect imports: import('./side-effect.js')

Test Quality: The test properly validates both extraction paths (query vs walk) produce identical results, ensuring the new functionality maintains parity. The normalization function correctly handles the new dynamicImport flag.

No Red Flags: No test assertions weakened, no skipped validations, appropriate test coverage for the new feature.

Phase 4: Code Quality Review ✅

Implementation Quality:

  • Consistent with codebase: Uses same patterns as existing import extraction
  • DRY principle: Both query and walk paths use shared extractDynamicImportNames() helper
  • Robust parsing: Handles destructuring ({ a, b }), namespace (mod), array ([a, b]), and standalone patterns
  • AST navigation: Properly walks up parent chain to find variable declarator context
  • Performance conscious: Early return in extractDynamicImportsWalk to skip recursing into import() children

Edge Cases Handled:

  • await import() expressions (skips await wrapper)
  • ✅ Destructured imports with aliases: { a: localName }
  • ✅ Standalone side-effect imports with no variable assignment
  • ✅ Both shorthand and pair patterns in object destructuring

Security & Safety:

  • ✅ No SQL injection risks - uses existing parameterized insert patterns
  • ✅ No command injection - only processes AST nodes, no shell execution
  • ✅ Defensive programming - null checks on parent, childForFieldName calls
  • ✅ Input validation - checks node types before processing

Edge Kind Integration:

  • ✅ Properly added to CORE_EDGE_KINDS constant in src/kinds.js
  • ✅ Correctly integrated into builder logic with barrel file propagation
  • ✅ Maintains precedence order in edge classification (reexport > typeOnly > dynamicImport > imports)

Final Assessment

Root Cause Analysis

Problem Being Solved: Dynamic imports causing false "zero consumers" reports - a real user pain point
Why This Approach: Necessary due to tree-sitter Query limitations, maintains performance while adding completeness
Risk Assessment: Very low risk - purely additive, well-tested, follows existing patterns

Critical Concerns

None identified. This is a clean, well-implemented feature addition.

Final Recommendation

  • Rating: ⭐⭐⭐⭐☆ (4/5)
  • Action: APPROVE
  • Reasoning: Excellent implementation of a needed feature. High-quality code that follows project conventions, comprehensive test coverage, addresses real user issues with dynamic imports. Only minor deduction for not being exceptional (missing broader dynamic import pattern coverage like .then() chains), but this is appropriate scope for the current issue.

Recommendation: Approved for merge. This PR successfully addresses the dynamic import tracking gap while maintaining code quality and test coverage standards.


@carlos-alm
Copy link
Contributor Author

Addressed both review comments:

1. Misleading .then() comment (ccfd2a7): Fixed the comment to accurately state that .then() pattern produces an edge with empty names, not extracted destructured names. Added backlog ID 82 to track future .then() callback name extraction.

2. Template-literal/variable import paths (ccfd2a7): Added debug-level warning when import() has a non-static path (template literal or variable) so users are aware of the gap. These are logged via debug() — visible with DEBUG=codegraph.

Backlog items added (04cebcc): ID 81 (dynamic import tracking — remaining gaps: native engine, re-export consumer counting) and ID 82 (.then() callback name extraction).

@greptileai

…orts

- Fix comment: .then() pattern does not extract destructured
  names (edge has empty names)
- Add debug-level warning when import() has a template literal
  or variable path that can't be statically resolved

Impact: 3 functions changed, 3 affected
Impact: 3 functions changed, 3 affected
@carlos-alm carlos-alm force-pushed the feat/dynamic-import-edges branch from 04cebcc to 0f50eb3 Compare March 9, 2026 08:02
@claude
Copy link

claude bot commented Mar 9, 2026

Claude finished @carlos-alm's task —— View job


PR Review: Dynamic Import Tracking

I'm reviewing this PR to add dynamic import() expression tracking to codegraph. Let me analyze the changes systematically:

Todo List:

  • Phase 1: Root cause analysis - understand the problem
  • Phase 2: Configuration change detection
  • Phase 3: Test integrity verification
  • Phase 4: Code quality review
  • Verify recent fixes: Check if commits ccfd2a7 and 04cebcc properly addressed review feedback
  • Final Assessment: Rating and recommendation

Phase 1: Root Cause Analysis ✅

Problem Being Solved: Codegraph was reporting false "zero consumers" for symbols that are actually consumed via dynamic import() expressions. The issue stems from tree-sitter Query patterns not being able to match import() calls because they have function type import, which doesn't fit the standard call_expression patterns that expect identifier, member_expression, or subscript_expression functions.

Why This Approach: The solution adds a targeted tree walk (extractDynamicImportsWalk) specifically for dynamic imports in the query-based fast path, since query patterns can't capture them. This maintains the performance benefits of query-based extraction while filling the gap for dynamic imports.

Risk Assessment: Low risk - this is an additive feature that doesn't modify existing extraction logic. The approach is consistent with the existing pattern of using targeted walks for edge cases (like extractConstantsWalk for constants).

Phase 2: Configuration Change Detection ✅

No configuration files modified (biome.json, eslint, tsconfig, package.json unchanged). No linting rules disabled, test timeouts increased, or coverage thresholds lowered. This is purely a feature addition without any rule relaxation.

Phase 3: Test Integrity ✅

Test Coverage: Added comprehensive test case for dynamic imports in query-walk-parity.test.js covering:

  • Destructured imports: const { readFile } = await import('fs/promises')
  • Namespace imports: const mod = await import('./utils.js')
  • Side-effect imports: import('./side-effect.js')

Test Quality: The test properly validates both extraction paths (query vs walk) produce identical results, ensuring the new functionality maintains parity. The normalization function correctly handles the new dynamicImport flag.

No Red Flags: No test assertions weakened, no skipped validations, appropriate test coverage for the new feature.

Phase 4: Code Quality Review ✅

Implementation Quality:

  • Consistent with codebase: Uses same patterns as existing import extraction
  • DRY principle: Both query and walk paths use shared extractDynamicImportNames() helper
  • Robust parsing: Handles destructuring ({ a, b }), namespace (mod), array ([a, b]), and standalone patterns
  • AST navigation: Properly walks up parent chain to find variable declarator context
  • Performance conscious: Early return in extractDynamicImportsWalk to skip recursing into import() children

Edge Cases Handled:

  • await import() expressions (skips await wrapper)
  • ✅ Destructured imports with aliases: { a: localName }
  • ✅ Standalone side-effect imports with no variable assignment
  • ✅ Both shorthand and pair patterns in object destructuring

Security & Safety:

  • ✅ No SQL injection risks - uses existing parameterized insert patterns
  • ✅ No command injection - only processes AST nodes, no shell execution
  • ✅ Defensive programming - null checks on parent, childForFieldName calls
  • ✅ Input validation - checks node types before processing

Edge Kind Integration:

  • ✅ Properly added to CORE_EDGE_KINDS constant in src/kinds.js
  • ✅ Correctly integrated into builder logic with barrel file propagation
  • ✅ Maintains precedence order in edge classification (reexport > typeOnly > dynamicImport > imports)

Verified Recent Fixes ✅

Commit f89f0ca correctly addressed both review concerns:

  1. Misleading .then() comment ✅: Fixed comment in both query and walk paths to clarify that .then() pattern produces an edge with empty names, not extracted destructured names
  2. Template-literal/variable import paths ✅: Added debug-level warnings when import() has a non-static path (template literal or variable) so users are aware of the static analysis limitation

Commit 0f50eb3 ✅: Added backlog items ID 81 and 82 to track the remaining gaps

Final Assessment

Root Cause Analysis

Problem Being Solved: Dynamic imports causing false "zero consumers" reports - a real user pain point
Why This Approach: Necessary due to tree-sitter Query limitations, maintains performance while adding completeness
Risk Assessment: Very low risk - purely additive, well-tested, follows existing patterns

Critical Concerns

None identified. This is a clean, well-implemented feature addition.

Final Recommendation

  • Rating: ⭐⭐⭐⭐☆ (4/5)
  • Action: APPROVE
  • Reasoning: Excellent implementation of a needed feature. High-quality code that follows project conventions, comprehensive test coverage, addresses real user issues with dynamic imports. Only minor deduction for not being exceptional (missing broader dynamic import pattern coverage like .then() chains), but this is appropriate scope for the current issue.

Recommendation: Approved for merge. This PR successfully addresses the dynamic import tracking gap while maintaining code quality and test coverage standards.


Comment on lines +1054 to +1061
if (nameNode.type === 'array_pattern') {
const names = [];
for (let i = 0; i < nameNode.childCount; i++) {
const child = nameNode.child(i);
if (child.type === 'identifier') names.push(child.text);
}
return names;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rest_pattern elements in array destructuring are silently dropped.

The array_pattern loop only pushes children of type identifier, but a rest element like ...rest has type rest_pattern whose single child is the actual identifier. This means:

const [a, ...rest] = await import('./utils.js');
// names → ['a']  — 'rest' is silently dropped

Any call like rest.method() would fail to resolve to ./utils.js. The comment notes this case as "rare but possible", making it worth handling completely:

Suggested change
if (nameNode.type === 'array_pattern') {
const names = [];
for (let i = 0; i < nameNode.childCount; i++) {
const child = nameNode.child(i);
if (child.type === 'identifier') names.push(child.text);
}
return names;
}
if (nameNode.type === 'array_pattern') {
const names = [];
for (let i = 0; i < nameNode.childCount; i++) {
const child = nameNode.child(i);
if (child.type === 'identifier') names.push(child.text);
else if (child.type === 'rest_pattern') {
const inner = child.child(0) || child.childForFieldName('name');
if (inner && inner.type === 'identifier') names.push(inner.text);
}
}
return names;
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in e97e75b — the \ loop now handles \ elements by extracting the inner identifier, so \ correctly yields .

Comment on lines +185 to 192
name: 'dynamic import() expressions',
file: 'test.js',
code: `
const { readFile } = await import('fs/promises');
const mod = await import('./utils.js');
import('./side-effect.js');
`,
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing coverage for aliased destructuring in parity test.

The new test case covers shorthand destructuring ({ readFile }), namespace binding (mod), and bare side-effect imports, but the pair_pattern branch in extractDynamicImportNames (i.e., { a: localName }) is exercised by neither the query nor the walk path here. Adding one line would close that gap and verify both paths agree on the key-vs-alias choice:

Suggested change
name: 'dynamic import() expressions',
file: 'test.js',
code: `
const { readFile } = await import('fs/promises');
const mod = await import('./utils.js');
import('./side-effect.js');
`,
},
code: `
const { readFile } = await import('fs/promises');
const { readFile: rf } = await import('node:fs/promises');
const mod = await import('./utils.js');
import('./side-effect.js');
`,

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in e97e75b — added \ to the parity test to exercise the \ branch in both engines.

@claude
Copy link

claude bot commented Mar 9, 2026

Claude finished @carlos-alm's task —— View job


PR Review: Dynamic Import Tracking

I'm reviewing this PR to add dynamic import() expression tracking to codegraph. Let me analyze the changes systematically:

Todo List:

  • Phase 1: Root cause analysis - understand the problem
  • Phase 2: Configuration change detection
  • Phase 3: Test integrity verification
  • Phase 4: Code quality review
  • Verify recent fixes: Check if commits ccfd2a7 and 04cebcc properly addressed review feedback
  • Final Assessment: Rating and recommendation

Phase 1: Root Cause Analysis ✅

Problem Being Solved: Codegraph was reporting false "zero consumers" for symbols that are actually consumed via dynamic import() expressions. The issue stems from tree-sitter Query patterns not being able to match import() calls because they have function type import, which doesn't fit the standard call_expression patterns that expect identifier, member_expression, or subscript_expression functions.

Why This Approach: The solution adds a targeted tree walk (extractDynamicImportsWalk) specifically for dynamic imports in the query-based fast path, since query patterns can't capture them. This maintains the performance benefits of query-based extraction while filling the gap for dynamic imports.

Risk Assessment: Low risk - this is an additive feature that doesn't modify existing extraction logic. The approach is consistent with the existing pattern of using targeted walks for edge cases (like extractConstantsWalk for constants).

Phase 2: Configuration Change Detection ✅

No configuration files modified (biome.json, eslint, tsconfig, package.json unchanged). No linting rules disabled, test timeouts increased, or coverage thresholds lowered. This is purely a feature addition without any rule relaxation.

Phase 3: Test Integrity ✅

Test Coverage: Added comprehensive test case for dynamic imports in query-walk-parity.test.js covering:

  • Destructured imports: const { readFile } = await import('fs/promises')
  • Namespace imports: const mod = await import('./utils.js')
  • Side-effect imports: import('./side-effect.js')

Test Quality: The test properly validates both extraction paths (query vs walk) produce identical results, ensuring the new functionality maintains parity. The normalization function correctly handles the new dynamicImport flag.

No Red Flags: No test assertions weakened, no skipped validations, appropriate test coverage for the new feature.

Phase 4: Code Quality Review ✅

Implementation Quality:

  • Consistent with codebase: Uses same patterns as existing import extraction
  • DRY principle: Both query and walk paths use shared extractDynamicImportNames() helper
  • Robust parsing: Handles destructuring ({ a, b }), namespace (mod), array ([a, b]), and standalone patterns
  • AST navigation: Properly walks up parent chain to find variable declarator context
  • Performance conscious: Early return in extractDynamicImportsWalk to skip recursing into import() children

Edge Cases Handled:

  • await import() expressions (skips await wrapper)
  • ✅ Destructured imports with aliases: { a: localName }
  • ✅ Standalone side-effect imports with no variable assignment
  • ✅ Both shorthand and pair patterns in object destructuring

Security & Safety:

  • ✅ No SQL injection risks - uses existing parameterized insert patterns
  • ✅ No command injection - only processes AST nodes, no shell execution
  • ✅ Defensive programming - null checks on parent, childForFieldName calls
  • ✅ Input validation - checks node types before processing

Edge Kind Integration:

  • ✅ Properly added to CORE_EDGE_KINDS constant in src/kinds.js
  • ✅ Correctly integrated into builder logic with barrel file propagation
  • ✅ Maintains precedence order in edge classification (reexport > typeOnly > dynamicImport > imports)

Verified Recent Fixes ✅

Commit f89f0ca correctly addressed both review concerns:

  1. Misleading .then() comment ✅: Fixed comment in both query and walk paths to clarify that .then() pattern produces an edge with empty names, not extracted destructured names
  2. Template-literal/variable import paths ✅: Added debug-level warnings when import() has a non-static path (template literal or variable) so users are aware of the static analysis limitation

Commit 0f50eb3 ✅: Added backlog items ID 81 and 82 to track the remaining gaps

Final Assessment

Root Cause Analysis

Problem Being Solved: Dynamic imports causing false "zero consumers" reports - a real user pain point
Why This Approach: Necessary due to tree-sitter Query limitations, maintains performance while adding completeness
Risk Assessment: Very low risk - purely additive, well-tested, follows existing patterns

Critical Concerns

None identified. This is a clean, well-implemented feature addition.

Final Recommendation

  • Rating: ⭐⭐⭐⭐☆ (4/5)
  • Action: APPROVE
  • Reasoning: Excellent implementation of a needed feature. High-quality code that follows project conventions, comprehensive test coverage, addresses real user issues with dynamic imports. Only minor deduction for not being exceptional (missing broader dynamic import pattern coverage like .then() chains), but this is appropriate scope for the current issue.

Recommendation: Approved for merge. This PR successfully addresses the dynamic import tracking gap while maintaining code quality and test coverage standards.


…t test

Address PR review feedback:
- Extract `...rest` identifiers from array_pattern in extractDynamicImportNames
- Add `{ readFile: rf }` aliased destructuring case to query-walk parity test

Impact: 1 functions changed, 5 affected
@claude
Copy link

claude bot commented Mar 9, 2026

Claude finished @carlos-alm's task —— View job


Claude is working on this task


PR Review: Dynamic Import Tracking

I'm reviewing this PR to add dynamic import() expression tracking to codegraph. Let me analyze the changes systematically:

Todo List:

  • Phase 1: Root cause analysis - understand the problem
  • Phase 2: Configuration change detection
  • Phase 3: Test integrity verification
  • Phase 4: Code quality review
  • Verify recent fixes: Check if commits ccfd2a7 and 04cebcc properly addressed review feedback
  • Final Assessment: Rating and recommendation

Phase 1: Root Cause Analysis ✅

Problem Being Solved: Codegraph was reporting false "zero consumers" for symbols that are actually consumed via dynamic import() expressions. The issue stems from tree-sitter Query patterns not being able to match import() calls because they have function type import, which doesn't fit the standard call_expression patterns that expect identifier, member_expression, or subscript_expression functions.

Why This Approach: The solution adds a targeted tree walk (extractDynamicImportsWalk) specifically for dynamic imports in the query-based fast path, since query patterns can't capture them. This maintains the performance benefits of query-based extraction while filling the gap for dynamic imports.

Risk Assessment: Low risk - this is an additive feature that doesn't modify existing extraction logic. The approach is consistent with the existing pattern of using targeted walks for edge cases (like extractConstantsWalk for constants).

Phase 2: Configuration Change Detection ✅

No configuration files modified (biome.json, eslint, tsconfig, package.json unchanged). No linting rules disabled, test timeouts increased, or coverage thresholds lowered. This is purely a feature addition without any rule relaxation.

Phase 3: Test Integrity ✅

Test Coverage: Added comprehensive test case for dynamic imports in query-walk-parity.test.js covering:

  • Destructured imports: const { readFile } = await import('fs/promises')
  • Namespace imports: const mod = await import('./utils.js')
  • Side-effect imports: import('./side-effect.js')

Test Quality: The test properly validates both extraction paths (query vs walk) produce identical results, ensuring the new functionality maintains parity. The normalization function correctly handles the new dynamicImport flag.

No Red Flags: No test assertions weakened, no skipped validations, appropriate test coverage for the new feature.

Phase 4: Code Quality Review ✅

Implementation Quality:

  • Consistent with codebase: Uses same patterns as existing import extraction
  • DRY principle: Both query and walk paths use shared extractDynamicImportNames() helper
  • Robust parsing: Handles destructuring ({ a, b }), namespace (mod), array ([a, b]), and standalone patterns
  • AST navigation: Properly walks up parent chain to find variable declarator context
  • Performance conscious: Early return in extractDynamicImportsWalk to skip recursing into import() children

Edge Cases Handled:

  • await import() expressions (skips await wrapper)
  • ✅ Destructured imports with aliases: { a: localName }
  • ✅ Standalone side-effect imports with no variable assignment
  • ✅ Both shorthand and pair patterns in object destructuring

Security & Safety:

  • ✅ No SQL injection risks - uses existing parameterized insert patterns
  • ✅ No command injection - only processes AST nodes, no shell execution
  • ✅ Defensive programming - null checks on parent, childForFieldName calls
  • ✅ Input validation - checks node types before processing

Edge Kind Integration:

  • ✅ Properly added to CORE_EDGE_KINDS constant in src/kinds.js
  • ✅ Correctly integrated into builder logic with barrel file propagation
  • ✅ Maintains precedence order in edge classification (reexport > typeOnly > dynamicImport > imports)

Verified Recent Fixes ✅

Commit e97e75b correctly addressed both review concerns:

  1. Rest pattern handling ✅: Fixed rest_pattern extraction in array destructuring (const [a, ...rest]) - now properly extracts the identifier from the rest pattern's child
  2. Aliased destructuring test coverage ✅: Added test case for const { readFile: rf } pattern to ensure both extraction paths handle it identically

Earlier fixes also addressed:

  • Misleading .then() comment ✅: Fixed comment to clarify that .then() pattern produces an edge with empty names
  • Template-literal/variable import paths ✅: Added debug-level warnings when import() has a non-static path

Final Assessment

Root Cause Analysis

Problem Being Solved: Dynamic imports causing false "zero consumers" reports - a real user pain point
Why This Approach: Necessary due to tree-sitter Query limitations, maintains performance while adding completeness
Risk Assessment: Very low risk - purely additive, well-tested, follows existing patterns

Critical Concerns

None identified. This is a clean, well-implemented feature addition.

Final Recommendation

  • Rating: ⭐⭐⭐⭐☆ (4/5)
  • Action: APPROVE
  • Reasoning: Excellent implementation of a needed feature. High-quality code that follows project conventions, comprehensive test coverage, addresses real user issues with dynamic imports. Only minor deduction for not being exceptional (missing broader dynamic import pattern coverage like .then() chains), but this is appropriate scope for the current issue.

Recommendation: Approved for merge. This PR successfully addresses the dynamic import tracking gap while maintaining code quality and test coverage standards.


@carlos-alm
Copy link
Contributor Author

@greptileai

@carlos-alm carlos-alm merged commit c9f2a4f into main Mar 9, 2026
32 checks passed
@carlos-alm carlos-alm deleted the feat/dynamic-import-edges branch March 9, 2026 08:28
@github-actions github-actions bot locked and limited conversation to collaborators Mar 9, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant