|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
#nullable enable
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Test.Utilities;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Test.Utilities;
using Xunit;
namespace Microsoft.NET.Sdk.Razor.SourceGenerators
{
public sealed class RazorSourceGeneratorTests : RazorSourceGeneratorTestsBase
{
[Fact]
public async Task SourceGenerator_RazorFiles_Works()
{
// Arrange
var project = CreateTestProject(new()
{
["Pages/Index.razor"] = "<h1>Hello world</h1>",
});
var compilation = await project.GetCompilationAsync();
var driver = await GetDriverAsync(project);
var result = RunGenerator(compilation!, ref driver)
.VerifyPageOutput(
@"#pragma checksum ""Pages/Index.razor"" ""{ff1816ec-aa5e-4d10-87f7-6f4963833460}"" ""6b5db227a6aa2228c777b0771108b184b1fc5df3""
// <auto-generated/>
#pragma warning disable 1591
namespace MyApp.Pages
{
#line default
using global::System;
using global::System.Collections.Generic;
using global::System.Linq;
using global::System.Threading.Tasks;
using global::Microsoft.AspNetCore.Components;
#line default
#line hidden
#nullable restore
public partial class Index : global::Microsoft.AspNetCore.Components.ComponentBase
#nullable disable
{
#pragma warning disable 1998
protected override void BuildRenderTree(global::Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder __builder)
{
__builder.AddMarkupContent(0, ""<h1>Hello world</h1>"");
}
#pragma warning restore 1998
}
}
#pragma warning restore 1591
");
Assert.Empty(result.Diagnostics);
Assert.Single(result.GeneratedSources);
}
[Fact, WorkItem("https://github.com/dotnet/razor/issues/8610")]
public async Task SourceGenerator_RazorFiles_Using_NestedClass()
{
// Arrange
var project = CreateTestProject(new()
{
["Pages/Index.razor"] = """
@code {
public class MyModel { }
}
""",
["Shared/MyComponent.razor"] = """
<MyComponent Data="@Data" />
@code {
[Parameter]
public Pages.Index.MyModel? Data { get; set; }
}
""",
});
var compilation = await project.GetCompilationAsync();
var driver = await GetDriverAsync(project, options =>
{
options.TestGlobalOptions["build_property.RazorLangVersion"] = "7.0";
});
// Act
var result = RunGenerator(compilation!, ref driver);
// Assert
Assert.Empty(result.Diagnostics);
Assert.Equal(2, result.GeneratedSources.Length);
}
[Fact, WorkItem("https://github.com/dotnet/razor/issues/8610")]
public async Task SourceGenerator_RazorFiles_UsingAlias_NestedClass()
{
// Arrange
var project = CreateTestProject(new()
{
["Pages/Index.razor"] = """
@code {
public class MyModel { }
}
""",
["Shared/MyComponent.razor"] = """
@using MyAlias = Pages.Index.MyModel;
<MyComponent Data="@Data" />
@code {
[Parameter]
public MyAlias? Data { get; set; }
}
""",
});
var compilation = await project.GetCompilationAsync();
var driver = await GetDriverAsync(project, options =>
{
options.TestGlobalOptions["build_property.RazorLangVersion"] = "7.0";
});
// Act
var result = RunGenerator(compilation!, ref driver);
// Assert
Assert.Empty(result.Diagnostics);
Assert.Equal(2, result.GeneratedSources.Length);
}
[Fact]
public async Task SourceGeneratorEvents_RazorFiles_Works()
{
// Arrange
var project = CreateTestProject(new()
{
["Pages/Index.razor"] = "<h1>Hello world</h1>",
["Pages/Counter.razor"] = "<h1>Counter</h1>",
});
var compilation = await project.GetCompilationAsync();
var (driver, _, _) = await GetDriverWithAdditionalTextAndProviderAsync(project, trackSteps: true);
var result = RunGenerator(compilation!, ref driver)
.VerifyPageOutput(
@"#pragma checksum ""Pages/Index.razor"" ""{ff1816ec-aa5e-4d10-87f7-6f4963833460}"" ""6b5db227a6aa2228c777b0771108b184b1fc5df3""
// <auto-generated/>
#pragma warning disable 1591
namespace MyApp.Pages
{
#line default
using global::System;
using global::System.Collections.Generic;
using global::System.Linq;
using global::System.Threading.Tasks;
using global::Microsoft.AspNetCore.Components;
#line default
#line hidden
#nullable restore
public partial class Index : global::Microsoft.AspNetCore.Components.ComponentBase
#nullable disable
{
#pragma warning disable 1998
protected override void BuildRenderTree(global::Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder __builder)
{
__builder.AddMarkupContent(0, ""<h1>Hello world</h1>"");
}
#pragma warning restore 1998
}
}
#pragma warning restore 1591
",
@"#pragma checksum ""Pages/Counter.razor"" ""{ff1816ec-aa5e-4d10-87f7-6f4963833460}"" ""0de17e526cd536d59072aa0e924e99111b16b97a""
// <auto-generated/>
#pragma warning disable 1591
namespace MyApp.Pages
{
#line default
using global::System;
using global::System.Collections.Generic;
using global::System.Linq;
using global::System.Threading.Tasks;
using global::Microsoft.AspNetCore.Components;
#line default
#line hidden
#nullable restore
public partial class Counter : global::Microsoft.AspNetCore.Components.ComponentBase
#nullable disable
{
#pragma warning disable 1998
protected override void BuildRenderTree(global::Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder __builder)
{
__builder.AddMarkupContent(0, ""<h1>Counter</h1>"");
}
#pragma warning restore 1998
}
}
#pragma warning restore 1591
");
Assert.Empty(result.Diagnostics);
Assert.Equal(2, result.GeneratedSources.Length);
// Verify all the incremental steps ran as expected for the initial generation
result.VerifyIncrementalSteps("RazorSourceGeneratorOptions", IncrementalStepRunReason.New);
result.VerifyIncrementalStepsMultiple("GeneratedDeclarationCode", IncrementalStepRunReason.New, IncrementalStepRunReason.New);
result.VerifyIncrementalSteps("TagHelpersFromCompilation", IncrementalStepRunReason.New);
result.VerifyIncrementalSteps("TagHelpersFromReferences", IncrementalStepRunReason.New);
result.VerifyIncrementalStepsMultiple("ParsedDocuments", IncrementalStepRunReason.New, IncrementalStepRunReason.New);
result.VerifyIncrementalStepsMultiple("RewrittenTagHelpers", IncrementalStepRunReason.New, IncrementalStepRunReason.New);
result.VerifyIncrementalStepsMultiple("CheckedAndRewrittenTagHelpers", IncrementalStepRunReason.New, IncrementalStepRunReason.New);
result.VerifyIncrementalStepsMultiple("GeneratedCode", IncrementalStepRunReason.New, IncrementalStepRunReason.New);
result.VerifyIncrementalStepsMultiple("CSharpDocuments", IncrementalStepRunReason.New, IncrementalStepRunReason.New);
}
[Fact]
public async Task IncrementalCompilation_DoesNotReexecuteSteps_WhenRazorFilesAreUnchanged()
{
// Arrange
var project = CreateTestProject(new()
{
["Pages/Index.razor"] = "<h1>Hello world</h1>",
["Pages/Counter.razor"] = "<h1>Counter</h1>",
});
var compilation = await project.GetCompilationAsync();
var (driver, _, _) = await GetDriverWithAdditionalTextAndProviderAsync(project, trackSteps: true);
var result = RunGenerator(compilation!, ref driver)
.VerifyPageOutput(@"
#pragma checksum ""Pages/Index.razor"" ""{ff1816ec-aa5e-4d10-87f7-6f4963833460}"" ""6b5db227a6aa2228c777b0771108b184b1fc5df3""
// <auto-generated/>
#pragma warning disable 1591
namespace MyApp.Pages
{
#line default
using global::System;
using global::System.Collections.Generic;
using global::System.Linq;
using global::System.Threading.Tasks;
using global::Microsoft.AspNetCore.Components;
#line default
#line hidden
#nullable restore
public partial class Index : global::Microsoft.AspNetCore.Components.ComponentBase
#nullable disable
{
#pragma warning disable 1998
protected override void BuildRenderTree(global::Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder __builder)
{
__builder.AddMarkupContent(0, ""<h1>Hello world</h1>"");
}
#pragma warning restore 1998
}
}
#pragma warning restore 1591",
@"
#pragma checksum ""Pages/Counter.razor"" ""{ff1816ec-aa5e-4d10-87f7-6f4963833460}"" ""0de17e526cd536d59072aa0e924e99111b16b97a""
// <auto-generated/>
#pragma warning disable 1591
namespace MyApp.Pages
{
#line default
using global::System;
using global::System.Collections.Generic;
using global::System.Linq;
using global::System.Threading.Tasks;
using global::Microsoft.AspNetCore.Components;
#line default
#line hidden
#nullable restore
public partial class Counter : global::Microsoft.AspNetCore.Components.ComponentBase
#nullable disable
{
#pragma warning disable 1998
protected override void BuildRenderTree(global::Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder __builder)
{
__builder.AddMarkupContent(0, ""<h1>Counter</h1>"");
}
#pragma warning restore 1998
}
}
#pragma warning restore 1591");
Assert.Empty(result.Diagnostics);
Assert.Equal(2, result.GeneratedSources.Length);
result = RunGenerator(compilation!, ref driver)
.VerifyOutputsMatch(result);
Assert.Empty(result.Diagnostics);
Assert.Equal(2, result.GeneratedSources.Length);
// Verify that when nothing changes, all steps are cached/unchanged
result.VerifyIncrementalSteps("RazorSourceGeneratorOptions", IncrementalStepRunReason.Cached);
result.VerifyIncrementalStepsMultiple("GeneratedDeclarationCode", IncrementalStepRunReason.Cached, IncrementalStepRunReason.Cached);
result.VerifyIncrementalSteps("TagHelpersFromCompilation", IncrementalStepRunReason.Cached);
result.VerifyIncrementalSteps("TagHelpersFromReferences", IncrementalStepRunReason.Cached);
result.VerifyIncrementalStepsMultiple("ParsedDocuments", IncrementalStepRunReason.Cached, IncrementalStepRunReason.Cached);
result.VerifyIncrementalStepsMultiple("RewrittenTagHelpers", IncrementalStepRunReason.Cached, IncrementalStepRunReason.Cached);
result.VerifyIncrementalStepsMultiple("CheckedAndRewrittenTagHelpers", IncrementalStepRunReason.Cached, IncrementalStepRunReason.Cached);
result.VerifyIncrementalStepsMultiple("GeneratedCode", IncrementalStepRunReason.Cached, IncrementalStepRunReason.Cached);
result.VerifyIncrementalStepsMultiple("CSharpDocuments", IncrementalStepRunReason.Cached, IncrementalStepRunReason.Cached);
}
[Fact]
public async Task IncrementalCompilation_WhenRazorFileMarkupChanges()
{
// Arrange
var project = CreateTestProject(new()
{
["Pages/Index.razor"] = "<h1>Hello world</h1>",
["Pages/Counter.razor"] = "<h1>Counter</h1>",
});
var compilation = await project.GetCompilationAsync();
var (driver, additionalTexts, _) = await GetDriverWithAdditionalTextAndProviderAsync(project, trackSteps: true);
var result = RunGenerator(compilation!, ref driver)
.VerifyPageOutput(
@"#pragma checksum ""Pages/Index.razor"" ""{ff1816ec-aa5e-4d10-87f7-6f4963833460}"" ""6b5db227a6aa2228c777b0771108b184b1fc5df3""
// <auto-generated/>
#pragma warning disable 1591
namespace MyApp.Pages
{
#line default
using global::System;
using global::System.Collections.Generic;
using global::System.Linq;
using global::System.Threading.Tasks;
using global::Microsoft.AspNetCore.Components;
#line default
#line hidden
#nullable restore
public partial class Index : global::Microsoft.AspNetCore.Components.ComponentBase
#nullable disable
{
#pragma warning disable 1998
protected override void BuildRenderTree(global::Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder __builder)
{
__builder.AddMarkupContent(0, ""<h1>Hello world</h1>"");
}
#pragma warning restore 1998
}
}
#pragma warning restore 1591
",
@"#pragma checksum ""Pages/Counter.razor"" ""{ff1816ec-aa5e-4d10-87f7-6f4963833460}"" ""0de17e526cd536d59072aa0e924e99111b16b97a""
// <auto-generated/>
#pragma warning disable 1591
namespace MyApp.Pages
{
#line default
using global::System;
using global::System.Collections.Generic;
using global::System.Linq;
using global::System.Threading.Tasks;
using global::Microsoft.AspNetCore.Components;
#line default
#line hidden
#nullable restore
public partial class Counter : global::Microsoft.AspNetCore.Components.ComponentBase
#nullable disable
{
#pragma warning disable 1998
protected override void BuildRenderTree(global::Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder __builder)
{
__builder.AddMarkupContent(0, ""<h1>Counter</h1>"");
}
#pragma warning restore 1998
}
}
#pragma warning restore 1591
");
Assert.Empty(result.Diagnostics);
Assert.Equal(2, result.GeneratedSources.Length);
result = RunGenerator(compilation!, ref driver)
.VerifyOutputsMatch(result);
Assert.Empty(result.Diagnostics);
Assert.Equal(2, result.GeneratedSources.Length);
// Verify that when nothing changes, all steps are cached
result.VerifyIncrementalStepsMultiple("ParsedDocuments", IncrementalStepRunReason.Cached, IncrementalStepRunReason.Cached);
result.VerifyIncrementalStepsMultiple("GeneratedCode", IncrementalStepRunReason.Cached, IncrementalStepRunReason.Cached);
result.VerifyIncrementalStepsMultiple("CSharpDocuments", IncrementalStepRunReason.Cached, IncrementalStepRunReason.Cached);
var updatedText = new TestAdditionalText("Pages/Counter.razor", SourceText.From("<h2>Counter</h2>", Encoding.UTF8));
driver = driver.ReplaceAdditionalText(additionalTexts.First(f => f.Path == updatedText.Path), updatedText);
result = RunGenerator(compilation!, ref driver)
.VerifyOutputsMatch(result, (1,
@"#pragma checksum ""Pages/Counter.razor"" ""{ff1816ec-aa5e-4d10-87f7-6f4963833460}"" ""e022c3eac864ad044e9b7d56f4c493ab4eab36da""
// <auto-generated/>
#pragma warning disable 1591
namespace MyApp.Pages
{
#line default
using global::System;
using global::System.Collections.Generic;
using global::System.Linq;
using global::System.Threading.Tasks;
using global::Microsoft.AspNetCore.Components;
#line default
#line hidden
#nullable restore
public partial class Counter : global::Microsoft.AspNetCore.Components.ComponentBase
#nullable disable
{
#pragma warning disable 1998
protected override void BuildRenderTree(global::Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder __builder)
{
__builder.AddMarkupContent(0, ""<h2>Counter</h2>"");
}
#pragma warning restore 1998
}
}
#pragma warning restore 1591
"));
Assert.Empty(result.Diagnostics);
Assert.Equal(2, result.GeneratedSources.Length);
// Verify that when Counter markup changes, only Counter steps re-run
result.VerifyIncrementalStepsMultiple("ParsedDocuments",
IncrementalStepRunReason.Cached, // Index unchanged
IncrementalStepRunReason.Modified); // Counter changed
result.VerifyIncrementalStepsMultiple("RewrittenTagHelpers",
IncrementalStepRunReason.Cached, // Index unchanged
IncrementalStepRunReason.Modified); // Counter changed
result.VerifyIncrementalStepsMultiple("CheckedAndRewrittenTagHelpers",
IncrementalStepRunReason.Cached, // Index unchanged
IncrementalStepRunReason.Modified); // Counter changed
result.VerifyIncrementalStepsMultiple("GeneratedCode",
IncrementalStepRunReason.Cached, // Index unchanged
IncrementalStepRunReason.Modified); // Counter changed
result.VerifyIncrementalStepsMultiple("CSharpDocuments",
IncrementalStepRunReason.Cached, // Index unchanged
IncrementalStepRunReason.Modified); // Counter changed
}
[ConditionalFact(typeof(IsEnglishLocal))]
public async Task IncrementalCompilation_RazorFiles_WhenNewTypeIsAdded()
{
// Arrange
var project = CreateTestProject(new()
{
["Pages/Index.razor"] = "<h1>Hello world</h1>",
["Pages/Counter.razor"] = "<h1>Counter</h1>",
});
var compilation = await project.GetCompilationAsync();
var (driver, additionalTexts, _) = await GetDriverWithAdditionalTextAndProviderAsync(project, trackSteps: true);
var result = RunGenerator(compilation!, ref driver)
.VerifyPageOutput(
@"#pragma checksum ""Pages/Index.razor"" ""{ff1816ec-aa5e-4d10-87f7-6f4963833460}"" ""6b5db227a6aa2228c777b0771108b184b1fc5df3""
// <auto-generated/>
#pragma warning disable 1591
namespace MyApp.Pages
{
#line default
using global::System;
using global::System.Collections.Generic;
using global::System.Linq;
using global::System.Threading.Tasks;
using global::Microsoft.AspNetCore.Components;
#line default
#line hidden
#nullable restore
public partial class Index : global::Microsoft.AspNetCore.Components.ComponentBase
#nullable disable
{
#pragma warning disable 1998
protected override void BuildRenderTree(global::Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder __builder)
{
__builder.AddMarkupContent(0, ""<h1>Hello world</h1>"");
}
#pragma warning restore 1998
}
}
#pragma warning restore 1591
",
@"#pragma checksum ""Pages/Counter.razor"" ""{ff1816ec-aa5e-4d10-87f7-6f4963833460}"" ""0de17e526cd536d59072aa0e924e99111b16b97a""
// <auto-generated/>
#pragma warning disable 1591
namespace MyApp.Pages
{
#line default
using global::System;
using global::System.Collections.Generic;
using global::System.Linq;
using global::System.Threading.Tasks;
using global::Microsoft.AspNetCore.Components;
#line default
#line hidden
#nullable restore
public partial class Counter : global::Microsoft.AspNetCore.Components.ComponentBase
#nullable disable
{
#pragma warning disable 1998
protected override void BuildRenderTree(global::Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder __builder)
{
__builder.AddMarkupContent(0, ""<h1>Counter</h1>"");
}
#pragma warning restore 1998
}
}
#pragma warning restore 1591
");
Assert.Empty(result.Diagnostics);
Assert.Equal(2, result.GeneratedSources.Length);
result = RunGenerator(compilation!, ref driver)
.VerifyOutputsMatch(result);
Assert.Empty(result.Diagnostics);
Assert.Equal(2, result.GeneratedSources.Length);
project = project.AddDocument("Person.cs", SourceText.From("""
public class Person
{
public string Name { get; set; }
}
""", Encoding.UTF8)).Project;
compilation = await project.GetCompilationAsync();
result = RunGenerator(compilation!, ref driver,
// Person.cs(3,19): warning CS8618: Non-nullable property 'Name' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.
// public string Name { get; set; }
Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "Name").WithArguments("property", "Name").WithLocation(3, 19)
);
result.VerifyOutputsMatch(result);
Assert.Empty(result.Diagnostics);
Assert.Equal(2, result.GeneratedSources.Length);
// Verify that when a new C# type is added, only TagHelpersFromCompilation re-runs
result.VerifyIncrementalSteps("TagHelpersFromCompilation", IncrementalStepRunReason.Unchanged); // Re-ran but no new tag helpers
}
[ConditionalFact(typeof(IsEnglishLocal))]
public async Task IncrementalCompilation_RazorFiles_WhenCSharpTypeChanges()
{
// Arrange
var project = CreateTestProject(new()
{
["Pages/Index.razor"] = "<h1>Hello world</h1>",
["Pages/Counter.razor"] = "<h1>Counter</h1>",
},
new()
{
["Person.cs"] = @"
public class Person
{
public string Name { get; set; }
}"
});
var compilation = await project.GetCompilationAsync();
var (driver, additionalTexts, _) = await GetDriverWithAdditionalTextAndProviderAsync(project, trackSteps: true);
var expectedDiagnostics = new DiagnosticDescription[]
{
// Person.cs(4,19): warning CS8618: Non-nullable property 'Name' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.
// public string Name { get; set; }
Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "Name").WithArguments("property", "Name").WithLocation(4, 19)
};
var result = RunGenerator(compilation!, ref driver, expectedDiagnostics)
.VerifyPageOutput(
@"#pragma checksum ""Pages/Index.razor"" ""{ff1816ec-aa5e-4d10-87f7-6f4963833460}"" ""6b5db227a6aa2228c777b0771108b184b1fc5df3""
// <auto-generated/>
#pragma warning disable 1591
namespace MyApp.Pages
{
#line default
using global::System;
using global::System.Collections.Generic;
using global::System.Linq;
using global::System.Threading.Tasks;
using global::Microsoft.AspNetCore.Components;
#line default
#line hidden
#nullable restore
public partial class Index : global::Microsoft.AspNetCore.Components.ComponentBase
#nullable disable
{
#pragma warning disable 1998
protected override void BuildRenderTree(global::Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder __builder)
{
__builder.AddMarkupContent(0, ""<h1>Hello world</h1>"");
}
#pragma warning restore 1998
}
}
#pragma warning restore 1591
",
@"#pragma checksum ""Pages/Counter.razor"" ""{ff1816ec-aa5e-4d10-87f7-6f4963833460}"" ""0de17e526cd536d59072aa0e924e99111b16b97a""
// <auto-generated/>
#pragma warning disable 1591
namespace MyApp.Pages
{
#line default
using global::System;
using global::System.Collections.Generic;
using global::System.Linq;
using global::System.Threading.Tasks;
using global::Microsoft.AspNetCore.Components;
#line default
#line hidden
#nullable restore
public partial class Counter : global::Microsoft.AspNetCore.Components.ComponentBase
#nullable disable
{
#pragma warning disable 1998
protected override void BuildRenderTree(global::Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder __builder)
{
__builder.AddMarkupContent(0, ""<h1>Counter</h1>"");
}
#pragma warning restore 1998
}
}
#pragma warning restore 1591
");
Assert.Empty(result.Diagnostics);
Assert.Equal(2, result.GeneratedSources.Length);
result = RunGenerator(compilation!, ref driver, expectedDiagnostics)
.VerifyOutputsMatch(result);
Assert.Empty(result.Diagnostics);
Assert.Equal(2, result.GeneratedSources.Length);
project = project.Documents.First().WithText(SourceText.From(@"
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
}", Encoding.UTF8)).Project;
compilation = await project.GetCompilationAsync();
result = RunGenerator(compilation!, ref driver, expectedDiagnostics)
.VerifyOutputsMatch(result);
Assert.Empty(result.Diagnostics);
Assert.Equal(2, result.GeneratedSources.Length);
// When a C# type changes, TagHelpersFromCompilation re-runs but the output is unchanged
// because Person is not a component and doesn't affect tag helpers
result.VerifyIncrementalSteps("TagHelpersFromCompilation", IncrementalStepRunReason.Unchanged);
}
[Fact]
public async Task IncrementalCompilation_RazorFiles_WhenChildComponentsAreAdded()
{
// Arrange
var project = CreateTestProject(new()
{
["Pages/Index.razor"] = "<h1>Hello world</h1>",
["Pages/Counter.razor"] = "<h1>Counter</h1>",
});
var compilation = await project.GetCompilationAsync();
var (driver, additionalTexts, _) = await GetDriverWithAdditionalTextAndProviderAsync(project, trackSteps: true);
var result = RunGenerator(compilation!, ref driver)
.VerifyPageOutput(
@"#pragma checksum ""Pages/Index.razor"" ""{ff1816ec-aa5e-4d10-87f7-6f4963833460}"" ""6b5db227a6aa2228c777b0771108b184b1fc5df3""
// <auto-generated/>
#pragma warning disable 1591
namespace MyApp.Pages
{
#line default
using global::System;
using global::System.Collections.Generic;
using global::System.Linq;
using global::System.Threading.Tasks;
using global::Microsoft.AspNetCore.Components;
#line default
#line hidden
#nullable restore
public partial class Index : global::Microsoft.AspNetCore.Components.ComponentBase
#nullable disable
{
#pragma warning disable 1998
protected override void BuildRenderTree(global::Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder __builder)
{
__builder.AddMarkupContent(0, ""<h1>Hello world</h1>"");
}
#pragma warning restore 1998
}
}
#pragma warning restore 1591
",
@"#pragma checksum ""Pages/Counter.razor"" ""{ff1816ec-aa5e-4d10-87f7-6f4963833460}"" ""0de17e526cd536d59072aa0e924e99111b16b97a""
// <auto-generated/>
#pragma warning disable 1591
namespace MyApp.Pages
{
#line default
using global::System;
using global::System.Collections.Generic;
using global::System.Linq;
using global::System.Threading.Tasks;
using global::Microsoft.AspNetCore.Components;
#line default
#line hidden
#nullable restore
public partial class Counter : global::Microsoft.AspNetCore.Components.ComponentBase
#nullable disable
{
#pragma warning disable 1998
protected override void BuildRenderTree(global::Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder __builder)
{
__builder.AddMarkupContent(0, ""<h1>Counter</h1>"");
}
#pragma warning restore 1998
}
}
#pragma warning restore 1591
");
Assert.Empty(result.Diagnostics);
Assert.Equal(2, result.GeneratedSources.Length);
result = RunGenerator(compilation!, ref driver)
.VerifyOutputsMatch(result);
Assert.Empty(result.Diagnostics);
Assert.Equal(2, result.GeneratedSources.Length);
var updatedText = new TestAdditionalText("Pages/Counter.razor", SourceText.From(@"
<h2>Counter</h2>
<h3>Current count: @count</h3>
<button @onclick=""Click"">Click me</button>
@code
{
private int count;
public void Click() => count++;
}
", Encoding.UTF8));
driver = driver.ReplaceAdditionalText(additionalTexts.First(f => f.Path == updatedText.Path), updatedText);
result = RunGenerator(compilation!, ref driver)
.VerifyOutputsMatch(result, (1,
@"#pragma checksum ""Pages/Counter.razor"" ""{ff1816ec-aa5e-4d10-87f7-6f4963833460}"" ""1ea7ff43d3a2eecc5502dd3771378e334a8fb068""
// <auto-generated/>
#pragma warning disable 1591
namespace MyApp.Pages
{
#line default
using global::System;
using global::System.Collections.Generic;
using global::System.Linq;
using global::System.Threading.Tasks;
using global::Microsoft.AspNetCore.Components;
#line default
#line hidden
#nullable restore
public partial class Counter : global::Microsoft.AspNetCore.Components.ComponentBase
#nullable disable
{
#pragma warning disable 1998
protected override void BuildRenderTree(global::Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder __builder)
{
__builder.AddMarkupContent(0, ""<h2>Counter</h2>\r\n"");
__builder.OpenElement(1, ""h3"");
__builder.AddContent(2, ""Current count: "");
#nullable restore
#line (3,21)-(3,26) 24 ""Pages/Counter.razor""
__builder.AddContent(3, count
#line default
#line hidden
#nullable disable
);
__builder.CloseElement();
__builder.AddMarkupContent(4, ""\r\n"");
__builder.AddMarkupContent(5, ""<button @onclick=\""Click\"">Click me</button>"");
}
#pragma warning restore 1998
#nullable restore
#line (7,2)-(11,1) ""Pages/Counter.razor""
private int count;
public void Click() => count++;
#line default
#line hidden
#nullable disable
}
}
#pragma warning restore 1591
"));
Assert.Empty(result.Diagnostics);
Assert.Equal(2, result.GeneratedSources.Length);
// Verify that when Counter adds code block, only Counter steps re-run
result.VerifyIncrementalStepsMultiple("ParsedDocuments",
IncrementalStepRunReason.Cached, // Index unchanged
IncrementalStepRunReason.Modified); // Counter changed
result.VerifyIncrementalStepsMultiple("GeneratedDeclarationCode",
IncrementalStepRunReason.Cached, // Index unchanged
IncrementalStepRunReason.Modified); // Counter changed (new code block)
result.VerifyIncrementalSteps("TagHelpersFromCompilation", IncrementalStepRunReason.Unchanged); // Re-run but no new tag helpers
result.VerifyIncrementalStepsMultiple("RewrittenTagHelpers",
IncrementalStepRunReason.Cached, // Index - document and tag helpers unchanged
IncrementalStepRunReason.Modified); // Counter - document changed
result.VerifyIncrementalStepsMultiple("CheckedAndRewrittenTagHelpers",
IncrementalStepRunReason.Cached, // Index unchanged
IncrementalStepRunReason.Modified); // Counter changed
result.VerifyIncrementalStepsMultiple("GeneratedCode",
IncrementalStepRunReason.Cached, // Index unchanged
IncrementalStepRunReason.Modified); // Counter changed
result.VerifyIncrementalStepsMultiple("CSharpDocuments",
IncrementalStepRunReason.Cached, // Index unchanged
IncrementalStepRunReason.Modified); // Counter changed
}
[Fact]
public async Task IncrementalCompilation_RazorFiles_WhenNewComponentParameterIsAdded()
{
// Arrange
var project = CreateTestProject(new()
{
["Pages/Index.razor"] = "<h1>Hello world</h1>",
["Pages/Counter.razor"] = "<h1>Counter</h1>",
});
var compilation = await project.GetCompilationAsync();
var (driver, additionalTexts, _) = await GetDriverWithAdditionalTextAndProviderAsync(project, trackSteps: true);
var result = RunGenerator(compilation!, ref driver)
.VerifyPageOutput(
@"#pragma checksum ""Pages/Index.razor"" ""{ff1816ec-aa5e-4d10-87f7-6f4963833460}"" ""6b5db227a6aa2228c777b0771108b184b1fc5df3""
// <auto-generated/>
#pragma warning disable 1591
namespace MyApp.Pages
{
#line default
using global::System;
using global::System.Collections.Generic;
using global::System.Linq;
using global::System.Threading.Tasks;
using global::Microsoft.AspNetCore.Components;
#line default
#line hidden
#nullable restore
public partial class Index : global::Microsoft.AspNetCore.Components.ComponentBase
#nullable disable
{
#pragma warning disable 1998
protected override void BuildRenderTree(global::Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder __builder)
{
__builder.AddMarkupContent(0, ""<h1>Hello world</h1>"");
}
#pragma warning restore 1998
}
}
#pragma warning restore 1591
",
@"#pragma checksum ""Pages/Counter.razor"" ""{ff1816ec-aa5e-4d10-87f7-6f4963833460}"" ""0de17e526cd536d59072aa0e924e99111b16b97a""
// <auto-generated/>
#pragma warning disable 1591
namespace MyApp.Pages
{
#line default
using global::System;
using global::System.Collections.Generic;
using global::System.Linq;
using global::System.Threading.Tasks;
using global::Microsoft.AspNetCore.Components;
#line default
#line hidden
#nullable restore
public partial class Counter : global::Microsoft.AspNetCore.Components.ComponentBase
#nullable disable
{
#pragma warning disable 1998
protected override void BuildRenderTree(global::Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder __builder)
{
__builder.AddMarkupContent(0, ""<h1>Counter</h1>"");
}
#pragma warning restore 1998
}
}
#pragma warning restore 1591
");
Assert.Empty(result.Diagnostics);
Assert.Equal(2, result.GeneratedSources.Length);
result = RunGenerator(compilation!, ref driver)
.VerifyOutputsMatch(result);
Assert.Empty(result.Diagnostics);
Assert.Equal(2, result.GeneratedSources.Length);
var updatedText = new TestAdditionalText("Pages/Counter.razor", SourceText.From(@"
<h2>Counter</h2>
<h3>Current count: @count</h3>
<button @onclick=""Click"">Click me</button>
@code
{
private int count;
public void Click() => count++;
[Parameter] public int IncrementAmount { get; set; }
}
", Encoding.UTF8));
driver = driver.ReplaceAdditionalText(additionalTexts.First(f => f.Path == updatedText.Path), updatedText);
result = RunGenerator(compilation!, ref driver)
.VerifyOutputsMatch(result, (1,
@"#pragma checksum ""Pages/Counter.razor"" ""{ff1816ec-aa5e-4d10-87f7-6f4963833460}"" ""fb93b97dc0d4885250500697f41ffde70decc444""
// <auto-generated/>
#pragma warning disable 1591
namespace MyApp.Pages
{
#line default
using global::System;
using global::System.Collections.Generic;
using global::System.Linq;
using global::System.Threading.Tasks;
using global::Microsoft.AspNetCore.Components;
#line default
#line hidden
#nullable restore
public partial class Counter : global::Microsoft.AspNetCore.Components.ComponentBase
#nullable disable
{
#pragma warning disable 1998
protected override void BuildRenderTree(global::Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder __builder)
{
__builder.AddMarkupContent(0, ""<h2>Counter</h2>\r\n"");
__builder.OpenElement(1, ""h3"");
__builder.AddContent(2, ""Current count: "");
#nullable restore
#line (3,21)-(3,26) 24 ""Pages/Counter.razor""
__builder.AddContent(3, count
#line default
#line hidden
#nullable disable
);
__builder.CloseElement();
__builder.AddMarkupContent(4, ""\r\n"");
__builder.AddMarkupContent(5, ""<button @onclick=\""Click\"">Click me</button>"");
}
#pragma warning restore 1998
#nullable restore
#line (7,2)-(13,1) ""Pages/Counter.razor""
private int count;
public void Click() => count++;
[Parameter] public int IncrementAmount { get; set; }
#line default
#line hidden
#nullable disable
}
}
#pragma warning restore 1591
"));
Assert.Empty(result.Diagnostics);
Assert.Equal(2, result.GeneratedSources.Length);
// Verify that when Counter adds Parameter, the necessary steps re-run
result.VerifyIncrementalStepsMultiple("ParsedDocuments",
IncrementalStepRunReason.Cached, // Index unchanged
IncrementalStepRunReason.Modified); // Counter changed
result.VerifyIncrementalStepsMultiple("GeneratedDeclarationCode",
IncrementalStepRunReason.Cached, // Index unchanged
IncrementalStepRunReason.Modified); // Counter changed (new Parameter)
result.VerifyIncrementalSteps("TagHelpersFromCompilation", IncrementalStepRunReason.Modified); // Parameter metadata changed
result.VerifyIncrementalStepsMultiple("RewrittenTagHelpers",
IncrementalStepRunReason.Cached, // Index - document didn't change (comparer only checks document)
IncrementalStepRunReason.Modified); // Counter - document changed and tag helpers updated
result.VerifyIncrementalStepsMultiple("CheckedAndRewrittenTagHelpers",
IncrementalStepRunReason.Modified, // Index - tag helper collection changed, needs re-check
IncrementalStepRunReason.Modified); // Counter changed
result.VerifyIncrementalStepsMultiple("GeneratedCode",
IncrementalStepRunReason.Modified, // Index - re-generated with new tag helper metadata
IncrementalStepRunReason.Modified); // Counter changed
result.VerifyIncrementalStepsMultiple("CSharpDocuments",
IncrementalStepRunReason.Unchanged, // Index - output unchanged (doesn't use Counter)
IncrementalStepRunReason.Modified); // Counter changed
}
[Fact]
public async Task IncrementalCompilation_RazorFiles_WhenProjectReferencesChange()
{
// Arrange
var project = CreateTestProject(new()
{
["Pages/Index.razor"] =
@"
@using SurveyPromptRootNamspace;
<h1>Hello world</h1>
<SurveyPrompt />
",
["Pages/Counter.razor"] = "<h1>Counter</h1>",
});
var compilation = await project.GetCompilationAsync();
var (driver, additionalTexts, _) = await GetDriverWithAdditionalTextAndProviderAsync(project, trackSteps: true);
var result = RunGenerator(compilation!, ref driver,
// Pages/Index.razor(2,8): error CS0246: The type or namespace name 'SurveyPromptRootNamspace' could not be found (are you missing a using directive or an assembly reference?)
// using SurveyPromptRootNamspace;
Diagnostic(ErrorCode.ERR_SingleTypeNameNotFound, "SurveyPromptRootNamspace").WithArguments("SurveyPromptRootNamspace").WithLocation(2, 8)
);
result.VerifyPageOutput(
@"#pragma checksum ""Pages/Index.razor"" ""{ff1816ec-aa5e-4d10-87f7-6f4963833460}"" ""4745828f8a0ab77b58022ed5d1095a0242f2a7ee""
// <auto-generated/>
#pragma warning disable 1591
namespace MyApp.Pages
{
#line default
using global::System;
using global::System.Collections.Generic;
using global::System.Linq;
using global::System.Threading.Tasks;
using global::Microsoft.AspNetCore.Components;
#nullable restore
#line (2,2)-(2,33) ""Pages/Index.razor""
using SurveyPromptRootNamspace;
#nullable disable
#line default
#line hidden
#nullable restore
public partial class Index : global::Microsoft.AspNetCore.Components.ComponentBase
#nullable disable
{
#pragma warning disable 1998
protected override void BuildRenderTree(global::Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder __builder)
{
__builder.AddMarkupContent(0, ""<h1>Hello world</h1>\r\n"");
__builder.OpenElement(1, ""SurveyPrompt"");
__builder.CloseElement();
}
#pragma warning restore 1998
}
}
#pragma warning restore 1591
",
@"#pragma checksum ""Pages/Counter.razor"" ""{ff1816ec-aa5e-4d10-87f7-6f4963833460}"" ""0de17e526cd536d59072aa0e924e99111b16b97a""
// <auto-generated/>
#pragma warning disable 1591
namespace MyApp.Pages
{
#line default
using global::System;
using global::System.Collections.Generic;
using global::System.Linq;
using global::System.Threading.Tasks;
using global::Microsoft.AspNetCore.Components;
#line default
#line hidden
#nullable restore
public partial class Counter : global::Microsoft.AspNetCore.Components.ComponentBase
#nullable disable
{
#pragma warning disable 1998
protected override void BuildRenderTree(global::Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder __builder)
{
__builder.AddMarkupContent(0, ""<h1>Counter</h1>"");
}
#pragma warning restore 1998
}
}
#pragma warning restore 1591
");
var diagnostic = Assert.Single(result.Diagnostics);
Assert.Equal("RZ10012", diagnostic.Id);
Assert.Equal(2, result.GeneratedSources.Length);
var surveyPromptAssembly = GetSurveyPromptMetadataReference(compilation!);
compilation = compilation!.AddReferences(surveyPromptAssembly);
result = RunGenerator(compilation, ref driver)
.VerifyOutputsMatch(result, (0,
@"#pragma checksum ""Pages/Index.razor"" ""{ff1816ec-aa5e-4d10-87f7-6f4963833460}"" ""4745828f8a0ab77b58022ed5d1095a0242f2a7ee""
// <auto-generated/>
#pragma warning disable 1591
namespace MyApp.Pages
{
#line default
using global::System;
using global::System.Collections.Generic;
using global::System.Linq;
using global::System.Threading.Tasks;
using global::Microsoft.AspNetCore.Components;
#nullable restore
#line (2,2)-(2,33) ""Pages/Index.razor""
using SurveyPromptRootNamspace;
#nullable disable
#line default
#line hidden
#nullable restore
public partial class Index : global::Microsoft.AspNetCore.Components.ComponentBase
#nullable disable
{
#pragma warning disable 1998
protected override void BuildRenderTree(global::Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder __builder)
{
__builder.AddMarkupContent(0, ""<h1>Hello world</h1>\r\n"");
__builder.OpenComponent<global::SurveyPromptRootNamspace.SurveyPrompt>(1);
__builder.CloseComponent();
}
#pragma warning restore 1998
}
}
#pragma warning restore 1591
"
));
Assert.Empty(result.Diagnostics);
Assert.Equal(2, result.GeneratedSources.Length);
// Verify that adding a metadata reference triggers tag helper discovery
result.VerifyIncrementalSteps("RazorSourceGeneratorOptions", IncrementalStepRunReason.Unchanged); // Re-ran but unchanged
result.VerifyIncrementalSteps("TagHelpersFromCompilation", IncrementalStepRunReason.Unchanged); // Re-ran but compilation tag helpers unchanged
result.VerifyIncrementalSteps("TagHelpersFromReferences", IncrementalStepRunReason.Modified); // New reference added
result.VerifyIncrementalStepsMultiple("CheckedAndRewrittenTagHelpers",
IncrementalStepRunReason.Modified, // Index - new tag helper discovered
IncrementalStepRunReason.Modified); // Counter - tag helpers changed, needs recheck
result.VerifyIncrementalStepsMultiple("GeneratedCode",
IncrementalStepRunReason.Modified, // Index - re-generated with tag helper
IncrementalStepRunReason.Modified); // Counter - re-generated due to tag helper changes
result.VerifyIncrementalStepsMultiple("CSharpDocuments",
IncrementalStepRunReason.Modified, // Index - output changed
IncrementalStepRunReason.Unchanged); // Counter - output unchanged (doesn't use new tag helper)
// Verify caching
result = RunGenerator(compilation, ref driver);
Assert.Empty(result.Diagnostics);
Assert.Equal(2, result.GeneratedSources.Length);
static MetadataReference GetSurveyPromptMetadataReference(Compilation currentCompilation)
{
var updatedCompilation = currentCompilation.RemoveAllSyntaxTrees()
.WithAssemblyName("SurveyPromptAssembly")
.AddSyntaxTrees(CSharpSyntaxTree.ParseText(Microsoft.CodeAnalysis.Text.SourceText.From(@"
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Rendering;
namespace SurveyPromptRootNamspace;
public class SurveyPrompt : ComponentBase
{
protected override void BuildRenderTree(RenderTreeBuilder builder) {}
}", System.Text.Encoding.UTF8)));
var stream = new MemoryStream();
var emitResult = updatedCompilation.Emit(stream);
Assert.True(emitResult.Success);
stream.Position = 0;
return MetadataReference.CreateFromStream(stream);
}
}
[Fact]
public async Task IncrementalCompilation_RazorFiles_CssScopeRemoved()
{
// Compile with CssScope set.
var project = CreateTestProject(new()
{
["Pages/Index.razor"] = "<h1>Hello world</h1>",
});
var compilation = await project.GetCompilationAsync();
var (driver, _, options) = await GetDriverWithAdditionalTextAndProviderAsync(project, static options =>
{
options.AdditionalTextOptions["Pages/Index.razor"]["build_metadata.AdditionalFiles.CssScope"] = "test-css-scope";
});
var result = RunGenerator(compilation!, ref driver);
result.Diagnostics.Verify();
// CSS isolation is enabled.
Assert.Contains("<h1 test-css-scope>Hello world</h1>", result.GeneratedSources.Single().SourceText.ToString());
// Unset CssScope.
options = options.Clone();
options.AdditionalTextOptions["Pages/Index.razor"].Options.Remove("build_metadata.AdditionalFiles.CssScope");
driver = driver.WithUpdatedAnalyzerConfigOptions(options);
result = RunGenerator(compilation!, ref driver);
result.Diagnostics.Verify();
// CSS isolation is disabled.
Assert.Contains("<h1>Hello world</h1>", result.GeneratedSources.Single().SourceText.ToString());
}
[Fact]
public async Task SourceGenerator_CshtmlFiles_Works()
{
// Arrange
var project = CreateTestProject(new()
{
["Pages/Index.cshtml"] = "<h1>Hello world</h1>",
["Views/Shared/_Layout.cshtml"] = "<h1>Layout</h1>",
});
var compilation = await project.GetCompilationAsync();
var (driver, _, _) = await GetDriverWithAdditionalTextAndProviderAsync(project, trackSteps: true);
var result = RunGenerator(compilation!, ref driver)
.VerifyPageOutput(
@"#pragma checksum ""Pages/Index.cshtml"" ""{ff1816ec-aa5e-4d10-87f7-6f4963833460}"" ""6b5db227a6aa2228c777b0771108b184b1fc5df3""
// <auto-generated/>
#pragma warning disable 1591
[assembly: global::Microsoft.AspNetCore.Razor.Hosting.RazorCompiledItemAttribute(typeof(AspNetCoreGeneratedDocument.Pages_Index), @""mvc.1.0.view"", @""/Pages/Index.cshtml"")]
namespace AspNetCoreGeneratedDocument
{
#line default
using global::System;
using global::System.Collections.Generic;
using global::System.Linq;
using global::System.Threading.Tasks;
using global::Microsoft.AspNetCore.Mvc;
using global::Microsoft.AspNetCore.Mvc.Rendering;
using global::Microsoft.AspNetCore.Mvc.ViewFeatures;
#line default
#line hidden
[global::Microsoft.AspNetCore.Razor.Hosting.RazorCompiledItemMetadataAttribute(""Identifier"", ""/Pages/Index.cshtml"")]
[global::System.Runtime.CompilerServices.CreateNewOnMetadataUpdateAttribute]
#nullable restore
internal sealed class Pages_Index : global::Microsoft.AspNetCore.Mvc.Razor.RazorPage<dynamic>
#nullable disable
{
#pragma warning disable 1998
public async override global::System.Threading.Tasks.Task ExecuteAsync()
{
WriteLiteral(""<h1>Hello world</h1>"");
}
#pragma warning restore 1998
#nullable restore
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.ViewFeatures.IModelExpressionProvider ModelExpressionProvider { get; private set; } = default!;
#nullable disable
#nullable restore
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.IUrlHelper Url { get; private set; } = default!;
#nullable disable
#nullable restore
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.IViewComponentHelper Component { get; private set; } = default!;
#nullable disable
#nullable restore
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.Rendering.IJsonHelper Json { get; private set; } = default!;
#nullable disable
#nullable restore
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper<dynamic> Html { get; private set; } = default!;
#nullable disable
}
}
#pragma warning restore 1591
",
@"#pragma checksum ""Views/Shared/_Layout.cshtml"" ""{ff1816ec-aa5e-4d10-87f7-6f4963833460}"" ""99939b97abcdd846bd8ea59f7a08dacbe060cb3e""
// <auto-generated/>
#pragma warning disable 1591
[assembly: global::Microsoft.AspNetCore.Razor.Hosting.RazorCompiledItemAttribute(typeof(AspNetCoreGeneratedDocument.Views_Shared__Layout), @""mvc.1.0.view"", @""/Views/Shared/_Layout.cshtml"")]
namespace AspNetCoreGeneratedDocument
{
#line default
using global::System;
using global::System.Collections.Generic;
using global::System.Linq;
using global::System.Threading.Tasks;
using global::Microsoft.AspNetCore.Mvc;
using global::Microsoft.AspNetCore.Mvc.Rendering;
using global::Microsoft.AspNetCore.Mvc.ViewFeatures;
#line default
#line hidden
[global::Microsoft.AspNetCore.Razor.Hosting.RazorCompiledItemMetadataAttribute(""Identifier"", ""/Views/Shared/_Layout.cshtml"")]
[global::System.Runtime.CompilerServices.CreateNewOnMetadataUpdateAttribute]
#nullable restore
internal sealed class Views_Shared__Layout : global::Microsoft.AspNetCore.Mvc.Razor.RazorPage<dynamic>
#nullable disable
{
#pragma warning disable 1998
public async override global::System.Threading.Tasks.Task ExecuteAsync()
{
WriteLiteral(""<h1>Layout</h1>"");
}
#pragma warning restore 1998
#nullable restore
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.ViewFeatures.IModelExpressionProvider ModelExpressionProvider { get; private set; } = default!;
#nullable disable
#nullable restore
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.IUrlHelper Url { get; private set; } = default!;
#nullable disable
#nullable restore
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.IViewComponentHelper Component { get; private set; } = default!;
#nullable disable
#nullable restore
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.Rendering.IJsonHelper Json { get; private set; } = default!;
#nullable disable
#nullable restore
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper<dynamic> Html { get; private set; } = default!;
#nullable disable
}
}
#pragma warning restore 1591
");
Assert.Empty(result.Diagnostics);
Assert.Equal(2, result.GeneratedSources.Length);
// Verify all steps ran for initial generation
result.VerifyIncrementalSteps("RazorSourceGeneratorOptions", IncrementalStepRunReason.New);
result.VerifyIncrementalStepsMultiple("ParsedDocuments", IncrementalStepRunReason.New, IncrementalStepRunReason.New);
result.VerifyIncrementalSteps("TagHelpersFromCompilation", IncrementalStepRunReason.New);
result.VerifyIncrementalSteps("TagHelpersFromReferences", IncrementalStepRunReason.New);
result.VerifyIncrementalStepsMultiple("RewrittenTagHelpers", IncrementalStepRunReason.New, IncrementalStepRunReason.New);
result.VerifyIncrementalStepsMultiple("CheckedAndRewrittenTagHelpers", IncrementalStepRunReason.New, IncrementalStepRunReason.New);
result.VerifyIncrementalStepsMultiple("GeneratedCode", IncrementalStepRunReason.New, IncrementalStepRunReason.New);
result.VerifyIncrementalStepsMultiple("CSharpDocuments", IncrementalStepRunReason.New, IncrementalStepRunReason.New);
}
[Fact, WorkItem("https://github.com/dotnet/razor/issues/7049")]
public async Task SourceGenerator_CshtmlFiles_TagHelperInFunction()
{
// Arrange
var project = CreateTestProject(new()
{
["Pages/Index.cshtml"] = """
@addTagHelper *, TestProject
@{ await RenderMyRazor(); }
@functions {
async Task RenderMyRazor()
{
<email>first tag helper</email>
<email>
second tag helper
<email>nested tag helper</email>
</email>
}
}
""",
}, new()
{
["EmailTagHelper.cs"] = """
using Microsoft.AspNetCore.Razor.TagHelpers;
public class EmailTagHelper : TagHelper
{
public override void Process(TagHelperContext context, TagHelperOutput output)
{
output.TagName = "a";
}
}
"""
});
var compilation = await project.GetCompilationAsync();
var driver = await GetDriverAsync(project);
// Act
var result = RunGenerator(compilation!, ref driver);
// Assert
Assert.Empty(result.Diagnostics);
Assert.Single(result.GeneratedSources);
}
[Fact, WorkItem("https://github.com/dotnet/razor/issues/7049")]
public async Task SourceGenerator_CshtmlFiles_TagHelperInFunction_ManualSuppression()
{
// Arrange
var project = CreateTestProject(new()
{
["Pages/Index.cshtml"] = """
@addTagHelper *, TestProject
@{ await RenderMyRazor(); }
@functions {
#pragma warning disable 1998
async Task RenderMyRazor()
{
var lambdaWithUnnecessaryAsync1 = async () => { };
<email>first tag helper</email>
<email>
second tag helper
<email>nested tag helper</email>
</email>
var lambdaWithUnnecessaryAsync2 = async () => { };
}
}
""",
}, new()
{
["EmailTagHelper.cs"] = """
using Microsoft.AspNetCore.Razor.TagHelpers;
public class EmailTagHelper : TagHelper
{
public override void Process(TagHelperContext context, TagHelperOutput output)
{
output.TagName = "a";
}
}
"""
});
var compilation = await project.GetCompilationAsync();
var driver = await GetDriverAsync(project);
// Act
var result = RunGenerator(compilation!, ref driver);
// Assert
Assert.Empty(result.Diagnostics);
Assert.Single(result.GeneratedSources);
}
[Fact, WorkItem("https://github.com/dotnet/razor/issues/7049")]
public async Task SourceGenerator_CshtmlFiles_TagHelperInBody()
{
// Arrange
var project = CreateTestProject(new()
{
["Pages/Index.cshtml"] = """
@addTagHelper *, TestProject
<email>tag helper</email>
@section MySection {
<p>my section</p>
}
""",
}, new()
{
["EmailTagHelper.cs"] = """
using Microsoft.AspNetCore.Razor.TagHelpers;
public class EmailTagHelper : TagHelper
{
public override void Process(TagHelperContext context, TagHelperOutput output)
{
output.TagName = "a";
}
}
"""
});
var compilation = await project.GetCompilationAsync();
var driver = await GetDriverAsync(project);
// Act
var result = RunGenerator(compilation!, ref driver);
// Assert
Assert.Empty(result.Diagnostics);
Assert.Single(result.GeneratedSources);
}
[Fact]
public async Task SourceGenerator_CshtmlFiles_WhenMarkupChanges()
{
// Arrange
var project = CreateTestProject(new()
{
["Pages/Index.cshtml"] = "<h1>Hello world</h1>",
["Views/Shared/_Layout.cshtml"] = "<h1>Layout</h1>",
});
var compilation = await project.GetCompilationAsync();
var (driver, additionalTexts, _) = await GetDriverWithAdditionalTextAndProviderAsync(project, trackSteps: true);
var result = RunGenerator(compilation!, ref driver)
.VerifyPageOutput(
@"#pragma checksum ""Pages/Index.cshtml"" ""{ff1816ec-aa5e-4d10-87f7-6f4963833460}"" ""6b5db227a6aa2228c777b0771108b184b1fc5df3""
// <auto-generated/>
#pragma warning disable 1591
[assembly: global::Microsoft.AspNetCore.Razor.Hosting.RazorCompiledItemAttribute(typeof(AspNetCoreGeneratedDocument.Pages_Index), @""mvc.1.0.view"", @""/Pages/Index.cshtml"")]
namespace AspNetCoreGeneratedDocument
{
#line default
using global::System;
using global::System.Collections.Generic;
using global::System.Linq;
using global::System.Threading.Tasks;
using global::Microsoft.AspNetCore.Mvc;
using global::Microsoft.AspNetCore.Mvc.Rendering;
using global::Microsoft.AspNetCore.Mvc.ViewFeatures;
#line default
#line hidden
[global::Microsoft.AspNetCore.Razor.Hosting.RazorCompiledItemMetadataAttribute(""Identifier"", ""/Pages/Index.cshtml"")]
[global::System.Runtime.CompilerServices.CreateNewOnMetadataUpdateAttribute]
#nullable restore
internal sealed class Pages_Index : global::Microsoft.AspNetCore.Mvc.Razor.RazorPage<dynamic>
#nullable disable
{
#pragma warning disable 1998
public async override global::System.Threading.Tasks.Task ExecuteAsync()
{
WriteLiteral(""<h1>Hello world</h1>"");
}
#pragma warning restore 1998
#nullable restore
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.ViewFeatures.IModelExpressionProvider ModelExpressionProvider { get; private set; } = default!;
#nullable disable
#nullable restore
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.IUrlHelper Url { get; private set; } = default!;
#nullable disable
#nullable restore
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.IViewComponentHelper Component { get; private set; } = default!;
#nullable disable
#nullable restore
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.Rendering.IJsonHelper Json { get; private set; } = default!;
#nullable disable
#nullable restore
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper<dynamic> Html { get; private set; } = default!;
#nullable disable
}
}
#pragma warning restore 1591
",
@"#pragma checksum ""Views/Shared/_Layout.cshtml"" ""{ff1816ec-aa5e-4d10-87f7-6f4963833460}"" ""99939b97abcdd846bd8ea59f7a08dacbe060cb3e""
// <auto-generated/>
#pragma warning disable 1591
[assembly: global::Microsoft.AspNetCore.Razor.Hosting.RazorCompiledItemAttribute(typeof(AspNetCoreGeneratedDocument.Views_Shared__Layout), @""mvc.1.0.view"", @""/Views/Shared/_Layout.cshtml"")]
namespace AspNetCoreGeneratedDocument
{
#line default
using global::System;
using global::System.Collections.Generic;
using global::System.Linq;
using global::System.Threading.Tasks;
using global::Microsoft.AspNetCore.Mvc;
using global::Microsoft.AspNetCore.Mvc.Rendering;
using global::Microsoft.AspNetCore.Mvc.ViewFeatures;
#line default
#line hidden
[global::Microsoft.AspNetCore.Razor.Hosting.RazorCompiledItemMetadataAttribute(""Identifier"", ""/Views/Shared/_Layout.cshtml"")]
[global::System.Runtime.CompilerServices.CreateNewOnMetadataUpdateAttribute]
#nullable restore
internal sealed class Views_Shared__Layout : global::Microsoft.AspNetCore.Mvc.Razor.RazorPage<dynamic>
#nullable disable
{
#pragma warning disable 1998
public async override global::System.Threading.Tasks.Task ExecuteAsync()
{
WriteLiteral(""<h1>Layout</h1>"");
}
#pragma warning restore 1998
#nullable restore
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.ViewFeatures.IModelExpressionProvider ModelExpressionProvider { get; private set; } = default!;
#nullable disable
#nullable restore
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.IUrlHelper Url { get; private set; } = default!;
#nullable disable
#nullable restore
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.IViewComponentHelper Component { get; private set; } = default!;
#nullable disable
#nullable restore
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.Rendering.IJsonHelper Json { get; private set; } = default!;
#nullable disable
#nullable restore
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper<dynamic> Html { get; private set; } = default!;
#nullable disable
}
}
#pragma warning restore 1591
");
Assert.Empty(result.Diagnostics);
Assert.Equal(2, result.GeneratedSources.Length);
result = RunGenerator(compilation!, ref driver)
.VerifyOutputsMatch(result);
Assert.Empty(result.Diagnostics);
Assert.Equal(2, result.GeneratedSources.Length);
var updatedText = new TestAdditionalText("Views/Shared/_Layout.cshtml", SourceText.From("<h2>Updated Layout</h2>", Encoding.UTF8));
driver = driver.ReplaceAdditionalText(additionalTexts.First(f => f.Path == updatedText.Path), updatedText);
result = RunGenerator(compilation!, ref driver)
.VerifyOutputsMatch(result, (1,
@"#pragma checksum ""Views/Shared/_Layout.cshtml"" ""{ff1816ec-aa5e-4d10-87f7-6f4963833460}"" ""525ae8e5f9273913494d7a628b965837c601aed4""
// <auto-generated/>
#pragma warning disable 1591
[assembly: global::Microsoft.AspNetCore.Razor.Hosting.RazorCompiledItemAttribute(typeof(AspNetCoreGeneratedDocument.Views_Shared__Layout), @""mvc.1.0.view"", @""/Views/Shared/_Layout.cshtml"")]
namespace AspNetCoreGeneratedDocument
{
#line default
using global::System;
using global::System.Collections.Generic;
using global::System.Linq;
using global::System.Threading.Tasks;
using global::Microsoft.AspNetCore.Mvc;
using global::Microsoft.AspNetCore.Mvc.Rendering;
using global::Microsoft.AspNetCore.Mvc.ViewFeatures;
#line default
#line hidden
[global::Microsoft.AspNetCore.Razor.Hosting.RazorCompiledItemMetadataAttribute(""Identifier"", ""/Views/Shared/_Layout.cshtml"")]
[global::System.Runtime.CompilerServices.CreateNewOnMetadataUpdateAttribute]
#nullable restore
internal sealed class Views_Shared__Layout : global::Microsoft.AspNetCore.Mvc.Razor.RazorPage<dynamic>
#nullable disable
{
#pragma warning disable 1998
public async override global::System.Threading.Tasks.Task ExecuteAsync()
{
WriteLiteral(""<h2>Updated Layout</h2>"");
}
#pragma warning restore 1998
#nullable restore
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.ViewFeatures.IModelExpressionProvider ModelExpressionProvider { get; private set; } = default!;
#nullable disable
#nullable restore
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.IUrlHelper Url { get; private set; } = default!;
#nullable disable
#nullable restore
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.IViewComponentHelper Component { get; private set; } = default!;
#nullable disable
#nullable restore
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.Rendering.IJsonHelper Json { get; private set; } = default!;
#nullable disable
#nullable restore
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper<dynamic> Html { get; private set; } = default!;
#nullable disable
}
}
#pragma warning restore 1591
"));
Assert.Empty(result.Diagnostics);
Assert.Equal(2, result.GeneratedSources.Length);
// Verify that when Layout markup changes, only Layout steps re-run
result.VerifyIncrementalStepsMultiple("ParsedDocuments",
IncrementalStepRunReason.Cached, // Index unchanged
IncrementalStepRunReason.Modified); // Layout changed
result.VerifyIncrementalStepsMultiple("RewrittenTagHelpers",
IncrementalStepRunReason.Cached, // Index unchanged
IncrementalStepRunReason.Modified); // Layout changed
result.VerifyIncrementalStepsMultiple("CheckedAndRewrittenTagHelpers",
IncrementalStepRunReason.Cached, // Index unchanged
IncrementalStepRunReason.Modified); // Layout changed
result.VerifyIncrementalStepsMultiple("GeneratedCode",
IncrementalStepRunReason.Cached, // Index unchanged
IncrementalStepRunReason.Modified); // Layout changed
result.VerifyIncrementalStepsMultiple("CSharpDocuments",
IncrementalStepRunReason.Cached, // Index unchanged
IncrementalStepRunReason.Modified); // Layout changed
}
[ConditionalFact(typeof(IsEnglishLocal))]
public async Task SourceGenerator_CshtmlFiles_CSharpTypeChanges()
{
// Arrange
var project = CreateTestProject(new()
{
["Pages/Index.cshtml"] = "<h1>Hello world</h1>",
["Views/Shared/_Layout.cshtml"] = "<h1>Layout</h1>",
},
new()
{
["Person.cs"] = @"
public class Person
{
public string Name { get; set; }
}"
});
var compilation = await project.GetCompilationAsync();
var (driver, additionalTexts, _) = await GetDriverWithAdditionalTextAndProviderAsync(project, trackSteps: true);
var expectedDiagnostics = new DiagnosticDescription[]
{
// Person.cs(4,19): warning CS8618: Non-nullable property 'Name' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.
// public string Name { get; set; }
Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "Name").WithArguments("property", "Name").WithLocation(4, 19)
};
var result = RunGenerator(compilation!, ref driver, expectedDiagnostics)
.VerifyPageOutput(@"#pragma checksum ""Pages/Index.cshtml"" ""{ff1816ec-aa5e-4d10-87f7-6f4963833460}"" ""6b5db227a6aa2228c777b0771108b184b1fc5df3""
// <auto-generated/>
#pragma warning disable 1591
[assembly: global::Microsoft.AspNetCore.Razor.Hosting.RazorCompiledItemAttribute(typeof(AspNetCoreGeneratedDocument.Pages_Index), @""mvc.1.0.view"", @""/Pages/Index.cshtml"")]
namespace AspNetCoreGeneratedDocument
{
#line default
using global::System;
using global::System.Collections.Generic;
using global::System.Linq;
using global::System.Threading.Tasks;
using global::Microsoft.AspNetCore.Mvc;
using global::Microsoft.AspNetCore.Mvc.Rendering;
using global::Microsoft.AspNetCore.Mvc.ViewFeatures;
#line default
#line hidden
[global::Microsoft.AspNetCore.Razor.Hosting.RazorCompiledItemMetadataAttribute(""Identifier"", ""/Pages/Index.cshtml"")]
[global::System.Runtime.CompilerServices.CreateNewOnMetadataUpdateAttribute]
#nullable restore
internal sealed class Pages_Index : global::Microsoft.AspNetCore.Mvc.Razor.RazorPage<dynamic>
#nullable disable
{
#pragma warning disable 1998
public async override global::System.Threading.Tasks.Task ExecuteAsync()
{
WriteLiteral(""<h1>Hello world</h1>"");
}
#pragma warning restore 1998
#nullable restore
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.ViewFeatures.IModelExpressionProvider ModelExpressionProvider { get; private set; } = default!;
#nullable disable
#nullable restore
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.IUrlHelper Url { get; private set; } = default!;
#nullable disable
#nullable restore
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.IViewComponentHelper Component { get; private set; } = default!;
#nullable disable
#nullable restore
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.Rendering.IJsonHelper Json { get; private set; } = default!;
#nullable disable
#nullable restore
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper<dynamic> Html { get; private set; } = default!;
#nullable disable
}
}
#pragma warning restore 1591
",
@"#pragma checksum ""Views/Shared/_Layout.cshtml"" ""{ff1816ec-aa5e-4d10-87f7-6f4963833460}"" ""99939b97abcdd846bd8ea59f7a08dacbe060cb3e""
// <auto-generated/>
#pragma warning disable 1591
[assembly: global::Microsoft.AspNetCore.Razor.Hosting.RazorCompiledItemAttribute(typeof(AspNetCoreGeneratedDocument.Views_Shared__Layout), @""mvc.1.0.view"", @""/Views/Shared/_Layout.cshtml"")]
namespace AspNetCoreGeneratedDocument
{
#line default
using global::System;
using global::System.Collections.Generic;
using global::System.Linq;
using global::System.Threading.Tasks;
using global::Microsoft.AspNetCore.Mvc;
using global::Microsoft.AspNetCore.Mvc.Rendering;
using global::Microsoft.AspNetCore.Mvc.ViewFeatures;
#line default
#line hidden
[global::Microsoft.AspNetCore.Razor.Hosting.RazorCompiledItemMetadataAttribute(""Identifier"", ""/Views/Shared/_Layout.cshtml"")]
[global::System.Runtime.CompilerServices.CreateNewOnMetadataUpdateAttribute]
#nullable restore
internal sealed class Views_Shared__Layout : global::Microsoft.AspNetCore.Mvc.Razor.RazorPage<dynamic>
#nullable disable
{
#pragma warning disable 1998
public async override global::System.Threading.Tasks.Task ExecuteAsync()
{
WriteLiteral(""<h1>Layout</h1>"");
}
#pragma warning restore 1998
#nullable restore
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.ViewFeatures.IModelExpressionProvider ModelExpressionProvider { get; private set; } = default!;
#nullable disable
#nullable restore
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.IUrlHelper Url { get; private set; } = default!;
#nullable disable
#nullable restore
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.IViewComponentHelper Component { get; private set; } = default!;
#nullable disable
#nullable restore
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.Rendering.IJsonHelper Json { get; private set; } = default!;
#nullable disable
#nullable restore
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper<dynamic> Html { get; private set; } = default!;
#nullable disable
}
}
#pragma warning restore 1591
");
Assert.Empty(result.Diagnostics);
Assert.Equal(2, result.GeneratedSources.Length);
result = RunGenerator(compilation!, ref driver, expectedDiagnostics)
.VerifyOutputsMatch(result);
Assert.Empty(result.Diagnostics);
Assert.Equal(2, result.GeneratedSources.Length);
project = project.Documents.First().WithText(SourceText.From(@"
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
}", Encoding.UTF8)).Project;
compilation = await project.GetCompilationAsync();
result = RunGenerator(compilation!, ref driver, expectedDiagnostics)
.VerifyOutputsMatch(result);
Assert.Empty(result.Diagnostics);
Assert.Equal(2, result.GeneratedSources.Length);
// Verify that when C# type changes, only TagHelpersFromCompilation re-runs
result.VerifyIncrementalSteps("TagHelpersFromCompilation", IncrementalStepRunReason.Unchanged);
}
[Fact]
public async Task SourceGenerator_CshtmlFiles_NewTagHelper()
{
// Arrange
var project = CreateTestProject(new()
{
["Pages/Index.cshtml"] =
@"
@addTagHelper *, TestProject
<h2>Hello world</h2>",
["Views/Shared/_Layout.cshtml"] = "<h1>Layout</h1>",
});
var compilation = await project.GetCompilationAsync();
var (driver, additionalTexts, _) = await GetDriverWithAdditionalTextAndProviderAsync(project, trackSteps: true);
var result = RunGenerator(compilation!, ref driver)
.VerifyPageOutput(
@"#pragma checksum ""Pages/Index.cshtml"" ""{ff1816ec-aa5e-4d10-87f7-6f4963833460}"" ""5d59ecd7b7cf7355d7f60234988be34b81a8b614""
// <auto-generated/>
#pragma warning disable 1591
[assembly: global::Microsoft.AspNetCore.Razor.Hosting.RazorCompiledItemAttribute(typeof(AspNetCoreGeneratedDocument.Pages_Index), @""mvc.1.0.view"", @""/Pages/Index.cshtml"")]
namespace AspNetCoreGeneratedDocument
{
#line default
using global::System;
using global::System.Collections.Generic;
using global::System.Linq;
using global::System.Threading.Tasks;
using global::Microsoft.AspNetCore.Mvc;
using global::Microsoft.AspNetCore.Mvc.Rendering;
using global::Microsoft.AspNetCore.Mvc.ViewFeatures;
#line default
#line hidden
[global::Microsoft.AspNetCore.Razor.Hosting.RazorCompiledItemMetadataAttribute(""Identifier"", ""/Pages/Index.cshtml"")]
[global::System.Runtime.CompilerServices.CreateNewOnMetadataUpdateAttribute]
#nullable restore
internal sealed class Pages_Index : global::Microsoft.AspNetCore.Mvc.Razor.RazorPage<dynamic>
#nullable disable
{
#pragma warning disable 1998
public async override global::System.Threading.Tasks.Task ExecuteAsync()
{
WriteLiteral(""\r\n"");
WriteLiteral(""<h2>Hello world</h2>"");
}
#pragma warning restore 1998
#nullable restore
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.ViewFeatures.IModelExpressionProvider ModelExpressionProvider { get; private set; } = default!;
#nullable disable
#nullable restore
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.IUrlHelper Url { get; private set; } = default!;
#nullable disable
#nullable restore
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.IViewComponentHelper Component { get; private set; } = default!;
#nullable disable
#nullable restore
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.Rendering.IJsonHelper Json { get; private set; } = default!;
#nullable disable
#nullable restore
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper<dynamic> Html { get; private set; } = default!;
#nullable disable
}
}
#pragma warning restore 1591
",
@"#pragma checksum ""Views/Shared/_Layout.cshtml"" ""{ff1816ec-aa5e-4d10-87f7-6f4963833460}"" ""99939b97abcdd846bd8ea59f7a08dacbe060cb3e""
// <auto-generated/>
#pragma warning disable 1591
[assembly: global::Microsoft.AspNetCore.Razor.Hosting.RazorCompiledItemAttribute(typeof(AspNetCoreGeneratedDocument.Views_Shared__Layout), @""mvc.1.0.view"", @""/Views/Shared/_Layout.cshtml"")]
namespace AspNetCoreGeneratedDocument
{
#line default
using global::System;
using global::System.Collections.Generic;
using global::System.Linq;
using global::System.Threading.Tasks;
using global::Microsoft.AspNetCore.Mvc;
using global::Microsoft.AspNetCore.Mvc.Rendering;
using global::Microsoft.AspNetCore.Mvc.ViewFeatures;
#line default
#line hidden
[global::Microsoft.AspNetCore.Razor.Hosting.RazorCompiledItemMetadataAttribute(""Identifier"", ""/Views/Shared/_Layout.cshtml"")]
[global::System.Runtime.CompilerServices.CreateNewOnMetadataUpdateAttribute]
#nullable restore
internal sealed class Views_Shared__Layout : global::Microsoft.AspNetCore.Mvc.Razor.RazorPage<dynamic>
#nullable disable
{
#pragma warning disable 1998
public async override global::System.Threading.Tasks.Task ExecuteAsync()
{
WriteLiteral(""<h1>Layout</h1>"");
}
#pragma warning restore 1998
#nullable restore
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.ViewFeatures.IModelExpressionProvider ModelExpressionProvider { get; private set; } = default!;
#nullable disable
#nullable restore
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.IUrlHelper Url { get; private set; } = default!;
#nullable disable
#nullable restore
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.IViewComponentHelper Component { get; private set; } = default!;
#nullable disable
#nullable restore
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.Rendering.IJsonHelper Json { get; private set; } = default!;
#nullable disable
#nullable restore
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper<dynamic> Html { get; private set; } = default!;
#nullable disable
}
}
#pragma warning restore 1591
");
Assert.Empty(result.Diagnostics);
Assert.Equal(2, result.GeneratedSources.Length);
result = RunGenerator(compilation!, ref driver)
.VerifyOutputsMatch(result);
Assert.Empty(result.Diagnostics);
Assert.Equal(2, result.GeneratedSources.Length);
project = project.AddDocument("HeaderTagHelper.cs", SourceText.From(@"
using Microsoft.AspNetCore.Razor.TagHelpers;
namespace MyApp;
[HtmlTargetElement(""h2"")]
public class HeaderTagHelper : TagHelper
{
public override int Order => 0;
public override void Process(TagHelperContext context, TagHelperOutput output)
{
output.Attributes.Add(""role"", ""heading"");
}
}", Encoding.UTF8)).Project;
compilation = await project.GetCompilationAsync();
result = RunGenerator(compilation!, ref driver);
Assert.Empty(result.Diagnostics);
Assert.Equal(2, result.GeneratedSources.Length);
// Verify that when new tag helper is added, tag helper discovery and rewrite steps re-run
result.VerifyIncrementalSteps("TagHelpersFromCompilation", IncrementalStepRunReason.Modified); // New tag helper discovered
result.VerifyIncrementalStepsMultiple("CheckedAndRewrittenTagHelpers",
IncrementalStepRunReason.Modified, // Index - new tag helper affects h2
IncrementalStepRunReason.Unchanged); // Layout - doesn't use h2
result.VerifyIncrementalStepsMultiple("GeneratedCode",
IncrementalStepRunReason.Modified, // Index - re-generated with tag helper
IncrementalStepRunReason.Cached); // Layout - unchanged
}
[Fact]
public async Task SourceGenerator_CshtmlFiles_RazorDiagnostics_Fixed()
{
// Arrange
var project = CreateTestProject(new()
{
["Pages/Index.cshtml"] =
@"
@{
<h1>Malformed h1
}
</h1>",
["Views/Shared/_Layout.cshtml"] = "<h1>Layout</h1>",
});
var compilation = await project.GetCompilationAsync();
var (driver, additionalTexts) = await GetDriverWithAdditionalTextAsync(project);
var result = RunGenerator(compilation!, ref driver)
.VerifyPageOutput(
@"#pragma checksum ""Pages/Index.cshtml"" ""{ff1816ec-aa5e-4d10-87f7-6f4963833460}"" ""8e83eb537e49cadda4aff01de0bd33aa716fc633""
// <auto-generated/>
#pragma warning disable 1591
[assembly: global::Microsoft.AspNetCore.Razor.Hosting.RazorCompiledItemAttribute(typeof(AspNetCoreGeneratedDocument.Pages_Index), @""mvc.1.0.view"", @""/Pages/Index.cshtml"")]
namespace AspNetCoreGeneratedDocument
{
#line default
using global::System;
using global::System.Collections.Generic;
using global::System.Linq;
using global::System.Threading.Tasks;
using global::Microsoft.AspNetCore.Mvc;
using global::Microsoft.AspNetCore.Mvc.Rendering;
using global::Microsoft.AspNetCore.Mvc.ViewFeatures;
#line default
#line hidden
[global::Microsoft.AspNetCore.Razor.Hosting.RazorCompiledItemMetadataAttribute(""Identifier"", ""/Pages/Index.cshtml"")]
[global::System.Runtime.CompilerServices.CreateNewOnMetadataUpdateAttribute]
#nullable restore
internal sealed class Pages_Index : global::Microsoft.AspNetCore.Mvc.Razor.RazorPage<dynamic>
#nullable disable
{
#pragma warning disable 1998
public async override global::System.Threading.Tasks.Task ExecuteAsync()
{
WriteLiteral(""\r\n"");
WriteLiteral(""<h1>Malformed h1\r\n}\r\n</h1>"");
}
#pragma warning restore 1998
#nullable restore
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.ViewFeatures.IModelExpressionProvider ModelExpressionProvider { get; private set; } = default!;
#nullable disable
#nullable restore
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.IUrlHelper Url { get; private set; } = default!;
#nullable disable
#nullable restore
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.IViewComponentHelper Component { get; private set; } = default!;
#nullable disable
#nullable restore
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.Rendering.IJsonHelper Json { get; private set; } = default!;
#nullable disable
#nullable restore
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper<dynamic> Html { get; private set; } = default!;
#nullable disable
}
}
#pragma warning restore 1591
",
@"#pragma checksum ""Views/Shared/_Layout.cshtml"" ""{ff1816ec-aa5e-4d10-87f7-6f4963833460}"" ""99939b97abcdd846bd8ea59f7a08dacbe060cb3e""
// <auto-generated/>
#pragma warning disable 1591
[assembly: global::Microsoft.AspNetCore.Razor.Hosting.RazorCompiledItemAttribute(typeof(AspNetCoreGeneratedDocument.Views_Shared__Layout), @""mvc.1.0.view"", @""/Views/Shared/_Layout.cshtml"")]
namespace AspNetCoreGeneratedDocument
{
#line default
using global::System;
using global::System.Collections.Generic;
using global::System.Linq;
using global::System.Threading.Tasks;
using global::Microsoft.AspNetCore.Mvc;
using global::Microsoft.AspNetCore.Mvc.Rendering;
using global::Microsoft.AspNetCore.Mvc.ViewFeatures;
#line default
#line hidden
[global::Microsoft.AspNetCore.Razor.Hosting.RazorCompiledItemMetadataAttribute(""Identifier"", ""/Views/Shared/_Layout.cshtml"")]
[global::System.Runtime.CompilerServices.CreateNewOnMetadataUpdateAttribute]
#nullable restore
internal sealed class Views_Shared__Layout : global::Microsoft.AspNetCore.Mvc.Razor.RazorPage<dynamic>
#nullable disable
{
#pragma warning disable 1998
public async override global::System.Threading.Tasks.Task ExecuteAsync()
{
WriteLiteral(""<h1>Layout</h1>"");
}
#pragma warning restore 1998
#nullable restore
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.ViewFeatures.IModelExpressionProvider ModelExpressionProvider { get; private set; } = default!;
#nullable disable
#nullable restore
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.IUrlHelper Url { get; private set; } = default!;
#nullable disable
#nullable restore
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.IViewComponentHelper Component { get; private set; } = default!;
#nullable disable
#nullable restore
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.Rendering.IJsonHelper Json { get; private set; } = default!;
#nullable disable
#nullable restore
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper<dynamic> Html { get; private set; } = default!;
#nullable disable
}
}
#pragma warning restore 1591
");
var diagnostic = Assert.Single(result.Diagnostics);
Assert.Equal("RZ1006", diagnostic.Id);
Assert.Equal(2, result.GeneratedSources.Length);
var updatedText = new TestAdditionalText("Pages/Index.cshtml", SourceText.From("<h1>Fixed header</h1>", Encoding.UTF8));
driver = driver.ReplaceAdditionalText(additionalTexts.First(f => f.Path == updatedText.Path), updatedText);
result = RunGenerator(compilation!, ref driver)
.VerifyOutputsMatch(result, (0,
@"#pragma checksum ""Pages/Index.cshtml"" ""{ff1816ec-aa5e-4d10-87f7-6f4963833460}"" ""26b4fe0ac8d17fb3e02d5cba709603ceba9ae3ef""
// <auto-generated/>
#pragma warning disable 1591
[assembly: global::Microsoft.AspNetCore.Razor.Hosting.RazorCompiledItemAttribute(typeof(AspNetCoreGeneratedDocument.Pages_Index), @""mvc.1.0.view"", @""/Pages/Index.cshtml"")]
namespace AspNetCoreGeneratedDocument
{
#line default
using global::System;
using global::System.Collections.Generic;
using global::System.Linq;
using global::System.Threading.Tasks;
using global::Microsoft.AspNetCore.Mvc;
using global::Microsoft.AspNetCore.Mvc.Rendering;
using global::Microsoft.AspNetCore.Mvc.ViewFeatures;
#line default
#line hidden
[global::Microsoft.AspNetCore.Razor.Hosting.RazorCompiledItemMetadataAttribute(""Identifier"", ""/Pages/Index.cshtml"")]
[global::System.Runtime.CompilerServices.CreateNewOnMetadataUpdateAttribute]
#nullable restore
internal sealed class Pages_Index : global::Microsoft.AspNetCore.Mvc.Razor.RazorPage<dynamic>
#nullable disable
{
#pragma warning disable 1998
public async override global::System.Threading.Tasks.Task ExecuteAsync()
{
WriteLiteral(""<h1>Fixed header</h1>"");
}
#pragma warning restore 1998
#nullable restore
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.ViewFeatures.IModelExpressionProvider ModelExpressionProvider { get; private set; } = default!;
#nullable disable
#nullable restore
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.IUrlHelper Url { get; private set; } = default!;
#nullable disable
#nullable restore
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.IViewComponentHelper Component { get; private set; } = default!;
#nullable disable
#nullable restore
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.Rendering.IJsonHelper Json { get; private set; } = default!;
#nullable disable
#nullable restore
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper<dynamic> Html { get; private set; } = default!;
#nullable disable
}
}
#pragma warning restore 1591
"));
Assert.Empty(result.Diagnostics);
Assert.Equal(2, result.GeneratedSources.Length);
}
[Fact]
public async Task SourceGenerator_CshtmlFiles_RazorDiagnostics_Introduced()
{
// Arrange
var project = CreateTestProject(new()
{
["Pages/Index.cshtml"] = "<h1>Valid h1</h1>",
["Views/Shared/_Layout.cshtml"] = "<h1>Layout</h1>",
});
var compilation = await project.GetCompilationAsync();
var (driver, additionalTexts) = await GetDriverWithAdditionalTextAsync(project);
var result = RunGenerator(compilation!, ref driver)
.VerifyPageOutput(
@"#pragma checksum ""Pages/Index.cshtml"" ""{ff1816ec-aa5e-4d10-87f7-6f4963833460}"" ""7f59d2951cc70dc08bc4e89b90bd3c5ba68459b9""
// <auto-generated/>
#pragma warning disable 1591
[assembly: global::Microsoft.AspNetCore.Razor.Hosting.RazorCompiledItemAttribute(typeof(AspNetCoreGeneratedDocument.Pages_Index), @""mvc.1.0.view"", @""/Pages/Index.cshtml"")]
namespace AspNetCoreGeneratedDocument
{
#line default
using global::System;
using global::System.Collections.Generic;
using global::System.Linq;
using global::System.Threading.Tasks;
using global::Microsoft.AspNetCore.Mvc;
using global::Microsoft.AspNetCore.Mvc.Rendering;
using global::Microsoft.AspNetCore.Mvc.ViewFeatures;
#line default
#line hidden
[global::Microsoft.AspNetCore.Razor.Hosting.RazorCompiledItemMetadataAttribute(""Identifier"", ""/Pages/Index.cshtml"")]
[global::System.Runtime.CompilerServices.CreateNewOnMetadataUpdateAttribute]
#nullable restore
internal sealed class Pages_Index : global::Microsoft.AspNetCore.Mvc.Razor.RazorPage<dynamic>
#nullable disable
{
#pragma warning disable 1998
public async override global::System.Threading.Tasks.Task ExecuteAsync()
{
WriteLiteral(""<h1>Valid h1</h1>"");
}
#pragma warning restore 1998
#nullable restore
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.ViewFeatures.IModelExpressionProvider ModelExpressionProvider { get; private set; } = default!;
#nullable disable
#nullable restore
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.IUrlHelper Url { get; private set; } = default!;
#nullable disable
#nullable restore
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.IViewComponentHelper Component { get; private set; } = default!;
#nullable disable
#nullable restore
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.Rendering.IJsonHelper Json { get; private set; } = default!;
#nullable disable
#nullable restore
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper<dynamic> Html { get; private set; } = default!;
#nullable disable
}
}
#pragma warning restore 1591
",
@"#pragma checksum ""Views/Shared/_Layout.cshtml"" ""{ff1816ec-aa5e-4d10-87f7-6f4963833460}"" ""99939b97abcdd846bd8ea59f7a08dacbe060cb3e""
// <auto-generated/>
#pragma warning disable 1591
[assembly: global::Microsoft.AspNetCore.Razor.Hosting.RazorCompiledItemAttribute(typeof(AspNetCoreGeneratedDocument.Views_Shared__Layout), @""mvc.1.0.view"", @""/Views/Shared/_Layout.cshtml"")]
namespace AspNetCoreGeneratedDocument
{
#line default
using global::System;
using global::System.Collections.Generic;
using global::System.Linq;
using global::System.Threading.Tasks;
using global::Microsoft.AspNetCore.Mvc;
using global::Microsoft.AspNetCore.Mvc.Rendering;
using global::Microsoft.AspNetCore.Mvc.ViewFeatures;
#line default
#line hidden
[global::Microsoft.AspNetCore.Razor.Hosting.RazorCompiledItemMetadataAttribute(""Identifier"", ""/Views/Shared/_Layout.cshtml"")]
[global::System.Runtime.CompilerServices.CreateNewOnMetadataUpdateAttribute]
#nullable restore
internal sealed class Views_Shared__Layout : global::Microsoft.AspNetCore.Mvc.Razor.RazorPage<dynamic>
#nullable disable
{
#pragma warning disable 1998
public async override global::System.Threading.Tasks.Task ExecuteAsync()
{
WriteLiteral(""<h1>Layout</h1>"");
}
#pragma warning restore 1998
#nullable restore
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.ViewFeatures.IModelExpressionProvider ModelExpressionProvider { get; private set; } = default!;
#nullable disable
#nullable restore
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.IUrlHelper Url { get; private set; } = default!;
#nullable disable
#nullable restore
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.IViewComponentHelper Component { get; private set; } = default!;
#nullable disable
#nullable restore
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.Rendering.IJsonHelper Json { get; private set; } = default!;
#nullable disable
#nullable restore
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper<dynamic> Html { get; private set; } = default!;
#nullable disable
}
}
#pragma warning restore 1591
");
Assert.Empty(result.Diagnostics);
Assert.Equal(2, result.GeneratedSources.Length);
var updatedText = new TestAdditionalText("Pages/Index.cshtml", SourceText.From(@"
@{
<h1>Malformed h1
}
</h1>", Encoding.UTF8));
driver = driver.ReplaceAdditionalText(additionalTexts.First(f => f.Path == updatedText.Path), updatedText);
result = RunGenerator(compilation!, ref driver)
.VerifyOutputsMatch(result, (0,
@"#pragma checksum ""Pages/Index.cshtml"" ""{ff1816ec-aa5e-4d10-87f7-6f4963833460}"" ""d088006c7620c4503e95ef0b4cd1dbc82362bf8b""
// <auto-generated/>
#pragma warning disable 1591
[assembly: global::Microsoft.AspNetCore.Razor.Hosting.RazorCompiledItemAttribute(typeof(AspNetCoreGeneratedDocument.Pages_Index), @""mvc.1.0.view"", @""/Pages/Index.cshtml"")]
namespace AspNetCoreGeneratedDocument
{
#line default
using global::System;
using global::System.Collections.Generic;
using global::System.Linq;
using global::System.Threading.Tasks;
using global::Microsoft.AspNetCore.Mvc;
using global::Microsoft.AspNetCore.Mvc.Rendering;
using global::Microsoft.AspNetCore.Mvc.ViewFeatures;
#line default
#line hidden
[global::Microsoft.AspNetCore.Razor.Hosting.RazorCompiledItemMetadataAttribute(""Identifier"", ""/Pages/Index.cshtml"")]
[global::System.Runtime.CompilerServices.CreateNewOnMetadataUpdateAttribute]
#nullable restore
internal sealed class Pages_Index : global::Microsoft.AspNetCore.Mvc.Razor.RazorPage<dynamic>
#nullable disable
{
#pragma warning disable 1998
public async override global::System.Threading.Tasks.Task ExecuteAsync()
{
WriteLiteral(""\r\n"");
WriteLiteral(""<h1>Malformed h1\r\n}\r\n</h1>"");
}
#pragma warning restore 1998
#nullable restore
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.ViewFeatures.IModelExpressionProvider ModelExpressionProvider { get; private set; } = default!;
#nullable disable
#nullable restore
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.IUrlHelper Url { get; private set; } = default!;
#nullable disable
#nullable restore
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.IViewComponentHelper Component { get; private set; } = default!;
#nullable disable
#nullable restore
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.Rendering.IJsonHelper Json { get; private set; } = default!;
#nullable disable
#nullable restore
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper<dynamic> Html { get; private set; } = default!;
#nullable disable
}
}
#pragma warning restore 1591
"));
var diagnostic = Assert.Single(result.Diagnostics);
Assert.Equal("RZ1006", diagnostic.Id);
Assert.Equal(2, result.GeneratedSources.Length);
}
[Fact, WorkItem("https://github.com/dotnet/razor/issues/8281")]
public async Task SourceGenerator_CshtmlFiles_ViewComponentTagHelper()
{
// Arrange
var project = CreateTestProject(new()
{
["Views/Home/Index.cshtml"] = """
@addTagHelper *, TestProject
<vc:test first-name="Jan" />
""",
}, new()
{
["TestViewComponent.cs"] = """
public class TestViewComponent
{
public string Invoke(string firstName)
{
return firstName;
}
}
""",
});
var compilation = await project.GetCompilationAsync();
var driver = await GetDriverAsync(project);
// Act
var result = RunGenerator(compilation!, ref driver);
// Assert
result.VerifyPageOutput(
"""
#pragma checksum "Views/Home/Index.cshtml" "{ff1816ec-aa5e-4d10-87f7-6f4963833460}" "209ff2a910aa467bb7942ed3e6cb586652327a44"
// <auto-generated/>
#pragma warning disable 1591
[assembly: global::Microsoft.AspNetCore.Razor.Hosting.RazorCompiledItemAttribute(typeof(AspNetCoreGeneratedDocument.Views_Home_Index), @"mvc.1.0.view", @"/Views/Home/Index.cshtml")]
namespace AspNetCoreGeneratedDocument
{
#line default
using global::System;
using global::System.Collections.Generic;
using global::System.Linq;
using global::System.Threading.Tasks;
using global::Microsoft.AspNetCore.Mvc;
using global::Microsoft.AspNetCore.Mvc.Rendering;
using global::Microsoft.AspNetCore.Mvc.ViewFeatures;
#line default
#line hidden
[global::Microsoft.AspNetCore.Razor.Hosting.RazorCompiledItemMetadataAttribute("Identifier", "/Views/Home/Index.cshtml")]
[global::System.Runtime.CompilerServices.CreateNewOnMetadataUpdateAttribute]
#nullable restore
internal sealed class Views_Home_Index : global::Microsoft.AspNetCore.Mvc.Razor.RazorPage<dynamic>
#nullable disable
{
#line hidden
#pragma warning disable 0649
private global::Microsoft.AspNetCore.Razor.Runtime.TagHelpers.TagHelperExecutionContext __tagHelperExecutionContext;
#pragma warning restore 0649
private global::Microsoft.AspNetCore.Razor.Runtime.TagHelpers.TagHelperRunner __tagHelperRunner = new global::Microsoft.AspNetCore.Razor.Runtime.TagHelpers.TagHelperRunner();
#pragma warning disable 0169
private string __tagHelperStringValueBuffer;
#pragma warning restore 0169
private global::Microsoft.AspNetCore.Razor.Runtime.TagHelpers.TagHelperScopeManager __backed__tagHelperScopeManager = null;
private global::Microsoft.AspNetCore.Razor.Runtime.TagHelpers.TagHelperScopeManager __tagHelperScopeManager
{
get
{
if (__backed__tagHelperScopeManager == null)
{
__backed__tagHelperScopeManager = new global::Microsoft.AspNetCore.Razor.Runtime.TagHelpers.TagHelperScopeManager(StartTagHelperWritingScope, EndTagHelperWritingScope);
}
return __backed__tagHelperScopeManager;
}
}
private global::AspNetCoreGeneratedDocument.Views_Home_Index.__Generated__TestViewComponentTagHelper __TestViewComponentTagHelper;
#pragma warning disable 1998
public async override global::System.Threading.Tasks.Task ExecuteAsync()
{
__tagHelperExecutionContext = __tagHelperScopeManager.Begin("vc:test", global::Microsoft.AspNetCore.Razor.TagHelpers.TagMode.SelfClosing, "test", async() => {
}
);
__TestViewComponentTagHelper = CreateTagHelper<global::AspNetCoreGeneratedDocument.Views_Home_Index.__Generated__TestViewComponentTagHelper>();
__tagHelperExecutionContext.Add(__TestViewComponentTagHelper);
BeginWriteTagHelperAttribute();
WriteLiteral("Jan");
__tagHelperStringValueBuffer = EndWriteTagHelperAttribute();
__TestViewComponentTagHelper.firstName = __tagHelperStringValueBuffer;
__tagHelperExecutionContext.AddTagHelperAttribute("first-name", __TestViewComponentTagHelper.firstName, global::Microsoft.AspNetCore.Razor.TagHelpers.HtmlAttributeValueStyle.DoubleQuotes);
await __tagHelperRunner.RunAsync(__tagHelperExecutionContext);
if (!__tagHelperExecutionContext.Output.IsContentModified)
{
await __tagHelperExecutionContext.SetOutputContentAsync();
}
Write(__tagHelperExecutionContext.Output);
__tagHelperExecutionContext = __tagHelperScopeManager.End();
}
#pragma warning restore 1998
#nullable restore
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.ViewFeatures.IModelExpressionProvider ModelExpressionProvider { get; private set; } = default!;
#nullable disable
#nullable restore
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.IUrlHelper Url { get; private set; } = default!;
#nullable disable
#nullable restore
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.IViewComponentHelper Component { get; private set; } = default!;
#nullable disable
#nullable restore
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.Rendering.IJsonHelper Json { get; private set; } = default!;
#nullable disable
#nullable restore
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper<dynamic> Html { get; private set; } = default!;
#nullable disable
[Microsoft.AspNetCore.Razor.TagHelpers.HtmlTargetElementAttribute("vc:test")]
public class __Generated__TestViewComponentTagHelper : Microsoft.AspNetCore.Razor.TagHelpers.TagHelper
{
private readonly global::Microsoft.AspNetCore.Mvc.IViewComponentHelper __helper = null;
public __Generated__TestViewComponentTagHelper(global::Microsoft.AspNetCore.Mvc.IViewComponentHelper helper)
{
__helper = helper;
}
[Microsoft.AspNetCore.Razor.TagHelpers.HtmlAttributeNotBoundAttribute, global::Microsoft.AspNetCore.Mvc.ViewFeatures.ViewContextAttribute]
public global::Microsoft.AspNetCore.Mvc.Rendering.ViewContext ViewContext { get; set; }
public System.String firstName { get; set; }
public override async global::System.Threading.Tasks.Task ProcessAsync(Microsoft.AspNetCore.Razor.TagHelpers.TagHelperContext __context, Microsoft.AspNetCore.Razor.TagHelpers.TagHelperOutput __output)
{
(__helper as global::Microsoft.AspNetCore.Mvc.ViewFeatures.IViewContextAware)?.Contextualize(ViewContext);
var __helperContent = await __helper.InvokeAsync("Test", ProcessInvokeAsyncArgs(__context));
__output.TagName = null;
__output.Content.SetHtmlContent(__helperContent);
}
private Dictionary<string, object> ProcessInvokeAsyncArgs(Microsoft.AspNetCore.Razor.TagHelpers.TagHelperContext __context)
{
Dictionary<string, object> args = new Dictionary<string, object>();
if (__context.AllAttributes.ContainsName("first-name"))
{
args[nameof(firstName)] = firstName;
}
return args;
}
}
}
}
#pragma warning restore 1591
""");
Assert.Empty(result.Diagnostics);
Assert.Single(result.GeneratedSources);
}
[Fact, WorkItem("https://github.com/dotnet/razor/issues/7914")]
public async Task SourceGenerator_UppercaseRazor_GeneratesComponent()
{
var project = CreateTestProject(new()
{
["Component.Razor"] = "<h1>Hello world</h1>",
});
var compilation = await project.GetCompilationAsync();
var driver = await GetDriverAsync(project);
var result = RunGenerator(compilation!, ref driver).VerifyPageOutput(@"
#pragma checksum ""Component.razor"" ""{ff1816ec-aa5e-4d10-87f7-6f4963833460}"" ""6b5db227a6aa2228c777b0771108b184b1fc5df3""
// <auto-generated/>
#pragma warning disable 1591
namespace MyApp
{
#line default
using global::System;
using global::System.Collections.Generic;
using global::System.Linq;
using global::System.Threading.Tasks;
using global::Microsoft.AspNetCore.Components;
#line default
#line hidden
#nullable restore
public partial class Component : global::Microsoft.AspNetCore.Components.ComponentBase
#nullable disable
{
#pragma warning disable 1998
protected override void BuildRenderTree(global::Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder __builder)
{
__builder.AddMarkupContent(0, ""<h1>Hello world</h1>"");
}
#pragma warning restore 1998
}
}
#pragma warning restore 1591
");
Assert.Empty(result.Diagnostics);
Assert.Single(result.GeneratedSources);
}
[Theory, WorkItem("https://github.com/dotnet/razor/issues/7236")]
[InlineData("")]
[InlineData(" ")]
[InlineData("\n")]
public async Task SourceGenerator_EmptyTargetPath(string targetPath)
{
const string componentPath = "Component.razor";
var project = CreateTestProject(new()
{
[componentPath] = "<h1>Hello world</h1>",
});
var compilation = await project.GetCompilationAsync();
var (driver, _, _) = await GetDriverWithAdditionalTextAndProviderAsync(project, optionsProvider =>
{
optionsProvider.AdditionalTextOptions[componentPath] = new TestAnalyzerConfigOptions
{
["build_metadata.AdditionalFiles.TargetPath"] = targetPath
};
});
var result = RunGenerator(compilation!, ref driver);
var diagnostic = Assert.Single(result.Diagnostics);
// RSG002: TargetPath not specified for additional file
Assert.Equal("RSG002", diagnostic.Id);
Assert.Empty(result.GeneratedSources);
}
[Fact]
public async Task SourceGenerator_Class_Inside_CodeBlock()
{
var project = CreateTestProject(new()
{
["Component.Razor"] =
"""
<h1>Hello world</h1>
@code
{
public class X {}
}
"""
});
var compilation = await project.GetCompilationAsync();
var driver = await GetDriverAsync(project);
var result = RunGenerator(compilation!, ref driver).VerifyPageOutput(
"""
#pragma checksum "Component.Razor" "{ff1816ec-aa5e-4d10-87f7-6f4963833460}" "20b14071a74e1fd554d7b3dff6ff41722270ebee"
// <auto-generated/>
#pragma warning disable 1591
namespace MyApp
{
#line default
using global::System;
using global::System.Collections.Generic;
using global::System.Linq;
using global::System.Threading.Tasks;
using global::Microsoft.AspNetCore.Components;
#line default
#line hidden
#nullable restore
public partial class Component : global::Microsoft.AspNetCore.Components.ComponentBase
#nullable disable
{
#pragma warning disable 1998
protected override void BuildRenderTree(global::Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder __builder)
{
__builder.AddMarkupContent(0, "<h1>Hello world</h1>");
}
#pragma warning restore 1998
#nullable restore
#line (4,2)-(6,1) "Component.Razor"
public class X {}
#line default
#line hidden
#nullable disable
}
}
#pragma warning restore 1591
""");
Assert.Empty(result.Diagnostics);
Assert.Single(result.GeneratedSources);
}
[Fact, WorkItem("https://github.com/dotnet/razor/issues/8850")]
public async Task SystemFolder()
{
var project = CreateTestProject(new()
{
["Pages/__Host.cshtml"] = """
@page "/"
@namespace MyApp.Pages
""",
["Pages/System/MyComponent.razor"] = """
<h1>My component</h1>
""",
});
var compilation = await project.GetCompilationAsync();
var driver = await GetDriverAsync(project);
var result = RunGenerator(compilation!, ref driver);
Assert.Empty(result.Diagnostics);
Assert.Equal(2, result.GeneratedSources.Length);
}
[Theory, CombinatorialData]
public async Task RazorLangVersion_Incorrect([CombinatorialValues("incorrect", "-1", "10000")] string langVersion)
{
var project = CreateTestProject(new()
{
["Pages/Index.razor"] = "<h1>Hello world</h1>",
});
var compilation = await project.GetCompilationAsync();
var driver = await GetDriverAsync(project, options =>
{
options.TestGlobalOptions["build_property.RazorLangVersion"] = langVersion;
});
var result = RunGenerator(compilation!, ref driver);
result.Diagnostics.Verify(
// error RZ3600: Invalid value '{0}' for RazorLangVersion. Valid values include 'Latest', 'Preview', or a valid version in range 1.0 to {1}.
Diagnostic("RZ3600").WithArguments(langVersion, RazorLanguageVersion.Preview.ToString()).WithLocation(1, 1));
Assert.Single(result.GeneratedSources);
}
[Theory, CombinatorialData]
public async Task RazorWarningLevel_Incorrect(
[CombinatorialValues("incorrect", "1.2", "0x1", "-1")] string warningLevel)
{
var project = CreateTestProject(new()
{
["Pages/Index.razor"] = "<h1>Hello world</h1>",
});
var compilation = await project.GetCompilationAsync();
var driver = await GetDriverAsync(project, options =>
{
options.TestGlobalOptions["build_property.RazorWarningLevel"] = warningLevel;
});
var result = RunGenerator(compilation!, ref driver);
result.Diagnostics.Verify(
// error RZ3601: Invalid value '{0}' for RazorWarningLevel. Must be empty or a non-negative integer.
Diagnostic("RZ3601").WithArguments(warningLevel).WithLocation(1, 1));
Assert.Single(result.GeneratedSources);
}
[Theory, CombinatorialData]
public async Task RazorWarningLevel_ValidValues(
[CombinatorialValues("0", "10", "11", "9999")] string warningLevel)
{
var project = CreateTestProject(new()
{
["Pages/Index.razor"] = "<h1>Hello world</h1>",
});
var compilation = await project.GetCompilationAsync();
var driver = await GetDriverAsync(project, options =>
{
options.TestGlobalOptions["build_property.RazorWarningLevel"] = warningLevel;
});
var result = RunGenerator(compilation!, ref driver);
result.Diagnostics.Verify();
Assert.Single(result.GeneratedSources);
}
[Fact]
public async Task RazorWarningLevel_Empty_IsValid()
{
var project = CreateTestProject(new()
{
["Pages/Index.razor"] = "<h1>Hello world</h1>",
});
var compilation = await project.GetCompilationAsync();
var driver = await GetDriverAsync(project, options =>
{
options.TestGlobalOptions["build_property.RazorWarningLevel"] = "";
});
var result = RunGenerator(compilation!, ref driver);
result.Diagnostics.Verify();
Assert.Single(result.GeneratedSources);
}
#pragma warning disable RS1041 // This compiler extension should not be implemented in an assembly with target framework '.NET 8.0'. References to other target frameworks will cause the compiler to behave unpredictably.
#pragma warning disable RS1038 // This compiler extension should not be implemented in an assembly containing a reference to Microsoft.CodeAnalysis.Workspaces.
[Generator]
#pragma warning restore RS1038 // This compiler extension should not be implemented in an assembly containing a reference to Microsoft.CodeAnalysis.Workspaces.
#pragma warning restore RS1041 // This compiler extension should not be implemented in an assembly with target framework '.NET 8.0'. References to other target frameworks will cause the compiler to behave unpredictably.
#pragma warning disable RS1036 // Specify analyzer banned API enforcement setting
public class LambdaGenerator(Action<IncrementalGeneratorInitializationContext> action) : IIncrementalGenerator
#pragma warning restore RS1036 // Specify analyzer banned API enforcement setting
{
public void Initialize(IncrementalGeneratorInitializationContext context) => action(context);
}
[Fact]
public async Task IncrementalCompilation_NothingRuns_When_AdditionalFiles_HaveSameContent()
{
// Arrange
var project = CreateTestProject(new()
{
["Pages/Index.razor"] = "<h1>Hello world</h1>",
["Pages/Counter.razor"] = "<h1>Counter</h1>",
});
var compilation = await project.GetCompilationAsync();
var (driver, additionalTexts) = await GetDriverWithAdditionalTextAsync(project);
var result = RunGenerator(compilation!, ref driver);
Assert.Empty(result.Diagnostics);
Assert.Equal(2, result.GeneratedSources.Length);
result = RunGenerator(compilation!, ref driver)
.VerifyOutputsMatch(result);
Assert.Empty(result.Diagnostics);
Assert.Equal(2, result.GeneratedSources.Length);
project = project.RemoveAdditionalDocument(project.AdditionalDocumentIds[1])
.AddAdditionalDocument("Counter.razor", SourceText.From("<h1>Counter</h1>", Encoding.UTF8))
.Project;
compilation = await project.GetCompilationAsync();
result = RunGenerator(compilation!, ref driver)
.VerifyOutputsMatch(result);
Assert.Empty(result.Diagnostics);
Assert.Equal(2, result.GeneratedSources.Length);
}
[Fact]
public async Task IncrementalCompilation_OnlyCompilationRuns_When_MetadataReferences_SameAssembly()
{
// Arrange
var project = CreateTestProject(new()
{
["Pages/Index.razor"] = "<h1>Hello world</h1>",
["Pages/Counter.razor"] = "<h1>Counter</h1>",
});
var compilation = await project.GetCompilationAsync();
var (driver, additionalTexts, _) = await GetDriverWithAdditionalTextAndProviderAsync(project, trackSteps: true);
var result = RunGenerator(compilation!, ref driver);
Assert.Empty(result.Diagnostics);
Assert.Equal(2, result.GeneratedSources.Length);
result = RunGenerator(compilation!, ref driver)
.VerifyOutputsMatch(result);
Assert.Empty(result.Diagnostics);
Assert.Equal(2, result.GeneratedSources.Length);
var reference = (PortableExecutableReference)project.MetadataReferences[^1];
project = project.RemoveMetadataReference(reference)
.AddMetadataReference(MetadataReference.CreateFromFile(reference.FilePath!));
compilation = await project.GetCompilationAsync();
result = RunGenerator(compilation!, ref driver)
.VerifyOutputsMatch(result);
Assert.Empty(result.Diagnostics);
Assert.Equal(2, result.GeneratedSources.Length);
// reference causes the compilation to change so we re-run tag helper discovery there
// but we didn't re-check the actual reference itself
result.VerifyIncrementalSteps("RazorSourceGeneratorOptions", IncrementalStepRunReason.Unchanged); // Re-ran but unchanged
result.VerifyIncrementalSteps("TagHelpersFromCompilation", IncrementalStepRunReason.Unchanged); // Re-ran but unchanged
}
[Theory]
[InlineData("true")]
[InlineData("True")]
[InlineData("TRUE")]
[InlineData("tRuE")]
public async Task RoslynTokenizerEnabledWithTrue(string value)
{
var parseOptions = CSharpParseOptions.Default.WithFeatures([new("use-roslyn-tokenizer", value)]);
var project = CreateTestProject(new()
{
["Pages/Index.razor"] = """"
<div>@("""
nested "
""")</div>
"""",
}, cSharpParseOptions: parseOptions);
var compilation = await project.GetCompilationAsync();
var (driver, additionalTexts) = await GetDriverWithAdditionalTextAsync(project);
var result = RunGenerator(compilation!, ref driver);
result.VerifyPageOutput(
""""
#pragma checksum "Pages/Index.razor" "{ff1816ec-aa5e-4d10-87f7-6f4963833460}" "c6855f3cabbcb69477e3f5a61f8d77fcfed086c2"
// <auto-generated/>
#pragma warning disable 1591
namespace MyApp.Pages
{
#line default
using global::System;
using global::System.Collections.Generic;
using global::System.Linq;
using global::System.Threading.Tasks;
using global::Microsoft.AspNetCore.Components;
#line default
#line hidden
#nullable restore
public partial class Index : global::Microsoft.AspNetCore.Components.ComponentBase
#nullable disable
{
#pragma warning disable 1998
protected override void BuildRenderTree(global::Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder __builder)
{
__builder.OpenElement(0, "div");
#nullable restore
#line (1,8)-(3,8) 24 "Pages/Index.razor"
__builder.AddContent(1, """
nested "
"""
#line default
#line hidden
#nullable disable
);
__builder.CloseElement();
}
#pragma warning restore 1998
}
}
#pragma warning restore 1591
"""");
}
[Theory]
[InlineData("false")]
[InlineData("False")]
[InlineData("FALSE")]
[InlineData("FaLsE")]
[InlineData("")]
[InlineData(null)]
public async Task RoslynTokenizerDisabledWithFalseOrNothing(string? value)
{
var parseOptions = CSharpParseOptions.Default;
if (value != null)
{
parseOptions = parseOptions.WithFeatures([new("use-roslyn-tokenizer", value)]);
}
var project = CreateTestProject(new()
{
["Pages/Index.razor"] = """"
<div>@("""
nested "
""")</div>
"""",
}, cSharpParseOptions: parseOptions);
var compilation = await project.GetCompilationAsync();
var (driver, additionalTexts) = await GetDriverWithAdditionalTextAsync(project);
var result = RunGenerator(compilation!, ref driver,
// Pages/Index.razor(3,10): error CS1525: Invalid expression term '/'
// """)</div>
Diagnostic(ErrorCode.ERR_InvalidExprTerm, "/").WithArguments("/").WithLocation(3, 10),
// Pages/Index.razor(3,11): error CS0103: The name 'div' does not exist in the current context
// """)</div>
Diagnostic(ErrorCode.ERR_NameNotInContext, "div").WithArguments("div").WithLocation(3, 11),
// Pages/Index.razor(3,15): error CS1525: Invalid expression term ')'
// """)</div>
Diagnostic(ErrorCode.ERR_InvalidExprTerm, "").WithArguments(")").WithLocation(3, 15),
// Pages/Index.razor(3,15): error CS1002: ; expected
// """)</div>
Diagnostic(ErrorCode.ERR_SemicolonExpected, "").WithLocation(3, 15),
// Pages/Index.razor(3,15): error CS1513: } expected
// """)</div>
Diagnostic(ErrorCode.ERR_RbraceExpected, "").WithLocation(3, 15));
result.VerifyPageOutput(
""""
#pragma checksum "Pages/Index.razor" "{ff1816ec-aa5e-4d10-87f7-6f4963833460}" "c6855f3cabbcb69477e3f5a61f8d77fcfed086c2"
// <auto-generated/>
#pragma warning disable 1591
namespace MyApp.Pages
{
#line default
using global::System;
using global::System.Collections.Generic;
using global::System.Linq;
using global::System.Threading.Tasks;
using global::Microsoft.AspNetCore.Components;
#line default
#line hidden
#nullable restore
public partial class Index : global::Microsoft.AspNetCore.Components.ComponentBase
#nullable disable
{
#pragma warning disable 1998
protected override void BuildRenderTree(global::Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder __builder)
{
__builder.OpenElement(0, "div");
#nullable restore
#line (1,8)-(3,15) 24 "Pages/Index.razor"
__builder.AddContent(1, """
nested "
""")</div>
#line default
#line hidden
#nullable disable
);
__builder.CloseElement();
}
#pragma warning restore 1998
}
}
#pragma warning restore 1591
"""");
}
[Fact]
public async Task IncrementalCompilation_RerunsGenerator_When_AdditionalFileRenamed()
{
// Arrange
var project = CreateTestProject(new()
{
["Pages/Index.razor"] = "<h1>Hello world</h1>",
["Pages/Counter.razor"] = "<h1>Counter</h1>",
});
var compilation = await project.GetCompilationAsync();
var (driver, additionalTexts, analyzerConfigOptionProvider) = await GetDriverWithAdditionalTextAndProviderAsync(project, trackSteps: true);
var result = RunGenerator(compilation!, ref driver);
Assert.Empty(result.Diagnostics);
Assert.Equal(2, result.GeneratedSources.Length);
// Verify no changes when re-running
result = RunGenerator(compilation!, ref driver)
.VerifyOutputsMatch(result);
Assert.Empty(result.Diagnostics);
Assert.Equal(2, result.GeneratedSources.Length);
// Rename Counter.razor to NewCounter.razor by removing and re-adding with same content
var counterText = additionalTexts.First(f => f.Path.EndsWith("Counter.razor", StringComparison.OrdinalIgnoreCase));
var renamedText = new TestAdditionalText("Pages/NewCounter.razor", counterText.GetText()!);
driver = driver.RemoveAdditionalTexts([counterText])
.AddAdditionalTexts([renamedText]);
// Update the analyzer config options with the new target path
analyzerConfigOptionProvider.AdditionalTextOptions[renamedText.Path] = new TestAnalyzerConfigOptions
{
["build_metadata.AdditionalFiles.TargetPath"] = Convert.ToBase64String(Encoding.UTF8.GetBytes(renamedText.Path))
};
driver = driver.WithUpdatedAnalyzerConfigOptions(analyzerConfigOptionProvider);
result = RunGenerator(compilation!, ref driver);
Assert.Empty(result.Diagnostics);
Assert.Equal(2, result.GeneratedSources.Length);
// Verify the new file was processed
result.VerifyIncrementalStepsMultiple("ParsedDocuments",
IncrementalStepRunReason.Cached, // Index unchanged
IncrementalStepRunReason.Modified); // NewCounter renamed from Counter
result.VerifyIncrementalStepsMultiple("GeneratedDeclarationCode",
IncrementalStepRunReason.Cached, // Index unchanged
IncrementalStepRunReason.Modified); // NewCounter renamed
result.VerifyIncrementalSteps("TagHelpersFromCompilation", IncrementalStepRunReason.Modified); // Re-discover (removed Counter, added NewCounter)
result.VerifyIncrementalStepsMultiple("CheckedAndRewrittenTagHelpers",
IncrementalStepRunReason.Modified, // Index - tag helpers changed
IncrementalStepRunReason.Modified); // NewCounter renamed
result.VerifyIncrementalStepsMultiple("GeneratedCode",
IncrementalStepRunReason.Modified, // Index - tag helpers changed
IncrementalStepRunReason.Modified); // NewCounter renamed
result.VerifyIncrementalStepsMultiple("CSharpDocuments",
IncrementalStepRunReason.Unchanged, // Index - output unchanged (doesn't use Counter/NewCounter)
IncrementalStepRunReason.Modified); // NewCounter renamed
// Verify the generated source has the correct namespace and class name
var newCounterSource = result.GeneratedSources.FirstOrDefault(s => s.HintName.Contains("NewCounter"));
Assert.Contains("namespace MyApp.Pages", newCounterSource.SourceText.ToString());
Assert.Contains("public partial class NewCounter", newCounterSource.SourceText.ToString());
// Do a case-only rename and make sure we update the generated class name still
// as component names are case sensitive even on Windows.
var renamedText2 = new TestAdditionalText("Pages/NewCouNter.razor", counterText.GetText()!);
driver = driver.RemoveAdditionalTexts([renamedText])
.AddAdditionalTexts([renamedText2]);
// Update the analyzer config options with the new target path
analyzerConfigOptionProvider.AdditionalTextOptions[renamedText2.Path] = new TestAnalyzerConfigOptions
{
["build_metadata.AdditionalFiles.TargetPath"] = Convert.ToBase64String(Encoding.UTF8.GetBytes(renamedText2.Path))
};
driver = driver.WithUpdatedAnalyzerConfigOptions(analyzerConfigOptionProvider);
result = RunGenerator(compilation!, ref driver);
var newCouNterSource = result.GeneratedSources.FirstOrDefault(s => s.HintName.Contains("NewCouNter"));
Assert.Contains("public partial class NewCouNter", newCouNterSource.SourceText.ToString());
}
[Fact, WorkItem("https://github.com/dotnet/razor/issues/12316")]
public async Task RazorClassLibrary_Change_Updates_DependentProject_WhenReferencedAsCompilation()
{
var rclProject = CreateTestProject(new()
{
["LibComponent.razor"] = "<p>Library component</p>",
});
rclProject = rclProject.WithAssemblyName("RazorClassLibrary");
var rclCompilation = await rclProject.GetCompilationAsync();
var rclDriver = await GetDriverAsync(rclProject);
var rclRun = RunGenerator(rclCompilation!, ref rclDriver, out var rclOutputCompilation);
Assert.Empty(rclRun.Diagnostics);
Assert.Single(rclRun.GeneratedSources); // LibComponent
// Explicitly use a CompilationReference
var rclReference = rclOutputCompilation.ToMetadataReference();
// Create the main project that references the RCL and uses its component.
var mainProject = CreateTestProject(new()
{
["Pages/Index.razor"] = "<LibComponent />",
});
mainProject = mainProject.AddMetadataReference(rclReference);
var mainCompilation = await mainProject.GetCompilationAsync();
var (mainDriver, mainAdditionalTexts, _) = await GetDriverWithAdditionalTextAndProviderAsync(mainProject, trackSteps: true);
var mainRun = RunGenerator(mainCompilation!, ref mainDriver);
Assert.Empty(mainRun.Diagnostics);
Assert.Single(mainRun.GeneratedSources);
// Rename the component in the RCL: LibComponent -> RenamedComponent
rclProject = CreateTestProject(new()
{
["RenamedComponent.razor"] = "<p>Library component</p>",
}).WithAssemblyName("RazorClassLibrary");
rclCompilation = await rclProject.GetCompilationAsync()!;
rclDriver = await GetDriverAsync(rclProject);
rclRun = RunGenerator(rclCompilation!, ref rclDriver, out rclOutputCompilation);
Assert.Empty(rclRun.Diagnostics);
Assert.Single(rclRun.GeneratedSources); // RenamedComponent
var rclReference2 = rclOutputCompilation.ToMetadataReference();
// Update main project to point to the new reference (with renamed component).
mainProject = mainProject.RemoveMetadataReference(rclReference)
.AddMetadataReference(rclReference2);
mainCompilation = await mainProject.GetCompilationAsync();
// Re-run generator: expect missing component diagnostic (RZ10012).
mainRun = RunGenerator(mainCompilation!, ref mainDriver);
var missing = Assert.Single(mainRun.Diagnostics);
Assert.Equal("RZ10012", missing.Id);
// Update main project's Index.razor to use the renamed component.
var updatedIndex = new TestAdditionalText("Pages/Index.razor", SourceText.From("<RenamedComponent />", Encoding.UTF8));
mainDriver = mainDriver.ReplaceAdditionalText(
mainAdditionalTexts.First(t => t.Path.EndsWith("Index.razor", StringComparison.OrdinalIgnoreCase)),
updatedIndex);
// Re-run generator: should compile cleanly again.
mainRun = RunGenerator(mainCompilation!, ref mainDriver);
Assert.Empty(mainRun.Diagnostics);
Assert.Single(mainRun.GeneratedSources);
// Update the compilation, which will cause us to re-run
mainCompilation = mainCompilation!.WithOptions(mainCompilation.Options.WithModuleName("newMain"));
mainRun = RunGenerator(mainCompilation!, ref mainDriver);
Assert.Empty(mainRun.Diagnostics);
Assert.Single(mainRun.GeneratedSources);
// Confirm that the tag helpers from metadata refs _didn't_ re-run
// TagHelpersFromCompilation re-runs when compilation changes but output is unchanged
mainRun.VerifyIncrementalSteps("TagHelpersFromCompilation", IncrementalStepRunReason.Unchanged);
}
}
}
|