Repowise ships language-aware resolvers under
packages/core/src/repowise/core/ingestion/resolvers/. Every language
lands on one rung of a five-rung ladder, and the rung decides which
pipeline stages produce meaningful output. Languages below the parsing
rungs still appear in the wiki and still carry git history.
Coverage matrix
| Tier | Languages | What you get |
|---|---|---|
| Full (13) | Python · TypeScript · JavaScript · Svelte · Vue · Java · Kotlin · Go · Rust · C++ · C# · Scala · Ruby | The whole pipeline: AST symbols, import resolution, a resolved call graph, heritage, docstrings, framework edges, and code-health markers |
| Good (11) | C · Swift · PHP · Dart · Object Pascal · COBOL · GDScript · VB.NET · Elixir · F# · Objective-C | Everything above except the full health suite. Dart and Object Pascal get health markers; C, F# and Objective-C get the complexity-derived ones. Swift, PHP, COBOL, GDScript, VB.NET and Elixir don't yet |
| Partial (2) | Luau / Roblox · Razor / Blazor | Luau: AST symbols and require() resolution, Rojo and .luaurc aware, no health markers yet. Razor: a component symbol per file, call edges from @code blocks and component tags, C# health markers, no import resolution yet |
| Lightweight (6) | Clojure · Haskell · Lean 4 · Erlang · HTML · QML | A real file-to-file import graph, no symbol-level claims |
| Structural (8) | R · Zig · Julia · Elm · OCaml · Crystal · Nim · D | Git history only: blame, hotspots, co-change. No AST parsing |
Tree-sitter parsing stops after the Partial rung. The first three rungs are the 26 languages parsed to a full AST; all five rungs are the 40 on the ladder. Both numbers are worth stating and neither is worth stating alone, so the useful thing to take from this page is the rung your language sits on rather than either count.
SQL and shell sit outside the ladder on purpose, because each has a
coverage shape the rungs cannot describe. SQL is parsed by sqlglot
rather than tree-sitter and gets symbols, wiki pages and health markers
but no call graph, with import edges only inside a dbt project. Shell
gets symbols, source edges and function-level complexity, but
reachability means little for a script invoked by name.
What each tier gets from the pipeline
| Stage | Full | Good | Partial | Lightweight | Structural |
|---|---|---|---|---|---|
| File discovery and git history | yes | yes | yes | yes | yes |
| AST symbol extraction | yes | yes | yes | no | no |
| Import resolution | yes | yes | Luau only | file-level | no |
| Call graph edges | yes | yes | yes | no | no |
| Heritage (extends / implements) | yes | yes | no | no | no |
| Named bindings | yes | yes | no | no | no |
| Code-health markers | yes | Dart, Object Pascal | Razor | no | no |
| Dead code detection | yes | yes, except COBOL | yes | yes | yes |
| Semantic search and wiki pages | yes | yes | yes | yes | yes |
Scala's import resolution is partial: it shares the JVM index with Java and Kotlin and falls back to parsing SBT or Mill build files. Every other Full and Good language resolves imports outright.
COBOL is excluded from dead-code claims. JCL, schedulers and dynamic program calls are external entry paths the repository graph cannot observe, so a COBOL program that nothing in the repo calls is not evidence that nothing calls it.
GDScript has a dedicated import resolver and Godot-specific framework
edges, but no named bindings. Scenes, autoloads and class_name
declarations connect scripts that no import statement ever linked.
Svelte and Vue share the TypeScript resolver: a single-file component's
<script> block and markup expressions are projected to a TypeScript
buffer at byte-identical offsets, so the same queries, config and
code-health dialects apply.
Framework awareness
Repowise recognises common framework conventions and emits edges for them, even when the underlying call goes through a decorator or a runtime registry.
| Language | Frameworks |
|---|---|
| Python | Django (URL patterns, models, signals), FastAPI (route handlers, dependency injection), Flask (route decorators) |
| C# | ASP.NET (controllers, attributes), dependency injection containers |
| Java / Kotlin | Spring Boot (@RestController, @Component, @Autowired), Micronaut |
| JavaScript / TypeScript | Express, NestJS (@Controller, @Module, @Injectable), Remix |
| Go | Gin, Echo, Chi router patterns |
| Rust | Axum, Actix routes |
| Ruby | Rails (Zeitwerk autoloading, route DSL) |
| PHP | Laravel routes, TYPO3 conventions |
| Dart | Flutter route tables, runApp() edges, and widget-tree edges from build() bodies |
| GDScript | Godot scenes, autoloads and class_name registration |
Dead-code awareness
The dead-code detector knows about dynamic dispatch patterns that look unused to a static analyzer but aren't:
- Naming patterns:
*Plugin,*Handler,*Adapter,*Middleware - Dynamic imports:
importlib.import_module(),__import__(), dynamicrequire() - Framework convention files (Flask blueprints, FastAPI routers, Django apps, Rails controllers, Laravel providers, TYPO3 extensions)
- Deprecation annotations:
@Deprecated,[Obsolete],@deprecatedand their siblings feed a finding's confidence - Symbols a docs build or an API dump names, and symbols reached by a framework, a container, or a registration decorator
These are excluded from safe_to_delete findings to keep the
false-positive rate near zero.
Code-health marker rollout
Health markers run off a per-language walker map that is independent of
.scm parsing, so a language can parse perfectly for the graph and
still need this map before markers fire. This table is most of the
reason a language is Full rather than Good.
| Language | Complexity / nesting | Class metrics | Assertion smells | Extract Method | Performance risk |
|---|---|---|---|---|---|
| Python | yes | yes | yes | yes | yes |
| TypeScript / JavaScript | yes | yes | yes | yes | yes |
| Svelte · Vue | yes | yes | yes | yes | yes |
| Java | yes | yes | yes | yes | yes |
| Go | yes | n/a | yes | yes | yes |
| Rust | yes | yes | yes | yes | yes |
| C++ | yes | yes | yes | yes | yes |
| C# | yes | yes | yes | later | yes |
| Kotlin | yes | yes | yes | blocked | yes |
| Scala | yes | yes | yes | later | yes |
| Ruby | yes | yes | yes | later | yes |
| Dart | yes | n/a | yes | later | yes |
| Object Pascal | yes | n/a | later | later | n/a |
| Razor | yes | n/a | n/a | later | yes |
| Shell | yes | n/a | n/a | n/a | n/a |
C, F# and Objective-C reach the complexity walker and carry real cyclomatic complexity and maintainability numbers, without the rest of the marker suite.
Every cell is a deliberate call rather than an oversight: a language
reaches a dialect or it stays silent, and an n/a records a metric the
language cannot carry rather than one nobody got to. Kotlin's Extract
Method is blocked on the grammar, not unscheduled.
Adding a language
Adding new language support is five required steps: a LanguageSpec
module, a manual LanguageTag registration, a tree-sitter .scm query
file, a LanguageConfig entry, and the grammar dependency. Two of those
are one-line registrations, but the LanguageTag edit can't be derived
and has to be made by hand. No changes to parser.py, graph.py, or
any other analysis core file. See
architecture/language-support.md
in the OSS repo for the full recipe, including the optional steps that
buy heritage extraction, named bindings, and call resolution.
Working in a language we don't list? Repowise still indexes the file tree, ownership, and churn; you just won't get import edges or framework-aware analysis. Open a Discussion and we'll prioritise.