diff --git a/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/BlazorDesignTimeNodeWriter.cs b/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/BlazorDesignTimeNodeWriter.cs index ffb96ef5b0..4553b0da03 100644 --- a/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/BlazorDesignTimeNodeWriter.cs +++ b/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/BlazorDesignTimeNodeWriter.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections.Generic; using System.Linq; using Microsoft.AspNetCore.Blazor.Shared; using Microsoft.AspNetCore.Razor.Language; @@ -368,73 +369,73 @@ namespace Microsoft.AspNetCore.Blazor.Razor // to handle here, since there are a few different cases for how an attribute might be structured. // // This roughly follows the design of the runtime writer for simplicity. - HtmlContentIntermediateNode htmlNode; - CSharpExpressionIntermediateNode cSharpNode; if (node.AttributeStructure == AttributeStructure.Minimized) { // Do nothing } - else if (node.Children.Count != 1) + else if (node.Children.Count > 1) { // We don't expect this to happen, we just want to know if it can. - throw new InvalidOperationException("Attribute nodes should either be minimized or a single type of content." + node.Children[0].ToString()); + throw new InvalidOperationException("Attribute nodes should either be minimized or a single type of content." + string.Join(", ", node.Children)); } - else if (node.BoundAttribute?.IsDelegateProperty() ?? false) - { - var tokens = node.Children; - if ((cSharpNode = node.Children[0] as CSharpExpressionIntermediateNode) != null) - { - tokens = node.Children[0].Children; - } - - // We always surround the expression with the delegate constructor. This makes type - // inference inside lambdas, and method group conversion do the right thing. - context.CodeWriter.Write(DesignTimeVariable); - context.CodeWriter.Write(" = "); - context.CodeWriter.Write("new "); - context.CodeWriter.Write(node.BoundAttribute.TypeName); - context.CodeWriter.Write("("); - context.CodeWriter.WriteLine(); - - for (var i = 0; i < tokens.Count; i++) - { - WriteCSharpToken(context, (IntermediateToken)tokens[i]); - } - - context.CodeWriter.Write(");"); - context.CodeWriter.WriteLine(); - } - else if ((cSharpNode = node.Children[0] as CSharpExpressionIntermediateNode) != null) - { - // This is the case when an attribute has an explicit C# transition like: - // - context.CodeWriter.Write(DesignTimeVariable); - context.CodeWriter.Write(" = "); - - for (var i = 0; i < cSharpNode.Children.Count; i++) - { - WriteCSharpToken(context, (IntermediateToken)cSharpNode.Children[i]); - } - - context.CodeWriter.Write(";"); - context.CodeWriter.WriteLine(); - } - else if ((htmlNode = node.Children[0] as HtmlContentIntermediateNode) != null) + else if (node.Children.Count == 1 && node.Children[0] is HtmlContentIntermediateNode) { // Do nothing } - else if (node.Children[0] is IntermediateToken token && token.IsCSharp) + else { - context.CodeWriter.Write(DesignTimeVariable); - context.CodeWriter.Write(" = "); + // There are a few different forms that could be used to contain all of the tokens, but we don't really care + // exactly what it looks like - we just want all of the content. + // + // This can include an empty list in some cases like the following (sic): + // + // + // Of a list of tokens directly in the attribute. + var tokens = GetCSharpTokens(node); - for (var i = 0; i < node.Children.Count; i++) + if (node.BoundAttribute?.IsDelegateProperty() ?? false) { - WriteCSharpToken(context, (IntermediateToken)node.Children[i]); - } + // We always surround the expression with the delegate constructor. This makes type + // inference inside lambdas, and method group conversion do the right thing. + context.CodeWriter.Write(DesignTimeVariable); + context.CodeWriter.Write(" = "); + context.CodeWriter.Write("new "); + context.CodeWriter.Write(node.BoundAttribute.TypeName); + context.CodeWriter.Write("("); + context.CodeWriter.WriteLine(); - context.CodeWriter.Write(";"); - context.CodeWriter.WriteLine(); + for (var i = 0; i < tokens.Count; i++) + { + WriteCSharpToken(context, tokens[i]); + } + + context.CodeWriter.Write(");"); + context.CodeWriter.WriteLine(); + } + else + { + // This is the case when an attribute has an explicit C# transition like: + // + context.CodeWriter.Write(DesignTimeVariable); + context.CodeWriter.Write(" = "); + + for (var i = 0; i < tokens.Count; i++) + { + WriteCSharpToken(context, tokens[i]); + } + + context.CodeWriter.Write(";"); + context.CodeWriter.WriteLine(); + } + } + + IReadOnlyList GetCSharpTokens(ComponentAttributeExtensionNode attribute) + { + // We generally expect all children to be CSharp, this is here just in case. + return attribute.FindDescendantNodes().Where(t => t.IsCSharp).ToArray(); } } diff --git a/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/BlazorRuntimeNodeWriter.cs b/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/BlazorRuntimeNodeWriter.cs index ab28e3ee55..c2fcefc275 100644 --- a/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/BlazorRuntimeNodeWriter.cs +++ b/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/BlazorRuntimeNodeWriter.cs @@ -347,63 +347,50 @@ namespace Microsoft.AspNetCore.Blazor.Razor // Minimized attributes always map to 'true' context.CodeWriter.Write("true"); } - else if (node.BoundAttribute?.IsDelegateProperty() ?? false) + else if (node.Children.Count > 1) { - // We always surround the expression with the delegate constructor. This makes type - // inference inside lambdas, and method group conversion do the right thing. - IntermediateToken token = null; - if ((node.Children[0] as CSharpExpressionIntermediateNode) != null) - { - token = node.Children[0].Children[0] as IntermediateToken; - } - else - { - token = node.Children[0] as IntermediateToken; - } - - if (token != null) - { - context.CodeWriter.Write("new "); - context.CodeWriter.Write(node.BoundAttribute.TypeName); - context.CodeWriter.Write("("); - context.CodeWriter.Write(token.Content); - context.CodeWriter.Write(")"); - } + // We don't expect this to happen, we just want to know if it can. + throw new InvalidOperationException("Attribute nodes should either be minimized or a single type of content." + string.Join(", ", node.Children)); } - else if (node.Children[0] is CSharpExpressionIntermediateNode cSharpNode) - { - // We don't allow mixed content in component attributes. If this happens, then - // we should make sure that all of the tokens are the same kind. We report an - // error if user code tries to do this, so this check is to catch bugs in the - // compiler. - for (var i = 0; i < cSharpNode.Children.Count; i++) - { - var token = (IntermediateToken)cSharpNode.Children[i]; - if (!token.IsCSharp) - { - throw new InvalidOperationException("Unexpected mixed content in a component."); - } - - context.CodeWriter.Write(token.Content); - } - } - else if (node.Children[0] is HtmlContentIntermediateNode htmlNode) + else if (node.Children.Count == 1 && node.Children[0] is HtmlContentIntermediateNode htmlNode) { // This is how string attributes are lowered by default, a single HTML node with a single HTML token. context.CodeWriter.WriteStringLiteral(((IntermediateToken)htmlNode.Children[0]).Content); } - else if (node.Children[0] is IntermediateToken token) - { - // This is what we expect for non-string nodes. - context.CodeWriter.Write(((IntermediateToken)node.Children[0]).Content); - } else { - throw new InvalidOperationException("Unexpected node type " + node.Children[0].GetType().FullName); + // See comments in BlazorDesignTimeNodeWriter for a description of the cases that are possible. + var tokens = GetCSharpTokens(node); + if (node.BoundAttribute?.IsDelegateProperty() ?? false) + { + context.CodeWriter.Write("new "); + context.CodeWriter.Write(node.BoundAttribute.TypeName); + context.CodeWriter.Write("("); + + for (var i = 0; i < tokens.Count; i++) + { + context.CodeWriter.Write(tokens[i].Content); + } + + context.CodeWriter.Write(")"); + } + else + { + for (var i = 0; i < tokens.Count; i++) + { + context.CodeWriter.Write(tokens[i].Content); + } + } } context.CodeWriter.Write(");"); context.CodeWriter.WriteLine(); + + IReadOnlyList GetCSharpTokens(ComponentAttributeExtensionNode attribute) + { + // We generally expect all children to be CSharp, this is here just in case. + return attribute.FindDescendantNodes().Where(t => t.IsCSharp).ToArray(); + } } public override void WriteReferenceCapture(CodeRenderingContext context, RefExtensionNode node) diff --git a/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/EventHandlerLoweringPass.cs b/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/EventHandlerLoweringPass.cs index 268ba3a83f..c0607e868c 100644 --- a/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/EventHandlerLoweringPass.cs +++ b/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/EventHandlerLoweringPass.cs @@ -125,7 +125,6 @@ namespace Microsoft.AspNetCore.Blazor.Razor Content = $"{BlazorApi.BindMethods.GetEventHandlerValue}<{eventArgsType}>(", Kind = TokenKind.CSharp }, - original, new IntermediateToken() { Content = $")", @@ -133,6 +132,11 @@ namespace Microsoft.AspNetCore.Blazor.Razor } }; + for (var i = 0; i < original.Count; i++) + { + tokens.Insert(i + 1, original[i]); + } + if (parent is HtmlElementIntermediateNode) { var result = new HtmlAttributeIntermediateNode() @@ -172,25 +176,20 @@ namespace Microsoft.AspNetCore.Blazor.Razor } } - private static IntermediateToken GetAttributeContent(TagHelperPropertyIntermediateNode node) + private static IReadOnlyList GetAttributeContent(TagHelperPropertyIntermediateNode node) { - if (node.Children[0] is HtmlContentIntermediateNode htmlContentNode) + if (node.Children.Count == 1 && node.Children[0] is HtmlContentIntermediateNode htmlContentNode) { // This case can be hit for a 'string' attribute. We want to turn it into // an expression. - var content = "\"" + ((IntermediateToken)htmlContentNode.Children.Single()).Content + "\""; - return new IntermediateToken() { Content = content, Kind = TokenKind.CSharp, }; - } - else if (node.Children[0] is CSharpExpressionIntermediateNode cSharpNode) - { - // This case can be hit when the attribute has an explicit @ inside, which - // 'escapes' any special sugar we provide for codegen. - return ((IntermediateToken)cSharpNode.Children.Single()); + var tokens = htmlContentNode.FindDescendantNodes(); + + var content = "\"" + string.Join(string.Empty, tokens.Select(t => t.Content))+ "\""; + return new[] { new IntermediateToken() { Content = content, Kind = TokenKind.CSharp, } }; } else { - // This is the common case for 'mixed' content - return ((IntermediateToken)node.Children.Single()); + return node.FindDescendantNodes(); } } } diff --git a/test/Microsoft.AspNetCore.Blazor.Build.Test/DesignTimeCodeGenerationTest.cs b/test/Microsoft.AspNetCore.Blazor.Build.Test/DesignTimeCodeGenerationTest.cs index c4b2ab8864..613c880a57 100644 --- a/test/Microsoft.AspNetCore.Blazor.Build.Test/DesignTimeCodeGenerationTest.cs +++ b/test/Microsoft.AspNetCore.Blazor.Build.Test/DesignTimeCodeGenerationTest.cs @@ -1,6 +1,7 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System.Linq; using Xunit; namespace Microsoft.AspNetCore.Blazor.Build.Test @@ -367,6 +368,45 @@ namespace Test CompileToAssembly(generated); } + [Fact] // https://github.com/aspnet/Blazor/issues/772 + public void Regression_772() + { + // Arrange + AdditionalSyntaxTrees.Add(Parse(@" +using Microsoft.AspNetCore.Blazor.Components; + +namespace Test +{ + public class SurveyPrompt : BlazorComponent + { + [Parameter] private string Title { get; set; } + } +} +")); + + // Act + var generated = CompileToCSharp(@" +@addTagHelper *, TestAssembly +@page ""/"" + +

Hello, world!

+ +Welcome to your new app. + + d.Id), + d => Assert.Equal("RZ1034", d.Id), + d => Assert.Equal("RZ1035", d.Id)); + } + [Fact] public void BindToComponent_SpecifiesValue_WithMatchingProperties() { diff --git a/test/Microsoft.AspNetCore.Blazor.Build.Test/RuntimeCodeGenerationTest.cs b/test/Microsoft.AspNetCore.Blazor.Build.Test/RuntimeCodeGenerationTest.cs index 6f0229ce52..07692a6447 100644 --- a/test/Microsoft.AspNetCore.Blazor.Build.Test/RuntimeCodeGenerationTest.cs +++ b/test/Microsoft.AspNetCore.Blazor.Build.Test/RuntimeCodeGenerationTest.cs @@ -1,6 +1,7 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System.Linq; using Xunit; namespace Microsoft.AspNetCore.Blazor.Build.Test @@ -617,6 +618,45 @@ namespace Test CompileToAssembly(generated); } + [Fact] // https://github.com/aspnet/Blazor/issues/772 + public void Regression_772() + { + // Arrange + AdditionalSyntaxTrees.Add(Parse(@" +using Microsoft.AspNetCore.Blazor.Components; + +namespace Test +{ + public class SurveyPrompt : BlazorComponent + { + [Parameter] private string Title { get; set; } + } +} +")); + + // Act + var generated = CompileToCSharp(@" +@addTagHelper *, TestAssembly +@page ""/"" + +

Hello, world!

+ +Welcome to your new app. + + d.Id), + d => Assert.Equal("RZ1034", d.Id), + d => Assert.Equal("RZ1035", d.Id)); + } + [Fact] public void BindToComponent_SpecifiesValue_WithMatchingProperties() { diff --git a/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/DesignTimeCodeGenerationTest/Regression_772/TestComponent.codegen.cs b/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/DesignTimeCodeGenerationTest/Regression_772/TestComponent.codegen.cs new file mode 100644 index 0000000000..e21dcde2b9 --- /dev/null +++ b/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/DesignTimeCodeGenerationTest/Regression_772/TestComponent.codegen.cs @@ -0,0 +1,42 @@ +// +#pragma warning disable 1591 +namespace Test +{ + #line hidden + using System; + using System.Collections.Generic; + using System.Linq; + using System.Threading.Tasks; + using Microsoft.AspNetCore.Blazor; + using Microsoft.AspNetCore.Blazor.Components; + [Microsoft.AspNetCore.Blazor.Components.RouteAttribute("/")] + public class TestComponent : Microsoft.AspNetCore.Blazor.Components.BlazorComponent + { + #pragma warning disable 219 + private void __RazorDirectiveTokenHelpers__() { + ((System.Action)(() => { +global::System.Object __typeHelper = "*, TestAssembly"; + } + ))(); + ((System.Action)(() => { +global::System.Object __typeHelper = "/"; + } + ))(); + } + #pragma warning restore 219 + #pragma warning disable 0414 + private static System.Object __o = null; + #pragma warning restore 0414 + #pragma warning disable 1998 + protected override void BuildRenderTree(Microsoft.AspNetCore.Blazor.RenderTree.RenderTreeBuilder builder) + { + base.BuildRenderTree(builder); + __o = ; + builder.AddAttribute(-1, "ChildContent", (Microsoft.AspNetCore.Blazor.RenderFragment)((builder2) => { + } + )); + } + #pragma warning restore 1998 + } +} +#pragma warning restore 1591 diff --git a/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/DesignTimeCodeGenerationTest/Regression_772/TestComponent.diagnostics.txt b/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/DesignTimeCodeGenerationTest/Regression_772/TestComponent.diagnostics.txt new file mode 100644 index 0000000000..57b2bc7949 --- /dev/null +++ b/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/DesignTimeCodeGenerationTest/Regression_772/TestComponent.diagnostics.txt @@ -0,0 +1,2 @@ +x:\dir\subdir\Test\TestComponent.cshtml(8,2): Error RZ1035: Missing close angle for tag helper 'SurveyPrompt'. +x:\dir\subdir\Test\TestComponent.cshtml(8,2): Error RZ1034: Found a malformed 'SurveyPrompt' tag helper. Tag helpers must have a start and end tag or be self closing. diff --git a/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/DesignTimeCodeGenerationTest/Regression_772/TestComponent.ir.txt b/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/DesignTimeCodeGenerationTest/Regression_772/TestComponent.ir.txt new file mode 100644 index 0000000000..a114b22a19 --- /dev/null +++ b/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/DesignTimeCodeGenerationTest/Regression_772/TestComponent.ir.txt @@ -0,0 +1,35 @@ +Document - + NamespaceDeclaration - - Test + UsingDirective - (3:1,1 [12] ) - System + UsingDirective - (18:2,1 [32] ) - System.Collections.Generic + UsingDirective - (53:3,1 [17] ) - System.Linq + UsingDirective - (73:4,1 [28] ) - System.Threading.Tasks + UsingDirective - (104:5,1 [33] ) - Microsoft.AspNetCore.Blazor + UsingDirective - (140:6,1 [44] ) - Microsoft.AspNetCore.Blazor.Components + RouteAttributeExtensionNode - - / + ClassDeclaration - - public - TestComponent - Microsoft.AspNetCore.Blazor.Components.BlazorComponent - + DesignTimeDirective - + DirectiveToken - (14:0,14 [32] ) - "*, Microsoft.AspNetCore.Blazor" + DirectiveToken - (14:0,14 [9] ) - "*, Test" + DirectiveToken - (14:0,14 [15] x:\dir\subdir\Test\TestComponent.cshtml) - *, TestAssembly + DirectiveToken - (37:1,6 [3] x:\dir\subdir\Test\TestComponent.cshtml) - "/" + CSharpCode - + IntermediateToken - - CSharp - #pragma warning disable 0414 + CSharpCode - + IntermediateToken - - CSharp - private static System.Object __o = null; + CSharpCode - + IntermediateToken - - CSharp - #pragma warning restore 0414 + MethodDeclaration - - protected override - void - BuildRenderTree + CSharpCode - + IntermediateToken - - CSharp - base.BuildRenderTree(builder); + HtmlContent - (29:0,29 [2] x:\dir\subdir\Test\TestComponent.cshtml) + IntermediateToken - (29:0,29 [2] x:\dir\subdir\Test\TestComponent.cshtml) - Html - \n + HtmlContent - (42:2,0 [2] x:\dir\subdir\Test\TestComponent.cshtml) + IntermediateToken - (42:2,0 [2] x:\dir\subdir\Test\TestComponent.cshtml) - Html - \n + HtmlElement - (44:3,0 [22] x:\dir\subdir\Test\TestComponent.cshtml) - h1 + HtmlContent - (48:3,4 [13] x:\dir\subdir\Test\TestComponent.cshtml) + IntermediateToken - (48:3,4 [13] x:\dir\subdir\Test\TestComponent.cshtml) - Html - Hello, world! + HtmlContent - (66:3,22 [32] x:\dir\subdir\Test\TestComponent.cshtml) + IntermediateToken - (66:3,22 [32] x:\dir\subdir\Test\TestComponent.cshtml) - Html - \n\nWelcome to your new app.\n\n + ComponentExtensionNode - (98:7,0 [23] x:\dir\subdir\Test\TestComponent.cshtml) - SurveyPrompt - Test.SurveyPrompt + ComponentAttributeExtensionNode - (119:7,21 [0] x:\dir\subdir\Test\TestComponent.cshtml) - Title - Title diff --git a/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/DesignTimeCodeGenerationTest/Regression_772/TestComponent.mappings.txt b/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/DesignTimeCodeGenerationTest/Regression_772/TestComponent.mappings.txt new file mode 100644 index 0000000000..ed2bbca8b6 --- /dev/null +++ b/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/DesignTimeCodeGenerationTest/Regression_772/TestComponent.mappings.txt @@ -0,0 +1,10 @@ +Source Location: (14:0,14 [15] x:\dir\subdir\Test\TestComponent.cshtml) +|*, TestAssembly| +Generated Location: (625:17,38 [15] ) +|*, TestAssembly| + +Source Location: (37:1,6 [3] x:\dir\subdir\Test\TestComponent.cshtml) +|"/"| +Generated Location: (741:21,37 [3] ) +|"/"| + diff --git a/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/RuntimeCodeGenerationTest/Regression_772/TestComponent.codegen.cs b/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/RuntimeCodeGenerationTest/Regression_772/TestComponent.codegen.cs new file mode 100644 index 0000000000..7d68051cbd --- /dev/null +++ b/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/RuntimeCodeGenerationTest/Regression_772/TestComponent.codegen.cs @@ -0,0 +1,30 @@ +// +#pragma warning disable 1591 +namespace Test +{ + #line hidden + using System; + using System.Collections.Generic; + using System.Linq; + using System.Threading.Tasks; + using Microsoft.AspNetCore.Blazor; + using Microsoft.AspNetCore.Blazor.Components; + [Microsoft.AspNetCore.Blazor.Components.RouteAttribute("/")] + public class TestComponent : Microsoft.AspNetCore.Blazor.Components.BlazorComponent + { + #pragma warning disable 1998 + protected override void BuildRenderTree(Microsoft.AspNetCore.Blazor.RenderTree.RenderTreeBuilder builder) + { + base.BuildRenderTree(builder); + builder.OpenElement(0, "h1"); + builder.AddContent(1, "Hello, world!"); + builder.CloseElement(); + builder.AddContent(2, "\n\nWelcome to your new app.\n\n"); + builder.OpenComponent(3); + builder.AddAttribute(4, "Title", ); + builder.CloseComponent(); + } + #pragma warning restore 1998 + } +} +#pragma warning restore 1591 diff --git a/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/RuntimeCodeGenerationTest/Regression_772/TestComponent.diagnostics.txt b/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/RuntimeCodeGenerationTest/Regression_772/TestComponent.diagnostics.txt new file mode 100644 index 0000000000..57b2bc7949 --- /dev/null +++ b/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/RuntimeCodeGenerationTest/Regression_772/TestComponent.diagnostics.txt @@ -0,0 +1,2 @@ +x:\dir\subdir\Test\TestComponent.cshtml(8,2): Error RZ1035: Missing close angle for tag helper 'SurveyPrompt'. +x:\dir\subdir\Test\TestComponent.cshtml(8,2): Error RZ1034: Found a malformed 'SurveyPrompt' tag helper. Tag helpers must have a start and end tag or be self closing. diff --git a/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/RuntimeCodeGenerationTest/Regression_772/TestComponent.ir.txt b/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/RuntimeCodeGenerationTest/Regression_772/TestComponent.ir.txt new file mode 100644 index 0000000000..fa34764590 --- /dev/null +++ b/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/RuntimeCodeGenerationTest/Regression_772/TestComponent.ir.txt @@ -0,0 +1,20 @@ +Document - + NamespaceDeclaration - - Test + UsingDirective - (3:1,1 [14] ) - System + UsingDirective - (18:2,1 [34] ) - System.Collections.Generic + UsingDirective - (53:3,1 [19] ) - System.Linq + UsingDirective - (73:4,1 [30] ) - System.Threading.Tasks + UsingDirective - (104:5,1 [35] ) - Microsoft.AspNetCore.Blazor + UsingDirective - (140:6,1 [46] ) - Microsoft.AspNetCore.Blazor.Components + RouteAttributeExtensionNode - - / + ClassDeclaration - - public - TestComponent - Microsoft.AspNetCore.Blazor.Components.BlazorComponent - + MethodDeclaration - - protected override - void - BuildRenderTree + CSharpCode - + IntermediateToken - - CSharp - base.BuildRenderTree(builder); + HtmlElement - (44:3,0 [22] x:\dir\subdir\Test\TestComponent.cshtml) - h1 + HtmlContent - (48:3,4 [13] x:\dir\subdir\Test\TestComponent.cshtml) + IntermediateToken - (48:3,4 [13] x:\dir\subdir\Test\TestComponent.cshtml) - Html - Hello, world! + HtmlContent - (66:3,22 [32] x:\dir\subdir\Test\TestComponent.cshtml) + IntermediateToken - (66:3,22 [32] x:\dir\subdir\Test\TestComponent.cshtml) - Html - \n\nWelcome to your new app.\n\n + ComponentExtensionNode - (98:7,0 [23] x:\dir\subdir\Test\TestComponent.cshtml) - SurveyPrompt - Test.SurveyPrompt + ComponentAttributeExtensionNode - (119:7,21 [0] x:\dir\subdir\Test\TestComponent.cshtml) - Title - Title diff --git a/test/Microsoft.AspNetCore.Blazor.Build.Test/TypingTest.cs b/test/Microsoft.AspNetCore.Blazor.Build.Test/TypingTest.cs new file mode 100644 index 0000000000..bfc6743f51 --- /dev/null +++ b/test/Microsoft.AspNetCore.Blazor.Build.Test/TypingTest.cs @@ -0,0 +1,70 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Linq; +using Xunit; +using Xunit.Sdk; + +namespace Microsoft.AspNetCore.Blazor.Build.Test +{ + // Similar to design time code generation tests, but goes a character at a time. + // Don't add many of these since they are slow - instead add features to existing + // tests here, and use these as smoke tests, not for detailed regression testing. + public class TypingTest : RazorIntegrationTestBase + { + internal override bool DesignTime => true; + + internal override bool UseTwoPhaseCompilation => false; + + [Fact] + public void DoSomeTyping() + { + // Arrange + AdditionalSyntaxTrees.Add(Parse(@" +using System; +using Microsoft.AspNetCore.Blazor.Components; + +namespace Test +{ + public class MyComponent : BlazorComponent + { + [Parameter] int Value { get; set; } + [Parameter] Action ValueChanged { get; set; } + [Parameter] string AnotherValue { get; set; } + } +} +")); + var text = @" +@addTagHelper *, TestAssembly +
+ + + +
+"; + + for (var i = 0; i <= text.Length; i++) + { + try + { + CompileToCSharp(text.Substring(0, i)); + } + catch (Exception ex) + { + throw new XunitException($@" +Code generation failed on iteration {i} with source text: +{text.Substring(0, i)} + +Exception: +{ex} +"); + } + } + } + } +}