From f6c6877716b79dbbb0c4be5419cc6a9bc0154a55 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 4 Aug 2025 18:34:39 +0000 Subject: [PATCH 01/10] Initial plan From 7f0fdf9d1d180b8b997bc5b2366ff3039f174618 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 4 Aug 2025 18:51:04 +0000 Subject: [PATCH 02/10] Add initial implementation of SupplyParameterFromForm analyzer Co-authored-by: javiercn <6995051+javiercn@users.noreply.github.com> --- .../Analyzers/src/ComponentFacts.cs | 51 +++++ .../Analyzers/src/ComponentSymbols.cs | 13 ++ src/Components/Analyzers/src/ComponentsApi.cs | 12 + .../Analyzers/src/DiagnosticDescriptors.cs | 9 + src/Components/Analyzers/src/Resources.resx | 9 + .../src/SupplyParameterFromFormAnalyzer.cs | 78 +++++++ .../SupplyParameterFromFormAnalyzerTest.cs | 206 ++++++++++++++++++ 7 files changed, 378 insertions(+) create mode 100644 src/Components/Analyzers/src/SupplyParameterFromFormAnalyzer.cs create mode 100644 src/Components/Analyzers/test/SupplyParameterFromFormAnalyzerTest.cs diff --git a/src/Components/Analyzers/src/ComponentFacts.cs b/src/Components/Analyzers/src/ComponentFacts.cs index 43561128b0a5..f7f976dce6dc 100644 --- a/src/Components/Analyzers/src/ComponentFacts.cs +++ b/src/Components/Analyzers/src/ComponentFacts.cs @@ -87,6 +87,57 @@ public static bool IsCascadingParameter(ComponentSymbols symbols, IPropertySymbo return property.GetAttributes().Any(a => SymbolEqualityComparer.Default.Equals(a.AttributeClass, symbols.CascadingParameterAttribute)); } + public static bool IsSupplyParameterFromForm(ComponentSymbols symbols, IPropertySymbol property) + { + if (symbols == null) + { + throw new ArgumentNullException(nameof(symbols)); + } + + if (property == null) + { + throw new ArgumentNullException(nameof(property)); + } + + if (symbols.SupplyParameterFromFormAttribute == null) + { + return false; + } + + return property.GetAttributes().Any(a => SymbolEqualityComparer.Default.Equals(a.AttributeClass, symbols.SupplyParameterFromFormAttribute)); + } + + public static bool IsComponentBase(ComponentSymbols symbols, INamedTypeSymbol type) + { + if (symbols is null) + { + throw new ArgumentNullException(nameof(symbols)); + } + + if (type is null) + { + throw new ArgumentNullException(nameof(type)); + } + + if (symbols.ComponentBaseType == null) + { + return false; + } + + // Check if the type inherits from ComponentBase + var current = type.BaseType; + while (current != null) + { + if (SymbolEqualityComparer.Default.Equals(current, symbols.ComponentBaseType)) + { + return true; + } + current = current.BaseType; + } + + return false; + } + public static bool IsComponent(ComponentSymbols symbols, Compilation compilation, INamedTypeSymbol type) { if (symbols is null) diff --git a/src/Components/Analyzers/src/ComponentSymbols.cs b/src/Components/Analyzers/src/ComponentSymbols.cs index ccdca61d9749..44d8903fbc41 100644 --- a/src/Components/Analyzers/src/ComponentSymbols.cs +++ b/src/Components/Analyzers/src/ComponentSymbols.cs @@ -29,6 +29,9 @@ public static bool TryCreate(Compilation compilation, out ComponentSymbols symbo return false; } + var supplyParameterFromFormAttribute = compilation.GetTypeByMetadataName(ComponentsApi.SupplyParameterFromFormAttribute.MetadataName); + var componentBaseType = compilation.GetTypeByMetadataName(ComponentsApi.ComponentBase.MetadataName); + var icomponentType = compilation.GetTypeByMetadataName(ComponentsApi.IComponent.MetadataName); if (icomponentType == null) { @@ -50,6 +53,8 @@ public static bool TryCreate(Compilation compilation, out ComponentSymbols symbo symbols = new ComponentSymbols( parameterAttribute, cascadingParameterAttribute, + supplyParameterFromFormAttribute, + componentBaseType, parameterCaptureUnmatchedValuesRuntimeType, icomponentType); return true; @@ -58,11 +63,15 @@ public static bool TryCreate(Compilation compilation, out ComponentSymbols symbo private ComponentSymbols( INamedTypeSymbol parameterAttribute, INamedTypeSymbol cascadingParameterAttribute, + INamedTypeSymbol supplyParameterFromFormAttribute, + INamedTypeSymbol componentBaseType, INamedTypeSymbol parameterCaptureUnmatchedValuesRuntimeType, INamedTypeSymbol icomponentType) { ParameterAttribute = parameterAttribute; CascadingParameterAttribute = cascadingParameterAttribute; + SupplyParameterFromFormAttribute = supplyParameterFromFormAttribute; + ComponentBaseType = componentBaseType; ParameterCaptureUnmatchedValuesRuntimeType = parameterCaptureUnmatchedValuesRuntimeType; IComponentType = icomponentType; } @@ -74,5 +83,9 @@ private ComponentSymbols( public INamedTypeSymbol CascadingParameterAttribute { get; } + public INamedTypeSymbol SupplyParameterFromFormAttribute { get; } + + public INamedTypeSymbol ComponentBaseType { get; } + public INamedTypeSymbol IComponentType { get; } } diff --git a/src/Components/Analyzers/src/ComponentsApi.cs b/src/Components/Analyzers/src/ComponentsApi.cs index a62d070dedef..361e99b3e146 100644 --- a/src/Components/Analyzers/src/ComponentsApi.cs +++ b/src/Components/Analyzers/src/ComponentsApi.cs @@ -23,6 +23,18 @@ public static class CascadingParameterAttribute public const string MetadataName = FullTypeName; } + public static class SupplyParameterFromFormAttribute + { + public const string FullTypeName = "Microsoft.AspNetCore.Components.SupplyParameterFromFormAttribute"; + public const string MetadataName = FullTypeName; + } + + public static class ComponentBase + { + public const string FullTypeName = "Microsoft.AspNetCore.Components.ComponentBase"; + public const string MetadataName = FullTypeName; + } + public static class IComponent { public const string FullTypeName = "Microsoft.AspNetCore.Components.IComponent"; diff --git a/src/Components/Analyzers/src/DiagnosticDescriptors.cs b/src/Components/Analyzers/src/DiagnosticDescriptors.cs index ea3332c56fc7..afba1e3acd50 100644 --- a/src/Components/Analyzers/src/DiagnosticDescriptors.cs +++ b/src/Components/Analyzers/src/DiagnosticDescriptors.cs @@ -74,4 +74,13 @@ internal static class DiagnosticDescriptors Usage, DiagnosticSeverity.Warning, isEnabledByDefault: true); + + public static readonly DiagnosticDescriptor SupplyParameterFromFormShouldNotHavePropertyInitializer = new( + "BL0008", + CreateLocalizableResourceString(nameof(Resources.SupplyParameterFromFormShouldNotHavePropertyInitializer_Title)), + CreateLocalizableResourceString(nameof(Resources.SupplyParameterFromFormShouldNotHavePropertyInitializer_Format)), + Usage, + DiagnosticSeverity.Warning, + isEnabledByDefault: true, + description: CreateLocalizableResourceString(nameof(Resources.SupplyParameterFromFormShouldNotHavePropertyInitializer_Description))); } diff --git a/src/Components/Analyzers/src/Resources.resx b/src/Components/Analyzers/src/Resources.resx index 5ef9f54b53eb..047427744b79 100644 --- a/src/Components/Analyzers/src/Resources.resx +++ b/src/Components/Analyzers/src/Resources.resx @@ -180,4 +180,13 @@ Component parameters should be auto properties + + Initializing a property decorated with [SupplyParameterFromForm] can be overwritten with null during a form post. To ensure the property is never null, move the initialization to a component lifecycle method. + + + Property '{0}' has [SupplyParameterFromForm] and a property initializer. This can be overwritten with null during form posts. + + + Property with [SupplyParameterFromForm] should not have initializer + \ No newline at end of file diff --git a/src/Components/Analyzers/src/SupplyParameterFromFormAnalyzer.cs b/src/Components/Analyzers/src/SupplyParameterFromFormAnalyzer.cs new file mode 100644 index 000000000000..d81dfc70a567 --- /dev/null +++ b/src/Components/Analyzers/src/SupplyParameterFromFormAnalyzer.cs @@ -0,0 +1,78 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Immutable; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; + +#nullable enable + +namespace Microsoft.AspNetCore.Components.Analyzers; + +[DiagnosticAnalyzer(LanguageNames.CSharp)] +public sealed class SupplyParameterFromFormAnalyzer : DiagnosticAnalyzer +{ + public SupplyParameterFromFormAnalyzer() + { + SupportedDiagnostics = ImmutableArray.Create( + DiagnosticDescriptors.SupplyParameterFromFormShouldNotHavePropertyInitializer); + } + + public override ImmutableArray SupportedDiagnostics { get; } + + public override void Initialize(AnalysisContext context) + { + context.EnableConcurrentExecution(); + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics); + context.RegisterCompilationStartAction(context => + { + if (!ComponentSymbols.TryCreate(context.Compilation, out var symbols)) + { + // Types we need are not defined. + return; + } + + context.RegisterSyntaxNodeAction(context => + { + var propertyDeclaration = (PropertyDeclarationSyntax)context.Node; + + // Check if property has an initializer + if (propertyDeclaration.Initializer == null) + { + return; + } + + var propertySymbol = context.SemanticModel.GetDeclaredSymbol(propertyDeclaration); + if (propertySymbol == null) + { + return; + } + + // Check if property has [SupplyParameterFromForm] attribute + if (!ComponentFacts.IsSupplyParameterFromForm(symbols, propertySymbol)) + { + return; + } + + // Check if the containing type inherits from ComponentBase + var containingType = propertySymbol.ContainingType; + if (!ComponentFacts.IsComponentBase(symbols, containingType)) + { + return; + } + + var propertyLocation = propertySymbol.Locations.FirstOrDefault(); + if (propertyLocation != null) + { + context.ReportDiagnostic(Diagnostic.Create( + DiagnosticDescriptors.SupplyParameterFromFormShouldNotHavePropertyInitializer, + propertyLocation, + propertySymbol.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat))); + } + }, SyntaxKind.PropertyDeclaration); + }); + } +} \ No newline at end of file diff --git a/src/Components/Analyzers/test/SupplyParameterFromFormAnalyzerTest.cs b/src/Components/Analyzers/test/SupplyParameterFromFormAnalyzerTest.cs new file mode 100644 index 000000000000..cb909348f9c6 --- /dev/null +++ b/src/Components/Analyzers/test/SupplyParameterFromFormAnalyzerTest.cs @@ -0,0 +1,206 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using TestHelper; + +namespace Microsoft.AspNetCore.Components.Analyzers.Test; + +public class SupplyParameterFromFormAnalyzerTest : DiagnosticVerifier +{ + protected override DiagnosticAnalyzer GetCSharpDiagnosticAnalyzer() => new SupplyParameterFromFormAnalyzer(); + + private static readonly string TestDeclarations = $@" + namespace {typeof(ParameterAttribute).Namespace} + {{ + public class {typeof(ParameterAttribute).Name} : System.Attribute + {{ + public bool CaptureUnmatchedValues {{ get; set; }} + }} + + public class {typeof(CascadingParameterAttribute).Name} : System.Attribute + {{ + }} + + public class SupplyParameterFromFormAttribute : System.Attribute + {{ + public string Name {{ get; set; }} + public string FormName {{ get; set; }} + }} + + public interface {typeof(IComponent).Name} + {{ + }} + + public abstract class ComponentBase : {typeof(IComponent).Name} + {{ + }} + }} +"; + + [Fact] + public void IgnoresPropertiesWithoutSupplyParameterFromFormAttribute() + { + var test = $@" + namespace ConsoleApplication1 + {{ + using {typeof(ParameterAttribute).Namespace}; + class TestComponent : ComponentBase + {{ + public string MyProperty {{ get; set; }} = ""default""; + }} + }}" + TestDeclarations; + + VerifyCSharpDiagnostic(test); + } + + [Fact] + public void IgnoresSupplyParameterFromFormWithoutInitializer() + { + var test = $@" + namespace ConsoleApplication1 + {{ + using {typeof(ParameterAttribute).Namespace}; + class TestComponent : ComponentBase + {{ + [SupplyParameterFromForm] public string MyProperty {{ get; set; }} + }} + }}" + TestDeclarations; + + VerifyCSharpDiagnostic(test); + } + + [Fact] + public void IgnoresNonComponentBaseClasses() + { + var test = $@" + namespace ConsoleApplication1 + {{ + using {typeof(ParameterAttribute).Namespace}; + class NotAComponent + {{ + [SupplyParameterFromForm] public string MyProperty {{ get; set; }} = ""default""; + }} + }}" + TestDeclarations; + + VerifyCSharpDiagnostic(test); + } + + [Fact] + public void ReportsWarningForSupplyParameterFromFormWithInitializer() + { + var test = $@" + namespace ConsoleApplication1 + {{ + using {typeof(ParameterAttribute).Namespace}; + class TestComponent : ComponentBase + {{ + [SupplyParameterFromForm] public string MyProperty {{ get; set; }} = ""default""; + }} + }}" + TestDeclarations; + + var expected = new DiagnosticResult + { + Id = "BL0008", + Message = "Property 'ConsoleApplication1.TestComponent.MyProperty' has [SupplyParameterFromForm] and a property initializer. This can be overwritten with null during form posts.", + Severity = DiagnosticSeverity.Warning, + Locations = new[] + { + new DiagnosticResultLocation("Test0.cs", 6, 50) + } + }; + + VerifyCSharpDiagnostic(test, expected); + } + + [Fact] + public void ReportsWarningForSupplyParameterFromFormWithObjectInitializer() + { + var test = $@" + namespace ConsoleApplication1 + {{ + using {typeof(ParameterAttribute).Namespace}; + class TestComponent : ComponentBase + {{ + [SupplyParameterFromForm] public InputModel Input {{ get; set; }} = new InputModel(); + }} + + class InputModel + {{ + public string Value {{ get; set; }} = """"; + }} + }}" + TestDeclarations; + + var expected = new DiagnosticResult + { + Id = "BL0008", + Message = "Property 'ConsoleApplication1.TestComponent.Input' has [SupplyParameterFromForm] and a property initializer. This can be overwritten with null during form posts.", + Severity = DiagnosticSeverity.Warning, + Locations = new[] + { + new DiagnosticResultLocation("Test0.cs", 6, 54) + } + }; + + VerifyCSharpDiagnostic(test, expected); + } + + [Fact] + public void ReportsWarningWithFormNameSpecified() + { + var test = $@" + namespace ConsoleApplication1 + {{ + using {typeof(ParameterAttribute).Namespace}; + class TestComponent : ComponentBase + {{ + [SupplyParameterFromForm(FormName = ""main-form"")] public string MyProperty {{ get; set; }} = ""default""; + }} + }}" + TestDeclarations; + + var expected = new DiagnosticResult + { + Id = "BL0008", + Message = "Property 'ConsoleApplication1.TestComponent.MyProperty' has [SupplyParameterFromForm] and a property initializer. This can be overwritten with null during form posts.", + Severity = DiagnosticSeverity.Warning, + Locations = new[] + { + new DiagnosticResultLocation("Test0.cs", 6, 80) + } + }; + + VerifyCSharpDiagnostic(test, expected); + } + + [Fact] + public void WorksWithInheritedComponentBase() + { + var test = $@" + namespace ConsoleApplication1 + {{ + using {typeof(ParameterAttribute).Namespace}; + class BaseComponent : ComponentBase + {{ + }} + + class TestComponent : BaseComponent + {{ + [SupplyParameterFromForm] public string MyProperty {{ get; set; }} = ""default""; + }} + }}" + TestDeclarations; + + var expected = new DiagnosticResult + { + Id = "BL0008", + Message = "Property 'ConsoleApplication1.TestComponent.MyProperty' has [SupplyParameterFromForm] and a property initializer. This can be overwritten with null during form posts.", + Severity = DiagnosticSeverity.Warning, + Locations = new[] + { + new DiagnosticResultLocation("Test0.cs", 11, 50) + } + }; + + VerifyCSharpDiagnostic(test, expected); + } +} \ No newline at end of file From 8f7c6398901e33e4c9d3579d51b6620b1b218d11 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 4 Aug 2025 19:01:38 +0000 Subject: [PATCH 03/10] Complete implementation and testing of SupplyParameterFromForm analyzer Co-authored-by: javiercn <6995051+javiercn@users.noreply.github.com> --- src/Components/Analyzers/src/ComponentSymbols.cs | 15 ++++++++------- .../test/SupplyParameterFromFormAnalyzerTest.cs | 8 ++++---- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/Components/Analyzers/src/ComponentSymbols.cs b/src/Components/Analyzers/src/ComponentSymbols.cs index 44d8903fbc41..e28da74f28d8 100644 --- a/src/Components/Analyzers/src/ComponentSymbols.cs +++ b/src/Components/Analyzers/src/ComponentSymbols.cs @@ -29,9 +29,6 @@ public static bool TryCreate(Compilation compilation, out ComponentSymbols symbo return false; } - var supplyParameterFromFormAttribute = compilation.GetTypeByMetadataName(ComponentsApi.SupplyParameterFromFormAttribute.MetadataName); - var componentBaseType = compilation.GetTypeByMetadataName(ComponentsApi.ComponentBase.MetadataName); - var icomponentType = compilation.GetTypeByMetadataName(ComponentsApi.IComponent.MetadataName); if (icomponentType == null) { @@ -50,6 +47,10 @@ public static bool TryCreate(Compilation compilation, out ComponentSymbols symbo var parameterCaptureUnmatchedValuesRuntimeType = dictionary.Construct(@string, @object); + // Try to get optional symbols for SupplyParameterFromForm analyzer + var supplyParameterFromFormAttribute = compilation.GetTypeByMetadataName(ComponentsApi.SupplyParameterFromFormAttribute.MetadataName); + var componentBaseType = compilation.GetTypeByMetadataName(ComponentsApi.ComponentBase.MetadataName); + symbols = new ComponentSymbols( parameterAttribute, cascadingParameterAttribute, @@ -70,8 +71,8 @@ private ComponentSymbols( { ParameterAttribute = parameterAttribute; CascadingParameterAttribute = cascadingParameterAttribute; - SupplyParameterFromFormAttribute = supplyParameterFromFormAttribute; - ComponentBaseType = componentBaseType; + SupplyParameterFromFormAttribute = supplyParameterFromFormAttribute; // Can be null + ComponentBaseType = componentBaseType; // Can be null ParameterCaptureUnmatchedValuesRuntimeType = parameterCaptureUnmatchedValuesRuntimeType; IComponentType = icomponentType; } @@ -83,9 +84,9 @@ private ComponentSymbols( public INamedTypeSymbol CascadingParameterAttribute { get; } - public INamedTypeSymbol SupplyParameterFromFormAttribute { get; } + public INamedTypeSymbol SupplyParameterFromFormAttribute { get; } // Can be null if not available - public INamedTypeSymbol ComponentBaseType { get; } + public INamedTypeSymbol ComponentBaseType { get; } // Can be null if not available public INamedTypeSymbol IComponentType { get; } } diff --git a/src/Components/Analyzers/test/SupplyParameterFromFormAnalyzerTest.cs b/src/Components/Analyzers/test/SupplyParameterFromFormAnalyzerTest.cs index cb909348f9c6..3e6a65d8c92f 100644 --- a/src/Components/Analyzers/test/SupplyParameterFromFormAnalyzerTest.cs +++ b/src/Components/Analyzers/test/SupplyParameterFromFormAnalyzerTest.cs @@ -107,7 +107,7 @@ class TestComponent : ComponentBase Severity = DiagnosticSeverity.Warning, Locations = new[] { - new DiagnosticResultLocation("Test0.cs", 6, 50) + new DiagnosticResultLocation("Test0.cs", 7, 53) } }; @@ -139,7 +139,7 @@ class InputModel Severity = DiagnosticSeverity.Warning, Locations = new[] { - new DiagnosticResultLocation("Test0.cs", 6, 54) + new DiagnosticResultLocation("Test0.cs", 7, 57) } }; @@ -166,7 +166,7 @@ class TestComponent : ComponentBase Severity = DiagnosticSeverity.Warning, Locations = new[] { - new DiagnosticResultLocation("Test0.cs", 6, 80) + new DiagnosticResultLocation("Test0.cs", 7, 77) } }; @@ -197,7 +197,7 @@ class TestComponent : BaseComponent Severity = DiagnosticSeverity.Warning, Locations = new[] { - new DiagnosticResultLocation("Test0.cs", 11, 50) + new DiagnosticResultLocation("Test0.cs", 11, 53) } }; From 93a68faf5323d47442acba822208dfdf1bdf3203 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 4 Aug 2025 20:49:35 +0000 Subject: [PATCH 04/10] Update Resources.resx description per feedback Co-authored-by: javiercn <6995051+javiercn@users.noreply.github.com> --- src/Components/Analyzers/src/Resources.resx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Components/Analyzers/src/Resources.resx b/src/Components/Analyzers/src/Resources.resx index 047427744b79..3cb016960cae 100644 --- a/src/Components/Analyzers/src/Resources.resx +++ b/src/Components/Analyzers/src/Resources.resx @@ -181,7 +181,7 @@ Component parameters should be auto properties - Initializing a property decorated with [SupplyParameterFromForm] can be overwritten with null during a form post. To ensure the property is never null, move the initialization to a component lifecycle method. + The value of a property decorated with [SupplyParameterFromForm] and initialized with a property initializer can be overwritten with null when the component receives parameters. Property '{0}' has [SupplyParameterFromForm] and a property initializer. This can be overwritten with null during form posts. From 0305c3f8161ebc7dad4d69f5b70176b7ad9f16fe Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 4 Aug 2025 21:00:08 +0000 Subject: [PATCH 05/10] Update Resources.resx description with complete suggested text including lifecycle method recommendation Co-authored-by: javiercn <6995051+javiercn@users.noreply.github.com> --- src/Components/Analyzers/src/Resources.resx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Components/Analyzers/src/Resources.resx b/src/Components/Analyzers/src/Resources.resx index 3cb016960cae..a6dc20636cc6 100644 --- a/src/Components/Analyzers/src/Resources.resx +++ b/src/Components/Analyzers/src/Resources.resx @@ -181,7 +181,7 @@ Component parameters should be auto properties - The value of a property decorated with [SupplyParameterFromForm] and initialized with a property initializer can be overwritten with null when the component receives parameters. + The value of a property decorated with [SupplyParameterFromForm] and initialized with a property initializer can be overwritten with null when the component receives parameters. To ensure the initialized value is not overwritten, move the initialization to a component lifecycle method like OnInitialized or OnInitializedAsync Property '{0}' has [SupplyParameterFromForm] and a property initializer. This can be overwritten with null during form posts. From 3c8fd5ae915fcb8da8577466ab0567dab59de895 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 4 Aug 2025 21:10:44 +0000 Subject: [PATCH 06/10] Remove irrelevant test that checks FormName parameter Co-authored-by: javiercn <6995051+javiercn@users.noreply.github.com> --- .../SupplyParameterFromFormAnalyzerTest.cs | 27 ------------------- 1 file changed, 27 deletions(-) diff --git a/src/Components/Analyzers/test/SupplyParameterFromFormAnalyzerTest.cs b/src/Components/Analyzers/test/SupplyParameterFromFormAnalyzerTest.cs index 3e6a65d8c92f..abbb194a27d8 100644 --- a/src/Components/Analyzers/test/SupplyParameterFromFormAnalyzerTest.cs +++ b/src/Components/Analyzers/test/SupplyParameterFromFormAnalyzerTest.cs @@ -146,33 +146,6 @@ class InputModel VerifyCSharpDiagnostic(test, expected); } - [Fact] - public void ReportsWarningWithFormNameSpecified() - { - var test = $@" - namespace ConsoleApplication1 - {{ - using {typeof(ParameterAttribute).Namespace}; - class TestComponent : ComponentBase - {{ - [SupplyParameterFromForm(FormName = ""main-form"")] public string MyProperty {{ get; set; }} = ""default""; - }} - }}" + TestDeclarations; - - var expected = new DiagnosticResult - { - Id = "BL0008", - Message = "Property 'ConsoleApplication1.TestComponent.MyProperty' has [SupplyParameterFromForm] and a property initializer. This can be overwritten with null during form posts.", - Severity = DiagnosticSeverity.Warning, - Locations = new[] - { - new DiagnosticResultLocation("Test0.cs", 7, 77) - } - }; - - VerifyCSharpDiagnostic(test, expected); - } - [Fact] public void WorksWithInheritedComponentBase() { From 28d5aba5b9736f2025580eff6c9bec6f207b17b8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 4 Aug 2025 22:22:09 +0000 Subject: [PATCH 07/10] Fix template files to follow BL0008 analyzer guidance Co-authored-by: javiercn <6995051+javiercn@users.noreply.github.com> --- .../Components/Account/Pages/ExternalLogin.razor | 2 +- .../Components/Account/Pages/ForgotPassword.razor | 2 +- .../BlazorWebCSharp.1/Components/Account/Pages/Login.razor | 2 +- .../Components/Account/Pages/LoginWith2fa.razor | 2 +- .../Components/Account/Pages/LoginWithRecoveryCode.razor | 2 +- .../Components/Account/Pages/Manage/ChangePassword.razor | 2 +- .../Components/Account/Pages/Manage/DeletePersonalData.razor | 2 +- .../Components/Account/Pages/Manage/Email.razor | 2 +- .../Components/Account/Pages/Manage/EnableAuthenticator.razor | 2 +- .../Components/Account/Pages/Manage/Index.razor | 2 +- .../Components/Account/Pages/Manage/Passkeys.razor | 2 +- .../Components/Account/Pages/Manage/RenamePasskey.razor | 2 +- .../Components/Account/Pages/Manage/SetPassword.razor | 2 +- .../BlazorWebCSharp.1/Components/Account/Pages/Register.razor | 2 +- .../Components/Account/Pages/ResendEmailConfirmation.razor | 2 +- .../Components/Account/Pages/ResetPassword.razor | 2 +- 16 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/ExternalLogin.razor b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/ExternalLogin.razor index 0c30821599fa..58b34581ae7e 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/ExternalLogin.razor +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/ExternalLogin.razor @@ -54,7 +54,7 @@ private HttpContext HttpContext { get; set; } = default!; [SupplyParameterFromForm] - private InputModel Input { get; set; } = default!; + private InputModel Input { get; set; } [SupplyParameterFromQuery] private string? RemoteError { get; set; } diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/ForgotPassword.razor b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/ForgotPassword.razor index ae82692ab7e9..a63ec90b244f 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/ForgotPassword.razor +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/ForgotPassword.razor @@ -35,7 +35,7 @@ @code { [SupplyParameterFromForm] - private InputModel Input { get; set; } = default!; + private InputModel Input { get; set; } protected override void OnInitialized() { diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/Login.razor b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/Login.razor index 65347e0afdb6..32e78dae8f81 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/Login.razor +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/Login.razor @@ -79,7 +79,7 @@ private HttpContext HttpContext { get; set; } = default!; [SupplyParameterFromForm] - private InputModel Input { get; set; } = default!; + private InputModel Input { get; set; } [SupplyParameterFromQuery] private string? ReturnUrl { get; set; } diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/LoginWith2fa.razor b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/LoginWith2fa.razor index 3d7243c9b775..a21436d993b0 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/LoginWith2fa.razor +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/LoginWith2fa.razor @@ -49,7 +49,7 @@ private ApplicationUser user = default!; [SupplyParameterFromForm] - private InputModel Input { get; set; } = default!; + private InputModel Input { get; set; } [SupplyParameterFromQuery] private string? ReturnUrl { get; set; } diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/LoginWithRecoveryCode.razor b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/LoginWithRecoveryCode.razor index f99d02900b12..313395ac9bde 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/LoginWithRecoveryCode.razor +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/LoginWithRecoveryCode.razor @@ -38,7 +38,7 @@ private ApplicationUser user = default!; [SupplyParameterFromForm] - private InputModel Input { get; set; } = default!; + private InputModel Input { get; set; } [SupplyParameterFromQuery] private string? ReturnUrl { get; set; } diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/Manage/ChangePassword.razor b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/Manage/ChangePassword.razor index c035566ae85b..ca8561add275 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/Manage/ChangePassword.razor +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/Manage/ChangePassword.razor @@ -48,7 +48,7 @@ private HttpContext HttpContext { get; set; } = default!; [SupplyParameterFromForm] - private InputModel Input { get; set; } = default!; + private InputModel Input { get; set; } protected override async Task OnInitializedAsync() { diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/Manage/DeletePersonalData.razor b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/Manage/DeletePersonalData.razor index f9c247897223..2982bafa7407 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/Manage/DeletePersonalData.razor +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/Manage/DeletePersonalData.razor @@ -46,7 +46,7 @@ private HttpContext HttpContext { get; set; } = default!; [SupplyParameterFromForm] - private InputModel Input { get; set; } = default!; + private InputModel Input { get; set; } protected override async Task OnInitializedAsync() { diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/Manage/Email.razor b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/Manage/Email.razor index 94f6b6a473ca..c45829e6d4b6 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/Manage/Email.razor +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/Manage/Email.razor @@ -63,7 +63,7 @@ private HttpContext HttpContext { get; set; } = default!; [SupplyParameterFromForm(FormName = "change-email")] - private InputModel Input { get; set; } = default!; + private InputModel Input { get; set; } protected override async Task OnInitializedAsync() { diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/Manage/EnableAuthenticator.razor b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/Manage/EnableAuthenticator.razor index 588134b5fa5a..3316bac84a74 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/Manage/EnableAuthenticator.razor +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/Manage/EnableAuthenticator.razor @@ -78,7 +78,7 @@ else private HttpContext HttpContext { get; set; } = default!; [SupplyParameterFromForm] - private InputModel Input { get; set; } = default!; + private InputModel Input { get; set; } protected override async Task OnInitializedAsync() { diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/Manage/Index.razor b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/Manage/Index.razor index 13faf41804f2..bad84305bcbf 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/Manage/Index.razor +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/Manage/Index.razor @@ -41,7 +41,7 @@ private HttpContext HttpContext { get; set; } = default!; [SupplyParameterFromForm] - private InputModel Input { get; set; } = default!; + private InputModel Input { get; set; } protected override async Task OnInitializedAsync() { diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/Manage/Passkeys.razor b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/Manage/Passkeys.razor index 283f26de1897..210e71dd3614 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/Manage/Passkeys.razor +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/Manage/Passkeys.razor @@ -65,7 +65,7 @@ else private string? CredentialId { get; set; } [SupplyParameterFromForm(FormName = "add-passkey")] - private PasskeyInputModel Input { get; set; } = default!; + private PasskeyInputModel Input { get; set; } protected override async Task OnInitializedAsync() { diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/Manage/RenamePasskey.razor b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/Manage/RenamePasskey.razor index 80595b5bcc1e..b88b63240bf6 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/Manage/RenamePasskey.razor +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/Manage/RenamePasskey.razor @@ -41,7 +41,7 @@ public string? Id { get; set; } [SupplyParameterFromForm] - private InputModel Input { get; set; } = default!; + private InputModel Input { get; set; } protected override async Task OnInitializedAsync() { diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/Manage/SetPassword.razor b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/Manage/SetPassword.razor index 180b4d77a4cc..c7f01ca3ac1b 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/Manage/SetPassword.razor +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/Manage/SetPassword.razor @@ -44,7 +44,7 @@ private HttpContext HttpContext { get; set; } = default!; [SupplyParameterFromForm] - private InputModel Input { get; set; } = default!; + private InputModel Input { get; set; } protected override async Task OnInitializedAsync() { diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/Register.razor b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/Register.razor index bee8e0530a88..5f395be6a1ab 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/Register.razor +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/Register.razor @@ -58,7 +58,7 @@ private IEnumerable? identityErrors; [SupplyParameterFromForm] - private InputModel Input { get; set; } = default!; + private InputModel Input { get; set; } [SupplyParameterFromQuery] private string? ReturnUrl { get; set; } diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/ResendEmailConfirmation.razor b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/ResendEmailConfirmation.razor index b6fdb411ec8a..68d38a05c057 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/ResendEmailConfirmation.razor +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/ResendEmailConfirmation.razor @@ -37,7 +37,7 @@ private string? message; [SupplyParameterFromForm] - private InputModel Input { get; set; } = default!; + private InputModel Input { get; set; } protected override void OnInitialized() { diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/ResetPassword.razor b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/ResetPassword.razor index 74f258a1c518..a6008f73f01b 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/ResetPassword.razor +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/ResetPassword.razor @@ -46,7 +46,7 @@ private IEnumerable? identityErrors; [SupplyParameterFromForm] - private InputModel Input { get; set; } = default!; + private InputModel Input { get; set; } [SupplyParameterFromQuery] private string? Code { get; set; } From 41245e2aff7edb9c8acaae97f256e2b581e494db Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 5 Aug 2025 07:25:52 +0000 Subject: [PATCH 08/10] Update analyzer to ignore default value initializers and revert template changes - Reverted template files to original state with `= default!` initializers - Modified SupplyParameterFromFormAnalyzer to ignore default value initializers (null, null!, default, default!) - Added comprehensive tests for ignored cases: = null, = null!, = default, = default! - All 49 analyzer tests pass, confirming correct behavior Co-authored-by: javiercn <6995051+javiercn@users.noreply.github.com> --- .../src/SupplyParameterFromFormAnalyzer.cs | 23 +++++++ .../SupplyParameterFromFormAnalyzerTest.cs | 64 +++++++++++++++++++ .../Account/Pages/ExternalLogin.razor | 2 +- .../Account/Pages/ForgotPassword.razor | 2 +- .../Components/Account/Pages/Login.razor | 2 +- .../Account/Pages/LoginWith2fa.razor | 2 +- .../Account/Pages/LoginWithRecoveryCode.razor | 2 +- .../Account/Pages/Manage/ChangePassword.razor | 2 +- .../Pages/Manage/DeletePersonalData.razor | 2 +- .../Account/Pages/Manage/Email.razor | 2 +- .../Pages/Manage/EnableAuthenticator.razor | 2 +- .../Account/Pages/Manage/Index.razor | 2 +- .../Account/Pages/Manage/Passkeys.razor | 2 +- .../Account/Pages/Manage/RenamePasskey.razor | 2 +- .../Account/Pages/Manage/SetPassword.razor | 2 +- .../Components/Account/Pages/Register.razor | 2 +- .../Pages/ResendEmailConfirmation.razor | 2 +- .../Account/Pages/ResetPassword.razor | 2 +- 18 files changed, 103 insertions(+), 16 deletions(-) diff --git a/src/Components/Analyzers/src/SupplyParameterFromFormAnalyzer.cs b/src/Components/Analyzers/src/SupplyParameterFromFormAnalyzer.cs index d81dfc70a567..00bbbcaa1180 100644 --- a/src/Components/Analyzers/src/SupplyParameterFromFormAnalyzer.cs +++ b/src/Components/Analyzers/src/SupplyParameterFromFormAnalyzer.cs @@ -45,6 +45,12 @@ public override void Initialize(AnalysisContext context) return; } + // Ignore initializers that set to default values (null, default, etc.) + if (IsDefaultValueInitializer(propertyDeclaration.Initializer.Value)) + { + return; + } + var propertySymbol = context.SemanticModel.GetDeclaredSymbol(propertyDeclaration); if (propertySymbol == null) { @@ -75,4 +81,21 @@ public override void Initialize(AnalysisContext context) }, SyntaxKind.PropertyDeclaration); }); } + + private static bool IsDefaultValueInitializer(ExpressionSyntax expression) + { + return expression switch + { + // null + LiteralExpressionSyntax { Token.ValueText: "null" } => true, + // null! + PostfixUnaryExpressionSyntax { Operand: LiteralExpressionSyntax { Token.ValueText: "null" }, OperatorToken.ValueText: "!" } => true, + // default + LiteralExpressionSyntax literal when literal.Token.IsKind(SyntaxKind.DefaultKeyword) => true, + // default! + PostfixUnaryExpressionSyntax { Operand: LiteralExpressionSyntax literal, OperatorToken.ValueText: "!" } + when literal.Token.IsKind(SyntaxKind.DefaultKeyword) => true, + _ => false + }; + } } \ No newline at end of file diff --git a/src/Components/Analyzers/test/SupplyParameterFromFormAnalyzerTest.cs b/src/Components/Analyzers/test/SupplyParameterFromFormAnalyzerTest.cs index abbb194a27d8..9151027394d4 100644 --- a/src/Components/Analyzers/test/SupplyParameterFromFormAnalyzerTest.cs +++ b/src/Components/Analyzers/test/SupplyParameterFromFormAnalyzerTest.cs @@ -146,6 +146,70 @@ class InputModel VerifyCSharpDiagnostic(test, expected); } + [Fact] + public void IgnoresSupplyParameterFromFormWithNullInitializer() + { + var test = $@" + namespace ConsoleApplication1 + {{ + using {typeof(ParameterAttribute).Namespace}; + class TestComponent : ComponentBase + {{ + [SupplyParameterFromForm] public string MyProperty {{ get; set; }} = null; + }} + }}" + TestDeclarations; + + VerifyCSharpDiagnostic(test); + } + + [Fact] + public void IgnoresSupplyParameterFromFormWithNullForgivingInitializer() + { + var test = $@" + namespace ConsoleApplication1 + {{ + using {typeof(ParameterAttribute).Namespace}; + class TestComponent : ComponentBase + {{ + [SupplyParameterFromForm] public string MyProperty {{ get; set; }} = null!; + }} + }}" + TestDeclarations; + + VerifyCSharpDiagnostic(test); + } + + [Fact] + public void IgnoresSupplyParameterFromFormWithDefaultInitializer() + { + var test = $@" + namespace ConsoleApplication1 + {{ + using {typeof(ParameterAttribute).Namespace}; + class TestComponent : ComponentBase + {{ + [SupplyParameterFromForm] public string MyProperty {{ get; set; }} = default; + }} + }}" + TestDeclarations; + + VerifyCSharpDiagnostic(test); + } + + [Fact] + public void IgnoresSupplyParameterFromFormWithDefaultForgivingInitializer() + { + var test = $@" + namespace ConsoleApplication1 + {{ + using {typeof(ParameterAttribute).Namespace}; + class TestComponent : ComponentBase + {{ + [SupplyParameterFromForm] public string MyProperty {{ get; set; }} = default!; + }} + }}" + TestDeclarations; + + VerifyCSharpDiagnostic(test); + } + [Fact] public void WorksWithInheritedComponentBase() { diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/ExternalLogin.razor b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/ExternalLogin.razor index 58b34581ae7e..0c30821599fa 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/ExternalLogin.razor +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/ExternalLogin.razor @@ -54,7 +54,7 @@ private HttpContext HttpContext { get; set; } = default!; [SupplyParameterFromForm] - private InputModel Input { get; set; } + private InputModel Input { get; set; } = default!; [SupplyParameterFromQuery] private string? RemoteError { get; set; } diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/ForgotPassword.razor b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/ForgotPassword.razor index a63ec90b244f..ae82692ab7e9 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/ForgotPassword.razor +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/ForgotPassword.razor @@ -35,7 +35,7 @@ @code { [SupplyParameterFromForm] - private InputModel Input { get; set; } + private InputModel Input { get; set; } = default!; protected override void OnInitialized() { diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/Login.razor b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/Login.razor index 32e78dae8f81..65347e0afdb6 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/Login.razor +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/Login.razor @@ -79,7 +79,7 @@ private HttpContext HttpContext { get; set; } = default!; [SupplyParameterFromForm] - private InputModel Input { get; set; } + private InputModel Input { get; set; } = default!; [SupplyParameterFromQuery] private string? ReturnUrl { get; set; } diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/LoginWith2fa.razor b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/LoginWith2fa.razor index a21436d993b0..3d7243c9b775 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/LoginWith2fa.razor +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/LoginWith2fa.razor @@ -49,7 +49,7 @@ private ApplicationUser user = default!; [SupplyParameterFromForm] - private InputModel Input { get; set; } + private InputModel Input { get; set; } = default!; [SupplyParameterFromQuery] private string? ReturnUrl { get; set; } diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/LoginWithRecoveryCode.razor b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/LoginWithRecoveryCode.razor index 313395ac9bde..f99d02900b12 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/LoginWithRecoveryCode.razor +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/LoginWithRecoveryCode.razor @@ -38,7 +38,7 @@ private ApplicationUser user = default!; [SupplyParameterFromForm] - private InputModel Input { get; set; } + private InputModel Input { get; set; } = default!; [SupplyParameterFromQuery] private string? ReturnUrl { get; set; } diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/Manage/ChangePassword.razor b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/Manage/ChangePassword.razor index ca8561add275..c035566ae85b 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/Manage/ChangePassword.razor +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/Manage/ChangePassword.razor @@ -48,7 +48,7 @@ private HttpContext HttpContext { get; set; } = default!; [SupplyParameterFromForm] - private InputModel Input { get; set; } + private InputModel Input { get; set; } = default!; protected override async Task OnInitializedAsync() { diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/Manage/DeletePersonalData.razor b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/Manage/DeletePersonalData.razor index 2982bafa7407..f9c247897223 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/Manage/DeletePersonalData.razor +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/Manage/DeletePersonalData.razor @@ -46,7 +46,7 @@ private HttpContext HttpContext { get; set; } = default!; [SupplyParameterFromForm] - private InputModel Input { get; set; } + private InputModel Input { get; set; } = default!; protected override async Task OnInitializedAsync() { diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/Manage/Email.razor b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/Manage/Email.razor index c45829e6d4b6..94f6b6a473ca 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/Manage/Email.razor +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/Manage/Email.razor @@ -63,7 +63,7 @@ private HttpContext HttpContext { get; set; } = default!; [SupplyParameterFromForm(FormName = "change-email")] - private InputModel Input { get; set; } + private InputModel Input { get; set; } = default!; protected override async Task OnInitializedAsync() { diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/Manage/EnableAuthenticator.razor b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/Manage/EnableAuthenticator.razor index 3316bac84a74..588134b5fa5a 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/Manage/EnableAuthenticator.razor +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/Manage/EnableAuthenticator.razor @@ -78,7 +78,7 @@ else private HttpContext HttpContext { get; set; } = default!; [SupplyParameterFromForm] - private InputModel Input { get; set; } + private InputModel Input { get; set; } = default!; protected override async Task OnInitializedAsync() { diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/Manage/Index.razor b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/Manage/Index.razor index bad84305bcbf..13faf41804f2 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/Manage/Index.razor +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/Manage/Index.razor @@ -41,7 +41,7 @@ private HttpContext HttpContext { get; set; } = default!; [SupplyParameterFromForm] - private InputModel Input { get; set; } + private InputModel Input { get; set; } = default!; protected override async Task OnInitializedAsync() { diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/Manage/Passkeys.razor b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/Manage/Passkeys.razor index 210e71dd3614..283f26de1897 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/Manage/Passkeys.razor +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/Manage/Passkeys.razor @@ -65,7 +65,7 @@ else private string? CredentialId { get; set; } [SupplyParameterFromForm(FormName = "add-passkey")] - private PasskeyInputModel Input { get; set; } + private PasskeyInputModel Input { get; set; } = default!; protected override async Task OnInitializedAsync() { diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/Manage/RenamePasskey.razor b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/Manage/RenamePasskey.razor index b88b63240bf6..80595b5bcc1e 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/Manage/RenamePasskey.razor +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/Manage/RenamePasskey.razor @@ -41,7 +41,7 @@ public string? Id { get; set; } [SupplyParameterFromForm] - private InputModel Input { get; set; } + private InputModel Input { get; set; } = default!; protected override async Task OnInitializedAsync() { diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/Manage/SetPassword.razor b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/Manage/SetPassword.razor index c7f01ca3ac1b..180b4d77a4cc 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/Manage/SetPassword.razor +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/Manage/SetPassword.razor @@ -44,7 +44,7 @@ private HttpContext HttpContext { get; set; } = default!; [SupplyParameterFromForm] - private InputModel Input { get; set; } + private InputModel Input { get; set; } = default!; protected override async Task OnInitializedAsync() { diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/Register.razor b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/Register.razor index 5f395be6a1ab..bee8e0530a88 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/Register.razor +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/Register.razor @@ -58,7 +58,7 @@ private IEnumerable? identityErrors; [SupplyParameterFromForm] - private InputModel Input { get; set; } + private InputModel Input { get; set; } = default!; [SupplyParameterFromQuery] private string? ReturnUrl { get; set; } diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/ResendEmailConfirmation.razor b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/ResendEmailConfirmation.razor index 68d38a05c057..b6fdb411ec8a 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/ResendEmailConfirmation.razor +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/ResendEmailConfirmation.razor @@ -37,7 +37,7 @@ private string? message; [SupplyParameterFromForm] - private InputModel Input { get; set; } + private InputModel Input { get; set; } = default!; protected override void OnInitialized() { diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/ResetPassword.razor b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/ResetPassword.razor index a6008f73f01b..74f258a1c518 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/ResetPassword.razor +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Pages/ResetPassword.razor @@ -46,7 +46,7 @@ private IEnumerable? identityErrors; [SupplyParameterFromForm] - private InputModel Input { get; set; } + private InputModel Input { get; set; } = default!; [SupplyParameterFromQuery] private string? Code { get; set; } From cb2088ee195d393ded00a1303ef3d81af40ad75d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 5 Aug 2025 09:11:19 +0000 Subject: [PATCH 09/10] Replace confusing 'default' string literal with 'initial-value' in analyzer tests Co-authored-by: javiercn <6995051+javiercn@users.noreply.github.com> --- .../Analyzers/test/SupplyParameterFromFormAnalyzerTest.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Components/Analyzers/test/SupplyParameterFromFormAnalyzerTest.cs b/src/Components/Analyzers/test/SupplyParameterFromFormAnalyzerTest.cs index 9151027394d4..df56f01b08d9 100644 --- a/src/Components/Analyzers/test/SupplyParameterFromFormAnalyzerTest.cs +++ b/src/Components/Analyzers/test/SupplyParameterFromFormAnalyzerTest.cs @@ -96,7 +96,7 @@ namespace ConsoleApplication1 using {typeof(ParameterAttribute).Namespace}; class TestComponent : ComponentBase {{ - [SupplyParameterFromForm] public string MyProperty {{ get; set; }} = ""default""; + [SupplyParameterFromForm] public string MyProperty {{ get; set; }} = ""initial-value""; }} }}" + TestDeclarations; @@ -223,7 +223,7 @@ class BaseComponent : ComponentBase class TestComponent : BaseComponent {{ - [SupplyParameterFromForm] public string MyProperty {{ get; set; }} = ""default""; + [SupplyParameterFromForm] public string MyProperty {{ get; set; }} = ""initial-value""; }} }}" + TestDeclarations; From 023fae459ab439ba849ceec698cbafc7b00be793 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 5 Aug 2025 10:59:31 +0000 Subject: [PATCH 10/10] Replace remaining string literal 'default' with 'initial-value' in analyzer tests Co-authored-by: javiercn <6995051+javiercn@users.noreply.github.com> --- .../Analyzers/test/SupplyParameterFromFormAnalyzerTest.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Components/Analyzers/test/SupplyParameterFromFormAnalyzerTest.cs b/src/Components/Analyzers/test/SupplyParameterFromFormAnalyzerTest.cs index df56f01b08d9..549b3cf115ec 100644 --- a/src/Components/Analyzers/test/SupplyParameterFromFormAnalyzerTest.cs +++ b/src/Components/Analyzers/test/SupplyParameterFromFormAnalyzerTest.cs @@ -48,7 +48,7 @@ namespace ConsoleApplication1 using {typeof(ParameterAttribute).Namespace}; class TestComponent : ComponentBase {{ - public string MyProperty {{ get; set; }} = ""default""; + public string MyProperty {{ get; set; }} = ""initial-value""; }} }}" + TestDeclarations; @@ -80,7 +80,7 @@ namespace ConsoleApplication1 using {typeof(ParameterAttribute).Namespace}; class NotAComponent {{ - [SupplyParameterFromForm] public string MyProperty {{ get; set; }} = ""default""; + [SupplyParameterFromForm] public string MyProperty {{ get; set; }} = ""initial-value""; }} }}" + TestDeclarations;