Commit 7a47ce2
feat(js-extractor): resolve named function references passed as arguments (#947)
* feat(js-extractor): resolve named function references passed as arguments
When a named function is passed as a callback argument — e.g.
`router.use(handleToken)`, `arr.map(transform)`, or
`promise.then(onSuccess)` — the extractor now creates a dynamic call
edge to the referenced function. Previously only the method call itself
(e.g. `router.use`) was recorded, leaving the handler with zero callers
and a `dead-unresolved` classification.
Additionally, destructured `const` bindings such as
`const { handleToken, checkPermissions } = initAuth(config)` are now
emitted as function definitions so the edge resolver can match them as
call targets.
Both the query-based and walk-based extraction paths are covered.
Tested on a 1 895-file / 10-service Express + TypeScript monorepo:
- Before: handleToken 0 callers, checkPermissions 0 callers
- After: handleToken 21 callers across 6 services,
checkPermissions 18 callers across 3 services
- Graph edges: 29 465 → 30 768 (+1 303)
- Graph nodes: 18 674 → 18 984 (+310 from destructured bindings)
* fix(js-extractor): restrict destructured binding extraction to const
Review feedback from Greptile: the previous implementation emitted
function definitions for destructured bindings regardless of whether
the declaration used `const`, `let`, or `var`. A transient binding
like `let { userId } = parseRequest(req)` would be treated as a stable
function reference, producing spurious dynamic call edges if `userId`
was later passed as an argument.
Restrict extraction to `const` declarations, matching the semantics
of a stable re-export pattern. Same guard applied in both the query
path (`handleVariableDecl`) and the walk path (`extractDestructuredBindingsWalk`).
* feat(rust-extractor): port callback reference and destructured binding extraction
Port two JS extractor features from WASM/TS to the Rust native engine
for engine parity:
1. Callback reference extraction — identifier and member_expression
arguments in call expressions are emitted as dynamic call edges
(e.g. router.use(handleToken) produces a call to handleToken)
2. Destructured const binding extraction — const { a, b } = init()
creates function definitions for each destructured property,
restricted to const (not let/var)
Impact: 15 functions changed, 3 affected
* fix(rust-extractor): skip callback reference extraction on dynamic import() (#947)
The Rust handle_call_expr was calling extract_callback_reference_calls
unconditionally, including when fn.kind() == "import". The TS walk-path
equivalent (handleCallExpr) only runs callback-reference extraction in
the else branch, so import(modulePath) emitted a spurious dynamic call
to modulePath in the Rust engine but not in TS — violating dual-engine
parity.
Restructured handle_call_expr to early-return after handle_dynamic_import,
mirroring the TS if/else. Added unit test asserting import() arguments
are not emitted as dynamic calls.
Impact: 2 functions changed, 1 affected
* test(js-extractor): add TS test for renamed destructured binding (#947)
The Rust suite includes extracts_renamed_destructured_binding covering
const { original: renamed } = initAuth() — but the TS side had no
equivalent test. If the pair_pattern branch in extractDestructuredBindings
were accidentally broken, none of the existing TS tests would catch it.
Mirrors the Rust test: asserts the local alias is emitted as a function
definition and the original property name is not.
* fix(rust-extractor): add function-scope guard to destructured bindings (#947)
The Rust walk path's handle_var_decl emitted destructured const bindings
from any scope — including inside function bodies — while the TS query
path's extractDestructuredBindingsWalk skips FUNCTION_SCOPE_TYPES. For
function setup() { const { handleToken } = initAuth(); } the Rust engine
emitted handleToken as a definition but the TS WASM engine did not,
violating the dual-engine parity requirement.
Added find_parent_of_types guard to the object_pattern branch, mirroring
the guard already present on the constant-extraction branch. Added unit
test skips_destructured_bindings_inside_function_scope asserting the
parity.
Impact: 2 functions changed, 1 affected
* fix(js-extractor): guard TS walk-path destructured bindings by function scope (#947)
The walk-path handleVariableDecl emitted destructured const bindings
from any scope — including inside function bodies — while the query-path
extractDestructuredBindingsWalk already skips FUNCTION_SCOPE_TYPES.
When the walk path is used as fallback, function-internal const destructurings
were incorrectly registered as definitions, diverging from the query path.
Added hasFunctionScopeAncestor helper and gated the object_pattern branch
on it, mirroring the Rust handle_var_decl find_parent_of_types guard.
Added regression test asserting function-internal destructured bindings
are not emitted.
Impact: 2 functions changed, 4 affected
* fix(ci): rebuild native addon from PR source for test and parity jobs
The test and parity CI jobs were running against the last-published native
binary (@optave/codegraph-*@3.9.3) installed via npm. When a PR includes
Rust extractor changes, the WASM side picks up the new TS changes while
the native side stays on the old published binary, producing false parity
failures.
Add a `scripts/ci-rebuild-native.mjs` helper that runs `napi build --release`
and copies the resulting .node file over the published binary in
node_modules/@optave/<platform-pkg>/. Wire it into the `test` and `parity`
jobs after `npm install` so parity is compared against the Rust source
under review.
Also reformat the destructured-binding guard in src/extractors/javascript.ts
to a single line to satisfy biome (the lint job was failing on this).
* refactor(ci): share native build between test and parity jobs
Extract the napi build into a dedicated `native-host-build` matrix job that
uploads the `.node` per OS as an artifact. `test` and `parity` download that
artifact and install it over the published platform binary.
Replaces the per-job rebuild (compiled Rust twice) with one build shared by
both downstream jobs. `ci-rebuild-native.mjs` becomes `ci-install-native.mjs`
— copy-only, no build invocation.
* fix(ci): add native-host-build to ci-pipeline needs (#947)
Without this, a Rust compile failure in native-host-build causes test
and parity to be skipped (not failed), which the ci-pipeline check
treats as success since it only matches 'failure' and 'cancelled'.
Adding native-host-build to the needs list propagates its failure
directly so the required status check correctly turns red.
---------
Co-authored-by: carlos-alm <127798846+carlos-alm@users.noreply.github.com>1 parent b961d1c commit 7a47ce2
5 files changed
Lines changed: 608 additions & 8 deletions
File tree
- .github/workflows
- crates/codegraph-core/src/extractors
- scripts
- src/extractors
- tests/parsers
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
38 | 38 | | |
39 | 39 | | |
40 | 40 | | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
41 | 78 | | |
| 79 | + | |
42 | 80 | | |
43 | 81 | | |
44 | 82 | | |
| |||
70 | 108 | | |
71 | 109 | | |
72 | 110 | | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
73 | 121 | | |
74 | 122 | | |
75 | 123 | | |
| |||
135 | 183 | | |
136 | 184 | | |
137 | 185 | | |
| 186 | + | |
138 | 187 | | |
139 | 188 | | |
140 | 189 | | |
| |||
165 | 214 | | |
166 | 215 | | |
167 | 216 | | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
168 | 227 | | |
169 | 228 | | |
170 | 229 | | |
| |||
224 | 283 | | |
225 | 284 | | |
226 | 285 | | |
227 | | - | |
| 286 | + | |
228 | 287 | | |
229 | 288 | | |
230 | 289 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
280 | 280 | | |
281 | 281 | | |
282 | 282 | | |
| 283 | + | |
| 284 | + | |
| 285 | + | |
| 286 | + | |
| 287 | + | |
| 288 | + | |
| 289 | + | |
| 290 | + | |
| 291 | + | |
| 292 | + | |
| 293 | + | |
283 | 294 | | |
284 | 295 | | |
285 | 296 | | |
| |||
302 | 313 | | |
303 | 314 | | |
304 | 315 | | |
305 | | - | |
306 | | - | |
307 | | - | |
308 | | - | |
309 | | - | |
310 | | - | |
| 316 | + | |
| 317 | + | |
| 318 | + | |
| 319 | + | |
| 320 | + | |
| 321 | + | |
| 322 | + | |
311 | 323 | | |
312 | 324 | | |
313 | 325 | | |
314 | 326 | | |
| 327 | + | |
315 | 328 | | |
316 | 329 | | |
317 | 330 | | |
| |||
865 | 878 | | |
866 | 879 | | |
867 | 880 | | |
| 881 | + | |
| 882 | + | |
| 883 | + | |
| 884 | + | |
| 885 | + | |
| 886 | + | |
| 887 | + | |
| 888 | + | |
| 889 | + | |
| 890 | + | |
| 891 | + | |
| 892 | + | |
| 893 | + | |
| 894 | + | |
| 895 | + | |
| 896 | + | |
| 897 | + | |
| 898 | + | |
| 899 | + | |
| 900 | + | |
| 901 | + | |
| 902 | + | |
| 903 | + | |
| 904 | + | |
| 905 | + | |
| 906 | + | |
| 907 | + | |
| 908 | + | |
| 909 | + | |
| 910 | + | |
| 911 | + | |
| 912 | + | |
| 913 | + | |
| 914 | + | |
| 915 | + | |
| 916 | + | |
| 917 | + | |
| 918 | + | |
| 919 | + | |
| 920 | + | |
| 921 | + | |
| 922 | + | |
| 923 | + | |
| 924 | + | |
| 925 | + | |
| 926 | + | |
| 927 | + | |
| 928 | + | |
| 929 | + | |
| 930 | + | |
| 931 | + | |
| 932 | + | |
| 933 | + | |
| 934 | + | |
| 935 | + | |
| 936 | + | |
| 937 | + | |
| 938 | + | |
| 939 | + | |
| 940 | + | |
| 941 | + | |
| 942 | + | |
| 943 | + | |
| 944 | + | |
| 945 | + | |
| 946 | + | |
| 947 | + | |
| 948 | + | |
| 949 | + | |
| 950 | + | |
| 951 | + | |
| 952 | + | |
| 953 | + | |
| 954 | + | |
| 955 | + | |
| 956 | + | |
| 957 | + | |
| 958 | + | |
| 959 | + | |
868 | 960 | | |
869 | 961 | | |
870 | 962 | | |
| |||
1584 | 1676 | | |
1585 | 1677 | | |
1586 | 1678 | | |
| 1679 | + | |
| 1680 | + | |
| 1681 | + | |
| 1682 | + | |
| 1683 | + | |
| 1684 | + | |
| 1685 | + | |
| 1686 | + | |
| 1687 | + | |
| 1688 | + | |
| 1689 | + | |
| 1690 | + | |
| 1691 | + | |
| 1692 | + | |
| 1693 | + | |
| 1694 | + | |
| 1695 | + | |
| 1696 | + | |
| 1697 | + | |
| 1698 | + | |
| 1699 | + | |
| 1700 | + | |
| 1701 | + | |
| 1702 | + | |
| 1703 | + | |
| 1704 | + | |
| 1705 | + | |
| 1706 | + | |
| 1707 | + | |
| 1708 | + | |
| 1709 | + | |
| 1710 | + | |
| 1711 | + | |
| 1712 | + | |
| 1713 | + | |
| 1714 | + | |
| 1715 | + | |
| 1716 | + | |
| 1717 | + | |
| 1718 | + | |
| 1719 | + | |
| 1720 | + | |
| 1721 | + | |
| 1722 | + | |
| 1723 | + | |
| 1724 | + | |
| 1725 | + | |
| 1726 | + | |
| 1727 | + | |
| 1728 | + | |
| 1729 | + | |
| 1730 | + | |
| 1731 | + | |
| 1732 | + | |
| 1733 | + | |
| 1734 | + | |
| 1735 | + | |
| 1736 | + | |
| 1737 | + | |
| 1738 | + | |
| 1739 | + | |
| 1740 | + | |
| 1741 | + | |
| 1742 | + | |
| 1743 | + | |
| 1744 | + | |
| 1745 | + | |
| 1746 | + | |
| 1747 | + | |
| 1748 | + | |
| 1749 | + | |
| 1750 | + | |
| 1751 | + | |
| 1752 | + | |
| 1753 | + | |
| 1754 | + | |
| 1755 | + | |
| 1756 | + | |
| 1757 | + | |
| 1758 | + | |
| 1759 | + | |
| 1760 | + | |
| 1761 | + | |
| 1762 | + | |
| 1763 | + | |
| 1764 | + | |
| 1765 | + | |
| 1766 | + | |
| 1767 | + | |
| 1768 | + | |
| 1769 | + | |
| 1770 | + | |
| 1771 | + | |
| 1772 | + | |
| 1773 | + | |
| 1774 | + | |
| 1775 | + | |
| 1776 | + | |
| 1777 | + | |
| 1778 | + | |
| 1779 | + | |
| 1780 | + | |
| 1781 | + | |
| 1782 | + | |
| 1783 | + | |
| 1784 | + | |
| 1785 | + | |
| 1786 | + | |
| 1787 | + | |
| 1788 | + | |
| 1789 | + | |
| 1790 | + | |
| 1791 | + | |
| 1792 | + | |
| 1793 | + | |
| 1794 | + | |
| 1795 | + | |
| 1796 | + | |
1587 | 1797 | | |
0 commit comments