Skip to content

Index Razor views and Blazor components - #117

Open
grinidx wants to merge 1 commit into
sourcegraph:mainfrom
dbhq-uk:razor-source-generated-documents
Open

Index Razor views and Blazor components#117
grinidx wants to merge 1 commit into
sourcegraph:mainfrom
dbhq-uk:razor-source-generated-documents

Conversation

@grinidx

@grinidx grinidx commented Jul 30, 2026

Copy link
Copy Markdown

Addresses #61.

The problem

ScipProjectIndexer iterates project.Documents, which is the set of files the compiler reads from disk. Razor views (.cshtml) and Blazor components (.razor) never reach the compiler that way. The Razor source generator turns them into C# and hands them straight to the compilation, so they live behind Project.GetSourceGeneratedDocumentsAsync and the indexer has never seen them.

Measured on freshly scaffolded apps against main at 4788446:

dotnet new webapp dotnet new blazor
Razor files on disk 7 .cshtml 11 .razor
Documents in index.scip 0 0

On #61 the reply was "Razor templates don't seem to be supported, Blazor should be supported. If you have an example of it not working, please provide a reproducer." The second column is that reproducer: a stock dotnet new blazor yields zero .razor documents today. Steps to reproduce are in the linked write-up.

That thread also said "We'll be happy to review a PR adding this feature", which is what this is.

Why the obvious fix is not enough

Adding the source-generated documents to the existing loop produces an index full of paths like

obj/Debug/net10.0/Microsoft.CodeAnalysis.Razor.Compiler/Microsoft.NET.Sdk.Razor.SourceGenerators.RazorSourceGenerator/Pages_Index_cshtml.g.cs

which do not exist on disk unless EmitCompilerGeneratedFiles is set, are excluded the moment anyone passes --exclude '**/obj/**', and mix generated boilerplate in with the developer's code.

What this does

  1. IndexSourceGeneratedDocuments enumerates the generated documents per project and, for each, uses SyntaxTree.GetLineMappings to find which real files its #line directives point at. One SCIP Document is created per original file, and --include / --exclude are matched against that path.
  2. ScipDocumentIndexer takes an optional originalFilePath and records only the occurrences whose GetMappedLineSpan().Path matches it, so generated boilerplate is dropped and the developer's code keeps the line and column numbers it has in the .cshtml. LocationToRange already called GetMappedLineSpan, so the ranges were always correct once the document was.
  3. RemoveDuplicates collapses identical occurrences, which is needed because _ViewImports.cshtml and _Imports.razor are folded into the generated file of every view that inherits them.

Result on the same two apps: 6 .cshtml documents with 17 occurrences, and 11 .razor documents with 153 occurrences. The seventh .cshtml, _ValidationScriptsPartial.cshtml, contains no C#, so there is nothing in it to index. No measurable change in indexing time.

Tests

A new snapshot fixture, snapshots/input/razor, covering a Razor Page with @model, @functions and inline expressions, a _ViewImports.cshtml, a Blazor component with @code, and an _Imports.razor, with expected output for net8.0, net9.0 and net10.0.

SnapshotTests previously only compared expected-output files ending in .cs, so Razor snapshots would have been written and never asserted on. IsSnapshotFile widens that to .cshtml and .razor.

All existing snapshots are byte-identical after the change on all three target frameworks, and dotnet format --verify-no-changes is clean.

Provenance and licensing

This came out of vela, a local code index for .NET that reached the same conclusion independently and has been running the source-generated-document approach against a 375,608-line solution with 307 Razor views. The full working notes, including the measurements above and how to reproduce them, are at docs/upstream/scip-dotnet-razor.md.

The approach was reimplemented for this codebase rather than copied: vela is MIT, scip-dotnet is Apache 2.0, and this contribution is offered under Apache 2.0 on the inbound-equals-outbound terms in section 5 of that licence.

What has not been checked

Only exercised on Linux. The CI matrix also covers Windows and macOS, where the path comparison in VisitOccurrence uses StringComparison.Ordinal against a path Roslyn produced from a #line directive Roslyn also produced. Both sides originate from the same string, so they should match, but that is reasoning rather than a measurement.

No Razor Class Library and no MVC Views/ layout were tested. Razor Pages and Blazor were.

Razor views (.cshtml) and Blazor components (.razor) never reach the
compiler as files, the Razor source generator feeds them to it, so
project.Documents does not see them and they are missing from the index.

Enumerate the source generated documents as well, and follow the #line
directives that the generator emits so that each occurrence is reported
against the .cshtml or .razor file it was written in rather than against
the generated C# under obj/, which usually does not exist on disk.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant