diff --git a/src/Microsoft.AspNet.Razor/Parser/TagHelpers/AddOrRemoveTagHelperSpanVisitor.cs b/src/Microsoft.AspNet.Razor/Parser/TagHelpers/AddOrRemoveTagHelperSpanVisitor.cs index 1efd57f2b8..aabbc3d895 100644 --- a/src/Microsoft.AspNet.Razor/Parser/TagHelpers/AddOrRemoveTagHelperSpanVisitor.cs +++ b/src/Microsoft.AspNet.Razor/Parser/TagHelpers/AddOrRemoveTagHelperSpanVisitor.cs @@ -19,6 +19,12 @@ namespace Microsoft.AspNet.Razor.Parser.TagHelpers private List _directiveDescriptors; + // Internal for testing use + internal AddOrRemoveTagHelperSpanVisitor(ITagHelperDescriptorResolver descriptorResolver) + : this(descriptorResolver, new ParserErrorSink()) + { + } + public AddOrRemoveTagHelperSpanVisitor([NotNull] ITagHelperDescriptorResolver descriptorResolver, [NotNull] ParserErrorSink errorSink) { diff --git a/src/Microsoft.AspNet.Razor/TagHelpers/TagHelperDescriptorResolutionContext.cs b/src/Microsoft.AspNet.Razor/TagHelpers/TagHelperDescriptorResolutionContext.cs index a06d56a9f0..0d070c7443 100644 --- a/src/Microsoft.AspNet.Razor/TagHelpers/TagHelperDescriptorResolutionContext.cs +++ b/src/Microsoft.AspNet.Razor/TagHelpers/TagHelperDescriptorResolutionContext.cs @@ -11,6 +11,12 @@ namespace Microsoft.AspNet.Razor.TagHelpers /// public class TagHelperDescriptorResolutionContext { + // Internal for testing purposes + internal TagHelperDescriptorResolutionContext(IEnumerable directiveDescriptors) + : this(directiveDescriptors, new ParserErrorSink()) + { + } + /// /// Instantiates a new instance of . /// diff --git a/src/Microsoft.AspNet.Razor/TagHelpers/TagHelperDirectiveDescriptor.cs b/src/Microsoft.AspNet.Razor/TagHelpers/TagHelperDirectiveDescriptor.cs index b508387b17..6a662f1dd6 100644 --- a/src/Microsoft.AspNet.Razor/TagHelpers/TagHelperDirectiveDescriptor.cs +++ b/src/Microsoft.AspNet.Razor/TagHelpers/TagHelperDirectiveDescriptor.cs @@ -10,6 +10,13 @@ namespace Microsoft.AspNet.Razor.TagHelpers /// public class TagHelperDirectiveDescriptor { + // Internal for testing purposes. + internal TagHelperDirectiveDescriptor(string lookupText, + TagHelperDirectiveType directiveType) + : this(lookupText, SourceLocation.Zero, directiveType) + { + } + /// /// Instantiates a new instance of . /// diff --git a/test/Microsoft.AspNet.Razor.Runtime.Test/TagHelpers/TagHelperDescriptorResolverTest.cs b/test/Microsoft.AspNet.Razor.Runtime.Test/TagHelpers/TagHelperDescriptorResolverTest.cs index c49893ece3..c2eba5d79d 100644 --- a/test/Microsoft.AspNet.Razor.Runtime.Test/TagHelpers/TagHelperDescriptorResolverTest.cs +++ b/test/Microsoft.AspNet.Razor.Runtime.Test/TagHelpers/TagHelperDescriptorResolverTest.cs @@ -5,7 +5,9 @@ using System; using System.Collections.Generic; using System.Linq; using System.Reflection; +using Microsoft.AspNet.Razor.Parser; using Microsoft.AspNet.Razor.TagHelpers; +using Microsoft.AspNet.Razor.Text; using Xunit; namespace Microsoft.AspNet.Razor.Runtime.TagHelpers @@ -49,7 +51,8 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers // Arrange var tagHelperDescriptorResolver = new AssemblyCheckingTagHelperDescriptorResolver(); var context = new TagHelperDescriptorResolutionContext( - new[] { new TagHelperDirectiveDescriptor(lookupText, TagHelperDirectiveType.AddTagHelper) }); + new[] { new TagHelperDirectiveDescriptor(lookupText, TagHelperDirectiveType.AddTagHelper) }, + new ParserErrorSink()); // Act tagHelperDescriptorResolver.Resolve(context); @@ -200,7 +203,9 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers var tagHelperDescriptorResolver = new TestTagHelperDescriptorResolver( new LookupBasedTagHelperTypeResolver(descriptorAssemblyLookups)); - var resolutionContext = new TagHelperDescriptorResolutionContext(directiveDescriptors); + var resolutionContext = new TagHelperDescriptorResolutionContext( + directiveDescriptors, + new ParserErrorSink()); // Act var descriptors = tagHelperDescriptorResolver.Resolve(resolutionContext); @@ -305,7 +310,9 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers var tagHelperDescriptorResolver = new TestTagHelperDescriptorResolver( new LookupBasedTagHelperTypeResolver(descriptorAssemblyLookups)); - var resolutionContext = new TagHelperDescriptorResolutionContext(directiveDescriptors); + var resolutionContext = new TagHelperDescriptorResolutionContext( + directiveDescriptors, + new ParserErrorSink()); // Act var descriptors = tagHelperDescriptorResolver.Resolve(resolutionContext); @@ -437,26 +444,56 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers [Theory] [InlineData("")] [InlineData(null)] - public void DescriptorResolver_ResolveThrowsIfNullOrEmptyLookupText(string lookupText) + public void DescriptorResolver_CreatesErrorIfNullOrEmptyLookupText_DoesNotThrow(string lookupText) { // Arrange + var errorSink = new ParserErrorSink(); var tagHelperDescriptorResolver = new TestTagHelperDescriptorResolver( new TestTagHelperTypeResolver(InvalidTestableTagHelpers)); + var documentLocation = new SourceLocation(1, 2, 3); + var directiveType = TagHelperDirectiveType.AddTagHelper; + var expectedErrorMessage = + Resources.FormatTagHelperDescriptorResolver_InvalidTagHelperLookupText(lookupText); + var resolutionContext = new TagHelperDescriptorResolutionContext( + new [] { new TagHelperDirectiveDescriptor(lookupText, documentLocation, directiveType)}, + errorSink); - var expectedMessage = - Resources.FormatTagHelperDescriptorResolver_InvalidTagHelperLookupText(lookupText) + - Environment.NewLine + - "Parameter name: lookupText"; + // Act + tagHelperDescriptorResolver.Resolve(resolutionContext); - // Act & Assert - var ex = Assert.Throws(nameof(lookupText), - () => - { - tagHelperDescriptorResolver.Resolve(lookupText); - }); + // Assert + var error = Assert.Single(errorSink.Errors); + Assert.Equal(1, error.Length); + Assert.Equal(documentLocation, error.Location); + Assert.Equal(expectedErrorMessage, error.Message); + } - Assert.Equal(expectedMessage, ex.Message); + [Fact] + public void DescriptorResolver_UnderstandsUnexpectedExceptions_DoesNotThrow() + { + // Arrange + var expectedErrorMessage = "Encountered an unexpected error when attempting to resolve tag helper " + + "directive '@addtaghelper' with value 'A custom lookup text'. Error: A " + + "custom exception"; + var documentLocation = new SourceLocation(1, 2, 3); + var directiveType = TagHelperDirectiveType.AddTagHelper; + var errorSink = new ParserErrorSink(); + var expectedError = new Exception("A custom exception"); + var tagHelperDescriptorResolver = new ThrowingTagHelperDescriptorResolver(expectedError); + var resolutionContext = new TagHelperDescriptorResolutionContext( + new[] { new TagHelperDirectiveDescriptor("A custom lookup text", documentLocation, directiveType) }, + errorSink); + + + // Act + tagHelperDescriptorResolver.Resolve(resolutionContext); + + // Assert + var error = Assert.Single(errorSink.Errors); + Assert.Equal(1, error.Length); + Assert.Equal(documentLocation, error.Location); + Assert.Equal(expectedErrorMessage, error.Message); } private class TestTagHelperDescriptorResolver : TagHelperDescriptorResolver @@ -472,7 +509,8 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers new TagHelperDescriptorResolutionContext( lookupTexts.Select( lookupText => - new TagHelperDirectiveDescriptor(lookupText, TagHelperDirectiveType.AddTagHelper)))); + new TagHelperDirectiveDescriptor(lookupText, TagHelperDirectiveType.AddTagHelper)), + new ParserErrorSink())); } } @@ -504,12 +542,33 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers { public string CalledWithAssemblyName { get; set; } - protected override IEnumerable ResolveDescriptorsInAssembly(string assemblyName) + protected override IEnumerable ResolveDescriptorsInAssembly( + string assemblyName, + SourceLocation documentLocation, + ParserErrorSink errorSink) { CalledWithAssemblyName = assemblyName; return Enumerable.Empty(); } } + + private class ThrowingTagHelperDescriptorResolver : TagHelperDescriptorResolver + { + private readonly Exception _error; + + public ThrowingTagHelperDescriptorResolver(Exception error) + { + _error = error; + } + + protected override IEnumerable ResolveDescriptorsInAssembly( + string assemblyName, + SourceLocation documentLocation, + ParserErrorSink errorSink) + { + throw _error; + } + } } } \ No newline at end of file diff --git a/test/Microsoft.AspNet.Razor.Runtime.Test/TagHelpers/TagHelperTypeResolverTest.cs b/test/Microsoft.AspNet.Razor.Runtime.Test/TagHelpers/TagHelperTypeResolverTest.cs index 85d58aaf6d..a48c06e8b5 100644 --- a/test/Microsoft.AspNet.Razor.Runtime.Test/TagHelpers/TagHelperTypeResolverTest.cs +++ b/test/Microsoft.AspNet.Razor.Runtime.Test/TagHelpers/TagHelperTypeResolverTest.cs @@ -3,11 +3,11 @@ using System; using System.Collections.Generic; -using System.Globalization; -using System.IO; using System.Linq; using System.Reflection; using System.Threading.Tasks; +using Microsoft.AspNet.Razor.Parser; +using Microsoft.AspNet.Razor.Text; using Xunit; namespace Microsoft.AspNet.Razor.Runtime.TagHelpers @@ -35,27 +35,29 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers ValidTestableTagHelpers.Concat(InvalidTestableTagHelpers).ToArray(); [Fact] - public void TypeResolver_ThrowsWhenCannotResolveAssembly() + public void TypeResolver_RecordsErrorWhenCannotResolveAssembly() { // Arrange var tagHelperTypeResolver = new TagHelperTypeResolver(); - var expectedErrorMessage = string.Format( - CultureInfo.InvariantCulture, - "Cannot resolve TagHelper containing assembly '{0}'.", - "abcd"); - - // Act & Assert - var ex = Assert.Throws(() => - { - tagHelperTypeResolver.Resolve("abcd"); - }); - - Assert.Equal(expectedErrorMessage, ex.Message); -#if ASPNETCORE50 - Assert.IsType(ex.InnerException); + var errorSink = new ParserErrorSink(); + var documentLocation = new SourceLocation(1, 2, 3); + var expectedErrorMessage = "Cannot resolve TagHelper containing assembly 'abcd'. Error: " + + "Could not load file or assembly '" + +#if ASPNET50 + "abcd' or one of its dependencies. The system cannot find the file specified."; #else - Assert.IsType(ex.InnerException); + "abcd, Culture=neutral, PublicKeyToken=null' or one of its dependencies. Could not find or load a " + + "specific file. (Exception from HRESULT: 0x80131621)"; #endif + + // Act + tagHelperTypeResolver.Resolve("abcd", documentLocation, errorSink); + + // Assert + var error = Assert.Single(errorSink.Errors); + Assert.Equal(1, error.Length); + Assert.Equal(documentLocation, error.Location); + Assert.Equal(expectedErrorMessage, error.Message); } [Fact] @@ -65,7 +67,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers var tagHelperTypeResolver = new TestTagHelperTypeResolver(TestableTagHelpers); // Act - var types = tagHelperTypeResolver.Resolve("Foo"); + var types = tagHelperTypeResolver.Resolve("Foo", SourceLocation.Zero, new ParserErrorSink()); // Assert Assert.Equal(ValidTestableTagHelpers, types); @@ -78,7 +80,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers var tagHelperTypeResolver = new TestTagHelperTypeResolver(InvalidTestableTagHelpers); // Act - var types = tagHelperTypeResolver.Resolve("Foo"); + var types = tagHelperTypeResolver.Resolve("Foo", SourceLocation.Zero, new ParserErrorSink()); // Assert Assert.Empty(types); @@ -87,22 +89,22 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers [Theory] [InlineData("")] [InlineData(null)] - public void TypeResolver_ResolveThrowsIfEmptyOrNullLookupText(string name) + public void TypeResolver_CreatesErrorIfNullOrEmptyAssmblyName_DoesNotThrow(string name) { // Arrange var tagHelperTypeResolver = new TestTagHelperTypeResolver(InvalidTestableTagHelpers); - var expectedMessage = "Tag helper directive assembly name cannot be null or empty." + - Environment.NewLine + - "Parameter name: name"; + var errorSink = new ParserErrorSink(); + var documentLocation = new SourceLocation(1, 2, 3); + var expectedErrorMessage = "Tag helper directive assembly name cannot be null or empty."; - // Act & Assert - var ex = Assert.Throws(nameof(name), - () => - { - tagHelperTypeResolver.Resolve(name); - }); + // Act + tagHelperTypeResolver.Resolve(name, documentLocation, errorSink); - Assert.Equal(expectedMessage, ex.Message); + // Assert + var error = Assert.Single(errorSink.Errors); + Assert.Equal(1, error.Length); + Assert.Equal(documentLocation, error.Location); + Assert.Equal(expectedErrorMessage, error.Message); } protected class TestTagHelperTypeResolver : TagHelperTypeResolver diff --git a/test/Microsoft.AspNet.Razor.Test/Parser/RazorParserTest.cs b/test/Microsoft.AspNet.Razor.Test/Parser/RazorParserTest.cs index 4b4b27089d..28f1eb594e 100644 --- a/test/Microsoft.AspNet.Razor.Test/Parser/RazorParserTest.cs +++ b/test/Microsoft.AspNet.Razor.Test/Parser/RazorParserTest.cs @@ -77,7 +77,9 @@ namespace Microsoft.AspNet.Razor.Test.Parser Mock.Of()); parser.CallBase = true; parser.Protected() - .Setup>("GetTagHelperDescriptors", ItExpr.IsAny()) + .Setup>("GetTagHelperDescriptors", + ItExpr.IsAny(), + ItExpr.IsAny()) .Returns(Enumerable.Empty()) .Verifiable(); diff --git a/test/Microsoft.AspNet.Razor.Test/TagHelpers/AddOrRemoveTagHelperSpanVisitorTest.cs b/test/Microsoft.AspNet.Razor.Test/TagHelpers/AddOrRemoveTagHelperSpanVisitorTest.cs index 68122afa1d..6a88364d50 100644 --- a/test/Microsoft.AspNet.Razor.Test/TagHelpers/AddOrRemoveTagHelperSpanVisitorTest.cs +++ b/test/Microsoft.AspNet.Razor.Test/TagHelpers/AddOrRemoveTagHelperSpanVisitorTest.cs @@ -28,7 +28,9 @@ namespace Microsoft.AspNet.Razor.TagHelpers var resolver = new Mock(); resolver.Setup(mock => mock.Resolve(It.IsAny())) .Returns(Enumerable.Empty()); - var addOrRemoveTagHelperSpanVisitor = new AddOrRemoveTagHelperSpanVisitor(resolver.Object); + var addOrRemoveTagHelperSpanVisitor = new AddOrRemoveTagHelperSpanVisitor( + resolver.Object, + new ParserErrorSink()); var document = new MarkupBlock( Factory.Code("\"one\"").AsAddTagHelper("one"), Factory.Code("\"two\"").AsRemoveTagHelper("two"), @@ -47,7 +49,7 @@ namespace Microsoft.AspNet.Razor.TagHelpers { // Arrange var resolver = new TestTagHelperDescriptorResolver(); - var addOrRemoveTagHelperSpanVisitor = new AddOrRemoveTagHelperSpanVisitor(resolver); + var addOrRemoveTagHelperSpanVisitor = new AddOrRemoveTagHelperSpanVisitor(resolver, new ParserErrorSink()); var document = new MarkupBlock( Factory.Code("\"one\"").AsAddTagHelper("one"), Factory.Code("\"two\"").AsRemoveTagHelper("two"), @@ -85,13 +87,13 @@ namespace Microsoft.AspNet.Razor.TagHelpers }; var addOrRemoveTagHelperSpanVisitor = new CustomAddOrRemoveTagHelperSpanVisitor( resolver, - (descriptors) => + (descriptors, errorSink) => { Assert.Equal(expectedInitialDirectiveDescriptors, descriptors, TagHelperDirectiveDescriptorComparer.Default); - return new TagHelperDescriptorResolutionContext(expectedEndDirectiveDescriptors); + return new TagHelperDescriptorResolutionContext(expectedEndDirectiveDescriptors, errorSink); }); var document = new MarkupBlock( Factory.Code("\"one\"").AsAddTagHelper("one"), @@ -113,7 +115,7 @@ namespace Microsoft.AspNet.Razor.TagHelpers { // Arrange var resolver = new TestTagHelperDescriptorResolver(); - var addOrRemoveTagHelperSpanVisitor = new AddOrRemoveTagHelperSpanVisitor(resolver); + var addOrRemoveTagHelperSpanVisitor = new AddOrRemoveTagHelperSpanVisitor(resolver, new ParserErrorSink()); var document = new MarkupBlock( new DirectiveBlock( Factory.CodeTransition(), @@ -137,7 +139,7 @@ namespace Microsoft.AspNet.Razor.TagHelpers { // Arrange var resolver = new TestTagHelperDescriptorResolver(); - var addOrRemoveTagHelperSpanVisitor = new AddOrRemoveTagHelperSpanVisitor(resolver); + var addOrRemoveTagHelperSpanVisitor = new AddOrRemoveTagHelperSpanVisitor(resolver, new ParserErrorSink()); var document = new MarkupBlock( new DirectiveBlock( Factory.CodeTransition(), @@ -162,7 +164,8 @@ namespace Microsoft.AspNet.Razor.TagHelpers // Arrange var addOrRemoveTagHelperSpanVisitor = new AddOrRemoveTagHelperSpanVisitor( - new TestTagHelperDescriptorResolver()); + new TestTagHelperDescriptorResolver(), + new ParserErrorSink()); var document = new MarkupBlock(Factory.Markup("Hello World")); // Act & Assert @@ -216,20 +219,25 @@ namespace Microsoft.AspNet.Razor.TagHelpers private class CustomAddOrRemoveTagHelperSpanVisitor : AddOrRemoveTagHelperSpanVisitor { - private Func, TagHelperDescriptorResolutionContext> _replacer; + private Func, + ParserErrorSink, + TagHelperDescriptorResolutionContext> _replacer; public CustomAddOrRemoveTagHelperSpanVisitor( ITagHelperDescriptorResolver descriptorResolver, - Func, TagHelperDescriptorResolutionContext> replacer) - : base(descriptorResolver) + Func, + ParserErrorSink, + TagHelperDescriptorResolutionContext> replacer) + : base(descriptorResolver, new ParserErrorSink()) { _replacer = replacer; } protected override TagHelperDescriptorResolutionContext GetTagHelperDescriptorResolutionContext( - IEnumerable descriptors) + IEnumerable descriptors, + ParserErrorSink errorSink) { - return _replacer(descriptors); + return _replacer(descriptors, errorSink); } } }