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)); } } }
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); }
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, }); } }
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)); } } }