feat(resolver): resolve calls through Object.defineProperty / defineProperties / create by carlos-alm · Pull Request #1328 · optave/ops-codegraph-tool · GitHub
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

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

@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