Skip to content

Fix JS/TS service call resolution and empty-graph reindex#455

Merged
vitali87 merged 1 commit intovitali87:mainfrom
clawdx3:fix/js-ts-service-resolution-and-empty-graph-reindex
Mar 21, 2026
Merged

Fix JS/TS service call resolution and empty-graph reindex#455
vitali87 merged 1 commit intovitali87:mainfrom
clawdx3:fix/js-ts-service-resolution-and-empty-graph-reindex

Conversation

@clawdx3
Copy link
Contributor

@clawdx3 clawdx3 commented Mar 18, 2026

Summary

  • force a full reindex when the graph is empty even if a hash cache exists
  • improve JS/TS member call resolution for constructor-injected services and this.method() calls
  • add regression tests for both behaviors

Problem

Two issues showed up in practice:

  1. --update-graph could leave Memgraph nearly empty if the database was cleaned but .cgr-hash-cache.json still existed, because unchanged files were skipped.
  2. JS/TS member call resolution could incorrectly resolve injected service calls like this.routeHistoryService.saveRoute() to a same-module method such as RoutesController.saveRoute().

Changes

Incremental indexing safeguard

  • add a graph-state probe in GraphUpdater
  • if the project has zero existing symbols in Memgraph, ignore the old hash cache and do a full reindex
  • keep the existing incremental path for normal non-empty graphs

JS/TS call resolution

  • treat this like an instance prefix alongside self
  • seed local type inference with constructor-injected property types and typed method parameters
  • resolve this.method() against the current class context first
  • resolve this.service.method() / typed local service vars before any weak same-module fallback
  • avoid guessing qualified member calls via trie/same-module fallback when they remain unresolved
  • add a single-suffix-match fallback in _try_resolve_method(...) to handle minor qualified-name namespace mismatches when the target is otherwise unambiguous

Validation

Ran:

uv run --with pytest pytest -q \
  codebase_rag/tests/test_call_resolver.py::TestJsTsMemberResolution \
  codebase_rag/tests/test_graph_updater_incremental.py::TestSlots::test_empty_graph_ignores_hash_cache_and_reindexes_all_files

Result:

  • 4 passed

Notes

This prefers missing edges over incorrect edges for unresolved qualified member calls, which should improve call-graph correctness for NestJS-style DI code.

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly improves the robustness and accuracy of the codebase's graph generation and call resolution capabilities. It addresses a critical issue where incremental indexing could fail to reindex an empty graph, and it refines how JavaScript and TypeScript member calls are resolved, particularly for dependency-injected services and this-prefixed methods. These changes lead to a more complete and correct call graph, especially for complex modern JavaScript/TypeScript applications.

Highlights

  • Incremental Indexing Safeguard: Implemented a mechanism to force a full graph reindex if the existing graph is empty, even when a hash cache is present, preventing --update-graph from leaving the database nearly empty after a cleanup.
  • Improved JS/TS Call Resolution: Enhanced the resolution of member calls for JavaScript/TypeScript, specifically addressing constructor-injected services and this.method() calls within class contexts.
  • Local Type Inference for JS/TS: Extended local type inference to include types from constructor-injected properties and typed method parameters, improving the accuracy of call resolution.
  • Refined Qualified Call Resolution Logic: Adjusted the call resolution order to prioritize stronger matches and avoid weak fallbacks (like same-module or trie-based guessing) for qualified member calls, aiming for higher correctness.
  • Single-Suffix-Match Fallback: Introduced a single-suffix-match fallback in _try_resolve_method to handle minor qualified-name namespace mismatches when the target is otherwise unambiguous.
  • Regression Tests Added: Added new regression tests to validate the improved empty-graph reindexing behavior and the enhanced JS/TS member call resolution.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Mar 18, 2026

Greptile Summary

This PR fixes two production bugs: (1) --update-graph leaving Memgraph nearly empty when a stale hash cache existed after a database wipe, and (2) NestJS-style this.service.method() calls being incorrectly resolved to same-module methods. Both fixes are accompanied by regression tests.

Key changes:

  • GraphUpdater._should_force_full_reindex probes Memgraph for existing project symbols; if zero are found and a non-empty hash cache is present, the cache is ignored and a full reindex is forced.
  • CallResolver now passes class_context through to _try_resolve_qualified_call / _resolve_two_part_call; this.method() two-part calls are resolved against the class context before any local-type or import lookup.
  • JsTypeInferenceEngine seeds local_var_types with constructor-injected property types (accessibility-modifier params) and typed method parameters before walking the AST, enabling this.service.method() to resolve correctly.
  • Same-module and trie fallbacks are now gated behind not self._has_separator(call_name), preventing incorrect cross-module guesses for qualified member calls.
  • A single-suffix-match fallback is added in _try_resolve_method to handle namespace mismatches; this can produce false-positive cross-namespace resolutions when the same class/method name exists elsewhere (see inline comment).
  • Several new hardcoded string literals ("this", "required_parameter", "optional_parameter", "accessibility_modifier", "type_annotation") and inline log messages were introduced without corresponding constants/logs.py entries, violating project code standards.

Confidence Score: 3/5

  • Mostly safe to merge after addressing the suffix-match false-positive risk and the hardcoded string violations.
  • The core logic for the empty-graph safeguard is solid and well-tested. The JS/TS this.method() resolution is correct for the targeted NestJS DI pattern. However, the new single-suffix-match fallback in _try_resolve_method can silently resolve calls to an unrelated class in a different namespace, producing incorrect call-graph edges — exactly the class of bug this PR aims to prevent. Additionally, several hardcoded string literals and inline log messages violate project standards and should be centralised before merging.
  • codebase_rag/parsers/call_resolver.py (suffix-match fallback correctness), codebase_rag/parsers/js_ts/type_inference.py and codebase_rag/graph_updater.py (hardcoded strings / log messages)

Important Files Changed

Filename Overview
codebase_rag/graph_updater.py Adds _should_force_full_reindex to probe Memgraph for empty symbol counts; integrates cleanly but the three new log messages are hardcoded inline rather than defined in logs.py.
codebase_rag/parsers/call_resolver.py Improves JS/TS this/self member-call resolution and stops the same-module/trie fallback for qualified calls; introduces a single-suffix-match fallback in _try_resolve_method that can produce false-positive cross-namespace resolutions, and uses hardcoded "this" instead of the existing constant.
codebase_rag/parsers/js_ts/type_inference.py Adds constructor-injected property and typed-parameter seeding to build_local_variable_type_map; logic is sound but introduces four new hardcoded tree-sitter node-type strings ("required_parameter", "optional_parameter", "accessibility_modifier", "type_annotation") that should be constants.
codebase_rag/tests/test_call_resolver.py Adds three focused regression tests for JS/TS member-call resolution covering injected-service calls, this.method() resolution, and the no-trie-fallback behaviour; all test cases are well-scoped.
codebase_rag/tests/test_graph_updater_incremental.py Adds a regression test verifying that an empty graph forces a full reindex even when a hash cache exists; test setup and assertions are correct.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[_process_files] --> B{force=True?}
    B -->|Yes| C[old_hashes = empty]
    B -->|No| D[Load hash cache from disk]
    D --> E[_should_force_full_reindex]
    E --> F{force or no cache?}
    F -->|Yes| G[return False]
    F -->|No| H[fetch_all: COUNT project symbols]
    H --> I{DB query fails?}
    I -->|Yes| G
    I -->|No| J{results empty?}
    J -->|Yes| K[return True]
    J -->|No| L{symbol_count == 0?}
    L -->|Yes| K
    L -->|No| G
    K --> M[old_hashes = empty - full reindex]
    C --> N[Process all eligible files]
    G --> O{old_hashes empty?}
    O -->|Yes| N
    O -->|No| P[Skip unchanged files via hash]
    M --> N

    subgraph JS_TS_Resolution[JS/TS Call Resolution]
        Q[resolve_function_call] --> R{IIFE or super?}
        R -->|No| S{method chain?}
        S -->|No| T[_try_resolve_via_imports with class_context]
        T --> U{2-part call with this prefix?}
        U -->|Yes| V[_try_resolve_method using class_context]
        U -->|No| W[_try_resolve_via_local_type]
        T --> X{call has separator?}
        X -->|No| Y[same-module and trie fallback]
        X -->|Yes, unresolved| Z[return None]
    end
Loading
Prompt To Fix All With AI
This is a comment left during a code review.
Path: codebase_rag/parsers/call_resolver.py
Line: 18

Comment:
**Hardcoded `"this"` string literal**

The project already has `JAVA_KEYWORD_THIS = "this"` in `constants.py` (line 1961). Using a bare `"this"` string here violates the "No Hardcoded Strings" rule. Either reuse the existing constant with a rename, or introduce a new `KEYWORD_THIS` constant.

```suggestion
_JS_INSTANCE_PREFIXES = {cs.KEYWORD_SELF, cs.JAVA_KEYWORD_THIS}
```

**Rule Used:** ## Technical Requirements

### Agentic Framework
-... ([source](https://app.greptile.com/review/custom-context?memory=d4240b05-b763-467a-a6bf-94f73e8b6859))

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: codebase_rag/parsers/js_ts/type_inference.py
Line: 134-145

Comment:
**Hardcoded tree-sitter node-type string literals**

`"required_parameter"`, `"optional_parameter"`, and `"accessibility_modifier"` appear as raw string literals in `_collect_constructor_parameter_type` (and also in `_collect_parameter_types` at lines 169-174). No corresponding constants exist in `constants.py` for these three node types. Per the project's "No Hardcoded Strings" rule, they should each be added as constants (e.g. `TS_REQUIRED_PARAMETER`, `TS_OPTIONAL_PARAMETER`, `TS_ACCESSIBILITY_MODIFIER`) and referenced via `cs.`.

The same applies to `"type_annotation"` used in `_extract_type_annotation_name` (line 195).

All four new literals should be centralised in `constants.py`.

**Rule Used:** ## Technical Requirements

### Agentic Framework
-... ([source](https://app.greptile.com/review/custom-context?memory=d4240b05-b763-467a-a6bf-94f73e8b6859))

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: codebase_rag/graph_updater.py
Line: 409-433

Comment:
**Inline log message strings not in `logs.py`**

Three new log-message strings are hardcoded directly inside `_should_force_full_reindex` rather than defined in `logs.py`:

- `"Incremental reindex graph-state probe failed for {name}: {error}"`
- `"No graph-state probe results for {name}; forcing full reindex"`
- `"No existing graph symbols found for {name}; ignoring hash cache and forcing full reindex"`

The project's coding standard says files that are not `logs.py` should have almost no string literals, and all log messages should live in `logs.py`. These three should be moved there and referenced via `ls.`.

**Rule Used:** ## Technical Requirements

### Agentic Framework
-... ([source](https://app.greptile.com/review/custom-context?memory=d4240b05-b763-467a-a6bf-94f73e8b6859))

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: codebase_rag/parsers/call_resolver.py
Line: 57-63

Comment:
**Suffix-match fallback can resolve to wrong namespace**

The single-suffix-match fallback searches the entire function registry for any entry ending with `ClassName.method_name`. If there is exactly one such entry globally, it is returned — even if it belongs to a completely unrelated namespace.

Example: `class_qn = "proj.a.MyClass"` with method `"doWork"`. If `"proj.b.MyClass.doWork"` exists (same short class name, different package) and is the only match, this fallback silently returns it instead of falling through to `_resolve_inherited_method`. This can produce the very kind of incorrect cross-module edge the PR aims to eliminate.

The safeguard should at minimum verify that `matched_qn` shares the same module prefix as `class_qn` before trusting the single match:

```python
if len(suffix_matches) == 1:
    matched_qn = suffix_matches[0]
    expected_prefix = cs.SEPARATOR_DOT.join(class_qn.split(cs.SEPARATOR_DOT)[:-1])
    if matched_qn.startswith(expected_prefix):
        return self.function_registry[matched_qn], matched_qn
```

How can I resolve this? If you propose a fix, please make it concise.

Last reviewed commit: "Fix JS/TS service ca..."


_SEPARATOR_PATTERN = re.compile(r"[.:]|::")
_CHAINED_METHOD_PATTERN = re.compile(r"\.([^.()]+)$")
_JS_INSTANCE_PREFIXES = {cs.KEYWORD_SELF, "this"}
Copy link
Contributor

Choose a reason for hiding this comment

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

P2 Hardcoded "this" string literal

The project already has JAVA_KEYWORD_THIS = "this" in constants.py (line 1961). Using a bare "this" string here violates the "No Hardcoded Strings" rule. Either reuse the existing constant with a rename, or introduce a new KEYWORD_THIS constant.

Suggested change
_JS_INSTANCE_PREFIXES = {cs.KEYWORD_SELF, "this"}
_JS_INSTANCE_PREFIXES = {cs.KEYWORD_SELF, cs.JAVA_KEYWORD_THIS}

Rule Used: ## Technical Requirements

Agentic Framework

-... (source)

Prompt To Fix With AI
This is a comment left during a code review.
Path: codebase_rag/parsers/call_resolver.py
Line: 18

Comment:
**Hardcoded `"this"` string literal**

The project already has `JAVA_KEYWORD_THIS = "this"` in `constants.py` (line 1961). Using a bare `"this"` string here violates the "No Hardcoded Strings" rule. Either reuse the existing constant with a rename, or introduce a new `KEYWORD_THIS` constant.

```suggestion
_JS_INSTANCE_PREFIXES = {cs.KEYWORD_SELF, cs.JAVA_KEYWORD_THIS}
```

**Rule Used:** ## Technical Requirements

### Agentic Framework
-... ([source](https://app.greptile.com/review/custom-context?memory=d4240b05-b763-467a-a6bf-94f73e8b6859))

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +134 to +145
if param_node.type not in {
"required_parameter",
"optional_parameter",
cs.TS_FORMAL_PARAMETER,
}:
return

has_accessibility_modifier = any(
child.type == "accessibility_modifier" for child in param_node.children
)
if not has_accessibility_modifier:
return
Copy link
Contributor

Choose a reason for hiding this comment

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

P2 Hardcoded tree-sitter node-type string literals

"required_parameter", "optional_parameter", and "accessibility_modifier" appear as raw string literals in _collect_constructor_parameter_type (and also in _collect_parameter_types at lines 169-174). No corresponding constants exist in constants.py for these three node types. Per the project's "No Hardcoded Strings" rule, they should each be added as constants (e.g. TS_REQUIRED_PARAMETER, TS_OPTIONAL_PARAMETER, TS_ACCESSIBILITY_MODIFIER) and referenced via cs..

The same applies to "type_annotation" used in _extract_type_annotation_name (line 195).

All four new literals should be centralised in constants.py.

Rule Used: ## Technical Requirements

Agentic Framework

-... (source)

Prompt To Fix With AI
This is a comment left during a code review.
Path: codebase_rag/parsers/js_ts/type_inference.py
Line: 134-145

Comment:
**Hardcoded tree-sitter node-type string literals**

`"required_parameter"`, `"optional_parameter"`, and `"accessibility_modifier"` appear as raw string literals in `_collect_constructor_parameter_type` (and also in `_collect_parameter_types` at lines 169-174). No corresponding constants exist in `constants.py` for these three node types. Per the project's "No Hardcoded Strings" rule, they should each be added as constants (e.g. `TS_REQUIRED_PARAMETER`, `TS_OPTIONAL_PARAMETER`, `TS_ACCESSIBILITY_MODIFIER`) and referenced via `cs.`.

The same applies to `"type_annotation"` used in `_extract_type_annotation_name` (line 195).

All four new literals should be centralised in `constants.py`.

**Rule Used:** ## Technical Requirements

### Agentic Framework
-... ([source](https://app.greptile.com/review/custom-context?memory=d4240b05-b763-467a-a6bf-94f73e8b6859))

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +409 to +433
except Exception as e:
logger.debug(
"Incremental reindex graph-state probe failed for {name}: {error}",
name=self.project_name,
error=e,
)
return False

if not results:
logger.info(
"No graph-state probe results for {name}; forcing full reindex",
name=self.project_name,
)
return True

symbol_count = results[0].get("c", 0)
if not isinstance(symbol_count, int):
return False

if symbol_count == 0:
logger.info(
"No existing graph symbols found for {name}; ignoring hash cache and forcing full reindex",
name=self.project_name,
)
return True
Copy link
Contributor

Choose a reason for hiding this comment

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

P2 Inline log message strings not in logs.py

Three new log-message strings are hardcoded directly inside _should_force_full_reindex rather than defined in logs.py:

  • "Incremental reindex graph-state probe failed for {name}: {error}"
  • "No graph-state probe results for {name}; forcing full reindex"
  • "No existing graph symbols found for {name}; ignoring hash cache and forcing full reindex"

The project's coding standard says files that are not logs.py should have almost no string literals, and all log messages should live in logs.py. These three should be moved there and referenced via ls..

Rule Used: ## Technical Requirements

Agentic Framework

-... (source)

Prompt To Fix With AI
This is a comment left during a code review.
Path: codebase_rag/graph_updater.py
Line: 409-433

Comment:
**Inline log message strings not in `logs.py`**

Three new log-message strings are hardcoded directly inside `_should_force_full_reindex` rather than defined in `logs.py`:

- `"Incremental reindex graph-state probe failed for {name}: {error}"`
- `"No graph-state probe results for {name}; forcing full reindex"`
- `"No existing graph symbols found for {name}; ignoring hash cache and forcing full reindex"`

The project's coding standard says files that are not `logs.py` should have almost no string literals, and all log messages should live in `logs.py`. These three should be moved there and referenced via `ls.`.

**Rule Used:** ## Technical Requirements

### Agentic Framework
-... ([source](https://app.greptile.com/review/custom-context?memory=d4240b05-b763-467a-a6bf-94f73e8b6859))

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +57 to +63
class_name = class_qn.split(cs.SEPARATOR_DOT)[-1]
suffix_matches = self.function_registry.find_ending_with(
f"{class_name}{separator}{method_name}"
)
if len(suffix_matches) == 1:
matched_qn = suffix_matches[0]
return self.function_registry[matched_qn], matched_qn
Copy link
Contributor

Choose a reason for hiding this comment

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

P1 Suffix-match fallback can resolve to wrong namespace

The single-suffix-match fallback searches the entire function registry for any entry ending with ClassName.method_name. If there is exactly one such entry globally, it is returned — even if it belongs to a completely unrelated namespace.

Example: class_qn = "proj.a.MyClass" with method "doWork". If "proj.b.MyClass.doWork" exists (same short class name, different package) and is the only match, this fallback silently returns it instead of falling through to _resolve_inherited_method. This can produce the very kind of incorrect cross-module edge the PR aims to eliminate.

The safeguard should at minimum verify that matched_qn shares the same module prefix as class_qn before trusting the single match:

if len(suffix_matches) == 1:
    matched_qn = suffix_matches[0]
    expected_prefix = cs.SEPARATOR_DOT.join(class_qn.split(cs.SEPARATOR_DOT)[:-1])
    if matched_qn.startswith(expected_prefix):
        return self.function_registry[matched_qn], matched_qn
Prompt To Fix With AI
This is a comment left during a code review.
Path: codebase_rag/parsers/call_resolver.py
Line: 57-63

Comment:
**Suffix-match fallback can resolve to wrong namespace**

The single-suffix-match fallback searches the entire function registry for any entry ending with `ClassName.method_name`. If there is exactly one such entry globally, it is returned — even if it belongs to a completely unrelated namespace.

Example: `class_qn = "proj.a.MyClass"` with method `"doWork"`. If `"proj.b.MyClass.doWork"` exists (same short class name, different package) and is the only match, this fallback silently returns it instead of falling through to `_resolve_inherited_method`. This can produce the very kind of incorrect cross-module edge the PR aims to eliminate.

The safeguard should at minimum verify that `matched_qn` shares the same module prefix as `class_qn` before trusting the single match:

```python
if len(suffix_matches) == 1:
    matched_qn = suffix_matches[0]
    expected_prefix = cs.SEPARATOR_DOT.join(class_qn.split(cs.SEPARATOR_DOT)[:-1])
    if matched_qn.startswith(expected_prefix):
        return self.function_registry[matched_qn], matched_qn
```

How can I resolve this? If you propose a fix, please make it concise.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

The pull request effectively addresses two key issues: ensuring a full reindex when the graph is empty despite an existing hash cache, and significantly improving JS/TS member call resolution for constructor-injected services and this.method() calls. The changes introduce robust logic for graph state probing and refined type inference mechanisms. The addition of comprehensive regression tests for both behaviors is highly commendable, ensuring the stability and correctness of these improvements. Overall, the changes enhance the reliability and accuracy of the codebase's graph generation and analysis capabilities.

),
{"prefix": f"{self.project_name}."},
)
except Exception as e:
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

Catching a broad Exception can mask underlying issues and make debugging harder. While fetch_all might be an external call, it's best practice to either catch more specific exceptions that are expected from the IngestorProtocol or add a comment explaining why a broad Exception is necessary here due to an unpredictable variety of errors.

Consider replacing Exception with a more specific exception type or a tuple of expected exceptions if possible. If not, please add a comment explaining the rationale for catching a generic Exception.

References
  1. When handling errors from external libraries that may raise multiple types of exceptions for configuration issues (e.g., ValueError for invalid formats and AssertionError for missing keys), catch a tuple of these specific exceptions, such as (ValueError, AssertionError), instead of a generic Exception.
  2. If catching a broad Exception is a pragmatic choice due to a wide and unpredictable variety of exceptions from external libraries, add a comment explaining the rationale to improve code maintainability.

@vitali87 vitali87 merged commit 58e5285 into vitali87:main Mar 21, 2026
1 check passed
vitali87 added a commit that referenced this pull request Mar 21, 2026
vitali87 added a commit that referenced this pull request Mar 21, 2026
vitali87 added a commit that referenced this pull request Mar 21, 2026
vitali87 added a commit that referenced this pull request Mar 21, 2026
revert: undo PR #455 due to 30 test regressions across multiple languages
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

2 participants