From 3f5d25d314cbb5672febd7a47fa5d62c4becaefa Mon Sep 17 00:00:00 2001 From: Ryan Nowak Date: Tue, 8 May 2018 12:07:06 -0700 Subject: [PATCH] Fix 773 We weren't handling a few cases that can occur during typing correctly. Our passes that look at the content of attributes need to be prepared for it to be empty in cases where the attribute has been partially typed in the editor. I added a smoke test for this that attempts to simulate typing and found another issue to fix. The end result of this is that the design for this kind of code is simpler and takes a more 'brute-force' approach to understanding attributes. I think this is a good change based on the problems with how this code has been written today, there are too many possible cases to try and have the code express and document them all. --- .../BlazorDesignTimeNodeWriter.cs | 107 +++++++++--------- .../BlazorRuntimeNodeWriter.cs | 77 ++++++------- .../EventHandlerLoweringPass.cs | 25 ++-- .../DesignTimeCodeGenerationTest.cs | 40 +++++++ .../RuntimeCodeGenerationTest.cs | 40 +++++++ .../Regression_772/TestComponent.codegen.cs | 42 +++++++ .../TestComponent.diagnostics.txt | 2 + .../Regression_772/TestComponent.ir.txt | 35 ++++++ .../Regression_772/TestComponent.mappings.txt | 10 ++ .../Regression_772/TestComponent.codegen.cs | 30 +++++ .../TestComponent.diagnostics.txt | 2 + .../Regression_772/TestComponent.ir.txt | 20 ++++ .../TypingTest.cs | 70 ++++++++++++ 13 files changed, 389 insertions(+), 111 deletions(-) create mode 100644 test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/DesignTimeCodeGenerationTest/Regression_772/TestComponent.codegen.cs create mode 100644 test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/DesignTimeCodeGenerationTest/Regression_772/TestComponent.diagnostics.txt create mode 100644 test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/DesignTimeCodeGenerationTest/Regression_772/TestComponent.ir.txt create mode 100644 test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/DesignTimeCodeGenerationTest/Regression_772/TestComponent.mappings.txt create mode 100644 test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/RuntimeCodeGenerationTest/Regression_772/TestComponent.codegen.cs create mode 100644 test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/RuntimeCodeGenerationTest/Regression_772/TestComponent.diagnostics.txt create mode 100644 test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/RuntimeCodeGenerationTest/Regression_772/TestComponent.ir.txt create mode 100644 test/Microsoft.AspNetCore.Blazor.Build.Test/TypingTest.cs 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} +"); + } + } + } + } +}