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 cb969d332a5..58c5c870d71 100644 --- a/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/IntegrationTests/ComponentCodeGenerationTestBase.cs +++ b/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/IntegrationTests/ComponentCodeGenerationTestBase.cs @@ -2373,6 +2373,20 @@ @layout System.Object CompileToAssembly(generated); } + [IntegrationTestFact, WorkItem("https://github.com/dotnet/razor/issues/8340")] + public void LayoutDirective_Null() + { + // Act + var generated = CompileToCSharp(""" + @layout null + """); + + // Assert + AssertDocumentNodeMatchesBaseline(generated.CodeDocument); + AssertCSharpDocumentMatchesBaseline(generated.CodeDocument); + CompileToAssembly(generated); + } + [IntegrationTestFact] public void Component_AddContent_Multiline() { diff --git a/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/IntegrationTests/ComponentDirectiveIntegrationTest.cs b/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/IntegrationTests/ComponentDirectiveIntegrationTest.cs index 9ec30ba0ba9..d06a74a5fbd 100644 --- a/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/IntegrationTests/ComponentDirectiveIntegrationTest.cs +++ b/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/IntegrationTests/ComponentDirectiveIntegrationTest.cs @@ -44,6 +44,18 @@ public void SupportsLayoutDeclarations() Assert.NotNull(layoutAttribute); } + [Fact] + public void SupportsLayoutNullDeclaration() + { + // Arrange/Act + var component = CompileToComponent( + "@layout null\n" + + "Hello"); + + // Assert - when layout is set to null, no layout attribute should be generated + Assert.Null(component.GetAttributes().FirstOrDefault(a => a.AttributeClass.Name == "LayoutAttribute")); + } + [Fact] public void SupportsImplementsDeclarations() { diff --git a/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentDesignTimeCodeGenerationTest/LayoutDirective_Null/TestComponent.codegen.cs b/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentDesignTimeCodeGenerationTest/LayoutDirective_Null/TestComponent.codegen.cs new file mode 100644 index 00000000000..2a53b15489f --- /dev/null +++ b/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentDesignTimeCodeGenerationTest/LayoutDirective_Null/TestComponent.codegen.cs @@ -0,0 +1,34 @@ +// +#pragma warning disable 1591 +namespace Test +{ + #line default + using global::System; + using global::System.Collections.Generic; + using global::System.Linq; + using global::System.Threading.Tasks; + using global::Microsoft.AspNetCore.Components; + #line default + #line hidden + #nullable restore + public partial class TestComponent : global::Microsoft.AspNetCore.Components.ComponentBase + #nullable disable + { + #pragma warning disable 219 + private void __RazorDirectiveTokenHelpers__() { + ((global::System.Action)(() => { + } + ))(); + } + #pragma warning restore 219 + #pragma warning disable 0414 + private static object __o = null; + #pragma warning restore 0414 + #pragma warning disable 1998 + protected override void BuildRenderTree(global::Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder __builder) + { + } + #pragma warning restore 1998 + } +} +#pragma warning restore 1591 diff --git a/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentDesignTimeCodeGenerationTest/LayoutDirective_Null/TestComponent.ir.txt b/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentDesignTimeCodeGenerationTest/LayoutDirective_Null/TestComponent.ir.txt new file mode 100644 index 00000000000..1a11a374c2a --- /dev/null +++ b/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentDesignTimeCodeGenerationTest/LayoutDirective_Null/TestComponent.ir.txt @@ -0,0 +1,17 @@ +Document - + NamespaceDeclaration - - Test + UsingDirective - (3:1,1 [20] ) - global::System + UsingDirective - (26:2,1 [40] ) - global::System.Collections.Generic + UsingDirective - (69:3,1 [25] ) - global::System.Linq + UsingDirective - (97:4,1 [36] ) - global::System.Threading.Tasks + UsingDirective - (136:5,1 [45] ) - global::Microsoft.AspNetCore.Components + ClassDeclaration - - public partial - TestComponent - global::Microsoft.AspNetCore.Components.ComponentBase - + DesignTimeDirective - + DirectiveToken - (8:0,8 [4] x:\dir\subdir\Test\TestComponent.cshtml) - null + CSharpCode - + IntermediateToken - - CSharp - #pragma warning disable 0414 + CSharpCode - + IntermediateToken - - CSharp - private static object __o = null; + CSharpCode - + IntermediateToken - - CSharp - #pragma warning restore 0414 + MethodDeclaration - - protected override - void - BuildRenderTree diff --git a/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentRuntimeCodeGenerationTest/LayoutDirective_Null/TestComponent.codegen.cs b/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentRuntimeCodeGenerationTest/LayoutDirective_Null/TestComponent.codegen.cs new file mode 100644 index 00000000000..9dfa26c2a18 --- /dev/null +++ b/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentRuntimeCodeGenerationTest/LayoutDirective_Null/TestComponent.codegen.cs @@ -0,0 +1,24 @@ +// +#pragma warning disable 1591 +namespace Test +{ + #line default + using global::System; + using global::System.Collections.Generic; + using global::System.Linq; + using global::System.Threading.Tasks; + using global::Microsoft.AspNetCore.Components; + #line default + #line hidden + #nullable restore + public partial class TestComponent : global::Microsoft.AspNetCore.Components.ComponentBase + #nullable disable + { + #pragma warning disable 1998 + protected override void BuildRenderTree(global::Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder __builder) + { + } + #pragma warning restore 1998 + } +} +#pragma warning restore 1591 diff --git a/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentRuntimeCodeGenerationTest/LayoutDirective_Null/TestComponent.ir.txt b/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentRuntimeCodeGenerationTest/LayoutDirective_Null/TestComponent.ir.txt new file mode 100644 index 00000000000..5271eee9142 --- /dev/null +++ b/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentRuntimeCodeGenerationTest/LayoutDirective_Null/TestComponent.ir.txt @@ -0,0 +1,9 @@ +Document - + NamespaceDeclaration - - Test + UsingDirective - (3:1,1 [20] ) - global::System + UsingDirective - (26:2,1 [40] ) - global::System.Collections.Generic + UsingDirective - (69:3,1 [25] ) - global::System.Linq + UsingDirective - (97:4,1 [36] ) - global::System.Threading.Tasks + UsingDirective - (136:5,1 [45] ) - global::Microsoft.AspNetCore.Components + ClassDeclaration - - public partial - TestComponent - global::Microsoft.AspNetCore.Components.ComponentBase - + MethodDeclaration - - protected override - void - BuildRenderTree diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentLayoutDirectivePass.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentLayoutDirectivePass.cs index fbf56b34cc3..23023a1d5f3 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentLayoutDirectivePass.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentLayoutDirectivePass.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System; using System.Linq; using System.Threading; using Microsoft.AspNetCore.Razor.Language.Intermediate; @@ -33,6 +34,12 @@ protected override void ExecuteCore( return; } + // If the layout is explicitly set to null, don't generate a layout attribute + if (string.Equals(token.Content, "null", StringComparison.Ordinal)) + { + return; + } + var attributeNode = new CSharpCodeIntermediateNode(); attributeNode.Children.AddRange([ IntermediateNodeFactory.CSharpToken($"[global::{ComponentsApi.LayoutAttribute.FullTypeName}(typeof("), diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Extensions/DesignTimeDirectiveTargetExtension.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Extensions/DesignTimeDirectiveTargetExtension.cs index 1f286f1cb31..eac169b1f48 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Extensions/DesignTimeDirectiveTargetExtension.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Extensions/DesignTimeDirectiveTargetExtension.cs @@ -73,6 +73,12 @@ private void WriteDesignTimeDirectiveToken(CodeRenderingContext context, DesignT break; } + // Skip validation for "null" which is used to disable layouts + if (string.Equals(node.Content, "null", StringComparison.Ordinal)) + { + break; + } + // {node.Content} __typeHelper = default({node.Content}); using (context.BuildLinePragma(node.Source)) {