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
This commit is contained in:
Pranav K 2018-06-14 10:54:18 -07:00
parent d6f3a1bd22
commit a57b7c4d7f
5 changed files with 134 additions and 34 deletions

View File

@ -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<INamedTypeSymbol>();
var visitor = TagHelperTypeVisitor.Create(compilation, types);
var visitor = new TagHelperTypeVisitor(@interface, types);
// We always visit the global namespace.
visitor.Visit(compilation.Assembly.GlobalNamespace);

View File

@ -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<INamedTypeSymbol> _results;
public static TagHelperTypeVisitor Create(Compilation compilation, List<INamedTypeSymbol> results)
{
var @interface = compilation.GetTypeByMetadataName(TagHelperTypes.ITagHelper);
return new TagHelperTypeVisitor(@interface, results);
}
private readonly INamedTypeSymbol _interface;
private readonly List<INamedTypeSymbol> _results;
public TagHelperTypeVisitor(INamedTypeSymbol @interface, List<INamedTypeSymbol> 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 &&

View File

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

View File

@ -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<T> : TagHelper
{
}
internal class Invalid_InternalTagHelper : TagHelper
{
}
public class Valid_PlainTagHelper : TagHelper
{
}
public class Valid_InheritedTagHelper : Valid_PlainTagHelper
{
}
}

View File

@ -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<T> : TagHelper
{
}
internal class Invalid_InternalTagHelper : TagHelper
{
}
public class Valid_PlainTagHelper : TagHelper
{
}
public class Valid_InheritedTagHelper : Valid_PlainTagHelper
{
}
}