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 documentation and still carry
git history.
Coverage matrix
| Tier | Languages | What you get |
|---|---|---|
| Full (15) | Python · TypeScript · JavaScript · Svelte · Vue · Astro · Java · Kotlin · Go · Rust · C++ · C# · Scala · Ruby · Apex (Salesforce) | 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, Object Pascal and PHP get health markers; C, Swift, COBOL, F# and Objective-C get the complexity-derived ones; GDScript, VB.NET and Elixir get none 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 28 languages parsed to a full AST; all five rungs are the 42 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, PHP; complexity only for C, Swift, COBOL, F#, Objective-C | Razor | no | no |
| Dead code detection | yes | yes, with COBOL limited to paragraphs | 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. Apex has no imports: names resolve case-insensitively within each sfdx project, a declared package dependency or a namespace prefix. Every other Full and Good language resolves imports outright.
COBOL files and programs get no dead-code claims. JCL, schedulers and dynamic program calls are external entry paths the repository graph cannot observe. The one claim is a paragraph that nothing names and nothing falls through into, at low confidence and never deletion-ready.
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, Vue and Astro share the TypeScript resolver: a single-file
component's <script> blocks (and an Astro file's frontmatter) and markup
expressions are projected to a TypeScript buffer at byte-identical offsets,
so the same queries, config and code-health dialects apply. Astro
frontmatter imports and exports become symbols and edges, PascalCase markup
tags become component calls, and pages and endpoints under src/pages are
entry points.
COBOL
COBOL is read in fixed reference format, with EXEC SQL, EXEC CICS and
EXEC DLI blocks and COPY statements masked before parsing.
| Source | What the graph gets |
|---|---|
Copybooks (COPY, EXEC SQL INCLUDE) | Resolve to the nearest copybook with that name; system copybooks such as DFHAID stay external. A copybook parses as a data or procedure fragment |
DB2 DCLGEN (.dcl) | A copybook when it holds an EXEC SQL DECLARE ... TABLE and level-numbered data entries |
JCL (.jcl, .prc, .proc) | Each job step links to the program or PROC it runs, which makes that program an entry point |
BMS mapsets (.bms) | Linked from the programs that send or receive their maps |
| CICS | LINK and XCTL link to a literal or constant program name; any other target shows as a cics-dynamic: reference. CSD definitions and TRANSID links make programs entry points |
| Calls | PERFORM binds within its program and the copybooks it copies; CALL, LINK and XCTL bind to a unique PROGRAM-ID |
Code health measures each paragraph and section as a function: IF,
WHEN arms, PERFORM UNTIL/VARYING loops and AND/OR add to its
complexity. The advisory cobol_unstructured_flow marker lists paragraphs
with ALTER, GO TO ... DEPENDING ON or a GO TO that does not jump
forward to its own ...EXIT, and never deducts from a score. Dynamic
CALL targets and free-format source produce no edges.
Salesforce
Apex (.cls, .trigger, .apex) is a Full language. Lightning Web
Components, Aura, Visualforce, flows, Lightning pages and quick actions are
entry points, and object and field metadata link to the Apex that names
them. Flows and SObject metadata become symbols; Aura and Visualforce
markup carries none. References are followed when the name is literal:
runtime-built names, and objects with no metadata file in the repository,
get no edge. Apex dead-code findings are review-only, since the platform
also reaches classes by name, and a third-party library in a static
resource counts as vendored. Apex code health carries advisory markers for SOQL injection and related
shapes (see code health).
Grammars on offline machines
The COBOL and Apex grammars download on first use, and only when the repo
holds that language. To index offline, stage them on a machine with
network access, copy the directory over and point REPOWISE_GRAMMAR_DIR at it.
repowise grammars fetch ./grammars
export REPOWISE_GRAMMAR_DIR=./grammarsgrammars fetch DIRECTORY downloads every runtime grammar into
DIRECTORY and exits non-zero if any fails. repowise doctor reports
whether each grammar is cached, and repowise init warns once, with a file
count, when COBOL indexes without symbols because its grammar did not load.
Config and build files
| Source | What the graph gets |
|---|---|
| Ansible YAML | Edges for import_playbook, import_tasks, include_tasks, import_role, include_role, roles: and role meta dependencies. Plain YAML config is untouched |
tsconfig.json references | Each file resolves against the project that owns it |
| Gradle build scripts | Declared dependencies feed the package inventory |
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, minimal APIs), EF Core, gRPC-dotnet, dependency injection containers, MassTransit message contracts in C# and VB.NET (publishers and consumers linked across workspace repos); MassTransit, NServiceBus, Azure Functions and WCF handlers are dead-code roots |
| Java / Kotlin | Spring Boot (a main is a declared entry point, ranked above filename guesses; @RestController, @Component, @Autowired, and every @GetMapping-style route linked to its handler method), Spring Data and MyBatis calls flagged as database I/O, Jakarta / JPA, Quarkus, Micronaut |
| JavaScript / TypeScript | Next.js App Router, Express (route mounts keyed on the mount path), NestJS (@Controller, @Module, @Injectable), Hono, Fastify, Remix, SvelteKit, Astro, tRPC, Angular |
| Go | Gin, Echo, Chi router patterns |
| Rust | Axum, Actix routes |
| Ruby | Rails (Zeitwerk autoloading, route DSL); a gem's lib entry file and what it requires count as public API |
| PHP | Laravel routes, TYPO3 conventions |
| Dart | Flutter route tables, runApp() edges, and widget-tree edges from build() bodies; calls on a dotted receiver and prefixed named constructors bind, and a closure parameter takes its type from the Iterable call it is passed to |
| Apex | Lightning Web Components, Aura, Visualforce, flows, Lightning pages and quick actions as entry points, object and field metadata, Apex REST resources |
| 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 · Astro | yes | yes | yes | yes | yes |
| Java | yes | yes | yes | yes | yes |
| Go | yes | n/a | yes | yes | yes |
| Rust · C++ · C# | yes | yes | yes | yes | yes |
| Scala | yes | yes | yes | yes | yes |
| Ruby | yes | yes | yes | later | yes |
| Apex | yes | yes | yes | yes | yes |
| COBOL | yes | no | no | no | no |
| Kotlin | yes | yes | yes | blocked | yes |
| Dart | yes | yes | yes | later | yes |
| Object Pascal | yes | n/a | yes | later | yes |
| Razor | yes | n/a | n/a | later | yes |
| PHP | yes | yes | yes | yes | later |
| C | yes | later | later | yes | later |
| Swift | yes | yes | later | later | later |
| F# · Objective-C | yes | yes | later | later | later |
| Shell | yes | n/a | n/a | n/a | n/a |
COBOL measures paragraphs and sections as functions. Apex also gets four advisory markers: SOQL injection, a class with queries and no sharing keyword, hardcoded record Ids and SeeAllData=true tests. Scala has a dataflow def/use dialect, which is what Extract Method needs.
C, F# and Objective-C reach the complexity walker and carry real cyclomatic complexity and maintainability numbers, without the rest of the marker suite. Swift has a complexity map: function rows (CCN, nesting, cognitive complexity, parameters) and class rows (methods, fields, LCOM4, TCC).
Code Health serves findings on every language. Where a layer measured below the precision bar on a language, its findings carry a Preview label with the latest hand-judged measurement, in the web UI, REST, MCP and the CLI.
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.