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 event handler *usage* we need to rewrite the tag helper node to map to basic constructs.
            // Each usage will be represented by a tag helper property that is a descendant of either
            // a component or element.
            var references = documentNode.FindDescendantReferences <TagHelperDirectiveAttributeIntermediateNode>();
            var parents    = new HashSet <IntermediateNode>();

            for (var i = 0; i < references.Count; i++)
            {
                parents.Add(references[i].Parent);
            }

            // We need to do something similar for directive attribute parameters like @onclick:preventDefault.
            var parameterReferences = documentNode.FindDescendantReferences <TagHelperDirectiveAttributeParameterIntermediateNode>();

            for (var i = 0; i < parameterReferences.Count; i++)
            {
                parents.Add(parameterReferences[i].Parent);
            }

            foreach (var parent in parents)
            {
                ProcessDuplicates(parent);
            }

            for (var i = 0; i < references.Count; i++)
            {
                var reference = references[i];
                var node      = (TagHelperDirectiveAttributeIntermediateNode)reference.Node;

                if (!reference.Parent.Children.Contains(node))
                {
                    // This node was removed as a duplicate, skip it.
                    continue;
                }

                if (node.TagHelper.IsEventHandlerTagHelper())
                {
                    reference.Replace(RewriteUsage(reference.Parent, node));
                }
            }

            for (var i = 0; i < parameterReferences.Count; i++)
            {
                var reference = parameterReferences[i];
                var node      = (TagHelperDirectiveAttributeParameterIntermediateNode)reference.Node;

                if (!reference.Parent.Children.Contains(node))
                {
                    // This node was removed as a duplicate, skip it.
                    continue;
                }

                if (node.TagHelper.IsEventHandlerTagHelper())
                {
                    reference.Replace(RewriteParameterUsage(reference.Parent, node));
                }
            }
        }
Ejemplo n.º 2
0
            private void CreateTypeInferenceMethod(
                DocumentIntermediateNode documentNode,
                ComponentExtensionNode node,
                Dictionary <string, GenericTypeNameRewriter.Binding> bindings)
            {
                var @namespace = documentNode.FindPrimaryNamespace().Content;

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

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

                    // Method name is generated and guaraneteed not to collide, since it's unique for each
                    // component call site.
                    MethodName   = $"Create{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>(BlazorMetadata.Component.GenericTypedKey, bool.TrueString)))
                                    .FirstOrDefault();

                if (namespaceNode == null)
                {
                    namespaceNode = new NamespaceDeclarationIntermediateNode()
                    {
                        Annotations =
                        {
                            { BlazorMetadata.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);
            }
    protected override void ExecuteCore(RazorCodeDocument codeDocument, DocumentIntermediateNode documentNode)
    {
        if (documentNode.Options.DesignTime)
        {
            return;
        }

        var @namespace = documentNode.FindPrimaryNamespace();

        if (@namespace == null || string.IsNullOrEmpty(@namespace.Content))
        {
            // No namespace node or it's incomplete. Skip.
            return;
        }

        var @class = documentNode.FindPrimaryClass();

        if (@class == null || string.IsNullOrEmpty(@class.ClassName))
        {
            // No class node or it's incomplete. Skip.
            return;
        }

        var generatedTypeName = $"{@namespace.Content}.{@class.ClassName}";

        // The MVC attributes require a relative path to be specified so that we can make a view engine path.
        // We can't use a rooted path because we don't know what the project root is.
        //
        // If we can't sanitize the path, we'll just set it to null and let is blow up at runtime - we don't
        // want to create noise if this code has to run in some unanticipated scenario.
        var escapedPath = MakeVerbatimStringLiteral(ConvertToViewEnginePath(codeDocument.Source.RelativePath));

        string attribute;

        if (documentNode.DocumentKind == MvcViewDocumentClassifierPass.MvcViewDocumentKind)
        {
            attribute = $"[assembly:{RazorViewAttribute}({escapedPath}, typeof({generatedTypeName}))]";
        }
        else if (documentNode.DocumentKind == RazorPageDocumentClassifierPass.RazorPageDocumentKind &&
                 PageDirective.TryGetPageDirective(documentNode, out var pageDirective))
        {
            var escapedRoutePrefix = MakeVerbatimStringLiteral(pageDirective.RouteTemplate);
            attribute = $"[assembly:{RazorPageAttribute}({escapedPath}, typeof({generatedTypeName}), {escapedRoutePrefix})]";
        }
        else
        {
            return;
        }

        var index = documentNode.Children.IndexOf(@namespace);

        Debug.Assert(index >= 0);

        var pageAttribute = new CSharpCodeIntermediateNode();

        pageAttribute.Children.Add(new IntermediateToken()
        {
            Kind    = TokenKind.CSharp,
            Content = attribute,
        });

        documentNode.Children.Insert(index, pageAttribute);
    }
Ejemplo n.º 4
0
        protected override void ExecuteCore(RazorCodeDocument codeDocument, DocumentIntermediateNode documentNode)
        {
            if (documentNode.Options == null || documentNode.Options.SuppressMetadataAttributes)
            {
                // Metadata attributes are turned off (or options not populated), nothing to do.
                return;
            }

            if (string.Equals(documentNode.DocumentKind, ComponentDocumentClassifierPass.ComponentDocumentKind, StringComparison.Ordinal))
            {
                // Metadata attributes are not used for components.
                return;
            }

            // We need to be able to compute the data we need for the [RazorCompiledItem] attribute - that includes
            // a full type name, and a document kind, and optionally an identifier.
            //
            // If we can't use [RazorCompiledItem] then we don't care about the rest of the attributes.
            var @namespace = documentNode.FindPrimaryNamespace();

            if (@namespace == null || string.IsNullOrEmpty(@namespace.Content))
            {
                // No namespace node or it's incomplete. Skip.
                return;
            }

            var @class = documentNode.FindPrimaryClass();

            if (@class == null || string.IsNullOrEmpty(@class.ClassName))
            {
                // No class node or it's incomplete. Skip.
                return;
            }

            if (documentNode.DocumentKind == null)
            {
                // No document kind. Skip.
                return;
            }

            var identifier = _identifierFeature?.GetIdentifier(codeDocument, codeDocument.Source);

            if (identifier == null)
            {
                // No identifier. Skip
                return;
            }

            // [RazorCompiledItem] is an [assembly: ... ] attribute, so it needs to be applied at the global scope.
            documentNode.Children.Insert(0, new RazorCompiledItemAttributeIntermediateNode()
            {
                TypeName   = @namespace.Content + "." + @class.ClassName,
                Kind       = documentNode.DocumentKind,
                Identifier = identifier,
            });

            // Now we need to add a [RazorSourceChecksum] for the source and for each import
            // these are class attributes, so we need to find the insertion point to put them
            // right before the class.
            var insert = (int?)null;

            for (var j = 0; j < @namespace.Children.Count; j++)
            {
                if (object.ReferenceEquals(@namespace.Children[j], @class))
                {
                    insert = j;
                    break;
                }
            }

            if (insert == null)
            {
                // Can't find a place to put the attributes, just bail.
                return;
            }

            // Checksum of the main source
            var checksum          = codeDocument.Source.GetChecksum();
            var checksumAlgorithm = codeDocument.Source.GetChecksumAlgorithm();

            if (checksum == null || checksum.Length == 0 || checksumAlgorithm == null)
            {
                // Don't generate anything unless we have all of the required information.
                return;
            }

            @namespace.Children.Insert((int)insert++, new RazorSourceChecksumAttributeIntermediateNode()
            {
                Checksum          = checksum,
                ChecksumAlgorithm = checksumAlgorithm,
                Identifier        = identifier,
            });

            // Now process the checksums of the imports
            Debug.Assert(_identifierFeature != null);
            for (var i = 0; i < codeDocument.Imports.Count; i++)
            {
                var import = codeDocument.Imports[i];

                checksum          = import.GetChecksum();
                checksumAlgorithm = import.GetChecksumAlgorithm();
                identifier        = _identifierFeature.GetIdentifier(codeDocument, import);

                if (checksum == null || checksum.Length == 0 || checksumAlgorithm == null || identifier == null)
                {
                    // It's ok to skip an import if we don't have all of the required information.
                    continue;
                }

                @namespace.Children.Insert((int)insert++, new RazorSourceChecksumAttributeIntermediateNode()
                {
                    Checksum          = checksum,
                    ChecksumAlgorithm = checksumAlgorithm,
                    Identifier        = identifier,
                });
            }
        }
Ejemplo n.º 5
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));
            }
        }
    }