Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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"));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,22 @@ public void AddDiagnostic(RazorDiagnostic diagnostic)
}

public ImmutableArray<RazorDiagnostic> 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<RazorDiagnostic>(capacity: _diagnostics.Count);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SelectAndOrderByAsArray?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, was SelectAndOrderByAsArray not a thing?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Never mind the old question, that was dumb. But, the new question is:

Is OrderByAsArray stable in netstandard2.0? Does it matter in this context?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like the stability is the same as the original, so never mind

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BTW, it feels like this method is pretty high traffic. In which case, maybe your original change is better.

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)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ public sealed class Builder
/// </summary>
public string? SuppressUniqueIds { get; set; }

/// <summary>
/// Gets or sets the warning level for diagnostic filtering.
/// </summary>
public uint RazorWarningLevel { get; set; }
Comment thread
ToddGrun marked this conversation as resolved.
Outdated

internal Builder()
{
IndentSize = DefaultIndentSize;
Expand Down Expand Up @@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -24,6 +25,7 @@ public sealed partial class RazorCodeGenerationOptions
rootNamespace: null,
cssScope: null,
suppressUniqueIds: null,
razorWarningLevel: 0,
flags: Flags.DefaultDesignTimeFlags);

public int IndentSize { get; }
Expand All @@ -44,6 +46,13 @@ public sealed partial class RazorCodeGenerationOptions
/// </summary>
public string? SuppressUniqueIds { get; }

/// <summary>
/// Gets the warning level for diagnostic filtering. Diagnostics with a
/// <see cref="RazorDiagnosticDescriptor.WarningLevel"/> greater than this value are suppressed.
/// A value of <c>0</c> means only always-on diagnostics (level 0) are reported.
/// </summary>
public uint RazorWarningLevel { get; }

private readonly Flags _flags;

private RazorCodeGenerationOptions(
Expand All @@ -52,13 +61,15 @@ private RazorCodeGenerationOptions(
string? rootNamespace,
string? cssScope,
string? suppressUniqueIds,
uint razorWarningLevel,
Flags flags)
{
IndentSize = indentSize;
NewLine = newLine;
RootNamespace = rootNamespace;
CssScope = cssScope;
SuppressUniqueIds = suppressUniqueIds;
RazorWarningLevel = razorWarningLevel;
_flags = flags;
}

Expand Down Expand Up @@ -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);
Comment thread
ToddGrun marked this conversation as resolved.

public RazorCodeGenerationOptions WithRazorWarningLevel(uint value)
=> RazorWarningLevel == value
? this
: new(IndentSize, NewLine, RootNamespace, CssScope, SuppressUniqueIds, value, _flags);

public RazorCodeGenerationOptions WithFlags(
Optional<bool> designTime = default,
Expand Down Expand Up @@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ public sealed record class RazorConfiguration(
bool UseConsolidatedMvcViews = true,
bool SuppressAddComponentParameter = false,
bool UseRoslynTokenizer = false,
ImmutableArray<string> PreprocessorSymbols = default)
ImmutableArray<string> PreprocessorSymbols = default,
uint RazorWarningLevel = 0)
{
public ImmutableArray<string> PreprocessorSymbols
{
Expand All @@ -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);

Expand All @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ public sealed class RazorDiagnostic : IEquatable<RazorDiagnostic>, IFormattable

public string Id => _descriptor.Id;
public RazorDiagnosticSeverity Severity => _descriptor.Severity;
public uint WarningLevel => _descriptor.WarningLevel;
public SourceSpan Span { get; }

private Checksum? _checksum;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,19 @@ public sealed record RazorDiagnosticDescriptor
public string MessageFormat { get; }
public RazorDiagnosticSeverity Severity { get; }

/// <summary>
/// The warning level at which this diagnostic is reported. Diagnostics with a warning level
/// greater than the configured <c>RazorWarningLevel</c> are suppressed.
/// A value of <c>0</c> means the diagnostic is always reported regardless of the configured level.
/// </summary>
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))
{
Expand All @@ -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}" """;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the extra space at the end intentional?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This wasn't the change that I was expecting.

}
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/// <summary>
/// Gets the default warning level for this language version.
/// The warning level corresponds to the major version number
/// (e.g., <see cref="Version_11_0"/> → <c>11</c>).
/// </summary>
public uint GetDefaultWarningLevel() => (uint)Major;
}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,14 @@ internal static class RazorDiagnostics
DiagnosticSeverity.Error,
isEnabledByDefault: true);

public static readonly DiagnosticDescriptor InvalidRazorWarningLevelDescriptor = new DiagnosticDescriptor(
Comment thread
ToddGrun marked this conversation as resolved.
Outdated
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)),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,12 @@
<data name="InvalidRazorLangMessage" xml:space="preserve">
<value>Invalid value '{0}' for RazorLangVersion. Valid values include 'Latest', 'Preview', or a valid version in range 1.0 to {1}.</value>
</data>
<data name="InvalidRazorWarningLevelTitle" xml:space="preserve">
<value>Invalid RazorWarningLevel</value>
</data>
<data name="InvalidRazorWarningLevelMessage" xml:space="preserve">
<value>Invalid value '{0}' for RazorWarningLevel. Must be empty or a non-negative integer.</value>
</data>
<data name="RecomputingTagHelpersTitle" xml:space="preserve">
<value>Recomputing tag helpers</value>
</data>
Expand Down
Loading