diff --git a/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/BindLoweringPass.cs b/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/BindLoweringPass.cs index 02889979a7..ddd1d2c516 100644 --- a/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/BindLoweringPass.cs +++ b/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/BindLoweringPass.cs @@ -311,6 +311,7 @@ namespace Microsoft.AspNetCore.Blazor.Razor BoundAttribute = valueAttribute, // Might be null if it doesn't match a component attribute PropertyName = valueAttribute?.GetPropertyName(), TagHelper = valueAttribute == null ? null : node.TagHelper, + TypeName = valueAttribute?.IsWeaklyTyped() == false ? valueAttribute.TypeName : null, }; valueNode.Children.Clear(); @@ -326,6 +327,7 @@ namespace Microsoft.AspNetCore.Blazor.Razor BoundAttribute = changeAttribute, // Might be null if it doesn't match a component attribute PropertyName = changeAttribute?.GetPropertyName(), TagHelper = changeAttribute == null ? null : node.TagHelper, + TypeName = changeAttribute?.IsWeaklyTyped() == false ? changeAttribute.TypeName : null, }; changeNode.Children.Clear(); diff --git a/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/BlazorDesignTimeNodeWriter.cs b/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/BlazorDesignTimeNodeWriter.cs index a12a7505eb..bf4e604b52 100644 --- a/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/BlazorDesignTimeNodeWriter.cs +++ b/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/BlazorDesignTimeNodeWriter.cs @@ -343,6 +343,11 @@ namespace Microsoft.AspNetCore.Blazor.Razor throw new ArgumentNullException(nameof(node)); } + foreach (var typeArgument in node.TypeArguments) + { + context.RenderNode(typeArgument); + } + foreach (var attribute in node.Attributes) { context.RenderNode(attribute); @@ -363,7 +368,10 @@ namespace Microsoft.AspNetCore.Blazor.Razor // Consider what would happen if the user's cursor was inside the element. At // design -time we want to render an empty lambda to provide proper scoping // for any code that the user types. - context.RenderNode(new ComponentChildContentIntermediateNode()); + context.RenderNode(new ComponentChildContentIntermediateNode() + { + TypeName = BlazorApi.RenderFragment.FullTypeName, + }); } foreach (var capture in node.Captures) @@ -425,7 +433,7 @@ namespace Microsoft.AspNetCore.Blazor.Razor context.CodeWriter.Write(DesignTimeVariable); context.CodeWriter.Write(" = "); context.CodeWriter.Write("new "); - context.CodeWriter.Write(node.BoundAttribute.TypeName); + context.CodeWriter.Write(node.TypeName); context.CodeWriter.Write("("); context.CodeWriter.WriteLine(); @@ -448,7 +456,7 @@ namespace Microsoft.AspNetCore.Blazor.Razor { context.CodeWriter.Write(BlazorApi.RuntimeHelpers.TypeCheck); context.CodeWriter.Write("<"); - context.CodeWriter.Write(node.BoundAttribute.TypeName); + context.CodeWriter.Write(node.TypeName); context.CodeWriter.Write(">"); context.CodeWriter.Write("("); } @@ -504,6 +512,41 @@ namespace Microsoft.AspNetCore.Blazor.Razor _scopeStack.CloseScope(context); } + public override void WriteComponentTypeArgument(CodeRenderingContext context, ComponentTypeArgumentExtensionNode node) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (node == null) + { + throw new ArgumentNullException(nameof(node)); + } + + // At design type we want write the equivalent of: + // + // __o = typeof(TItem); + context.CodeWriter.Write(DesignTimeVariable); + context.CodeWriter.Write(" = "); + context.CodeWriter.Write("typeof("); + + var tokens = GetCSharpTokens(node); + for (var i = 0; i < tokens.Count; i++) + { + WriteCSharpToken(context, tokens[i]); + } + + context.CodeWriter.Write(");"); + context.CodeWriter.WriteLine(); + + IReadOnlyList GetCSharpTokens(ComponentTypeArgumentExtensionNode arg) + { + // We generally expect all children to be CSharp, this is here just in case. + return arg.FindDescendantNodes().Where(t => t.IsCSharp).ToArray(); + } + } + public override void WriteTemplate(CodeRenderingContext context, TemplateIntermediateNode node) { if (context == null) diff --git a/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/BlazorDiagnosticFactory.cs b/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/BlazorDiagnosticFactory.cs index 9b326e24e9..cae48af8f1 100644 --- a/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/BlazorDiagnosticFactory.cs +++ b/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/BlazorDiagnosticFactory.cs @@ -263,5 +263,23 @@ namespace Microsoft.AspNetCore.Blazor.Razor childContent2.AttributeName, component2.TagName); } + + public static readonly RazorDiagnosticDescriptor GenericComponentMissingTypeArgument = + new RazorDiagnosticDescriptor( + "BL10000", + () => "The component '{0}' is missing required type arguments. Specify the missing types using the attributes: {1}.", + RazorDiagnosticSeverity.Error); + + public static RazorDiagnostic Create_GenericComponentMissingTypeArgument( + SourceSpan? source, + ComponentExtensionNode component, + IEnumerable attributes) + { + Debug.Assert(component.Component.IsGenericTypedComponent()); + + var attributesText = string.Join(", ", attributes.Select(a => $"'{a.Name}'")); + return RazorDiagnostic.Create(GenericComponentMissingTypeArgument, source ?? SourceSpan.Undefined, component.TagName, attributesText); + } } + } diff --git a/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/BlazorExtensionInitializer.cs b/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/BlazorExtensionInitializer.cs index 9e856fd883..403c3d7303 100644 --- a/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/BlazorExtensionInitializer.cs +++ b/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/BlazorExtensionInitializer.cs @@ -54,6 +54,7 @@ namespace Microsoft.AspNetCore.Blazor.Razor InjectDirective.Register(builder); LayoutDirective.Register(builder); PageDirective.Register(builder); + TypeParamDirective.Register(builder); builder.Features.Remove(builder.Features.OfType().Single()); builder.Features.Add(new BlazorImportProjectFeature()); @@ -82,6 +83,7 @@ namespace Microsoft.AspNetCore.Blazor.Razor builder.Features.Add(new RefLoweringPass()); builder.Features.Add(new BindLoweringPass()); builder.Features.Add(new TemplateDiagnosticPass()); + builder.Features.Add(new GenericComponentPass()); builder.Features.Add(new ChildContentDiagnosticPass()); builder.Features.Add(new HtmlBlockPass()); diff --git a/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/BlazorMetadata.cs b/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/BlazorMetadata.cs index 648f94c42b..cebb3d9041 100644 --- a/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/BlazorMetadata.cs +++ b/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/BlazorMetadata.cs @@ -46,6 +46,10 @@ namespace Microsoft.AspNetCore.Blazor.Razor public static readonly string RuntimeName = "Blazor.IComponent"; public readonly static string TagHelperKind = "Blazor.Component"; + + public readonly static string GenericTypedKey = "Blazor.GenericTyped"; + + public readonly static string TypeParameterKey = "Blazor.TypeParameter"; } public static class EventHandler diff --git a/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/BlazorNodeWriter.cs b/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/BlazorNodeWriter.cs index 7642c9ed78..63d266d708 100644 --- a/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/BlazorNodeWriter.cs +++ b/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/BlazorNodeWriter.cs @@ -19,6 +19,8 @@ namespace Microsoft.AspNetCore.Blazor.Razor public abstract void WriteComponentChildContent(CodeRenderingContext context, ComponentChildContentIntermediateNode node); + public abstract void WriteComponentTypeArgument(CodeRenderingContext context, ComponentTypeArgumentExtensionNode node); + public abstract void WriteHtmlElement(CodeRenderingContext context, HtmlElementIntermediateNode node); public abstract void WriteHtmlBlock(CodeRenderingContext context, HtmlBlockIntermediateNode node); diff --git a/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/BlazorRuntimeNodeWriter.cs b/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/BlazorRuntimeNodeWriter.cs index b5b4b96dc8..bd0b85a57c 100644 --- a/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/BlazorRuntimeNodeWriter.cs +++ b/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/BlazorRuntimeNodeWriter.cs @@ -307,6 +307,9 @@ namespace Microsoft.AspNetCore.Blazor.Razor context.CodeWriter.Write(");"); context.CodeWriter.WriteLine(); + // We can skip type arguments during runtime codegen, they are handled in the + // type/parameter declarations. + foreach (var attribute in node.Attributes) { context.RenderNode(attribute); @@ -322,7 +325,7 @@ namespace Microsoft.AspNetCore.Blazor.Razor context.RenderNode(capture); } - // builder.OpenComponent(42); + // builder.CloseComponent(); context.CodeWriter.Write(_scopeStack.BuilderVarName); context.CodeWriter.Write("."); context.CodeWriter.Write(BlazorApi.RenderTreeBuilder.CloseComponent); @@ -376,7 +379,7 @@ namespace Microsoft.AspNetCore.Blazor.Razor (node.BoundAttribute?.IsChildContentProperty() ?? false)) { context.CodeWriter.Write("new "); - context.CodeWriter.Write(node.BoundAttribute.TypeName); + context.CodeWriter.Write(node.TypeName); context.CodeWriter.Write("("); for (var i = 0; i < tokens.Count; i++) @@ -392,7 +395,7 @@ namespace Microsoft.AspNetCore.Blazor.Razor { context.CodeWriter.Write(BlazorApi.RuntimeHelpers.TypeCheck); context.CodeWriter.Write("<"); - context.CodeWriter.Write(node.BoundAttribute.TypeName); + context.CodeWriter.Write(node.TypeName); context.CodeWriter.Write(">"); context.CodeWriter.Write("("); } @@ -454,6 +457,12 @@ namespace Microsoft.AspNetCore.Blazor.Razor _scopeStack.CloseScope(context); } + public override void WriteComponentTypeArgument(CodeRenderingContext context, ComponentTypeArgumentExtensionNode node) + { + // We can skip type arguments during runtime codegen, they are handled in the + // type/parameter declarations. + } + public override void WriteTemplate(CodeRenderingContext context, TemplateIntermediateNode node) { if (context == null) diff --git a/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/ComponentAttributeExtensionNode.cs b/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/ComponentAttributeExtensionNode.cs index 64797b5fca..0a347d5bbb 100644 --- a/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/ComponentAttributeExtensionNode.cs +++ b/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/ComponentAttributeExtensionNode.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// 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; @@ -49,6 +49,7 @@ namespace Microsoft.AspNetCore.Blazor.Razor PropertyName = propertyNode.BoundAttribute.GetPropertyName(); Source = propertyNode.Source; TagHelper = propertyNode.TagHelper; + TypeName = propertyNode.BoundAttribute.IsWeaklyTyped() ? null : propertyNode.BoundAttribute.TypeName; for (var i = 0; i < propertyNode.Children.Count; i++) { @@ -74,6 +75,7 @@ namespace Microsoft.AspNetCore.Blazor.Razor PropertyName = attributeNode.BoundAttribute.GetPropertyName(); Source = attributeNode.Source; TagHelper = attributeNode.TagHelper; + TypeName = attributeNode.BoundAttribute.IsWeaklyTyped() ? null : attributeNode.BoundAttribute.TypeName; for (var i = 0; i < attributeNode.Children.Count; i++) { @@ -98,6 +100,8 @@ namespace Microsoft.AspNetCore.Blazor.Razor public TagHelperDescriptor TagHelper { get; set; } + public string TypeName { get; set; } + public override void Accept(IntermediateNodeVisitor visitor) { if (visitor == null) diff --git a/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/ComponentChildContentIntermediateNode.cs b/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/ComponentChildContentIntermediateNode.cs index 9b4551cebc..211eccfb6f 100644 --- a/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/ComponentChildContentIntermediateNode.cs +++ b/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/ComponentChildContentIntermediateNode.cs @@ -21,7 +21,7 @@ namespace Microsoft.AspNetCore.Blazor.Razor public string ParameterName { get; set; } = "context"; - public string TypeName => BoundAttribute?.TypeName == null ? BlazorApi.RenderFragment.FullTypeName : BoundAttribute.TypeName; + public string TypeName { get; set; } public override void Accept(IntermediateNodeVisitor visitor) { diff --git a/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/ComponentDocumentClassifierPass.cs b/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/ComponentDocumentClassifierPass.cs index 9ec80b81e3..9e89eac457 100644 --- a/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/ComponentDocumentClassifierPass.cs +++ b/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/ComponentDocumentClassifierPass.cs @@ -1,7 +1,8 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// 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.Collections.Generic; using System.IO; using System.Linq; using System.Text; @@ -91,6 +92,19 @@ namespace Microsoft.AspNetCore.Blazor.Razor @class.Modifiers.Clear(); @class.Modifiers.Add("public"); + var documentNode = codeDocument.GetDocumentIntermediateNode(); + var typeParamReferences = documentNode.FindDirectiveReferences(TypeParamDirective.Directive); + for (var i = 0; i < typeParamReferences.Count; i++) + { + var typeParamNode = (DirectiveIntermediateNode)typeParamReferences[i].Node; + if (typeParamNode.HasDiagnostics) + { + continue; + } + + @class.TypeParameters.Add(new TypeParameter() { ParameterName = typeParamNode.Tokens.First().Content, }); + } + method.ReturnType = "void"; method.MethodName = BlazorApi.BlazorComponent.BuildRenderTree; method.Modifiers.Clear(); diff --git a/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/ComponentExtensionNode.cs b/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/ComponentExtensionNode.cs index bfb222d405..45e955ffb5 100644 --- a/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/ComponentExtensionNode.cs +++ b/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/ComponentExtensionNode.cs @@ -23,9 +23,11 @@ namespace Microsoft.AspNetCore.Blazor.Razor public TagHelperDescriptor Component { get; set; } - public string TagName { get; set; } + public IEnumerable TypeArguments => Children.OfType(); - public string TypeName => Component?.GetTypeName(); + public string TagName { get; set; } + + public string TypeName { get; set; } public override void Accept(IntermediateNodeVisitor visitor) { @@ -76,6 +78,13 @@ namespace Microsoft.AspNetCore.Blazor.Razor builder.Append("=\"...\""); } + foreach (var typeArgument in TypeArguments) + { + builder.Append(" "); + builder.Append(typeArgument.TypeParameterName); + builder.Append("=\"...\""); + } + builder.Append(">"); builder.Append(ChildContents.Any() ? "..." : string.Empty); builder.Append(" - { - pb.Name = property.property.Name; - pb.TypeName = property.property.Type.ToDisplayString(FullNameTypeDisplayFormat); - pb.SetPropertyName(property.property.Name); - - if (property.kind == PropertyKind.Enum) - { - pb.IsEnum = true; - } - - if (property.kind == PropertyKind.ChildContent) - { - pb.Metadata.Add(BlazorMetadata.Component.ChildContentKey, bool.TrueString); - } - - if (property.kind == PropertyKind.Delegate) - { - pb.Metadata.Add(BlazorMetadata.Component.DelegateSignatureKey, bool.TrueString); - } - - xml = property.property.GetDocumentationCommentXml(); - if (!string.IsNullOrEmpty(xml)) - { - pb.Documentation = xml; - } - }); + CreateProperty(builder, property.property, property.kind); } var descriptor = builder.Build(); @@ -145,6 +134,97 @@ namespace Microsoft.AspNetCore.Blazor.Razor return descriptor; } + private void CreateProperty(TagHelperDescriptorBuilder builder, IPropertySymbol property, PropertyKind kind) + { + builder.BindAttribute(pb => + { + pb.Name = property.Name; + pb.TypeName = property.Type.ToDisplayString(FullNameTypeDisplayFormat); + pb.SetPropertyName(property.Name); + + if (kind == PropertyKind.Enum) + { + pb.IsEnum = true; + } + + if (kind == PropertyKind.ChildContent) + { + pb.Metadata.Add(BlazorMetadata.Component.ChildContentKey, bool.TrueString); + } + + if (kind == PropertyKind.Delegate) + { + pb.Metadata.Add(BlazorMetadata.Component.DelegateSignatureKey, bool.TrueString); + } + + if (HasTypeParameter(property.Type)) + { + pb.Metadata.Add(BlazorMetadata.Component.GenericTypedKey, bool.TrueString); + } + + var xml = property.GetDocumentationCommentXml(); + if (!string.IsNullOrEmpty(xml)) + { + pb.Documentation = xml; + } + }); + + bool HasTypeParameter(ITypeSymbol type) + { + if (type is ITypeParameterSymbol) + { + return true; + } + + // We need to check for cases like: + // [Parameter] List MyProperty { get; set; } + // AND + // [Parameter] List MyProperty { get; set; } + // + // We need to inspect the type arguments to tell the difference between a property that + // uses the containing class' type parameter(s) and a vanilla usage of generic types like + // List<> and Dictionary<,> + // + // Since we need to handle cases like RenderFragment>, this check must be recursive. + if (type is INamedTypeSymbol namedType && namedType.IsGenericType) + { + var typeArguments = namedType.TypeArguments; + for (var i = 0; i < typeArguments.Length; i++) + { + if (HasTypeParameter(typeArguments[i])) + { + return true; + } + } + + // Another case to handle - if the type being inspected is a nested type + // inside a generic containing class. The common usage for this would be a case + // where a generic templated component defines a 'context' nested class. + if (namedType.ContainingType != null && HasTypeParameter(namedType.ContainingType)) + { + return true; + } + } + + return false; + } + } + + private void CreateTypeParameterProperty(TagHelperDescriptorBuilder builder, ITypeSymbol typeParameter) + { + builder.BindAttribute(pb => + { + pb.DisplayName = typeParameter.Name; + pb.Name = typeParameter.Name; + pb.TypeName = typeof(Type).FullName; + pb.SetPropertyName(typeParameter.Name); + + pb.Metadata[BlazorMetadata.Component.TypeParameterKey] = bool.TrueString; + + pb.Documentation = string.Format(Resources.ComponentTypeParameter_Documentation, typeParameter.Name, builder.Name); + }); + } + private TagHelperDescriptor CreateChildContentDescriptor(BlazorSymbols symbols, TagHelperDescriptor component, BoundAttributeDescriptor attribute) { var typeName = component.GetTypeName() + "." + attribute.Name; @@ -396,7 +476,6 @@ namespace Microsoft.AspNetCore.Blazor.Razor return symbol.DeclaredAccessibility == Accessibility.Public && !symbol.IsAbstract && - !symbol.IsGenericType && symbol.AllInterfaces.Contains(_symbols.IComponent); } } diff --git a/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/ComponentTypeArgumentExtensionNode.cs b/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/ComponentTypeArgumentExtensionNode.cs new file mode 100644 index 0000000000..6f5e5e0aab --- /dev/null +++ b/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/ComponentTypeArgumentExtensionNode.cs @@ -0,0 +1,69 @@ +// 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 Microsoft.AspNetCore.Razor.Language; +using Microsoft.AspNetCore.Razor.Language.CodeGeneration; +using Microsoft.AspNetCore.Razor.Language.Intermediate; + +namespace Microsoft.AspNetCore.Blazor.Razor +{ + internal class ComponentTypeArgumentExtensionNode : ExtensionIntermediateNode + { + public ComponentTypeArgumentExtensionNode(TagHelperPropertyIntermediateNode propertyNode) + { + if (propertyNode == null) + { + throw new ArgumentNullException(nameof(propertyNode)); + } + + BoundAttribute = propertyNode.BoundAttribute; + Source = propertyNode.Source; + TagHelper = propertyNode.TagHelper; + + for (var i = 0; i < propertyNode.Children.Count; i++) + { + Children.Add(propertyNode.Children[i]); + } + + for (var i = 0; i < propertyNode.Diagnostics.Count; i++) + { + Diagnostics.Add(propertyNode.Diagnostics[i]); + } + } + + public override IntermediateNodeCollection Children { get; } = new IntermediateNodeCollection(); + + public BoundAttributeDescriptor BoundAttribute { get; set; } + + public string TypeParameterName => BoundAttribute.Name; + + public TagHelperDescriptor TagHelper { get; set; } + + public override void Accept(IntermediateNodeVisitor visitor) + { + if (visitor == null) + { + throw new ArgumentNullException(nameof(visitor)); + } + + AcceptExtensionNode(this, visitor); + } + + public override void WriteNode(CodeTarget target, CodeRenderingContext context) + { + if (target == null) + { + throw new ArgumentNullException(nameof(target)); + } + + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + var writer = (BlazorNodeWriter)context.NodeWriter; + writer.WriteComponentTypeArgument(context, this); + } + } +} diff --git a/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/GenericComponentPass.cs b/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/GenericComponentPass.cs new file mode 100644 index 0000000000..cef0a2dad4 --- /dev/null +++ b/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/GenericComponentPass.cs @@ -0,0 +1,118 @@ +// 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.Collections.Generic; +using System.Linq; +using Microsoft.AspNetCore.Razor.Language; +using Microsoft.AspNetCore.Razor.Language.Intermediate; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace Microsoft.AspNetCore.Blazor.Razor +{ + // This pass: + // 1. Adds diagnostics for missing generic type arguments + // 2. Rewrites the type name of the component to substitute generic type arguments + // 3. Rewrites the type names of parameters/child content to substitute generic type arguments + internal class GenericComponentPass : IntermediateNodePassBase, IRazorOptimizationPass + { + // Runs after components/eventhandlers/ref/bind/templates. We want to validate every component + // and it's usage of ChildContent. + public override int Order => 160; + + protected override void ExecuteCore(RazorCodeDocument codeDocument, DocumentIntermediateNode documentNode) + { + var visitor = new Visitor(); + visitor.Visit(documentNode); + } + + + private class Visitor : IntermediateNodeWalker, IExtensionIntermediateNodeVisitor + { + public void VisitExtension(ComponentExtensionNode node) + { + if (node.Component.IsGenericTypedComponent()) + { + // Not generic, ignore. + Process(node); + } + + base.VisitDefault(node); + } + + private void Process(ComponentExtensionNode node) + { + // First collect all of the information we have about each type parameter + var bindings = new Dictionary(); + foreach (var attribute in node.Component.GetTypeParameters()) + { + bindings.Add(attribute.Name, new GenericTypeNameRewriter.Binding() { Attribute = attribute, }); + } + + foreach (var typeArgumentNode in node.TypeArguments) + { + var binding = bindings[typeArgumentNode.TypeParameterName]; + binding.Node = typeArgumentNode; + binding.Content = GetContent(typeArgumentNode); + } + + // Right now we don't have type inference, so all type arguments are required. + var missing = new List(); + foreach (var binding in bindings) + { + if (binding.Value.Node == null || string.IsNullOrWhiteSpace(binding.Value.Content)) + { + missing.Add(binding.Value.Attribute); + } + } + + if (missing.Count > 0) + { + // We add our own error for this because its likely the user will see other errors due + // to incorrect codegen without the types. Our errors message will pretty clearly indicate + // what to do, whereas the other errors might be confusing. + node.Diagnostics.Add(BlazorDiagnosticFactory.Create_GenericComponentMissingTypeArgument(node.Source, node, missing)); + } + + var rewriter = new GenericTypeNameRewriter(bindings); + + // Rewrite the component type name + node.TypeName = RewriteTypeName(rewriter, node.TypeName); + + foreach (var attribute in node.Attributes) + { + if (attribute.BoundAttribute?.IsGenericTypedProperty() ?? false && attribute.TypeName != null) + { + // If we know the type name, then replace any generic type parameter inside it with + // the known types. + attribute.TypeName = RewriteTypeName(rewriter, attribute.TypeName); + } + } + + foreach (var childContent in node.ChildContents) + { + if (childContent.BoundAttribute?.IsGenericTypedProperty() ?? false && childContent.TypeName != null) + { + // If we know the type name, then replace any generic type parameter inside it with + // the known types. + childContent.TypeName = RewriteTypeName(rewriter, childContent.TypeName); + } + } + } + + private string RewriteTypeName(GenericTypeNameRewriter rewriter, string typeName) + { + var parsed = SyntaxFactory.ParseTypeName(typeName); + var rewritten = (TypeSyntax)rewriter.Visit(parsed); + return rewritten.ToFullString(); + } + + private string GetContent(ComponentTypeArgumentExtensionNode node) + { + return string.Join(string.Empty, node.FindDescendantNodes().Where(t => t.IsCSharp).Select(t => t.Content)); + } + } + } +} diff --git a/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/GenericTypeNameRewriter.cs b/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/GenericTypeNameRewriter.cs new file mode 100644 index 0000000000..a98e520eda --- /dev/null +++ b/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/GenericTypeNameRewriter.cs @@ -0,0 +1,62 @@ +// 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.Collections.Generic; +using Microsoft.AspNetCore.Razor.Language; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace Microsoft.AspNetCore.Blazor.Razor +{ + internal class GenericTypeNameRewriter : CSharpSyntaxRewriter + { + private readonly Dictionary _bindings; + + public GenericTypeNameRewriter(Dictionary bindings) + { + _bindings = bindings; + } + + public override SyntaxNode Visit(SyntaxNode node) + { + // We can handle a single IdentifierNameSyntax at the top level (like 'TItem) + // OR a GenericNameSyntax recursively (like `List`) + if (node is IdentifierNameSyntax identifier && !(identifier.Parent is QualifiedNameSyntax)) + { + if (_bindings.TryGetValue(identifier.Identifier.Text, out var binding)) + { + // If we don't have a valid replacement, use object. This will make the code at least reasonable + // compared to leaving the type parameter in place. + // + // We add our own diagnostics for missing/invalid type parameters anyway. + var replacement = binding?.Content == null ? typeof(object).FullName : binding.Content; + return identifier.Update(SyntaxFactory.Identifier(replacement)); + } + } + + return base.Visit(node); + } + + public override SyntaxNode VisitGenericName(GenericNameSyntax node) + { + var args = node.TypeArgumentList.Arguments; + for (var i = 0; i < args.Count; i++) + { + var typeArgument = args[i]; + args = args.Replace(typeArgument, (TypeSyntax)Visit(typeArgument)); + } + + return node.WithTypeArgumentList(node.TypeArgumentList.WithArguments(args)); + } + + public class Binding + { + public BoundAttributeDescriptor Attribute { get; set; } + + public string Content { get; set; } + + public ComponentTypeArgumentExtensionNode Node { get; set; } + } + } +} diff --git a/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/Resources.Designer.cs b/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/Resources.Designer.cs index e6b792f3ac..56fbd920e3 100644 --- a/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/Resources.Designer.cs +++ b/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/Resources.Designer.cs @@ -114,6 +114,15 @@ namespace Microsoft.AspNetCore.Blazor.Razor { } } + /// + /// Looks up a localized string similar to Specifies the type of the type parameter {0} for the {1} component.. + /// + internal static string ComponentTypeParameter_Documentation { + get { + return ResourceManager.GetString("ComponentTypeParameter_Documentation", resourceCulture); + } + } + /// /// Looks up a localized string similar to Sets the '{0}' attribute to the provided string or delegate value. A delegate value should be of type '{1}'.. /// @@ -221,5 +230,32 @@ namespace Microsoft.AspNetCore.Blazor.Razor { return ResourceManager.GetString("RefTagHelper_Documentation", resourceCulture); } } + + /// + /// Looks up a localized string similar to Declares a generic type parameter for the generated component class.. + /// + internal static string TypeParamDirective_Description { + get { + return ResourceManager.GetString("TypeParamDirective_Description", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The name of the type parameter.. + /// + internal static string TypeParamDirective_Token_Description { + get { + return ResourceManager.GetString("TypeParamDirective_Token_Description", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to type parameter. + /// + internal static string TypeParamDirective_Token_Name { + get { + return ResourceManager.GetString("TypeParamDirective_Token_Name", resourceCulture); + } + } } } diff --git a/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/Resources.resx b/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/Resources.resx index 9ec29c40ea..3cec7f2d43 100644 --- a/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/Resources.resx +++ b/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/Resources.resx @@ -135,6 +135,9 @@ Specifies the parameter name for the '{0}' lambda expression. + + Specifies the type of the type parameter {0} for the {1} component. + Sets the '{0}' attribute to the provided string or delegate value. A delegate value should be of type '{1}'. @@ -171,4 +174,13 @@ Populates the specified field or property with a reference to the element or component. + + Declares a generic type parameter for the generated component class. + + + The name of the type parameter. + + + type parameter + \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/TagHelperBoundAttributeDescriptorExtensions.cs b/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/TagHelperBoundAttributeDescriptorExtensions.cs index 4971b40c23..03242a37fa 100644 --- a/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/TagHelperBoundAttributeDescriptorExtensions.cs +++ b/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/TagHelperBoundAttributeDescriptorExtensions.cs @@ -22,6 +22,30 @@ namespace Microsoft.AspNetCore.Blazor.Razor string.Equals(value, bool.TrueString); } + public static bool IsGenericTypedProperty(this BoundAttributeDescriptor attribute) + { + if (attribute == null) + { + throw new ArgumentNullException(nameof(attribute)); + } + + return + attribute.Metadata.TryGetValue(BlazorMetadata.Component.GenericTypedKey, out var value) && + string.Equals(value, bool.TrueString); + } + + public static bool IsTypeParameterProperty(this BoundAttributeDescriptor attribute) + { + if (attribute == null) + { + throw new ArgumentNullException(nameof(attribute)); + } + + return + attribute.Metadata.TryGetValue(BlazorMetadata.Component.TypeParameterKey, out var value) && + string.Equals(value, bool.TrueString); + } + public static bool IsWeaklyTyped(this BoundAttributeDescriptor attribute) { if (attribute == null) diff --git a/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/TagHelperDescriptorExtensions.cs b/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/TagHelperDescriptorExtensions.cs index ebb66117f4..0b8beec586 100644 --- a/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/TagHelperDescriptorExtensions.cs +++ b/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/TagHelperDescriptorExtensions.cs @@ -35,6 +35,19 @@ namespace Microsoft.AspNetCore.Blazor.Razor string.Equals(bool.TrueString, fallback); } + public static bool IsGenericTypedComponent(this TagHelperDescriptor tagHelper) + { + if (tagHelper == null) + { + throw new ArgumentNullException(nameof(tagHelper)); + } + + return + IsComponentTagHelper(tagHelper) && + tagHelper.Metadata.TryGetValue(BlazorMetadata.Component.GenericTypedKey, out var value) && + string.Equals(bool.TrueString, value); + } + public static bool IsInputElementBindTagHelper(this TagHelperDescriptor tagHelper) { if (tagHelper == null) @@ -160,5 +173,27 @@ namespace Microsoft.AspNetCore.Blazor.Razor } } } + + /// + /// Gets the set of component attributes that represent generic type parameters of the component type. + /// + /// The . + /// The type parameter attributes + public static IEnumerable GetTypeParameters(this TagHelperDescriptor tagHelper) + { + if (tagHelper == null) + { + throw new ArgumentNullException(nameof(tagHelper)); + } + + for (var i = 0; i < tagHelper.BoundAttributes.Count; i++) + { + var attribute = tagHelper.BoundAttributes[i]; + if (attribute.IsTypeParameterProperty()) + { + yield return attribute; + } + } + } } } diff --git a/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/TypeParamDirective.cs b/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/TypeParamDirective.cs new file mode 100644 index 0000000000..53ee3160c4 --- /dev/null +++ b/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/TypeParamDirective.cs @@ -0,0 +1,32 @@ +// 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 Microsoft.AspNetCore.Razor.Language; + +namespace Microsoft.AspNetCore.Blazor.Razor +{ + internal class TypeParamDirective + { + public static readonly DirectiveDescriptor Directive = DirectiveDescriptor.CreateDirective( + "typeparam", + DirectiveKind.SingleLine, + builder => + { + builder.AddMemberToken(Resources.TypeParamDirective_Token_Name, Resources.TypeParamDirective_Token_Description); + builder.Usage = DirectiveUsage.FileScopedMultipleOccurring; + builder.Description = Resources.TypeParamDirective_Description; + }); + + public static RazorProjectEngineBuilder Register(RazorProjectEngineBuilder builder) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + builder.AddDirective(Directive); + return builder; + } + } +} diff --git a/test/Microsoft.AspNetCore.Blazor.Build.Test/CodeGenerationTestBase.cs b/test/Microsoft.AspNetCore.Blazor.Build.Test/CodeGenerationTestBase.cs index 88684c7e40..e820360060 100644 --- a/test/Microsoft.AspNetCore.Blazor.Build.Test/CodeGenerationTestBase.cs +++ b/test/Microsoft.AspNetCore.Blazor.Build.Test/CodeGenerationTestBase.cs @@ -11,8 +11,8 @@ namespace Microsoft.AspNetCore.Blazor.Build.Test internal override bool UseTwoPhaseCompilation => true; public CodeGenerationTestBase() + : base(generateBaselines: false) { - GenerateBaselines = true; } #region Basics @@ -81,6 +81,36 @@ namespace Test CompileToAssembly(generated); } + [Fact] + public void ComponentWithTypeParameters() + { + // Arrange + + // Act + var generated = CompileToCSharp(@" +@using Microsoft.AspNetCore.Blazor; +@typeparam TItem1 +@typeparam TItem2 + +

Item1

+@foreach (var item2 in Items2) +{ +

+ @ChildContent(item2); +

+} +@functions { + [Parameter] TItem1 Item1 { get; set; } + [Parameter] List Items2 { get; set; } + [Parameter] RenderFragment ChildContent { get; set; } +}"); + + // Assert + AssertDocumentNodeMatchesBaseline(generated.CodeDocument); + AssertCSharpDocumentMatchesBaseline(generated.CodeDocument); + CompileToAssembly(generated); + } + [Fact] public void ChildComponent_WithExplicitStringParameter() { @@ -1199,6 +1229,146 @@ namespace Test #endregion + #region Generics + + [Fact] + public void ChildComponent_Generic() + { + // Arrange + AdditionalSyntaxTrees.Add(Parse(@" +using Microsoft.AspNetCore.Blazor.Components; + +namespace Test +{ + public class MyComponent : BlazorComponent + { + [Parameter] TItem Item { get; set; } + } +} +")); + + // Act + var generated = CompileToCSharp(@" +@addTagHelper *, TestAssembly +"); + + // Assert + AssertDocumentNodeMatchesBaseline(generated.CodeDocument); + AssertCSharpDocumentMatchesBaseline(generated.CodeDocument); + CompileToAssembly(generated); + } + + [Fact] + public void ChildComponent_GenericBind() + { + // Arrange + AdditionalSyntaxTrees.Add(Parse(@" +using System; +using Microsoft.AspNetCore.Blazor.Components; + +namespace Test +{ + public class MyComponent : BlazorComponent + { + [Parameter] + TItem Value { get; set; } + + [Parameter] + Action ValueChanged { get; set; } + } +} +")); + + // Act + var generated = CompileToCSharp(@" +@addTagHelper *, TestAssembly + +@functions { + string Value; +}"); + + // Assert + AssertDocumentNodeMatchesBaseline(generated.CodeDocument); + AssertCSharpDocumentMatchesBaseline(generated.CodeDocument); + CompileToAssembly(generated); + } + + [Fact] + public void ChildComponent_GenericChildContent() + { + // Arrange + AdditionalSyntaxTrees.Add(Parse(@" +using Microsoft.AspNetCore.Blazor; +using Microsoft.AspNetCore.Blazor.Components; + +namespace Test +{ + public class MyComponent : BlazorComponent + { + [Parameter] TItem Item { get; set; } + + [Parameter] RenderFragment ChildContent { get; set; } + } +} +")); + + // Act + var generated = CompileToCSharp(@" +@addTagHelper *, TestAssembly + +
@context.ToLower()
+
"); + + // Assert + AssertDocumentNodeMatchesBaseline(generated.CodeDocument); + AssertCSharpDocumentMatchesBaseline(generated.CodeDocument); + CompileToAssembly(generated); + } + + [Fact] + public void ChildComponent_MultipleGenerics() + { + // Arrange + AdditionalSyntaxTrees.Add(Parse(@" +using Microsoft.AspNetCore.Blazor; +using Microsoft.AspNetCore.Blazor.Components; + +namespace Test +{ + public class MyComponent : BlazorComponent + { + [Parameter] TItem1 Item { get; set; } + + [Parameter] RenderFragment ChildContent { get; set; } + + [Parameter] RenderFragment AnotherChildContent { get; set; } + + public class Context + { + public TItem2 Item { get; set; } + } + } +} +")); + + // Act + var generated = CompileToCSharp(@" +@addTagHelper *, TestAssembly + +
@context.ToLower()
+ + @System.Math.Max(0, item.Item); + +
"); + + // Assert + AssertDocumentNodeMatchesBaseline(generated.CodeDocument); + AssertCSharpDocumentMatchesBaseline(generated.CodeDocument); + CompileToAssembly(generated); + } + + #endregion + #region Ref [Fact] @@ -1335,7 +1505,7 @@ namespace Test AssertCSharpDocumentMatchesBaseline(generated.CodeDocument); CompileToAssembly(generated); } - + [Fact] public void RazorTemplate_NonGeneric_InImplicitExpression() { diff --git a/test/Microsoft.AspNetCore.Blazor.Build.Test/ComponentDiscoveryRazorIntegrationTest.cs b/test/Microsoft.AspNetCore.Blazor.Build.Test/ComponentDiscoveryRazorIntegrationTest.cs index 6b069f0c9b..e1a9c40976 100644 --- a/test/Microsoft.AspNetCore.Blazor.Build.Test/ComponentDiscoveryRazorIntegrationTest.cs +++ b/test/Microsoft.AspNetCore.Blazor.Build.Test/ComponentDiscoveryRazorIntegrationTest.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// 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.IO; @@ -85,5 +85,43 @@ namespace Test.AnotherNamespace var bindings = result.CodeDocument.GetTagHelperContext(); Assert.Single(bindings.TagHelpers, t => t.Name == "Microsoft.AspNetCore.Blazor.Routing.NavLink"); } + + [Fact] + public void ComponentDiscovery_CanFindComponent_WithTypeParameter() + { + // Arrange + + // Act + var result = CompileToCSharp("UniqueName.cshtml", @" +@addTagHelper *, TestAssembly +@typeparam TItem +@functions { + [Parameter] TItem Item { get; set; } +}"); + + // Assert + var bindings = result.CodeDocument.GetTagHelperContext(); + Assert.Single(bindings.TagHelpers, t => t.Name == "Test.UniqueName"); + } + + [Fact] + public void ComponentDiscovery_CanFindComponent_WithMultipleTypeParameters() + { + // Arrange + + // Act + var result = CompileToCSharp("UniqueName.cshtml", @" +@addTagHelper *, TestAssembly +@typeparam TItem1 +@typeparam TItem2 +@typeparam TItem3 +@functions { + [Parameter] TItem1 Item { get; set; } +}"); + + // Assert + var bindings = result.CodeDocument.GetTagHelperContext(); + Assert.Single(bindings.TagHelpers, t => t.Name == "Test.UniqueName"); + } } } diff --git a/test/Microsoft.AspNetCore.Blazor.Build.Test/GenericComponentRazorIntegrationTest.cs b/test/Microsoft.AspNetCore.Blazor.Build.Test/GenericComponentRazorIntegrationTest.cs new file mode 100644 index 0000000000..addde66eaa --- /dev/null +++ b/test/Microsoft.AspNetCore.Blazor.Build.Test/GenericComponentRazorIntegrationTest.cs @@ -0,0 +1,190 @@ +// 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 Microsoft.AspNetCore.Blazor.Razor; +using Microsoft.AspNetCore.Blazor.RenderTree; +using Microsoft.AspNetCore.Blazor.Test.Helpers; +using Microsoft.CodeAnalysis.CSharp; +using System.Collections.Generic; +using System.Linq; +using Xunit; + +namespace Microsoft.AspNetCore.Blazor.Build.Test +{ + public class GenericComponentRazorIntegrationTest : RazorIntegrationTestBase + { + private readonly CSharpSyntaxTree GenericContextComponent = Parse(@" +using System; +using System.Collections.Generic; +using Microsoft.AspNetCore.Blazor; +using Microsoft.AspNetCore.Blazor.Components; +using Microsoft.AspNetCore.Blazor.RenderTree; +namespace Test +{ + public class GenericContext : BlazorComponent + { + protected override void BuildRenderTree(RenderTreeBuilder builder) + { + var items = (IReadOnlyList)Items ?? Array.Empty(); + for (var i = 0; i < items.Count; i++) + { + builder.AddContent(i, ChildContent, new Context() { Index = i, Item = items[i], }); + } + } + + [Parameter] + List Items { get; set; } + + [Parameter] + RenderFragment ChildContent { get; set; } + + public class Context + { + public int Index { get; set; } + public TItem Item { get; set; } + } + } +} +"); + + private readonly CSharpSyntaxTree MultipleGenericParameterComponent = Parse(@" +using Microsoft.AspNetCore.Blazor; +using Microsoft.AspNetCore.Blazor.Components; +using Microsoft.AspNetCore.Blazor.RenderTree; +namespace Test +{ + public class MultipleGenericParameter : BlazorComponent + { + protected override void BuildRenderTree(RenderTreeBuilder builder) + { + builder.AddContent(0, Item1); + builder.AddContent(1, Item2); + builder.AddContent(2, Item3); + } + + [Parameter] + TItem1 Item1 { get; set; } + + [Parameter] + TItem2 Item2 { get; set; } + + [Parameter] + TItem3 Item3 { get; set; } + } +} +"); + + internal override bool UseTwoPhaseCompilation => true; + + [Fact] + public void Render_GenericComponent_WithChildContent() + { + // Arrange + AdditionalSyntaxTrees.Add(GenericContextComponent); + + var component = CompileToComponent(@" +@addTagHelper *, TestAssembly +() { 1, 2, })""> +
@(context.Item * context.Index)
+
"); + + // Act + var frames = GetRenderTree(component); + + // Assert + var genericComponentType = component.GetType().Assembly.DefinedTypes + .Where(t => t.Name == "GenericContext`1") + .Single() + .MakeGenericType(typeof(int)); + + Assert.Collection( + frames, + frame => AssertFrame.Component(frame, genericComponentType.FullName, 3, 0), + frame => AssertFrame.Attribute(frame, "Items", typeof(List), 1), + frame => AssertFrame.Attribute(frame, RenderTreeBuilder.ChildContent, 2), + frame => AssertFrame.Whitespace(frame, 3), + frame => AssertFrame.Element(frame, "div", 2, 4), + frame => AssertFrame.Text(frame, "0", 5), + frame => AssertFrame.Whitespace(frame, 6), + frame => AssertFrame.Whitespace(frame, 3), + frame => AssertFrame.Element(frame, "div", 2, 4), + frame => AssertFrame.Text(frame, "2", 5), + frame => AssertFrame.Whitespace(frame, 6)); + } + + [Fact] + public void Render_GenericComponent_MultipleParameters_WithChildContent() + { + // Arrange + AdditionalSyntaxTrees.Add(MultipleGenericParameterComponent); + + var component = CompileToComponent(@" +@addTagHelper *, TestAssembly +"); + + // Act + var frames = GetRenderTree(component); + + // Assert + var genericComponentType = component.GetType().Assembly.DefinedTypes + .Where(t => t.Name == "MultipleGenericParameter`3") + .Single() + .MakeGenericType(typeof(int), typeof(string), typeof(long)); + + Assert.Collection( + frames, + frame => AssertFrame.Component(frame, genericComponentType.FullName, 4, 0), + frame => AssertFrame.Attribute(frame, "Item1", 3, 1), + frame => AssertFrame.Attribute(frame, "Item2", "FOO", 2), + frame => AssertFrame.Attribute(frame, "Item3", 39L, 3), + frame => AssertFrame.Text(frame, "3", 0), + frame => AssertFrame.Text(frame, "FOO", 1), + frame => AssertFrame.Text(frame, "39", 2)); + } + + [Fact] + public void GenericComponent_WithoutAnyTypeParameters_TriggersDiagnostic() + { + // Arrange + AdditionalSyntaxTrees.Add(GenericContextComponent); + + // Act + var generated = CompileToCSharp(@" +@addTagHelper *, TestAssembly +"); + + // Assert + var diagnostic = Assert.Single(generated.Diagnostics); + Assert.Same(BlazorDiagnosticFactory.GenericComponentMissingTypeArgument.Id, diagnostic.Id); + Assert.Equal( + "The component 'GenericContext' is missing required type arguments. Specify the missing types using the attributes: 'TItem'.", + diagnostic.GetMessage()); + } + + [Fact] + public void GenericComponent_WithMissingTypeParameters_TriggersDiagnostic() + { + // Arrange + AdditionalSyntaxTrees.Add(MultipleGenericParameterComponent); + + // Act + var generated = CompileToCSharp(@" +@addTagHelper *, TestAssembly +"); + + // Assert + var diagnostic = Assert.Single(generated.Diagnostics); + Assert.Same(BlazorDiagnosticFactory.GenericComponentMissingTypeArgument.Id, diagnostic.Id); + Assert.Equal( + "The component 'MultipleGenericParameter' is missing required type arguments. " + + "Specify the missing types using the attributes: 'TItem2', 'TItem3'.", + diagnostic.GetMessage()); + } + } +} diff --git a/test/Microsoft.AspNetCore.Blazor.Build.Test/Razor/IntermediateNodeWriter.cs b/test/Microsoft.AspNetCore.Blazor.Build.Test/Razor/IntermediateNodeWriter.cs index 4e39b09cec..b3496ca6cc 100644 --- a/test/Microsoft.AspNetCore.Blazor.Build.Test/Razor/IntermediateNodeWriter.cs +++ b/test/Microsoft.AspNetCore.Blazor.Build.Test/Razor/IntermediateNodeWriter.cs @@ -19,6 +19,7 @@ namespace Microsoft.AspNetCore.Razor.Language.IntegrationTests IExtensionIntermediateNodeVisitor, IExtensionIntermediateNodeVisitor, IExtensionIntermediateNodeVisitor, + IExtensionIntermediateNodeVisitor, IExtensionIntermediateNodeVisitor, IExtensionIntermediateNodeVisitor { @@ -291,6 +292,11 @@ namespace Microsoft.AspNetCore.Razor.Language.IntegrationTests WriteContentNode(node, node.AttributeName); } + void IExtensionIntermediateNodeVisitor.VisitExtension(ComponentTypeArgumentExtensionNode node) + { + WriteContentNode(node, node.TypeParameterName); + } + void IExtensionIntermediateNodeVisitor.VisitExtension(RouteAttributeExtensionNode node) { WriteContentNode(node, node.Template); diff --git a/test/Microsoft.AspNetCore.Blazor.Build.Test/RazorBaselineIntegrationTestBase.cs b/test/Microsoft.AspNetCore.Blazor.Build.Test/RazorBaselineIntegrationTestBase.cs index fc29daeb9a..69098aad9c 100644 --- a/test/Microsoft.AspNetCore.Blazor.Build.Test/RazorBaselineIntegrationTestBase.cs +++ b/test/Microsoft.AspNetCore.Blazor.Build.Test/RazorBaselineIntegrationTestBase.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// 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; @@ -17,9 +17,14 @@ namespace Microsoft.AspNetCore.Blazor.Build.Test { private static readonly AsyncLocal _directoryPath = new AsyncLocal(); - protected RazorBaselineIntegrationTestBase() + protected RazorBaselineIntegrationTestBase(bool? generateBaselines = null) { TestProjectRoot = TestProject.GetProjectDirectory(GetType()); + + if (generateBaselines.HasValue) + { + GenerateBaselines = generateBaselines.Value; + } } // Used by the test framework to set the directory for test files. @@ -30,9 +35,9 @@ namespace Microsoft.AspNetCore.Blazor.Build.Test } #if GENERATE_BASELINES - protected bool GenerateBaselines { get; set; } = true; + protected bool GenerateBaselines { get; } = true; #else - protected bool GenerateBaselines { get; set; } = false; + protected bool GenerateBaselines { get; } = false; #endif protected string TestProjectRoot { get; } @@ -47,6 +52,12 @@ namespace Microsoft.AspNetCore.Blazor.Build.Test // Force consistent paths since they are going to be recorded in files. internal override string WorkingDirectory => ArbitraryWindowsPath; + [Fact] + public void GenerateBaselinesMustBeFalse() + { + Assert.False(GenerateBaselines, "GenerateBaselines should be set back to false before you check in!"); + } + protected void AssertDocumentNodeMatchesBaseline(RazorCodeDocument codeDocument) { var document = codeDocument.GetDocumentIntermediateNode(); diff --git a/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/DesignTimeCodeGenerationTest/ChildComponent_Generic/TestComponent.codegen.cs b/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/DesignTimeCodeGenerationTest/ChildComponent_Generic/TestComponent.codegen.cs new file mode 100644 index 0000000000..93fe02db54 --- /dev/null +++ b/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/DesignTimeCodeGenerationTest/ChildComponent_Generic/TestComponent.codegen.cs @@ -0,0 +1,50 @@ +// +#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; + public class TestComponent : Microsoft.AspNetCore.Blazor.Components.BlazorComponent + { + #pragma warning disable 219 + private void __RazorDirectiveTokenHelpers__() { + ((System.Action)(() => { +global::System.Object __typeHelper = "*, TestAssembly"; + } + ))(); + } + #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 = typeof( +#line 2 "x:\dir\subdir\Test\TestComponent.cshtml" + string + +#line default +#line hidden + ); + __o = Microsoft.AspNetCore.Blazor.Components.RuntimeHelpers.TypeCheck( +#line 2 "x:\dir\subdir\Test\TestComponent.cshtml" + "hi" + +#line default +#line hidden + ); + 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/ChildComponent_Generic/TestComponent.ir.txt b/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/DesignTimeCodeGenerationTest/ChildComponent_Generic/TestComponent.ir.txt new file mode 100644 index 0000000000..146087d762 --- /dev/null +++ b/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/DesignTimeCodeGenerationTest/ChildComponent_Generic/TestComponent.ir.txt @@ -0,0 +1,30 @@ +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 + 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 + 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 + ComponentExtensionNode - (31:1,0 [42] x:\dir\subdir\Test\TestComponent.cshtml) - MyComponent - Test.MyComponent + ComponentTypeArgumentExtensionNode - (50:1,19 [6] x:\dir\subdir\Test\TestComponent.cshtml) - TItem + IntermediateToken - (50:1,19 [6] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - string + ComponentAttributeExtensionNode - (63:1,32 [7] x:\dir\subdir\Test\TestComponent.cshtml) - Item - Item + CSharpExpression - (64:1,33 [6] x:\dir\subdir\Test\TestComponent.cshtml) + IntermediateToken - (65:1,34 [4] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - "hi" diff --git a/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/DesignTimeCodeGenerationTest/ChildComponent_Generic/TestComponent.mappings.txt b/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/DesignTimeCodeGenerationTest/ChildComponent_Generic/TestComponent.mappings.txt new file mode 100644 index 0000000000..6f3c26f86b --- /dev/null +++ b/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/DesignTimeCodeGenerationTest/ChildComponent_Generic/TestComponent.mappings.txt @@ -0,0 +1,15 @@ +Source Location: (14:0,14 [15] x:\dir\subdir\Test\TestComponent.cshtml) +|*, TestAssembly| +Generated Location: (559:16,38 [15] ) +|*, TestAssembly| + +Source Location: (50:1,19 [6] x:\dir\subdir\Test\TestComponent.cshtml) +|string| +Generated Location: (1083:30,19 [6] ) +|string| + +Source Location: (65:1,34 [4] x:\dir\subdir\Test\TestComponent.cshtml) +|"hi"| +Generated Location: (1315:37,34 [4] ) +|"hi"| + diff --git a/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/DesignTimeCodeGenerationTest/ChildComponent_GenericBind/TestComponent.codegen.cs b/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/DesignTimeCodeGenerationTest/ChildComponent_GenericBind/TestComponent.codegen.cs new file mode 100644 index 0000000000..68a6c9d5cc --- /dev/null +++ b/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/DesignTimeCodeGenerationTest/ChildComponent_GenericBind/TestComponent.codegen.cs @@ -0,0 +1,57 @@ +// +#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; + public class TestComponent : Microsoft.AspNetCore.Blazor.Components.BlazorComponent + { + #pragma warning disable 219 + private void __RazorDirectiveTokenHelpers__() { + ((System.Action)(() => { +global::System.Object __typeHelper = "*, TestAssembly"; + } + ))(); + } + #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 = typeof( +#line 2 "x:\dir\subdir\Test\TestComponent.cshtml" + string + +#line default +#line hidden + ); + __o = Microsoft.AspNetCore.Blazor.Components.BindMethods.GetValue( +#line 2 "x:\dir\subdir\Test\TestComponent.cshtml" + Value + +#line default +#line hidden + ); + __o = Microsoft.AspNetCore.Blazor.Components.BindMethods.SetValueHandler(__value => Value = __value, Value); + builder.AddAttribute(-1, "ChildContent", (Microsoft.AspNetCore.Blazor.RenderFragment)((builder2) => { + } + )); + } + #pragma warning restore 1998 +#line 3 "x:\dir\subdir\Test\TestComponent.cshtml" + + string Value; + +#line default +#line hidden + } +} +#pragma warning restore 1591 diff --git a/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/DesignTimeCodeGenerationTest/ChildComponent_GenericBind/TestComponent.ir.txt b/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/DesignTimeCodeGenerationTest/ChildComponent_GenericBind/TestComponent.ir.txt new file mode 100644 index 0000000000..716a6113c3 --- /dev/null +++ b/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/DesignTimeCodeGenerationTest/ChildComponent_GenericBind/TestComponent.ir.txt @@ -0,0 +1,39 @@ +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 + 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 + 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 + ComponentExtensionNode - (31:1,0 [43] x:\dir\subdir\Test\TestComponent.cshtml) - MyComponent - Test.MyComponent + ComponentTypeArgumentExtensionNode - (50:1,19 [6] x:\dir\subdir\Test\TestComponent.cshtml) - TItem + IntermediateToken - (50:1,19 [6] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - string + ComponentAttributeExtensionNode - (67:1,36 [5] x:\dir\subdir\Test\TestComponent.cshtml) - Item - + CSharpExpression - + IntermediateToken - - CSharp - Microsoft.AspNetCore.Blazor.Components.BindMethods.GetValue( + IntermediateToken - (67:1,36 [5] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - Value + IntermediateToken - - CSharp - ) + ComponentAttributeExtensionNode - (67:1,36 [5] x:\dir\subdir\Test\TestComponent.cshtml) - ItemChanged - + CSharpExpression - + IntermediateToken - - CSharp - Microsoft.AspNetCore.Blazor.Components.BindMethods.SetValueHandler(__value => Value = __value, Value) + HtmlContent - (74:1,43 [2] x:\dir\subdir\Test\TestComponent.cshtml) + IntermediateToken - (74:1,43 [2] x:\dir\subdir\Test\TestComponent.cshtml) - Html - \n + CSharpCode - (88:2,12 [21] x:\dir\subdir\Test\TestComponent.cshtml) + IntermediateToken - (88:2,12 [21] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - \n string Value;\n diff --git a/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/DesignTimeCodeGenerationTest/ChildComponent_GenericBind/TestComponent.mappings.txt b/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/DesignTimeCodeGenerationTest/ChildComponent_GenericBind/TestComponent.mappings.txt new file mode 100644 index 0000000000..24f508cdda --- /dev/null +++ b/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/DesignTimeCodeGenerationTest/ChildComponent_GenericBind/TestComponent.mappings.txt @@ -0,0 +1,24 @@ +Source Location: (14:0,14 [15] x:\dir\subdir\Test\TestComponent.cshtml) +|*, TestAssembly| +Generated Location: (559:16,38 [15] ) +|*, TestAssembly| + +Source Location: (50:1,19 [6] x:\dir\subdir\Test\TestComponent.cshtml) +|string| +Generated Location: (1083:30,19 [6] ) +|string| + +Source Location: (67:1,36 [5] x:\dir\subdir\Test\TestComponent.cshtml) +|Value| +Generated Location: (1305:37,36 [5] ) +|Value| + +Source Location: (88:2,12 [21] x:\dir\subdir\Test\TestComponent.cshtml) +| + string Value; +| +Generated Location: (1740:49,12 [21] ) +| + string Value; +| + diff --git a/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/DesignTimeCodeGenerationTest/ChildComponent_GenericChildContent/TestComponent.codegen.cs b/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/DesignTimeCodeGenerationTest/ChildComponent_GenericChildContent/TestComponent.codegen.cs new file mode 100644 index 0000000000..efd94b198a --- /dev/null +++ b/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/DesignTimeCodeGenerationTest/ChildComponent_GenericChildContent/TestComponent.codegen.cs @@ -0,0 +1,55 @@ +// +#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; + public class TestComponent : Microsoft.AspNetCore.Blazor.Components.BlazorComponent + { + #pragma warning disable 219 + private void __RazorDirectiveTokenHelpers__() { + ((System.Action)(() => { +global::System.Object __typeHelper = "*, TestAssembly"; + } + ))(); + } + #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 = typeof( +#line 2 "x:\dir\subdir\Test\TestComponent.cshtml" + string + +#line default +#line hidden + ); + __o = Microsoft.AspNetCore.Blazor.Components.RuntimeHelpers.TypeCheck( +#line 2 "x:\dir\subdir\Test\TestComponent.cshtml" + "hi" + +#line default +#line hidden + ); + builder.AddAttribute(-1, "ChildContent", (Microsoft.AspNetCore.Blazor.RenderFragment)((context) => (builder2) => { +#line 3 "x:\dir\subdir\Test\TestComponent.cshtml" + __o = context.ToLower(); + +#line default +#line hidden + } + )); + } + #pragma warning restore 1998 + } +} +#pragma warning restore 1591 diff --git a/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/DesignTimeCodeGenerationTest/ChildComponent_GenericChildContent/TestComponent.ir.txt b/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/DesignTimeCodeGenerationTest/ChildComponent_GenericChildContent/TestComponent.ir.txt new file mode 100644 index 0000000000..7ad56e0a55 --- /dev/null +++ b/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/DesignTimeCodeGenerationTest/ChildComponent_GenericChildContent/TestComponent.ir.txt @@ -0,0 +1,38 @@ +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 + 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 + 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 + ComponentExtensionNode - (31:1,0 [90] x:\dir\subdir\Test\TestComponent.cshtml) - MyComponent - Test.MyComponent + ComponentChildContent - - ChildContent + HtmlContent - (72:1,41 [4] x:\dir\subdir\Test\TestComponent.cshtml) + IntermediateToken - (72:1,41 [4] x:\dir\subdir\Test\TestComponent.cshtml) - Html - \n + HtmlElement - (76:2,2 [29] x:\dir\subdir\Test\TestComponent.cshtml) - div + CSharpExpression - (82:2,8 [17] x:\dir\subdir\Test\TestComponent.cshtml) + IntermediateToken - (82:2,8 [17] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - context.ToLower() + HtmlContent - (105:2,31 [2] x:\dir\subdir\Test\TestComponent.cshtml) + IntermediateToken - (105:2,31 [2] x:\dir\subdir\Test\TestComponent.cshtml) - Html - \n + ComponentTypeArgumentExtensionNode - (50:1,19 [6] x:\dir\subdir\Test\TestComponent.cshtml) - TItem + IntermediateToken - (50:1,19 [6] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - string + ComponentAttributeExtensionNode - (63:1,32 [7] x:\dir\subdir\Test\TestComponent.cshtml) - Item - Item + CSharpExpression - (64:1,33 [6] x:\dir\subdir\Test\TestComponent.cshtml) + IntermediateToken - (65:1,34 [4] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - "hi" diff --git a/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/DesignTimeCodeGenerationTest/ChildComponent_GenericChildContent/TestComponent.mappings.txt b/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/DesignTimeCodeGenerationTest/ChildComponent_GenericChildContent/TestComponent.mappings.txt new file mode 100644 index 0000000000..bc3d4f0ca6 --- /dev/null +++ b/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/DesignTimeCodeGenerationTest/ChildComponent_GenericChildContent/TestComponent.mappings.txt @@ -0,0 +1,20 @@ +Source Location: (14:0,14 [15] x:\dir\subdir\Test\TestComponent.cshtml) +|*, TestAssembly| +Generated Location: (559:16,38 [15] ) +|*, TestAssembly| + +Source Location: (50:1,19 [6] x:\dir\subdir\Test\TestComponent.cshtml) +|string| +Generated Location: (1083:30,19 [6] ) +|string| + +Source Location: (65:1,34 [4] x:\dir\subdir\Test\TestComponent.cshtml) +|"hi"| +Generated Location: (1315:37,34 [4] ) +|"hi"| + +Source Location: (82:2,8 [17] x:\dir\subdir\Test\TestComponent.cshtml) +|context.ToLower()| +Generated Location: (1563:44,8 [17] ) +|context.ToLower()| + diff --git a/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/DesignTimeCodeGenerationTest/ChildComponent_MultipleGenerics/TestComponent.codegen.cs b/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/DesignTimeCodeGenerationTest/ChildComponent_MultipleGenerics/TestComponent.codegen.cs new file mode 100644 index 0000000000..508ac45263 --- /dev/null +++ b/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/DesignTimeCodeGenerationTest/ChildComponent_MultipleGenerics/TestComponent.codegen.cs @@ -0,0 +1,70 @@ +// +#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; + public class TestComponent : Microsoft.AspNetCore.Blazor.Components.BlazorComponent + { + #pragma warning disable 219 + private void __RazorDirectiveTokenHelpers__() { + ((System.Action)(() => { +global::System.Object __typeHelper = "*, TestAssembly"; + } + ))(); + } + #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 = typeof( +#line 2 "x:\dir\subdir\Test\TestComponent.cshtml" + string + +#line default +#line hidden + ); + __o = typeof( +#line 2 "x:\dir\subdir\Test\TestComponent.cshtml" + int + +#line default +#line hidden + ); + __o = Microsoft.AspNetCore.Blazor.Components.RuntimeHelpers.TypeCheck( +#line 2 "x:\dir\subdir\Test\TestComponent.cshtml" + "hi" + +#line default +#line hidden + ); + builder.AddAttribute(-1, "ChildContent", (Microsoft.AspNetCore.Blazor.RenderFragment)((context) => (builder2) => { +#line 3 "x:\dir\subdir\Test\TestComponent.cshtml" + __o = context.ToLower(); + +#line default +#line hidden + } + )); + builder.AddAttribute(-1, "AnotherChildContent", (Microsoft.AspNetCore.Blazor.RenderFragment.Context>)((item) => (builder2) => { +#line 5 "x:\dir\subdir\Test\TestComponent.cshtml" +__o = System.Math.Max(0, item.Item); + +#line default +#line hidden + } + )); + } + #pragma warning restore 1998 + } +} +#pragma warning restore 1591 diff --git a/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/DesignTimeCodeGenerationTest/ChildComponent_MultipleGenerics/TestComponent.ir.txt b/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/DesignTimeCodeGenerationTest/ChildComponent_MultipleGenerics/TestComponent.ir.txt new file mode 100644 index 0000000000..e51bbc93ec --- /dev/null +++ b/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/DesignTimeCodeGenerationTest/ChildComponent_MultipleGenerics/TestComponent.ir.txt @@ -0,0 +1,43 @@ +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 + 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 + 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 + ComponentExtensionNode - (31:1,0 [228] x:\dir\subdir\Test\TestComponent.cshtml) - MyComponent - Test.MyComponent + ComponentChildContent - (88:2,2 [58] x:\dir\subdir\Test\TestComponent.cshtml) - ChildContent + HtmlElement - (102:2,16 [29] x:\dir\subdir\Test\TestComponent.cshtml) - div + CSharpExpression - (108:2,22 [17] x:\dir\subdir\Test\TestComponent.cshtml) + IntermediateToken - (108:2,22 [17] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - context.ToLower() + ComponentChildContent - (148:3,0 [95] x:\dir\subdir\Test\TestComponent.cshtml) - AnotherChildContent + HtmlContent - (184:3,36 [4] x:\dir\subdir\Test\TestComponent.cshtml) + IntermediateToken - (184:3,36 [4] x:\dir\subdir\Test\TestComponent.cshtml) - Html - \n + CSharpExpression - (189:4,3 [29] x:\dir\subdir\Test\TestComponent.cshtml) + IntermediateToken - (189:4,3 [29] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - System.Math.Max(0, item.Item) + HtmlContent - (218:4,32 [3] x:\dir\subdir\Test\TestComponent.cshtml) + IntermediateToken - (218:4,32 [3] x:\dir\subdir\Test\TestComponent.cshtml) - Html - ;\n + ComponentTypeArgumentExtensionNode - (51:1,20 [6] x:\dir\subdir\Test\TestComponent.cshtml) - TItem1 + IntermediateToken - (51:1,20 [6] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - string + ComponentTypeArgumentExtensionNode - (65:1,34 [3] x:\dir\subdir\Test\TestComponent.cshtml) - TItem2 + IntermediateToken - (65:1,34 [3] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - int + ComponentAttributeExtensionNode - (75:1,44 [7] x:\dir\subdir\Test\TestComponent.cshtml) - Item - Item + CSharpExpression - (76:1,45 [6] x:\dir\subdir\Test\TestComponent.cshtml) + IntermediateToken - (77:1,46 [4] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - "hi" diff --git a/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/DesignTimeCodeGenerationTest/ChildComponent_MultipleGenerics/TestComponent.mappings.txt b/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/DesignTimeCodeGenerationTest/ChildComponent_MultipleGenerics/TestComponent.mappings.txt new file mode 100644 index 0000000000..c84693ddef --- /dev/null +++ b/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/DesignTimeCodeGenerationTest/ChildComponent_MultipleGenerics/TestComponent.mappings.txt @@ -0,0 +1,30 @@ +Source Location: (14:0,14 [15] x:\dir\subdir\Test\TestComponent.cshtml) +|*, TestAssembly| +Generated Location: (559:16,38 [15] ) +|*, TestAssembly| + +Source Location: (51:1,20 [6] x:\dir\subdir\Test\TestComponent.cshtml) +|string| +Generated Location: (1084:30,20 [6] ) +|string| + +Source Location: (65:1,34 [3] x:\dir\subdir\Test\TestComponent.cshtml) +|int| +Generated Location: (1251:37,34 [3] ) +|int| + +Source Location: (77:1,46 [4] x:\dir\subdir\Test\TestComponent.cshtml) +|"hi"| +Generated Location: (1492:44,46 [4] ) +|"hi"| + +Source Location: (108:2,22 [17] x:\dir\subdir\Test\TestComponent.cshtml) +|context.ToLower()| +Generated Location: (1754:51,22 [17] ) +|context.ToLower()| + +Source Location: (189:4,3 [29] x:\dir\subdir\Test\TestComponent.cshtml) +|System.Math.Max(0, item.Item)| +Generated Location: (2065:59,6 [29] ) +|System.Math.Max(0, item.Item)| + diff --git a/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/DesignTimeCodeGenerationTest/ComponentWithTypeParameters/TestComponent.codegen.cs b/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/DesignTimeCodeGenerationTest/ComponentWithTypeParameters/TestComponent.codegen.cs new file mode 100644 index 0000000000..fa3845b83d --- /dev/null +++ b/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/DesignTimeCodeGenerationTest/ComponentWithTypeParameters/TestComponent.codegen.cs @@ -0,0 +1,67 @@ +// +#pragma warning disable 1591 +namespace Test +{ + #line hidden + using System; + using System.Collections.Generic; + using System.Linq; + using System.Threading.Tasks; +#line 1 "x:\dir\subdir\Test\TestComponent.cshtml" +using Microsoft.AspNetCore.Blazor; + +#line default +#line hidden + using Microsoft.AspNetCore.Blazor.Components; + public class TestComponent : Microsoft.AspNetCore.Blazor.Components.BlazorComponent + { + #pragma warning disable 219 + private void __RazorDirectiveTokenHelpers__() { + ((System.Action)(() => { +global::System.Object TItem1 = null; + } + ))(); + ((System.Action)(() => { +global::System.Object TItem2 = null; + } + ))(); + } + #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); +#line 6 "x:\dir\subdir\Test\TestComponent.cshtml" + foreach (var item2 in Items2) +{ + + +#line default +#line hidden +#line 9 "x:\dir\subdir\Test\TestComponent.cshtml" +__o = ChildContent(item2); + +#line default +#line hidden +#line 10 "x:\dir\subdir\Test\TestComponent.cshtml" + +} + +#line default +#line hidden + } + #pragma warning restore 1998 +#line 12 "x:\dir\subdir\Test\TestComponent.cshtml" + + [Parameter] TItem1 Item1 { get; set; } + [Parameter] List Items2 { get; set; } + [Parameter] RenderFragment ChildContent { get; set; } + +#line default +#line hidden + } +} +#pragma warning restore 1591 diff --git a/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/DesignTimeCodeGenerationTest/ComponentWithTypeParameters/TestComponent.ir.txt b/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/DesignTimeCodeGenerationTest/ComponentWithTypeParameters/TestComponent.ir.txt new file mode 100644 index 0000000000..ca67ec22b0 --- /dev/null +++ b/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/DesignTimeCodeGenerationTest/ComponentWithTypeParameters/TestComponent.ir.txt @@ -0,0 +1,47 @@ +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 - (1:0,1 [34] x:\dir\subdir\Test\TestComponent.cshtml) - Microsoft.AspNetCore.Blazor + UsingDirective - (140:6,1 [44] ) - Microsoft.AspNetCore.Blazor.Components + ClassDeclaration - - public - TestComponent - Microsoft.AspNetCore.Blazor.Components.BlazorComponent - + DesignTimeDirective - + DirectiveToken - (14:0,14 [32] ) - "*, Microsoft.AspNetCore.Blazor" + DirectiveToken - (14:0,14 [9] ) - "*, Test" + DirectiveToken - (48:1,11 [6] x:\dir\subdir\Test\TestComponent.cshtml) - TItem1 + DirectiveToken - (67:2,11 [6] x:\dir\subdir\Test\TestComponent.cshtml) - TItem2 + 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 - (35:0,35 [2] x:\dir\subdir\Test\TestComponent.cshtml) + IntermediateToken - (35:0,35 [2] x:\dir\subdir\Test\TestComponent.cshtml) - Html - \n + HtmlContent - (75:3,0 [2] x:\dir\subdir\Test\TestComponent.cshtml) + IntermediateToken - (75:3,0 [2] x:\dir\subdir\Test\TestComponent.cshtml) - Html - \n + HtmlElement - (77:4,0 [14] x:\dir\subdir\Test\TestComponent.cshtml) - h1 + HtmlContent - (81:4,4 [5] x:\dir\subdir\Test\TestComponent.cshtml) + IntermediateToken - (81:4,4 [5] x:\dir\subdir\Test\TestComponent.cshtml) - Html - Item1 + HtmlContent - (91:4,14 [2] x:\dir\subdir\Test\TestComponent.cshtml) + IntermediateToken - (91:4,14 [2] x:\dir\subdir\Test\TestComponent.cshtml) - Html - \n + CSharpCode - (94:5,1 [38] x:\dir\subdir\Test\TestComponent.cshtml) + IntermediateToken - (94:5,1 [38] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - foreach (var item2 in Items2)\n{\n + HtmlElement - (132:7,4 [40] x:\dir\subdir\Test\TestComponent.cshtml) - p + HtmlContent - (135:7,7 [6] x:\dir\subdir\Test\TestComponent.cshtml) + IntermediateToken - (135:7,7 [6] x:\dir\subdir\Test\TestComponent.cshtml) - Html - \n + CSharpExpression - (142:8,5 [19] x:\dir\subdir\Test\TestComponent.cshtml) + IntermediateToken - (142:8,5 [19] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - ChildContent(item2) + HtmlContent - (161:8,24 [7] x:\dir\subdir\Test\TestComponent.cshtml) + IntermediateToken - (161:8,24 [7] x:\dir\subdir\Test\TestComponent.cshtml) - Html - ;\n + CSharpCode - (172:9,8 [3] x:\dir\subdir\Test\TestComponent.cshtml) + IntermediateToken - (172:9,8 [3] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - \n} + HtmlContent - (175:10,1 [2] x:\dir\subdir\Test\TestComponent.cshtml) + IntermediateToken - (175:10,1 [2] x:\dir\subdir\Test\TestComponent.cshtml) - Html - \n + CSharpCode - (189:11,12 [164] x:\dir\subdir\Test\TestComponent.cshtml) + IntermediateToken - (189:11,12 [164] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - \n [Parameter] TItem1 Item1 { get; set; }\n [Parameter] List Items2 { get; set; }\n [Parameter] RenderFragment ChildContent { get; set; }\n diff --git a/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/DesignTimeCodeGenerationTest/ComponentWithTypeParameters/TestComponent.mappings.txt b/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/DesignTimeCodeGenerationTest/ComponentWithTypeParameters/TestComponent.mappings.txt new file mode 100644 index 0000000000..114a9d97e1 --- /dev/null +++ b/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/DesignTimeCodeGenerationTest/ComponentWithTypeParameters/TestComponent.mappings.txt @@ -0,0 +1,49 @@ +Source Location: (1:0,1 [34] x:\dir\subdir\Test\TestComponent.cshtml) +|using Microsoft.AspNetCore.Blazor;| +Generated Location: (257:10,0 [34] ) +|using Microsoft.AspNetCore.Blazor;| + +Source Location: (48:1,11 [6] x:\dir\subdir\Test\TestComponent.cshtml) +|TItem1| +Generated Location: (637:20,22 [6] ) +|TItem1| + +Source Location: (67:2,11 [6] x:\dir\subdir\Test\TestComponent.cshtml) +|TItem2| +Generated Location: (735:24,22 [6] ) +|TItem2| + +Source Location: (94:5,1 [38] x:\dir\subdir\Test\TestComponent.cshtml) +|foreach (var item2 in Items2) +{ + | +Generated Location: (1211:37,1 [38] ) +|foreach (var item2 in Items2) +{ + | + +Source Location: (142:8,5 [19] x:\dir\subdir\Test\TestComponent.cshtml) +|ChildContent(item2)| +Generated Location: (1339:44,6 [19] ) +|ChildContent(item2)| + +Source Location: (172:9,8 [3] x:\dir\subdir\Test\TestComponent.cshtml) +| +}| +Generated Location: (1452:49,8 [3] ) +| +}| + +Source Location: (189:11,12 [164] x:\dir\subdir\Test\TestComponent.cshtml) +| + [Parameter] TItem1 Item1 { get; set; } + [Parameter] List Items2 { get; set; } + [Parameter] RenderFragment ChildContent { get; set; } +| +Generated Location: (1601:57,12 [164] ) +| + [Parameter] TItem1 Item1 { get; set; } + [Parameter] List Items2 { get; set; } + [Parameter] RenderFragment ChildContent { get; set; } +| + diff --git a/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/RuntimeCodeGenerationTest/ChildComponent_Generic/TestComponent.codegen.cs b/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/RuntimeCodeGenerationTest/ChildComponent_Generic/TestComponent.codegen.cs new file mode 100644 index 0000000000..fd3b3213e8 --- /dev/null +++ b/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/RuntimeCodeGenerationTest/ChildComponent_Generic/TestComponent.codegen.cs @@ -0,0 +1,25 @@ +// +#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; + 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.OpenComponent>(0); + builder.AddAttribute(1, "Item", Microsoft.AspNetCore.Blazor.Components.RuntimeHelpers.TypeCheck("hi")); + builder.CloseComponent(); + } + #pragma warning restore 1998 + } +} +#pragma warning restore 1591 diff --git a/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/RuntimeCodeGenerationTest/ChildComponent_Generic/TestComponent.ir.txt b/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/RuntimeCodeGenerationTest/ChildComponent_Generic/TestComponent.ir.txt new file mode 100644 index 0000000000..6f32468287 --- /dev/null +++ b/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/RuntimeCodeGenerationTest/ChildComponent_Generic/TestComponent.ir.txt @@ -0,0 +1,18 @@ +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 + ClassDeclaration - - public - TestComponent - Microsoft.AspNetCore.Blazor.Components.BlazorComponent - + MethodDeclaration - - protected override - void - BuildRenderTree + CSharpCode - + IntermediateToken - - CSharp - base.BuildRenderTree(builder); + ComponentExtensionNode - (31:1,0 [42] x:\dir\subdir\Test\TestComponent.cshtml) - MyComponent - Test.MyComponent + ComponentTypeArgumentExtensionNode - (50:1,19 [6] x:\dir\subdir\Test\TestComponent.cshtml) - TItem + IntermediateToken - (50:1,19 [6] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - string + ComponentAttributeExtensionNode - (63:1,32 [7] x:\dir\subdir\Test\TestComponent.cshtml) - Item - Item + CSharpExpression - (64:1,33 [6] x:\dir\subdir\Test\TestComponent.cshtml) + IntermediateToken - (65:1,34 [4] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - "hi" diff --git a/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/RuntimeCodeGenerationTest/ChildComponent_GenericBind/TestComponent.codegen.cs b/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/RuntimeCodeGenerationTest/ChildComponent_GenericBind/TestComponent.codegen.cs new file mode 100644 index 0000000000..b22e42adf1 --- /dev/null +++ b/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/RuntimeCodeGenerationTest/ChildComponent_GenericBind/TestComponent.codegen.cs @@ -0,0 +1,32 @@ +// +#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; + 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.OpenComponent>(0); + builder.AddAttribute(1, "Item", Microsoft.AspNetCore.Blazor.Components.BindMethods.GetValue(Value)); + builder.AddAttribute(2, "ItemChanged", Microsoft.AspNetCore.Blazor.Components.BindMethods.SetValueHandler(__value => Value = __value, Value)); + builder.CloseComponent(); + } + #pragma warning restore 1998 +#line 3 "x:\dir\subdir\Test\TestComponent.cshtml" + + string Value; + +#line default +#line hidden + } +} +#pragma warning restore 1591 diff --git a/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/RuntimeCodeGenerationTest/ChildComponent_GenericBind/TestComponent.ir.txt b/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/RuntimeCodeGenerationTest/ChildComponent_GenericBind/TestComponent.ir.txt new file mode 100644 index 0000000000..7995a9493c --- /dev/null +++ b/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/RuntimeCodeGenerationTest/ChildComponent_GenericBind/TestComponent.ir.txt @@ -0,0 +1,25 @@ +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 + ClassDeclaration - - public - TestComponent - Microsoft.AspNetCore.Blazor.Components.BlazorComponent - + MethodDeclaration - - protected override - void - BuildRenderTree + CSharpCode - + IntermediateToken - - CSharp - base.BuildRenderTree(builder); + ComponentExtensionNode - (31:1,0 [43] x:\dir\subdir\Test\TestComponent.cshtml) - MyComponent - Test.MyComponent + ComponentTypeArgumentExtensionNode - (50:1,19 [6] x:\dir\subdir\Test\TestComponent.cshtml) - TItem + IntermediateToken - (50:1,19 [6] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - string + ComponentAttributeExtensionNode - (67:1,36 [5] x:\dir\subdir\Test\TestComponent.cshtml) - Item - + CSharpExpression - + IntermediateToken - - CSharp - Microsoft.AspNetCore.Blazor.Components.BindMethods.GetValue( + IntermediateToken - (67:1,36 [5] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - Value + IntermediateToken - - CSharp - ) + ComponentAttributeExtensionNode - (67:1,36 [5] x:\dir\subdir\Test\TestComponent.cshtml) - ItemChanged - + CSharpExpression - + IntermediateToken - - CSharp - Microsoft.AspNetCore.Blazor.Components.BindMethods.SetValueHandler(__value => Value = __value, Value) + CSharpCode - (88:2,12 [21] x:\dir\subdir\Test\TestComponent.cshtml) + IntermediateToken - (88:2,12 [21] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - \n string Value;\n diff --git a/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/RuntimeCodeGenerationTest/ChildComponent_GenericBind/TestComponent.mappings.txt b/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/RuntimeCodeGenerationTest/ChildComponent_GenericBind/TestComponent.mappings.txt new file mode 100644 index 0000000000..28f6af134e --- /dev/null +++ b/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/RuntimeCodeGenerationTest/ChildComponent_GenericBind/TestComponent.mappings.txt @@ -0,0 +1,9 @@ +Source Location: (88:2,12 [21] x:\dir\subdir\Test\TestComponent.cshtml) +| + string Value; +| +Generated Location: (1087:24,12 [21] ) +| + string Value; +| + diff --git a/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/RuntimeCodeGenerationTest/ChildComponent_GenericChildContent/TestComponent.codegen.cs b/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/RuntimeCodeGenerationTest/ChildComponent_GenericChildContent/TestComponent.codegen.cs new file mode 100644 index 0000000000..e9a7f0a0ef --- /dev/null +++ b/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/RuntimeCodeGenerationTest/ChildComponent_GenericChildContent/TestComponent.codegen.cs @@ -0,0 +1,33 @@ +// +#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; + 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.OpenComponent>(0); + builder.AddAttribute(1, "Item", Microsoft.AspNetCore.Blazor.Components.RuntimeHelpers.TypeCheck("hi")); + builder.AddAttribute(2, "ChildContent", (Microsoft.AspNetCore.Blazor.RenderFragment)((context) => (builder2) => { + builder2.AddContent(3, "\n "); + builder2.OpenElement(4, "div"); + builder2.AddContent(5, context.ToLower()); + builder2.CloseElement(); + builder2.AddContent(6, "\n"); + } + )); + builder.CloseComponent(); + } + #pragma warning restore 1998 + } +} +#pragma warning restore 1591 diff --git a/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/RuntimeCodeGenerationTest/ChildComponent_GenericChildContent/TestComponent.ir.txt b/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/RuntimeCodeGenerationTest/ChildComponent_GenericChildContent/TestComponent.ir.txt new file mode 100644 index 0000000000..f5f357532b --- /dev/null +++ b/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/RuntimeCodeGenerationTest/ChildComponent_GenericChildContent/TestComponent.ir.txt @@ -0,0 +1,26 @@ +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 + ClassDeclaration - - public - TestComponent - Microsoft.AspNetCore.Blazor.Components.BlazorComponent - + MethodDeclaration - - protected override - void - BuildRenderTree + CSharpCode - + IntermediateToken - - CSharp - base.BuildRenderTree(builder); + ComponentExtensionNode - (31:1,0 [90] x:\dir\subdir\Test\TestComponent.cshtml) - MyComponent - Test.MyComponent + ComponentChildContent - - ChildContent + HtmlContent - (72:1,41 [4] x:\dir\subdir\Test\TestComponent.cshtml) + IntermediateToken - (72:1,41 [4] x:\dir\subdir\Test\TestComponent.cshtml) - Html - \n + HtmlElement - (76:2,2 [29] x:\dir\subdir\Test\TestComponent.cshtml) - div + CSharpExpression - (82:2,8 [17] x:\dir\subdir\Test\TestComponent.cshtml) + IntermediateToken - (82:2,8 [17] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - context.ToLower() + HtmlContent - (105:2,31 [2] x:\dir\subdir\Test\TestComponent.cshtml) + IntermediateToken - (105:2,31 [2] x:\dir\subdir\Test\TestComponent.cshtml) - Html - \n + ComponentTypeArgumentExtensionNode - (50:1,19 [6] x:\dir\subdir\Test\TestComponent.cshtml) - TItem + IntermediateToken - (50:1,19 [6] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - string + ComponentAttributeExtensionNode - (63:1,32 [7] x:\dir\subdir\Test\TestComponent.cshtml) - Item - Item + CSharpExpression - (64:1,33 [6] x:\dir\subdir\Test\TestComponent.cshtml) + IntermediateToken - (65:1,34 [4] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - "hi" diff --git a/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/RuntimeCodeGenerationTest/ChildComponent_MultipleGenerics/TestComponent.codegen.cs b/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/RuntimeCodeGenerationTest/ChildComponent_MultipleGenerics/TestComponent.codegen.cs new file mode 100644 index 0000000000..e3b8a93751 --- /dev/null +++ b/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/RuntimeCodeGenerationTest/ChildComponent_MultipleGenerics/TestComponent.codegen.cs @@ -0,0 +1,37 @@ +// +#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; + 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.OpenComponent>(0); + builder.AddAttribute(1, "Item", Microsoft.AspNetCore.Blazor.Components.RuntimeHelpers.TypeCheck("hi")); + builder.AddAttribute(2, "ChildContent", (Microsoft.AspNetCore.Blazor.RenderFragment)((context) => (builder2) => { + builder2.OpenElement(3, "div"); + builder2.AddContent(4, context.ToLower()); + builder2.CloseElement(); + } + )); + builder.AddAttribute(5, "AnotherChildContent", (Microsoft.AspNetCore.Blazor.RenderFragment.Context>)((item) => (builder2) => { + builder2.AddContent(6, "\n "); + builder2.AddContent(7, System.Math.Max(0, item.Item)); + builder2.AddContent(8, ";\n"); + } + )); + builder.CloseComponent(); + } + #pragma warning restore 1998 + } +} +#pragma warning restore 1591 diff --git a/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/RuntimeCodeGenerationTest/ChildComponent_MultipleGenerics/TestComponent.ir.txt b/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/RuntimeCodeGenerationTest/ChildComponent_MultipleGenerics/TestComponent.ir.txt new file mode 100644 index 0000000000..eb31527ec6 --- /dev/null +++ b/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/RuntimeCodeGenerationTest/ChildComponent_MultipleGenerics/TestComponent.ir.txt @@ -0,0 +1,31 @@ +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 + ClassDeclaration - - public - TestComponent - Microsoft.AspNetCore.Blazor.Components.BlazorComponent - + MethodDeclaration - - protected override - void - BuildRenderTree + CSharpCode - + IntermediateToken - - CSharp - base.BuildRenderTree(builder); + ComponentExtensionNode - (31:1,0 [228] x:\dir\subdir\Test\TestComponent.cshtml) - MyComponent - Test.MyComponent + ComponentChildContent - (88:2,2 [58] x:\dir\subdir\Test\TestComponent.cshtml) - ChildContent + HtmlElement - (102:2,16 [29] x:\dir\subdir\Test\TestComponent.cshtml) - div + CSharpExpression - (108:2,22 [17] x:\dir\subdir\Test\TestComponent.cshtml) + IntermediateToken - (108:2,22 [17] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - context.ToLower() + ComponentChildContent - (148:3,0 [95] x:\dir\subdir\Test\TestComponent.cshtml) - AnotherChildContent + HtmlContent - (184:3,36 [4] x:\dir\subdir\Test\TestComponent.cshtml) + IntermediateToken - (184:3,36 [4] x:\dir\subdir\Test\TestComponent.cshtml) - Html - \n + CSharpExpression - (189:4,3 [29] x:\dir\subdir\Test\TestComponent.cshtml) + IntermediateToken - (189:4,3 [29] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - System.Math.Max(0, item.Item) + HtmlContent - (218:4,32 [3] x:\dir\subdir\Test\TestComponent.cshtml) + IntermediateToken - (218:4,32 [3] x:\dir\subdir\Test\TestComponent.cshtml) - Html - ;\n + ComponentTypeArgumentExtensionNode - (51:1,20 [6] x:\dir\subdir\Test\TestComponent.cshtml) - TItem1 + IntermediateToken - (51:1,20 [6] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - string + ComponentTypeArgumentExtensionNode - (65:1,34 [3] x:\dir\subdir\Test\TestComponent.cshtml) - TItem2 + IntermediateToken - (65:1,34 [3] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - int + ComponentAttributeExtensionNode - (75:1,44 [7] x:\dir\subdir\Test\TestComponent.cshtml) - Item - Item + CSharpExpression - (76:1,45 [6] x:\dir\subdir\Test\TestComponent.cshtml) + IntermediateToken - (77:1,46 [4] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - "hi" diff --git a/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/RuntimeCodeGenerationTest/ComponentWithTypeParameters/TestComponent.codegen.cs b/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/RuntimeCodeGenerationTest/ComponentWithTypeParameters/TestComponent.codegen.cs new file mode 100644 index 0000000000..24fa6212c4 --- /dev/null +++ b/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/RuntimeCodeGenerationTest/ComponentWithTypeParameters/TestComponent.codegen.cs @@ -0,0 +1,49 @@ +// +#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; + 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.AddMarkupContent(0, "

Item1

\n"); +#line 6 "x:\dir\subdir\Test\TestComponent.cshtml" + foreach (var item2 in Items2) +{ + +#line default +#line hidden + builder.AddContent(1, " "); + builder.OpenElement(2, "p"); + builder.AddContent(3, "\n "); + builder.AddContent(4, ChildContent(item2)); + builder.AddContent(5, ";\n "); + builder.CloseElement(); + builder.AddContent(6, "\n"); +#line 11 "x:\dir\subdir\Test\TestComponent.cshtml" +} + +#line default +#line hidden + } + #pragma warning restore 1998 +#line 12 "x:\dir\subdir\Test\TestComponent.cshtml" + + [Parameter] TItem1 Item1 { get; set; } + [Parameter] List Items2 { get; set; } + [Parameter] RenderFragment ChildContent { get; set; } + +#line default +#line hidden + } +} +#pragma warning restore 1591 diff --git a/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/RuntimeCodeGenerationTest/ComponentWithTypeParameters/TestComponent.ir.txt b/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/RuntimeCodeGenerationTest/ComponentWithTypeParameters/TestComponent.ir.txt new file mode 100644 index 0000000000..1748cec2ee --- /dev/null +++ b/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/RuntimeCodeGenerationTest/ComponentWithTypeParameters/TestComponent.ir.txt @@ -0,0 +1,30 @@ +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 - (1:0,1 [36] x:\dir\subdir\Test\TestComponent.cshtml) - Microsoft.AspNetCore.Blazor + UsingDirective - (140:6,1 [46] ) - Microsoft.AspNetCore.Blazor.Components + ClassDeclaration - - public - TestComponent - Microsoft.AspNetCore.Blazor.Components.BlazorComponent - + MethodDeclaration - - protected override - void - BuildRenderTree + CSharpCode - + IntermediateToken - - CSharp - base.BuildRenderTree(builder); + HtmlBlock - -

Item1

\n + CSharpCode - (94:5,1 [34] x:\dir\subdir\Test\TestComponent.cshtml) + IntermediateToken - (94:5,1 [34] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - foreach (var item2 in Items2)\n{\n + HtmlContent - (128:7,0 [4] x:\dir\subdir\Test\TestComponent.cshtml) + IntermediateToken - (128:7,0 [4] x:\dir\subdir\Test\TestComponent.cshtml) - Html - + HtmlElement - (132:7,4 [40] x:\dir\subdir\Test\TestComponent.cshtml) - p + HtmlContent - (135:7,7 [6] x:\dir\subdir\Test\TestComponent.cshtml) + IntermediateToken - (135:7,7 [6] x:\dir\subdir\Test\TestComponent.cshtml) - Html - \n + CSharpExpression - (142:8,5 [19] x:\dir\subdir\Test\TestComponent.cshtml) + IntermediateToken - (142:8,5 [19] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - ChildContent(item2) + HtmlContent - (161:8,24 [7] x:\dir\subdir\Test\TestComponent.cshtml) + IntermediateToken - (161:8,24 [7] x:\dir\subdir\Test\TestComponent.cshtml) - Html - ;\n + HtmlContent - (172:9,8 [2] x:\dir\subdir\Test\TestComponent.cshtml) + IntermediateToken - (172:9,8 [2] x:\dir\subdir\Test\TestComponent.cshtml) - Html - \n + CSharpCode - (174:10,0 [3] x:\dir\subdir\Test\TestComponent.cshtml) + IntermediateToken - (174:10,0 [3] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - }\n + CSharpCode - (189:11,12 [164] x:\dir\subdir\Test\TestComponent.cshtml) + IntermediateToken - (189:11,12 [164] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - \n [Parameter] TItem1 Item1 { get; set; }\n [Parameter] List Items2 { get; set; }\n [Parameter] RenderFragment ChildContent { get; set; }\n diff --git a/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/RuntimeCodeGenerationTest/ComponentWithTypeParameters/TestComponent.mappings.txt b/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/RuntimeCodeGenerationTest/ComponentWithTypeParameters/TestComponent.mappings.txt new file mode 100644 index 0000000000..be6729fc7c --- /dev/null +++ b/test/Microsoft.AspNetCore.Blazor.Build.Test/TestFiles/RuntimeCodeGenerationTest/ComponentWithTypeParameters/TestComponent.mappings.txt @@ -0,0 +1,29 @@ +Source Location: (94:5,1 [34] x:\dir\subdir\Test\TestComponent.cshtml) +|foreach (var item2 in Items2) +{ +| +Generated Location: (731:20,1 [34] ) +|foreach (var item2 in Items2) +{ +| + +Source Location: (174:10,0 [3] x:\dir\subdir\Test\TestComponent.cshtml) +|} +| +Generated Location: (1163:34,0 [3] ) +|} +| + +Source Location: (189:11,12 [164] x:\dir\subdir\Test\TestComponent.cshtml) +| + [Parameter] TItem1 Item1 { get; set; } + [Parameter] List Items2 { get; set; } + [Parameter] RenderFragment ChildContent { get; set; } +| +Generated Location: (1310:41,12 [164] ) +| + [Parameter] TItem1 Item1 { get; set; } + [Parameter] List Items2 { get; set; } + [Parameter] RenderFragment ChildContent { get; set; } +| + diff --git a/test/Microsoft.AspNetCore.Blazor.Razor.Extensions.Test/ComponentTagHelperDescriptorProviderTest.cs b/test/Microsoft.AspNetCore.Blazor.Razor.Extensions.Test/ComponentTagHelperDescriptorProviderTest.cs index 87aa02767e..5417dc9cde 100644 --- a/test/Microsoft.AspNetCore.Blazor.Razor.Extensions.Test/ComponentTagHelperDescriptorProviderTest.cs +++ b/test/Microsoft.AspNetCore.Blazor.Razor.Extensions.Test/ComponentTagHelperDescriptorProviderTest.cs @@ -124,6 +124,73 @@ namespace Test kvp => { Assert.Equal(TagHelperMetadata.Common.PropertyName, kvp.Key); Assert.Equal("MyProperty", kvp.Value); }); } + [Fact] + public void Excecute_FindsIComponentType_CreatesDescriptor_Generic() + { + // Arrange + + var compilation = BaseCompilation.AddSyntaxTrees(Parse(@" +using Microsoft.AspNetCore.Blazor.Components; + +namespace Test +{ + public class MyComponent : IComponent + { + public void Init(RenderHandle renderHandle) { } + + public void SetParameters(ParameterCollection parameters) { } + + [Parameter] + private string MyProperty { get; set; } + } +} + +")); + + Assert.Empty(compilation.GetDiagnostics()); + + var context = TagHelperDescriptorProviderContext.Create(); + context.SetCompilation(compilation); + + var provider = new ComponentTagHelperDescriptorProvider(); + + // Act + provider.Execute(context); + + // Assert + var components = ExcludeBuiltInComponents(context); + var component = Assert.Single(components); + + Assert.Equal("TestAssembly", component.AssemblyName); + Assert.Equal("Test.MyComponent", component.Name); + Assert.Equal("Test.MyComponent", component.DisplayName); + Assert.Equal("Test.MyComponent", component.GetTypeName()); + + Assert.True(component.IsGenericTypedComponent()); + + var rule = Assert.Single(component.TagMatchingRules); + Assert.Equal("MyComponent", rule.TagName); + + Assert.Collection( + component.BoundAttributes.OrderBy(a => a.Name), + a => + { + Assert.Equal("MyProperty", a.Name); + Assert.Equal("MyProperty", a.GetPropertyName()); + Assert.Equal("string Test.MyComponent.MyProperty", a.DisplayName); + Assert.Equal("System.String", a.TypeName); + + }, + a => + { + Assert.Equal("T", a.Name); + Assert.Equal("T", a.GetPropertyName()); + Assert.Equal("T", a.DisplayName); + Assert.Equal("System.Type", a.TypeName); + Assert.True(a.IsTypeParameterProperty()); + }); + } + [Fact] public void Excecute_FindsBlazorComponentType_CreatesDescriptor() { @@ -263,6 +330,142 @@ namespace Test Assert.False(attribute.IsStringProperty); } + [Fact] + public void Excecute_GenericProperty_CreatesDescriptor() + { + // Arrange + + var compilation = BaseCompilation.AddSyntaxTrees(Parse(@" +using Microsoft.AspNetCore.Blazor.Components; + +namespace Test +{ + public class MyComponent : BlazorComponent + { + [Parameter] + T MyProperty { get; set; } + } +} + +")); + + Assert.Empty(compilation.GetDiagnostics()); + + var context = TagHelperDescriptorProviderContext.Create(); + context.SetCompilation(compilation); + + var provider = new ComponentTagHelperDescriptorProvider(); + + // Act + provider.Execute(context); + + // Assert + var components = ExcludeBuiltInComponents(context); + var component = Assert.Single(components); + + Assert.Equal("TestAssembly", component.AssemblyName); + Assert.Equal("Test.MyComponent", component.Name); + + Assert.Collection( + component.BoundAttributes.OrderBy(a => a.Name), + a => + { + Assert.Equal("MyProperty", a.Name); + Assert.Equal("MyProperty", a.GetPropertyName()); + Assert.Equal("T Test.MyComponent.MyProperty", a.DisplayName); + Assert.Equal("T", a.TypeName); + Assert.True(a.IsGenericTypedProperty()); + + }, + a => + { + Assert.Equal("T", a.Name); + Assert.Equal("T", a.GetPropertyName()); + Assert.Equal("T", a.DisplayName); + Assert.Equal("System.Type", a.TypeName); + Assert.True(a.IsTypeParameterProperty()); + }); + } + + [Fact] + public void Excecute_MultipleGenerics_CreatesDescriptor() + { + // Arrange + + var compilation = BaseCompilation.AddSyntaxTrees(Parse(@" +using Microsoft.AspNetCore.Blazor.Components; + +namespace Test +{ + public class MyComponent : BlazorComponent + { + [Parameter] + T MyProperty1 { get; set; } + + [Parameter] + U MyProperty2 { get; set; } + + [Parameter] + V MyProperty3 { get; set; } + } +} + +")); + + Assert.Empty(compilation.GetDiagnostics()); + + var context = TagHelperDescriptorProviderContext.Create(); + context.SetCompilation(compilation); + + var provider = new ComponentTagHelperDescriptorProvider(); + + // Act + provider.Execute(context); + + // Assert + var components = ExcludeBuiltInComponents(context); + var component = Assert.Single(components); + + Assert.Equal("TestAssembly", component.AssemblyName); + Assert.Equal("Test.MyComponent", component.Name); + + Assert.Collection( + component.BoundAttributes.OrderBy(a => a.Name), + a => + { + Assert.Equal("MyProperty1", a.Name); + Assert.Equal("T", a.TypeName); + Assert.True(a.IsGenericTypedProperty()); + }, + a => + { + Assert.Equal("MyProperty2", a.Name); + Assert.Equal("U", a.TypeName); + Assert.True(a.IsGenericTypedProperty()); + }, + a => + { + Assert.Equal("MyProperty3", a.Name); + Assert.Equal("V", a.TypeName); + Assert.True(a.IsGenericTypedProperty()); + }, + a => + { + Assert.Equal("T", a.Name); + Assert.True(a.IsTypeParameterProperty()); + }, + a => + { + Assert.Equal("U", a.Name); + Assert.True(a.IsTypeParameterProperty()); + }, + a => + { + Assert.Equal("V", a.Name); + Assert.True(a.IsTypeParameterProperty()); + }); + } + [Fact] public void Execute_DelegateProperty_CreatesDescriptor() { @@ -313,6 +516,68 @@ namespace Test Assert.False(attribute.IsChildContentProperty()); } + [Fact] + public void Execute_DelegateProperty_CreatesDescriptor_Generic() + { + // Arrange + + var compilation = BaseCompilation.AddSyntaxTrees(Parse(@" +using System; +using Microsoft.AspNetCore.Blazor.Components; + +namespace Test +{ + public class MyComponent : BlazorComponent + { + [Parameter] + Action OnClick { get; set; } + } +} + +")); + + Assert.Empty(compilation.GetDiagnostics()); + + var context = TagHelperDescriptorProviderContext.Create(); + context.SetCompilation(compilation); + + var provider = new ComponentTagHelperDescriptorProvider(); + + // Act + provider.Execute(context); + + // Assert + var components = ExcludeBuiltInComponents(context); + var component = Assert.Single(components); + + Assert.Equal("TestAssembly", component.AssemblyName); + Assert.Equal("Test.MyComponent", component.Name); + + Assert.Collection( + component.BoundAttributes.OrderBy(a => a.Name), + a => + { + Assert.Equal("OnClick", a.Name); + Assert.Equal("System.Action", a.TypeName); + Assert.False(a.HasIndexer); + Assert.False(a.IsBooleanProperty); + Assert.False(a.IsEnum); + Assert.False(a.IsStringProperty); + Assert.True(a.IsDelegateProperty()); + Assert.False(a.IsChildContentProperty()); + Assert.True(a.IsGenericTypedProperty()); + + }, + a => + { + Assert.Equal("T", a.Name); + Assert.Equal("T", a.GetPropertyName()); + Assert.Equal("T", a.DisplayName); + Assert.Equal("System.Type", a.TypeName); + Assert.True(a.IsTypeParameterProperty()); + }); + } + [Fact] public void Execute_RenderFragmentProperty_CreatesDescriptors() { @@ -418,6 +683,7 @@ namespace Test Assert.False(attribute.IsDelegateProperty()); // We treat RenderFragment as separate from generalized delegates Assert.True(attribute.IsChildContentProperty()); Assert.True(attribute.IsParameterizedChildContentProperty()); + Assert.False(attribute.IsGenericTypedProperty()); var childContent = Assert.Single(components, c => c.IsChildContentTagHelper()); @@ -431,6 +697,313 @@ namespace Test Assert.Equal("Specifies the parameter name for the 'ChildContent2' lambda expression.", contextAttribute.Documentation); } + [Fact] + public void Execute_RenderFragmentGenericProperty_CreatesDescriptor() + { + // Arrange + + var compilation = BaseCompilation.AddSyntaxTrees(Parse(@" +using Microsoft.AspNetCore.Blazor; +using Microsoft.AspNetCore.Blazor.Components; + +namespace Test +{ + public class MyComponent : BlazorComponent + { + [Parameter] + RenderFragment ChildContent2 { get; set; } + } +} + +")); + + Assert.Empty(compilation.GetDiagnostics()); + + var context = TagHelperDescriptorProviderContext.Create(); + context.SetCompilation(compilation); + + var provider = new ComponentTagHelperDescriptorProvider(); + + // Act + provider.Execute(context); + + // Assert + var components = ExcludeBuiltInComponents(context); + var component = Assert.Single(components, c => c.IsComponentTagHelper()); + + Assert.Equal("TestAssembly", component.AssemblyName); + Assert.Equal("Test.MyComponent", component.Name); + + Assert.Collection( + component.BoundAttributes.OrderBy(a => a.Name), + a => + { + Assert.Equal("ChildContent2", a.Name); + Assert.Equal("Microsoft.AspNetCore.Blazor.RenderFragment", a.TypeName); + + Assert.False(a.HasIndexer); + Assert.False(a.IsBooleanProperty); + Assert.False(a.IsEnum); + Assert.False(a.IsStringProperty); + Assert.False(a.IsDelegateProperty()); // We treat RenderFragment as separate from generalized delegates + Assert.True(a.IsChildContentProperty()); + Assert.True(a.IsParameterizedChildContentProperty()); + Assert.True(a.IsGenericTypedProperty()); + + }, + a => + { + Assert.Equal("T", a.Name); + Assert.Equal("T", a.GetPropertyName()); + Assert.Equal("T", a.DisplayName); + Assert.Equal("System.Type", a.TypeName); + Assert.True(a.IsTypeParameterProperty()); + }); + + var childContent = Assert.Single(components, c => c.IsChildContentTagHelper()); + + Assert.Equal("TestAssembly", childContent.AssemblyName); + Assert.Equal("Test.MyComponent.ChildContent2", childContent.Name); + + // A RenderFragment tag helper has a parameter to allow you to set the lambda parameter name. + var contextAttribute = Assert.Single(childContent.BoundAttributes); + Assert.Equal("Context", contextAttribute.Name); + Assert.Equal("System.String", contextAttribute.TypeName); + Assert.Equal("Specifies the parameter name for the 'ChildContent2' lambda expression.", contextAttribute.Documentation); + } + + [Fact] + public void Execute_RenderFragmentClosedGenericListProperty_CreatesDescriptor() + { + // Arrange + + var compilation = BaseCompilation.AddSyntaxTrees(Parse(@" +using System.Collections.Generic; +using Microsoft.AspNetCore.Blazor; +using Microsoft.AspNetCore.Blazor.Components; + +namespace Test +{ + public class MyComponent : BlazorComponent + { + [Parameter] + RenderFragment> ChildContent2 { get; set; } + } +} + +")); + + Assert.Empty(compilation.GetDiagnostics()); + + var context = TagHelperDescriptorProviderContext.Create(); + context.SetCompilation(compilation); + + var provider = new ComponentTagHelperDescriptorProvider(); + + // Act + provider.Execute(context); + + // Assert + var components = ExcludeBuiltInComponents(context); + var component = Assert.Single(components, c => c.IsComponentTagHelper()); + + Assert.Equal("TestAssembly", component.AssemblyName); + Assert.Equal("Test.MyComponent", component.Name); + + Assert.Collection( + component.BoundAttributes.OrderBy(a => a.Name), + a => + { + Assert.Equal("ChildContent2", a.Name); + Assert.Equal("Microsoft.AspNetCore.Blazor.RenderFragment>", a.TypeName); + + Assert.False(a.HasIndexer); + Assert.False(a.IsBooleanProperty); + Assert.False(a.IsEnum); + Assert.False(a.IsStringProperty); + Assert.False(a.IsDelegateProperty()); // We treat RenderFragment as separate from generalized delegates + Assert.True(a.IsChildContentProperty()); + Assert.True(a.IsParameterizedChildContentProperty()); + Assert.False(a.IsGenericTypedProperty()); + + }, + a => + { + Assert.Equal("T", a.Name); + Assert.Equal("T", a.GetPropertyName()); + Assert.Equal("T", a.DisplayName); + Assert.Equal("System.Type", a.TypeName); + Assert.True(a.IsTypeParameterProperty()); + }); + + var childContent = Assert.Single(components, c => c.IsChildContentTagHelper()); + + Assert.Equal("TestAssembly", childContent.AssemblyName); + Assert.Equal("Test.MyComponent.ChildContent2", childContent.Name); + + // A RenderFragment tag helper has a parameter to allow you to set the lambda parameter name. + var contextAttribute = Assert.Single(childContent.BoundAttributes); + Assert.Equal("Context", contextAttribute.Name); + Assert.Equal("System.String", contextAttribute.TypeName); + Assert.Equal("Specifies the parameter name for the 'ChildContent2' lambda expression.", contextAttribute.Documentation); + } + + [Fact] + public void Execute_RenderFragmentGenericListProperty_CreatesDescriptor() + { + // Arrange + + var compilation = BaseCompilation.AddSyntaxTrees(Parse(@" +using System.Collections.Generic; +using Microsoft.AspNetCore.Blazor; +using Microsoft.AspNetCore.Blazor.Components; + +namespace Test +{ + public class MyComponent : BlazorComponent + { + [Parameter] + RenderFragment> ChildContent2 { get; set; } + } +} + +")); + + Assert.Empty(compilation.GetDiagnostics()); + + var context = TagHelperDescriptorProviderContext.Create(); + context.SetCompilation(compilation); + + var provider = new ComponentTagHelperDescriptorProvider(); + + // Act + provider.Execute(context); + + // Assert + var components = ExcludeBuiltInComponents(context); + var component = Assert.Single(components, c => c.IsComponentTagHelper()); + + Assert.Equal("TestAssembly", component.AssemblyName); + Assert.Equal("Test.MyComponent", component.Name); + + Assert.Collection( + component.BoundAttributes.OrderBy(a => a.Name), + a => + { + Assert.Equal("ChildContent2", a.Name); + Assert.Equal("Microsoft.AspNetCore.Blazor.RenderFragment>", a.TypeName); + + Assert.False(a.HasIndexer); + Assert.False(a.IsBooleanProperty); + Assert.False(a.IsEnum); + Assert.False(a.IsStringProperty); + Assert.False(a.IsDelegateProperty()); // We treat RenderFragment as separate from generalized delegates + Assert.True(a.IsChildContentProperty()); + Assert.True(a.IsParameterizedChildContentProperty()); + Assert.True(a.IsGenericTypedProperty()); + + }, + a => + { + Assert.Equal("T", a.Name); + Assert.Equal("T", a.GetPropertyName()); + Assert.Equal("T", a.DisplayName); + Assert.Equal("System.Type", a.TypeName); + Assert.True(a.IsTypeParameterProperty()); + }); + + var childContent = Assert.Single(components, c => c.IsChildContentTagHelper()); + + Assert.Equal("TestAssembly", childContent.AssemblyName); + Assert.Equal("Test.MyComponent.ChildContent2", childContent.Name); + + // A RenderFragment tag helper has a parameter to allow you to set the lambda parameter name. + var contextAttribute = Assert.Single(childContent.BoundAttributes); + Assert.Equal("Context", contextAttribute.Name); + Assert.Equal("System.String", contextAttribute.TypeName); + Assert.Equal("Specifies the parameter name for the 'ChildContent2' lambda expression.", contextAttribute.Documentation); + } + + [Fact] + public void Execute_RenderFragmentGenericContextProperty_CreatesDescriptor() + { + // Arrange + + var compilation = BaseCompilation.AddSyntaxTrees(Parse(@" +using Microsoft.AspNetCore.Blazor; +using Microsoft.AspNetCore.Blazor.Components; + +namespace Test +{ + public class MyComponent : BlazorComponent + { + [Parameter] + RenderFragment ChildContent2 { get; set; } + + public class Context + { + public T Item { get; set; } + } + } +} + +")); + + Assert.Empty(compilation.GetDiagnostics()); + + var context = TagHelperDescriptorProviderContext.Create(); + context.SetCompilation(compilation); + + var provider = new ComponentTagHelperDescriptorProvider(); + + // Act + provider.Execute(context); + + // Assert + var components = ExcludeBuiltInComponents(context); + var component = Assert.Single(components, c => c.IsComponentTagHelper()); + + Assert.Equal("TestAssembly", component.AssemblyName); + Assert.Equal("Test.MyComponent", component.Name); + + Assert.Collection( + component.BoundAttributes.OrderBy(a => a.Name), + a => + { + Assert.Equal("ChildContent2", a.Name); + Assert.Equal("Microsoft.AspNetCore.Blazor.RenderFragment.Context>", a.TypeName); + + Assert.False(a.HasIndexer); + Assert.False(a.IsBooleanProperty); + Assert.False(a.IsEnum); + Assert.False(a.IsStringProperty); + Assert.False(a.IsDelegateProperty()); // We treat RenderFragment as separate from generalized delegates + Assert.True(a.IsChildContentProperty()); + Assert.True(a.IsParameterizedChildContentProperty()); + Assert.True(a.IsGenericTypedProperty()); + + }, + a => + { + Assert.Equal("T", a.Name); + Assert.Equal("T", a.GetPropertyName()); + Assert.Equal("T", a.DisplayName); + Assert.Equal("System.Type", a.TypeName); + Assert.True(a.IsTypeParameterProperty()); + }); + + var childContent = Assert.Single(components, c => c.IsChildContentTagHelper()); + + Assert.Equal("TestAssembly", childContent.AssemblyName); + Assert.Equal("Test.MyComponent.ChildContent2", childContent.Name); + + // A RenderFragment tag helper has a parameter to allow you to set the lambda parameter name. + var contextAttribute = Assert.Single(childContent.BoundAttributes); + Assert.Equal("Context", contextAttribute.Name); + Assert.Equal("System.String", contextAttribute.TypeName); + Assert.Equal("Specifies the parameter name for the 'ChildContent2' lambda expression.", contextAttribute.Documentation); + } + [Fact] public void Execute_MultipleRenderFragmentProperties_CreatesDescriptor() { diff --git a/test/Microsoft.AspNetCore.Blazor.Razor.Extensions.Test/GenericTypeNameRewriterTest.cs b/test/Microsoft.AspNetCore.Blazor.Razor.Extensions.Test/GenericTypeNameRewriterTest.cs new file mode 100644 index 0000000000..fbb60a254b --- /dev/null +++ b/test/Microsoft.AspNetCore.Blazor.Razor.Extensions.Test/GenericTypeNameRewriterTest.cs @@ -0,0 +1,46 @@ +// 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.Collections.Generic; +using Microsoft.CodeAnalysis.CSharp; +using Xunit; + +namespace Microsoft.AspNetCore.Blazor.Razor +{ + public class GenericTypeNameRewriterTest + { + [Theory] + [InlineData("TItem2", "Type2")] + + // Unspecified argument -> System.Object + [InlineData("TItem3", "System.Object")] + + // Not a type parameter + [InlineData("TItem4", "TItem4")] + + // In a qualified name, not a type parameter + [InlineData("TItem1.TItem2", "TItem1.TItem2")] + + // Type parameters can't have type parameters + [InlineData("TItem1.TItem2", "TItem1.TItem2")] + [InlineData("TItem2, System.TItem2, RenderFragment>", "TItem2, System.TItem2, RenderFragment>")] + public void GenericTypeNameRewriter_CanReplaceTypeParametersWithTypeArguments(string original, string expected) + { + // Arrange + var visitor = new GenericTypeNameRewriter(new Dictionary() + { + { "TItem1", new GenericTypeNameRewriter.Binding(){ Content = "Type1", } }, + { "TItem2", new GenericTypeNameRewriter.Binding(){ Content = "Type2", } }, + { "TItem3", new GenericTypeNameRewriter.Binding(){ Content = null, } }, + }); + + var parsed = SyntaxFactory.ParseTypeName(original); + + // Act + var actual = visitor.Visit(parsed); + + // Assert + Assert.Equal(expected, actual.ToString()); + } + } +} diff --git a/test/testapps/BasicTestApp/MultipleChildContent.cshtml b/test/testapps/BasicTestApp/MultipleChildContent.cshtml index 5e6b42cbda..b0d1d97076 100644 --- a/test/testapps/BasicTestApp/MultipleChildContent.cshtml +++ b/test/testapps/BasicTestApp/MultipleChildContent.cshtml @@ -1,4 +1,4 @@ - +
Col1Col2Col3