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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
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(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);
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(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(
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(0, 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(11, descriptor.WarningLevel);
}

[Fact]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -92,7 +93,15 @@ 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.
return _diagnostics
.Where(d => d.WarningLevel <= warningLevel)
.OrderByAsArray(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 int RazorWarningLevel { get; set; }

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 int RazorWarningLevel { get; }

private readonly Flags _flags;

private RazorCodeGenerationOptions(
Expand All @@ -52,13 +61,15 @@ private RazorCodeGenerationOptions(
string? rootNamespace,
string? cssScope,
string? suppressUniqueIds,
int 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
=> SuppressUniqueIds == value
? this
: new(IndentSize, NewLine, RootNamespace, CssScope, value, RazorWarningLevel, _flags);
Comment thread
ToddGrun marked this conversation as resolved.

public RazorCodeGenerationOptions WithRazorWarningLevel(int value)
=> RazorWarningLevel == value
? this
: new(IndentSize, NewLine, RootNamespace, CssScope, value, _flags);
: 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,
int 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 int 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 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, int 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}""";
}
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 int GetDefaultWarningLevel() => 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
Loading
Loading