Modify TagHelperDescriptorResolver and dependencies to not throw.

- Changed the TagHelperDescriptorResolver, TagHelperTypeResolver and AddOrRemoveTagHelperSpanVisitor to not throw when they're unable to understand the users directive lookup text.
- This involved utilizing the new ParserErrorSink to capture errors found during TagHelperDescriptor resolution.

#210
This commit is contained in:
N. Taylor Mullen 2014-11-17 17:02:47 -08:00
parent a477bd5cb1
commit ed9c432889
9 changed files with 151 additions and 51 deletions

View File

@ -31,7 +31,7 @@ namespace Microsoft.AspNet.Razor.Runtime
} }
/// <summary> /// <summary>
/// Cannot resolve TagHelper containing assembly '{0}'. /// Cannot resolve TagHelper containing assembly '{0}'. Error: {1}
/// </summary> /// </summary>
internal static string TagHelperTypeResolver_CannotResolveTagHelperAssembly internal static string TagHelperTypeResolver_CannotResolveTagHelperAssembly
{ {
@ -39,11 +39,11 @@ namespace Microsoft.AspNet.Razor.Runtime
} }
/// <summary> /// <summary>
/// Cannot resolve TagHelper containing assembly '{0}'. /// Cannot resolve TagHelper containing assembly '{0}'. Error: {1}
/// </summary> /// </summary>
internal static string FormatTagHelperTypeResolver_CannotResolveTagHelperAssembly(object p0) internal static string FormatTagHelperTypeResolver_CannotResolveTagHelperAssembly(object p0, object p1)
{ {
return string.Format(CultureInfo.CurrentCulture, GetString("TagHelperTypeResolver_CannotResolveTagHelperAssembly"), p0); return string.Format(CultureInfo.CurrentCulture, GetString("TagHelperTypeResolver_CannotResolveTagHelperAssembly"), p0, p1);
} }
/// <summary> /// <summary>
@ -110,6 +110,22 @@ namespace Microsoft.AspNet.Razor.Runtime
return GetString("ArgumentCannotBeNullOrEmpty"); return GetString("ArgumentCannotBeNullOrEmpty");
} }
/// <summary>
/// Encountered an unexpected error when attempting to resolve tag helper directive '{0}' with value '{1}'. Error: {2}
/// </summary>
internal static string TagHelperDescriptorResolver_EncounteredUnexpectedError
{
get { return GetString("TagHelperDescriptorResolver_EncounteredUnexpectedError"); }
}
/// <summary>
/// Encountered an unexpected error when attempting to resolve tag helper directive '{0}' with value '{1}'. Error: {2}
/// </summary>
internal static string FormatTagHelperDescriptorResolver_EncounteredUnexpectedError(object p0, object p1, object p2)
{
return string.Format(CultureInfo.CurrentCulture, GetString("TagHelperDescriptorResolver_EncounteredUnexpectedError"), p0, p1, p2);
}
private static string GetString(string name, params string[] formatterNames) private static string GetString(string name, params string[] formatterNames)
{ {
var value = _resourceManager.GetString(name); var value = _resourceManager.GetString(name);

View File

@ -123,7 +123,7 @@
"typeName, assemblyName"</value> "typeName, assemblyName"</value>
</data> </data>
<data name="TagHelperTypeResolver_CannotResolveTagHelperAssembly" xml:space="preserve"> <data name="TagHelperTypeResolver_CannotResolveTagHelperAssembly" xml:space="preserve">
<value>Cannot resolve TagHelper containing assembly '{0}'.</value> <value>Cannot resolve TagHelper containing assembly '{0}'. Error: {1}</value>
</data> </data>
<data name="TagHelperTypeResolver_TagHelperAssemblyNameCannotBeEmptyOrNull" xml:space="preserve"> <data name="TagHelperTypeResolver_TagHelperAssemblyNameCannotBeEmptyOrNull" xml:space="preserve">
<value>Tag helper directive assembly name cannot be null or empty.</value> <value>Tag helper directive assembly name cannot be null or empty.</value>
@ -137,4 +137,7 @@
<data name="ArgumentCannotBeNullOrEmpty" xml:space="preserve"> <data name="ArgumentCannotBeNullOrEmpty" xml:space="preserve">
<value>The value cannot be null or empty.</value> <value>The value cannot be null or empty.</value>
</data> </data>
<data name="TagHelperDescriptorResolver_EncounteredUnexpectedError" xml:space="preserve">
<value>Encountered an unexpected error when attempting to resolve tag helper directive '{0}' with value '{1}'. Error: {2}</value>
</data>
</root> </root>

View File

@ -4,7 +4,9 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Microsoft.AspNet.Razor.Parser;
using Microsoft.AspNet.Razor.TagHelpers; using Microsoft.AspNet.Razor.TagHelpers;
using Microsoft.AspNet.Razor.Text;
namespace Microsoft.AspNet.Razor.Runtime.TagHelpers namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
{ {
@ -36,20 +38,42 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
foreach (var directiveDescriptor in context.DirectiveDescriptors) foreach (var directiveDescriptor in context.DirectiveDescriptors)
{ {
var lookupInfo = GetLookupInfo(directiveDescriptor); try
if (directiveDescriptor.DirectiveType == TagHelperDirectiveType.RemoveTagHelper)
{ {
resolvedDescriptors.RemoveWhere(descriptor => MatchesLookupInfo(descriptor, lookupInfo)); var lookupInfo = GetLookupInfo(directiveDescriptor, context.ErrorSink);
// Could not resolve the lookup info.
if (lookupInfo == null)
{
return Enumerable.Empty<TagHelperDescriptor>();
}
if (directiveDescriptor.DirectiveType == TagHelperDirectiveType.RemoveTagHelper)
{
resolvedDescriptors.RemoveWhere(descriptor => MatchesLookupInfo(descriptor, lookupInfo));
}
else if (directiveDescriptor.DirectiveType == TagHelperDirectiveType.AddTagHelper)
{
var descriptors = ResolveDescriptorsInAssembly(lookupInfo.AssemblyName,
directiveDescriptor.Location,
context.ErrorSink);
// Only use descriptors that match our lookup info
descriptors = descriptors.Where(descriptor => MatchesLookupInfo(descriptor, lookupInfo));
resolvedDescriptors.UnionWith(descriptors);
}
} }
else if (directiveDescriptor.DirectiveType == TagHelperDirectiveType.AddTagHelper) catch (Exception ex)
{ {
var descriptors = ResolveDescriptorsInAssembly(lookupInfo.AssemblyName); var directiveName = "@" + directiveDescriptor.DirectiveType.ToString().ToLowerInvariant();
// Only use descriptors that match our lookup info context.ErrorSink.OnError(
descriptors = descriptors.Where(descriptor => MatchesLookupInfo(descriptor, lookupInfo)); directiveDescriptor.Location,
Resources.FormatTagHelperDescriptorResolver_EncounteredUnexpectedError(
resolvedDescriptors.UnionWith(descriptors); directiveName,
directiveDescriptor.LookupText,
ex.Message));
} }
} }
@ -63,13 +87,18 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
/// <param name="assemblyName"> /// <param name="assemblyName">
/// The name of the assembly to resolve <see cref="TagHelperDescriptor"/>s from. /// The name of the assembly to resolve <see cref="TagHelperDescriptor"/>s from.
/// </param> /// </param>
/// <param name="documentLocation">The <see cref="SourceLocation"/> of the directive.</param>
/// <param name="errorSink">Used to record errors found when resolving <see cref="TagHelperDescriptor"/>s
/// within the given <paramref name="assemblyName"/>.</param>
/// <returns><see cref="TagHelperDescriptor"/>s for <see cref="ITagHelper"/>s from the given /// <returns><see cref="TagHelperDescriptor"/>s for <see cref="ITagHelper"/>s from the given
/// <paramref name="assemblyName"/>.</returns> /// <paramref name="assemblyName"/>.</returns>
// This is meant to be overridden by tooling to enable assembly level caching. // This is meant to be overridden by tooling to enable assembly level caching.
protected virtual IEnumerable<TagHelperDescriptor> ResolveDescriptorsInAssembly(string assemblyName) protected virtual IEnumerable<TagHelperDescriptor> ResolveDescriptorsInAssembly(string assemblyName,
SourceLocation documentLocation,
ParserErrorSink errorSink)
{ {
// Resolve valid tag helper types from the assembly. // Resolve valid tag helper types from the assembly.
var tagHelperTypes = _typeResolver.Resolve(assemblyName); var tagHelperTypes = _typeResolver.Resolve(assemblyName, documentLocation, errorSink);
// Convert types to TagHelperDescriptors // Convert types to TagHelperDescriptors
var descriptors = tagHelperTypes.SelectMany(TagHelperDescriptorFactory.CreateDescriptors); var descriptors = tagHelperTypes.SelectMany(TagHelperDescriptorFactory.CreateDescriptors);
@ -88,7 +117,8 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
string.Equals(descriptor.TypeName, lookupInfo.TypeName, StringComparison.Ordinal); string.Equals(descriptor.TypeName, lookupInfo.TypeName, StringComparison.Ordinal);
} }
private static LookupInfo GetLookupInfo(TagHelperDirectiveDescriptor directiveDescriptor) private static LookupInfo GetLookupInfo(TagHelperDirectiveDescriptor directiveDescriptor,
ParserErrorSink errorSink)
{ {
var lookupText = directiveDescriptor.LookupText; var lookupText = directiveDescriptor.LookupText;
var lookupStrings = lookupText?.Split(new[] { ',' }); var lookupStrings = lookupText?.Split(new[] { ',' });
@ -100,9 +130,11 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
lookupStrings.Any(string.IsNullOrWhiteSpace) || lookupStrings.Any(string.IsNullOrWhiteSpace) ||
(lookupStrings.Length != 1 && lookupStrings.Length != 2)) (lookupStrings.Length != 1 && lookupStrings.Length != 2))
{ {
throw new ArgumentException( errorSink.OnError(
Resources.FormatTagHelperDescriptorResolver_InvalidTagHelperLookupText(lookupText), directiveDescriptor.Location,
nameof(lookupText)); Resources.FormatTagHelperDescriptorResolver_InvalidTagHelperLookupText(lookupText));
return null;
} }
// Grab the assembly name from the lookup text strings. Due to our supported lookupText formats it will // Grab the assembly name from the lookup text strings. Due to our supported lookupText formats it will

View File

@ -5,6 +5,8 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using Microsoft.AspNet.Razor.Parser;
using Microsoft.AspNet.Razor.Text;
namespace Microsoft.AspNet.Razor.Runtime.TagHelpers namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
{ {
@ -27,18 +29,43 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
/// all valid <see cref="ITagHelper"/> <see cref="Type"/>s. /// all valid <see cref="ITagHelper"/> <see cref="Type"/>s.
/// </summary> /// </summary>
/// <param name="name">The name of an <see cref="Assembly"/> to search.</param> /// <param name="name">The name of an <see cref="Assembly"/> to search.</param>
/// <returns>An <see cref="IEnumerable{Type}"/> of valid <see cref="ITagHelper"/> <see cref="Type"/>s.</returns> /// <param name="documentLocation">The <see cref="SourceLocation"/> of the associated
public IEnumerable<Type> Resolve(string name) /// <see cref="Parser.SyntaxTree.SyntaxTreeNode"/> responsible for the current <see cref="Resolve"/> call.
/// </param>
/// <param name="errorSink">The <see cref="ParserErrorSink"/> used to record errors found when resolving
/// <see cref="ITagHelper"/> <see cref="Type"/>s.</param>
/// <returns>An <see cref="IEnumerable{Type}"/> of valid <see cref="ITagHelper"/> <see cref="Type"/>s.
/// </returns>
public IEnumerable<Type> Resolve(string name,
SourceLocation documentLocation,
[NotNull] ParserErrorSink errorSink)
{ {
if (string.IsNullOrEmpty(name)) if (string.IsNullOrEmpty(name))
{ {
throw new ArgumentException( errorSink.OnError(documentLocation,
Resources.TagHelperTypeResolver_TagHelperAssemblyNameCannotBeEmptyOrNull, Resources.TagHelperTypeResolver_TagHelperAssemblyNameCannotBeEmptyOrNull);
nameof(name));
return Type.EmptyTypes;
} }
var assemblyName = new AssemblyName(name); var assemblyName = new AssemblyName(name);
var libraryTypes = GetLibraryDefinedTypes(assemblyName);
IEnumerable<TypeInfo> libraryTypes;
try
{
libraryTypes = GetLibraryDefinedTypes(assemblyName);
}
catch (Exception ex)
{
errorSink.OnError(
documentLocation,
Resources.FormatTagHelperTypeResolver_CannotResolveTagHelperAssembly(
assemblyName.Name,
ex.Message));
return Type.EmptyTypes;
}
var validTagHelpers = libraryTypes.Where(IsTagHelper); var validTagHelpers = libraryTypes.Where(IsTagHelper);
// Convert from TypeInfo[] to Type[] // Convert from TypeInfo[] to Type[]
@ -48,17 +75,9 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
// Internal for testing, don't want to be loading assemblies during a test. // Internal for testing, don't want to be loading assemblies during a test.
internal virtual IEnumerable<TypeInfo> GetLibraryDefinedTypes(AssemblyName assemblyName) internal virtual IEnumerable<TypeInfo> GetLibraryDefinedTypes(AssemblyName assemblyName)
{ {
try var assembly = Assembly.Load(assemblyName);
{
var assembly = Assembly.Load(assemblyName);
return assembly.DefinedTypes; return assembly.ExportedTypes.Select(type => type.GetTypeInfo());
}
catch (Exception ex)
{
throw new InvalidOperationException(
Resources.FormatTagHelperTypeResolver_CannotResolveTagHelperAssembly(assemblyName.Name), ex);
}
} }
// Internal for testing. // Internal for testing.

View File

@ -243,9 +243,8 @@ namespace Microsoft.AspNet.Razor.Generator.Compiler.CSharp
// https://github.com/aspnet/Razor/issues/129 // https://github.com/aspnet/Razor/issues/129
if (!isPlainTextValue) if (!isPlainTextValue)
{ {
throw new InvalidOperationException( _writer.WriteLine(";");
RazorResources.FormatTagHelpers_AttributesThatAreNotStringsMustNotContainAtSymbols( return;
attributeDescriptor.PropertyName));
} }
// We aren't a bufferable attribute which means we have no Razor code in our value. // We aren't a bufferable attribute which means we have no Razor code in our value.

View File

@ -172,7 +172,7 @@ namespace Microsoft.AspNet.Razor.Parser
if (TagHelperDescriptorResolver != null) if (TagHelperDescriptorResolver != null)
{ {
var descriptors = GetTagHelperDescriptors(rewritingContext.SyntaxTree); var descriptors = GetTagHelperDescriptors(rewritingContext.SyntaxTree, rewritingContext.ErrorSink);
var tagHelperProvider = new TagHelperDescriptorProvider(descriptors); var tagHelperProvider = new TagHelperDescriptorProvider(descriptors);
var tagHelperParseTreeRewriter = new TagHelperParseTreeRewriter(tagHelperProvider); var tagHelperParseTreeRewriter = new TagHelperParseTreeRewriter(tagHelperProvider);
@ -203,10 +203,15 @@ namespace Microsoft.AspNet.Razor.Parser
/// specified <paramref name="documentRoot"/>. /// specified <paramref name="documentRoot"/>.
/// </summary> /// </summary>
/// <param name="documentRoot">The <see cref="Block"/> to scan for tag helper registrations in.</param> /// <param name="documentRoot">The <see cref="Block"/> to scan for tag helper registrations in.</param>
/// <returns></returns> /// <param name="errorSink">Used to manage <see cref="RazorError"/>s encountered during the Razor parsing
protected virtual IEnumerable<TagHelperDescriptor> GetTagHelperDescriptors([NotNull] Block documentRoot) /// phase.</param>
/// <returns><see cref="TagHelperDescriptor"/>s that are applicable to the <paramref name="documentRoot"/>
/// </returns>
protected virtual IEnumerable<TagHelperDescriptor> GetTagHelperDescriptors([NotNull] Block documentRoot,
[NotNull] ParserErrorSink errorSink)
{ {
var addOrRemoveTagHelperSpanVisitor = new AddOrRemoveTagHelperSpanVisitor(TagHelperDescriptorResolver); var addOrRemoveTagHelperSpanVisitor =
new AddOrRemoveTagHelperSpanVisitor(TagHelperDescriptorResolver, errorSink);
return addOrRemoveTagHelperSpanVisitor.GetDescriptors(documentRoot); return addOrRemoveTagHelperSpanVisitor.GetDescriptors(documentRoot);
} }

View File

@ -15,12 +15,15 @@ namespace Microsoft.AspNet.Razor.Parser.TagHelpers
public class AddOrRemoveTagHelperSpanVisitor : ParserVisitor public class AddOrRemoveTagHelperSpanVisitor : ParserVisitor
{ {
private readonly ITagHelperDescriptorResolver _descriptorResolver; private readonly ITagHelperDescriptorResolver _descriptorResolver;
private readonly ParserErrorSink _errorSink;
private List<TagHelperDirectiveDescriptor> _directiveDescriptors; private List<TagHelperDirectiveDescriptor> _directiveDescriptors;
public AddOrRemoveTagHelperSpanVisitor([NotNull] ITagHelperDescriptorResolver descriptorResolver) public AddOrRemoveTagHelperSpanVisitor([NotNull] ITagHelperDescriptorResolver descriptorResolver,
[NotNull] ParserErrorSink errorSink)
{ {
_descriptorResolver = descriptorResolver; _descriptorResolver = descriptorResolver;
_errorSink = errorSink;
} }
public IEnumerable<TagHelperDescriptor> GetDescriptors([NotNull] Block root) public IEnumerable<TagHelperDescriptor> GetDescriptors([NotNull] Block root)
@ -30,7 +33,7 @@ namespace Microsoft.AspNet.Razor.Parser.TagHelpers
// This will recurse through the syntax tree. // This will recurse through the syntax tree.
VisitBlock(root); VisitBlock(root);
var resolutionContext = GetTagHelperDescriptorResolutionContext(_directiveDescriptors); var resolutionContext = GetTagHelperDescriptorResolutionContext(_directiveDescriptors, _errorSink);
var descriptors = _descriptorResolver.Resolve(resolutionContext); var descriptors = _descriptorResolver.Resolve(resolutionContext);
return descriptors; return descriptors;
@ -38,9 +41,10 @@ namespace Microsoft.AspNet.Razor.Parser.TagHelpers
// Allows MVC a chance to override the TagHelperDescriptorResolutionContext // Allows MVC a chance to override the TagHelperDescriptorResolutionContext
protected virtual TagHelperDescriptorResolutionContext GetTagHelperDescriptorResolutionContext( protected virtual TagHelperDescriptorResolutionContext GetTagHelperDescriptorResolutionContext(
[NotNull] IEnumerable<TagHelperDirectiveDescriptor> descriptors) [NotNull] IEnumerable<TagHelperDirectiveDescriptor> descriptors,
[NotNull] ParserErrorSink errorSink)
{ {
return new TagHelperDescriptorResolutionContext(descriptors); return new TagHelperDescriptorResolutionContext(descriptors, errorSink);
} }
public override void VisitSpan(Span span) public override void VisitSpan(Span span)
@ -55,7 +59,9 @@ namespace Microsoft.AspNet.Razor.Parser.TagHelpers
TagHelperDirectiveType.RemoveTagHelper : TagHelperDirectiveType.RemoveTagHelper :
TagHelperDirectiveType.AddTagHelper; TagHelperDirectiveType.AddTagHelper;
var directiveDescriptor = new TagHelperDirectiveDescriptor(codeGenerator.LookupText, directive); var directiveDescriptor = new TagHelperDirectiveDescriptor(codeGenerator.LookupText,
span.Start,
directive);
_directiveDescriptors.Add(directiveDescriptor); _directiveDescriptors.Add(directiveDescriptor);
} }

View File

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic; using System.Collections.Generic;
using Microsoft.AspNet.Razor.Parser;
namespace Microsoft.AspNet.Razor.TagHelpers namespace Microsoft.AspNet.Razor.TagHelpers
{ {
@ -15,15 +16,23 @@ namespace Microsoft.AspNet.Razor.TagHelpers
/// </summary> /// </summary>
/// <param name="directiveDescriptors"><see cref="TagHelperDirectiveDescriptor"/>s used to resolve /// <param name="directiveDescriptors"><see cref="TagHelperDirectiveDescriptor"/>s used to resolve
/// <see cref="TagHelperDescriptor"/>s.</param> /// <see cref="TagHelperDescriptor"/>s.</param>
/// <param name="errorSink">Used to aggregate <see cref="Parser.SyntaxTree.RazorError"/>s.</param>
public TagHelperDescriptorResolutionContext( public TagHelperDescriptorResolutionContext(
[NotNull] IEnumerable<TagHelperDirectiveDescriptor> directiveDescriptors) [NotNull] IEnumerable<TagHelperDirectiveDescriptor> directiveDescriptors,
[NotNull] ParserErrorSink errorSink)
{ {
DirectiveDescriptors = new List<TagHelperDirectiveDescriptor>(directiveDescriptors); DirectiveDescriptors = new List<TagHelperDirectiveDescriptor>(directiveDescriptors);
ErrorSink = errorSink;
} }
/// <summary> /// <summary>
/// <see cref="TagHelperDirectiveDescriptor"/>s used to resolve <see cref="TagHelperDescriptor"/>s. /// <see cref="TagHelperDirectiveDescriptor"/>s used to resolve <see cref="TagHelperDescriptor"/>s.
/// </summary> /// </summary>
public IList<TagHelperDirectiveDescriptor> DirectiveDescriptors { get; private set; } public IList<TagHelperDirectiveDescriptor> DirectiveDescriptors { get; private set; }
/// <summary>
/// Used to aggregate <see cref="Parser.SyntaxTree.RazorError"/>s.
/// </summary>
public ParserErrorSink ErrorSink { get; private set; }
} }
} }

View File

@ -1,6 +1,8 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNet.Razor.Text;
namespace Microsoft.AspNet.Razor.TagHelpers namespace Microsoft.AspNet.Razor.TagHelpers
{ {
/// <summary> /// <summary>
@ -12,10 +14,14 @@ namespace Microsoft.AspNet.Razor.TagHelpers
/// Instantiates a new instance of <see cref="TagHelperDirectiveDescriptor"/>. /// Instantiates a new instance of <see cref="TagHelperDirectiveDescriptor"/>.
/// </summary> /// </summary>
/// <param name="lookupText">A <see cref="string"/> used to find tag helper <see cref="System.Type"/>s.</param> /// <param name="lookupText">A <see cref="string"/> used to find tag helper <see cref="System.Type"/>s.</param>
/// <param name="location">The <see cref="SourceLocation"/> of the directive.</param>
/// <param name="directiveType">The <see cref="TagHelperDirectiveType"/> of this directive.</param> /// <param name="directiveType">The <see cref="TagHelperDirectiveType"/> of this directive.</param>
public TagHelperDirectiveDescriptor([NotNull] string lookupText, TagHelperDirectiveType directiveType) public TagHelperDirectiveDescriptor([NotNull] string lookupText,
SourceLocation location,
TagHelperDirectiveType directiveType)
{ {
LookupText = lookupText; LookupText = lookupText;
Location = location;
DirectiveType = directiveType; DirectiveType = directiveType;
} }
@ -28,5 +34,10 @@ namespace Microsoft.AspNet.Razor.TagHelpers
/// The <see cref="TagHelperDirectiveType"/> of this directive. /// The <see cref="TagHelperDirectiveType"/> of this directive.
/// </summary> /// </summary>
public TagHelperDirectiveType DirectiveType { get; private set; } public TagHelperDirectiveType DirectiveType { get; private set; }
/// <summary>
/// The <see cref="SourceLocation"/> of the directive.
/// </summary>
public SourceLocation Location { get; private set; }
} }
} }