You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
* Add Rust AST extraction tests and Rust extractor detection tests
- Implemented comprehensive tests for Rust AST extraction in `rust_ast_test.go`, covering various Rust constructs such as structs, enums, traits, type aliases, constants, and method calls.
- Added tests for handling dependencies, including internal and external dependencies, and cross-crate workspace dependencies.
- Created utility functions for writing temporary Rust repositories for testing purposes in `rust_test.go`.
- Established detection tests for identifying Rust projects with and without `Cargo.toml`, including support for subdirectory monorepos.
* feat: add Rust support to architecture and documentation
* feat(rust): add support for struct literal instantiation tracking in AST extraction
* feat(rust): implement Axum route extraction and add corresponding tests
* feat(rust): enhance Rust extractor with Axum route detection and update cache version
* feat(rust): update cache version to v112 and enhance AST extraction with macro argument handling
* feat(rust): enhance Rust AST extraction with function reference tracking and macro argument handling
* feat(rust): enhance AST extraction with scoped function references and sibling file submodule handling
* feat(rust): update cache version to v114 and enhance AST extraction with Drop and Future method overrides
* improve(rust): enchance Rust support to extractors and enhance AST instantiation tracking
* feat(rust): enhance AST extraction with scoped variant handling and update cache version to v119
* feat(rust): update cache version to v113
* feat(rust): update cache version to v114 and enhance AST extraction for test functions and array literals
| `output.dir` | Output directory for artifacts | `".enola"` |
@@ -531,6 +532,7 @@ Each extractor is detected by characteristic project files and then parses what
531
532
| Svelte | tree-sitter | `package.json` with `svelte` dependency, or `svelte.config.js/ts/mjs` / `@sveltejs/kit` for SvelteKit |
532
533
| Swift | tree-sitter | `Package.swift`, `.xcodeproj`, or `.xcworkspace` present |
533
534
| Ruby | tree-sitter | `Gemfile` present |
535
+
| Rust | tree-sitter | `Cargo.toml` present (root or up to 3 levels deep) |
534
536
| C/C++ | tree-sitter | a C source (`.c`) or C++ source (`.cpp`/`.cc`/`.cxx`/`.hpp`/...) present, or a build file (`CMakeLists.txt`/`Makefile`/`meson.build`/`*.vcxproj`) plus any header |
535
537
| PHP | tree-sitter | `composer.json`, a WordPress bootstrap file (`wp-load.php`/`wp-settings.php`/`wp-config.php`), or any `.php` source within 3 directory levels |
536
538
| OpenAPI | YAML/JSON scanner| any file containing `openapi:` or `swagger:` |
@@ -554,6 +556,8 @@ Each extractor is detected by characteristic project files and then parses what
554
556
555
557
**Swift** (tree-sitter) emits symbol facts for classes, structs, enums, protocols, and extensions plus their methods, initializers, and properties, named `<targetDir>.<Type>.<member>` — where `<targetDir>` is the file's resolved SPM/XcodeGen *target* module (parsed from `Package.swift` and `project.yml`), not its leaf directory. Members declared inside a type are classified `symbol_kind: method`; free functions stay `function`. It walks bodies for the call graph: same-type `self.`/`self?.` dispatch, member calls on any receiver (`coordinator?.show()`, `delegate?.tap()`), and cross-`extension` calls all become `calls` edges — emitted as bare short names at walk time (extraction is parallel-per-file) and bound in a serial post-pass against a project-wide method index (unique name → the qualified `dir.Type.method`, ambiguous → the bare name still matched by short name, unmatched → dropped so stdlib/framework calls don't create phantom edges). A further post-pass resolves **inherited-method calls** — a subclass or protocol conformer calling a base-class / protocol-extension method — by walking the caller type's supertype chain (from the `implements` edges) nearest-first and rewriting the otherwise-dangling call target to the declaring ancestor's method fact (`dir.DataModel.runRequest`), so class/protocol hierarchies are traversable for impact analysis, dead-code, and the performs_io closure. `Foo()` → `instantiates`, constructor/property DI → `injects`, SwiftUI `View`→`ViewModel` → `depends_on`, and custom-operator usage (`a <- b`, but not stdlib operators like `+`/`<=`) → a `calls` edge to the operator. Top-level calls in `#!/usr/bin/swift` scripts are captured via a file-scope reference fact. Like the other AST extractors, it walks function/method bodies — and also computed-property getters and `willSet`/`didSet` observers — for the standard complexity metrics `cyclomatic`, `loop_depth`, `loop_count`, `calls_in_loop`, and `recursive_self`, which the enterprise `analyze_performance` tool consumes; syntactic `for`/`while`/`repeat-while` and iterator closures (`map`/`forEach`/`filter`/…) count as loops, but **constant-bounded loops do not add scaling depth** — a literal integer range (`for i in 0..<10`), a literal-bound `stride(...)`, or an iterator over an array/dictionary literal or ALL-CAPS constant (`STOP_CHARS.forEach`) runs a fixed number of times, so it never inflates a genuine O(n) into a false O(n²)/O(n³). A method whose body invokes a network/file I/O primitive (`URLSession`/`dataTask`/`.data(for:)`, Alamofire `request`/`download`/`upload`, `Data(contentsOf:)`) is tagged `io_direct`; a serial post-pass then propagates that up the call graph into a transitive `performs_io` prop — crossing ambiguous kept-bare member-call edges by expanding them through the method-name index (bounded) rather than mutating the graph — so the enterprise `analyze_performance` tool can flag a per-iteration network N+1 (a loop calling a method that transitively hits the network) even when the I/O sits behind wrapper layers. It is **iOS-aware**: SwiftUI views (`View`/`App`/`Scene`), UIKit (`UIViewController`/`UIView` subclasses), Combine view models (`ObservableObject`, `@Observable`), architectural roles (Repositories, Use Cases, Coordinators, Services, DI containers), and `@MainActor`. *Limitation:* the vendored tree-sitter-swift grammar cannot parse a few advanced constructs — notably a tuple-type metatype `(A, B).self` (e.g. `withTaskGroup(of: (UUID, Result<T, Error>).self)`) — and its error recovery then flattens the whole enclosing type to file scope, so that file's type node is lost and its methods surface as top-level `function` symbols (~3% of files in a large iOS codebase). Dead-code detection stays accurate on these — a member call whose method was flattened falls back to resolving against the top-level function of that name (a rare same-name collision biases toward a missed lead, never a false accusation) — but the type's coupling/impact edges are degraded for the affected file until the construct is removed or the grammar gains support.
556
558
559
+
**Rust** (tree-sitter) targets a Cargo workspace or single crate. Every `Cargo.toml` in the repo is scanned up front (a minimal line-based `[package]`/`name` scan, not a full TOML parser) into a crate-name → crate-directory index, so cross-crate `use` paths resolve without a second parsing pass. It emits symbol facts for `fn`/`struct`/`enum`/`trait`/`type`/`const`/`static` items, qualifying nested `mod { }` blocks and `impl`/`trait` bodies into the name (`<dir>.<mod>.<Type>.<method>`); a function/method inside an `impl` or `trait` block is a `method`, everything else a `function`, and an impl-block method with no `self` parameter is tagged `static`. Because `impl Trait for Type` is a separate top-level item from `Type`'s own declaration — frequently in a different file — the resulting `implements` edge is attached to `Type`'s existing symbol fact by a small post-pass over the merged fact set (`applyImplements`) rather than emitted as a second, otherwise-empty fact that would double-count `Type` in symbol-kind stats. `use` declarations (including brace-expanded lists like `use std::{fmt, collections::HashMap}`) become `dependency` facts classified `internal`/`external`/`stdlib`: `self::`/`super::`/`crate::` paths and other in-workspace crate names resolve to the real submodule directory when one is known (trying progressively shorter suffixes against the known module-directory set, mirroring Python's import resolution) and fall back to the crate/module root otherwise; `std`/`core`/`alloc` are `stdlib`. Call resolution is deliberately conservative: a bare call resolves against a sibling method in the enclosing `impl`/`trait` block or a same-directory top-level function; `self.method()`/`Self::method()` resolve to the enclosing type's sibling method; any other receiver or path form (`recv.method()`, `Type::method()` for a different type) falls back to a bare short-name `calls` edge so dead-code matching still sees it used, without guessing a canonical target it can't verify. It also computes `cyclomatic` per function/method body (`if`, `match` arm, `while`/`for`/`loop`, the `?` operator, and `&&`/`||` each add one). Both construction forms emit `instantiates`: a tuple-call (`Foo()`) as part of ordinary call handling, and a named struct literal (`Foo { field: value }`, including functional-update syntax `Foo { field: value, ..base }`) via its own `struct_expression` handler, since it isn't a `call_expression` in the grammar. It also detects Axum routes: a `.route("/path", get(handler))` builder call (including chained verbs like `get(a).post(b)`) emits one `route` fact per (path, method) pair, matched structurally rather than gated on an `axum` dependency check. *Scope:* `.route_service(...)` and `.nest(...)` sub-router prefixes aren't handled (see [`axum.go`](internal/extractors/rustextractor/axum.go)), Actix/Rocket have no route awareness yet, and macros are treated as opaque (no expansion, no edges through them).
560
+
557
561
**OpenAPI** scans for spec files independently of the main walker (so it finds them even when `*.yaml`/`*.json` are globally ignored), confirming candidates by an `openapi:`/`swagger:` key. It emits one `route` per operation enriched with method, `operationId`, summary, tags, and a spec back-reference; specs under an `openapi/client/` directory are marked `role:"client"`. Gateway extensions (`x-gateway-config`, `x-gateway-capabilities`) are parsed into props.
558
562
559
563
**gRPC** models Protocol Buffers services the same way HTTP endpoints are modeled, so a gRPC surface answers the same cross-repo and unused-endpoint questions as a REST one. A small dependency-free proto3 scanner (comment-stripped, brace-depth aware — the same class of parser as OpenAPI's) reads each `.proto` and emits, for every `rpc`, a **server-role `route`** whose `Name` is the gRPC wire path `/pkg.Service/Method` (e.g. `/users.v1.UserService/GetUser`) with `method:"POST"`, `framework:"grpc"`, `source:"grpc-proto"`, `type:"grpc"`, and `rpc_service`/`rpc_method`/`streaming` props — the exact path+method a gRPC-web client hits over HTTP, so these flow through the cross-repo linker's normalized path+method matching and the `unused-routes` explainer with no linker special-casing. Each service also emits an `interface` symbol, each RPC a `method` symbol (`has_method`-linked to its service), and each message a `struct`/`enum` symbol, and proto `import`s become `dependency` facts — so proto participates in `traverse`, `find_path`, and `impact_analysis`. **Client-side detection** covers both TypeScript and Go. In the **TypeScript** extractor a repo-wide pre-pass resolves generated stubs — `@protobuf-ts` (`new ServiceType(...)`), connect-es (`typeName`), and **classic grpc-web** (where it derives the service and methods from the `MethodDescriptor`/`rpcCall` `/pkg.Service/Method` path literals) — into a service→method map, then per-file it binds `new XxxServiceClient(...)` variables (including typed constructor-injected fields) and emits a **client-role `route`** (`source:"ts-grpc-client"`) for each `client.method(...)` **call site** — only for methods actually called, so an RPC the frontend never invokes correctly surfaces as unmatched by clients. The **Go** extractor does the same for grpc-go consumers: because a Go call site (`client.GetUser(ctx, req)`) carries no wire path, a repo-wide pre-pass reads the authoritative `/pkg.Service/Method` from the *generated* code — the concrete client's `Invoke`/`NewStream` string literal (grpc-go, unary + streaming) or the `…Procedure` const (connect-go) — and builds a client-interface→method→path index. Per-file, it reuses the Go extractor's own receiver/field/local-variable type resolution (`resolveChain`) so a client is recognized whether it's a **local variable**, an **inline construction**, a **struct field** (`s.users.GetUser(...)` — dependency injection), or a **package-level var**, emitting a **client-role `route`** (`source:"go-grpc-client"`) per call site. Both **grpc-go** and **connect-go** consumers are covered. On the TypeScript side, **connect-es** consumers using `createClient(Service, transport)` / `createPromiseClient(...)` are detected alongside the `new XxxClient(...)` form. Cross-repo gRPC edges are tagged `via:"grpc"`. **Go handler binding:** a post-extraction pass connects each gRPC server route to the Go method that serves it via a `handled_by` edge (route → `pkg.Type.Method`) and a `handler` prop, so `impact_analysis`/`find_path` traverse from the RPC to its implementation (and, through the cross-repo edges, on to its clients). The bridge is the `protoc-gen-go-grpc` forward-compatibility convention — a server impl embeds `Unimplemented<Service>Server`, which the Go extractor already records as an `implements` edge, so the service short name matches the route's `rpc_service` with no new Go parsing; ambiguous or non-embedding impls are left unbound. *Scope:* client detection targets protoc-gen-go-grpc, connect-go, `@protobuf-ts`, connect-es, and classic grpc-web generated stubs; hand-rolled clients that bypass the generated stubs are not recognized (a namespaced commonjs grpc-web constructor, `new proto.pkg.XxxClient(...)`, binds only best-effort).
Copy file name to clipboardExpand all lines: README.md
+3-2Lines changed: 3 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -46,7 +46,7 @@ GitHub Copilot / VS Code (add to `.vscode/mcp.json`):
46
46
47
47
Done. Your agent now has a precise structural map of your code. For configuration options, multi-repo setup, and what to ask next, see [Quick start](#quick-start) below.
48
48
49
-
**Supported languages:** Go · JavaScript · TypeScript · Python · Java · Kotlin · Swift · Ruby · C · C++ · PHP · Vue · Svelte · OpenAPI · gRPC — with framework awareness (Next.js, Nuxt, SvelteKit, FastAPI, Django, Spring, Rails, Laravel, Symfony, SwiftUI, Jetpack Compose, WordPress, …)
Framework- and platform-specific detection for each language is described in **[ARCHITECTURE.md → Supported languages](ARCHITECTURE.md#supported-languages)**.
305
306
306
-
> Python, Ruby, and PHP are parsed with tree-sitter and contribute call and dependency edges to the graph, so `traverse`, `find_path`, and `impact_analysis` reach into them — not just modules and routes.
307
+
> Python, Ruby, PHP, and Rust are parsed with tree-sitter and contribute call and dependency edges to the graph, so `traverse`, `find_path`, and `impact_analysis` reach into them — not just modules and routes.
0 commit comments