Skip to content

feat(resolver): resolve calls through Object.defineProperty / defineProperties / create#1328

Merged
carlos-alm merged 13 commits into
mainfrom
feat/phase-8.3e-constructor-property-types
Jun 6, 2026
Merged

feat(resolver): resolve calls through Object.defineProperty / defineProperties / create#1328
carlos-alm merged 13 commits into
mainfrom
feat/phase-8.3e-constructor-property-types

Conversation

@carlos-alm

Copy link
Copy Markdown
Contributor

Summary

  • Seeds composite pts-map entries from Object.defineProperty(obj, "key", { value: fn }), Object.defineProperties(obj, { k: { value: fn } }), and const obj = Object.create({ f1, f2 }), so that obj.key() call sites can be traced back to the original function reference via the existing composite-key lookup in the edge builder
  • Adds a define-property.js fixture with 5 expected edges (3 pts-define-property + 2 pts-create-prototype), all resolving at 100% recall
  • Implemented in both the Rust extractor (native engine) and the TS extractor (wasm engine) for parity; 3 Rust unit tests added

Test plan

  • cargo test -p codegraph-core type_map_from_define — 3 new unit tests pass (334 total)
  • npx vitest run tests/benchmarks/resolution/resolution-benchmark.test.ts — 171 tests pass, JS recall 100% (23/23 edges)
  • npx vitest run --exclude "tests/benchmarks/**" — 2640 tests pass, 11 skipped (no regressions)

Closes #1320

…r-typed resolution (JS/TS)

Closes #1306

Extends `handlePropWriteTypeMap` to seed the points-to type map from
`this.prop = new ClassName(...)` assignments, enabling resolution of
`this.prop.method()` calls through the existing receiver-typed path.

Before: `this.logger.error/info/warn()` in UserService were unresolvable
because only `obj.prop = identifier` writes were tracked (Phase 8.3d).

After: `this.logger = new Logger(...)` seeds `typeMap['this.logger'] = Logger`
with confidence 1.0 (same as `const x = new Ctor()` in variable declarators).
The existing `resolveByMethodOrGlobal` and native `edge_builder.rs` fallback
(`or_else(|| type_map.get(receiver))`) then pick up `this.logger` and resolve
`this.logger.error()` → `Logger.error`.

Impact: JS fixture receiver-typed recall 2/5 → 5/5 (40% → 100%).
Ratchet JS benchmark gate: precision 0.85→1.0, recall 0.5→0.9.
…M path (#1314)

When a class method calls this.method() or super.method(), the WASM/JS
resolution path failed to find the target because methods are stored as
qualified names (e.g. Logger._write) but lookups used the unqualified
name (_write). The native Rust engine resolved this implicitly via its
class-scoped symbol table.

Fix: in buildFileCallEdges, after resolveCallTargets returns no targets
for a this/super receiver call, extract the class name prefix from the
caller's qualified name (e.g. Logger.info → Logger) and retry with the
fully-qualified method name (Logger._write) in the same file.

Fixes the JS benchmark recall (83.3% → 100%) and TS benchmark recall
(69.4% → 72.2%), satisfying CI thresholds of 90% and 72% respectively.
…1314)

Add inline comments explaining why JS recall is 0.9 and TS recall is
0.72, linking the thresholds to the Phase 8.3e same-class this.method()
fix and noting which remaining gaps (interface-dispatch, CHA) are tracked
in future phases.

Addresses Greptile P2 review comment on the threshold bump.
…roperties / create

Seed composite pts keys from three property descriptor APIs so that
obj.key() can be traced back to the original function reference:

- Object.defineProperty(obj, "key", { value: fn })
  → type_map["obj.key"] = "fn"
- Object.defineProperties(obj, { "k1": { value: v1 }, ... })
  → type_map["obj.k1"] = "v1", …
- const obj = Object.create({ f1, f2 })   (shorthand or pair)
  → type_map["obj.f1"] = "f1", type_map["obj.f2"] = "f2"

Implemented in both the Rust extractor (native engine) and the TS
extractor (wasm engine) for parity.  Adds a `define-property.js`
fixture with 5 expected edges (3 defineProperty/defineProperties + 2
create) that all now resolve at 100% recall.

Closes #1320
@greptile-apps

greptile-apps Bot commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR extends the call-graph resolver to track function references assigned via Object.defineProperty, Object.defineProperties, and Object.create, seeding composite pts-map entries so that obj.key() call sites can be traced to their original function. The feature is implemented in both the Rust extractor (native engine) and the TypeScript extractor (WASM engine) with matched logic and four Rust unit tests.

  • New Rust helpers (seed_define_property_entries, seed_object_create_entries, seed_descriptor_object, find_descriptor_value, extract_string_fragment) walk the AST at Phase 8.3e and push typed TypeMapEntry records.
  • New TS functions (handleDefinePropertyTypeMap, seedProtoProperties, findDescriptorValue) mirror the Rust logic, with string-type guards and anchored quote-stripping regex addressing issues from the previous review pass.
  • Test fixture define-property.js adds 5 new expected edges, and the embedding-regression teardown is simplified using fs.rmSync built-in retry options.

Confidence Score: 5/5

Safe to merge — the new AST-seeding helpers are well-guarded, the previously flagged string-type and quote-stripping bugs are all fixed, and the Rust/TS implementations are in parity.

The change adds three new type-map seeding paths, each with identifier-type and string-type guards that prevent malformed keys. All previously reviewed issues have been corrected. Four Rust unit tests and five new benchmark edges cover the concrete cases, and the full test suite passes without regressions.

No files require special attention.

Important Files Changed

Filename Overview
crates/codegraph-core/src/extractors/javascript.rs Adds five new helper functions for Phase 8.3e in the Rust extractor with four unit tests; logic is consistent with the TS counterpart and string guards are correct throughout.
src/extractors/javascript.ts Adds handleDefinePropertyTypeMap, findDescriptorValue, and seedProtoProperties matching Rust parity; all previously flagged quote-stripping and string-type-guard issues are resolved.
src/extractors/csharp.ts Minor cleanup: four if (!x
tests/benchmarks/resolution/fixtures/javascript/define-property.js New fixture covering all three APIs with five call sites; matches the five expected edges added to the JSON.
tests/benchmarks/resolution/fixtures/javascript/expected-edges.json Five new edge entries added (3 pts-define-property, 2 pts-create-prototype) consistent with the fixture; existing entries unchanged.
tests/benchmarks/resolution/resolution-benchmark.test.ts Registers the two new mode tags into TECHNIQUE_MAP and updates the JS recall comment; threshold value unchanged.
tests/search/embedding-regression.test.ts Replaces manual SharedArrayBuffer/Atomics retry loop with fs.rmSync built-in maxRetries/retryDelay options — cleaner and retries on more Windows error codes.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[AST Walker extractTypeMapWalk] -->|call_expression| B{handleDefinePropertyTypeMap}
    A -->|variable_declarator + call_expression| C{Object.create?}
    B --> D{method?}
    D -->|defineProperty| E[string type guard on key arg]
    D -->|defineProperties| F[inner loop over pairs]
    E --> G[findDescriptorValue]
    F --> G
    C -->|proto is object literal| I[seedProtoProperties]
    I --> J{child kind?}
    J -->|shorthand| K[name equals value]
    J -->|pair + identifier| L[key extraction with quote strip]
    G --> M[setTypeMapEntry obj.key to fn at 0.85]
    K --> M
    L --> M
    M --> N[Edge builder resolves call site]
Loading

Reviews (8): Last reviewed commit: "fix: resolve merge conflicts with main (..." | Re-trigger Greptile

Comment thread src/extractors/javascript.ts Outdated
Comment on lines +1598 to +1599
const key = arg1.text.replace(/['"]/g, '');
if (!key) return;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 Missing string type guard on the defineProperty key argument — diverges from the Rust counterpart. The Rust seed_define_property_entries calls extract_string_fragment, which returns None for anything that isn't a "string" AST node. The TS version uses .text.replace(/['"]/g, '') on whatever node is present, so Object.defineProperty(obj, myKey, { value: fn }) produces the entry obj.myKey (treating the variable name literally), and a key like "it's-a-key" would be incorrectly trimmed to its-a-key (the ' stripped by the regex). Both cases create a wrong key that can never match a real call site, polluting the type_map.

Suggested change
const key = arg1.text.replace(/['"]/g, '');
if (!key) return;
if (arg1.type !== 'string') return;
const key = arg1.text.replace(/^['"]|['"]$/g, '');
if (!key) return;

Fix in Claude Code

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed — added if (arg1.type !== 'string') return; guard before key extraction in the defineProperty path, matching the Rust extract_string_fragment behaviour which returns None for non-string AST nodes. Also switched to anchored /^['"]|['"]$/ to avoid stripping quotes within key values.

Comment thread src/extractors/javascript.ts Outdated
Comment on lines +1615 to +1616
const key = keyN.text.replace(/['"]/g, '');
const target = findDescriptorValue(valN);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Same quote-stripping inconsistency in the defineProperties path for the handleDefinePropertyTypeMap inner loop. The keyN.text.replace(/['"]/g, '') global replace would corrupt keys that contain a single or double quote character (e.g., "O'Brien"OBrien) and, unlike the Rust seed_descriptor_object, strips quotes globally rather than anchored to the string boundaries. The seedProtoProperties function has the same pattern.

Suggested change
const key = keyN.text.replace(/['"]/g, '');
const target = findDescriptorValue(valN);
const key = keyN.type === 'string' ? keyN.text.replace(/^['"]|['"]$/g, '') : keyN.text;
const target = findDescriptorValue(valN);

Fix in Claude Code

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed — updated the defineProperties inner loop to use keyN.type === 'string' ? keyN.text.replace(/^['"]|['"]$/g, '') : keyN.text, matching the Rust seed_descriptor_object approach. The anchored regex prevents corrupting keys that contain apostrophes or quote characters.

Comment thread src/extractors/javascript.ts Outdated
const keyN = child.childForFieldName('key');
const valN = child.childForFieldName('value');
if (!keyN || !valN || valN.type !== 'identifier') continue;
const key = keyN.type === 'string' ? keyN.text.replace(/['"]/g, '') : keyN.text;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 The same global-replace pattern appears in seedProtoProperties for pair keys, so a string key containing an apostrophe or double quote would be trimmed incorrectly.

Suggested change
const key = keyN.type === 'string' ? keyN.text.replace(/['"]/g, '') : keyN.text;
const key = keyN.type === 'string' ? keyN.text.replace(/^['"]|['"]$/g, '') : keyN.text;

Fix in Claude Code

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed — updated seedProtoProperties to use anchored /^['"]|['"]$/ (with the existing keyN.type === 'string' guard retained). This matches the Rust seed_descriptor_object behaviour and prevents corrupting keys containing internal apostrophes.

…operty key extraction (#1328)

- defineProperty path: guard arg1.type !== 'string' before extraction, matching
  the Rust seed_define_property_entries behaviour (which returns None for non-string AST nodes)
- defineProperties path: use anchored /^['"]|['"]$/ to strip only boundary quotes,
  not quotes within the key value, with string-type guard
- seedProtoProperties: same anchored regex fix for pair keys

Closes the TS/Rust engine parity gap flagged in Greptile review. (docs check acknowledged)
@carlos-alm

Copy link
Copy Markdown
Contributor Author

Addressed all 3 Greptile review items (in commit 818a557):

  1. defineProperty string-type guard — added if (arg1.type !== 'string') return; before key extraction, matching the Rust extract_string_fragment that returns None for non-string nodes
  2. defineProperties anchored regex + type guard — switched to keyN.type === 'string' ? keyN.text.replace(/^['"]|['"]$/, '') : keyN.text
  3. seedProtoProperties anchored regex — same anchored fix for pair key extraction (type guard was already present)

Also resolved the merge conflict with main (commit f8b727b), preserving all additions from both sides:

  • Main: 'class-hierarchy': 'cha-rta' and 'points-to': 'points-to' technique map entries
  • PR: 'pts-define-property' and 'pts-create-prototype' technique map entries
  • Merged the detailed precision-1.0 comment from main with the recall-0.9 rationale from this PR

@carlos-alm

Copy link
Copy Markdown
Contributor Author

@greptileai

@github-actions

github-actions Bot commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

Codegraph Impact Analysis

24 functions changed25 callers affected across 4 files

  • match_js_type_map in crates/codegraph-core/src/extractors/javascript.rs:73 (0 transitive callers)
  • seed_define_property_entries in crates/codegraph-core/src/extractors/javascript.rs:209 (1 transitive callers)
  • seed_object_create_entries in crates/codegraph-core/src/extractors/javascript.rs:254 (1 transitive callers)
  • seed_descriptor_object in crates/codegraph-core/src/extractors/javascript.rs:307 (2 transitive callers)
  • extract_string_fragment in crates/codegraph-core/src/extractors/javascript.rs:329 (4 transitive callers)
  • find_descriptor_value in crates/codegraph-core/src/extractors/javascript.rs:335 (3 transitive callers)
  • type_map_from_define_property in crates/codegraph-core/src/extractors/javascript.rs:2451 (0 transitive callers)
  • type_map_from_define_properties in crates/codegraph-core/src/extractors/javascript.rs:2464 (0 transitive callers)
  • type_map_from_object_create in crates/codegraph-core/src/extractors/javascript.rs:2484 (0 transitive callers)
  • call_receiver_for_define_property in crates/codegraph-core/src/extractors/javascript.rs:2500 (0 transitive callers)
  • extractCSharpParameters in src/extractors/csharp.ts:277 (4 transitive callers)
  • extractCSharpClassFields in src/extractors/csharp.ts:291 (4 transitive callers)
  • handleCSharpVarDecl in src/extractors/csharp.ts:333 (3 transitive callers)
  • extractTypeMapWalk in src/extractors/javascript.ts:1417 (3 transitive callers)
  • walk in src/extractors/javascript.ts:1424 (12 transitive callers)
  • handleVarDeclaratorTypeMap in src/extractors/javascript.ts:1444 (12 transitive callers)
  • handleDefinePropertyTypeMap in src/extractors/javascript.ts:1629 (12 transitive callers)
  • findDescriptorValue in src/extractors/javascript.ts:1684 (11 transitive callers)
  • seedProtoProperties in src/extractors/javascript.ts:1697 (11 transitive callers)
  • f1 in tests/benchmarks/resolution/fixtures/javascript/define-property.js:2 (2 transitive callers)

…ng regression (#1328)

On Windows, buildGraph uses closeDbDeferred which schedules the SQLite
WAL checkpoint via setImmediate. When afterAll runs before the deferred
close executes, fs.rmSync throws EBUSY on graph.db. Call flushDeferredClose
before the temp-dir cleanup to ensure the handle is closed synchronously.
@carlos-alm

Copy link
Copy Markdown
Contributor Author

Fixed Windows CI failure (commit 89f8f70): embedding-regression.test.ts afterAll was calling fs.rmSync on the temp dir before the SQLite handle deferred via closeDbDeferred/setImmediate had time to close, causing EBUSY on Windows. Added flushDeferredClose() before the fs.rmSync call per the documented guidance in src/db/connection.ts.

@carlos-alm

Copy link
Copy Markdown
Contributor Author

@greptileai

Merge origin/main (16a1182, 34988f8) into feat/phase-8.3e:

- expected-edges.json: keep define-property edges (pts-define-property,
  pts-create-prototype) from this branch AND inheritance/prototypes edges
  from main's #1325; merged file now has 30 total edges
- embedding-regression.test.ts: use main's retry-backoff afterAll and
  TestContext/ctx.skip() pattern; import from src/db/index.js
@carlos-alm

Copy link
Copy Markdown
Contributor Author

Resolved remaining merge conflicts and lint failure:

  1. Lint fix (commit 81a0041): applied biome formatting to the new define-property.js fixture — 4-space indent → 2-space, double-quoted keys in defineProperties → unquoted identifiers (which tree-sitter handles correctly via the existing keyN.type === 'string' guard)

  2. Merge conflicts resolved (commit 4299168): merged origin/main (16a1182 + 34988f8) — the two conflicts were:

    • expected-edges.json: kept both the 5 new define-property edges from this PR and the 7 class-inheritance/prototype edges added by test(bench): add JS/TS super.method() class-inheritance fixtures #1325; merged file now has 30 total edges
    • embedding-regression.test.ts: used main's approach (retry-backoff + TestContext/ctx.skip() + import from src/db/index.js)

@carlos-alm

Copy link
Copy Markdown
Contributor Author

Fixed Windows CI failure (commit eae19de): replaced the manual SharedArrayBuffer/Atomics.wait retry loop in embedding-regression.test.ts afterAll with Node.js's built-in fs.rmSync maxRetries/retryDelay options. The previous fix (89f8f70) used 3 attempts × 100ms = 300ms total, which is not enough for Windows Defender to release file locks. The new approach delegates to Node's native retry mechanism with 10 attempts × 200ms = 2s max wait, which is the idiomatic way to handle EBUSY on Windows.

@carlos-alm

Copy link
Copy Markdown
Contributor Author

@greptileai

- Keep both define-property.js edges (5, from this PR) and bind-call-apply.js
  edges (3, from main #1330) in expected-edges.json -- total now 33
- Preserve handleDefinePropertyTypeMap, findDescriptorValue, seedProtoProperties,
  and Object.create composite-key seeding (auto-merged correctly)
- Incorporate bind/call/apply tracking code from main into javascript.ts
- Update JS edge count comment in resolution-benchmark.test.ts (30 -> 33)
@carlos-alm

Copy link
Copy Markdown
Contributor Author

Resolved merge conflict with main (commit 38cf57b): PR #1330 (bind/call/apply) added bind-call-apply.js with 3 expected edges and its bind-tracking code to javascript.ts. The merge preserves both sets of changes — all 5 define-property edges (this PR) and the 3 bind-call-apply edges (from main). Updated the edge count comment to 33 in resolution-benchmark.test.ts.

@carlos-alm carlos-alm merged commit f0db64c into main Jun 6, 2026
29 checks passed
@carlos-alm carlos-alm deleted the feat/phase-8.3e-constructor-property-types branch June 6, 2026 02:55
@github-actions github-actions Bot locked and limited conversation to collaborators Jun 6, 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.

feat(resolver): resolve calls through Object.defineProperty / defineProperties / create

1 participant