-
Notifications
You must be signed in to change notification settings - Fork 481
Dart Language Bug Report #87
Description
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:
- Read
pubspec.yamlto get the package name - Map
package:<name>/path/to/file.dart→<project_root>/lib/path/to/file.dart - 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