// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Collections.Generic; using System.Linq; using Microsoft.AspNet.Razor; using Microsoft.AspNet.Razor.Generator.Compiler; using Microsoft.AspNet.Razor.Parser; using Microsoft.AspNet.Razor.Parser.SyntaxTree; using Microsoft.AspNet.Razor.Parser.TagHelpers; using Microsoft.AspNet.Razor.TagHelpers; using Microsoft.Framework.Internal; namespace Microsoft.AspNet.Mvc.Razor { /// /// A subtype of that uses to support inheritance of tag /// helpers from _GlobalImport files. /// public class MvcRazorParser : RazorParser { private readonly IEnumerable _globalImportDirectiveDescriptors; /// /// Initializes a new instance of . /// /// The to copy properties from. /// The s that are inherited /// from parsed pages from _GlobalImport files. /// The inherited by /// default by all Razor pages in the application. public MvcRazorParser( [NotNull] RazorParser parser, [NotNull] IReadOnlyList inheritedCodeTrees, [NotNull] IReadOnlyList defaultInheritedChunks) : base(parser) { // Construct tag helper descriptors from @addTagHelper, @removeTagHelper and @tagHelperPrefix chunks _globalImportDirectiveDescriptors = GetTagHelperDirectiveDescriptors( inheritedCodeTrees, defaultInheritedChunks); } /// protected override IEnumerable GetTagHelperDescriptors( [NotNull] Block documentRoot, [NotNull] ErrorSink errorSink) { var visitor = new GlobalImportTagHelperDirectiveSpanVisitor( TagHelperDescriptorResolver, _globalImportDirectiveDescriptors, errorSink); return visitor.GetDescriptors(documentRoot); } private static IEnumerable GetTagHelperDirectiveDescriptors( IReadOnlyList inheritedCodeTrees, IReadOnlyList defaultInheritedChunks) { var descriptors = new List(); // For tag helpers, the @removeTagHelper only applies tag helpers that were added prior to it. // Consequently we must visit tag helpers outside-in - furthest _GlobalImport first and nearest one last. // This is different from the behavior of chunk merging where we visit the nearest one first and ignore // chunks that were previously visited. var chunksFromGlobalImports = inheritedCodeTrees .Reverse() .SelectMany(tree => tree.Chunks); var chunksInOrder = defaultInheritedChunks.Concat(chunksFromGlobalImports); foreach (var chunk in chunksInOrder) { // All TagHelperDirectiveDescriptors created here have undefined source locations because the source // that created them is not in the same file. var addTagHelperChunk = chunk as AddTagHelperChunk; if (addTagHelperChunk != null) { var descriptor = new TagHelperDirectiveDescriptor( addTagHelperChunk.LookupText, chunk.Start, TagHelperDirectiveType.AddTagHelper); descriptors.Add(descriptor); continue; } var removeTagHelperChunk = chunk as RemoveTagHelperChunk; if (removeTagHelperChunk != null) { var descriptor = new TagHelperDirectiveDescriptor( removeTagHelperChunk.LookupText, chunk.Start, TagHelperDirectiveType.RemoveTagHelper); descriptors.Add(descriptor); continue; } var tagHelperPrefixDirectiveChunk = chunk as TagHelperPrefixDirectiveChunk; if (tagHelperPrefixDirectiveChunk != null) { var descriptor = new TagHelperDirectiveDescriptor( tagHelperPrefixDirectiveChunk.Prefix, chunk.Start, TagHelperDirectiveType.TagHelperPrefix); descriptors.Add(descriptor); } } return descriptors; } private class GlobalImportTagHelperDirectiveSpanVisitor : TagHelperDirectiveSpanVisitor { private readonly IEnumerable _globalImportDirectiveDescriptors; public GlobalImportTagHelperDirectiveSpanVisitor( ITagHelperDescriptorResolver descriptorResolver, IEnumerable globalImportDirectiveDescriptors, ErrorSink errorSink) : base(descriptorResolver, errorSink) { _globalImportDirectiveDescriptors = globalImportDirectiveDescriptors; } protected override TagHelperDescriptorResolutionContext GetTagHelperDescriptorResolutionContext( IEnumerable descriptors, ErrorSink errorSink) { var directivesToImport = MergeDirectiveDescriptors(descriptors, _globalImportDirectiveDescriptors); return base.GetTagHelperDescriptorResolutionContext(directivesToImport, errorSink); } private static IEnumerable MergeDirectiveDescriptors( IEnumerable descriptors, IEnumerable inheritedDescriptors) { var mergedDescriptors = new List(); TagHelperDirectiveDescriptor prefixDirectiveDescriptor = null; foreach (var descriptor in inheritedDescriptors) { if (descriptor.DirectiveType == TagHelperDirectiveType.TagHelperPrefix) { // Always take the latest @tagHelperPrefix descriptor. Can only have 1 per page. prefixDirectiveDescriptor = descriptor; } else { mergedDescriptors.Add(descriptor); } } // We need to see if the provided descriptors contain a @tagHelperPrefix directive. If so, it // takes precedence and overrides any provided by the inheritedDescriptors. If not we need to add the // inherited @tagHelperPrefix directive back into the merged list. if (prefixDirectiveDescriptor != null && !descriptors.Any(descriptor => descriptor.DirectiveType == TagHelperDirectiveType.TagHelperPrefix)) { mergedDescriptors.Add(prefixDirectiveDescriptor); } mergedDescriptors.AddRange(descriptors); return mergedDescriptors; } } } }