Ejemplo n.º 1
0
        public static RazorDiagnostic Create_ChildContentRepeatedParameterName(
            SourceSpan?source,
            ComponentChildContentIntermediateNode childContent1,
            ComponentIntermediateNode component1,
            ComponentChildContentIntermediateNode childContent2,
            ComponentIntermediateNode component2)
        {
            Debug.Assert(childContent1.ParameterName == childContent2.ParameterName);
            Debug.Assert(childContent1.IsParameterized);
            Debug.Assert(childContent2.IsParameterized);

            return(RazorDiagnostic.Create(
                       ChildContentRepeatedParameterName,
                       source ?? SourceSpan.Undefined,
                       childContent1.AttributeName,
                       component1.TagName,
                       childContent1.ParameterName,
                       childContent2.AttributeName,
                       component2.TagName));
        }
            private static bool ValidateTypeArguments(ComponentIntermediateNode node, Dictionary <string, Binding> bindings)
            {
                var missing = new List <BoundAttributeDescriptor>();

                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(ComponentDiagnosticFactory.Create_GenericComponentMissingTypeArgument(node.Source, node, missing));
                    return(false);
                }

                return(true);
            }
Ejemplo n.º 3
0
 public virtual void VisitComponent(ComponentIntermediateNode node)
 {
     VisitDefault(node);
 }
            private void Process(ComponentIntermediateNode node)
            {
                // First collect all of the information we have about each type parameter
                //
                // Listing all type parameters that exist
                var bindings = new Dictionary <string, Binding>();

                foreach (var attribute in node.Component.GetTypeParameters())
                {
                    bindings.Add(attribute.Name, new Binding()
                    {
                        Attribute = attribute,
                    });
                }

                // Listing all type arguments that have been specified.
                var hasTypeArgumentSpecified = false;

                foreach (var typeArgumentNode in node.TypeArguments)
                {
                    hasTypeArgumentSpecified = true;

                    var binding = bindings[typeArgumentNode.TypeParameterName];
                    binding.Node    = typeArgumentNode;
                    binding.Content = GetContent(typeArgumentNode);
                }

                if (hasTypeArgumentSpecified)
                {
                    // OK this means that the developer has specified at least one type parameter.
                    // Either they specified everything and its OK to rewrite, or its an error.
                    if (ValidateTypeArguments(node, bindings))
                    {
                        var mappings = bindings.ToDictionary(kvp => kvp.Key, kvp => kvp.Value.Content);
                        RewriteTypeNames(_pass.TypeNameFeature.CreateGenericTypeRewriter(mappings), node);
                    }

                    return;
                }

                // OK if we get here that means that no type arguments were specified, so we will try to infer
                // the type.
                //
                // The actual inference is done by the C# compiler, we just emit an a method that represents the
                // use of this component.

                // Since we're generating code in a different namespace, we need to 'global qualify' all of the types
                // to avoid clashes with our generated code.
                RewriteTypeNames(_pass.TypeNameFeature.CreateGlobalQualifiedTypeNameRewriter(bindings.Keys), node);

                //
                // We need to verify that an argument was provided that 'covers' each type parameter.
                //
                // For example, consider a repeater where the generic type is the 'item' type, but the developer has
                // not set the items. We won't be able to do type inference on this and so it will just be nonsense.
                var attributes = node.Attributes.Select(a => a.BoundAttribute).Concat(node.ChildContents.Select(c => c.BoundAttribute));

                foreach (var attribute in attributes)
                {
                    if (attribute == null)
                    {
                        // Will be null for attributes set on the component that don't match a declared component parameter
                        continue;
                    }

                    // Now we need to parse the type name and extract the generic parameters.
                    //
                    // Two cases;
                    // 1. name is a simple identifier like TItem
                    // 2. name contains type parameters like Dictionary<string, TItem>
                    if (!attribute.IsGenericTypedProperty())
                    {
                        continue;
                    }

                    var typeParameters = _pass.TypeNameFeature.ParseTypeParameters(attribute.TypeName);
                    if (typeParameters.Count == 0)
                    {
                        bindings.Remove(attribute.TypeName);
                    }
                    else
                    {
                        for (var i = 0; i < typeParameters.Count; i++)
                        {
                            var typeParameter = typeParameters[i];
                            bindings.Remove(typeParameter.ToString());
                        }
                    }
                }

                // If any bindings remain then this means we would never be able to infer the arguments of this
                // component usage because the user hasn't set properties that include all of the types.
                if (bindings.Count > 0)
                {
                    // However we still want to generate 'type inference' code because we want the errors to be as
                    // helpful as possible. So let's substitute 'object' for all of those type parameters, and add
                    // an error.
                    var mappings = bindings.ToDictionary(kvp => kvp.Key, kvp => kvp.Value.Content);
                    RewriteTypeNames(_pass.TypeNameFeature.CreateGenericTypeRewriter(mappings), node);

                    node.Diagnostics.Add(ComponentDiagnosticFactory.Create_GenericComponentTypeInferenceUnderspecified(node.Source, node, node.Component.GetTypeParameters()));
                }

                // Next we need to generate a type inference 'method' node. This represents a method that we will codegen that
                // contains all of the operations on the render tree building. Calling a method to operate on the builder
                // will allow the C# compiler to perform type inference.
                var documentNode = (DocumentIntermediateNode)Ancestors[Ancestors.Count - 1];

                CreateTypeInferenceMethod(documentNode, node);
            }
            private void CreateTypeInferenceMethod(DocumentIntermediateNode documentNode, ComponentIntermediateNode node)
            {
                var @namespace = documentNode.FindPrimaryNamespace().Content;

                @namespace  = string.IsNullOrEmpty(@namespace) ? "__Blazor" : "__Blazor." + @namespace;
                @namespace += "." + documentNode.FindPrimaryClass().ClassName;

                var typeInferenceNode = new ComponentTypeInferenceMethodIntermediateNode()
                {
                    Component = node,

                    // Method name is generated and guaranteed not to collide, since it's unique for each
                    // component call site.
                    MethodName   = $"Create{CSharpIdentifier.SanitizeIdentifier(node.TagName)}_{_id++}",
                    FullTypeName = @namespace + ".TypeInference",
                };

                node.TypeInferenceNode = typeInferenceNode;

                // Now we need to insert the type inference node into the tree.
                var namespaceNode = documentNode.Children
                                    .OfType <NamespaceDeclarationIntermediateNode>()
                                    .Where(n => n.Annotations.Contains(new KeyValuePair <object, object>(ComponentMetadata.Component.GenericTypedKey, bool.TrueString)))
                                    .FirstOrDefault();

                if (namespaceNode == null)
                {
                    namespaceNode = new NamespaceDeclarationIntermediateNode()
                    {
                        Annotations =
                        {
                            { ComponentMetadata.Component.GenericTypedKey, bool.TrueString },
                        },
                        Content = @namespace,
                    };

                    documentNode.Children.Add(namespaceNode);
                }

                var classNode = namespaceNode.Children
                                .OfType <ClassDeclarationIntermediateNode>()
                                .Where(n => n.ClassName == "TypeInference")
                                .FirstOrDefault();

                if (classNode == null)
                {
                    classNode = new ClassDeclarationIntermediateNode()
                    {
                        ClassName = "TypeInference",
                        Modifiers =
                        {
                            "internal",
                            "static",
                        },
                    };
                    namespaceNode.Children.Add(classNode);
                }

                classNode.Children.Add(typeInferenceNode);
            }
            private void RewriteTypeNames(TypeNameRewriter rewriter, ComponentIntermediateNode node)
            {
                // Rewrite the component type name
                node.TypeName = rewriter.Rewrite(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 = rewriter.Rewrite(attribute.TypeName);
                    }
                    else if (attribute.TypeName == null && (attribute.BoundAttribute?.IsDelegateProperty() ?? false))
                    {
                        // This is a weakly typed delegate, treat it as Action<object>
                        attribute.TypeName = "System.Action<System.Object>";
                    }
                    else if (attribute.TypeName == null && (attribute.BoundAttribute?.IsEventCallbackProperty() ?? false))
                    {
                        // This is a weakly typed event-callback, treat it as EventCallback (non-generic)
                        attribute.TypeName = ComponentsApi.EventCallback.FullTypeName;
                    }
                    else if (attribute.TypeName == null)
                    {
                        // This is a weakly typed attribute, treat it as System.Object
                        attribute.TypeName = "System.Object";
                    }
                }

                foreach (var capture in node.Captures)
                {
                    if (capture.IsComponentCapture && capture.ComponentCaptureTypeName != null)
                    {
                        capture.ComponentCaptureTypeName = rewriter.Rewrite(capture.ComponentCaptureTypeName);
                    }
                    else if (capture.IsComponentCapture)
                    {
                        capture.ComponentCaptureTypeName = "System.Object";
                    }
                }

                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 = rewriter.Rewrite(childContent.TypeName);
                    }
                    else if (childContent.IsParameterized)
                    {
                        // This is a weakly typed parameterized child content, treat it as RenderFragment<object>
                        childContent.TypeName = ComponentsApi.RenderFragment.FullTypeName + "<System.Object>";
                    }
                    else
                    {
                        // This is a weakly typed child content, treat it as RenderFragment
                        childContent.TypeName = ComponentsApi.RenderFragment.FullTypeName;
                    }
                }
            }
Ejemplo n.º 7
0
            private void Process(ComponentIntermediateNode node)
            {
                // First collect all of the information we have about each type parameter
                //
                // Listing all type parameters that exist
                var bindings = new Dictionary <string, Binding>();
                var componentTypeParameters       = node.Component.GetTypeParameters().ToList();
                var supplyCascadingTypeParameters = componentTypeParameters
                                                    .Where(p => p.IsCascadingTypeParameterProperty())
                                                    .Select(p => p.Name)
                                                    .ToList();

                foreach (var attribute in componentTypeParameters)
                {
                    bindings.Add(attribute.Name, new Binding()
                    {
                        Attribute = attribute,
                    });
                }

                // Listing all type arguments that have been specified.
                var hasTypeArgumentSpecified = false;

                foreach (var typeArgumentNode in node.TypeArguments)
                {
                    hasTypeArgumentSpecified = true;

                    var binding = bindings[typeArgumentNode.TypeParameterName];
                    binding.Node    = typeArgumentNode;
                    binding.Content = GetContent(typeArgumentNode);

                    // Offer this explicit type argument to descendants too
                    if (supplyCascadingTypeParameters.Contains(typeArgumentNode.TypeParameterName))
                    {
                        node.ProvidesCascadingGenericTypes ??= new();
                        node.ProvidesCascadingGenericTypes[typeArgumentNode.TypeParameterName] = new CascadingGenericTypeParameter
                        {
                            GenericTypeNames = new[] { typeArgumentNode.TypeParameterName },
                            ValueType        = typeArgumentNode.TypeParameterName,
                            ValueExpression  = $"default({binding.Content})",
                        };
                    }
                }

                if (hasTypeArgumentSpecified)
                {
                    // OK this means that the developer has specified at least one type parameter.
                    // Either they specified everything and its OK to rewrite, or its an error.
                    if (ValidateTypeArguments(node, bindings))
                    {
                        var mappings = bindings.ToDictionary(kvp => kvp.Key, kvp => kvp.Value.Content);
                        RewriteTypeNames(_pass.TypeNameFeature.CreateGenericTypeRewriter(mappings), node);
                    }

                    return;
                }

                // OK if we get here that means that no type arguments were specified, so we will try to infer
                // the type.
                //
                // The actual inference is done by the C# compiler, we just emit an a method that represents the
                // use of this component.

                // Since we're generating code in a different namespace, we need to 'global qualify' all of the types
                // to avoid clashes with our generated code.
                RewriteTypeNames(_pass.TypeNameFeature.CreateGlobalQualifiedTypeNameRewriter(bindings.Keys), node);

                //
                // We need to verify that an argument was provided that 'covers' each type parameter.
                //
                // For example, consider a repeater where the generic type is the 'item' type, but the developer has
                // not set the items. We won't be able to do type inference on this and so it will just be nonsense.
                foreach (var attribute in node.Attributes)
                {
                    if (attribute != null && TryFindGenericTypeNames(attribute.BoundAttribute, out var typeParameters))
                    {
                        var attributeValueIsLambda       = _pass.TypeNameFeature.IsLambda(GetContent(attribute));
                        var provideCascadingGenericTypes = new CascadingGenericTypeParameter
                        {
                            GenericTypeNames = typeParameters,
                            ValueType        = attribute.BoundAttribute.TypeName,
                            ValueSourceNode  = attribute,
                        };

                        foreach (var typeName in typeParameters)
                        {
                            if (supplyCascadingTypeParameters.Contains(typeName))
                            {
                                // Advertise that this particular inferred generic type is available to descendants.
                                // There might be multiple sources for each generic type, so pick the one that has the
                                // fewest other generic types on it. For example if we could infer from either List<T>
                                // or Dictionary<T, U>, we prefer List<T>.
                                node.ProvidesCascadingGenericTypes ??= new();
                                if (!node.ProvidesCascadingGenericTypes.TryGetValue(typeName, out var existingValue) ||
                                    existingValue.GenericTypeNames.Count > typeParameters.Count)
                                {
                                    node.ProvidesCascadingGenericTypes[typeName] = provideCascadingGenericTypes;
                                }
                            }

                            if (attributeValueIsLambda)
                            {
                                // For attributes whose values are lambdas, we don't know whether or not the value
                                // covers the generic type - it depends on the content of the lambda.
                                // For example, "() => 123" can cover Func<T>, but "() => null" cannot. So we'll
                                // accept cascaded generic types from ancestors if they are compatible with the lambda,
                                // hence we don't remove it from the list of uncovered generic types until after
                                // we try matching against ancestor cascades.
                                if (bindings.TryGetValue(typeName, out var binding))
                                {
                                    binding.CoveredByLambda = true;
                                }
                            }
                            else
                            {
                                bindings.Remove(typeName);
                            }
                        }
                    }
                }

                // For any remaining bindings, scan up the hierarchy of ancestor components and try to match them
                // with a cascaded generic parameter that can cover this one
                List <CascadingGenericTypeParameter> receivesCascadingGenericTypes = null;

                foreach (var uncoveredBindingKey in bindings.Keys.ToList())
                {
                    var uncoveredBinding = bindings[uncoveredBindingKey];
                    foreach (var candidateAncestor in Ancestors.OfType <ComponentIntermediateNode>())
                    {
                        if (candidateAncestor.ProvidesCascadingGenericTypes != null &&
                            candidateAncestor.ProvidesCascadingGenericTypes.TryGetValue(uncoveredBindingKey, out var genericTypeProvider))
                        {
                            // If the parameter value is an expression that includes multiple generic types, we only want
                            // to use it if we want *all* those generic types. That is, a parameter of type MyType<T0, T1>
                            // can supply types to a Child<T0, T1>, but not to a Child<T0>.
                            // This is purely to avoid blowing up the complexity of the implementation here and could be
                            // overcome in the future if we want. We'd need to figure out which extra types are unwanted,
                            // and rewrite them to some unique name, and add that to the generic parameters list of the
                            // inference methods.
                            if (genericTypeProvider.GenericTypeNames.All(GenericTypeIsUsed))
                            {
                                bindings.Remove(uncoveredBindingKey);
                                receivesCascadingGenericTypes ??= new();
                                receivesCascadingGenericTypes.Add(genericTypeProvider);

                                // It's sufficient to identify the closest provider for each type parameter
                                break;
                            }

                            bool GenericTypeIsUsed(string typeName) => componentTypeParameters
                            .Select(t => t.Name)
                            .Contains(typeName, StringComparer.Ordinal);
                        }
                    }
                }

                // There are two remaining sources of possible generic type info which we consider
                // lower-priority than cascades from ancestors. Since these two sources *may* actually
                // resolve generic type ambiguities in some cases, we treat them as covering.
                //
                // [1] Attributes given as lambda expressions. These are lower priority than ancestor
                //     cascades because in most cases, lambdas don't provide type info
                foreach (var entryToRemove in bindings.Where(e => e.Value.CoveredByLambda).ToList())
                {
                    // Treat this binding as covered, because it's possible that the lambda does provide
                    // enough info for type inference to succeed.
                    bindings.Remove(entryToRemove.Key);
                }

                // [2] Child content parameters, which are nearly always defined as untyped lambdas
                //     (at least, that's what the Razor compiler produces), but can technically be
                //     hardcoded as a RenderFragment<Something> and hence actually give type info.
                foreach (var attribute in node.ChildContents)
                {
                    if (TryFindGenericTypeNames(attribute.BoundAttribute, out var typeParameters))
                    {
                        foreach (var typeName in typeParameters)
                        {
                            bindings.Remove(typeName);
                        }
                    }
                }

                // If any bindings remain then this means we would never be able to infer the arguments of this
                // component usage because the user hasn't set properties that include all of the types.
                if (bindings.Count > 0)
                {
                    // However we still want to generate 'type inference' code because we want the errors to be as
                    // helpful as possible. So let's substitute 'object' for all of those type parameters, and add
                    // an error.
                    var mappings = bindings.ToDictionary(kvp => kvp.Key, kvp => kvp.Value.Content);
                    RewriteTypeNames(_pass.TypeNameFeature.CreateGenericTypeRewriter(mappings), node);

                    node.Diagnostics.Add(ComponentDiagnosticFactory.Create_GenericComponentTypeInferenceUnderspecified(node.Source, node, node.Component.GetTypeParameters()));
                }

                // Next we need to generate a type inference 'method' node. This represents a method that we will codegen that
                // contains all of the operations on the render tree building. Calling a method to operate on the builder
                // will allow the C# compiler to perform type inference.
                var documentNode = (DocumentIntermediateNode)Ancestors[Ancestors.Count - 1];

                CreateTypeInferenceMethod(documentNode, node, receivesCascadingGenericTypes);
            }
Ejemplo n.º 8
0
 public ComponentRewriteVisitor(ComponentIntermediateNode component)
 {
     _component = component;
     _children  = component.Children;
 }
Ejemplo n.º 9
0
 public override void VisitComponent(ComponentIntermediateNode node)
 {
     Context.NodeWriter.WriteComponent(Context, node);
 }
Ejemplo n.º 10
0
 public override void VisitComponent(ComponentIntermediateNode node)
 {
     WriteContentNode(node, node.TagName);
 }
        public static RazorDiagnostic Create_ChildContentMixedWithExplicitChildContent(SourceSpan?source, ComponentIntermediateNode component)
        {
            var supportedElements = string.Join(", ", component.Component.GetChildContentProperties().Select(p => $"'{p.Name}'"));

            return(RazorDiagnostic.Create(ChildContentMixedWithExplicitChildContent, source ?? SourceSpan.Undefined, component.TagName, supportedElements));
        }
Ejemplo n.º 12
0
 public virtual void WriteComponent(CodeRenderingContext context, ComponentIntermediateNode node)
 {
     throw new NotSupportedException("This writer does not support components.");
 }
Ejemplo n.º 13
0
        private static void ValidateRequiredAttributes(TagHelperIntermediateNode node, TagHelperDescriptor tagHelper, ComponentIntermediateNode intermediateNode)
        {
            if (intermediateNode.Children.Any(c => c is TagHelperDirectiveAttributeIntermediateNode node && (node.TagHelper?.IsSplatTagHelper() ?? false)))
            {
                // If there are any splat attributes, assume the user may have provided all values.
                // This pass runs earlier than ComponentSplatLoweringPass, so we cannot rely on the presence of SplatIntermediateNode to make this check.
                return;
            }

            foreach (var requiredAttribute in tagHelper.EditorRequiredAttributes)
            {
                if (!IsPresentAsAttribute(requiredAttribute.Name, intermediateNode))
                {
                    intermediateNode.Diagnostics.Add(
                        RazorDiagnosticFactory.CreateComponent_EditorRequiredParameterNotSpecified(
                            node.Source ?? SourceSpan.Undefined,
                            intermediateNode.TagName,
                            requiredAttribute.Name));
                }
            }