From a57b7c4d7f77f79f632aaad76094e9c96142a7a5 Mon Sep 17 00:00:00 2001 From: Pranav K Date: Thu, 14 Jun 2018 10:54:18 -0700 Subject: [PATCH] Don't discover tag helpers if some references are missing A common cause for runtime view compilation failure in MVC is when an application is published without reference assemblies. MVC usually handles this at compilation, by looking for specific error codes. More recently, TagHelper discovery fails with an not-so-helpful error message in this scenario. This change attempts to add a little more error checking to cover the most common cases --- .../DefaultTagHelperDescriptorProvider.cs | 13 ++- .../TagHelperTypeVisitor.cs | 16 +--- .../DefaultTagHelperDescriptorProviderTest.cs | 92 +++++++++++++++++++ .../TagHelperTypeVisitorTest.cs | 20 ---- .../TagHelperTypes.cs | 27 ++++++ 5 files changed, 134 insertions(+), 34 deletions(-) create mode 100644 test/Microsoft.CodeAnalysis.Razor.Test/TagHelperTypes.cs diff --git a/src/Microsoft.CodeAnalysis.Razor/DefaultTagHelperDescriptorProvider.cs b/src/Microsoft.CodeAnalysis.Razor/DefaultTagHelperDescriptorProvider.cs index b0d07db459..3b7c410b0d 100644 --- a/src/Microsoft.CodeAnalysis.Razor/DefaultTagHelperDescriptorProvider.cs +++ b/src/Microsoft.CodeAnalysis.Razor/DefaultTagHelperDescriptorProvider.cs @@ -31,8 +31,19 @@ namespace Microsoft.CodeAnalysis.Razor return; } + var @interface = compilation.GetTypeByMetadataName(TagHelperTypes.ITagHelper); + var @string = compilation.GetSpecialType(SpecialType.System_String); + + // Ensure ITagHelper and System.String are available. They may be missing or + // errored if we're missing references. + if (@interface == null || @interface.TypeKind == TypeKind.Error || + @string == null || @string.TypeKind == TypeKind.Error) + { + return; + } + var types = new List(); - var visitor = TagHelperTypeVisitor.Create(compilation, types); + var visitor = new TagHelperTypeVisitor(@interface, types); // We always visit the global namespace. visitor.Visit(compilation.Assembly.GlobalNamespace); diff --git a/src/Microsoft.CodeAnalysis.Razor/TagHelperTypeVisitor.cs b/src/Microsoft.CodeAnalysis.Razor/TagHelperTypeVisitor.cs index 625e8284c1..dfe5a9f0a1 100644 --- a/src/Microsoft.CodeAnalysis.Razor/TagHelperTypeVisitor.cs +++ b/src/Microsoft.CodeAnalysis.Razor/TagHelperTypeVisitor.cs @@ -8,14 +8,8 @@ namespace Microsoft.CodeAnalysis.Razor // Visits top-level types and finds interface implementations. internal class TagHelperTypeVisitor : SymbolVisitor { - private INamedTypeSymbol _interface; - private List _results; - - public static TagHelperTypeVisitor Create(Compilation compilation, List results) - { - var @interface = compilation.GetTypeByMetadataName(TagHelperTypes.ITagHelper); - return new TagHelperTypeVisitor(@interface, results); - } + private readonly INamedTypeSymbol _interface; + private readonly List _results; public TagHelperTypeVisitor(INamedTypeSymbol @interface, List results) { @@ -41,12 +35,8 @@ namespace Microsoft.CodeAnalysis.Razor internal bool IsTagHelper(INamedTypeSymbol symbol) { - if (_interface == null) - { - return false; - } - return + symbol.TypeKind != TypeKind.Error && symbol.DeclaredAccessibility == Accessibility.Public && !symbol.IsAbstract && !symbol.IsGenericType && diff --git a/test/Microsoft.CodeAnalysis.Razor.Test/DefaultTagHelperDescriptorProviderTest.cs b/test/Microsoft.CodeAnalysis.Razor.Test/DefaultTagHelperDescriptorProviderTest.cs index b7cbf6a9f6..04883a123a 100644 --- a/test/Microsoft.CodeAnalysis.Razor.Test/DefaultTagHelperDescriptorProviderTest.cs +++ b/test/Microsoft.CodeAnalysis.Razor.Test/DefaultTagHelperDescriptorProviderTest.cs @@ -1,9 +1,12 @@ // 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.IO; using System.Linq; using System.Reflection; using Microsoft.AspNetCore.Razor.Language; +using Microsoft.AspNetCore.Razor.TagHelpers; +using Microsoft.CodeAnalysis.CSharp; using Xunit; namespace Microsoft.CodeAnalysis.Razor @@ -21,6 +24,7 @@ namespace Microsoft.CodeAnalysis.Razor var descriptorProvider = new DefaultTagHelperDescriptorProvider(); var context = TagHelperDescriptorProviderContext.Create(); + context.SetCompilation(compilation); context.ExcludeHidden = true; // Act @@ -33,5 +37,93 @@ namespace Microsoft.CodeAnalysis.Razor var editorBrowsableDescriptor = context.Results.Where(descriptor => descriptor.GetTypeName() == editorBrowsableTypeName); Assert.Empty(editorBrowsableDescriptor); } + + [Fact] + public void Execute_NoOpsIfCompilationSymbolIsNotSet() + { + // Arrange + var descriptorProvider = new DefaultTagHelperDescriptorProvider(); + + var context = TagHelperDescriptorProviderContext.Create(); + + // Act + descriptorProvider.Execute(context); + + // Assert + Assert.Empty(context.Results); + } + + [Fact] + public void Execute_NoOpsIfTagHelperInterfaceCannotBeFound() + { + // Arrange + var references = new[] + { + MetadataReference.CreateFromFile(typeof(string).Assembly.Location), + }; + var compilation = CSharpCompilation.Create("Test", references: references); + + var descriptorProvider = new DefaultTagHelperDescriptorProvider(); + + var context = TagHelperDescriptorProviderContext.Create(); + context.SetCompilation(compilation); + + // Act + descriptorProvider.Execute(context); + + // Assert + Assert.Empty(context.Results); + } + + [Fact] + public void Execute_NoOpsIfStringCannotBeFound() + { + // Arrange + var references = new[] + { + MetadataReference.CreateFromFile(typeof(ITagHelper).Assembly.Location), + }; + var compilation = CSharpCompilation.Create("Test", references: references); + + var descriptorProvider = new DefaultTagHelperDescriptorProvider(); + + var context = TagHelperDescriptorProviderContext.Create(); + context.SetCompilation(compilation); + + // Act + descriptorProvider.Execute(context); + + // Assert + Assert.Empty(context.Results); + } + + [Fact] + public void Execute_DiscoversTagHelpersFromCompilation() + { + // Arrange + var references = new[] + { + MetadataReference.CreateFromFile(typeof(string).Assembly.Location), + MetadataReference.CreateFromFile(typeof(ITagHelper).Assembly.Location), + }; + var projectDirectory = TestProject.GetProjectDirectory(GetType()); + var tagHelperContent = File.ReadAllText(Path.Combine(projectDirectory, "TagHelperTypes.cs")); + var syntaxTree = CSharpSyntaxTree.ParseText(tagHelperContent); + var compilation = CSharpCompilation.Create("Test", references: references, syntaxTrees: new[] { syntaxTree }); + + var descriptorProvider = new DefaultTagHelperDescriptorProvider(); + + var context = TagHelperDescriptorProviderContext.Create(); + context.SetCompilation(compilation); + + // Act + descriptorProvider.Execute(context); + + // Assert + Assert.Collection( + context.Results.OrderBy(r => r.Name), + tagHelper => Assert.Equal(typeof(Valid_InheritedTagHelper).FullName, tagHelper.Name), + tagHelper => Assert.Equal(typeof(Valid_PlainTagHelper).FullName, tagHelper.Name)); + } } } diff --git a/test/Microsoft.CodeAnalysis.Razor.Test/TagHelperTypeVisitorTest.cs b/test/Microsoft.CodeAnalysis.Razor.Test/TagHelperTypeVisitorTest.cs index 8f14017c91..88e26016e5 100644 --- a/test/Microsoft.CodeAnalysis.Razor.Test/TagHelperTypeVisitorTest.cs +++ b/test/Microsoft.CodeAnalysis.Razor.Test/TagHelperTypeVisitorTest.cs @@ -98,24 +98,4 @@ namespace Microsoft.CodeAnalysis.Razor.Workspaces public string Invoke(string foo) => null; } } - - public abstract class Invalid_AbstractTagHelper : TagHelper - { - } - - public class Invalid_GenericTagHelper : TagHelper - { - } - - internal class Invalid_InternalTagHelper : TagHelper - { - } - - public class Valid_PlainTagHelper : TagHelper - { - } - - public class Valid_InheritedTagHelper : Valid_PlainTagHelper - { - } } diff --git a/test/Microsoft.CodeAnalysis.Razor.Test/TagHelperTypes.cs b/test/Microsoft.CodeAnalysis.Razor.Test/TagHelperTypes.cs new file mode 100644 index 0000000000..fea9cd5ad3 --- /dev/null +++ b/test/Microsoft.CodeAnalysis.Razor.Test/TagHelperTypes.cs @@ -0,0 +1,27 @@ +// 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 Microsoft.AspNetCore.Razor.TagHelpers; + +namespace Microsoft.CodeAnalysis.Razor +{ + public abstract class Invalid_AbstractTagHelper : TagHelper + { + } + + public class Invalid_GenericTagHelper : TagHelper + { + } + + internal class Invalid_InternalTagHelper : TagHelper + { + } + + public class Valid_PlainTagHelper : TagHelper + { + } + + public class Valid_InheritedTagHelper : Valid_PlainTagHelper + { + } +}