Exemple #1
0
        public override void VisitMarkupElement(MarkupElementIntermediateNode node)
        {
            // Disallow <script> in components as per #552
            if (string.Equals(node.TagName, "script", StringComparison.OrdinalIgnoreCase))
            {
                for (var i = 0; i < node.Children.Count; i++)
                {
                    // We allow you to suppress this error like:
                    // <script suppress-error="BL9992" />
                    var attribute = node.Children[i] as HtmlAttributeIntermediateNode;
                    if (attribute != null &&
                        attribute.AttributeName == "suppress-error" &&
                        attribute.Children.Count == 1 &&
                        attribute.Children[0] is HtmlAttributeValueIntermediateNode value &&
                        value.Children.Count == 1 &&
                        value.Children[0] is IntermediateToken token &&
                        token.IsHtml &&
                        string.Equals(token.Content, "BL9992", StringComparison.Ordinal))
                    {
                        node.Children.RemoveAt(i);
                        return;
                    }
                }

                var diagnostic = ComponentDiagnosticFactory.Create_DisallowedScriptTag(node.Source);
                node.Diagnostics.Add(diagnostic);
            }

            base.VisitDefault(node);
        }
    private static IReadOnlyList <IntermediateToken> GetAttributeContent(IntermediateNode node)
    {
        var nodes    = node.FindDescendantNodes <TemplateIntermediateNode>();
        var template = nodes.Count > 0 ? nodes[0] : default;

        if (template != null)
        {
            // See comments in TemplateDiagnosticPass
            node.Diagnostics.Add(ComponentDiagnosticFactory.Create_TemplateInvalidLocation(template.Source));
            return(new[] { new IntermediateToken()
                           {
                               Kind = TokenKind.CSharp, Content = string.Empty,
                           }, });
        }

        if (node.Children.Count == 1 && node.Children[0] is HtmlContentIntermediateNode htmlContentNode)
        {
            // This case can be hit for a 'string' attribute. We want to turn it into
            // an expression.
            var tokens = htmlContentNode.FindDescendantNodes <IntermediateToken>();

            var content = "\"" + string.Join(string.Empty, tokens.Select(t => t.Content.Replace("\"", "\\\""))) + "\"";
            return(new[] { new IntermediateToken()
                           {
                               Content = content, Kind = TokenKind.CSharp,
                           } });
        }
        else
        {
            return(node.FindDescendantNodes <IntermediateToken>());
        }
    }
        public override void VisitComponentChildContent(ComponentChildContentIntermediateNode node)
        {
            // Check that each child content has a unique parameter name within its scope. This is important
            // because the parameter name can be implicit, and it doesn't work well when nested.
            if (node.IsParameterized)
            {
                for (var i = 0; i < Ancestors.Count - 1; i++)
                {
                    var ancestor = Ancestors[i] as ComponentChildContentIntermediateNode;
                    if (ancestor != null &&
                        ancestor.IsParameterized &&
                        string.Equals(node.ParameterName, ancestor.ParameterName, StringComparison.Ordinal))
                    {
                        // Duplicate name. We report an error because this will almost certainly also lead to an error
                        // from the C# compiler that's way less clear.
                        node.Diagnostics.Add(ComponentDiagnosticFactory.Create_ChildContentRepeatedParameterName(
                                                 node.Source,
                                                 node,
                                                 (ComponentIntermediateNode)Ancestors[0],       // Enclosing component
                                                 ancestor,                                      // conflicting child content node
                                                 (ComponentIntermediateNode)Ancestors[i + 1])); // Enclosing component of conflicting child content node
                    }
                }
            }

            base.VisitDefault(node);
        }
    protected override void ExecuteCore(RazorCodeDocument codeDocument, DocumentIntermediateNode documentNode)
    {
        if (!IsComponentDocument(documentNode))
        {
            return;
        }

        var @namespace = documentNode.FindPrimaryNamespace();
        var @class     = documentNode.FindPrimaryClass();

        if (@namespace == null || @class == null)
        {
            // Nothing to do, bail. We can't function without the standard structure.
            return;
        }

        // For each component *usage* we need to rewrite the tag helper node to map to the relevant component
        // APIs.
        var references = documentNode.FindDescendantReferences <TagHelperIntermediateNode>();

        for (var i = 0; i < references.Count; i++)
        {
            var reference = references[i];
            var node      = (TagHelperIntermediateNode)reference.Node;
            if (node.TagHelpers.Any(t => t.IsChildContentTagHelper()))
            {
                // This is a child content tag helper. This will be rewritten when we visit its parent.
                continue;
            }

            // The element didn't match any child content descriptors. Look for any matching component descriptors.
            var count = 0;
            for (var j = 0; j < node.TagHelpers.Count; j++)
            {
                if (node.TagHelpers[j].IsComponentTagHelper())
                {
                    // Only allow a single component tag helper per element. If there are multiple, we'll just consider
                    // the first one and ignore the others.
                    if (++count > 1)
                    {
                        node.Diagnostics.Add(ComponentDiagnosticFactory.Create_MultipleComponents(node.Source, node.TagName, node.TagHelpers));
                        break;
                    }
                }
            }

            if (count >= 1)
            {
                reference.Replace(RewriteAsComponent(node, node.TagHelpers.First(t => t.IsComponentTagHelper())));
            }
            else
            {
                reference.Replace(RewriteAsElement(node));
            }
        }
    }
Exemple #5
0
    public sealed override void WriteCSharpCodeAttributeValue(CodeRenderingContext context, CSharpCodeAttributeValueIntermediateNode node)
    {
        // We used to support syntaxes like <elem onsomeevent=@{ /* some C# code */ } /> but this is no longer the
        // case.
        //
        // We provide an error for this case just to be friendly.
        var content = string.Join("", node.Children.OfType <IntermediateToken>().Select(t => t.Content));

        context.Diagnostics.Add(ComponentDiagnosticFactory.Create_CodeBlockInAttribute(node.Source, content));
        return;
    }
        public override void VisitComponent(ComponentIntermediateNode node)
        {
            // Check for properties that are set by both element contents (body) and the attribute itself.
            foreach (var childContent in node.ChildContents)
            {
                foreach (var attribute in node.Attributes)
                {
                    if (attribute.AttributeName == childContent.AttributeName)
                    {
                        node.Diagnostics.Add(ComponentDiagnosticFactory.Create_ChildContentSetByAttributeAndBody(
                                                 attribute.Source,
                                                 attribute.AttributeName));
                    }
                }
            }

            base.VisitDefault(node);
        }
        public override void VisitComponent(ComponentIntermediateNode node)
        {
            for (var i = 0; i < node.Children.Count; i++)
            {
                // Note that we don't handle ChildContent cases here. Those have their own pass for diagnostics.
                if (node.Children[i] is ComponentAttributeIntermediateNode attribute && attribute.AttributeName != null)
                {
                    if (_attributes.TryGetValue(attribute.AttributeName, out var other))
                    {
                        // As a special case we want to point it out explicitly where a directive or other construct
                        // has emitted an attribute that causes a conflict. We're already looking at the lowered version
                        // of this construct, so it's easy to detect. We just need the original name to report the issue.
                        //
                        // Example: `bind-Value` will set `Value` and `ValueChanged`.
                        var originalAttributeName =
                            attribute.Annotations[ComponentMetadata.Common.OriginalAttributeName] as string ??
                            other.node.Annotations[ComponentMetadata.Common.OriginalAttributeName] as string;
                        if (originalAttributeName != null)
                        {
                            other.node.Diagnostics.Add(ComponentDiagnosticFactory.Create_DuplicateComponentParameterDirective(
                                                           other.name,
                                                           originalAttributeName,
                                                           other.node.Source ?? node.Source));
                        }
                        else
                        {
                            // This is a conflict in the code the user wrote.
                            other.node.Diagnostics.Add(ComponentDiagnosticFactory.Create_DuplicateComponentParameter(
                                                           other.name,
                                                           other.node.Source ?? node.Source));
                        }
                    }

                    // Replace the attribute we were previously tracking. Then if you have three, the two on the left will have
                    // diagnostics.
                    _attributes[attribute.AttributeName] = (attribute.AttributeName, attribute);
                }
            }

            _attributes.Clear();
            base.VisitComponent(node);
        }
    protected override void ExecuteCore(RazorCodeDocument codeDocument, DocumentIntermediateNode documentNode)
    {
        if (!IsComponentDocument(documentNode))
        {
            return;
        }

        var visitor = new Visitor();

        visitor.Visit(documentNode);

        for (var i = 0; i < visitor.Candidates.Count; i++)
        {
            var candidate = visitor.Candidates[i];
            candidate.Parent.Diagnostics.Add(ComponentDiagnosticFactory.Create_TemplateInvalidLocation(candidate.Node.Source));

            // Remove the offending node since we don't know how to render it. This means that the user won't get C#
            // completion at this location, which is fine because it's inside an HTML attribute.
            candidate.Remove();
        }
    }
 private void ProcessAttributes(TagHelperIntermediateNode node)
 {
     for (var i = node.Children.Count - 1; i >= 0; i--)
     {
         if (node.Children[i] is TagHelperPropertyIntermediateNode propertyNode)
         {
             if (!TrySimplifyContent(propertyNode) && node.TagHelpers.Any(t => t.IsComponentTagHelper()))
             {
                 node.Diagnostics.Add(ComponentDiagnosticFactory.Create_UnsupportedComplexContent(
                                          propertyNode,
                                          propertyNode.AttributeName));
                 node.Children.RemoveAt(i);
                 continue;
             }
         }
         else if (node.Children[i] is TagHelperHtmlAttributeIntermediateNode htmlNode)
         {
             if (!TrySimplifyContent(htmlNode) && node.TagHelpers.Any(t => t.IsComponentTagHelper()))
             {
                 node.Diagnostics.Add(ComponentDiagnosticFactory.Create_UnsupportedComplexContent(
                                          htmlNode,
                                          htmlNode.AttributeName));
                 node.Children.RemoveAt(i);
                 continue;
             }
         }
         else if (node.Children[i] is TagHelperDirectiveAttributeIntermediateNode directiveAttributeNode)
         {
             if (!TrySimplifyContent(directiveAttributeNode))
             {
                 node.Diagnostics.Add(ComponentDiagnosticFactory.Create_UnsupportedComplexContent(
                                          directiveAttributeNode,
                                          directiveAttributeNode.OriginalAttributeName));
                 node.Children.RemoveAt(i);
                 continue;
             }
         }
     }
 }
        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);
        }
Exemple #11
0
        public override void VisitRazorDirective(RazorDirectiveSyntax node)
        {
            var descendantLiterals = node.DescendantNodes();

            foreach (var child in descendantLiterals)
            {
                if (child is not CSharpStatementLiteralSyntax literal)
                {
                    continue;
                }

                var context = literal.GetSpanContext();
                if (context == null)
                {
                    // We can't find a chunk generator.
                    continue;
                }
                else if (context.ChunkGenerator is AddTagHelperChunkGenerator addTagHelper)
                {
                    // Make sure this node exists in the file we're parsing and not in its imports.
                    if (_filePath.Equals(_source.FilePath, StringComparison.Ordinal))
                    {
                        addTagHelper.Diagnostics.Add(
                            ComponentDiagnosticFactory.Create_UnsupportedTagHelperDirective(node.GetSourceSpan(_source)));
                    }
                }
                else if (context.ChunkGenerator is RemoveTagHelperChunkGenerator removeTagHelper)
                {
                    // Make sure this node exists in the file we're parsing and not in its imports.
                    if (_filePath.Equals(_source.FilePath, StringComparison.Ordinal))
                    {
                        removeTagHelper.Diagnostics.Add(
                            ComponentDiagnosticFactory.Create_UnsupportedTagHelperDirective(node.GetSourceSpan(_source)));
                    }
                }
                else if (context.ChunkGenerator is TagHelperPrefixDirectiveChunkGenerator tagHelperPrefix)
                {
                    // Make sure this node exists in the file we're parsing and not in its imports.
                    if (_filePath.Equals(_source.FilePath, StringComparison.Ordinal))
                    {
                        tagHelperPrefix.Diagnostics.Add(
                            ComponentDiagnosticFactory.Create_UnsupportedTagHelperDirective(node.GetSourceSpan(_source)));
                    }
                }
                else if (context.ChunkGenerator is AddImportChunkGenerator usingStatement && !usingStatement.IsStatic)
                {
                    // Get the namespace from the using statement.
                    var @namespace = usingStatement.ParsedNamespace;
                    if (@namespace.IndexOf('=') != -1)
                    {
                        // We don't support usings with alias.
                        continue;
                    }

                    for (var i = 0; _notFullyQualifiedComponents is not null && i < _notFullyQualifiedComponents.Count; i++)
                    {
                        var tagHelper = _notFullyQualifiedComponents[i];
                        Debug.Assert(!tagHelper.IsComponentFullyQualifiedNameMatch(), "We've already processed these.");

                        if (tagHelper.IsChildContentTagHelper())
                        {
                            // If this is a child content tag helper, we want to add it if it's original type is in scope of the given namespace.
                            // E.g, if the type name is `Test.MyComponent.ChildContent`, we want to add it if `Test.MyComponent` is in this namespace.
                            TrySplitNamespaceAndType(tagHelper, out var typeName);
                            if (!typeName.IsEmpty && IsTypeInNamespace(typeName, @namespace))
                            {
                                Matches.Add(tagHelper);
                            }
                        }
                        else if (IsTypeInNamespace(tagHelper, @namespace))
                        {
                            // If the type is at the top-level or if the type's namespace matches the using's namespace, add it.
                            Matches.Add(tagHelper);
                        }
                    }
                }
            }
        }
    private void ProcessDuplicates(IntermediateNode parent)
    {
        // Reverse order because we will remove nodes.
        //
        // Each 'property' node could be duplicated if there are multiple tag helpers that match that
        // particular attribute. This is likely to happen when a component also defines something like
        // OnClick. We want to remove the 'onclick' and let it fall back to be handled by the component.
        for (var i = parent.Children.Count - 1; i >= 0; i--)
        {
            var eventHandler = parent.Children[i] as TagHelperPropertyIntermediateNode;
            if (eventHandler != null &&
                eventHandler.TagHelper != null &&
                eventHandler.TagHelper.IsEventHandlerTagHelper())
            {
                for (var j = 0; j < parent.Children.Count; j++)
                {
                    var componentAttribute = parent.Children[j] as ComponentAttributeIntermediateNode;
                    if (componentAttribute != null &&
                        componentAttribute.TagHelper != null &&
                        componentAttribute.TagHelper.IsComponentTagHelper() &&
                        componentAttribute.AttributeName == eventHandler.AttributeName)
                    {
                        // Found a duplicate - remove the 'fallback' in favor of the component's own handling.
                        parent.Children.RemoveAt(i);
                        break;
                    }
                }
            }
        }

        // If we still have duplicates at this point then they are genuine conflicts.
        var duplicates = parent.Children
                         .OfType <TagHelperDirectiveAttributeIntermediateNode>()
                         .Where(p => p.TagHelper?.IsEventHandlerTagHelper() ?? false)
                         .GroupBy(p => p.AttributeName)
                         .Where(g => g.Count() > 1);

        foreach (var duplicate in duplicates)
        {
            parent.Diagnostics.Add(ComponentDiagnosticFactory.CreateEventHandler_Duplicates(
                                       parent.Source,
                                       duplicate.Key,
                                       duplicate.ToArray()));
            foreach (var property in duplicate)
            {
                parent.Children.Remove(property);
            }
        }

        var parameterDuplicates = parent.Children
                                  .OfType <TagHelperDirectiveAttributeParameterIntermediateNode>()
                                  .Where(p => p.TagHelper?.IsEventHandlerTagHelper() ?? false)
                                  .GroupBy(p => p.AttributeName)
                                  .Where(g => g.Count() > 1);

        foreach (var duplicate in parameterDuplicates)
        {
            parent.Diagnostics.Add(ComponentDiagnosticFactory.CreateEventHandlerParameter_Duplicates(
                                       parent.Source,
                                       duplicate.Key,
                                       duplicate.ToArray()));
            foreach (var property in duplicate)
            {
                parent.Children.Remove(property);
            }
        }
    }
    /// <inheritdoc />
    protected override void OnDocumentStructureCreated(
        RazorCodeDocument codeDocument,
        NamespaceDeclarationIntermediateNode @namespace,
        ClassDeclarationIntermediateNode @class,
        MethodDeclarationIntermediateNode method)
    {
        if (!codeDocument.TryComputeNamespace(fallbackToRootNamespace: true, out var computedNamespace) ||
            !TryComputeClassName(codeDocument, out var computedClass))
        {
            // If we can't compute a nice namespace (no relative path) then just generate something
            // mangled.
            computedNamespace = FallbackRootNamespace;
            var checksum = Checksum.BytesToString(codeDocument.Source.GetChecksum());
            computedClass = $"AspNetCore_{checksum}";
        }

        var documentNode = codeDocument.GetDocumentIntermediateNode();

        if (char.IsLower(computedClass, 0))
        {
            // We don't allow component names to start with a lowercase character.
            documentNode.Diagnostics.Add(
                ComponentDiagnosticFactory.Create_ComponentNamesCannotStartWithLowerCase(computedClass, documentNode.Source));
        }

        if (MangleClassNames)
        {
            computedClass = ComponentMetadata.MangleClassName(computedClass);
        }

        @namespace.Content = computedNamespace;
        @class.ClassName   = computedClass;
        @class.Modifiers.Clear();
        @class.Modifiers.Add("public");
        @class.Modifiers.Add("partial");

        if (FileKinds.IsComponentImport(codeDocument.GetFileKind()))
        {
            // We don't want component imports to be considered as real component.
            // But we still want to generate code for it so we can get diagnostics.
            @class.BaseType = typeof(object).FullName;

            method.ReturnType = "void";
            method.MethodName = "Execute";
            method.Modifiers.Clear();
            method.Modifiers.Add("protected");

            method.Parameters.Clear();
        }
        else
        {
            @class.BaseType = ComponentsApi.ComponentBase.FullTypeName;

            // Constrained type parameters are only supported in Razor language versions v6.0
            var razorLanguageVersion = codeDocument.GetParserOptions()?.Version ?? RazorLanguageVersion.Latest;
            var directiveType        = razorLanguageVersion.CompareTo(RazorLanguageVersion.Version_6_0) >= 0
                ? ComponentConstrainedTypeParamDirective.Directive
                : ComponentTypeParamDirective.Directive;
            var typeParamReferences = documentNode.FindDirectiveReferences(directiveType);
            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,
                    Constraints   = typeParamNode.Tokens.Skip(1).FirstOrDefault()?.Content
                });
            }

            method.ReturnType = "void";
            method.MethodName = ComponentsApi.ComponentBase.BuildRenderTree;
            method.Modifiers.Clear();
            method.Modifiers.Add("protected");
            method.Modifiers.Add("override");

            method.Parameters.Clear();
            method.Parameters.Add(new MethodParameter()
            {
                ParameterName = ComponentsApi.RenderTreeBuilder.BuilderParameter,
                TypeName      = ComponentsApi.RenderTreeBuilder.FullTypeName,
            });
        }
    }
        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);
        }
Exemple #15
0
    protected override void ExecuteCore(RazorCodeDocument codeDocument, DocumentIntermediateNode documentNode)
    {
        if (codeDocument == null)
        {
            throw new ArgumentNullException(nameof(codeDocument));
        }

        if (documentNode == null)
        {
            throw new ArgumentNullException(nameof(documentNode));
        }

        var @namespace = documentNode.FindPrimaryNamespace();
        var @class     = documentNode.FindPrimaryClass();

        if (@namespace == null || @class == null)
        {
            return;
        }

        var directives = documentNode.FindDirectiveReferences(ComponentPageDirective.Directive);

        if (directives.Count == 0)
        {
            return;
        }

        // We don't allow @page directives in imports
        for (var i = 0; i < directives.Count; i++)
        {
            var directive = directives[i];
            if (FileKinds.IsComponentImport(codeDocument.GetFileKind()) || directive.Node.IsImported())
            {
                directive.Node.Diagnostics.Add(ComponentDiagnosticFactory.CreatePageDirective_CannotBeImported(directive.Node.Source.Value));
            }
        }

        // Insert the attributes 'on-top' of the class declaration, since classes don't directly support attributes.
        var index = 0;

        for (; index < @namespace.Children.Count; index++)
        {
            if (object.ReferenceEquals(@class, @namespace.Children[index]))
            {
                break;
            }
        }

        for (var i = 0; i < directives.Count; i++)
        {
            var pageDirective = (DirectiveIntermediateNode)directives[i].Node;

            // The parser also adds errors for invalid syntax, we just need to not crash.
            var routeToken = pageDirective.Tokens.FirstOrDefault();

            if (routeToken != null &&
                routeToken.Content.Length >= 3 &&
                routeToken.Content[0] == '\"' &&
                routeToken.Content[1] == '/' &&
                routeToken.Content[routeToken.Content.Length - 1] == '\"')
            {
                var template = new StringSegment(routeToken.Content, 1, routeToken.Content.Length - 2);
                @namespace.Children.Insert(index++, new RouteAttributeExtensionNode(template));
            }
            else
            {
                pageDirective.Diagnostics.Add(ComponentDiagnosticFactory.CreatePageDirective_MustSpecifyRoute(pageDirective.Source));
            }
        }
    }