Adds support for imports to IR lowering

This change adds support for 'imports' - extra source files which contain
directives that can merged with 'main' source files. The purpose of course
is to support things like global usings or addTagHelpers, like
_ViewImports in MVC does today.

Instead of a one-off this is now a feature of the Razor langugage since
things like addTagHelper have an impact on the parsing behavior. Also,
having a standard imports concept keeps out feature creep, for instance
the 'global' usings we have today could really just be an import.

Imports allow single-line directives including the fundamental directives
like addTagHelper, using, and other friends. Code, content, and block
directives are not merged and will be ignored. We can consider making
these kinds of things warnings in the future.
This commit is contained in:
Ryan Nowak 2017-01-13 18:11:16 -08:00
parent 79663ef90e
commit 4a49ee2164
12 changed files with 451 additions and 740 deletions

View File

@ -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<string>();
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<string> _namespaces;
private DeclareTagHelperFieldsIRNode _tagHelperFields;
public Visitor(RazorCodeDocument codeDocument, RazorParserOptions options)
public LoweringVisitor(RazorIRBuilder builder, HashSet<string> namespaces)
{
_codeDocument = codeDocument;
_options = options;
Namespaces = new HashSet<string>();
Builder = RazorIRBuilder.Document();
_builder = builder;
_namespaces = namespaces;
}
public RazorIRBuilder Builder { get; }
public string Filename { get; set; }
public HashSet<string> 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<string> 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<string> namespaces)
: base(builder, namespaces)
{
}
// Example
// <input` checked="hello-world @false"`/>
@ -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());
}
}
}

View File

@ -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<TagHelperDirectiveDescriptor> _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<TagHelperDescriptor> GetDescriptors(Block root)
{
if (root == null)
{
throw new ArgumentNullException(nameof(root));
}
_directiveDescriptors = new List<TagHelperDirectiveDescriptor>();
// 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<TagHelperDirectiveDescriptor> 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();
}
}
}

View File

@ -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<TagHelperFeature>().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<TagHelperDirectiveDescriptor> Directives { get; } = new List<TagHelperDirectiveDescriptor>();
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;
}
}
}
}

View File

@ -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());
}
}
}

View File

@ -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<RazorSourceDocument>();
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";

View File

@ -24,7 +24,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Intermediate
// Assert
Children(irDocument,
n => Assert.IsType<ChecksumIRNode>(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<ChecksumIRNode>(n),
n => Checksum(n),
n => Assert.IsType<UsingStatementIRNode>(n),
n => Assert.IsType<UsingStatementIRNode>(n),
n => Html("Hello, World!", n));
@ -62,7 +62,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Intermediate
// Assert
Children(irDocument,
n => Assert.IsType<ChecksumIRNode>(n),
n => Checksum(n),
n => Assert.IsType<UsingStatementIRNode>(n),
n => Assert.IsType<UsingStatementIRNode>(n),
n => Html(
@ -92,7 +92,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Intermediate
// Assert
Children(irDocument,
n => Assert.IsType<ChecksumIRNode>(n),
n => Checksum(n),
n => Assert.IsType<UsingStatementIRNode>(n),
n => Assert.IsType<UsingStatementIRNode>(n),
n => Html(
@ -127,7 +127,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Intermediate
// Assert
Children(irDocument,
n => Assert.IsType<ChecksumIRNode>(n),
n => Checksum(n),
n => Assert.IsType<UsingStatementIRNode>(n),
n => Assert.IsType<UsingStatementIRNode>(n),
n => Directive(
@ -147,7 +147,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Intermediate
// Assert
Children(irDocument,
n => Assert.IsType<ChecksumIRNode>(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(@"<span val=""@Hello World""></span>");
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<ChecksumIRNode>(n),
n => Checksum(n),
n => Using("System", n),
n => Using(typeof(Task).Namespace, n),
n => TagHelperFieldDeclaration(n, "SpanTagHelper"),
n =>
{
var tagHelperNode = Assert.IsType<TagHelperIRNode>(n);
Children(tagHelperNode,
Children(
tagHelperNode,
c => TagHelperStructure("span", TagMode.StartTagAndEndTag, c),
c => Assert.IsType<CreateTagHelperIRNode>(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<ChecksumIRNode>(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<TagHelperDescriptor>());
// Arrange
var source = TestRazorSourceDocument.Create("<p>Hi!</p>");
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("<p>Hi!</p>", n));
}
private DocumentIRNode Lower(RazorCodeDocument codeDocument, IEnumerable<TagHelperDescriptor> descriptors)
[Fact]
public void Lower_WithImports_Directive()
{
var engine = RazorEngine.Create(builder =>
// Arrange
var source = TestRazorSourceDocument.Create("<p>Hi!</p>");
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("<p>Hi!</p>", n));
}
[Fact]
public void Lower_WithImports_IgnoresBlockDirective()
{
// Arrange
var source = TestRazorSourceDocument.Create("<p>Hi!</p>");
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("<p>Hi!</p>", n));
}
private DocumentIRNode Lower(
RazorCodeDocument codeDocument,
Action<IRazorEngineBuilder> builder = null,
IEnumerable<TagHelperDescriptor> 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++)

View File

@ -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<DirectiveTokenIRNode>(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<ChecksumIRNode>(node);
}
catch (XunitException e)
{
throw new IRAssertException(node, node.Children, e.Message, e);
}
}
public static void CSharpExpression(string expected, RazorIRNode node)
{
try

View File

@ -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<MarkupBlock, IEnumerable<TagHelperDirectiveDescriptor>>
{
{
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<TagHelperDirectiveDescriptor>)expectedDescriptors,
resolver.DirectiveDescriptors,
TagHelperDirectiveDescriptorComparer.Default);
}
[Fact]
public void GetDescriptors_InvokesResolveOnceForAllDirectives()
{
// Arrange
var factory = new SpanFactory();
var resolver = new Mock<ITagHelperDescriptorResolver>();
resolver.Setup(mock => mock.Resolve(It.IsAny<TagHelperDescriptorResolutionContext>()))
.Returns(Enumerable.Empty<TagHelperDescriptor>());
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<TagHelperDescriptorResolutionContext>()), 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<TagHelperDirectiveDescriptor>();
}
public List<TagHelperDirectiveDescriptor> DirectiveDescriptors { get; }
public IEnumerable<TagHelperDescriptor> Resolve(TagHelperDescriptorResolutionContext resolutionContext)
{
DirectiveDescriptors.AddRange(resolutionContext.DirectiveDescriptors);
return Enumerable.Empty<TagHelperDescriptor>();
}
}
private class TagHelperDirectiveDescriptorComparer : IEqualityComparer<TagHelperDirectiveDescriptor>
{
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<IEnumerable<TagHelperDirectiveDescriptor>,
ErrorSink,
TagHelperDescriptorResolutionContext> _replacer;
public CustomTagHelperDirectiveSpanVisitor(
ITagHelperDescriptorResolver descriptorResolver,
Func<IEnumerable<TagHelperDirectiveDescriptor>,
ErrorSink,
TagHelperDescriptorResolutionContext> replacer)
: base(descriptorResolver, new ErrorSink())
{
_replacer = replacer;
}
protected override TagHelperDescriptorResolutionContext GetTagHelperDescriptorResolutionContext(
IEnumerable<TagHelperDirectiveDescriptor> descriptors,
ErrorSink errorSink)
{
return _replacer(descriptors, errorSink);
}
}
}
}

View File

@ -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) - <p>Hi there!</p>\n

View File

@ -0,0 +1,4 @@
<p>This will get ignored</p>
@using System.Globalization
@("And also this")
@using System.ComponentModel

View File

@ -0,0 +1,2 @@
@section ignored { }
@using System.Text;