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>
/// Cannot resolve TagHelper containing assembly '{0}'.
/// Cannot resolve TagHelper containing assembly '{0}'. Error: {1}
/// </summary>
internal static string TagHelperTypeResolver_CannotResolveTagHelperAssembly
{
@ -39,11 +39,11 @@ namespace Microsoft.AspNet.Razor.Runtime
}
/// <summary>
/// Cannot resolve TagHelper containing assembly '{0}'.
/// Cannot resolve TagHelper containing assembly '{0}'. Error: {1}
/// </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>
@ -110,6 +110,22 @@ namespace Microsoft.AspNet.Razor.Runtime
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)
{
var value = _resourceManager.GetString(name);

View File

@ -123,7 +123,7 @@
"typeName, assemblyName"</value>
</data>
<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 name="TagHelperTypeResolver_TagHelperAssemblyNameCannotBeEmptyOrNull" xml:space="preserve">
<value>Tag helper directive assembly name cannot be null or empty.</value>
@ -137,4 +137,7 @@
<data name="ArgumentCannotBeNullOrEmpty" xml:space="preserve">
<value>The value cannot be null or empty.</value>
</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>

View File

@ -4,7 +4,9 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNet.Razor.Parser;
using Microsoft.AspNet.Razor.TagHelpers;
using Microsoft.AspNet.Razor.Text;
namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
{
@ -36,20 +38,42 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
foreach (var directiveDescriptor in context.DirectiveDescriptors)
{
var lookupInfo = GetLookupInfo(directiveDescriptor);
if (directiveDescriptor.DirectiveType == TagHelperDirectiveType.RemoveTagHelper)
try
{
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
descriptors = descriptors.Where(descriptor => MatchesLookupInfo(descriptor, lookupInfo));
resolvedDescriptors.UnionWith(descriptors);
context.ErrorSink.OnError(
directiveDescriptor.Location,
Resources.FormatTagHelperDescriptorResolver_EncounteredUnexpectedError(
directiveName,
directiveDescriptor.LookupText,
ex.Message));
}
}
@ -63,13 +87,18 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
/// <param name="assemblyName">
/// The name of the assembly to resolve <see cref="TagHelperDescriptor"/>s from.
/// </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
/// <paramref name="assemblyName"/>.</returns>
// 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.
var tagHelperTypes = _typeResolver.Resolve(assemblyName);
var tagHelperTypes = _typeResolver.Resolve(assemblyName, documentLocation, errorSink);
// Convert types to TagHelperDescriptors
var descriptors = tagHelperTypes.SelectMany(TagHelperDescriptorFactory.CreateDescriptors);
@ -88,7 +117,8 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
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 lookupStrings = lookupText?.Split(new[] { ',' });
@ -100,9 +130,11 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
lookupStrings.Any(string.IsNullOrWhiteSpace) ||
(lookupStrings.Length != 1 && lookupStrings.Length != 2))
{
throw new ArgumentException(
Resources.FormatTagHelperDescriptorResolver_InvalidTagHelperLookupText(lookupText),
nameof(lookupText));
errorSink.OnError(
directiveDescriptor.Location,
Resources.FormatTagHelperDescriptorResolver_InvalidTagHelperLookupText(lookupText));
return null;
}
// 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.Linq;
using System.Reflection;
using Microsoft.AspNet.Razor.Parser;
using Microsoft.AspNet.Razor.Text;
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.
/// </summary>
/// <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>
public IEnumerable<Type> Resolve(string name)
/// <param name="documentLocation">The <see cref="SourceLocation"/> of the associated
/// <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))
{
throw new ArgumentException(
Resources.TagHelperTypeResolver_TagHelperAssemblyNameCannotBeEmptyOrNull,
nameof(name));
errorSink.OnError(documentLocation,
Resources.TagHelperTypeResolver_TagHelperAssemblyNameCannotBeEmptyOrNull);
return Type.EmptyTypes;
}
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);
// 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 virtual IEnumerable<TypeInfo> GetLibraryDefinedTypes(AssemblyName assemblyName)
{
try
{
var assembly = Assembly.Load(assemblyName);
var assembly = Assembly.Load(assemblyName);
return assembly.DefinedTypes;
}
catch (Exception ex)
{
throw new InvalidOperationException(
Resources.FormatTagHelperTypeResolver_CannotResolveTagHelperAssembly(assemblyName.Name), ex);
}
return assembly.ExportedTypes.Select(type => type.GetTypeInfo());
}
// Internal for testing.

View File

@ -243,9 +243,8 @@ namespace Microsoft.AspNet.Razor.Generator.Compiler.CSharp
// https://github.com/aspnet/Razor/issues/129
if (!isPlainTextValue)
{
throw new InvalidOperationException(
RazorResources.FormatTagHelpers_AttributesThatAreNotStringsMustNotContainAtSymbols(
attributeDescriptor.PropertyName));
_writer.WriteLine(";");
return;
}
// 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)
{
var descriptors = GetTagHelperDescriptors(rewritingContext.SyntaxTree);
var descriptors = GetTagHelperDescriptors(rewritingContext.SyntaxTree, rewritingContext.ErrorSink);
var tagHelperProvider = new TagHelperDescriptorProvider(descriptors);
var tagHelperParseTreeRewriter = new TagHelperParseTreeRewriter(tagHelperProvider);
@ -203,10 +203,15 @@ namespace Microsoft.AspNet.Razor.Parser
/// specified <paramref name="documentRoot"/>.
/// </summary>
/// <param name="documentRoot">The <see cref="Block"/> to scan for tag helper registrations in.</param>
/// <returns></returns>
protected virtual IEnumerable<TagHelperDescriptor> GetTagHelperDescriptors([NotNull] Block documentRoot)
/// <param name="errorSink">Used to manage <see cref="RazorError"/>s encountered during the Razor parsing
/// 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);
}

View File

@ -15,12 +15,15 @@ namespace Microsoft.AspNet.Razor.Parser.TagHelpers
public class AddOrRemoveTagHelperSpanVisitor : ParserVisitor
{
private readonly ITagHelperDescriptorResolver _descriptorResolver;
private readonly ParserErrorSink _errorSink;
private List<TagHelperDirectiveDescriptor> _directiveDescriptors;
public AddOrRemoveTagHelperSpanVisitor([NotNull] ITagHelperDescriptorResolver descriptorResolver)
public AddOrRemoveTagHelperSpanVisitor([NotNull] ITagHelperDescriptorResolver descriptorResolver,
[NotNull] ParserErrorSink errorSink)
{
_descriptorResolver = descriptorResolver;
_errorSink = errorSink;
}
public IEnumerable<TagHelperDescriptor> GetDescriptors([NotNull] Block root)
@ -30,7 +33,7 @@ namespace Microsoft.AspNet.Razor.Parser.TagHelpers
// This will recurse through the syntax tree.
VisitBlock(root);
var resolutionContext = GetTagHelperDescriptorResolutionContext(_directiveDescriptors);
var resolutionContext = GetTagHelperDescriptorResolutionContext(_directiveDescriptors, _errorSink);
var descriptors = _descriptorResolver.Resolve(resolutionContext);
return descriptors;
@ -38,9 +41,10 @@ namespace Microsoft.AspNet.Razor.Parser.TagHelpers
// Allows MVC a chance to override the TagHelperDescriptorResolutionContext
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)
@ -55,7 +59,9 @@ namespace Microsoft.AspNet.Razor.Parser.TagHelpers
TagHelperDirectiveType.RemoveTagHelper :
TagHelperDirectiveType.AddTagHelper;
var directiveDescriptor = new TagHelperDirectiveDescriptor(codeGenerator.LookupText, directive);
var directiveDescriptor = new TagHelperDirectiveDescriptor(codeGenerator.LookupText,
span.Start,
directive);
_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.
using System.Collections.Generic;
using Microsoft.AspNet.Razor.Parser;
namespace Microsoft.AspNet.Razor.TagHelpers
{
@ -15,15 +16,23 @@ namespace Microsoft.AspNet.Razor.TagHelpers
/// </summary>
/// <param name="directiveDescriptors"><see cref="TagHelperDirectiveDescriptor"/>s used to resolve
/// <see cref="TagHelperDescriptor"/>s.</param>
/// <param name="errorSink">Used to aggregate <see cref="Parser.SyntaxTree.RazorError"/>s.</param>
public TagHelperDescriptorResolutionContext(
[NotNull] IEnumerable<TagHelperDirectiveDescriptor> directiveDescriptors)
[NotNull] IEnumerable<TagHelperDirectiveDescriptor> directiveDescriptors,
[NotNull] ParserErrorSink errorSink)
{
DirectiveDescriptors = new List<TagHelperDirectiveDescriptor>(directiveDescriptors);
ErrorSink = errorSink;
}
/// <summary>
/// <see cref="TagHelperDirectiveDescriptor"/>s used to resolve <see cref="TagHelperDescriptor"/>s.
/// </summary>
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.
// 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
{
/// <summary>
@ -12,10 +14,14 @@ namespace Microsoft.AspNet.Razor.TagHelpers
/// Instantiates a new instance of <see cref="TagHelperDirectiveDescriptor"/>.
/// </summary>
/// <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>
public TagHelperDirectiveDescriptor([NotNull] string lookupText, TagHelperDirectiveType directiveType)
public TagHelperDirectiveDescriptor([NotNull] string lookupText,
SourceLocation location,
TagHelperDirectiveType directiveType)
{
LookupText = lookupText;
Location = location;
DirectiveType = directiveType;
}
@ -28,5 +34,10 @@ namespace Microsoft.AspNet.Razor.TagHelpers
/// The <see cref="TagHelperDirectiveType"/> of this directive.
/// </summary>
public TagHelperDirectiveType DirectiveType { get; private set; }
/// <summary>
/// The <see cref="SourceLocation"/> of the directive.
/// </summary>
public SourceLocation Location { get; private set; }
}
}