diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/DefaultRazorIRLoweringPhase.cs b/src/Microsoft.AspNetCore.Razor.Evolution/DefaultRazorIRLoweringPhase.cs index 58bfabba4f..eb807681c3 100644 --- a/src/Microsoft.AspNetCore.Razor.Evolution/DefaultRazorIRLoweringPhase.cs +++ b/src/Microsoft.AspNetCore.Razor.Evolution/DefaultRazorIRLoweringPhase.cs @@ -16,13 +16,13 @@ namespace Microsoft.AspNetCore.Razor.Evolution var syntaxTree = codeDocument.GetSyntaxTree(); ThrowForMissingDependency(syntaxTree); - var visitor = new Visitor(codeDocument, syntaxTree.Options); + var builder = RazorIRBuilder.Document(); + var namespaces = new HashSet(); var i = 0; - var builder = visitor.Builder; foreach (var namespaceImport in syntaxTree.Options.NamespaceImports) { - if (visitor.Namespaces.Add(namespaceImport)) + if (namespaces.Add(namespaceImport)) { var @using = new UsingStatementIRNode() { @@ -34,35 +34,158 @@ namespace Microsoft.AspNetCore.Razor.Evolution } var checksum = ChecksumIRNode.Create(codeDocument.Source); - visitor.Builder.Insert(0, checksum); + builder.Insert(0, checksum); + + // The import documents should be inserted logically before the main document. + var imports = codeDocument.GetImportSyntaxTrees(); + if (imports != null) + { + var importsVisitor = new ImportsVisitor(builder, namespaces); + + for (var j = 0; j < imports.Count; j++) + { + var import = imports[j]; + + importsVisitor.Filename = import.Source.Filename; + importsVisitor.VisitBlock(import.Root); + } + } + + var visitor = new MainSourceVisitor(builder, namespaces) + { + Filename = syntaxTree.Source.Filename, + }; visitor.VisitBlock(syntaxTree.Root); - - var irDocument = (DocumentIRNode)visitor.Builder.Build(); + var irDocument = (DocumentIRNode)builder.Build(); codeDocument.SetIRDocument(irDocument); } - private class Visitor : ParserVisitor + private class LoweringVisitor : ParserVisitor { - private readonly RazorParserOptions _options; - private readonly RazorCodeDocument _codeDocument; + protected readonly RazorIRBuilder _builder; + protected readonly HashSet _namespaces; - private DeclareTagHelperFieldsIRNode _tagHelperFields; - - public Visitor(RazorCodeDocument codeDocument, RazorParserOptions options) + public LoweringVisitor(RazorIRBuilder builder, HashSet namespaces) { - _codeDocument = codeDocument; - _options = options; - - Namespaces = new HashSet(); - - Builder = RazorIRBuilder.Document(); + _builder = builder; + _namespaces = namespaces; } - public RazorIRBuilder Builder { get; } + public string Filename { get; set; } - public HashSet Namespaces { get; } + public override void VisitImportSpan(AddImportChunkGenerator chunkGenerator, Span span) + { + var namespaceImport = chunkGenerator.Namespace.Trim(); + + // Track seen namespaces so we don't add duplicates from options. + if (_namespaces.Add(namespaceImport)) + { + _builder.Add(new UsingStatementIRNode() + { + Content = namespaceImport, + Source = BuildSourceSpanFromNode(span), + }); + } + } + + public override void VisitDirectiveToken(DirectiveTokenChunkGenerator chunkGenerator, Span span) + { + _builder.Add(new DirectiveTokenIRNode() + { + Content = span.Content, + Descriptor = chunkGenerator.Descriptor, + Source = BuildSourceSpanFromNode(span), + }); + } + + public override void VisitStartDirectiveBlock(DirectiveChunkGenerator chunkGenerator, Block block) + { + _builder.Push(new DirectiveIRNode() + { + Name = chunkGenerator.Descriptor.Name, + Descriptor = chunkGenerator.Descriptor, + }); + } + + public override void VisitEndDirectiveBlock(DirectiveChunkGenerator chunkGenerator, Block block) + { + _builder.Pop(); + } + + public override void VisitAddTagHelperSpan(AddTagHelperChunkGenerator chunkGenerator, Span span) + { + } + + public override void VisitRemoveTagHelperSpan(RemoveTagHelperChunkGenerator chunkGenerator, Span span) + { + } + + public override void VisitTagHelperPrefixDirectiveSpan(TagHelperPrefixDirectiveChunkGenerator chunkGenerator, Span span) + { + } + + protected SourceSpan BuildSourceSpanFromNode(SyntaxTreeNode node) + { + var location = node.Start; + var sourceRange = new SourceSpan( + node.Start.FilePath ?? Filename, + node.Start.AbsoluteIndex, + node.Start.LineIndex, + node.Start.CharacterIndex, + node.Length); + return sourceRange; + } + } + + private class ImportsVisitor : LoweringVisitor + { + // Imports only supports usings and single-line directives. We only want to include directive tokens + // when we're inside a single line directive. Also single line directives can't nest which makes + // this simple. + private bool _insideLineDirective; + + public ImportsVisitor(RazorIRBuilder builder, HashSet namespaces) + : base(builder, namespaces) + { + } + + public override void VisitDirectiveToken(DirectiveTokenChunkGenerator chunkGenerator, Span span) + { + if (_insideLineDirective) + { + base.VisitDirectiveToken(chunkGenerator, span); + } + } + + public override void VisitStartDirectiveBlock(DirectiveChunkGenerator chunkGenerator, Block block) + { + if (chunkGenerator.Descriptor.Kind == DirectiveDescriptorKind.SingleLine) + { + _insideLineDirective = true; + base.VisitStartDirectiveBlock(chunkGenerator, block); + } + } + + public override void VisitEndDirectiveBlock(DirectiveChunkGenerator chunkGenerator, Block block) + { + if (_insideLineDirective) + { + _insideLineDirective = false; + base.VisitEndDirectiveBlock(chunkGenerator, block); + } + } + } + + private class MainSourceVisitor : LoweringVisitor + { + private DeclareTagHelperFieldsIRNode _tagHelperFields; + + public MainSourceVisitor(RazorIRBuilder builder, HashSet namespaces) + : base(builder, namespaces) + { + } // Example // @@ -71,18 +194,18 @@ namespace Microsoft.AspNetCore.Razor.Evolution // Suffix=" public override void VisitStartAttributeBlock(AttributeBlockChunkGenerator chunkGenerator, Block block) { - Builder.Push(new HtmlAttributeIRNode() + _builder.Push(new HtmlAttributeIRNode() { Name = chunkGenerator.Name, Prefix = chunkGenerator.Prefix, Suffix = chunkGenerator.Suffix, - Source = BuildSourceRangeFromNode(block), + Source = BuildSourceSpanFromNode(block), }); } public override void VisitEndAttributeBlock(AttributeBlockChunkGenerator chunkGenerator, Block block) { - Builder.Pop(); + _builder.Pop(); } // Example @@ -91,36 +214,36 @@ namespace Microsoft.AspNetCore.Razor.Evolution // Children will contain a token for @false. public override void VisitStartDynamicAttributeBlock(DynamicAttributeBlockChunkGenerator chunkGenerator, Block block) { - Builder.Push(new CSharpAttributeValueIRNode() + _builder.Push(new CSharpAttributeValueIRNode() { Prefix = chunkGenerator.Prefix, - Source = BuildSourceRangeFromNode(block), + Source = BuildSourceSpanFromNode(block), }); } public override void VisitEndDynamicAttributeBlock(DynamicAttributeBlockChunkGenerator chunkGenerator, Block block) { - Builder.Pop(); + _builder.Pop(); } public override void VisitLiteralAttributeSpan(LiteralAttributeChunkGenerator chunkGenerator, Span span) { - Builder.Add(new HtmlAttributeValueIRNode() + _builder.Add(new HtmlAttributeValueIRNode() { Prefix = chunkGenerator.Prefix, Content = chunkGenerator.Value, - Source = BuildSourceRangeFromNode(span), + Source = BuildSourceSpanFromNode(span), }); } public override void VisitStartTemplateBlock(TemplateBlockChunkGenerator chunkGenerator, Block block) { - Builder.Push(new TemplateIRNode()); + _builder.Push(new TemplateIRNode()); } public override void VisitEndTemplateBlock(TemplateBlockChunkGenerator chunkGenerator, Block block) { - var templateNode = Builder.Pop(); + var templateNode = _builder.Pop(); if (templateNode.Children.Count > 0) { var sourceRangeStart = templateNode @@ -133,7 +256,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution var contentLength = templateNode.Children.Sum(child => child.Source?.Length ?? 0); templateNode.Source = new SourceSpan( - sourceRangeStart.Value.FilePath ?? _codeDocument.Source.Filename, + sourceRangeStart.Value.FilePath ?? Filename, sourceRangeStart.Value.AbsoluteIndex, sourceRangeStart.Value.LineIndex, sourceRangeStart.Value.CharacterIndex, @@ -150,12 +273,12 @@ namespace Microsoft.AspNetCore.Razor.Evolution // We need to capture this in the IR so that we can give each piece the correct source mappings public override void VisitStartExpressionBlock(ExpressionChunkGenerator chunkGenerator, Block block) { - Builder.Push(new CSharpExpressionIRNode()); + _builder.Push(new CSharpExpressionIRNode()); } public override void VisitEndExpressionBlock(ExpressionChunkGenerator chunkGenerator, Block block) { - var expressionNode = Builder.Pop(); + var expressionNode = _builder.Pop(); if (expressionNode.Children.Count > 0) { @@ -169,7 +292,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution var contentLength = expressionNode.Children.Sum(child => child.Source?.Length ?? 0); expressionNode.Source = new SourceSpan( - sourceRangeStart.Value.FilePath ?? _codeDocument.Source.Filename, + sourceRangeStart.Value.FilePath ?? Filename, sourceRangeStart.Value.AbsoluteIndex, sourceRangeStart.Value.LineIndex, sourceRangeStart.Value.CharacterIndex, @@ -192,19 +315,19 @@ namespace Microsoft.AspNetCore.Razor.Evolution } } - Builder.Add(new CSharpTokenIRNode() + _builder.Add(new CSharpTokenIRNode() { Content = span.Content, - Source = BuildSourceRangeFromNode(span), + Source = BuildSourceSpanFromNode(span), }); } public override void VisitStatementSpan(StatementChunkGenerator chunkGenerator, Span span) { - Builder.Add(new CSharpStatementIRNode() + _builder.Add(new CSharpStatementIRNode() { Content = span.Content, - Source = BuildSourceRangeFromNode(span), + Source = BuildSourceSpanFromNode(span), }); } @@ -222,7 +345,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution } } - var currentChildren = Builder.Current.Children; + var currentChildren = _builder.Current.Children; if (currentChildren.Count > 0 && currentChildren[currentChildren.Count - 1] is HtmlContentIRNode) { var existingHtmlContent = (HtmlContentIRNode)currentChildren[currentChildren.Count - 1]; @@ -233,7 +356,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution var contentLength = existingHtmlContent.Source.Value.Length + span.Content.Length; existingHtmlContent.Source = new SourceSpan( - existingHtmlContent.Source.Value.FilePath ?? _codeDocument.Source.Filename, + existingHtmlContent.Source.Value.FilePath ?? Filename, existingHtmlContent.Source.Value.AbsoluteIndex, existingHtmlContent.Source.Value.LineIndex, existingHtmlContent.Source.Value.CharacterIndex, @@ -242,53 +365,14 @@ namespace Microsoft.AspNetCore.Razor.Evolution } else { - Builder.Add(new HtmlContentIRNode() + _builder.Add(new HtmlContentIRNode() { Content = span.Content, - Source = BuildSourceRangeFromNode(span), + Source = BuildSourceSpanFromNode(span), }); } } - public override void VisitImportSpan(AddImportChunkGenerator chunkGenerator, Span span) - { - var namespaceImport = chunkGenerator.Namespace.Trim(); - - // Track seen namespaces so we don't add duplicates from options. - if (Namespaces.Add(namespaceImport)) - { - Builder.Add(new UsingStatementIRNode() - { - Content = namespaceImport, - Source = BuildSourceRangeFromNode(span), - }); - } - } - - public override void VisitDirectiveToken(DirectiveTokenChunkGenerator chunkGenerator, Span span) - { - Builder.Add(new DirectiveTokenIRNode() - { - Content = span.Content, - Descriptor = chunkGenerator.Descriptor, - Source = BuildSourceRangeFromNode(span), - }); - } - - public override void VisitStartDirectiveBlock(DirectiveChunkGenerator chunkGenerator, Block block) - { - Builder.Push(new DirectiveIRNode() - { - Name = chunkGenerator.Descriptor.Name, - Descriptor = chunkGenerator.Descriptor, - }); - } - - public override void VisitEndDirectiveBlock(DirectiveChunkGenerator chunkGenerator, Block block) - { - Builder.Pop(); - } - public override void VisitStartTagHelperBlock(TagHelperChunkGenerator chunkGenerator, Block block) { var tagHelperBlock = block as TagHelperBlock; @@ -299,12 +383,12 @@ namespace Microsoft.AspNetCore.Razor.Evolution DeclareTagHelperFields(tagHelperBlock); - Builder.Push(new TagHelperIRNode() + _builder.Push(new TagHelperIRNode() { - Source = BuildSourceRangeFromNode(block) + Source = BuildSourceSpanFromNode(block) }); - Builder.Push(new InitializeTagHelperStructureIRNode() + _builder.Push(new InitializeTagHelperStructureIRNode() { TagName = tagHelperBlock.TagName, TagMode = tagHelperBlock.TagMode @@ -319,25 +403,13 @@ namespace Microsoft.AspNetCore.Razor.Evolution return; } - Builder.Pop(); // Pop InitializeTagHelperStructureIRNode + _builder.Pop(); // Pop InitializeTagHelperStructureIRNode AddTagHelperCreation(tagHelperBlock.Descriptors); AddTagHelperAttributes(tagHelperBlock.Attributes, tagHelperBlock.Descriptors); AddExecuteTagHelpers(); - Builder.Pop(); // Pop TagHelperIRNode - } - - public override void VisitAddTagHelperSpan(AddTagHelperChunkGenerator chunkGenerator, Span span) - { - } - - public override void VisitRemoveTagHelperSpan(RemoveTagHelperChunkGenerator chunkGenerator, Span span) - { - } - - public override void VisitTagHelperPrefixDirectiveSpan(TagHelperPrefixDirectiveChunkGenerator chunkGenerator, Span span) - { + _builder.Pop(); // Pop TagHelperIRNode } private void DeclareTagHelperFields(TagHelperBlock block) @@ -345,7 +417,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution if (_tagHelperFields == null) { _tagHelperFields = new DeclareTagHelperFieldsIRNode(); - Builder.Add(_tagHelperFields); + _builder.Add(_tagHelperFields); } foreach (var descriptor in block.Descriptors) @@ -364,7 +436,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution Descriptor = descriptor }; - Builder.Add(createTagHelper); + _builder.Add(createTagHelper); } } @@ -397,12 +469,12 @@ namespace Microsoft.AspNetCore.Razor.Evolution TagHelperTypeName = associatedDescriptor.TypeName, Descriptor = associatedAttributeDescriptor, ValueStyle = attribute.ValueStyle, - Source = BuildSourceRangeFromNode(attributeValueNode) + Source = BuildSourceSpanFromNode(attributeValueNode) }; - Builder.Push(setTagHelperProperty); + _builder.Push(setTagHelperProperty); attributeValueNode.Accept(this); - Builder.Pop(); + _builder.Pop(); } } else @@ -413,31 +485,19 @@ namespace Microsoft.AspNetCore.Razor.Evolution ValueStyle = attribute.ValueStyle }; - Builder.Push(addHtmlAttribute); + _builder.Push(addHtmlAttribute); if (attributeValueNode != null) { attributeValueNode.Accept(this); } - Builder.Pop(); + _builder.Pop(); } } } private void AddExecuteTagHelpers() { - Builder.Add(new ExecuteTagHelpersIRNode()); - } - - private SourceSpan BuildSourceRangeFromNode(SyntaxTreeNode node) - { - var location = node.Start; - var sourceRange = new SourceSpan( - node.Start.FilePath ?? _codeDocument.Source.Filename, - node.Start.AbsoluteIndex, - node.Start.LineIndex, - node.Start.CharacterIndex, - node.Length); - return sourceRange; + _builder.Add(new ExecuteTagHelpersIRNode()); } } } diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/TagHelperDirectiveSpanVisitor.cs b/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/TagHelperDirectiveSpanVisitor.cs deleted file mode 100644 index 22a1412997..0000000000 --- a/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/TagHelperDirectiveSpanVisitor.cs +++ /dev/null @@ -1,170 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; - -namespace Microsoft.AspNetCore.Razor.Evolution.Legacy -{ - internal class TagHelperDirectiveSpanVisitor - { - private readonly ITagHelperDescriptorResolver _descriptorResolver; - private readonly ErrorSink _errorSink; - - private List _directiveDescriptors; - - public int Order { get; } - - public RazorEngine Engine { get; set; } - - // Internal for testing use - internal TagHelperDirectiveSpanVisitor(ITagHelperDescriptorResolver descriptorResolver) - : this(descriptorResolver, new ErrorSink()) - { - } - - public TagHelperDirectiveSpanVisitor( - ITagHelperDescriptorResolver descriptorResolver, - ErrorSink errorSink) - { - if (descriptorResolver == null) - { - throw new ArgumentNullException(nameof(descriptorResolver)); - } - - if (errorSink == null) - { - throw new ArgumentNullException(nameof(errorSink)); - } - - _descriptorResolver = descriptorResolver; - _errorSink = errorSink; - } - - public IEnumerable GetDescriptors(Block root) - { - if (root == null) - { - throw new ArgumentNullException(nameof(root)); - } - - _directiveDescriptors = new List(); - - // This will recurse through the syntax tree. - VisitBlock(root); - - var resolutionContext = GetTagHelperDescriptorResolutionContext(_directiveDescriptors, _errorSink); - var descriptors = _descriptorResolver.Resolve(resolutionContext); - - return descriptors; - } - - // Allows MVC a chance to override the TagHelperDescriptorResolutionContext - protected virtual TagHelperDescriptorResolutionContext GetTagHelperDescriptorResolutionContext( - IEnumerable descriptors, - ErrorSink errorSink) - { - if (descriptors == null) - { - throw new ArgumentNullException(nameof(descriptors)); - } - - if (errorSink == null) - { - throw new ArgumentNullException(nameof(errorSink)); - } - - return new TagHelperDescriptorResolutionContext(descriptors, errorSink); - } - - public void VisitBlock(Block block) - { - for (var i = 0; i < block.Children.Count; i++) - { - var child = block.Children[i]; - - if (child.IsBlock) - { - VisitBlock((Block)child); - } - else - { - VisitSpan((Span)child); - } - } - } - - public void VisitSpan(Span span) - { - if (span == null) - { - throw new ArgumentNullException(nameof(span)); - } - - string directiveText; - TagHelperDirectiveType directiveType; - - var addTagHelperChunkGenerator = span.ChunkGenerator as AddTagHelperChunkGenerator; - var removeTagHelperChunkGenerator = span.ChunkGenerator as RemoveTagHelperChunkGenerator; - var tagHelperPrefixChunkGenerator = span.ChunkGenerator as TagHelperPrefixDirectiveChunkGenerator; - - if (addTagHelperChunkGenerator != null) - { - directiveType = TagHelperDirectiveType.AddTagHelper; - directiveText = addTagHelperChunkGenerator.LookupText; - } - else if (removeTagHelperChunkGenerator != null) - { - directiveType = TagHelperDirectiveType.RemoveTagHelper; - directiveText = removeTagHelperChunkGenerator.LookupText; - } - else if (tagHelperPrefixChunkGenerator != null) - { - directiveType = TagHelperDirectiveType.TagHelperPrefix; - directiveText = tagHelperPrefixChunkGenerator.Prefix; - } - else - { - // Not a chunk generator that we're interested in. - return; - } - - directiveText = directiveText.Trim(); - - // If this is the "string literal" form of a directive, we'll need to postprocess the location - // and content. - // - // Ex: @addTagHelper "*, Microsoft.AspNetCore.CoolLibrary" - // ^ ^ - // Start End - var directiveStart = span.Start; - if (span.Symbols.Count == 1 && (span.Symbols[0] as CSharpSymbol)?.Type == CSharpSymbolType.StringLiteral) - { - var offset = span.Content.IndexOf(directiveText, StringComparison.Ordinal); - - // This is safe because inside one of these directives all of the text needs to be on the - // same line. - var original = span.Start; - directiveStart = new SourceLocation( - original.FilePath, - original.AbsoluteIndex + offset, - original.LineIndex, - original.CharacterIndex + offset); - } - - var directiveDescriptor = new TagHelperDirectiveDescriptor - { - DirectiveText = directiveText, - Location = directiveStart, - DirectiveType = directiveType - }; - - _directiveDescriptors.Add(directiveDescriptor); - } - - public RazorSyntaxTree Execute(RazorCodeDocument codeDocument, RazorSyntaxTree syntaxTree) - { - throw new NotImplementedException(); - } - } -} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/TagHelperBinderSyntaxTreePass.cs b/src/Microsoft.AspNetCore.Razor.Evolution/TagHelperBinderSyntaxTreePass.cs index 4bce049a7f..c066a79906 100644 --- a/src/Microsoft.AspNetCore.Razor.Evolution/TagHelperBinderSyntaxTreePass.cs +++ b/src/Microsoft.AspNetCore.Razor.Evolution/TagHelperBinderSyntaxTreePass.cs @@ -1,8 +1,10 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; using Microsoft.AspNetCore.Razor.Evolution.Legacy; namespace Microsoft.AspNetCore.Razor.Evolution @@ -13,7 +15,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution public int Order => 150; - public RazorSyntaxTree Execute(RazorCodeDocument document, RazorSyntaxTree syntaxTree) + public RazorSyntaxTree Execute(RazorCodeDocument codeDocument, RazorSyntaxTree syntaxTree) { var resolver = Engine.Features.OfType().FirstOrDefault()?.Resolver; if (resolver == null) @@ -22,18 +24,33 @@ namespace Microsoft.AspNetCore.Razor.Evolution return syntaxTree; } - var errorSink = new ErrorSink(); - var visitor = new TagHelperDirectiveSpanVisitor(resolver, errorSink); - var descriptors = visitor.GetDescriptors(syntaxTree.Root); - - if (!descriptors.Any()) + // 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. + var visitor = new Visitor(); + var imports = codeDocument.GetImportSyntaxTrees(); + if (imports != null) { + for (var i = 0; i < imports.Count; i++) + { + var import = imports[i]; + visitor.VisitBlock(import.Root); + } + } + + visitor.VisitBlock(syntaxTree.Root); + + var directives = visitor.Directives; + var errorSink = new ErrorSink(); + var descriptors = resolver.Resolve(new TagHelperDescriptorResolutionContext(directives, errorSink)).ToArray(); + if (descriptors.Length == 0) + { + // No TagHelpers, add any errors if we have them. if (errorSink.Errors.Count > 0) { - var combinedErrors = CombineErrors(syntaxTree.Diagnostics, errorSink.Errors); - var erroredTree = RazorSyntaxTree.Create(syntaxTree.Root, syntaxTree.Source, combinedErrors, syntaxTree.Options); - - return erroredTree; + var errors = CombineErrors(syntaxTree.Diagnostics, errorSink.Errors); + return RazorSyntaxTree.Create(syntaxTree.Root, syntaxTree.Source, errors, syntaxTree.Options); } return syntaxTree; @@ -61,5 +78,63 @@ namespace Microsoft.AspNetCore.Razor.Evolution return combinedErrors; } + + private class Visitor : ParserVisitor + { + public List Directives { get; } = new List(); + + public override void VisitAddTagHelperSpan(AddTagHelperChunkGenerator chunkGenerator, Span span) + { + Directives.Add(CreateDirective(span, chunkGenerator.LookupText, TagHelperDirectiveType.AddTagHelper)); + } + + public override void VisitRemoveTagHelperSpan(RemoveTagHelperChunkGenerator chunkGenerator, Span span) + { + Directives.Add(CreateDirective(span, chunkGenerator.LookupText, TagHelperDirectiveType.RemoveTagHelper)); + } + + public override void VisitTagHelperPrefixDirectiveSpan(TagHelperPrefixDirectiveChunkGenerator chunkGenerator, Span span) + { + Directives.Add(CreateDirective(span, chunkGenerator.Prefix, TagHelperDirectiveType.TagHelperPrefix)); + } + + private TagHelperDirectiveDescriptor CreateDirective( + Span span, + string directiveText, + TagHelperDirectiveType directiveType) + { + directiveText = directiveText.Trim(); + + // If this is the "string literal" form of a directive, we'll need to postprocess the location + // and content. + // + // Ex: @addTagHelper "*, Microsoft.AspNetCore.CoolLibrary" + // ^ ^ + // Start End + var directiveStart = span.Start; + if (span.Symbols.Count == 1 && (span.Symbols[0] as CSharpSymbol)?.Type == CSharpSymbolType.StringLiteral) + { + var offset = span.Content.IndexOf(directiveText, StringComparison.Ordinal); + + // This is safe because inside one of these directives all of the text needs to be on the + // same line. + var original = span.Start; + directiveStart = new SourceLocation( + original.FilePath, + original.AbsoluteIndex + offset, + original.LineIndex, + original.CharacterIndex + offset); + } + + var directiveDescriptor = new TagHelperDirectiveDescriptor + { + DirectiveText = directiveText, + Location = directiveStart, + DirectiveType = directiveType, + }; + + return directiveDescriptor; + } + } } } diff --git a/test/Microsoft.AspNetCore.Razor.Evolution.Test/IntegrationTests/ImportsIntegrationTest.cs b/test/Microsoft.AspNetCore.Razor.Evolution.Test/IntegrationTests/ImportsIntegrationTest.cs new file mode 100644 index 0000000000..0eab7f0bd0 --- /dev/null +++ b/test/Microsoft.AspNetCore.Razor.Evolution.Test/IntegrationTests/ImportsIntegrationTest.cs @@ -0,0 +1,25 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Xunit; + +namespace Microsoft.AspNetCore.Razor.Evolution.IntegrationTests +{ + public class ImportsIntegrationTest : IntegrationTestBase + { + [Fact] + public void BasicImports() + { + // Arrange + var engine = RazorEngine.Create(); + + var document = CreateCodeDocument(); + + // Act + engine.Process(document); + + // Assert + AssertIRMatchesBaseline(document.GetIRDocument()); + } + } +} diff --git a/test/Microsoft.AspNetCore.Razor.Evolution.Test/IntegrationTests/IntegrationTestBase.cs b/test/Microsoft.AspNetCore.Razor.Evolution.Test/IntegrationTests/IntegrationTestBase.cs index 405643496e..cc0549649a 100644 --- a/test/Microsoft.AspNetCore.Razor.Evolution.Test/IntegrationTests/IntegrationTestBase.cs +++ b/test/Microsoft.AspNetCore.Razor.Evolution.Test/IntegrationTests/IntegrationTestBase.cs @@ -88,7 +88,19 @@ namespace Microsoft.AspNetCore.Razor.Evolution.IntegrationTests throw new XunitException($"The resource {sourceFilename} was not found."); } - var codeDocument = RazorCodeDocument.Create(TestRazorSourceDocument.CreateResource(sourceFilename)); + var imports = new List(); + while (true) + { + var importsFilename = Path.ChangeExtension(Filename + "_Imports" + imports.Count.ToString(), ".cshtml"); + if (!TestFile.Create(importsFilename).Exists()) + { + break; + } + + imports.Add(TestRazorSourceDocument.CreateResource(importsFilename)); + } + + var codeDocument = RazorCodeDocument.Create(TestRazorSourceDocument.CreateResource(sourceFilename), imports); // This will ensure that we're not putting any randomly generated data in a baseline. codeDocument.Items[DefaultRazorRuntimeCSharpLoweringPhase.SuppressUniqueIds] = "test"; diff --git a/test/Microsoft.AspNetCore.Razor.Evolution.Test/Intermediate/DefaultRazorIRLoweringPhaseIntegrationTest.cs b/test/Microsoft.AspNetCore.Razor.Evolution.Test/Intermediate/DefaultRazorIRLoweringPhaseIntegrationTest.cs index 568a3b648c..4248764d0f 100644 --- a/test/Microsoft.AspNetCore.Razor.Evolution.Test/Intermediate/DefaultRazorIRLoweringPhaseIntegrationTest.cs +++ b/test/Microsoft.AspNetCore.Razor.Evolution.Test/Intermediate/DefaultRazorIRLoweringPhaseIntegrationTest.cs @@ -24,7 +24,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Intermediate // Assert Children(irDocument, - n => Assert.IsType(n), + n => Checksum(n), n => Using("System", n), n => Using("System.Threading.Tasks", n)); } @@ -40,7 +40,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Intermediate // Assert Children(irDocument, - n => Assert.IsType(n), + n => Checksum(n), n => Assert.IsType(n), n => Assert.IsType(n), n => Html("Hello, World!", n)); @@ -62,7 +62,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Intermediate // Assert Children(irDocument, - n => Assert.IsType(n), + n => Checksum(n), n => Assert.IsType(n), n => Assert.IsType(n), n => Html( @@ -92,7 +92,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Intermediate // Assert Children(irDocument, - n => Assert.IsType(n), + n => Checksum(n), n => Assert.IsType(n), n => Assert.IsType(n), n => Html( @@ -127,7 +127,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Intermediate // Assert Children(irDocument, - n => Assert.IsType(n), + n => Checksum(n), n => Assert.IsType(n), n => Assert.IsType(n), n => Directive( @@ -147,7 +147,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Intermediate // Assert Children(irDocument, - n => Assert.IsType(n), + n => Checksum(n), n => Using("System", n), n => Using(typeof(Task).Namespace, n)); } @@ -157,7 +157,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Intermediate { // Arrange var codeDocument = TestRazorCodeDocument.Create(@""); - var descriptors = new[] + var tagHelpers = new[] { new TagHelperDescriptor { @@ -167,18 +167,19 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Intermediate }; // Act - var irDocument = Lower(codeDocument, descriptors); + var irDocument = Lower(codeDocument, tagHelpers: tagHelpers); // Assert Children(irDocument, - n => Assert.IsType(n), + n => Checksum(n), n => Using("System", n), n => Using(typeof(Task).Namespace, n), n => TagHelperFieldDeclaration(n, "SpanTagHelper"), n => { var tagHelperNode = Assert.IsType(n); - Children(tagHelperNode, + Children( + tagHelperNode, c => TagHelperStructure("span", TagMode.StartTagAndEndTag, c), c => Assert.IsType(c), c => TagHelperHtmlAttribute( @@ -209,11 +210,12 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Intermediate }; // Act - var irDocument = Lower(codeDocument, new[] { descriptor }); + var irDocument = Lower(codeDocument, tagHelpers: new[] { descriptor }); // Assert - Children(irDocument, - n => Assert.IsType(n), + Children( + irDocument, + n => Checksum(n), n => Using("System", n), n => Using(typeof(Task).Namespace, n), n => TagHelperFieldDeclaration(n, "InputTagHelper"), @@ -233,16 +235,102 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Intermediate }); } - private DocumentIRNode Lower(RazorCodeDocument codeDocument) + [Fact] + public void Lower_WithImports_Using() { - return Lower(codeDocument, Enumerable.Empty()); + // Arrange + var source = TestRazorSourceDocument.Create("

Hi!

"); + var imports = new[] + { + TestRazorSourceDocument.Create("@using System.Globalization"), + TestRazorSourceDocument.Create("@using System.Text"), + }; + + var codeDocument = TestRazorCodeDocument.Create(source, imports); + + // Act + var irDocument = Lower(codeDocument); + + // Assert + Children( + irDocument, + n => Checksum(n), + n => Using("System", n), + n => Using(typeof(Task).Namespace, n), + n => Using("System.Globalization", n), + n => Using("System.Text", n), + n => Html("

Hi!

", n)); } - private DocumentIRNode Lower(RazorCodeDocument codeDocument, IEnumerable descriptors) + [Fact] + public void Lower_WithImports_Directive() { - var engine = RazorEngine.Create(builder => + // Arrange + var source = TestRazorSourceDocument.Create("

Hi!

"); + var imports = new[] { - builder.Features.Add(new TagHelperFeature(new TestTagHelperDescriptorResolver(descriptors))); + TestRazorSourceDocument.Create("@test value1"), + TestRazorSourceDocument.Create("@test value2"), + }; + + var codeDocument = TestRazorCodeDocument.Create(source, imports); + + // Act + var irDocument = Lower(codeDocument, b => + { + b.AddDirective(DirectiveDescriptorBuilder.Create("test").AddMember().Build()); + }); + + // Assert + Children( + irDocument, + n => Checksum(n), + n => Using("System", n), + n => Using(typeof(Task).Namespace, n), + n => Directive("test", n, c => DirectiveToken(DirectiveTokenKind.Member, "value1", c)), + n => Directive("test", n, c => DirectiveToken(DirectiveTokenKind.Member, "value2", c)), + n => Html("

Hi!

", n)); + } + + [Fact] + public void Lower_WithImports_IgnoresBlockDirective() + { + // Arrange + var source = TestRazorSourceDocument.Create("

Hi!

"); + var imports = new[] + { + TestRazorSourceDocument.Create("@block token { }"), + }; + + var codeDocument = TestRazorCodeDocument.Create(source, imports); + + // Act + var irDocument = Lower(codeDocument, b => + { + b.AddDirective(DirectiveDescriptorBuilder.CreateRazorBlock("block").AddMember().Build()); + }); + + // Assert + Children( + irDocument, + n => Checksum(n), + n => Using("System", n), + n => Using(typeof(Task).Namespace, n), + n => Html("

Hi!

", n)); + } + + private DocumentIRNode Lower( + RazorCodeDocument codeDocument, + Action builder = null, + IEnumerable tagHelpers = null) + { + tagHelpers = tagHelpers ?? new TagHelperDescriptor[0]; + + var engine = RazorEngine.Create(b => + { + builder?.Invoke(b); + + b.Features.Add(new TagHelperFeature(new TestTagHelperDescriptorResolver(tagHelpers))); }); for (var i = 0; i < engine.Phases.Count; i++) diff --git a/test/Microsoft.AspNetCore.Razor.Evolution.Test/Intermediate/RazorIRAssert.cs b/test/Microsoft.AspNetCore.Razor.Evolution.Test/Intermediate/RazorIRAssert.cs index 6b79fd73ea..fb2c3e4f47 100644 --- a/test/Microsoft.AspNetCore.Razor.Evolution.Test/Intermediate/RazorIRAssert.cs +++ b/test/Microsoft.AspNetCore.Razor.Evolution.Test/Intermediate/RazorIRAssert.cs @@ -102,6 +102,20 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Intermediate Children(node, childValidators); } + public static void DirectiveToken(DirectiveTokenKind expectedKind, string expectedContent, RazorIRNode node) + { + try + { + var token = Assert.IsType(node); + Assert.Equal(expectedKind, token.Descriptor.Kind); + Assert.Equal(expectedContent, token.Content); + } + catch (XunitException e) + { + throw new IRAssertException(node, node.Children, e.Message, e); + } + } + public static void Using(string expected, RazorIRNode node) { try @@ -168,6 +182,18 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Intermediate } } + public static void Checksum(RazorIRNode node) + { + try + { + Assert.IsType(node); + } + catch (XunitException e) + { + throw new IRAssertException(node, node.Children, e.Message, e); + } + } + public static void CSharpExpression(string expected, RazorIRNode node) { try diff --git a/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/TagHelperDirectiveSpanVisitorTest.cs b/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/TagHelperDirectiveSpanVisitorTest.cs deleted file mode 100644 index 2a6e007843..0000000000 --- a/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/TagHelperDirectiveSpanVisitorTest.cs +++ /dev/null @@ -1,423 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; -using System.Linq; -using Microsoft.Extensions.Internal; -using Moq; -using Xunit; - -namespace Microsoft.AspNetCore.Razor.Evolution.Legacy -{ - public class TagHelperDirectiveSpanVisitorTest - { - public static TheoryData QuotedTagHelperDirectivesData - { - get - { - var factory = new SpanFactory(); - - // document, expectedDescriptors - return new TheoryData> - { - { - new MarkupBlock(factory.Code("\"*, someAssembly\"").AsAddTagHelper("*, someAssembly")), - new[] - { - new TagHelperDirectiveDescriptor - { - DirectiveText = "*, someAssembly", - DirectiveType = TagHelperDirectiveType.AddTagHelper - }, - } - }, - { - new MarkupBlock(factory.Code("\"*, someAssembly\"").AsRemoveTagHelper("*, someAssembly")), - new[] - { - new TagHelperDirectiveDescriptor - { - DirectiveText = "*, someAssembly", - DirectiveType = TagHelperDirectiveType.RemoveTagHelper - }, - } - }, - { - new MarkupBlock(factory.Code("\"th:\"").AsTagHelperPrefixDirective("th:")), - new[] - { - new TagHelperDirectiveDescriptor - { - DirectiveText = "th:", - DirectiveType = TagHelperDirectiveType.TagHelperPrefix - }, - } - }, - { - new MarkupBlock(factory.Code(" \"*, someAssembly \" ").AsAddTagHelper("*, someAssembly ")), - new[] - { - new TagHelperDirectiveDescriptor - { - DirectiveText = "*, someAssembly", - DirectiveType = TagHelperDirectiveType.AddTagHelper - }, - } - }, - { - new MarkupBlock(factory.Code(" \"*, someAssembly \" ").AsRemoveTagHelper("*, someAssembly ")), - new[] - { - new TagHelperDirectiveDescriptor - { - DirectiveText = "*, someAssembly", - DirectiveType = TagHelperDirectiveType.RemoveTagHelper - }, - } - }, - { - new MarkupBlock(factory.Code(" \" th :\"").AsTagHelperPrefixDirective(" th :")), - new[] - { - new TagHelperDirectiveDescriptor - { - DirectiveText = "th :", - DirectiveType = TagHelperDirectiveType.TagHelperPrefix - }, - } - }, - }; - } - } - - [Theory] - [MemberData(nameof(QuotedTagHelperDirectivesData))] - public void GetDescriptors_LocatesQuotedTagHelperDirectives_CreatesDirectiveDescriptors( - object document, - object expectedDescriptors) - { - // Arrange - var resolver = new TestTagHelperDescriptorResolver(); - var tagHelperDirectiveSpanVisitor = new TagHelperDirectiveSpanVisitor(resolver, new ErrorSink()); - - // Act - tagHelperDirectiveSpanVisitor.GetDescriptors((MarkupBlock)document); - - // Assert - Assert.Equal( - (IEnumerable)expectedDescriptors, - resolver.DirectiveDescriptors, - TagHelperDirectiveDescriptorComparer.Default); - } - - [Fact] - public void GetDescriptors_InvokesResolveOnceForAllDirectives() - { - // Arrange - var factory = new SpanFactory(); - var resolver = new Mock(); - resolver.Setup(mock => mock.Resolve(It.IsAny())) - .Returns(Enumerable.Empty()); - var tagHelperDirectiveSpanVisitor = new TagHelperDirectiveSpanVisitor( - resolver.Object, - new ErrorSink()); - var document = new MarkupBlock( - factory.Code("one").AsAddTagHelper("one"), - factory.Code("two").AsRemoveTagHelper("two"), - factory.Code("three").AsRemoveTagHelper("three"), - factory.Code("four").AsTagHelperPrefixDirective("four")); - - // Act - tagHelperDirectiveSpanVisitor.GetDescriptors(document); - - // Assert - resolver.Verify(mock => mock.Resolve(It.IsAny()), Times.Once); - } - - [Fact] - public void GetDescriptors_LocatesTagHelperChunkGenerator_CreatesDirectiveDescriptors() - { - // Arrange - var factory = new SpanFactory(); - var resolver = new TestTagHelperDescriptorResolver(); - var tagHelperDirectiveSpanVisitor = new TagHelperDirectiveSpanVisitor(resolver, new ErrorSink()); - var document = new MarkupBlock( - factory.Code("one").AsAddTagHelper("one"), - factory.Code("two").AsRemoveTagHelper("two"), - factory.Code("three").AsRemoveTagHelper("three"), - factory.Code("four").AsTagHelperPrefixDirective("four")); - var expectedDescriptors = new TagHelperDirectiveDescriptor[] - { - new TagHelperDirectiveDescriptor - { - DirectiveText = "one", - DirectiveType = TagHelperDirectiveType.AddTagHelper - }, - new TagHelperDirectiveDescriptor - { - DirectiveText = "two", - DirectiveType = TagHelperDirectiveType.RemoveTagHelper - }, - new TagHelperDirectiveDescriptor - { - DirectiveText = "three", - DirectiveType = TagHelperDirectiveType.RemoveTagHelper - }, - new TagHelperDirectiveDescriptor - { - DirectiveText = "four", - DirectiveType = TagHelperDirectiveType.TagHelperPrefix - } - }; - - // Act - tagHelperDirectiveSpanVisitor.GetDescriptors(document); - - // Assert - Assert.Equal( - expectedDescriptors, - resolver.DirectiveDescriptors, - TagHelperDirectiveDescriptorComparer.Default); - } - - [Fact] - public void GetDescriptors_CanOverrideResolutionContext() - { - // Arrange - var factory = new SpanFactory(); - var resolver = new TestTagHelperDescriptorResolver(); - var expectedInitialDirectiveDescriptors = new TagHelperDirectiveDescriptor[] - { - new TagHelperDirectiveDescriptor - { - DirectiveText = "one", - DirectiveType = TagHelperDirectiveType.AddTagHelper - }, - new TagHelperDirectiveDescriptor - { - DirectiveText = "two", - DirectiveType = TagHelperDirectiveType.RemoveTagHelper - }, - new TagHelperDirectiveDescriptor - { - DirectiveText = "three", - DirectiveType = TagHelperDirectiveType.RemoveTagHelper - }, - new TagHelperDirectiveDescriptor - { - DirectiveText = "four", - DirectiveType = TagHelperDirectiveType.TagHelperPrefix - } - }; - var expectedEndDirectiveDescriptors = new TagHelperDirectiveDescriptor[] - { - new TagHelperDirectiveDescriptor - { - DirectiveText = "custom", - DirectiveType = TagHelperDirectiveType.AddTagHelper - } - }; - var tagHelperDirectiveSpanVisitor = new CustomTagHelperDirectiveSpanVisitor( - resolver, - (descriptors, errorSink) => - { - Assert.Equal( - expectedInitialDirectiveDescriptors, - descriptors, - TagHelperDirectiveDescriptorComparer.Default); - - return new TagHelperDescriptorResolutionContext(expectedEndDirectiveDescriptors, errorSink); - }); - var document = new MarkupBlock( - factory.Code("one").AsAddTagHelper("one"), - factory.Code("two").AsRemoveTagHelper("two"), - factory.Code("three").AsRemoveTagHelper("three"), - factory.Code("four").AsTagHelperPrefixDirective("four")); - - - // Act - tagHelperDirectiveSpanVisitor.GetDescriptors(document); - - // Assert - Assert.Equal(expectedEndDirectiveDescriptors, - resolver.DirectiveDescriptors, - TagHelperDirectiveDescriptorComparer.Default); - } - - [Fact] - public void GetDescriptors_LocatesTagHelperPrefixDirectiveChunkGenerator() - { - // Arrange - var factory = new SpanFactory(); - var resolver = new TestTagHelperDescriptorResolver(); - var tagHelperDirectiveSpanVisitor = new TagHelperDirectiveSpanVisitor(resolver, new ErrorSink()); - var document = new MarkupBlock( - new DirectiveBlock( - factory.CodeTransition(), - factory - .MetaCode(SyntaxConstants.CSharp.TagHelperPrefixKeyword + " ") - .Accepts(AcceptedCharacters.None), - factory.Code("something").AsTagHelperPrefixDirective("something"))); - var expectedDirectiveDescriptor = - new TagHelperDirectiveDescriptor - { - DirectiveText = "something", - DirectiveType = TagHelperDirectiveType.TagHelperPrefix - }; - - // Act - tagHelperDirectiveSpanVisitor.GetDescriptors(document); - - // Assert - var directiveDescriptor = Assert.Single(resolver.DirectiveDescriptors); - Assert.Equal( - expectedDirectiveDescriptor, - directiveDescriptor, - TagHelperDirectiveDescriptorComparer.Default); - } - - [Fact] - public void GetDescriptors_LocatesAddTagHelperChunkGenerator() - { - // Arrange - var factory = new SpanFactory(); - var resolver = new TestTagHelperDescriptorResolver(); - var tagHelperDirectiveSpanVisitor = new TagHelperDirectiveSpanVisitor(resolver, new ErrorSink()); - var document = new MarkupBlock( - new DirectiveBlock( - factory.CodeTransition(), - factory.MetaCode(SyntaxConstants.CSharp.RemoveTagHelperKeyword + " ") - .Accepts(AcceptedCharacters.None), - factory.Code("something").AsAddTagHelper("something")) - ); - var expectedRegistration = new TagHelperDirectiveDescriptor - { - DirectiveText = "something", - DirectiveType = TagHelperDirectiveType.AddTagHelper - }; - - // Act - tagHelperDirectiveSpanVisitor.GetDescriptors(document); - - // Assert - var directiveDescriptor = Assert.Single(resolver.DirectiveDescriptors); - Assert.Equal(expectedRegistration, directiveDescriptor, TagHelperDirectiveDescriptorComparer.Default); - } - - [Fact] - public void GetDescriptors_LocatesNestedRemoveTagHelperChunkGenerator() - { - // Arrange - var factory = new SpanFactory(); - var resolver = new TestTagHelperDescriptorResolver(); - var tagHelperDirectiveSpanVisitor = new TagHelperDirectiveSpanVisitor(resolver, new ErrorSink()); - var document = new MarkupBlock( - new DirectiveBlock( - factory.CodeTransition(), - factory.MetaCode(SyntaxConstants.CSharp.RemoveTagHelperKeyword + " ") - .Accepts(AcceptedCharacters.None), - factory.Code("something").AsRemoveTagHelper("something")) - ); - var expectedRegistration = new TagHelperDirectiveDescriptor - { - DirectiveText = "something", - DirectiveType = TagHelperDirectiveType.RemoveTagHelper - }; - - // Act - tagHelperDirectiveSpanVisitor.GetDescriptors(document); - - // Assert - var directiveDescriptor = Assert.Single(resolver.DirectiveDescriptors); - Assert.Equal(expectedRegistration, directiveDescriptor, TagHelperDirectiveDescriptorComparer.Default); - } - - [Fact] - public void GetDescriptors_RemoveTagHelperNotInDocument_DoesNotThrow() - { - // Arrange - var factory = new SpanFactory(); - var tagHelperDirectiveSpanVisitor = - new TagHelperDirectiveSpanVisitor( - new TestTagHelperDescriptorResolver(), - new ErrorSink()); - var document = new MarkupBlock(factory.Markup("Hello World")); - - // Act - var descriptors = tagHelperDirectiveSpanVisitor.GetDescriptors(document); - - Assert.Empty(descriptors); - } - - private class TestTagHelperDescriptorResolver : ITagHelperDescriptorResolver - { - public TestTagHelperDescriptorResolver() - { - DirectiveDescriptors = new List(); - } - - public List DirectiveDescriptors { get; } - - public IEnumerable Resolve(TagHelperDescriptorResolutionContext resolutionContext) - { - DirectiveDescriptors.AddRange(resolutionContext.DirectiveDescriptors); - - return Enumerable.Empty(); - } - } - - private class TagHelperDirectiveDescriptorComparer : IEqualityComparer - { - public static readonly TagHelperDirectiveDescriptorComparer Default = - new TagHelperDirectiveDescriptorComparer(); - - private TagHelperDirectiveDescriptorComparer() - { - } - - public bool Equals(TagHelperDirectiveDescriptor directiveDescriptorX, - TagHelperDirectiveDescriptor directiveDescriptorY) - { - return string.Equals(directiveDescriptorX.DirectiveText, - directiveDescriptorY.DirectiveText, - StringComparison.Ordinal) && - directiveDescriptorX.DirectiveType == directiveDescriptorY.DirectiveType; - } - - public int GetHashCode(TagHelperDirectiveDescriptor directiveDescriptor) - { - var hashCodeCombiner = HashCodeCombiner.Start(); - hashCodeCombiner.Add(base.GetHashCode()); - hashCodeCombiner.Add(directiveDescriptor.DirectiveText); - hashCodeCombiner.Add(directiveDescriptor.DirectiveType); - - return hashCodeCombiner; - } - } - - private class CustomTagHelperDirectiveSpanVisitor : TagHelperDirectiveSpanVisitor - { - private Func, - ErrorSink, - TagHelperDescriptorResolutionContext> _replacer; - - public CustomTagHelperDirectiveSpanVisitor( - ITagHelperDescriptorResolver descriptorResolver, - Func, - ErrorSink, - TagHelperDescriptorResolutionContext> replacer) - : base(descriptorResolver, new ErrorSink()) - { - _replacer = replacer; - } - - protected override TagHelperDescriptorResolutionContext GetTagHelperDescriptorResolutionContext( - IEnumerable descriptors, - ErrorSink errorSink) - { - return _replacer(descriptors, errorSink); - } - } - } -} diff --git a/test/Microsoft.AspNetCore.Razor.Evolution.Test/TestFiles/IntegrationTests/ImportsIntegrationTest/BasicImports.cshtml b/test/Microsoft.AspNetCore.Razor.Evolution.Test/TestFiles/IntegrationTests/ImportsIntegrationTest/BasicImports.cshtml new file mode 100644 index 0000000000..bf4d925aea --- /dev/null +++ b/test/Microsoft.AspNetCore.Razor.Evolution.Test/TestFiles/IntegrationTests/ImportsIntegrationTest/BasicImports.cshtml @@ -0,0 +1 @@ +

Hi there!

diff --git a/test/Microsoft.AspNetCore.Razor.Evolution.Test/TestFiles/IntegrationTests/ImportsIntegrationTest/BasicImports.ir.txt b/test/Microsoft.AspNetCore.Razor.Evolution.Test/TestFiles/IntegrationTests/ImportsIntegrationTest/BasicImports.ir.txt new file mode 100644 index 0000000000..d9eff77c60 --- /dev/null +++ b/test/Microsoft.AspNetCore.Razor.Evolution.Test/TestFiles/IntegrationTests/ImportsIntegrationTest/BasicImports.ir.txt @@ -0,0 +1,11 @@ +Document - + Checksum - + NamespaceDeclaration - - + UsingStatement - - System + UsingStatement - - System.Threading.Tasks + UsingStatement - (31:1,1 [28] BasicImports_Imports0.cshtml) - System.Globalization + UsingStatement - (80:3,1 [29] BasicImports_Imports0.cshtml) - System.ComponentModel + UsingStatement - (23:1,1 [20] BasicImports_Imports1.cshtml) - System.Text + ClassDeclaration - - - - - + RazorMethodDeclaration - - - - - + HtmlContent - (0:0,0 [18] BasicImports.cshtml) -

Hi there!

\n diff --git a/test/Microsoft.AspNetCore.Razor.Evolution.Test/TestFiles/IntegrationTests/ImportsIntegrationTest/BasicImports_Imports0.cshtml b/test/Microsoft.AspNetCore.Razor.Evolution.Test/TestFiles/IntegrationTests/ImportsIntegrationTest/BasicImports_Imports0.cshtml new file mode 100644 index 0000000000..c237575b2c --- /dev/null +++ b/test/Microsoft.AspNetCore.Razor.Evolution.Test/TestFiles/IntegrationTests/ImportsIntegrationTest/BasicImports_Imports0.cshtml @@ -0,0 +1,4 @@ +

This will get ignored

+@using System.Globalization +@("And also this") +@using System.ComponentModel diff --git a/test/Microsoft.AspNetCore.Razor.Evolution.Test/TestFiles/IntegrationTests/ImportsIntegrationTest/BasicImports_Imports1.cshtml b/test/Microsoft.AspNetCore.Razor.Evolution.Test/TestFiles/IntegrationTests/ImportsIntegrationTest/BasicImports_Imports1.cshtml new file mode 100644 index 0000000000..bac4a38695 --- /dev/null +++ b/test/Microsoft.AspNetCore.Razor.Evolution.Test/TestFiles/IntegrationTests/ImportsIntegrationTest/BasicImports_Imports1.cshtml @@ -0,0 +1,2 @@ +@section ignored { } +@using System.Text;