From 8fb898718c205551dd64861c1b79d3b7135f1391 Mon Sep 17 00:00:00 2001 From: Jan Jones Date: Tue, 16 Jul 2024 13:22:44 +0200 Subject: [PATCH 1/3] Add utility for verifying razor diagnostics --- .../DiagnosticExtensions.cs | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 src/Shared/Microsoft.AspNetCore.Razor.Test.Common/DiagnosticExtensions.cs diff --git a/src/Shared/Microsoft.AspNetCore.Razor.Test.Common/DiagnosticExtensions.cs b/src/Shared/Microsoft.AspNetCore.Razor.Test.Common/DiagnosticExtensions.cs new file mode 100644 index 00000000000..54f800e718d --- /dev/null +++ b/src/Shared/Microsoft.AspNetCore.Razor.Test.Common/DiagnosticExtensions.cs @@ -0,0 +1,20 @@ +// 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.Generic; +using System.Linq; +using Microsoft.AspNetCore.Razor.Language; +using Microsoft.CodeAnalysis.Test.Utilities; +using Microsoft.NET.Sdk.Razor.SourceGenerators; + +namespace Microsoft.CodeAnalysis; + +public static class RazorDiagnosticExtensions +{ + public static void Verify( + this IEnumerable diagnostics, + params DiagnosticDescription[] expected) + { + diagnostics.Select(d => d.AsDiagnostic()).Verify(expected); + } +} From 75f542b21727e3147bcfc7e7a3b912f573722098 Mon Sep 17 00:00:00 2001 From: Jan Jones Date: Tue, 16 Jul 2024 13:14:35 +0200 Subject: [PATCH 2/3] Handle `:get`/`:set` in `EditorRequired` checking --- .../ComponentCodeGenerationTestBase.cs | 90 +++++++++++++++++++ .../Components/ComponentLoweringPass.cs | 7 ++ 2 files changed, 97 insertions(+) diff --git a/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/IntegrationTests/ComponentCodeGenerationTestBase.cs b/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/IntegrationTests/ComponentCodeGenerationTestBase.cs index 8c948743a59..926365aa11c 100644 --- a/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/IntegrationTests/ComponentCodeGenerationTestBase.cs +++ b/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/IntegrationTests/ComponentCodeGenerationTestBase.cs @@ -1198,6 +1198,96 @@ public class ComponentWithEditorRequiredParameters : ComponentBase Assert.Empty(generated.RazorDiagnostics); } + [IntegrationTestFact, WorkItem("https://github.com/dotnet/razor/issues/10553")] + public void Component_WithEditorRequiredParameter_ValueSpecifiedUsingBindGetSet() + { + AdditionalSyntaxTrees.Add(Parse(""" + using System; + using Microsoft.AspNetCore.Components; + + namespace Test; + + public class ComponentWithEditorRequiredParameters : ComponentBase + { + [Parameter] + [EditorRequired] + public string Property1 { get; set; } + } + """)); + + var generated = CompileToCSharp(""" + + + @code { + private string myField = "Some Value"; + private void OnFieldChanged(string value) { } + } + """); + + CompileToAssembly(generated); + Assert.Empty(generated.RazorDiagnostics); + } + + [IntegrationTestFact, WorkItem("https://github.com/dotnet/razor/issues/10553")] + public void Component_WithEditorRequiredParameter_ValueSpecifiedUsingBindGet() + { + AdditionalSyntaxTrees.Add(Parse(""" + using System; + using Microsoft.AspNetCore.Components; + + namespace Test; + + public class ComponentWithEditorRequiredParameters : ComponentBase + { + [Parameter] + [EditorRequired] + public string Property1 { get; set; } + } + """)); + + var generated = CompileToCSharp(""" + + + @code { + private string myField = "Some Value"; + } + """); + + CompileToAssembly(generated); + Assert.Empty(generated.RazorDiagnostics); + } + + [IntegrationTestFact, WorkItem("https://github.com/dotnet/razor/issues/10553")] + public void Component_WithEditorRequiredParameter_ValueSpecifiedUsingBindSet() + { + AdditionalSyntaxTrees.Add(Parse(""" + using System; + using Microsoft.AspNetCore.Components; + + namespace Test; + + public class ComponentWithEditorRequiredParameters : ComponentBase + { + [Parameter] + [EditorRequired] + public string Property1 { get; set; } + } + """)); + + var generated = CompileToCSharp(""" + + + @code { + private void OnFieldChanged(string value) { } + } + """); + + var compiled = CompileToAssembly(generated); + generated.RazorDiagnostics.Verify( + // x:\dir\subdir\Test\TestComponent.cshtml(1,61): error RZ10016: Attribute 'bind-Property1:set' was used but no attribute 'bind-Property1:get' was found. + Diagnostic("RZ10016").WithLocation(1, 61)); + } + [IntegrationTestFact] public void Component_WithEditorRequiredChildContent_NoValueSpecified() { diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentLoweringPass.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentLoweringPass.cs index e01340e82cf..4eb412de453 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentLoweringPass.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentLoweringPass.cs @@ -207,6 +207,13 @@ static bool IsPresentAsAttribute(string attributeName, ComponentIntermediateNode { return true; } + if (child is TagHelperDirectiveAttributeParameterIntermediateNode { } parameterNode && + parameterNode.OriginalAttributeName?.StartsWith(bindPrefix, StringComparison.Ordinal) == true && + parameterNode.AttributeNameWithoutParameter?.AsSpan()[(bindPrefix.Length - 1)..].Equals(attributeName.AsSpan(), StringComparison.Ordinal) == true) + { + // `@bind-Value:get` or `@bind-Value:set` is specified. + return true; + } } return false; From 6a3c4b102b8a46557d025450a2463136bf68e29f Mon Sep 17 00:00:00 2001 From: Jan Jones Date: Tue, 16 Jul 2024 17:45:31 +0200 Subject: [PATCH 3/3] Simplify code --- .../src/Language/Components/ComponentLoweringPass.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentLoweringPass.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentLoweringPass.cs index 4eb412de453..8d809696490 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentLoweringPass.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentLoweringPass.cs @@ -203,13 +203,13 @@ static bool IsPresentAsAttribute(string attributeName, ComponentIntermediateNode const string bindPrefix = "@bind-"; if (child is TagHelperDirectiveAttributeIntermediateNode { OriginalAttributeName: { } originalAttributeName } && originalAttributeName.StartsWith(bindPrefix, StringComparison.Ordinal) && - originalAttributeName.AsSpan()[bindPrefix.Length..].Equals(attributeName.AsSpan(), StringComparison.Ordinal)) + originalAttributeName.AsSpan(start: bindPrefix.Length).Equals(attributeName.AsSpan(), StringComparison.Ordinal)) { return true; } - if (child is TagHelperDirectiveAttributeParameterIntermediateNode { } parameterNode && - parameterNode.OriginalAttributeName?.StartsWith(bindPrefix, StringComparison.Ordinal) == true && - parameterNode.AttributeNameWithoutParameter?.AsSpan()[(bindPrefix.Length - 1)..].Equals(attributeName.AsSpan(), StringComparison.Ordinal) == true) + if (child is TagHelperDirectiveAttributeParameterIntermediateNode { OriginalAttributeName: { } originalName, AttributeNameWithoutParameter: { } nameWithoutParameter } && + originalName.StartsWith(bindPrefix, StringComparison.Ordinal) && + nameWithoutParameter.AsSpan(start: bindPrefix.Length - 1).Equals(attributeName.AsSpan(), StringComparison.Ordinal)) { // `@bind-Value:get` or `@bind-Value:set` is specified. return true;