Status: P1+P2+@code IMPLEMENTED (commits 59b8de2 directives/tags, 90c5f39 @code
delegation) on feat/cross-language-impact-coverage. Razor/Blazor markup is parsed
(src/extraction/razor-extractor.ts). Remaining: @using namespace disambiguation
for DTO-vs-entity name collisions (the residual ASP.NET gap), and Thymeleaf/Django
(P4, deferred — weak code links). Authored 2026-06-04.
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.
- ~20 markup-coverable by this feature:
- 5 MVC
ViewModels/*← Razor@model X - 7
BlazorShared/Models/*(DTOs) ← Blazor@bind/ component params - 6
BlazorAdmin/*C# components ← Blazor<Component/>tags - 1
BasketComponentViewComponent ←<vc:basket>/Component.InvokeAsync - 1 Razor page helper
- 5 MVC
- ~13 NOT covered (separate frontier — reflection/proxy + value-reads): AutoMapper
MappingProfile, SwaggerCustomSchemaFilters/ImageValidators,ExceptionMiddleware, health checks,Constants(static-member reads),Buyerentity.
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).htmlundertemplates/→'thymeleaf'. (Django.htmlis ambiguous with plain HTML — gate on atemplates/path or a{% %}/{{ }}content sniff, like the framework resolvers do.)src/extraction/tree-sitter.ts— dispatch by extension to a newRazorExtractor(andThymeleafExtractor), exactly asSvelteExtractoris 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):- Emit ONE
componentnode for the file (so.razorcomponents are linkable as<X/>targets and the file is a graph citizen). - Emit
referencesper the P1–P3 patterns above,fromNodeId= the file/component node,referenceKind: 'references',language: 'razor'. - Code-behind link: a
Foo.razor+Foo.razor.cs(partial class) — emit areferences(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.cspartial case is the other shape.)
- Emit ONE
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.
- +1
componentnode 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 becomereferencesedges, not nodes). - All edges are
references(counted by impact /affected/getFileDependents, not bycallers/callees— matches howroute/componentedges already behave). - Idempotent re-index; node count stable across re-runs.
- P1 (highest value/effort ratio): Razor
@model+@injectfor.cshtmlAND.razor. Covers the 5 ViewModels + injected services. + the resolver family-gate fix. - P2: Blazor
<PascalComponent/>tags +@typeof/@inherits+ code-behind link. Covers the 6 Blazor.cscomponents + the 7 DTOs (via component params/@bind). - P3: Razor
<partial>/<vc:>/Component.InvokeAsync/asp-page. - P4 (defer / probably skip): Thymeleaf + Django templates — weak code links, low coverage payoff; revisit only if a Thymeleaf/Django app is a priority.
- PascalCase tag vs HTML element: only
[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 emitimportsto the namespace, low value).- Generic components
<Grid TItem="CatalogItem">: capture the type-arg as areferencestoCatalogItem(bonus DTO coverage). - Name collisions: component/model names are usually unique; rely on the name-matcher's existing proximity scoring. Same-named class in another language is blocked by the family gate.
- Razor
@{ ... }C# blocks: contain real C# (calls,new) — P-future; regex scanning the C# inside markup is noisy. Defer (the directives above are the wins). .razoris NOT.cs: must add togrammars.ts+ the indexer's include globs (verify.razor/.cshtmlaren't in a default-exclude).
- Build
RazorExtractor; unit tests in__tests__/extraction.test.ts(a.cshtmlwith@model XcoversX; a.razorwith<ToastComponent/>covers it; an HTML<div>does NOT create an edge). - Re-measure eShopOnWeb FAIR coverage before/after (
/tmp/faircov.cjs): target 77% → ~90%; node count stable (only +template-file component nodes); residual zeros are the reflection/value-read set only. - No regression on a non-.NET control (gin/requests) and on the Razor-free C# repos (cs-mediatr/cs-polly unchanged).
- Record in this doc + the coverage handoff.
- P1: ~0.5 day (extractor skeleton +
@model/@injectscan + family-gate fix + tests). - P2: ~1 day (Blazor tags + code-behind + generic type-args).
- P3: ~0.5 day. P4 (Thymeleaf/Django): ~1–2 days, low ROI — defer.
- Total for the ASP.NET win (P1+P2+P3): ~2 days → ASP.NET ~90%.
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.