protected override void ExecuteCore(RazorCodeDocument codeDocument, DocumentIntermediateNode documentNode) { 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 <TagHelperPropertyIntermediateNode>(); var parents = new HashSet <IntermediateNode>(); for (var i = 0; i < references.Count; i++) { parents.Add(references[i].Parent); } foreach (var parent in parents) { ProcessDuplicates(parent); } for (var i = 0; i < references.Count; i++) { var reference = references[i]; var node = (TagHelperPropertyIntermediateNode)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)); } } }
protected override void ExecuteCore(RazorCodeDocument codeDocument, DocumentIntermediateNode documentNode) { 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; } string generatedTypeName = $"{@namespace.Content}.{@class.ClassName}"; string templateKey = codeDocument.Source.FilePath; string escapedTemplateKey = EscapeAsVerbatimLiteral(templateKey); string attribute; if (documentNode.DocumentKind == MvcViewDocumentClassifierPass2.MvcViewDocumentKind) { attribute = $"[assembly:{assemblyTemplateAttribute}({escapedTemplateKey}, typeof({generatedTypeName}))]"; } else { return; } int index = documentNode.Children.IndexOf(@namespace); var pageAttribute = new CSharpCodeIntermediateNode(); pageAttribute.Children.Add(new IntermediateToken() { Kind = TokenKind.CSharp, Content = attribute, }); documentNode.Children.Insert(index, pageAttribute); }
public void Execute_NoIdentifier_Noops() { // Arrange var engine = CreateEngine(); var pass = new MetadataAttributePass() { Engine = engine, }; var sourceDocument = TestRazorSourceDocument.Create(); var codeDocument = RazorCodeDocument.Create(sourceDocument); var irDocument = new DocumentIntermediateNode() { DocumentKind = "test", Options = RazorCodeGenerationOptions.Create((o) => { }), }; var builder = IntermediateNodeBuilder.Create(irDocument); var @namespace = new NamespaceDeclarationIntermediateNode { Annotations = { [CommonAnnotations.PrimaryNamespace] = CommonAnnotations.PrimaryNamespace, }, Content = "Some.Namespace" }; builder.Push(@namespace); var @class = new ClassDeclarationIntermediateNode { Annotations = { [CommonAnnotations.PrimaryClass] = CommonAnnotations.PrimaryClass, }, ClassName = "Test", }; builder.Add(@class); // Act pass.Execute(codeDocument, irDocument); // Assert SingleChild <NamespaceDeclarationIntermediateNode>(irDocument); }
private void Rewrite(RazorCodeDocument codeDocument, DocumentIntermediateNode documentNode) { // Rewrite the document from a flat structure to use a sensible default structure, // a namespace and class declaration with a single 'razor' method. var children = new List <IntermediateNode>(documentNode.Children); documentNode.Children.Clear(); var @namespace = new NamespaceDeclarationIntermediateNode(); @namespace.Annotations[CommonAnnotations.PrimaryNamespace] = CommonAnnotations.PrimaryNamespace; var @class = new ClassDeclarationIntermediateNode(); @class.Annotations[CommonAnnotations.PrimaryClass] = CommonAnnotations.PrimaryClass; var method = new MethodDeclarationIntermediateNode(); method.Annotations[CommonAnnotations.PrimaryMethod] = CommonAnnotations.PrimaryMethod; var documentBuilder = IntermediateNodeBuilder.Create(documentNode); var namespaceBuilder = IntermediateNodeBuilder.Create(documentBuilder.Current); namespaceBuilder.Push(@namespace); var classBuilder = IntermediateNodeBuilder.Create(namespaceBuilder.Current); classBuilder.Push(@class); var methodBuilder = IntermediateNodeBuilder.Create(classBuilder.Current); methodBuilder.Push(method); var visitor = new Visitor(documentBuilder, namespaceBuilder, classBuilder, methodBuilder); for (var i = 0; i < children.Count; i++) { visitor.Visit(children[i]); } // Note that this is called at the *end* of rewriting so that user code can see the tree // and look at its content to make a decision. OnDocumentStructureCreated(codeDocument, @namespace, @class, method); }
protected override void ExecuteCore(RazorCodeDocument codeDocument, DocumentIntermediateNode documentNode) { 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; 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(BlazorDiagnosticFactory.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)); } } }
protected override void ExecuteCore(RazorCodeDocument codeDocument, DocumentIntermediateNode documentNode) { if (documentNode.Options.DesignTime) { return; } var walker = new Visitor(); walker.VisitDocument(documentNode); for (var i = 0; i < walker.Items.Count; i++) { var node = walker.Items[i]; AddInstrumentation(node); } }
public void FindPrimaryNamespace_FindsNamespaceWithAnnotation() { // Arrange var document = new DocumentIntermediateNode(); var @namespace = new NamespaceDeclarationIntermediateNode(); @namespace.Annotations[CommonAnnotations.PrimaryNamespace] = CommonAnnotations.PrimaryNamespace; var builder = IntermediateNodeBuilder.Create(document); builder.Add(@namespace); // Act var result = document.FindPrimaryNamespace(); // Assert Assert.Same(@namespace, result); }
public void FindPrimaryMethod_FindsMethodWithAnnotation() { // Arrange var document = new DocumentIntermediateNode(); var method = new MethodDeclarationIntermediateNode(); method.Annotations[CommonAnnotations.PrimaryMethod] = CommonAnnotations.PrimaryMethod; var builder = IntermediateNodeBuilder.Create(document); builder.Add(method); // Act var result = document.FindPrimaryMethod(); // Assert Assert.Same(method, result); }
public void FindPrimaryClass_FindsClassWithAnnotation() { // Arrange var document = new DocumentIntermediateNode(); var @class = new ClassDeclarationIntermediateNode(); @class.Annotations[CommonAnnotations.PrimaryClass] = CommonAnnotations.PrimaryClass; var builder = IntermediateNodeBuilder.Create(document); builder.Add(@class); // Act var result = document.FindPrimaryClass(); // Assert Assert.Same(@class, result); }
public void TryComputeNamespace_OverrideImportsNamespaceDirective() { // Arrange var sourceDocument = TestRazorSourceDocument.Create( content: "@namespace My.Custom.OverrideNS", filePath: "C:\\Hello\\Components\\Test.cshtml", relativePath: "\\Components\\Test.cshtml"); var codeDocument = TestRazorCodeDocument.Create(sourceDocument, Array.Empty <RazorSourceDocument>()); codeDocument.SetFileKind(FileKinds.Component); codeDocument.SetSyntaxTree(RazorSyntaxTree.Parse(sourceDocument, RazorParserOptions.Create(options => { options.Directives.Add(NamespaceDirective.Directive); }))); var importSourceDocument = TestRazorSourceDocument.Create( content: "@namespace My.Custom.NS", filePath: "C:\\Hello\\_Imports.razor", relativePath: "\\_Imports.razor"); codeDocument.SetImportSyntaxTrees(new[] { RazorSyntaxTree.Parse(importSourceDocument, RazorParserOptions.Create(options => { options.Directives.Add(NamespaceDirective.Directive); })) }); var documentNode = new DocumentIntermediateNode() { Options = RazorCodeGenerationOptions.Create(c => { c.RootNamespace = "Hello.World"; }) }; codeDocument.SetDocumentIntermediateNode(documentNode); // Act codeDocument.TryComputeNamespace(fallbackToRootNamespace: true, out var @namespace); // Assert Assert.Equal("My.Custom.OverrideNS", @namespace); }
protected override void ExecuteCore(RazorCodeDocument codeDocument, DocumentIntermediateNode documentNode) { if (documentNode.DocumentKind != RazorPageDocumentClassifierPass.RazorPageDocumentKind && documentNode.DocumentKind != MvcViewDocumentClassifierPass.MvcViewDocumentKind) { // Not a MVC file. Skip. return; } var visitor = new Visitor(); visitor.Visit(documentNode); var modelType = ModelDirective.GetModelType(documentNode); var properties = new HashSet <string>(StringComparer.Ordinal); for (var i = visitor.Directives.Count - 1; i >= 0; i--) { var directive = visitor.Directives[i]; var tokens = directive.Tokens.ToArray(); if (tokens.Length < 2) { continue; } var typeName = tokens[0].Content; var memberName = tokens[1].Content; if (!properties.Add(memberName)) { continue; } typeName = typeName.Replace("<TModel>", "<" + modelType + ">"); var injectNode = new InjectIntermediateNode() { TypeName = typeName, MemberName = memberName, }; visitor.Class.Children.Add(injectNode); } }
protected override void ExecuteCore(RazorCodeDocument codeDocument, DocumentIntermediateNode documentNode) { if (codeDocument == null) { throw new ArgumentNullException(nameof(codeDocument)); } if (documentNode == null) { throw new ArgumentNullException(nameof(documentNode)); } if (!IsComponentDocument(documentNode)) { return; } if (documentNode.Options.SuppressPrimaryMethodBody) { // There's no benefit running the whitespace trimmer if we're not emitting // the method bodies. return; } // There's no benefit running the whitespace trimmer during design-time builds if (documentNode.Options.DesignTime) { return; } // Respect @preservewhitespace directives if (PreserveWhitespaceIsEnabled(documentNode)) { return; } var @class = documentNode.FindPrimaryClass(); if (@class != null) { var visitor = new Visitor(); visitor.Visit(@class); } }
public void WriteDocument_WritesMethod() { // Arrange var document = new DocumentIntermediateNode(); var builder = IntermediateNodeBuilder.Create(document); builder.Add(new MethodDeclarationIntermediateNode() { Modifiers = { "internal", "virtual", "async", }, MethodName = "TestMethod", ReturnType = "string", }); var codeDocument = TestRazorCodeDocument.CreateEmpty(); var options = RazorCodeGenerationOptions.CreateDefault(); var target = CodeTarget.CreateDefault(codeDocument, options); var writer = new DefaultDocumentWriter(target, options); // Act var result = writer.WriteDocument(codeDocument, document); // Assert var csharp = result.GeneratedCode; Assert.Equal( @"#pragma checksum ""test.cshtml"" ""{ff1816ec-aa5e-4d10-87f7-6f4963833460}"" ""da39a3ee5e6b4b0d3255bfef95601890afd80709"" // <auto-generated/> #pragma warning disable 1591 #pragma warning disable 1998 internal virtual async string TestMethod() { } #pragma warning restore 1998 #pragma warning restore 1591 ", csharp, ignoreLineEndingDifferences: true); }
public void Execute_NoOps_IfNamespaceNodeIsMissing() { // Arrange var irDocument = new DocumentIntermediateNode() { Options = RazorCodeGenerationOptions.CreateDefault(), }; var pass = new AssemblyAttributeInjectionPass { Engine = RazorProjectEngine.Create().Engine, }; // Act pass.Execute(TestRazorCodeDocument.CreateEmpty(), irDocument); // Assert Assert.Empty(irDocument.Children); }
protected override void ExecuteCore(RazorCodeDocument codeDocument, DocumentIntermediateNode documentNode) { var @class = documentNode.FindPrimaryClass(); if (@class == null) { return; } foreach (var inherits in documentNode.FindDirectiveReferences(InheritsDirective.Directive)) { var token = ((DirectiveIntermediateNode)inherits.Node).Tokens.FirstOrDefault(); if (token != null) { @class.BaseType = token.Content; break; } } }
public void Execute(RazorCodeDocument codeDocument, DocumentIntermediateNode documentNode) { if (codeDocument == null) { throw new ArgumentNullException(nameof(codeDocument)); } if (documentNode == null) { throw new ArgumentNullException(nameof(documentNode)); } if (Engine == null) { throw new InvalidOperationException(Resources.FormatPhaseMustBeInitialized(nameof(Engine))); } ExecuteCore(codeDocument, documentNode); }
public void WriteDocument_WritesClass() { // Arrange var document = new DocumentIntermediateNode(); var builder = IntermediateNodeBuilder.Create(document); builder.Add(new ClassDeclarationIntermediateNode() { Modifiers = { "internal" }, BaseType = "TestBase", Interfaces = new List <string> { "IFoo", "IBar", }, ClassName = "TestClass", }); var codeDocument = TestRazorCodeDocument.CreateEmpty(); var options = RazorCodeGenerationOptions.CreateDefault(); var target = CodeTarget.CreateDefault(codeDocument, options); var writer = new DefaultDocumentWriter(target, options); // Act var result = writer.WriteDocument(codeDocument, document); // Assert var csharp = result.GeneratedCode; Assert.Equal( @"#pragma checksum ""test.cshtml"" ""{ff1816ec-aa5e-4d10-87f7-6f4963833460}"" ""da39a3ee5e6b4b0d3255bfef95601890afd80709"" // <auto-generated/> #pragma warning disable 1591 internal class TestClass : TestBase, IFoo, IBar { } #pragma warning restore 1591 ", csharp, ignoreLineEndingDifferences: true); }
protected override void ExecuteCore(RazorCodeDocument codeDocument, DocumentIntermediateNode documentNode) { ClassDeclarationIntermediateNode primaryClass = documentNode.FindPrimaryClass(); if (primaryClass == null) { return; } string fullClassName = null; foreach (IntermediateNodeReference directiveReference in documentNode.FindDirectiveReferences(InheritsDirective.Directive)) { DirectiveTokenIntermediateNode intermediateNode = ((DirectiveIntermediateNode)directiveReference.Node).Tokens .FirstOrDefault <DirectiveTokenIntermediateNode>(); if (intermediateNode != null) { fullClassName = intermediateNode.Content; break; } } if (fullClassName == null) { return; } if (PartialClassMode) { var info = new TypeReferenceInfo(fullClassName); var pns = documentNode.FindPrimaryNamespace().Content = info.Namespace; primaryClass.BaseType = null; primaryClass.Modifiers.Add("partial"); primaryClass.ClassName = info.Name; } else { primaryClass.BaseType = fullClassName; } }
protected override void ExecuteCore(RazorCodeDocument codeDocument, DocumentIntermediateNode documentNode) { var @namespace = documentNode.FindPrimaryNamespace(); var @class = documentNode.FindPrimaryClass(); if (@namespace == null || @class == null) { return; } var directives = documentNode.FindDirectiveReferences(ComponentLayoutDirective.Directive); if (directives.Count == 0) { return; } var token = ((DirectiveIntermediateNode)directives[0].Node).Tokens.FirstOrDefault(); if (token == null) { return; } var attributeNode = new CSharpCodeIntermediateNode(); attributeNode.Children.Add(new IntermediateToken() { Kind = TokenKind.CSharp, Content = $"[{ComponentsApi.LayoutAttribute.FullTypeName}(typeof({token.Content}))]", }); // Insert the new attribute on top of the class for (var i = 0; i < @namespace.Children.Count; i++) { if (object.ReferenceEquals(@namespace.Children[i], @class)) { @namespace.Children.Insert(i, attributeNode); break; } } }
public void Execute_IgnoresDocumentsWithDocumentKind() { // Arrange var documentNode = new DocumentIntermediateNode() { DocumentKind = "ignore", Options = RazorCodeGenerationOptions.CreateDefault(), }; var pass = new DefaultDocumentClassifierPass(); pass.Engine = RazorProjectEngine.Create().Engine; // Act pass.Execute(TestRazorCodeDocument.CreateEmpty(), documentNode); // Assert Assert.Equal("ignore", documentNode.DocumentKind); NoChildren(documentNode); }
public void Execute_NullCodeGenerationOptions_Noops() { // Arrange var engine = CreateEngine(); var pass = new MetadataAttributePass() { Engine = engine, }; var sourceDocument = TestRazorSourceDocument.Create(); var codeDocument = RazorCodeDocument.Create(sourceDocument); var irDocument = new DocumentIntermediateNode(); // Act pass.Execute(codeDocument, irDocument); // Assert NoChildren(irDocument); }
public void Build_PopsMultipleLevels() { // Arrange var builder = new DefaultRazorIntermediateNodeBuilder(); var document = new DocumentIntermediateNode(); builder.Push(document); var node = new BasicIntermediateNode(); builder.Push(node); // Act var result = builder.Build(); // Assert Assert.Same(document, result); Assert.Null(builder.Current); }
protected override void ExecuteCore(RazorCodeDocument codeDocument, DocumentIntermediateNode documentNode) { if (documentNode.DocumentKind != RazorPageDocumentClassifierPass.RazorPageDocumentKind && documentNode.DocumentKind != MvcViewDocumentClassifierPass.MvcViewDocumentKind) { // Not a MVC file. Skip. 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; } var context = new Context(@namespace, @class); // For each VCTH *usage* we need to rewrite the tag helper node to use the tag helper runtime to construct // and set properties on the the correct field, and using the name of the type we will generate. var nodes = documentNode.FindDescendantNodes <TagHelperIntermediateNode>(); for (var i = 0; i < nodes.Count; i++) { var node = nodes[i]; foreach (var tagHelper in node.TagHelpers) { RewriteUsage(context, node, tagHelper); } } // Then for each VCTH *definition* that we've seen we need to generate the class that implements // ITagHelper and the field that will hold it. foreach (var tagHelper in context.TagHelpers) { AddField(context, tagHelper); AddTagHelperClass(context, tagHelper); } }
protected override void ExecuteCore(RazorCodeDocument codeDocument, DocumentIntermediateNode documentNode) { var visitor = new Visitor(); visitor.Visit(documentNode); foreach (var implementsNode in visitor.ImplementsNodes) { visitor.MethodNode.Children.Remove(implementsNode); } if (visitor.ClassNode.Interfaces == null) { visitor.ClassNode.Interfaces = new List <string>(); } foreach (var implementsType in visitor.ImplementsTypes) { visitor.ClassNode.Interfaces.Add(implementsType); } }
protected override void ExecuteCore(RazorCodeDocument codeDocument, DocumentIntermediateNode documentNode) { if (!IsComponentDocument(documentNode)) { return; } var cssScope = codeDocument.GetCssScope(); if (string.IsNullOrEmpty(cssScope)) { return; } var nodes = documentNode.FindDescendantNodes <MarkupElementIntermediateNode>(); for (var i = 0; i < nodes.Count; i++) { ProcessElement(nodes[i], cssScope); } }
protected sealed override void ExecuteCore(RazorCodeDocument codeDocument, DocumentIntermediateNode documentNode) { if (documentNode.DocumentKind != null) { return; } if (!IsMatch(codeDocument, documentNode)) { return; } documentNode.DocumentKind = DocumentKind; documentNode.Target = CreateTarget(codeDocument, documentNode.Options); if (documentNode.Target == null) { throw new InvalidOperationException($"{nameof(CreateTarget)} must return a non-null {nameof(CodeTarget)}."); } Rewrite(codeDocument, documentNode); }
public void TryComputeNamespace_SanitizesNamespaceName() { // Arrange var sourceDocument = TestRazorSourceDocument.Create(filePath: "C:\\Hello\\Components with space\\Test$name.cshtml", relativePath: "\\Components with space\\Test$name.cshtml"); var codeDocument = TestRazorCodeDocument.Create(sourceDocument, Array.Empty <RazorSourceDocument>()); var documentNode = new DocumentIntermediateNode() { Options = RazorCodeGenerationOptions.Create(c => { c.RootNamespace = "Hel?o.World"; }) }; codeDocument.SetDocumentIntermediateNode(documentNode); // Act codeDocument.TryComputeNamespace(fallbackToRootNamespace: true, out var @namespace); // Assert Assert.Equal("Hel_o.World.Components_with_space", @namespace); }
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(); } }
public void InstrumentationPass_SkipsTagHelper_WithoutLocation() { // Arrange var document = new DocumentIntermediateNode(); var builder = IntermediateNodeBuilder.Create(document); builder.Push(new TagHelperIntermediateNode()); var pass = new InstrumentationPass() { Engine = RazorEngine.CreateEmpty(b => { }), }; // Act pass.Execute(TestRazorCodeDocument.CreateEmpty(), document); // Assert Children( document, n => Assert.IsType <TagHelperIntermediateNode>(n)); }
public void Execute_NoMatch_IgnoresDocument() { // Arrange var documentNode = new DocumentIntermediateNode() { Options = RazorCodeGenerationOptions.CreateDefault(), }; var pass = new TestDocumentClassifierPass() { Engine = Engine, ShouldMatch = false, }; // Act pass.Execute(TestRazorCodeDocument.CreateEmpty(), documentNode); // Assert Assert.Null(documentNode.DocumentKind); NoChildren(documentNode); }