private static DocumentIntermediateNode Lower(RazorCodeDocument codeDocument, RazorProjectEngine projectEngine) { for (var i = 0; i < projectEngine.Phases.Count; i++) { var phase = projectEngine.Phases[i]; phase.Execute(codeDocument); if (phase is IRazorDocumentClassifierPhase) { break; } } var irDocument = codeDocument.GetDocumentIntermediateNode(); Assert.NotNull(irDocument); return(irDocument); }
private DocumentIntermediateNode Lower(RazorCodeDocument codeDocument, RazorProjectEngine projectEngine) { for (var i = 0; i < projectEngine.Phases.Count; i++) { var phase = projectEngine.Phases[i]; phase.Execute(codeDocument); if (phase is IRazorIntermediateNodeLoweringPhase) { break; } } var documentNode = codeDocument.GetDocumentIntermediateNode(); Assert.NotNull(documentNode); return(documentNode); }
private DocumentIntermediateNode Lower(RazorCodeDocument codeDocument) { for (var i = 0; i < Engine.Phases.Count; i++) { var phase = Engine.Phases[i]; if (phase is IRazorCSharpLoweringPhase) { break; } phase.Execute(codeDocument); } var document = codeDocument.GetDocumentIntermediateNode(); Engine.Features.OfType <ComponentDocumentClassifierPass>().Single().Execute(codeDocument, document); Engine.Features.OfType <ComponentMarkupDiagnosticPass>().Single().Execute(codeDocument, document); return(document); }
private static DocumentIntermediateNode Lower(RazorCodeDocument codeDocument, RazorEngine engine) { for (var i = 0; i < engine.Phases.Count; i++) { var phase = engine.Phases[i]; phase.Execute(codeDocument); if (phase is IRazorDirectiveClassifierPhase) { break; } } var documentNode = codeDocument.GetDocumentIntermediateNode(); Assert.NotNull(documentNode); return(documentNode); }
protected override void HandleMatchedContent(RazorCodeDocument codeDocument, IEnumerable <string> matchedContent) { var chosenLayoutType = matchedContent.Last(); var attributeNode = new CSharpCodeIntermediateNode(); attributeNode.Children.Add(new IntermediateToken() { Kind = TokenKind.CSharp, Content = $"[{LayoutAttributeTypeName}(typeof ({chosenLayoutType}))]" + Environment.NewLine, }); var docNode = codeDocument.GetDocumentIntermediateNode(); var namespaceNode = docNode.FindPrimaryNamespace(); var classNode = docNode.FindPrimaryClass(); var classNodeIndex = namespaceNode .Children .IndexOf(classNode); namespaceNode.Children.Insert(classNodeIndex, attributeNode); }
protected override void ExecuteCore(RazorCodeDocument codeDocument) { var documentNode = codeDocument.GetDocumentIntermediateNode(); ThrowForMissingDocumentDependency(documentNode); #pragma warning disable CS0618 var writer = new DocumentWriterWorkaround().Create(documentNode.Target, documentNode.Options); #pragma warning restore CS0618 try { var cSharpDocument = writer.WriteDocument(codeDocument, documentNode); codeDocument.SetCSharpDocument(cSharpDocument); } catch (RazorCompilerException ex) { // Currently the Blazor code generation has some 'fatal errors' that can cause code generation // to fail completely. This class is here to make that implementation work gracefully. var cSharpDocument = RazorCSharpDocument.Create("", documentNode.Options, new[] { ex.Diagnostic }); codeDocument.SetCSharpDocument(cSharpDocument); } }
protected override void OnDocumentStructureCreated( RazorCodeDocument codeDocument, NamespaceDeclarationIntermediateNode @namespace, ClassDeclarationIntermediateNode @class, MethodDeclarationIntermediateNode method) { var documentNode = codeDocument.GetDocumentIntermediateNode(); foreach (var @using in @namespace.Children.OfType <UsingDirectiveIntermediateNode>()) { documentNode.Children.Add(@using); } foreach (var child in method.Children) { documentNode.Children.Add(child); } documentNode.Children.Remove(@namespace); return; }
protected override void ExecuteCore(RazorCodeDocument codeDocument) { var documentNode = codeDocument.GetDocumentIntermediateNode(); ThrowForMissingDocumentDependency(documentNode); var target = documentNode.Target; if (target == null) { var message = Resources.FormatDocumentMissingTarget( documentNode.DocumentKind, nameof(CodeTarget), nameof(DocumentIntermediateNode.Target)); throw new InvalidOperationException(message); } var writer = DocumentWriter.CreateDefault(documentNode.Target, documentNode.Options); var cSharpDocument = writer.WriteDocument(codeDocument, documentNode); codeDocument.SetCSharpDocument(cSharpDocument); }
private DocumentIntermediateNode Lower( RazorCodeDocument codeDocument, Action <RazorProjectEngineBuilder> builder = null, IEnumerable <TagHelperDescriptor> tagHelpers = null, bool designTime = false) { tagHelpers = tagHelpers ?? new TagHelperDescriptor[0]; Action <RazorProjectEngineBuilder> configureEngine = b => { builder?.Invoke(b); SectionDirective.Register(b); b.AddTagHelpers(tagHelpers); b.Features.Add(new DesignTimeOptionsFeature(designTime)); }; var projectEngine = RazorProjectEngine.Create(configureEngine); for (var i = 0; i < projectEngine.Phases.Count; i++) { var phase = projectEngine.Phases[i]; phase.Execute(codeDocument); if (phase is IRazorIntermediateNodeLoweringPhase) { break; } } var documentNode = codeDocument.GetDocumentIntermediateNode(); Assert.NotNull(documentNode); return(documentNode); }
/// <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; var typeParamReferences = documentNode.FindDirectiveReferences(ComponentTypeParamDirective.Directive); 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, }); } }
/// <inheritdoc /> protected override void OnDocumentStructureCreated( RazorCodeDocument codeDocument, NamespaceDeclarationIntermediateNode @namespace, ClassDeclarationIntermediateNode @class, MethodDeclarationIntermediateNode method) { if (!TryComputeNamespaceAndClass( codeDocument.Source.FilePath, codeDocument.Source.RelativePath, out var computedNamespace, out var computedClass)) { // If we can't compute a nice namespace (no relative path) then just generate something // mangled. computedNamespace = BaseNamespace; computedClass = CSharpIdentifier.GetClassNameFromPath(codeDocument.Source.FilePath) ?? "__BlazorComponent"; } if (MangleClassNames) { computedClass = "__" + computedClass; } @namespace.Content = computedNamespace; @class.BaseType = ComponentsApi.ComponentBase.FullTypeName; @class.ClassName = computedClass; @class.Modifiers.Clear(); @class.Modifiers.Add("public"); var documentNode = codeDocument.GetDocumentIntermediateNode(); var typeParamReferences = documentNode.FindDirectiveReferences(TypeParamDirective.Directive); 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, }); } 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 = "builder", TypeName = ComponentsApi.RenderTreeBuilder.FullTypeName, }); // We need to call the 'base' method as the first statement. var callBase = new CSharpCodeIntermediateNode(); callBase.Annotations.Add(BuildRenderTreeBaseCallAnnotation, true); callBase.Children.Add(new IntermediateToken { Kind = TokenKind.CSharp, Content = $"base.{ComponentsApi.ComponentBase.BuildRenderTree}(builder);" }); method.Children.Insert(0, callBase); }
// In general documents will have a relative path (relative to the project root). // We can only really compute a nice namespace when we know a relative path. // // However all kinds of thing are possible in tools. We shouldn't barf here if the document isn't // set up correctly. public static bool TryComputeNamespace(this RazorCodeDocument document, bool fallbackToRootNamespace, out string @namespace) { if (document == null) { throw new ArgumentNullException(nameof(document)); } var filePath = document.Source.FilePath; if (filePath == null || document.Source.RelativePath == null || filePath.Length < document.Source.RelativePath.Length) { @namespace = null; return(false); } // If the document or it's imports contains a @namespace directive, we want to use that over the root namespace. var baseNamespace = string.Empty; var appendSuffix = true; var lastNamespaceContent = string.Empty; var lastNamespaceLocation = SourceSpan.Undefined; var importSyntaxTrees = document.GetImportSyntaxTrees(); if (importSyntaxTrees != null) { // ImportSyntaxTrees is usually set. Just being defensive. foreach (var importSyntaxTree in importSyntaxTrees) { if (importSyntaxTree != null && NamespaceVisitor.TryGetLastNamespaceDirective(importSyntaxTree, out var importNamespaceContent, out var importNamespaceLocation)) { lastNamespaceContent = importNamespaceContent; lastNamespaceLocation = importNamespaceLocation; } } } var syntaxTree = document.GetSyntaxTree(); if (syntaxTree != null && NamespaceVisitor.TryGetLastNamespaceDirective(syntaxTree, out var namespaceContent, out var namespaceLocation)) { lastNamespaceContent = namespaceContent; lastNamespaceLocation = namespaceLocation; } StringSegment relativePath = document.Source.RelativePath; // If there are multiple @namespace directives in the heirarchy, // we want to pick the closest one to the current document. if (!string.IsNullOrEmpty(lastNamespaceContent)) { baseNamespace = lastNamespaceContent; var directiveLocationDirectory = NormalizeDirectory(lastNamespaceLocation.FilePath); var sourceFilePath = new StringSegment(document.Source.FilePath); // We're specifically using OrdinalIgnoreCase here because Razor treats all paths as case-insensitive. if (!sourceFilePath.StartsWith(directiveLocationDirectory, StringComparison.OrdinalIgnoreCase) || sourceFilePath.Length <= directiveLocationDirectory.Length) { // The most relevant directive is not from the directory hierarchy, can't compute a suffix. appendSuffix = false; } else { // We know that the document containing the namespace directive is in the current document's heirarchy. // Let's compute the actual relative path that we'll use to compute the namespace suffix. relativePath = sourceFilePath.Subsegment(directiveLocationDirectory.Length); } } else if (fallbackToRootNamespace) { var options = document.GetCodeGenerationOptions() ?? document.GetDocumentIntermediateNode()?.Options; baseNamespace = options?.RootNamespace; appendSuffix = true; } if (string.IsNullOrEmpty(baseNamespace)) { // There was no valid @namespace directive and we couldn't compute the RootNamespace. @namespace = null; return(false); } var builder = new StringBuilder(); // Sanitize the base namespace, but leave the dots. var segments = new StringTokenizer(baseNamespace, NamespaceSeparators); var first = true; foreach (var token in segments) { if (token.IsEmpty) { continue; } if (first) { first = false; } else { builder.Append('.'); } CSharpIdentifier.AppendSanitized(builder, token); } if (appendSuffix) { // If we get here, we already have a base namespace and the relative path that should be used as the namespace suffix. segments = new StringTokenizer(relativePath, PathSeparators); var previousLength = builder.Length; foreach (var token in segments) { if (token.IsEmpty) { continue; } previousLength = builder.Length; builder.Append('.'); CSharpIdentifier.AppendSanitized(builder, token); } // Trim the last segment because it's the FileName. builder.Length = previousLength; } @namespace = builder.ToString(); return(true); // We want to normalize the path of the file containing the '@namespace' directive to just the containing // directory with a trailing separator. // // Not using Path.GetDirectoryName here because it doesn't meet these requirements, and we want to handle // both 'view engine' style paths and absolute paths. // // We also don't normalize the separators here. We expect that all documents are using a consistent style of path. // // If we can't normalize the path, we just return null so it will be ignored. StringSegment NormalizeDirectory(string path) { if (string.IsNullOrEmpty(path)) { return(default);
protected override void OnDocumentStructureCreated(RazorCodeDocument codeDocument, NamespaceDeclarationIntermediateNode @namespace, ClassDeclarationIntermediateNode @class, MethodDeclarationIntermediateNode method) { base.OnDocumentStructureCreated(codeDocument, @namespace, @class, method); if (!TryComputeNamespaceAndClass( codeDocument.Source.FilePath, codeDocument.Source.RelativePath, out var computedNamespace, out var computedClass)) { // If we can't compute a nice namespace (no relative path) then just generate something // mangled. computedNamespace = "AspNetCore"; var checksum = Checksum.BytesToString(codeDocument.Source.GetChecksum()); computedClass = $"AspNetCore_{checksum}"; } @namespace.Content = computedNamespace; @class.ClassName = computedClass; @class.BaseType = $"{CodeGenerationConstants.ComponentBase.FullTypeName}"; var filePath = codeDocument.Source.RelativePath ?? codeDocument.Source.FilePath; if (string.IsNullOrEmpty(filePath)) { // It's possible for a Razor document to not have a file path. // Eg. When we try to generate code for an in memory document like default imports. var checksum = Checksum.BytesToString(codeDocument.Source.GetChecksum()); @class.ClassName = $"AspNetCore_{checksum}"; } else { @class.ClassName = CSharpIdentifier.SanitizeIdentifier(Path.GetFileNameWithoutExtension(filePath)); } @class.Modifiers.Clear(); @class.Modifiers.Add("public"); var documentNode = codeDocument.GetDocumentIntermediateNode(); var typeParamReferences = documentNode.FindDirectiveReferences(ComponentTypeParamDirective.Directive); 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, }); } method.MethodName = CodeGenerationConstants.ComponentBase.BuildRenderTree; method.ReturnType = "void"; method.Modifiers.Clear(); method.Modifiers.Add("protected"); method.Modifiers.Add("override"); method.Parameters.Clear(); method.Parameters.Add(new MethodParameter() { TypeName = CodeGenerationConstants.RenderTreeBuilder.FullTypeName, ParameterName = CodeGenerationConstants.ComponentBase.BuildRenderTreeParameter, }); // We need to call the 'base' method as the first statement. var callBase = new CSharpCodeIntermediateNode(); callBase.Annotations.Add(BuildRenderTreeBaseCallAnnotation, true); callBase.Children.Add(new IntermediateToken { Kind = TokenKind.CSharp, Content = $"base.{CodeGenerationConstants.ComponentBase.BuildRenderTree}({CodeGenerationConstants.ComponentBase.BuildRenderTreeParameter});" }); method.Children.Insert(0, callBase); }