Пример #1
0
        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);
        }
Пример #3
0
        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);
    }
Пример #5
0
        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);
        }
Пример #6
0
        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);
            }
        }
Пример #7
0
        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,
                });
            }
        }
Пример #11
0
        /// <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);
        }
Пример #12
0
    // 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);
        }