From eb3e376c0320e61293938ee7bbe7de000a698347 Mon Sep 17 00:00:00 2001 From: Chris Sienkiewicz Date: Mon, 6 Apr 2026 21:11:11 -0700 Subject: [PATCH 1/2] Add warning waves infrastructure to Razor compiler MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implement the plumbing for Razor warning waves, modeled after C#'s warning waves feature. This PR adds the infrastructure only — no new warnings are introduced yet. Changes: - Add WarningLevel (uint) to RazorDiagnosticDescriptor and RazorDiagnostic - 0 = always reported, >0 = suppressed when exceeding configured level - Add RazorWarningLevel (uint) to RazorConfiguration and RazorCodeGenerationOptions - Default derived from RazorLanguageVersion major version - Add central diagnostic filtering in CodeRenderingContext.GetDiagnostics() - Add GetDefaultWarningLevel() to RazorLanguageVersion - Parse/validate build_property.RazorWarningLevel in source generator (RZ3601 for invalid values) - Add ReportDiagnostics overload for ImmutableArray Tests: - CodeRenderingContextTest: 9 tests for warning level filtering - Source generator tests: validation of RazorWarningLevel parsing Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../CodeRenderingContextTest.cs | 116 ++++++++++++++++++ .../test/RazorDiagnosticDescriptorTest.cs | 14 +++ .../CodeGeneration/CodeRenderingContext.cs | 17 ++- .../RazorCodeGenerationOptions.Builder.cs | 7 +- .../Language/RazorCodeGenerationOptions.cs | 28 ++++- .../src/Language/RazorConfiguration.cs | 5 +- .../src/Language/RazorDiagnostic.cs | 1 + .../src/Language/RazorDiagnosticDescriptor.cs | 18 ++- .../src/Language/RazorLanguageVersion.cs | 7 ++ .../src/Language/RazorProjectEngine.cs | 3 +- .../Diagnostics/DiagnosticIds.cs | 1 + .../Diagnostics/RazorDiagnostics.cs | 8 ++ .../RazorSourceGeneratorResources.resx | 6 + .../IncrementalValueProviderExtensions.cs | 15 +++ .../RazorSourceGenerator.RazorProviders.cs | 30 ++++- .../RazorSourceGeneratorTests.cs | 61 +++++++++ 16 files changed, 318 insertions(+), 19 deletions(-) create mode 100644 src/Compiler/Microsoft.AspNetCore.Razor.Language/test/CodeGeneration/CodeRenderingContextTest.cs diff --git a/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/CodeGeneration/CodeRenderingContextTest.cs b/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/CodeGeneration/CodeRenderingContextTest.cs new file mode 100644 index 00000000000..a4464ef16a6 --- /dev/null +++ b/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/CodeGeneration/CodeRenderingContextTest.cs @@ -0,0 +1,116 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Linq; +using Microsoft.AspNetCore.Razor.Language.Intermediate; +using Xunit; + +namespace Microsoft.AspNetCore.Razor.Language.CodeGeneration; + +public class CodeRenderingContextTest +{ + [Theory] + [InlineData(0u, false)] + [InlineData(11u, false)] + [InlineData(12u, true)] + [InlineData(9999u, true)] + public void GetDiagnostics_FiltersWarningsByLevel(uint warningLevel, bool expectDiagnostic) + { + // Arrange + var descriptor = new RazorDiagnosticDescriptor("RZTest", "Test warning for '{0}'", RazorDiagnosticSeverity.Warning, warningLevel: 12); + var diagnostic = RazorDiagnostic.Create(descriptor, SourceSpan.Undefined, "param"); + + var documentNode = new DocumentIntermediateNode(); + documentNode.AddDiagnostic(diagnostic); + + var options = RazorCodeGenerationOptions.Default.WithRazorWarningLevel(warningLevel); + using var context = new CodeRenderingContext( + RuntimeNodeWriter.Instance, + TestRazorSourceDocument.Create(), + documentNode, + options); + + // Act + var diagnostics = context.GetDiagnostics(); + + // Assert + if (expectDiagnostic) + { + Assert.Contains(diagnostics, d => d.Id == "RZTest"); + } + else + { + Assert.DoesNotContain(diagnostics, d => d.Id == "RZTest"); + } + } + + [Fact] + public void GetDiagnostics_AlwaysOnDiagnostics_NotFilteredAtAnyLevel() + { + // Arrange — level 0 diagnostics are always on + var descriptor = new RazorDiagnosticDescriptor("RZAlways", "Always on: '{0}'", RazorDiagnosticSeverity.Warning); + var diagnostic = RazorDiagnostic.Create(descriptor, SourceSpan.Undefined, "param"); + + var documentNode = new DocumentIntermediateNode(); + documentNode.AddDiagnostic(diagnostic); + + var options = RazorCodeGenerationOptions.Default.WithRazorWarningLevel(0); + using var context = new CodeRenderingContext( + RuntimeNodeWriter.Instance, + TestRazorSourceDocument.Create(), + documentNode, + options); + + // Act + var diagnostics = context.GetDiagnostics(); + + // Assert — level 0 diagnostic should be present even at warning level 0 + Assert.Contains(diagnostics, d => d.Id == "RZAlways"); + } + + [Theory] + [InlineData(10u)] + [InlineData(11u)] + [InlineData(12u)] + [InlineData(13u)] + public void GetDiagnostics_MultipleLevels_FiltersCorrectly(uint warningLevel) + { + // Arrange — diagnostics at levels 0, 11, 12, and 13 + var alwaysOn = RazorDiagnostic.Create( + new RazorDiagnosticDescriptor("RZ0", "Always", RazorDiagnosticSeverity.Warning), + SourceSpan.Undefined); + var level11 = RazorDiagnostic.Create( + new RazorDiagnosticDescriptor("RZ11", "Level 11", RazorDiagnosticSeverity.Warning, warningLevel: 11), + SourceSpan.Undefined); + var level12 = RazorDiagnostic.Create( + new RazorDiagnosticDescriptor("RZ12", "Level 12", RazorDiagnosticSeverity.Warning, warningLevel: 12), + SourceSpan.Undefined); + var level13 = RazorDiagnostic.Create( + new RazorDiagnosticDescriptor("RZ13", "Level 13", RazorDiagnosticSeverity.Warning, warningLevel: 13), + SourceSpan.Undefined); + + var documentNode = new DocumentIntermediateNode(); + documentNode.AddDiagnostic(alwaysOn); + documentNode.AddDiagnostic(level11); + documentNode.AddDiagnostic(level12); + documentNode.AddDiagnostic(level13); + + var options = RazorCodeGenerationOptions.Default.WithRazorWarningLevel(warningLevel); + using var context = new CodeRenderingContext( + RuntimeNodeWriter.Instance, + TestRazorSourceDocument.Create(), + documentNode, + options); + + // Act + var diagnostics = context.GetDiagnostics(); + + // Assert — always-on is always present + Assert.Contains(diagnostics, d => d.Id == "RZ0"); + + // Each level is present only when warningLevel >= that level + Assert.Equal(warningLevel >= 11, diagnostics.Any(d => d.Id == "RZ11")); + Assert.Equal(warningLevel >= 12, diagnostics.Any(d => d.Id == "RZ12")); + Assert.Equal(warningLevel >= 13, diagnostics.Any(d => d.Id == "RZ13")); + } +} diff --git a/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/RazorDiagnosticDescriptorTest.cs b/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/RazorDiagnosticDescriptorTest.cs index d57ad2cc813..e78944cc751 100644 --- a/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/RazorDiagnosticDescriptorTest.cs +++ b/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/RazorDiagnosticDescriptorTest.cs @@ -20,6 +20,20 @@ public void RazorDiagnosticDescriptor_Ctor() Assert.Equal("RZ0001", descriptor.Id); Assert.Equal(RazorDiagnosticSeverity.Error, descriptor.Severity); Assert.Equal("Hello, World!", descriptor.MessageFormat); + Assert.Equal(0u, descriptor.WarningLevel); + } + + [Fact] + public void RazorDiagnosticDescriptor_Ctor_WithWarningLevel() + { + // Arrange & Act + var descriptor = new RazorDiagnosticDescriptor("RZ0001", "Hello, World!", RazorDiagnosticSeverity.Warning, warningLevel: 11); + + // Assert + Assert.Equal("RZ0001", descriptor.Id); + Assert.Equal(RazorDiagnosticSeverity.Warning, descriptor.Severity); + Assert.Equal("Hello, World!", descriptor.MessageFormat); + Assert.Equal(11u, descriptor.WarningLevel); } [Fact] diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/CodeGeneration/CodeRenderingContext.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/CodeGeneration/CodeRenderingContext.cs index f00fd395f8c..b9e02d9c159 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/CodeGeneration/CodeRenderingContext.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/CodeGeneration/CodeRenderingContext.cs @@ -92,7 +92,22 @@ public void AddDiagnostic(RazorDiagnostic diagnostic) } public ImmutableArray GetDiagnostics() - => _diagnostics.ToImmutableOrderedBy(static d => d.Span.AbsoluteIndex); + { + var warningLevel = Options.RazorWarningLevel; + + // Filter out diagnostics whose warning level exceeds the configured level. + // Diagnostics with level 0 are always reported regardless of the configured level. + using var filtered = new PooledArrayBuilder(capacity: _diagnostics.Count); + foreach (var diagnostic in _diagnostics) + { + if (diagnostic.WarningLevel <= warningLevel) + { + filtered.Add(diagnostic); + } + } + + return filtered.ToImmutableOrderedBy(static d => d.Span.AbsoluteIndex); + } public void AddSourceMappingFor(IntermediateNode node) { diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/RazorCodeGenerationOptions.Builder.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/RazorCodeGenerationOptions.Builder.cs index 812a115c35d..b2c99da46e8 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/RazorCodeGenerationOptions.Builder.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/RazorCodeGenerationOptions.Builder.cs @@ -27,6 +27,11 @@ public sealed class Builder /// public string? SuppressUniqueIds { get; set; } + /// + /// Gets or sets the warning level for diagnostic filtering. + /// + public uint RazorWarningLevel { get; set; } + internal Builder() { IndentSize = DefaultIndentSize; @@ -159,6 +164,6 @@ public bool RemapLinePragmaPathsOnWindows } public RazorCodeGenerationOptions ToOptions() - => new(IndentSize, NewLine, RootNamespace, CssScope, SuppressUniqueIds, _flags); + => new(IndentSize, NewLine, RootNamespace, CssScope, SuppressUniqueIds, RazorWarningLevel, _flags); } } diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/RazorCodeGenerationOptions.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/RazorCodeGenerationOptions.cs index 3774b8783c1..419d29ed604 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/RazorCodeGenerationOptions.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/RazorCodeGenerationOptions.cs @@ -16,6 +16,7 @@ public sealed partial class RazorCodeGenerationOptions rootNamespace: null, cssScope: null, suppressUniqueIds: null, + razorWarningLevel: 0, flags: Flags.DefaultFlags); public static RazorCodeGenerationOptions DesignTimeDefault { get; } = new( @@ -24,6 +25,7 @@ public sealed partial class RazorCodeGenerationOptions rootNamespace: null, cssScope: null, suppressUniqueIds: null, + razorWarningLevel: 0, flags: Flags.DefaultDesignTimeFlags); public int IndentSize { get; } @@ -44,6 +46,13 @@ public sealed partial class RazorCodeGenerationOptions /// public string? SuppressUniqueIds { get; } + /// + /// Gets the warning level for diagnostic filtering. Diagnostics with a + /// greater than this value are suppressed. + /// A value of 0 means only always-on diagnostics (level 0) are reported. + /// + public uint RazorWarningLevel { get; } + private readonly Flags _flags; private RazorCodeGenerationOptions( @@ -52,6 +61,7 @@ private RazorCodeGenerationOptions( string? rootNamespace, string? cssScope, string? suppressUniqueIds, + uint razorWarningLevel, Flags flags) { IndentSize = indentSize; @@ -59,6 +69,7 @@ private RazorCodeGenerationOptions( RootNamespace = rootNamespace; CssScope = cssScope; SuppressUniqueIds = suppressUniqueIds; + RazorWarningLevel = razorWarningLevel; _flags = flags; } @@ -162,27 +173,32 @@ public bool RemapLinePragmaPathsOnWindows public RazorCodeGenerationOptions WithIndentSize(int value) => IndentSize == value ? this - : new(value, NewLine, RootNamespace, CssScope, SuppressUniqueIds, _flags); + : new(value, NewLine, RootNamespace, CssScope, SuppressUniqueIds, RazorWarningLevel, _flags); public RazorCodeGenerationOptions WithNewLine(string value) => NewLine == value ? this - : new(IndentSize, value, RootNamespace, CssScope, SuppressUniqueIds, _flags); + : new(IndentSize, value, RootNamespace, CssScope, SuppressUniqueIds, RazorWarningLevel, _flags); public RazorCodeGenerationOptions WithRootNamespace(string? value) => RootNamespace == value ? this - : new(IndentSize, NewLine, value, CssScope, SuppressUniqueIds, _flags); + : new(IndentSize, NewLine, value, CssScope, SuppressUniqueIds, RazorWarningLevel, _flags); public RazorCodeGenerationOptions WithCssScope(string? value) => CssScope == value ? this - : new(IndentSize, NewLine, RootNamespace, value, SuppressUniqueIds, _flags); + : new(IndentSize, NewLine, RootNamespace, value, SuppressUniqueIds, RazorWarningLevel, _flags); public RazorCodeGenerationOptions WithSuppressUniqueIds(string? value) => RootNamespace == value ? this - : new(IndentSize, NewLine, RootNamespace, CssScope, value, _flags); + : new(IndentSize, NewLine, RootNamespace, CssScope, value, RazorWarningLevel, _flags); + + public RazorCodeGenerationOptions WithRazorWarningLevel(uint value) + => RazorWarningLevel == value + ? this + : new(IndentSize, NewLine, RootNamespace, CssScope, SuppressUniqueIds, value, _flags); public RazorCodeGenerationOptions WithFlags( Optional designTime = default, @@ -262,6 +278,6 @@ public RazorCodeGenerationOptions WithFlags( return flags == _flags ? this - : new(IndentSize, NewLine, RootNamespace, CssScope, SuppressUniqueIds, flags); + : new(IndentSize, NewLine, RootNamespace, CssScope, SuppressUniqueIds, RazorWarningLevel, flags); } } diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/RazorConfiguration.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/RazorConfiguration.cs index e6b1eea1305..c150bef8b76 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/RazorConfiguration.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/RazorConfiguration.cs @@ -16,7 +16,8 @@ public sealed record class RazorConfiguration( bool UseConsolidatedMvcViews = true, bool SuppressAddComponentParameter = false, bool UseRoslynTokenizer = false, - ImmutableArray PreprocessorSymbols = default) + ImmutableArray PreprocessorSymbols = default, + uint RazorWarningLevel = 0) { public ImmutableArray PreprocessorSymbols { @@ -42,6 +43,7 @@ public bool Equals(RazorConfiguration? other) SuppressAddComponentParameter == other.SuppressAddComponentParameter && UseConsolidatedMvcViews == other.UseConsolidatedMvcViews && UseRoslynTokenizer == other.UseRoslynTokenizer && + RazorWarningLevel == other.RazorWarningLevel && PreprocessorSymbols.SequenceEqual(other.PreprocessorSymbols) && Extensions.SequenceEqual(other.Extensions); @@ -55,6 +57,7 @@ public override int GetHashCode() hash.Add(SuppressAddComponentParameter); hash.Add(UseConsolidatedMvcViews); hash.Add(UseRoslynTokenizer); + hash.Add(RazorWarningLevel); hash.Add(PreprocessorSymbols); return hash; } diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/RazorDiagnostic.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/RazorDiagnostic.cs index 351b0c44040..0a832f12d23 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/RazorDiagnostic.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/RazorDiagnostic.cs @@ -14,6 +14,7 @@ public sealed class RazorDiagnostic : IEquatable, IFormattable public string Id => _descriptor.Id; public RazorDiagnosticSeverity Severity => _descriptor.Severity; + public uint WarningLevel => _descriptor.WarningLevel; public SourceSpan Span { get; } private Checksum? _checksum; diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/RazorDiagnosticDescriptor.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/RazorDiagnosticDescriptor.cs index c6027ef6487..247e9b14fc0 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/RazorDiagnosticDescriptor.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/RazorDiagnosticDescriptor.cs @@ -13,7 +13,19 @@ public sealed record RazorDiagnosticDescriptor public string MessageFormat { get; } public RazorDiagnosticSeverity Severity { get; } + /// + /// The warning level at which this diagnostic is reported. Diagnostics with a warning level + /// greater than the configured RazorWarningLevel are suppressed. + /// A value of 0 means the diagnostic is always reported regardless of the configured level. + /// + public uint WarningLevel { get; } + public RazorDiagnosticDescriptor(string id, string messageFormat, RazorDiagnosticSeverity severity) + : this(id, messageFormat, severity, warningLevel: 0) + { + } + + public RazorDiagnosticDescriptor(string id, string messageFormat, RazorDiagnosticSeverity severity, uint warningLevel) { if (string.IsNullOrEmpty(id)) { @@ -28,9 +40,9 @@ public RazorDiagnosticDescriptor(string id, string messageFormat, RazorDiagnosti Id = id; MessageFormat = messageFormat; Severity = severity; + WarningLevel = warningLevel; } - private string DebuggerToString() => $""" - Error "{Id}": "{MessageFormat}" - """; + private string DebuggerToString() + => $"""Error "{Id}" (level {WarningLevel}): "{MessageFormat}" """; } diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/RazorLanguageVersion.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/RazorLanguageVersion.cs index fa8f7a1e977..2d777e77f01 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/RazorLanguageVersion.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/RazorLanguageVersion.cs @@ -112,4 +112,11 @@ public int CompareTo(RazorLanguageVersion? other) public static bool operator <=(RazorLanguageVersion x, RazorLanguageVersion y) => x.CompareTo(y) <= 0; public static bool operator >(RazorLanguageVersion x, RazorLanguageVersion y) => x.CompareTo(y) > 0; public static bool operator >=(RazorLanguageVersion x, RazorLanguageVersion y) => x.CompareTo(y) >= 0; + + /// + /// Gets the default warning level for this language version. + /// The warning level corresponds to the major version number + /// (e.g., 11). + /// + public uint GetDefaultWarningLevel() => (uint)Major; } diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/RazorProjectEngine.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/RazorProjectEngine.cs index 260b7a2d1a2..1b15ae97448 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/RazorProjectEngine.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/RazorProjectEngine.cs @@ -253,7 +253,8 @@ private RazorCodeGenerationOptions ComputeCodeGenerationOptions(string? cssScope var builder = new RazorCodeGenerationOptions.Builder() { CssScope = cssScope, - SuppressAddComponentParameter = configuration.SuppressAddComponentParameter + SuppressAddComponentParameter = configuration.SuppressAddComponentParameter, + RazorWarningLevel = configuration.RazorWarningLevel }; configure?.Invoke(builder); diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/SourceGenerators/Diagnostics/DiagnosticIds.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/SourceGenerators/Diagnostics/DiagnosticIds.cs index d5cdb1480d2..8002e8b3c5c 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/SourceGenerators/Diagnostics/DiagnosticIds.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/SourceGenerators/Diagnostics/DiagnosticIds.cs @@ -6,6 +6,7 @@ namespace Microsoft.NET.Sdk.Razor.SourceGenerators internal static class DiagnosticIds { public const string InvalidRazorLangVersionRuleId = "RZ3600"; + public const string InvalidRazorWarningLevelRuleId = "RZ3601"; public const string ReComputingTagHelpersRuleId = "RSG001"; public const string TargetPathNotProvidedRuleId = "RSG002"; public const string GeneratedOutputFullPathNotProvidedRuleId = "RSG003"; diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/SourceGenerators/Diagnostics/RazorDiagnostics.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/SourceGenerators/Diagnostics/RazorDiagnostics.cs index a44f75aa970..6a81ae75b84 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/SourceGenerators/Diagnostics/RazorDiagnostics.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/SourceGenerators/Diagnostics/RazorDiagnostics.cs @@ -20,6 +20,14 @@ internal static class RazorDiagnostics DiagnosticSeverity.Error, isEnabledByDefault: true); + public static readonly DiagnosticDescriptor InvalidRazorWarningLevelDescriptor = new DiagnosticDescriptor( + DiagnosticIds.InvalidRazorWarningLevelRuleId, + new LocalizableResourceString(nameof(RazorSourceGeneratorResources.InvalidRazorWarningLevelTitle), RazorSourceGeneratorResources.ResourceManager, typeof(RazorSourceGeneratorResources)), + new LocalizableResourceString(nameof(RazorSourceGeneratorResources.InvalidRazorWarningLevelMessage), RazorSourceGeneratorResources.ResourceManager, typeof(RazorSourceGeneratorResources)), + "RazorSourceGenerator", + DiagnosticSeverity.Error, + isEnabledByDefault: true); + public static readonly DiagnosticDescriptor ReComputingTagHelpersDescriptor = new DiagnosticDescriptor( DiagnosticIds.ReComputingTagHelpersRuleId, new LocalizableResourceString(nameof(RazorSourceGeneratorResources.RecomputingTagHelpersTitle), RazorSourceGeneratorResources.ResourceManager, typeof(RazorSourceGeneratorResources)), diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/SourceGenerators/Diagnostics/RazorSourceGeneratorResources.resx b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/SourceGenerators/Diagnostics/RazorSourceGeneratorResources.resx index 9ce6a9a2f0b..1104ddc87d1 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/SourceGenerators/Diagnostics/RazorSourceGeneratorResources.resx +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/SourceGenerators/Diagnostics/RazorSourceGeneratorResources.resx @@ -123,6 +123,12 @@ Invalid value '{0}' for RazorLangVersion. Valid values include 'Latest', 'Preview', or a valid version in range 1.0 to {1}. + + Invalid RazorWarningLevel + + + Invalid value '{0}' for RazorWarningLevel. Must be empty or a non-negative integer. + Recomputing tag helpers diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/SourceGenerators/IncrementalValueProviderExtensions.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/SourceGenerators/IncrementalValueProviderExtensions.cs index 01e769c1f3c..97453c13335 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/SourceGenerators/IncrementalValueProviderExtensions.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/SourceGenerators/IncrementalValueProviderExtensions.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; using Microsoft.AspNetCore.Razor; using Microsoft.CodeAnalysis; @@ -52,6 +53,20 @@ internal static IncrementalValueProvider ReportDiagnostics(thi return source.Select((pair, ct) => pair.Item1!); } + + internal static IncrementalValueProvider ReportDiagnostics(this IncrementalValueProvider<(TSource?, ImmutableArray)> source, IncrementalGeneratorInitializationContext context) + { + context.RegisterSourceOutput(source, (spc, source) => + { + var (_, diagnostics) = source; + foreach (var diagnostic in diagnostics) + { + spc.ReportDiagnostic(diagnostic); + } + }); + + return source.Select((pair, ct) => pair.Item1!); + } } internal sealed class LambdaComparer : IEqualityComparer diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/SourceGenerators/RazorSourceGenerator.RazorProviders.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/SourceGenerators/RazorSourceGenerator.RazorProviders.cs index d9759cecc3c..f7d32f7caa8 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/SourceGenerators/RazorSourceGenerator.RazorProviders.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/SourceGenerators/RazorSourceGenerator.RazorProviders.cs @@ -17,7 +17,7 @@ namespace Microsoft.NET.Sdk.Razor.SourceGenerators { public partial class RazorSourceGenerator { - private (RazorSourceGenerationOptions?, Diagnostic?) ComputeRazorSourceGeneratorOptions(((AnalyzerConfigOptionsProvider, ParseOptions), ImmutableArray) pair, CancellationToken ct) + private (RazorSourceGenerationOptions?, ImmutableArray) ComputeRazorSourceGeneratorOptions(((AnalyzerConfigOptionsProvider, ParseOptions), ImmutableArray) pair, CancellationToken ct) { var ((options, parseOptions), references) = pair; var globalOptions = options.GlobalOptions; @@ -29,18 +29,36 @@ public partial class RazorSourceGenerator globalOptions.TryGetValue("build_property.SupportLocalizedComponentNames", out var supportLocalizedComponentNames); globalOptions.TryGetValue("build_property.GenerateRazorMetadataSourceChecksumAttributes", out var generateMetadataSourceChecksumAttributes); - Diagnostic? diagnostic = null; + var diagnostics = ImmutableArray.CreateBuilder(); + if (!globalOptions.TryGetValue("build_property.RazorLangVersion", out var razorLanguageVersionString) || !RazorLanguageVersion.TryParse(razorLanguageVersionString, out var razorLanguageVersion)) { - diagnostic = Diagnostic.Create( + diagnostics.Add(Diagnostic.Create( RazorDiagnostics.InvalidRazorLangVersionDescriptor, Location.None, razorLanguageVersionString, - RazorLanguageVersion.Preview.ToString()); + RazorLanguageVersion.Preview.ToString())); razorLanguageVersion = RazorLanguageVersion.Latest; } + uint razorWarningLevel = razorLanguageVersion.GetDefaultWarningLevel(); + if (globalOptions.TryGetValue("build_property.RazorWarningLevel", out var razorWarningLevelString) && + !string.IsNullOrEmpty(razorWarningLevelString)) + { + if (uint.TryParse(razorWarningLevelString, out var parsedLevel)) + { + razorWarningLevel = parsedLevel; + } + else + { + diagnostics.Add(Diagnostic.Create( + RazorDiagnostics.InvalidRazorWarningLevelDescriptor, + Location.None, + razorWarningLevelString)); + } + } + var minimalReferences = references .Where(r => r.Display is { } display && display.EndsWith("Microsoft.AspNetCore.Components.dll", StringComparison.Ordinal)) .ToImmutableArray(); @@ -49,7 +67,7 @@ public partial class RazorSourceGenerator ? false : CSharpCompilation.Create("components", references: minimalReferences).HasAddComponentParameter(); - var razorConfiguration = new RazorConfiguration(razorLanguageVersion, configurationName ?? "default", Extensions: [], UseConsolidatedMvcViews: true, SuppressAddComponentParameter: !isComponentParameterSupported); + var razorConfiguration = new RazorConfiguration(razorLanguageVersion, configurationName ?? "default", Extensions: [], UseConsolidatedMvcViews: true, SuppressAddComponentParameter: !isComponentParameterSupported, RazorWarningLevel: razorWarningLevel); // We use the new tokenizer only when requested for now. var useRoslynTokenizer = parseOptions.UseRoslynTokenizer(); @@ -65,7 +83,7 @@ public partial class RazorSourceGenerator UseRoslynTokenizer = useRoslynTokenizer, }; - return (razorSourceGenerationOptions, diagnostic); + return (razorSourceGenerationOptions, diagnostics.ToImmutable()); } private static (SourceGeneratorProjectItem?, Diagnostic?) ComputeProjectItems((AdditionalText, AnalyzerConfigOptionsProvider) pair, CancellationToken ct) diff --git a/src/Compiler/test/Microsoft.NET.Sdk.Razor.SourceGenerators.Tests/RazorSourceGeneratorTests.cs b/src/Compiler/test/Microsoft.NET.Sdk.Razor.SourceGenerators.Tests/RazorSourceGeneratorTests.cs index c4247771986..e164f89cb3f 100644 --- a/src/Compiler/test/Microsoft.NET.Sdk.Razor.SourceGenerators.Tests/RazorSourceGeneratorTests.cs +++ b/src/Compiler/test/Microsoft.NET.Sdk.Razor.SourceGenerators.Tests/RazorSourceGeneratorTests.cs @@ -2726,6 +2726,67 @@ public async Task RazorLangVersion_Incorrect([CombinatorialValues("incorrect", " 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"] = "

Hello world

", + }); + 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"] = "

Hello world

", + }); + 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"] = "

Hello world

", + }); + 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] From 9d15caf2ff6510e106eec14229f9d944b2de9fce Mon Sep 17 00:00:00 2001 From: Chris Sienkiewicz Date: Thu, 9 Apr 2026 14:06:47 -0700 Subject: [PATCH 2/2] Address PR review feedback - Use Where().OrderByAsArray() for non-destructive filtering in GetDiagnostics - Revert uint back to int for consistency with Roslyn - Fix copypasta bug in WithSuppressUniqueIds (RootNamespace -> SuppressUniqueIds) - Remove trailing space in debugger display string - Extract CreateDescriptor helper and refactor all descriptors in RazorDiagnostics.cs - Make lambdas static in IncrementalValueProviderExtensions - Use PooledArrayBuilder(capacity: 2) in source generator RazorProviders - Extract ParseRazorLanguageVersion and ParseRazorWarningLevel helper methods - Distinguish absent vs empty RazorWarningLevel: absent (old SDK) defaults to 0, empty (new SDK) defaults to language version - Fix encoding issues in comments (em dash -> dash) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../CodeRenderingContextTest.cs | 20 +- .../test/RazorDiagnosticDescriptorTest.cs | 4 +- .../CodeGeneration/CodeRenderingContext.cs | 14 +- .../RazorCodeGenerationOptions.Builder.cs | 2 +- .../Language/RazorCodeGenerationOptions.cs | 8 +- .../src/Language/RazorConfiguration.cs | 2 +- .../src/Language/RazorDiagnostic.cs | 2 +- .../src/Language/RazorDiagnosticDescriptor.cs | 6 +- .../src/Language/RazorLanguageVersion.cs | 2 +- .../Diagnostics/RazorDiagnostics.cs | 182 +++++++++--------- .../IncrementalValueProviderExtensions.cs | 4 +- .../RazorSourceGenerator.RazorProviders.cs | 77 +++++--- 12 files changed, 164 insertions(+), 159 deletions(-) diff --git a/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/CodeGeneration/CodeRenderingContextTest.cs b/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/CodeGeneration/CodeRenderingContextTest.cs index a4464ef16a6..0df5f6c103d 100644 --- a/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/CodeGeneration/CodeRenderingContextTest.cs +++ b/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/CodeGeneration/CodeRenderingContextTest.cs @@ -10,11 +10,11 @@ namespace Microsoft.AspNetCore.Razor.Language.CodeGeneration; public class CodeRenderingContextTest { [Theory] - [InlineData(0u, false)] - [InlineData(11u, false)] - [InlineData(12u, true)] - [InlineData(9999u, true)] - public void GetDiagnostics_FiltersWarningsByLevel(uint warningLevel, bool expectDiagnostic) + [InlineData(0, false)] + [InlineData(11, false)] + [InlineData(12, true)] + [InlineData(9999, true)] + public void GetDiagnostics_FiltersWarningsByLevel(int warningLevel, bool expectDiagnostic) { // Arrange var descriptor = new RazorDiagnosticDescriptor("RZTest", "Test warning for '{0}'", RazorDiagnosticSeverity.Warning, warningLevel: 12); @@ -69,11 +69,11 @@ public void GetDiagnostics_AlwaysOnDiagnostics_NotFilteredAtAnyLevel() } [Theory] - [InlineData(10u)] - [InlineData(11u)] - [InlineData(12u)] - [InlineData(13u)] - public void GetDiagnostics_MultipleLevels_FiltersCorrectly(uint warningLevel) + [InlineData(10)] + [InlineData(11)] + [InlineData(12)] + [InlineData(13)] + public void GetDiagnostics_MultipleLevels_FiltersCorrectly(int warningLevel) { // Arrange — diagnostics at levels 0, 11, 12, and 13 var alwaysOn = RazorDiagnostic.Create( diff --git a/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/RazorDiagnosticDescriptorTest.cs b/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/RazorDiagnosticDescriptorTest.cs index e78944cc751..67fbdbcc4de 100644 --- a/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/RazorDiagnosticDescriptorTest.cs +++ b/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/RazorDiagnosticDescriptorTest.cs @@ -20,7 +20,7 @@ public void RazorDiagnosticDescriptor_Ctor() Assert.Equal("RZ0001", descriptor.Id); Assert.Equal(RazorDiagnosticSeverity.Error, descriptor.Severity); Assert.Equal("Hello, World!", descriptor.MessageFormat); - Assert.Equal(0u, descriptor.WarningLevel); + Assert.Equal(0, descriptor.WarningLevel); } [Fact] @@ -33,7 +33,7 @@ public void RazorDiagnosticDescriptor_Ctor_WithWarningLevel() Assert.Equal("RZ0001", descriptor.Id); Assert.Equal(RazorDiagnosticSeverity.Warning, descriptor.Severity); Assert.Equal("Hello, World!", descriptor.MessageFormat); - Assert.Equal(11u, descriptor.WarningLevel); + Assert.Equal(11, descriptor.WarningLevel); } [Fact] diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/CodeGeneration/CodeRenderingContext.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/CodeGeneration/CodeRenderingContext.cs index b9e02d9c159..f1aa24ad79d 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/CodeGeneration/CodeRenderingContext.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/CodeGeneration/CodeRenderingContext.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.Linq; using Microsoft.AspNetCore.Razor.Language.Intermediate; using Microsoft.AspNetCore.Razor.PooledObjects; @@ -97,16 +98,9 @@ public ImmutableArray GetDiagnostics() // Filter out diagnostics whose warning level exceeds the configured level. // Diagnostics with level 0 are always reported regardless of the configured level. - using var filtered = new PooledArrayBuilder(capacity: _diagnostics.Count); - foreach (var diagnostic in _diagnostics) - { - if (diagnostic.WarningLevel <= warningLevel) - { - filtered.Add(diagnostic); - } - } - - return filtered.ToImmutableOrderedBy(static d => d.Span.AbsoluteIndex); + return _diagnostics + .Where(d => d.WarningLevel <= warningLevel) + .OrderByAsArray(static d => d.Span.AbsoluteIndex); } public void AddSourceMappingFor(IntermediateNode node) diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/RazorCodeGenerationOptions.Builder.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/RazorCodeGenerationOptions.Builder.cs index b2c99da46e8..abacfbde00d 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/RazorCodeGenerationOptions.Builder.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/RazorCodeGenerationOptions.Builder.cs @@ -30,7 +30,7 @@ public sealed class Builder /// /// Gets or sets the warning level for diagnostic filtering. /// - public uint RazorWarningLevel { get; set; } + public int RazorWarningLevel { get; set; } internal Builder() { diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/RazorCodeGenerationOptions.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/RazorCodeGenerationOptions.cs index 419d29ed604..eb190417843 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/RazorCodeGenerationOptions.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/RazorCodeGenerationOptions.cs @@ -51,7 +51,7 @@ public sealed partial class RazorCodeGenerationOptions /// greater than this value are suppressed. /// A value of 0 means only always-on diagnostics (level 0) are reported. /// - public uint RazorWarningLevel { get; } + public int RazorWarningLevel { get; } private readonly Flags _flags; @@ -61,7 +61,7 @@ private RazorCodeGenerationOptions( string? rootNamespace, string? cssScope, string? suppressUniqueIds, - uint razorWarningLevel, + int razorWarningLevel, Flags flags) { IndentSize = indentSize; @@ -191,11 +191,11 @@ public RazorCodeGenerationOptions WithCssScope(string? value) : new(IndentSize, NewLine, RootNamespace, value, SuppressUniqueIds, RazorWarningLevel, _flags); public RazorCodeGenerationOptions WithSuppressUniqueIds(string? value) - => RootNamespace == value + => SuppressUniqueIds == value ? this : new(IndentSize, NewLine, RootNamespace, CssScope, value, RazorWarningLevel, _flags); - public RazorCodeGenerationOptions WithRazorWarningLevel(uint value) + public RazorCodeGenerationOptions WithRazorWarningLevel(int value) => RazorWarningLevel == value ? this : new(IndentSize, NewLine, RootNamespace, CssScope, SuppressUniqueIds, value, _flags); diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/RazorConfiguration.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/RazorConfiguration.cs index c150bef8b76..35371ec0661 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/RazorConfiguration.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/RazorConfiguration.cs @@ -17,7 +17,7 @@ public sealed record class RazorConfiguration( bool SuppressAddComponentParameter = false, bool UseRoslynTokenizer = false, ImmutableArray PreprocessorSymbols = default, - uint RazorWarningLevel = 0) + int RazorWarningLevel = 0) { public ImmutableArray PreprocessorSymbols { diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/RazorDiagnostic.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/RazorDiagnostic.cs index 0a832f12d23..12dd2439886 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/RazorDiagnostic.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/RazorDiagnostic.cs @@ -14,7 +14,7 @@ public sealed class RazorDiagnostic : IEquatable, IFormattable public string Id => _descriptor.Id; public RazorDiagnosticSeverity Severity => _descriptor.Severity; - public uint WarningLevel => _descriptor.WarningLevel; + public int WarningLevel => _descriptor.WarningLevel; public SourceSpan Span { get; } private Checksum? _checksum; diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/RazorDiagnosticDescriptor.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/RazorDiagnosticDescriptor.cs index 247e9b14fc0..865a27fb97f 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/RazorDiagnosticDescriptor.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/RazorDiagnosticDescriptor.cs @@ -18,14 +18,14 @@ public sealed record RazorDiagnosticDescriptor /// greater than the configured RazorWarningLevel are suppressed. /// A value of 0 means the diagnostic is always reported regardless of the configured level. /// - public uint WarningLevel { get; } + public int WarningLevel { get; } public RazorDiagnosticDescriptor(string id, string messageFormat, RazorDiagnosticSeverity severity) : this(id, messageFormat, severity, warningLevel: 0) { } - public RazorDiagnosticDescriptor(string id, string messageFormat, RazorDiagnosticSeverity severity, uint warningLevel) + public RazorDiagnosticDescriptor(string id, string messageFormat, RazorDiagnosticSeverity severity, int warningLevel) { if (string.IsNullOrEmpty(id)) { @@ -44,5 +44,5 @@ public RazorDiagnosticDescriptor(string id, string messageFormat, RazorDiagnosti } private string DebuggerToString() - => $"""Error "{Id}" (level {WarningLevel}): "{MessageFormat}" """; + => $"""Error "{Id}" (level {WarningLevel}): "{MessageFormat}"""; } diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/RazorLanguageVersion.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/RazorLanguageVersion.cs index 2d777e77f01..7bf9c0d447d 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/RazorLanguageVersion.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/RazorLanguageVersion.cs @@ -118,5 +118,5 @@ public int CompareTo(RazorLanguageVersion? other) /// The warning level corresponds to the major version number /// (e.g., 11). /// - public uint GetDefaultWarningLevel() => (uint)Major; + public int GetDefaultWarningLevel() => Major; } diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/SourceGenerators/Diagnostics/RazorDiagnostics.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/SourceGenerators/Diagnostics/RazorDiagnostics.cs index 6a81ae75b84..a1cba5c258c 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/SourceGenerators/Diagnostics/RazorDiagnostics.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/SourceGenerators/Diagnostics/RazorDiagnostics.cs @@ -12,101 +12,82 @@ namespace Microsoft.NET.Sdk.Razor.SourceGenerators { internal static class RazorDiagnostics { - public static readonly DiagnosticDescriptor InvalidRazorLangVersionDescriptor = new DiagnosticDescriptor( - DiagnosticIds.InvalidRazorLangVersionRuleId, - new LocalizableResourceString(nameof(RazorSourceGeneratorResources.InvalidRazorLangTitle), RazorSourceGeneratorResources.ResourceManager, typeof(RazorSourceGeneratorResources)), - new LocalizableResourceString(nameof(RazorSourceGeneratorResources.InvalidRazorLangMessage), RazorSourceGeneratorResources.ResourceManager, typeof(RazorSourceGeneratorResources)), - "RazorSourceGenerator", - DiagnosticSeverity.Error, - isEnabledByDefault: true); - - public static readonly DiagnosticDescriptor InvalidRazorWarningLevelDescriptor = new DiagnosticDescriptor( - DiagnosticIds.InvalidRazorWarningLevelRuleId, - new LocalizableResourceString(nameof(RazorSourceGeneratorResources.InvalidRazorWarningLevelTitle), RazorSourceGeneratorResources.ResourceManager, typeof(RazorSourceGeneratorResources)), - new LocalizableResourceString(nameof(RazorSourceGeneratorResources.InvalidRazorWarningLevelMessage), RazorSourceGeneratorResources.ResourceManager, typeof(RazorSourceGeneratorResources)), - "RazorSourceGenerator", - DiagnosticSeverity.Error, - isEnabledByDefault: true); - - public static readonly DiagnosticDescriptor ReComputingTagHelpersDescriptor = new DiagnosticDescriptor( - DiagnosticIds.ReComputingTagHelpersRuleId, - new LocalizableResourceString(nameof(RazorSourceGeneratorResources.RecomputingTagHelpersTitle), RazorSourceGeneratorResources.ResourceManager, typeof(RazorSourceGeneratorResources)), - new LocalizableResourceString(nameof(RazorSourceGeneratorResources.RecomputingTagHelpersMessage), RazorSourceGeneratorResources.ResourceManager, typeof(RazorSourceGeneratorResources)), - "RazorSourceGenerator", - DiagnosticSeverity.Info, - isEnabledByDefault: true); - - public static readonly DiagnosticDescriptor TargetPathNotProvided = new DiagnosticDescriptor( - DiagnosticIds.TargetPathNotProvidedRuleId, - new LocalizableResourceString(nameof(RazorSourceGeneratorResources.TargetPathNotProvidedTitle), RazorSourceGeneratorResources.ResourceManager, typeof(RazorSourceGeneratorResources)), - new LocalizableResourceString(nameof(RazorSourceGeneratorResources.TargetPathNotProvidedMessage), RazorSourceGeneratorResources.ResourceManager, typeof(RazorSourceGeneratorResources)), - "RazorSourceGenerator", - DiagnosticSeverity.Warning, - isEnabledByDefault: true - ); - - public static readonly DiagnosticDescriptor GeneratedOutputFullPathNotProvided = new DiagnosticDescriptor( - DiagnosticIds.GeneratedOutputFullPathNotProvidedRuleId, - new LocalizableResourceString(nameof(RazorSourceGeneratorResources.GeneratedOutputFullPathNotProvidedTitle), RazorSourceGeneratorResources.ResourceManager, typeof(RazorSourceGeneratorResources)), - new LocalizableResourceString(nameof(RazorSourceGeneratorResources.GeneratedOutputFullPathNotProvidedMessage), RazorSourceGeneratorResources.ResourceManager, typeof(RazorSourceGeneratorResources)), - "RazorSourceGenerator", - DiagnosticSeverity.Warning, - isEnabledByDefault: true - ); - - public static readonly DiagnosticDescriptor CurrentCompilationReferenceNotFoundDescriptor = new DiagnosticDescriptor( - DiagnosticIds.CurrentCompilationReferenceNotFoundId, - new LocalizableResourceString(nameof(RazorSourceGeneratorResources.CurrentCompilationReferenceNotFoundTitle), RazorSourceGeneratorResources.ResourceManager, typeof(RazorSourceGeneratorResources)), - new LocalizableResourceString(nameof(RazorSourceGeneratorResources.CurrentCompilationReferenceNotFoundMessage), RazorSourceGeneratorResources.ResourceManager, typeof(RazorSourceGeneratorResources)), - "RazorSourceGenerator", - DiagnosticSeverity.Warning, - isEnabledByDefault: true - ); - - public static readonly DiagnosticDescriptor SkippingGeneratedFileWriteDescriptor = new DiagnosticDescriptor( - DiagnosticIds.SkippingGeneratedFileWriteId, - new LocalizableResourceString(nameof(RazorSourceGeneratorResources.SkippingGeneratedFileWriteTitle), RazorSourceGeneratorResources.ResourceManager, typeof(RazorSourceGeneratorResources)), - new LocalizableResourceString(nameof(RazorSourceGeneratorResources.SkippingGeneratedFileWriteMessage), RazorSourceGeneratorResources.ResourceManager, typeof(RazorSourceGeneratorResources)), - "RazorSourceGenerator", - DiagnosticSeverity.Warning, - isEnabledByDefault: true - ); - - public static readonly DiagnosticDescriptor SourceTextNotFoundDescriptor = new DiagnosticDescriptor( - DiagnosticIds.SourceTextNotFoundId, - new LocalizableResourceString(nameof(RazorSourceGeneratorResources.SourceTextNotFoundTitle), RazorSourceGeneratorResources.ResourceManager, typeof(RazorSourceGeneratorResources)), - new LocalizableResourceString(nameof(RazorSourceGeneratorResources.SourceTextNotFoundMessage), RazorSourceGeneratorResources.ResourceManager, typeof(RazorSourceGeneratorResources)), - "RazorSourceGenerator", - DiagnosticSeverity.Error, - isEnabledByDefault: true - ); - - public static readonly DiagnosticDescriptor UnexpectedProjectItemReadCallDescriptor = new DiagnosticDescriptor( - DiagnosticIds.UnexpectedProjectItemReadCallId, - new LocalizableResourceString(nameof(RazorSourceGeneratorResources.UnexpectedProjectItemReadCallTitle), RazorSourceGeneratorResources.ResourceManager, typeof(RazorSourceGeneratorResources)), - new LocalizableResourceString(nameof(RazorSourceGeneratorResources.UnexpectedProjectItemReadCallMessage), RazorSourceGeneratorResources.ResourceManager, typeof(RazorSourceGeneratorResources)), - "RazorSourceGenerator", - DiagnosticSeverity.Error, - isEnabledByDefault: true - ); - - public static readonly DiagnosticDescriptor InvalidRazorContextComputedDescriptor = new DiagnosticDescriptor( - DiagnosticIds.InvalidRazorContextComputedId, - new LocalizableResourceString(nameof(RazorSourceGeneratorResources.InvalidRazorContextComputedTitle), RazorSourceGeneratorResources.ResourceManager, typeof(RazorSourceGeneratorResources)), - new LocalizableResourceString(nameof(RazorSourceGeneratorResources.InvalidRazorContextComputedMessage), RazorSourceGeneratorResources.ResourceManager, typeof(RazorSourceGeneratorResources)), - "RazorSourceGenerator", - DiagnosticSeverity.Info, - isEnabledByDefault: true - ); - - public static readonly DiagnosticDescriptor MetadataReferenceNotProvidedDescriptor = new DiagnosticDescriptor( - DiagnosticIds.MetadataReferenceNotProvidedId, - new LocalizableResourceString(nameof(RazorSourceGeneratorResources.MetadataReferenceNotProvidedTitle), RazorSourceGeneratorResources.ResourceManager, typeof(RazorSourceGeneratorResources)), - new LocalizableResourceString(nameof(RazorSourceGeneratorResources.MetadataReferenceNotProvidedMessage), RazorSourceGeneratorResources.ResourceManager, typeof(RazorSourceGeneratorResources)), - "RazorSourceGenerator", - DiagnosticSeverity.Info, - isEnabledByDefault: true - ); + public static readonly DiagnosticDescriptor InvalidRazorLangVersionDescriptor = + CreateDescriptor( + DiagnosticIds.InvalidRazorLangVersionRuleId, + nameof(RazorSourceGeneratorResources.InvalidRazorLangTitle), + nameof(RazorSourceGeneratorResources.InvalidRazorLangMessage), + DiagnosticSeverity.Error); + + public static readonly DiagnosticDescriptor InvalidRazorWarningLevelDescriptor = + CreateDescriptor( + DiagnosticIds.InvalidRazorWarningLevelRuleId, + nameof(RazorSourceGeneratorResources.InvalidRazorWarningLevelTitle), + nameof(RazorSourceGeneratorResources.InvalidRazorWarningLevelMessage), + DiagnosticSeverity.Error); + + public static readonly DiagnosticDescriptor ReComputingTagHelpersDescriptor = + CreateDescriptor( + DiagnosticIds.ReComputingTagHelpersRuleId, + nameof(RazorSourceGeneratorResources.RecomputingTagHelpersTitle), + nameof(RazorSourceGeneratorResources.RecomputingTagHelpersMessage), + DiagnosticSeverity.Info); + + public static readonly DiagnosticDescriptor TargetPathNotProvided = + CreateDescriptor( + DiagnosticIds.TargetPathNotProvidedRuleId, + nameof(RazorSourceGeneratorResources.TargetPathNotProvidedTitle), + nameof(RazorSourceGeneratorResources.TargetPathNotProvidedMessage), + DiagnosticSeverity.Warning); + + public static readonly DiagnosticDescriptor GeneratedOutputFullPathNotProvided = + CreateDescriptor( + DiagnosticIds.GeneratedOutputFullPathNotProvidedRuleId, + nameof(RazorSourceGeneratorResources.GeneratedOutputFullPathNotProvidedTitle), + nameof(RazorSourceGeneratorResources.GeneratedOutputFullPathNotProvidedMessage), + DiagnosticSeverity.Warning); + + public static readonly DiagnosticDescriptor CurrentCompilationReferenceNotFoundDescriptor = + CreateDescriptor( + DiagnosticIds.CurrentCompilationReferenceNotFoundId, + nameof(RazorSourceGeneratorResources.CurrentCompilationReferenceNotFoundTitle), + nameof(RazorSourceGeneratorResources.CurrentCompilationReferenceNotFoundMessage), + DiagnosticSeverity.Warning); + + public static readonly DiagnosticDescriptor SkippingGeneratedFileWriteDescriptor = + CreateDescriptor( + DiagnosticIds.SkippingGeneratedFileWriteId, + nameof(RazorSourceGeneratorResources.SkippingGeneratedFileWriteTitle), + nameof(RazorSourceGeneratorResources.SkippingGeneratedFileWriteMessage), + DiagnosticSeverity.Warning); + + public static readonly DiagnosticDescriptor SourceTextNotFoundDescriptor = + CreateDescriptor( + DiagnosticIds.SourceTextNotFoundId, + nameof(RazorSourceGeneratorResources.SourceTextNotFoundTitle), + nameof(RazorSourceGeneratorResources.SourceTextNotFoundMessage), + DiagnosticSeverity.Error); + + public static readonly DiagnosticDescriptor UnexpectedProjectItemReadCallDescriptor = + CreateDescriptor( + DiagnosticIds.UnexpectedProjectItemReadCallId, + nameof(RazorSourceGeneratorResources.UnexpectedProjectItemReadCallTitle), + nameof(RazorSourceGeneratorResources.UnexpectedProjectItemReadCallMessage), + DiagnosticSeverity.Error); + + public static readonly DiagnosticDescriptor InvalidRazorContextComputedDescriptor = + CreateDescriptor( + DiagnosticIds.InvalidRazorContextComputedId, + nameof(RazorSourceGeneratorResources.InvalidRazorContextComputedTitle), + nameof(RazorSourceGeneratorResources.InvalidRazorContextComputedMessage), + DiagnosticSeverity.Info); + + public static readonly DiagnosticDescriptor MetadataReferenceNotProvidedDescriptor = + CreateDescriptor( + DiagnosticIds.MetadataReferenceNotProvidedId, + nameof(RazorSourceGeneratorResources.MetadataReferenceNotProvidedTitle), + nameof(RazorSourceGeneratorResources.MetadataReferenceNotProvidedMessage), + DiagnosticSeverity.Info); public static Diagnostic AsDiagnostic(this RazorDiagnostic razorDiagnostic) { @@ -145,5 +126,16 @@ public static Diagnostic AsDiagnostic(this RazorDiagnostic razorDiagnostic) return Diagnostic.Create(descriptor, location); } + + private static DiagnosticDescriptor CreateDescriptor(string id, string titleResourceName, string messageResourceName, DiagnosticSeverity defaultSeverity) + { + return new DiagnosticDescriptor( + id, + new LocalizableResourceString(titleResourceName, RazorSourceGeneratorResources.ResourceManager, typeof(RazorSourceGeneratorResources)), + new LocalizableResourceString(messageResourceName, RazorSourceGeneratorResources.ResourceManager, typeof(RazorSourceGeneratorResources)), + "RazorSourceGenerator", + defaultSeverity, + isEnabledByDefault: true); + } } } diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/SourceGenerators/IncrementalValueProviderExtensions.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/SourceGenerators/IncrementalValueProviderExtensions.cs index 97453c13335..6a9be9ce198 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/SourceGenerators/IncrementalValueProviderExtensions.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/SourceGenerators/IncrementalValueProviderExtensions.cs @@ -56,7 +56,7 @@ internal static IncrementalValueProvider ReportDiagnostics(thi internal static IncrementalValueProvider ReportDiagnostics(this IncrementalValueProvider<(TSource?, ImmutableArray)> source, IncrementalGeneratorInitializationContext context) { - context.RegisterSourceOutput(source, (spc, source) => + context.RegisterSourceOutput(source, static (spc, source) => { var (_, diagnostics) = source; foreach (var diagnostic in diagnostics) @@ -65,7 +65,7 @@ internal static IncrementalValueProvider ReportDiagnostics(thi } }); - return source.Select((pair, ct) => pair.Item1!); + return source.Select(static (pair, ct) => pair.Item1!); } } diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/SourceGenerators/RazorSourceGenerator.RazorProviders.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/SourceGenerators/RazorSourceGenerator.RazorProviders.cs index f7d32f7caa8..5f3ca8c6ae5 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/SourceGenerators/RazorSourceGenerator.RazorProviders.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/SourceGenerators/RazorSourceGenerator.RazorProviders.cs @@ -8,6 +8,7 @@ using System.Text; using System.Threading; using Microsoft.AspNetCore.Razor.Language; +using Microsoft.AspNetCore.Razor.PooledObjects; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Diagnostics; @@ -29,35 +30,10 @@ public partial class RazorSourceGenerator globalOptions.TryGetValue("build_property.SupportLocalizedComponentNames", out var supportLocalizedComponentNames); globalOptions.TryGetValue("build_property.GenerateRazorMetadataSourceChecksumAttributes", out var generateMetadataSourceChecksumAttributes); - var diagnostics = ImmutableArray.CreateBuilder(); + using var diagnostics = new PooledArrayBuilder(capacity: 2); - if (!globalOptions.TryGetValue("build_property.RazorLangVersion", out var razorLanguageVersionString) || - !RazorLanguageVersion.TryParse(razorLanguageVersionString, out var razorLanguageVersion)) - { - diagnostics.Add(Diagnostic.Create( - RazorDiagnostics.InvalidRazorLangVersionDescriptor, - Location.None, - razorLanguageVersionString, - RazorLanguageVersion.Preview.ToString())); - razorLanguageVersion = RazorLanguageVersion.Latest; - } - - uint razorWarningLevel = razorLanguageVersion.GetDefaultWarningLevel(); - if (globalOptions.TryGetValue("build_property.RazorWarningLevel", out var razorWarningLevelString) && - !string.IsNullOrEmpty(razorWarningLevelString)) - { - if (uint.TryParse(razorWarningLevelString, out var parsedLevel)) - { - razorWarningLevel = parsedLevel; - } - else - { - diagnostics.Add(Diagnostic.Create( - RazorDiagnostics.InvalidRazorWarningLevelDescriptor, - Location.None, - razorWarningLevelString)); - } - } + var razorLanguageVersion = ParseRazorLanguageVersion(globalOptions, ref diagnostics.AsRef()); + var razorWarningLevel = ParseRazorWarningLevel(globalOptions, razorLanguageVersion, ref diagnostics.AsRef()); var minimalReferences = references .Where(r => r.Display is { } display && display.EndsWith("Microsoft.AspNetCore.Components.dll", StringComparison.Ordinal)) @@ -83,7 +59,50 @@ public partial class RazorSourceGenerator UseRoslynTokenizer = useRoslynTokenizer, }; - return (razorSourceGenerationOptions, diagnostics.ToImmutable()); + return (razorSourceGenerationOptions, diagnostics.ToImmutableAndClear()); + } + + private static RazorLanguageVersion ParseRazorLanguageVersion(AnalyzerConfigOptions globalOptions, ref PooledArrayBuilder diagnostics) + { + if (!globalOptions.TryGetValue("build_property.RazorLangVersion", out var razorLanguageVersionString) || + !RazorLanguageVersion.TryParse(razorLanguageVersionString, out var razorLanguageVersion)) + { + diagnostics.Add(Diagnostic.Create( + RazorDiagnostics.InvalidRazorLangVersionDescriptor, + Location.None, + razorLanguageVersionString, + RazorLanguageVersion.Preview.ToString())); + return RazorLanguageVersion.Latest; + } + + return razorLanguageVersion; + } + + private static int ParseRazorWarningLevel(AnalyzerConfigOptions globalOptions, RazorLanguageVersion razorLanguageVersion, ref PooledArrayBuilder diagnostics) + { + if (!globalOptions.TryGetValue("build_property.RazorWarningLevel", out var razorWarningLevelString)) + { + // Property not registered - old SDK that doesn't know about warning waves. + // Default to 0 so no wave-gated warnings are reported. + return 0; + } + + if (string.IsNullOrEmpty(razorWarningLevelString)) + { + // Property registered but not set - new SDK, use language version default. + return razorLanguageVersion.GetDefaultWarningLevel(); + } + + if (int.TryParse(razorWarningLevelString, out var parsedLevel) && parsedLevel >= 0) + { + return parsedLevel; + } + + diagnostics.Add(Diagnostic.Create( + RazorDiagnostics.InvalidRazorWarningLevelDescriptor, + Location.None, + razorWarningLevelString)); + return razorLanguageVersion.GetDefaultWarningLevel(); } private static (SourceGeneratorProjectItem?, Diagnostic?) ComputeProjectItems((AdditionalText, AnalyzerConfigOptionsProvider) pair, CancellationToken ct)