Skip to content

Dart Language Bug Report #87

@alangprs

Description

@alangprs

1 | Zero CALLS edges for Dart files | Critical | Dart only
2 | importers_of query returns 0 results for Dart package: imports | Critical | Dart only
3 | inheritors_of query returns 0 results due to bare vs qualified name mismatch | Medium | All languages

Combined effect: callers_of, callees_of, importers_of, inheritors_of, get_impact_radius_tool, detect_changes_tool, and get_affected_flows_tool are all non-functional for Dart projects.


Bug 1: Zero CALLS edges for Dart

Observed behavior

After a full build on a Dart project with ~7K files:

Edges: 101,398
  IMPORTS_FROM: 58,033
  CONTAINS:     33,096
  INHERITS:     10,036
  CALLS:           233   ← all from 9 JavaScript files, 0 from Dart

query_graph_tool(pattern="callers_of", target="<any Dart method>") always returns 0 results.

Root cause

parser.py line ~200 — the _CALL_TYPES dictionary does not contain a "dart" entry:

_CALL_TYPES: dict[str, list[str]] = {
    "python": ["call"],
    "javascript": ["call_expression"],
    "typescript": ["call_expression"],
    # ... other languages
    # "dart" is missing
}

When the parser walks the AST (_extract_from_tree), it does:

call_types = set(_CALL_TYPES.get(language, []))

For Dart, this returns an empty set, so no CALLS edges are ever created.

Why it's not a simple fix

Dart's tree-sitter grammar (tree-sitter-dart) does not provide a call_expression node type like most other languages. A function call like print('hello') produces:

expression_statement
  identifier              ← function name "print"
  selector
    argument_part
      arguments           ← the parenthesized args

And a method call like obj.method() produces:

expression_statement
  identifier              ← "obj"
  selector
    unconditional_assignable_selector   ← ".method"
  selector
    argument_part
      arguments

There is no single wrapper node type for the call. Adding "dart": ["call_expression"] to _CALL_TYPES would have no effect.

Suggested fix

Add a Dart-specific call extraction handler (similar to the existing _extract_r_constructs or _extract_lua_constructs). The handler would detect expression_statement nodes where a child selector contains argument_part, then trace back the preceding identifier / unconditional_assignable_selector chain to extract the callee name.


Bug 2: importers_of returns 0 for Dart package: imports

Observed behavior

query_graph_tool(pattern="importers_of", target="lib/features/casino_v2/constants/casino_tab_bar_category/casino_tab_bar_category.dart")
# → 0 results

Even though dozens of files import this file via import 'package:tiger_app/features/casino_v2/constants/...'.

Root cause

Edge storage: _extract_import (parser.py ~line 2388) correctly extracts the import string. But _resolve_module_to_file (parser.py ~line 2079) only resolves relative Dart imports (starting with .). For package: URIs, it returns None, so the edge target is stored as the raw URI string:

IMPORTS_FROM: /abs/path/to/file_a.dart → package:tiger_app/features/.../file_b.dart

Query: importers_of (query.py ~line 229) resolves the target to an absolute file path (/Users/.../lib/features/.../file_b.dart) and queries store.get_edges_by_target(abs_path). Since edges store package:tiger_app/... and the query uses /Users/.../lib/..., they never match.

Suggested fix

In _do_resolve_module, add package: URI resolution for Dart:

  1. Read pubspec.yaml to get the package name
  2. Map package:<name>/path/to/file.dart<project_root>/lib/path/to/file.dart
  3. Store the resolved absolute path as the edge target

This would make importers_of work, and also improve get_impact_radius_tool accuracy since it traverses IMPORTS_FROM edges.


Bug 3: inheritors_of returns 0 (all languages)

Observed behavior

query_graph_tool(pattern="inheritors_of", target="Animal")
# → 0 results, even though Dog extends Animal in the same file

Root cause

Edge storage: _get_bases returns bare class names. INHERITS edges are stored with unqualified targets:

INHERITS: tests/fixtures/sample.dart::Dog → Animal           ← bare name
INHERITS: tests/fixtures/sample.dart::Dog → SwimmingMixin    ← bare name

Query: inheritors_of (query.py ~line 263) first resolves "Animal" to a node, getting the qualified name tests/fixtures/sample.dart::Animal. It then calls store.get_edges_by_target("tests/fixtures/sample.dart::Animal"). The SQL is WHERE target_qualified = ?. Since the stored target is "Animal" (bare), it never matches "tests/fixtures/sample.dart::Animal".

This bug is not Dart-specific — it affects all languages because _get_bases always returns bare class names.

Suggested fix

Either:

Option A (fix at storage time): In _get_bases or the INHERITS edge creation step, resolve bare class names to qualified names using the file's symbol table or a cross-file lookup during the graph merge phase.

Option B (fix at query time): In the inheritors_of query handler, add a fallback that also matches bare target names. Similar to how callers_of already has search_edges_by_target_name fallback logic.

Option B is lower risk and doesn't require reprocessing existing graphs.


Test gap

tests/test_parser.py lines 277-345 test Dart for:

  • Language detection
  • Class/Function/Mixin/Enum node extraction
  • Import edges
  • Inheritance edges
  • Contains edges
  • Method parent resolution

There are no tests for Dart CALLS edges, which is why Bug 1 was never caught. Adding a test like test_parse_dart_call_edges that parses a file with function/method calls and asserts CALLS edges exist would prevent regression.


Environment

  • code-review-graph: v2.0.0
  • Python: 3.13
  • tree-sitter: <1, >=0.23.0
  • tree-sitter-language-pack: <1, >=0.3.0
  • OS: macOS Darwin 24.3.0

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions