Skip to content

fix(aver-extractor): parse-once, canonical identities, per-language correctness - #8

Open
5queezer wants to merge 1 commit into
masterfrom
fix/extractor-correctness
Open

fix(aver-extractor): parse-once, canonical identities, per-language correctness#8
5queezer wants to merge 1 commit into
masterfrom
fix/extractor-correctness

Conversation

@5queezer

Copy link
Copy Markdown
Owner

Summary

Fixes all 7 major findings and 9 minors from the aver-extractor code review. Every extract_<lang>_facts now parses its file exactly once, Rust entities get a single canonical qualified identity, and each per-language correctness bug has a regression test (including negative assertions where the bug was a false positive).

Major findings

1. Same source re-parsed many times per file — Every extract_*_facts now calls parse_with_language once and threads tree.root_node() into node-based collectors (rust.rs went from ~23 parses to 1; java 10, csharp 10, php 9, swift 8, kotlin/cpp 7, go/ts 6, ruby 5, c 4 — all now 1). The public string functions (extract_rust_functions, extract_csharp_classes, ...) remain as thin parse-once wrappers, so the public API and existing tests are unchanged.

2. Rust entity identity collisionsextract_rust_facts is now a single scope-aware walk emitting ONE canonical identity per entity and call edge:

  • top-level: path defines Function:foo (unchanged)
  • module items: Module:m defines Function:m::foo (no more parallel unqualified Function:foo)
  • impl methods: Type:T defines Function:T::m (methods no longer also appear as free functions or as Function:m::method module functions)
  • call edges: qualified caller; bare callees qualified with the caller's module path
  • two modules each defining fn f() now yield Function:a::f / Function:b::f instead of merging into one Function:f

3. Text-split impl headersimpl_item headers are read from the grammar's trait/type fields with generic base-name stripping (Trait<T>Trait). impl<T> Trait<T> for Type<T> where T: Clone and unsafe impl Send for Foo now produce correct implements facts instead of garbage/none.

4. C struct/enum usages emitted as definesstruct_specifier/enum_specifier/class_specifier only count as definitions when they have a body field (C and C++). struct Foo *p; and class Foo; no longer emit defines. Forward declarations still inform the C++ base-kind lookup.

5. C#/Swift interface-only base lists emitted false extends Class:* — the extends branch now skips names in the file-locally declared interface/protocol set and picks the first remaining base (Kotlin extends applies the same rule). New negative assertions cover the false positives.

6. Kotlin text-scan classificationclass_declaration is classified by its own keyword tokens (class/interface/enum) and its direct type_identifier child, not by scanning node text. data/sealed/annotated classes are found; an interface whose body mentions class in a comment is no longer misclassified; delegation specifiers are anchored to direct children.

7. Plugin runner — stdin is explicitly closed after the request write (plugins reading to EOF can't deadlock), requests now have a wait timeout (default 30s, JsonRpcPluginRunner::with_timeout to override; timed-out children are killed), JSON-RPC error responses surface as Error::PluginError { code, message } instead of an opaque serde error, and the response id is validated against the request (Error::PluginIdMismatch).

Minors

  • go.rs / php.rs / ruby.rs: utf8_text(...).unwrap_or_default() now propagates with ?.
  • collect_tests: detects #[tokio::test]/#[rstest]/#[test_case] via the attribute path's last segment; extra attributes or comments between attribute and fn no longer drop the marker; #[cfg(test)] is not a test.
  • Module test mappings are computed from the module's own direct items (nested-module tests no longer re-emitted with the outer path).
  • Module imports reuse the use-declaration expansion: mod m { pub use a::{b, c}; }Module:m imports Module:a::b + Module:a::c.
  • C++ extends objects use the base's declared kind (struct Foo : Barextends Class:Bar when Bar is a class; falls back to the subject kind for undeclared bases).
  • js/cpp/php heritage/base clauses are anchored on the type's own direct-child clause (nested/anonymous classes' clauses no longer leak to the outer type).
  • Swift actors keep one identity: Actor:Name in implements/extends subjects (was folded into Class:).
  • validate_facts is shared between src/lib.rs and prose/mod.rs (was duplicated verbatim).

Test evidence

  • cargo test -p aver-extractor: 180 passed, 0 failed (was 148; +32 regression tests: 12 rust, 15 multilang, 5 plugin — deterministic, offline, no #[ignore])
  • cargo clippy -p aver-extractor --all-targets -- -D warnings: clean
  • cargo fmt: applied
  • cargo test -p aver-core --test extractor_ingestion --test extractor: 8 passed (aver-core's parallel ExtractorFact/ingestion path unaffected — no workspace crate depends on aver-extractor yet)

Sample of new negative-assertion regressions: no unqualified Function:f for mod-nested fns, no method-as-free-function, no defines Struct:Foo for struct Foo *p;, no extends Class:IRecallable for C# interface-only bases, no outer-class capture of nested JS/C++/PHP clauses, plugin timeout/id-mismatch/error-response handling.

@coderabbitai

coderabbitai Bot commented Jul 18, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@5queezer, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 43 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: bda7001c-8287-466d-868d-0544cd70c979

📥 Commits

Reviewing files that changed from the base of the PR and between b5f1045 and e4774cf.

📒 Files selected for processing (18)
  • crates/aver-extractor/src/lang/c.rs
  • crates/aver-extractor/src/lang/cpp.rs
  • crates/aver-extractor/src/lang/csharp.rs
  • crates/aver-extractor/src/lang/go.rs
  • crates/aver-extractor/src/lang/java.rs
  • crates/aver-extractor/src/lang/javascript.rs
  • crates/aver-extractor/src/lang/kotlin.rs
  • crates/aver-extractor/src/lang/php.rs
  • crates/aver-extractor/src/lang/python.rs
  • crates/aver-extractor/src/lang/ruby.rs
  • crates/aver-extractor/src/lang/rust.rs
  • crates/aver-extractor/src/lang/swift.rs
  • crates/aver-extractor/src/lang/typescript.rs
  • crates/aver-extractor/src/lib.rs
  • crates/aver-extractor/src/prose/mod.rs
  • crates/aver-extractor/tests/jsonrpc_plugin.rs
  • crates/aver-extractor/tests/multilang.rs
  • crates/aver-extractor/tests/rust.rs
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/extractor-correctness

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@5queezer

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 27, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@5queezer
5queezer force-pushed the fix/extractor-correctness branch from 3279087 to 57bbe11 Compare July 27, 2026 12:40
@5queezer

Copy link
Copy Markdown
Owner Author

Review and validation update: CodeRabbit completed a fresh review with no actionable inline findings. Rebased onto current master at 57bbe11.

Local validation passed: format check; aver-extractor Clippy with all targets/features and -D warnings; and the full extractor test suite (6 JSON-RPC, 108 multi-language, 4 prose, and 62 Rust tests passed).

— Hermes Agent

…orrectness

- Parse each file once per extract_*_facts call and thread the root
  node into node-based collectors (was: up to ~23 re-parses per Rust
  file, 4-10 for other languages). Public string functions stay as
  thin parse-once wrappers.
- rust.rs: emit ONE canonical, qualified identity per entity and call
  edge via a single scope-aware walk - module-qualified for mod items
  (Function:m::f), type-qualified for impl methods
  (Function:Type::method); drop the unqualified duplicate layer that
  merged same-named entities in the claim graph.
- rust.rs: read impl headers from tree-sitter trait/type fields with
  generic base-name stripping instead of text splitting, fixing
  generics, where-clauses and unsafe impls.
- c.rs/cpp.rs: only emit defines for struct/enum/class specifiers that
  have a body (usages like 'struct Foo *p;' and forward declarations
  no longer produce definitions).
- csharp/swift: exclude file-locally declared interfaces/protocols
  from the extends branch (interface-only base lists no longer emit
  false 'extends Class:*'); Kotlin extends applies the same rule.
- kotlin: classify class_declaration by its own keyword tokens and
  direct type_identifier child instead of text scans (data/sealed/
  annotated classes found; comments mentioning 'class' cannot
  misclassify interfaces).
- plugin runner: close stdin after the request write, add a wait
  timeout (default 30s, with_timeout to override), surface JSON-RPC
  error responses as Error::PluginError, and validate the response id.
- minors: propagate utf8 errors in go/php/ruby; detect
  #[tokio::test]/#[rstest]/#[test_case] via attribute path segments
  and keep the marker across attributes/comments; scope module test
  mappings to the module's own items; expand grouped 'pub use' in
  module imports; C++ extends objects use the declared base kind;
  anchor heritage/base clauses on the type's own clause in js/cpp/php;
  keep Swift actors as Actor:* in implements/extends; share
  validate_facts between lib.rs and prose.
@5queezer
5queezer force-pushed the fix/extractor-correctness branch from 57bbe11 to e4774cf Compare July 27, 2026 12:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant