Status: proposed (scoping only — not implemented). Authored 2026-06-04 from the
cross-language impact-coverage campaign (feat/cross-language-impact-coverage).
The impact graph is built from code the engine parses. Template markup is not parsed, so any code-behind, component, view-model, or DTO that is referenced only from markup looks like it has no in-repo dependent. On convention-heavy frameworks this is the dominant residual gap after framework-entry exclusions:
| Framework | App | FAIR coverage (entries excluded) | Residual cause |
|---|---|---|---|
| ASP.NET | eShopOnWeb | 77.2% (115/149) | Razor .cshtml + Blazor .razor reference .cs we don't parse |
| Spring | petclinic | 65.2% | mostly Spring Data proxies + JPA, not templates (Thymeleaf links are weak) |
| Django | django-realworld | 74.1% | signals / DRF / string-config, not templates |
This feature is primarily an ASP.NET (Razor + Blazor) win. Thymeleaf and Django templates link to code only weakly (template→template fragments + fuzzy model-attribute strings), and those frameworks' real gaps are elsewhere — so they are explicitly lower priority here.
ViewModels/* ← Razor @model XBlazorShared/Models/* (DTOs) ← Blazor @bind / component paramsBlazorAdmin/* C# components ← Blazor <Component/> tagsBasketComponent ViewComponent ← <vc:basket> / Component.InvokeAsyncMappingProfile, Swagger CustomSchemaFilters/ImageValidators, ExceptionMiddleware,
health checks, Constants (static-member reads), Buyer entity.Honest ceiling: ASP.NET ~77% → ~90%, not 95%. The last ~10% is reflection/proxy (AutoMapper, Swagger, DI/middleware registration) + C# static-const reads — a separate feature (reflection modeling + extending the static-member pass to C#).
| Pri | Format | Markup construct | Edge to emit | Resolves to |
|---|---|---|---|---|
| P1 | Razor .cshtml/.razor |
@model Foo / @inherits X<Foo> |
references |
the model/VM class Foo |
| P1 | Razor/Blazor | @inject IBar bar |
references |
the service type IBar |
| P2 | Blazor .razor |
<MyComponent .../> (PascalCase element) |
references |
component class (.razor or .cs : ComponentBase) |
| P2 | Blazor .razor |
@typeof(MainLayout), @inherits LayoutBase |
references |
the type |
| P3 | Razor .cshtml |
<partial name="_X"/>, <vc:basket>, Component.InvokeAsync("X") |
references |
the partial view / XViewComponent |
| P3 | Razor .cshtml |
asp-page="./Register", asp-controller/asp-action |
references |
the page / controller action |
| P4 (defer) | Thymeleaf .html |
th:replace="~{frag :: x}" |
references |
template fragment (template→template only) |
| P4 (defer) | Django .html |
{% extends %} / {% include %} / {% url 'n' %} |
references |
template / named route |
asp-for="Prop", th:field="*{prop}" (property-string bindings) are the data-flow
frontier — out of scope (would need model-type inference; low value, high noise).
The engine already has non-tree-sitter extractors (svelte-extractor.ts,
vue-extractor.ts, liquid-extractor.ts): a class taking (filePath, source),
returning { nodes, references }, wired in two places. Mirror exactly:
src/extraction/grammars.ts — map extensions to a synthetic language:
.cshtml/.razor → 'razor', (later) .html under templates/ → 'thymeleaf'.
(Django .html is ambiguous with plain HTML — gate on a templates/ path or a
{% %}/{{ }} content sniff, like the framework resolvers do.)src/extraction/tree-sitter.ts — dispatch by extension to a new
RazorExtractor (and ThymeleafExtractor), exactly as SvelteExtractor is
dispatched (~line 4025).src/extraction/razor-extractor.ts (new) — regex/line scan (markup is
highly stylized; no grammar needed, same as Liquid/Svelte template scanning):
component node for the file (so .razor components are linkable as
<X/> targets and the file is a graph citizen).references per the P1–P3 patterns above, fromNodeId = the file/component
node, referenceKind: 'references', language: 'razor'.Foo.razor + Foo.razor.cs (partial class) — emit a
references (or rely on same-basename) so the markup's refs also credit the
code-behind. (eShop's Blazor components are plain .cs : ComponentBase, named
<ToastComponent/> → resolves by class name; the .razor.cs partial case is
the other shape.)Resolution: no new resolver needed. The emitted refs are ordinary references
to a class/component by name; the existing name-matcher resolves them (@model
RegisterModel → class RegisterModel; <ToastComponent/> → class ToastComponent).
Apply the same cross-family language gate already in place — a razor ref must
resolve to a csharp symbol, so add razor to the web/dotnet family or treat
razor↔csharp as same-family (otherwise the gate from commit 082353e drops it).
This is the one resolver-side change and must be done or every edge is gated away.
component node per template file (real new symbol — like .svelte/.vue).
Node count grows by the template-file count only; no per-tag node explosion
(component tags become references edges, not nodes).references (counted by impact / affected / getFileDependents,
not by callers/callees — matches how route/component edges already behave).@model + @inject for .cshtml AND
.razor. Covers the 5 ViewModels + injected services. + the resolver family-gate fix.<PascalComponent/> tags + @typeof/@inherits + code-behind link.
Covers the 6 Blazor .cs components + the 7 DTOs (via component params/@bind).<partial> / <vc:> / Component.InvokeAsync / asp-page.[A-Z]-initial tags are Blazor components
(HTML is lowercase) — safe discriminator. Skip known framework components
(<Router>, <Found>, <LayoutView>, <RouteView>, <CascadingValue>) via a
builtin set, or just let them fail to resolve (no false edge — they're not in-repo)._Imports.razor @using: namespace imports, not code refs — ignore (or emit
imports to the namespace, low value).<Grid TItem="CatalogItem">: capture the type-arg as a
references to CatalogItem (bonus DTO coverage).@{ ... } C# blocks: contain real C# (calls, new) — P-future; regex
scanning the C# inside markup is noisy. Defer (the directives above are the wins)..razor is NOT .cs: must add to grammars.ts + the indexer's include globs
(verify .razor/.cshtml aren't in a default-exclude).RazorExtractor; unit tests in __tests__/extraction.test.ts (a .cshtml
with @model X covers X; a .razor with <ToastComponent/> covers it; an HTML
<div> does NOT create an edge)./tmp/faircov.cjs): target
77% → ~90%; node count stable (only +template-file component nodes); residual
zeros are the reflection/value-read set only.@model/@inject scan + family-gate fix + tests).This feature does NOT close: reflection/proxy registration (Spring Data repository
proxies, AutoMapper profiles, Swagger filters, DI container / middleware), property-
string data bindings (asp-for/th:field), or C# static-const value reads
(Constants.X). Convention apps reaching literal 95% additionally need a reflection/
DI-registration modeling pass and extending the static-member pass to C#/TS —
tracked separately. Markup parsing is the single biggest, most self-contained step.