コード例 #1
0
        /// <summary>
        /// Visits each line in the document's imports (in order), followed by
        /// each line in the document's primary syntax tree.
        /// </summary>
        public void Visit(RazorCodeDocument codeDocument)
        {
            foreach (var import in codeDocument.GetImportSyntaxTrees())
            {
                VisitSyntaxTree(import);
            }

            VisitSyntaxTree(codeDocument.GetSyntaxTree());
        }
コード例 #2
0
    protected override void ExecuteCore(RazorCodeDocument codeDocument)
    {
        var syntaxTree = codeDocument.GetSyntaxTree();

        ThrowForMissingDocumentDependency(syntaxTree);

        var descriptors = codeDocument.GetTagHelpers();

        if (descriptors == null)
        {
            var feature = Engine.GetFeature <ITagHelperFeature>();
            if (feature == null)
            {
                // No feature, nothing to do.
                return;
            }

            descriptors = feature.GetDescriptors();
        }

        var parserOptions = codeDocument.GetParserOptions();

        // We need to find directives in all of the *imports* as well as in the main razor file
        //
        // The imports come logically before the main razor file and are in the order they
        // should be processed.
        DirectiveVisitor visitor;

        if (FileKinds.IsComponent(codeDocument.GetFileKind()) &&
            (parserOptions == null || parserOptions.FeatureFlags.AllowComponentFileKind))
        {
            codeDocument.TryComputeNamespace(fallbackToRootNamespace: true, out var currentNamespace);
            visitor = new ComponentDirectiveVisitor(codeDocument.Source.FilePath, descriptors, currentNamespace);
        }
        else
        {
            visitor = new TagHelperDirectiveVisitor(descriptors);
        }
        var imports = codeDocument.GetImportSyntaxTrees();

        if (imports != null)
        {
            for (var i = 0; i < imports.Count; i++)
            {
                var import = imports[i];
                visitor.Visit(import);
            }
        }

        visitor.Visit(syntaxTree);

        // This will always be null for a component document.
        var tagHelperPrefix = visitor.TagHelperPrefix;

        descriptors = visitor.Matches.ToArray();

        var context = TagHelperDocumentContext.Create(tagHelperPrefix, descriptors);

        codeDocument.SetTagHelperContext(context);

        if (descriptors.Count == 0)
        {
            // No descriptors, no-op.
            return;
        }

        var rewrittenSyntaxTree = TagHelperParseTreeRewriter.Rewrite(syntaxTree, tagHelperPrefix, descriptors);

        codeDocument.SetSyntaxTree(rewrittenSyntaxTree);
    }
コード例 #3
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);