Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
6 changes: 6 additions & 0 deletions .github/instructions/Razor.instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@ their original sub-tree layout
`solution.GetDocumentIdsWithFilePath(filePath)` then `solution.GetAdditionalDocument(documentId)`.
- **Remote services**: Place the public stub method (calling `RunServiceAsync`) directly
above its private implementation method.
- **Formatting options across OOP**: Cohost endpoints must read
`CSharpSyntaxFormattingOptions` from the local Roslyn solution services and include them in
`RazorFormattingOptions` sent to remote formatting consumers. Remote `IClientSettingsManager`
state does not contain the user's C# formatting preferences, so do not reconstruct them OOP.
Resolve the options at the public handler boundary and keep downstream product parameters
non-null.
- **Runtime-declared attribute lists**: When the runtime declares a set the compiler must read
(e.g. `[EventHandler]`, `[AcceptsAssetPath]`), it applies the attributes to a public type with
a well-known name (`EventHandlers`, `AssetPathAttributes`). A `TagHelperProducer` under
Expand Down
14 changes: 10 additions & 4 deletions .github/memory/testing/razor.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,24 @@ Layer-specific test guidance for Razor tooling/compiler tests under `src/Razor`.
- Test end-user scenarios, not implementation details.
- Verify/helper methods go at the bottom of test files; new test methods go above
them.
- New tooling tests go in
`src\Razor\src\Razor\test\Microsoft.VisualStudioCode.RazorExtension.UnitTests`
(Cohosting architecture).
- Shared cohost endpoint tests go in
`src\Razor\src\Razor\test\Microsoft.CodeAnalysis.Razor.CohostingShared.UnitTests`
and must be listed in its `.projitems` file; both the Visual Studio and VS Code
unit-test projects import that suite.
- Integration tests using `AdditionalSyntaxTrees` for tag helper discovery must
set `UseTwoPhaseCompilation => true` (see `ComponentDiscoveryIntegrationTest`).
Under two-phase compilation the `AdditionalSyntaxTrees` are compiled into a
temp assembly and added as a *reference*, so discovery sees those types as
coming from a referenced assembly (not source).
- Regenerate baseline-backed compiler tests with a targeted test filter and
`/p:GenerateBaselines=true` on one CoreCLR target framework, then rerun the
tests normally. Two-phase tests can produce `.decl.codegen.cs` and
`.decl.mappings.txt` in addition to implementation and component
`.builder.txt` baselines.
- After Razor compiler tests or their `TestFiles` change, run the complete
affected test project. A successful build does not validate embedded
baseline resources.

## Baseline (codegen) tests

- `ComponentCodeGenerationTestBase` and similar baseline tests assert generated
Expand Down
41 changes: 37 additions & 4 deletions .github/skills/merge-into-branch/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,11 +98,42 @@ dotnet run --file eng/generate-compiler-code.cs

Review and stage any generated changes before finishing the merge.

#### 3d. Other conflicts
#### 3d. Razor compiler baselines and tests

If the merge changes files under `src/Razor/src/Compiler`, identify and run every affected test project before presenting the merge as ready.

If baseline-backed Razor compiler tests or their `TestFiles` change:

1. Identify the affected fully qualified test names from the changed test methods and baseline directories.
2. Regenerate those baselines on one CoreCLR target framework to avoid concurrent writers:

```bash
dotnet test <test-project> --framework <coreclr-tfm> --filter "<affected-tests>" -p:GenerateBaselines=true
```

Always use a targeted filter when generating baselines because the unfiltered suite includes `GenerateBaselinesMustBeFalse`, which intentionally fails while generation is enabled.
3. Review all generated changes. Two-phase Razor tests may add or update `.decl.codegen.cs` and `.decl.mappings.txt` alongside `.codegen.cs`, `.mappings.txt`, `.ir.txt`, `.diagnostics.txt`, and component `.builder.txt` files.
4. Rerun the affected tests normally, without `GenerateBaselines=true`.
5. Run the complete affected test project normally so newly added or auto-merged tests are not missed:

```bash
dotnet test <test-project>
```

Common Razor compiler test projects include:

- `src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/Microsoft.AspNetCore.Razor.Language.UnitTests.csproj`
- `src/Razor/src/Compiler/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.UnitTests.csproj`
- `src/Razor/src/Compiler/Microsoft.CodeAnalysis.Razor/test/Microsoft.CodeAnalysis.Razor.UnitTests.csproj`
- `src/Razor/src/Compiler/test/Microsoft.NET.Sdk.Razor.SourceGenerators.UnitTests/Microsoft.NET.Sdk.Razor.SourceGenerators.UnitTests.csproj`

An analyzer build is not a substitute for these tests. Do not sign off on the merge until baseline generation has been reviewed and all affected test projects pass.

#### 3e. Other conflicts

Resolve any remaining conflicts carefully using the repo's existing code patterns. Avoid unrelated cleanup or opportunistic edits during the merge.

#### 3e. Validate the merge after conflict resolution
#### 3f. Validate the merge after conflict resolution

Before presenting the summary or creating the merge commit, run a validating build with analyzers enabled to make sure the merge did not introduce new issues:

Expand All @@ -128,9 +159,11 @@ Once all conflicts are resolved and staged, present a concise but specific summa
- `.xlf` → accepted ours and updated XLFs
- `.resx` → manually merged to preserve all strings and updated XLFs
- `src/Compilers` → reran compiler code generation
- Razor compiler tests → regenerated and reviewed affected baselines, reran targeted tests, and ran the complete affected test projects
- other files → briefly describe the manual resolution
4. The result of the post-merge validation build/analyzer run.
5. A diff summary using commands such as:
4. The exact post-merge test commands and results, including affected test-project totals.
5. The result of the post-merge validation build/analyzer run.
6. A diff summary using commands such as:

```bash
git status --short
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,13 @@ public TextDocumentIdentifier GetTextDocumentIdentifier(LSP.CallHierarchyPrepare
=> request.TextDocument;

public async Task<LSP.CallHierarchyItem[]?> HandleRequestAsync(LSP.CallHierarchyPrepareParams request, RequestContext context, CancellationToken cancellationToken)
=> await PrepareCallHierarchyAsync(context.GetRequiredDocument(), ProtocolConversions.PositionToLinePosition(request.Position), cancellationToken).ConfigureAwait(false);
{
var document = context.GetRequiredDocument();
return await PrepareCallHierarchyAsync(document, ProtocolConversions.PositionToLinePosition(request.Position), document.Id, cancellationToken)
.ConfigureAwait(false);
}

internal static async Task<LSP.CallHierarchyItem[]?> PrepareCallHierarchyAsync(Document document, LinePosition linePosition, CancellationToken cancellationToken)
internal static async Task<LSP.CallHierarchyItem[]?> PrepareCallHierarchyAsync(Document document, LinePosition linePosition, DocumentId preferredDocumentId, CancellationToken cancellationToken)
{
var solution = document.Project.Solution;
var position = await document.GetPositionFromLinePositionAsync(linePosition, cancellationToken).ConfigureAwait(false);
Expand All @@ -47,7 +51,7 @@ public TextDocumentIdentifier GetTextDocumentIdentifier(LSP.CallHierarchyPrepare
if (itemDescriptor == null)
return null;

var item = await CallHierarchyHelpers.CreateItemAsync(itemDescriptor, solution, preferredDocumentId: document.Id, cancellationToken).ConfigureAwait(false);
var item = await CallHierarchyHelpers.CreateItemAsync(itemDescriptor, solution, preferredDocumentId, cancellationToken).ConfigureAwait(false);
return item == null ? null : [item];
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.CodeLens;

[ExportCSharpVisualBasicStatelessLspService(typeof(CodeLensHandler)), Shared]
[Method(LSP.Methods.TextDocumentCodeLensName)]
internal sealed class CodeLensHandler : ILspServiceDocumentRequestHandler<LSP.CodeLensParams, LSP.CodeLens[]?>
internal sealed class CodeLensHandler : ILspServiceDocumentRequestHandler<LSP.CodeLensParams, LSP.CodeLens[]>
{
public const string RunTestsCommandIdentifier = "dotnet.test.run";

Expand All @@ -42,10 +42,10 @@ public CodeLensHandler(IGlobalOptionService globalOptionService)
public LSP.TextDocumentIdentifier GetTextDocumentIdentifier(LSP.CodeLensParams request)
=> request.TextDocument;

public Task<LSP.CodeLens[]?> HandleRequestAsync(LSP.CodeLensParams request, RequestContext context, CancellationToken cancellationToken)
public Task<LSP.CodeLens[]> HandleRequestAsync(LSP.CodeLensParams request, RequestContext context, CancellationToken cancellationToken)
=> GetCodeLensAsync(request.TextDocument, context.GetRequiredDocument(), _globalOptionService, cancellationToken);

internal static async Task<LSP.CodeLens[]?> GetCodeLensAsync(LSP.TextDocumentIdentifier textDocumentIdentifier, Document document, IGlobalOptionService globalOptionService, CancellationToken cancellationToken)
internal static async Task<LSP.CodeLens[]> GetCodeLensAsync(LSP.TextDocumentIdentifier textDocumentIdentifier, Document document, IGlobalOptionService globalOptionService, CancellationToken cancellationToken)
{
var referencesCodeLensEnabled = globalOptionService.GetOption(LspOptionsStorage.LspEnableReferencesCodeLens, document.Project.Language);
var testsCodeLensEnabled = globalOptionService.GetOption(LspOptionsStorage.LspEnableTestsCodeLens, document.Project.Language);
Expand Down Expand Up @@ -200,4 +200,3 @@ private static void AddTestCodeLens(
}
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -46,24 +46,19 @@ public DocumentHighlightsHandler(IHighlightingService highlightingService, IGlob
if (document == null)
return null;

var position = ProtocolConversions.PositionToLinePosition(request.Position);
return await GetHighlightsAsync(_globalOptions, _highlightingService, document, position, cancellationToken).ConfigureAwait(false);
}

internal static async Task<DocumentHighlight[]?> GetHighlightsAsync(IGlobalOptionService globalOptions, IHighlightingService highlightingService, Document document, LinePosition linePosition, CancellationToken cancellationToken)
{
var linePosition = ProtocolConversions.PositionToLinePosition(request.Position);
var text = await document.GetValueTextAsync(cancellationToken).ConfigureAwait(false);
var position = await document.GetPositionFromLinePositionAsync(linePosition, cancellationToken).ConfigureAwait(false);

// First check if this is a keyword that needs highlighting.
var keywordHighlights = await GetKeywordHighlightsAsync(highlightingService, document, text, position, cancellationToken).ConfigureAwait(false);
var keywordHighlights = await GetKeywordHighlightsAsync(document, text, position, cancellationToken).ConfigureAwait(false);
if (keywordHighlights.Any())
{
return [.. keywordHighlights];
}

// Not a keyword, check if it is a reference that needs highlighting.
var referenceHighlights = await GetReferenceHighlightsAsync(globalOptions, document, text, position, cancellationToken).ConfigureAwait(false);
var referenceHighlights = await GetReferenceHighlightsAsync(document, text, position, cancellationToken).ConfigureAwait(false);
if (referenceHighlights.Any())
{
return [.. referenceHighlights];
Expand All @@ -73,12 +68,12 @@ public DocumentHighlightsHandler(IHighlightingService highlightingService, IGlob
return [];
}

private static async Task<ImmutableArray<DocumentHighlight>> GetKeywordHighlightsAsync(IHighlightingService highlightingService, Document document, SourceText text, int position, CancellationToken cancellationToken)
private async Task<ImmutableArray<DocumentHighlight>> GetKeywordHighlightsAsync(Document document, SourceText text, int position, CancellationToken cancellationToken)
{
var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false);

var keywordSpans = new List<TextSpan>();
highlightingService.AddHighlights(root, position, keywordSpans, cancellationToken);
_highlightingService.AddHighlights(root, position, keywordSpans, cancellationToken);

return keywordSpans.SelectAsArray(highlight => new DocumentHighlight
{
Expand All @@ -87,10 +82,10 @@ private static async Task<ImmutableArray<DocumentHighlight>> GetKeywordHighlight
});
}

private static async Task<ImmutableArray<DocumentHighlight>> GetReferenceHighlightsAsync(IGlobalOptionService globalOptions, Document document, SourceText text, int position, CancellationToken cancellationToken)
private async Task<ImmutableArray<DocumentHighlight>> GetReferenceHighlightsAsync(Document document, SourceText text, int position, CancellationToken cancellationToken)
{
var documentHighlightService = document.GetRequiredLanguageService<IDocumentHighlightsService>();
var options = globalOptions.GetHighlightingOptions(document.Project.Language);
var options = _globalOptions.GetHighlightingOptions(document.Project.Language);
var highlights = await documentHighlightService.GetDocumentHighlightsAsync(
document,
position,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Licensed to the .NET Foundation under one or more agreements.
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

#nullable disable
Expand Down Expand Up @@ -44,11 +44,11 @@ public class MyService<TModel>

// Assert
AssertDocumentNodeMatchesBaseline(compiled.CodeDocument.GetDocumentNode());
AssertCSharpDocumentMatchesBaseline(compiled.CodeDocument.GetCSharpDocument());
AssertCSharpDocumentMatchesBaseline(compiled.CodeDocument.GetImplCSharpDocument());
AssertSourceMappingsMatchBaseline(compiled.CodeDocument);

// We expect this test to generate a bunch of errors.
Assert.True(compiled.CodeDocument.GetCSharpDocument().Diagnostics.Length > 0);
Assert.True(compiled.CodeDocument.GetImplCSharpDocument().Diagnostics.Length > 0);
}

[Fact]
Expand All @@ -69,7 +69,7 @@ public class MyApp

// Assert
AssertDocumentNodeMatchesBaseline(compiled.CodeDocument.GetDocumentNode());
AssertCSharpDocumentMatchesBaseline(compiled.CodeDocument.GetCSharpDocument());
AssertCSharpDocumentMatchesBaseline(compiled.CodeDocument.GetImplCSharpDocument());
AssertLinePragmas(compiled.CodeDocument);
AssertSourceMappingsMatchBaseline(compiled.CodeDocument);
}
Expand Down Expand Up @@ -101,7 +101,7 @@ public class MyApp

// Assert
AssertDocumentNodeMatchesBaseline(compiled.CodeDocument.GetDocumentNode());
AssertCSharpDocumentMatchesBaseline(compiled.CodeDocument.GetCSharpDocument());
AssertCSharpDocumentMatchesBaseline(compiled.CodeDocument.GetImplCSharpDocument());
AssertLinePragmas(compiled.CodeDocument);
AssertSourceMappingsMatchBaseline(compiled.CodeDocument);
}
Expand Down Expand Up @@ -134,7 +134,7 @@ public class MyService<TModel>

// Assert
AssertDocumentNodeMatchesBaseline(compiled.CodeDocument.GetDocumentNode());
AssertCSharpDocumentMatchesBaseline(compiled.CodeDocument.GetCSharpDocument());
AssertCSharpDocumentMatchesBaseline(compiled.CodeDocument.GetImplCSharpDocument());
AssertLinePragmas(compiled.CodeDocument);
AssertSourceMappingsMatchBaseline(compiled.CodeDocument);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ protected override void ConfigureProjectEngine(RazorProjectEngineBuilder builder

protected override void ConfigureCodeDocumentProcessor(RazorCodeDocumentProcessor processor)
{
processor.ExecutePhasesThrough<IRazorDocumentClassifierPhase>();
processor.ExecutePhasesThrough<DefaultRazorTagHelperRewritePhase>();
}

[Fact]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ protected override void ConfigureProjectEngine(RazorProjectEngineBuilder builder

protected override void ConfigureCodeDocumentProcessor(RazorCodeDocumentProcessor processor)
{
processor.ExecutePhasesThrough<IRazorDocumentClassifierPhase>();
processor.ExecutePhasesThrough<DefaultRazorTagHelperRewritePhase>();

// We also expect the default tag helper pass to run first.
processor.ExecutePass<DefaultTagHelperOptimizationPass>();
Expand Down
Loading