Add a new THProvider api
This change adds an API for Tag Helper discovery. I also got rid of the 'design time' flag for the provider as an experimental change. We need to think through the consequences of this before committing to it. Right now I've left those tests failing until we can make a decision. This change decouples VCTH discovery a bit more, but we're still not ready to move that into a the MVC extensions assembly. For that we need the ability to discover the MVC extensibility.
This commit is contained in:
parent
577511945b
commit
ad294fb4ba
|
|
@ -10,8 +10,15 @@
|
|||
<PackageTags>aspnetcore;aspnetcoremvc;cshtml;razor</PackageTags>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Include="..\Microsoft.CodeAnalysis.Razor\ViewComponentTagHelperDescriptorConventions.cs">
|
||||
<Link>ViewComponentTagHelperDescriptorConventions.cs</Link>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="../Microsoft.AspNetCore.Razor.Language/Microsoft.AspNetCore.Razor.Language.csproj" />
|
||||
<ProjectReference Include="../Microsoft.CodeAnalysis.Razor/Microsoft.CodeAnalysis.Razor.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ using System.Threading.Tasks;
|
|||
using Microsoft.AspNetCore.Razor.Language;
|
||||
using Microsoft.AspNetCore.Razor.Language.Intermediate;
|
||||
using Microsoft.AspNetCore.Razor.Language.Legacy;
|
||||
using Microsoft.CodeAnalysis.Razor;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Razor.Extensions
|
||||
{
|
||||
|
|
|
|||
|
|
@ -20,10 +20,10 @@ namespace Microsoft.AspNetCore.Razor.Language
|
|||
var syntaxTree = codeDocument.GetSyntaxTree();
|
||||
ThrowForMissingDocumentDependency(syntaxTree);
|
||||
|
||||
var resolver = Engine.Features.OfType<ITagHelperFeature>().FirstOrDefault()?.Resolver;
|
||||
if (resolver == null)
|
||||
var feature = Engine.Features.OfType<ITagHelperFeature>().FirstOrDefault();
|
||||
if (feature == null)
|
||||
{
|
||||
// No resolver, nothing to do.
|
||||
// No feature, nothing to do.
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -45,7 +45,7 @@ namespace Microsoft.AspNetCore.Razor.Language
|
|||
visitor.VisitBlock(syntaxTree.Root);
|
||||
|
||||
var errorList = new List<RazorDiagnostic>();
|
||||
var descriptors = (IReadOnlyList<TagHelperDescriptor>)resolver.Resolve(errorList).ToList();
|
||||
var descriptors = feature.GetDescriptors();
|
||||
|
||||
var errorSink = new ErrorSink();
|
||||
var directives = visitor.Directives;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,40 @@
|
|||
// 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;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.Language
|
||||
{
|
||||
public static class HtmlCase
|
||||
{
|
||||
private const string HtmlCaseRegexReplacement = "-$1$2";
|
||||
|
||||
// This matches the following AFTER the start of the input string (MATCH).
|
||||
// Any letter/number followed by an uppercase letter then lowercase letter: 1(Aa), a(Aa), A(Aa)
|
||||
// Any lowercase letter followed by an uppercase letter: a(A)
|
||||
// Each match is then prefixed by a "-" via the ToHtmlCase method.
|
||||
private static readonly Regex HtmlCaseRegex =
|
||||
new Regex(
|
||||
"(?<!^)((?<=[a-zA-Z0-9])[A-Z][a-z])|((?<=[a-z])[A-Z])",
|
||||
RegexOptions.None,
|
||||
TimeSpan.FromMilliseconds(500));
|
||||
|
||||
/// <summary>
|
||||
/// Converts from pascal/camel case to lower kebab-case.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// SomeThing => some-thing
|
||||
/// capsONInside => caps-on-inside
|
||||
/// CAPSOnOUTSIDE => caps-on-outside
|
||||
/// ALLCAPS => allcaps
|
||||
/// One1Two2Three3 => one1-two2-three3
|
||||
/// ONE1TWO2THREE3 => one1two2three3
|
||||
/// First_Second_ThirdHi => first_second_third-hi
|
||||
/// </example>
|
||||
public static string ToHtmlCase(string name)
|
||||
{
|
||||
return HtmlCaseRegex.Replace(name, HtmlCaseRegexReplacement).ToLowerInvariant();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +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.
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.Language
|
||||
{
|
||||
public interface ITagHelperDescriptorProvider : IRazorEngineFeature
|
||||
{
|
||||
int Order { get; }
|
||||
|
||||
void Execute(TagHelperDescriptorProviderContext context);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +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 Microsoft.AspNetCore.Razor.Language.Legacy;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.Language
|
||||
{
|
||||
public interface ITagHelperFeature : IRazorEngineFeature
|
||||
{
|
||||
ITagHelperDescriptorResolver Resolver { get; }
|
||||
IReadOnlyList<TagHelperDescriptor> GetDescriptors();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,15 +0,0 @@
|
|||
// 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.Collections.Generic;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||
{
|
||||
/// <summary>
|
||||
/// Contract used to resolve <see cref="TagHelperDescriptor"/>s.
|
||||
/// </summary>
|
||||
public interface ITagHelperDescriptorResolver
|
||||
{
|
||||
IEnumerable<TagHelperDescriptor> Resolve(IList<RazorDiagnostic> errors);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +1,6 @@
|
|||
// 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.Language;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.Language
|
||||
{
|
||||
internal static class RazorDiagnosticFactory
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Razor.Language.Legacy;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.Language
|
||||
{
|
||||
|
|
@ -16,6 +15,7 @@ namespace Microsoft.AspNetCore.Razor.Language
|
|||
private static ICollection<char> InvalidNonWhitespaceAllowedChildCharacters { get; } = new HashSet<char>(
|
||||
new[] { '@', '!', '<', '/', '?', '[', '>', ']', '=', '"', '\'', '*' });
|
||||
|
||||
|
||||
private string _documentation;
|
||||
private string _tagOutputHint;
|
||||
private HashSet<string> _allowedChildTags;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,44 @@
|
|||
// 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;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.Language
|
||||
{
|
||||
public abstract class TagHelperDescriptorProviderContext
|
||||
{
|
||||
public abstract ItemCollection Items { get; }
|
||||
|
||||
public abstract ICollection<TagHelperDescriptor> Results { get; }
|
||||
|
||||
public static TagHelperDescriptorProviderContext Create()
|
||||
{
|
||||
return new DefaultContext(new List<TagHelperDescriptor>());
|
||||
}
|
||||
|
||||
public static TagHelperDescriptorProviderContext Create(ICollection<TagHelperDescriptor> results)
|
||||
{
|
||||
if (results == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(results));
|
||||
}
|
||||
|
||||
return new DefaultContext(results);
|
||||
}
|
||||
|
||||
private class DefaultContext : TagHelperDescriptorProviderContext
|
||||
{
|
||||
public DefaultContext(ICollection<TagHelperDescriptor> results)
|
||||
{
|
||||
Results = results;
|
||||
|
||||
Items = new DefaultItemCollection();
|
||||
}
|
||||
|
||||
public override ItemCollection Items { get; }
|
||||
|
||||
public override ICollection<TagHelperDescriptor> Results { get; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,7 +2,6 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
|
||||
namespace Microsoft.CodeAnalysis.Razor
|
||||
|
|
@ -20,62 +19,26 @@ namespace Microsoft.CodeAnalysis.Razor
|
|||
{
|
||||
var descriptors = new List<TagHelperDescriptor>();
|
||||
|
||||
VisitTagHelpers(compilation, descriptors);
|
||||
VisitViewComponents(compilation, descriptors);
|
||||
var providers = new ITagHelperDescriptorProvider[]
|
||||
{
|
||||
new DefaultTagHelperDescriptorProvider() { DesignTime = true, },
|
||||
new ViewComponentTagHelperDescriptorProvider(),
|
||||
};
|
||||
|
||||
var results = new List<TagHelperDescriptor>();
|
||||
var context = TagHelperDescriptorProviderContext.Create(results);
|
||||
context.SetCompilation(compilation);
|
||||
|
||||
for (var i = 0; i < providers.Length; i++)
|
||||
{
|
||||
var provider = providers[i];
|
||||
provider.Execute(context);
|
||||
}
|
||||
|
||||
var diagnostics = new List<RazorDiagnostic>();
|
||||
var resolutionResult = new TagHelperResolutionResult(descriptors, diagnostics);
|
||||
var resolutionResult = new TagHelperResolutionResult(results, diagnostics);
|
||||
|
||||
return resolutionResult;
|
||||
}
|
||||
|
||||
private void VisitTagHelpers(Compilation compilation, List<TagHelperDescriptor> results)
|
||||
{
|
||||
var types = new List<INamedTypeSymbol>();
|
||||
var visitor = TagHelperTypeVisitor.Create(compilation, types);
|
||||
|
||||
VisitCompilation(visitor, compilation);
|
||||
|
||||
var factory = new DefaultTagHelperDescriptorFactory(compilation, DesignTime);
|
||||
|
||||
foreach (var type in types)
|
||||
{
|
||||
var descriptor = factory.CreateDescriptor(type);
|
||||
|
||||
if (descriptor != null)
|
||||
{
|
||||
results.Add(descriptor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void VisitViewComponents(Compilation compilation, List<TagHelperDescriptor> results)
|
||||
{
|
||||
var types = new List<INamedTypeSymbol>();
|
||||
var visitor = ViewComponentTypeVisitor.Create(compilation, types);
|
||||
|
||||
VisitCompilation(visitor, compilation);
|
||||
|
||||
var factory = new ViewComponentTagHelperDescriptorFactory(compilation);
|
||||
foreach (var type in types)
|
||||
{
|
||||
var descriptor = factory.CreateDescriptor(type);
|
||||
|
||||
results.Add(descriptor);
|
||||
}
|
||||
}
|
||||
|
||||
private static void VisitCompilation(SymbolVisitor visitor, Compilation compilation)
|
||||
{
|
||||
visitor.Visit(compilation.Assembly.GlobalNamespace);
|
||||
|
||||
foreach (var reference in compilation.References)
|
||||
{
|
||||
if (compilation.GetAssemblyOrModuleSymbol(reference) is IAssemblySymbol assembly)
|
||||
{
|
||||
visitor.Visit(assembly.GlobalNamespace);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
// 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.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
|
||||
namespace Microsoft.CodeAnalysis.Razor
|
||||
{
|
||||
public class CompilationTagHelperFeature : RazorEngineFeatureBase, ITagHelperFeature
|
||||
{
|
||||
private ITagHelperDescriptorProvider[] _providers;
|
||||
private IMetadataReferenceFeature _referenceFeature;
|
||||
|
||||
public IReadOnlyList<TagHelperDescriptor> GetDescriptors()
|
||||
{
|
||||
var results = new List<TagHelperDescriptor>();
|
||||
|
||||
var context = TagHelperDescriptorProviderContext.Create(results);
|
||||
var compilation = CSharpCompilation.Create("__TagHelpers", references: _referenceFeature.References);
|
||||
context.SetCompilation(compilation);
|
||||
|
||||
for (var i = 0; i < _providers.Length; i++)
|
||||
{
|
||||
_providers[i].Execute(context);
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
_referenceFeature = Engine.Features.OfType<IMetadataReferenceFeature>().FirstOrDefault();
|
||||
_providers = Engine.Features.OfType<ITagHelperDescriptorProvider>().OrderBy(f => f.Order).ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -5,9 +5,7 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
using Microsoft.AspNetCore.Razor.Language.Legacy;
|
||||
using Microsoft.CodeAnalysis;
|
||||
|
||||
namespace Microsoft.CodeAnalysis.Razor
|
||||
|
|
@ -16,17 +14,6 @@ namespace Microsoft.CodeAnalysis.Razor
|
|||
{
|
||||
private const string DataDashPrefix = "data-";
|
||||
private const string TagHelperNameEnding = "TagHelper";
|
||||
private const string HtmlCaseRegexReplacement = "-$1$2";
|
||||
|
||||
// This matches the following AFTER the start of the input string (MATCH).
|
||||
// Any letter/number followed by an uppercase letter then lowercase letter: 1(Aa), a(Aa), A(Aa)
|
||||
// Any lowercase letter followed by an uppercase letter: a(A)
|
||||
// Each match is then prefixed by a "-" via the ToHtmlCase method.
|
||||
private static readonly Regex HtmlCaseRegex =
|
||||
new Regex(
|
||||
"(?<!^)((?<=[a-zA-Z0-9])[A-Z][a-z])|((?<=[a-z])[A-Z])",
|
||||
RegexOptions.None,
|
||||
TimeSpan.FromMilliseconds(500));
|
||||
|
||||
private readonly INamedTypeSymbol _htmlAttributeNameAttributeSymbol;
|
||||
private readonly INamedTypeSymbol _htmlAttributeNotBoundAttributeSymbol;
|
||||
|
|
@ -36,9 +23,6 @@ namespace Microsoft.CodeAnalysis.Razor
|
|||
private readonly INamedTypeSymbol _restrictChildrenAttributeSymbol;
|
||||
private readonly INamedTypeSymbol _editorBrowsableAttributeSymbol;
|
||||
|
||||
public static ICollection<char> InvalidNonWhitespaceNameCharacters { get; } = new HashSet<char>(
|
||||
new[] { '@', '!', '<', '/', '?', '[', '>', ']', '=', '"', '\'', '*' });
|
||||
|
||||
private static readonly SymbolDisplayFormat FullNameTypeDisplayFormat =
|
||||
SymbolDisplayFormat.FullyQualifiedFormat
|
||||
.WithGlobalNamespaceStyle(SymbolDisplayGlobalNamespaceStyle.Omitted)
|
||||
|
|
@ -104,7 +88,7 @@ namespace Microsoft.CodeAnalysis.Razor
|
|||
|
||||
descriptorBuilder.TagMatchingRule(ruleBuilder =>
|
||||
{
|
||||
var htmlCasedName = ToHtmlCase(name);
|
||||
var htmlCasedName = HtmlCase.ToHtmlCase(name);
|
||||
ruleBuilder.RequireTagName(htmlCasedName);
|
||||
});
|
||||
|
||||
|
|
@ -213,7 +197,7 @@ namespace Microsoft.CodeAnalysis.Razor
|
|||
string.IsNullOrEmpty((string)attributeNameAttribute.ConstructorArguments[0].Value))
|
||||
{
|
||||
hasExplicitName = false;
|
||||
attributeName = ToHtmlCase(property.Name);
|
||||
attributeName = HtmlCase.ToHtmlCase(property.Name);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -449,23 +433,6 @@ namespace Microsoft.CodeAnalysis.Razor
|
|||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts from pascal/camel case to lower kebab-case.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// SomeThing => some-thing
|
||||
/// capsONInside => caps-on-inside
|
||||
/// CAPSOnOUTSIDE => caps-on-outside
|
||||
/// ALLCAPS => allcaps
|
||||
/// One1Two2Three3 => one1-two2-three3
|
||||
/// ONE1TWO2THREE3 => one1two2three3
|
||||
/// First_Second_ThirdHi => first_second_third-hi
|
||||
/// </example>
|
||||
internal static string ToHtmlCase(string name)
|
||||
{
|
||||
return HtmlCaseRegex.Replace(name, HtmlCaseRegexReplacement).ToLowerInvariant();
|
||||
}
|
||||
|
||||
private static string GetFullName(ITypeSymbol type) => type.ToDisplayString(FullNameTypeDisplayFormat);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
// 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;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
|
||||
namespace Microsoft.CodeAnalysis.Razor
|
||||
{
|
||||
public sealed class DefaultTagHelperDescriptorProvider : RazorEngineFeatureBase, ITagHelperDescriptorProvider
|
||||
{
|
||||
public bool DesignTime { get; set; }
|
||||
|
||||
public int Order { get; set; }
|
||||
|
||||
public void Execute(TagHelperDescriptorProviderContext context)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
var compilation = context.GetCompilation();
|
||||
if (compilation == null)
|
||||
{
|
||||
// No compilation, nothing to do.
|
||||
return;
|
||||
}
|
||||
|
||||
var types = new List<INamedTypeSymbol>();
|
||||
var visitor = TagHelperTypeVisitor.Create(compilation, types);
|
||||
|
||||
// We always visit the global namespace.
|
||||
visitor.Visit(compilation.Assembly.GlobalNamespace);
|
||||
|
||||
foreach (var reference in compilation.References)
|
||||
{
|
||||
if (compilation.GetAssemblyOrModuleSymbol(reference) is IAssemblySymbol assembly)
|
||||
{
|
||||
if (IsTagHelperAssembly(assembly))
|
||||
{
|
||||
visitor.Visit(assembly.GlobalNamespace);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var factory = new DefaultTagHelperDescriptorFactory(compilation, DesignTime);
|
||||
for (var i = 0; i < types.Count; i++)
|
||||
{
|
||||
var descriptor = factory.CreateDescriptor(types[i]);
|
||||
context.Results.Add(descriptor);
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsTagHelperAssembly(IAssemblySymbol assembly)
|
||||
{
|
||||
return assembly.Name != null && !assembly.Name.StartsWith("System.", StringComparison.Ordinal);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,35 +0,0 @@
|
|||
// 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.Collections.Generic;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
using Microsoft.AspNetCore.Razor.Language.Legacy;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
|
||||
namespace Microsoft.CodeAnalysis.Razor
|
||||
{
|
||||
public class DefaultTagHelperFeature : RazorEngineFeatureBase, ITagHelperFeature
|
||||
{
|
||||
public ITagHelperDescriptorResolver Resolver { get; private set; }
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
Resolver = new InnerResolver(GetRequiredFeature<IMetadataReferenceFeature>());
|
||||
}
|
||||
|
||||
private class InnerResolver : ITagHelperDescriptorResolver
|
||||
{
|
||||
private readonly IMetadataReferenceFeature _referenceFeature;
|
||||
|
||||
public InnerResolver(IMetadataReferenceFeature referenceFeature)
|
||||
{
|
||||
_referenceFeature = referenceFeature;
|
||||
}
|
||||
public IEnumerable<TagHelperDescriptor> Resolve(IList<RazorDiagnostic> errors)
|
||||
{
|
||||
var compilation = CSharpCompilation.Create("__TagHelpers", references: _referenceFeature.References);
|
||||
return TagHelpers.GetTagHelpers(compilation);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
// 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;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
|
||||
namespace Microsoft.CodeAnalysis.Razor
|
||||
{
|
||||
public static class TagHelperDescriptorProviderContextExtensions
|
||||
{
|
||||
public static Compilation GetCompilation(this TagHelperDescriptorProviderContext context)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
return (Compilation)context.Items[typeof(Compilation)];
|
||||
}
|
||||
|
||||
public static void SetCompilation(this TagHelperDescriptorProviderContext context, Compilation compilation)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
context.Items[typeof(Compilation)] = compilation;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,83 +0,0 @@
|
|||
// 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.Language;
|
||||
using Microsoft.AspNetCore.Razor.Language.Legacy;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.CodeAnalysis.Razor
|
||||
{
|
||||
internal static class TagHelpers
|
||||
{
|
||||
public static IReadOnlyList<TagHelperDescriptor> GetTagHelpers(Compilation compilation)
|
||||
{
|
||||
var results = new List<TagHelperDescriptor>();
|
||||
var errors = new ErrorSink();
|
||||
|
||||
VisitTagHelpers(compilation, results, errors);
|
||||
VisitViewComponents(compilation, results, errors);
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
private static void VisitTagHelpers(Compilation compilation, List<TagHelperDescriptor> results, ErrorSink errors)
|
||||
{
|
||||
var types = new List<INamedTypeSymbol>();
|
||||
var visitor = TagHelperTypeVisitor.Create(compilation, types);
|
||||
|
||||
VisitCompilation(visitor, compilation);
|
||||
|
||||
var factory = new DefaultTagHelperDescriptorFactory(compilation, designTime: false);
|
||||
|
||||
foreach (var type in types)
|
||||
{
|
||||
var descriptor = factory.CreateDescriptor(type);
|
||||
if (descriptor != null)
|
||||
{
|
||||
results.Add(descriptor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void VisitViewComponents(Compilation compilation, List<TagHelperDescriptor> results, ErrorSink errors)
|
||||
{
|
||||
var types = new List<INamedTypeSymbol>();
|
||||
var visitor = ViewComponentTypeVisitor.Create(compilation, types);
|
||||
|
||||
VisitCompilation(visitor, compilation);
|
||||
|
||||
var factory = new ViewComponentTagHelperDescriptorFactory(compilation);
|
||||
|
||||
foreach (var type in types)
|
||||
{
|
||||
try
|
||||
{
|
||||
var descriptor = factory.CreateDescriptor(type);
|
||||
|
||||
if (descriptor != null)
|
||||
{
|
||||
results.Add(descriptor);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
errors.OnError(SourceLocation.Zero, ex.Message, length: 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void VisitCompilation(SymbolVisitor visitor, Compilation compilation)
|
||||
{
|
||||
visitor.Visit(compilation.Assembly.GlobalNamespace);
|
||||
|
||||
foreach (var reference in compilation.References)
|
||||
{
|
||||
if (compilation.GetAssemblyOrModuleSymbol(reference) is IAssemblySymbol assembly)
|
||||
{
|
||||
visitor.Visit(assembly.GlobalNamespace);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,102 @@
|
|||
// 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.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
|
||||
namespace Microsoft.CodeAnalysis.Razor
|
||||
{
|
||||
internal class ViewComponentDiagnosticFactory
|
||||
{
|
||||
private const string DiagnosticPrefix = "RZ";
|
||||
|
||||
public static readonly RazorDiagnosticDescriptor ViewComponent_CannotFindMethod =
|
||||
new RazorDiagnosticDescriptor(
|
||||
$"{DiagnosticPrefix}3900",
|
||||
() => ViewComponentResources.ViewComponent_CannotFindMethod,
|
||||
RazorDiagnosticSeverity.Error);
|
||||
|
||||
public static RazorDiagnostic CreateViewComponent_CannotFindMethod(string tagHelperType)
|
||||
{
|
||||
var diagnostic = RazorDiagnostic.Create(
|
||||
ViewComponent_CannotFindMethod,
|
||||
new SourceSpan(SourceLocation.Undefined, contentLength: 0),
|
||||
ViewComponentTypes.SyncMethodName,
|
||||
ViewComponentTypes.AsyncMethodName,
|
||||
tagHelperType);
|
||||
|
||||
return diagnostic;
|
||||
}
|
||||
|
||||
public static readonly RazorDiagnosticDescriptor ViewComponent_AmbiguousMethods =
|
||||
new RazorDiagnosticDescriptor(
|
||||
$"{DiagnosticPrefix}3901",
|
||||
() => ViewComponentResources.ViewComponent_AmbiguousMethods,
|
||||
RazorDiagnosticSeverity.Error);
|
||||
|
||||
public static RazorDiagnostic CreateViewComponent_AmbiguousMethods(string tagHelperType)
|
||||
{
|
||||
var diagnostic = RazorDiagnostic.Create(
|
||||
ViewComponent_AmbiguousMethods,
|
||||
new SourceSpan(SourceLocation.Undefined, contentLength: 0),
|
||||
tagHelperType,
|
||||
ViewComponentTypes.SyncMethodName,
|
||||
ViewComponentTypes.AsyncMethodName);
|
||||
|
||||
return diagnostic;
|
||||
}
|
||||
|
||||
public static readonly RazorDiagnosticDescriptor ViewComponent_AsyncMethod_ShouldReturnTask =
|
||||
new RazorDiagnosticDescriptor(
|
||||
$"{DiagnosticPrefix}3902",
|
||||
() => ViewComponentResources.ViewComponent_AsyncMethod_ShouldReturnTask,
|
||||
RazorDiagnosticSeverity.Error);
|
||||
|
||||
public static RazorDiagnostic CreateViewComponent_AsyncMethod_ShouldReturnTask(string tagHelperType)
|
||||
{
|
||||
var diagnostic = RazorDiagnostic.Create(
|
||||
ViewComponent_AsyncMethod_ShouldReturnTask,
|
||||
new SourceSpan(SourceLocation.Undefined, contentLength: 0),
|
||||
ViewComponentTypes.AsyncMethodName,
|
||||
tagHelperType,
|
||||
nameof(Task));
|
||||
|
||||
return diagnostic;
|
||||
}
|
||||
|
||||
public static readonly RazorDiagnosticDescriptor ViewComponent_SyncMethod_ShouldReturnValue =
|
||||
new RazorDiagnosticDescriptor(
|
||||
$"{DiagnosticPrefix}3903",
|
||||
() => ViewComponentResources.ViewComponent_SyncMethod_ShouldReturnValue,
|
||||
RazorDiagnosticSeverity.Error);
|
||||
|
||||
public static RazorDiagnostic CreateViewComponent_SyncMethod_ShouldReturnValue(string tagHelperType)
|
||||
{
|
||||
var diagnostic = RazorDiagnostic.Create(
|
||||
ViewComponent_SyncMethod_ShouldReturnValue,
|
||||
new SourceSpan(SourceLocation.Undefined, contentLength: 0),
|
||||
ViewComponentTypes.SyncMethodName,
|
||||
tagHelperType);
|
||||
|
||||
return diagnostic;
|
||||
}
|
||||
|
||||
public static readonly RazorDiagnosticDescriptor ViewComponent_SyncMethod_CannotReturnTask =
|
||||
new RazorDiagnosticDescriptor(
|
||||
$"{DiagnosticPrefix}3904",
|
||||
() => ViewComponentResources.ViewComponent_SyncMethod_CannotReturnTask,
|
||||
RazorDiagnosticSeverity.Error);
|
||||
|
||||
public static RazorDiagnostic CreateViewComponent_SyncMethod_CannotReturnTask(string tagHelperType)
|
||||
{
|
||||
var diagnostic = RazorDiagnostic.Create(
|
||||
ViewComponent_SyncMethod_CannotReturnTask,
|
||||
new SourceSpan(SourceLocation.Undefined, contentLength: 0),
|
||||
ViewComponentTypes.SyncMethodName,
|
||||
tagHelperType,
|
||||
nameof(Task));
|
||||
|
||||
return diagnostic;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,12 +3,12 @@
|
|||
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Razor.Extensions
|
||||
namespace Microsoft.CodeAnalysis.Razor
|
||||
{
|
||||
/// <summary>
|
||||
/// A library of methods used to generate <see cref="TagHelperDescriptor"/>s for view components.
|
||||
/// </summary>
|
||||
public static class ViewComponentTagHelperDescriptorConventions
|
||||
internal static class ViewComponentTagHelperDescriptorConventions
|
||||
{
|
||||
/// <summary>
|
||||
/// The key in a <see cref="TagHelperDescriptor.Metadata"/> containing
|
||||
|
|
@ -4,7 +4,6 @@
|
|||
using System;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
|
||||
namespace Microsoft.CodeAnalysis.Razor
|
||||
|
|
@ -26,24 +25,32 @@ namespace Microsoft.CodeAnalysis.Razor
|
|||
_viewComponentAttributeSymbol = compilation.GetTypeByMetadataName(ViewComponentTypes.ViewComponentAttribute);
|
||||
_genericTaskSymbol = compilation.GetTypeByMetadataName(ViewComponentTypes.GenericTask);
|
||||
_taskSymbol = compilation.GetTypeByMetadataName(ViewComponentTypes.Task);
|
||||
_iDictionarySymbol = compilation.GetTypeByMetadataName(TagHelperTypes.IDictionary);
|
||||
_iDictionarySymbol = compilation.GetTypeByMetadataName(ViewComponentTypes.IDictionary);
|
||||
}
|
||||
|
||||
public virtual TagHelperDescriptor CreateDescriptor(INamedTypeSymbol type)
|
||||
{
|
||||
var assemblyName = type.ContainingAssembly.Name;
|
||||
var shortName = GetShortName(type);
|
||||
var tagName = $"vc:{DefaultTagHelperDescriptorFactory.ToHtmlCase(shortName)}";
|
||||
var tagName = $"vc:{HtmlCase.ToHtmlCase(shortName)}";
|
||||
var typeName = $"__Generated__{shortName}ViewComponentTagHelper";
|
||||
var descriptorBuilder = TagHelperDescriptorBuilder.Create(typeName, assemblyName);
|
||||
var methodParameters = GetInvokeMethodParameters(type);
|
||||
descriptorBuilder.TagMatchingRule(ruleBuilder =>
|
||||
{
|
||||
ruleBuilder.RequireTagName(tagName);
|
||||
AddRequiredAttributes(methodParameters, ruleBuilder);
|
||||
});
|
||||
|
||||
AddBoundAttributes(methodParameters, descriptorBuilder);
|
||||
if (TryFindInvokeMethod(type, out var method, out var diagnostic))
|
||||
{
|
||||
var methodParameters = method.Parameters;
|
||||
descriptorBuilder.TagMatchingRule(ruleBuilder =>
|
||||
{
|
||||
ruleBuilder.RequireTagName(tagName);
|
||||
AddRequiredAttributes(methodParameters, ruleBuilder);
|
||||
});
|
||||
|
||||
AddBoundAttributes(methodParameters, descriptorBuilder);
|
||||
}
|
||||
else
|
||||
{
|
||||
descriptorBuilder.AddDiagnostic(diagnostic);
|
||||
}
|
||||
|
||||
descriptorBuilder.AddMetadata(ViewComponentTypes.ViewComponentNameKey, shortName);
|
||||
|
||||
|
|
@ -51,6 +58,77 @@ namespace Microsoft.CodeAnalysis.Razor
|
|||
return descriptor;
|
||||
}
|
||||
|
||||
private bool TryFindInvokeMethod(INamedTypeSymbol type, out IMethodSymbol method, out RazorDiagnostic diagnostic)
|
||||
{
|
||||
var methods = type.GetMembers()
|
||||
.OfType<IMethodSymbol>()
|
||||
.Where(m =>
|
||||
m.DeclaredAccessibility == Accessibility.Public &&
|
||||
(string.Equals(m.Name, ViewComponentTypes.AsyncMethodName, StringComparison.Ordinal) ||
|
||||
string.Equals(m.Name, ViewComponentTypes.SyncMethodName, StringComparison.Ordinal)))
|
||||
.ToArray();
|
||||
|
||||
if (methods.Length == 0)
|
||||
{
|
||||
diagnostic = ViewComponentDiagnosticFactory.CreateViewComponent_CannotFindMethod(type.ToDisplayString(FullNameTypeDisplayFormat));
|
||||
method = null;
|
||||
return false;
|
||||
}
|
||||
else if (methods.Length > 1)
|
||||
{
|
||||
diagnostic = ViewComponentDiagnosticFactory.CreateViewComponent_AmbiguousMethods(type.ToDisplayString(FullNameTypeDisplayFormat));
|
||||
method = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
var selectedMethod = methods[0];
|
||||
var returnType = selectedMethod.ReturnType as INamedTypeSymbol;
|
||||
if (string.Equals(selectedMethod.Name, ViewComponentTypes.AsyncMethodName, StringComparison.Ordinal))
|
||||
{
|
||||
// Will invoke asynchronously. Method must not return Task or Task<T>.
|
||||
if (returnType == _taskSymbol)
|
||||
{
|
||||
// This is ok.
|
||||
}
|
||||
else if (returnType.IsGenericType && returnType.ConstructedFrom == _genericTaskSymbol)
|
||||
{
|
||||
// This is ok.
|
||||
}
|
||||
else
|
||||
{
|
||||
diagnostic = ViewComponentDiagnosticFactory.CreateViewComponent_AsyncMethod_ShouldReturnTask(type.ToDisplayString(FullNameTypeDisplayFormat));
|
||||
method = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Will invoke synchronously. Method must not return void, Task or Task<T>.
|
||||
if (returnType.SpecialType == SpecialType.System_Void)
|
||||
{
|
||||
diagnostic = ViewComponentDiagnosticFactory.CreateViewComponent_SyncMethod_ShouldReturnValue(type.ToDisplayString(FullNameTypeDisplayFormat));
|
||||
method = null;
|
||||
return false;
|
||||
}
|
||||
else if (returnType == _taskSymbol)
|
||||
{
|
||||
diagnostic = ViewComponentDiagnosticFactory.CreateViewComponent_SyncMethod_CannotReturnTask(type.ToDisplayString(FullNameTypeDisplayFormat));
|
||||
method = null;
|
||||
return false;
|
||||
}
|
||||
else if (returnType.IsGenericType && returnType.ConstructedFrom == _genericTaskSymbol)
|
||||
{
|
||||
diagnostic = ViewComponentDiagnosticFactory.CreateViewComponent_SyncMethod_CannotReturnTask(type.ToDisplayString(FullNameTypeDisplayFormat));
|
||||
method = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
method = selectedMethod;
|
||||
diagnostic = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
private void AddRequiredAttributes(ImmutableArray<IParameterSymbol> methodParameters, TagMatchingRuleBuilder builder)
|
||||
{
|
||||
foreach (var parameter in methodParameters)
|
||||
|
|
@ -61,7 +139,7 @@ namespace Microsoft.CodeAnalysis.Razor
|
|||
// because there are two ways of setting values for the attribute.
|
||||
builder.RequireAttribute(attributeBuilder =>
|
||||
{
|
||||
var lowerKebabName = DefaultTagHelperDescriptorFactory.ToHtmlCase(parameter.Name);
|
||||
var lowerKebabName = HtmlCase.ToHtmlCase(parameter.Name);
|
||||
attributeBuilder.Name(lowerKebabName);
|
||||
});
|
||||
}
|
||||
|
|
@ -72,7 +150,7 @@ namespace Microsoft.CodeAnalysis.Razor
|
|||
{
|
||||
foreach (var parameter in methodParameters)
|
||||
{
|
||||
var lowerKebabName = DefaultTagHelperDescriptorFactory.ToHtmlCase(parameter.Name);
|
||||
var lowerKebabName = HtmlCase.ToHtmlCase(parameter.Name);
|
||||
var typeName = parameter.Type.ToDisplayString(FullNameTypeDisplayFormat);
|
||||
builder.BindAttribute(attributeBuilder =>
|
||||
{
|
||||
|
|
@ -124,77 +202,6 @@ namespace Microsoft.CodeAnalysis.Razor
|
|||
return typeName;
|
||||
}
|
||||
|
||||
private ImmutableArray<IParameterSymbol> GetInvokeMethodParameters(INamedTypeSymbol componentType)
|
||||
{
|
||||
var methods = componentType.GetMembers()
|
||||
.OfType<IMethodSymbol>()
|
||||
.Where(method =>
|
||||
method.DeclaredAccessibility == Accessibility.Public &&
|
||||
(string.Equals(method.Name, ViewComponentTypes.AsyncMethodName, StringComparison.Ordinal) ||
|
||||
string.Equals(method.Name, ViewComponentTypes.SyncMethodName, StringComparison.Ordinal)))
|
||||
.ToArray();
|
||||
|
||||
if (methods.Length == 0)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
ViewComponentResources.FormatViewComponent_CannotFindMethod(ViewComponentTypes.SyncMethodName, ViewComponentTypes.AsyncMethodName, componentType.ToDisplayString(FullNameTypeDisplayFormat)));
|
||||
}
|
||||
else if (methods.Length > 1)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
ViewComponentResources.FormatViewComponent_AmbiguousMethods(componentType.ToDisplayString(FullNameTypeDisplayFormat), ViewComponentTypes.AsyncMethodName, ViewComponentTypes.SyncMethodName));
|
||||
}
|
||||
|
||||
var selectedMethod = methods[0];
|
||||
var returnType = selectedMethod.ReturnType as INamedTypeSymbol;
|
||||
if (string.Equals(selectedMethod.Name, ViewComponentTypes.AsyncMethodName, StringComparison.Ordinal) && returnType != null)
|
||||
{
|
||||
if (!returnType.IsGenericType == true ||
|
||||
returnType.ConstructedFrom == _genericTaskSymbol)
|
||||
{
|
||||
throw new InvalidOperationException(ViewComponentResources.FormatViewComponent_AsyncMethod_ShouldReturnTask(
|
||||
ViewComponentTypes.AsyncMethodName,
|
||||
componentType.ToDisplayString(FullNameTypeDisplayFormat),
|
||||
nameof(Task)));
|
||||
}
|
||||
}
|
||||
else if (returnType != null)
|
||||
{
|
||||
// Will invoke synchronously. Method must not return void, Task or Task<T>.
|
||||
if (returnType.SpecialType == SpecialType.System_Void)
|
||||
{
|
||||
throw new InvalidOperationException(ViewComponentResources.FormatViewComponent_SyncMethod_ShouldReturnValue(
|
||||
ViewComponentTypes.SyncMethodName,
|
||||
componentType.ToDisplayString(FullNameTypeDisplayFormat)));
|
||||
}
|
||||
|
||||
var inheritsFromTask = false;
|
||||
var currentType = returnType;
|
||||
while (currentType != null)
|
||||
{
|
||||
if (currentType == _taskSymbol)
|
||||
{
|
||||
inheritsFromTask = true;
|
||||
break;
|
||||
}
|
||||
|
||||
currentType = currentType.BaseType;
|
||||
}
|
||||
|
||||
if (inheritsFromTask)
|
||||
{
|
||||
throw new InvalidOperationException(ViewComponentResources.FormatViewComponent_SyncMethod_CannotReturnTask(
|
||||
ViewComponentTypes.SyncMethodName,
|
||||
componentType.ToDisplayString(FullNameTypeDisplayFormat),
|
||||
nameof(Task)));
|
||||
}
|
||||
}
|
||||
|
||||
var methodParameters = selectedMethod.Parameters;
|
||||
|
||||
return methodParameters;
|
||||
}
|
||||
|
||||
private string GetShortName(INamedTypeSymbol componentType)
|
||||
{
|
||||
var viewComponentAttribute = componentType.GetAttributes().Where(a => a.AttributeClass == _viewComponentAttributeSymbol).FirstOrDefault();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,65 @@
|
|||
// 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;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
|
||||
namespace Microsoft.CodeAnalysis.Razor
|
||||
{
|
||||
public sealed class ViewComponentTagHelperDescriptorProvider : RazorEngineFeatureBase, ITagHelperDescriptorProvider
|
||||
{
|
||||
// Hack for testability. The visitor will normally just no op if we're not referencing
|
||||
// an appropriate version of MVC.
|
||||
internal bool ForceEnabled { get; set; }
|
||||
|
||||
public int Order { get; set; }
|
||||
|
||||
public void Execute(TagHelperDescriptorProviderContext context)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
var compilation = context.GetCompilation();
|
||||
if (compilation == null)
|
||||
{
|
||||
// No compilation, nothing to do.
|
||||
return;
|
||||
}
|
||||
|
||||
var types = new List<INamedTypeSymbol>();
|
||||
var visitor = ViewComponentTypeVisitor.Create(compilation, types);
|
||||
if (ForceEnabled)
|
||||
{
|
||||
visitor.Enabled = true;
|
||||
}
|
||||
|
||||
// We always visit the global namespace.
|
||||
visitor.Visit(compilation.Assembly.GlobalNamespace);
|
||||
|
||||
foreach (var reference in compilation.References)
|
||||
{
|
||||
if (compilation.GetAssemblyOrModuleSymbol(reference) is IAssemblySymbol assembly)
|
||||
{
|
||||
if (IsTagHelperAssembly(assembly))
|
||||
{
|
||||
visitor.Visit(assembly.GlobalNamespace);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var factory = new ViewComponentTagHelperDescriptorFactory(compilation);
|
||||
for (var i = 0; i < types.Count; i++)
|
||||
{
|
||||
context.Results.Add(factory.CreateDescriptor(types[i]));
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsTagHelperAssembly(IAssemblySymbol assembly)
|
||||
{
|
||||
return assembly.Name != null && !assembly.Name.StartsWith("System.", StringComparison.Ordinal);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -11,9 +11,9 @@ namespace Microsoft.CodeAnalysis.Razor
|
|||
{
|
||||
private static readonly Version SupportedVCTHMvcVersion = new Version(1, 1);
|
||||
|
||||
private INamedTypeSymbol _viewComponentAttribute;
|
||||
private INamedTypeSymbol _nonViewComponentAttribute;
|
||||
private List<INamedTypeSymbol> _results;
|
||||
private readonly INamedTypeSymbol _viewComponentAttribute;
|
||||
private readonly INamedTypeSymbol _nonViewComponentAttribute;
|
||||
private readonly List<INamedTypeSymbol> _results;
|
||||
|
||||
public static ViewComponentTypeVisitor Create(Compilation compilation, List<INamedTypeSymbol> results)
|
||||
{
|
||||
|
|
@ -45,8 +45,12 @@ namespace Microsoft.CodeAnalysis.Razor
|
|||
_viewComponentAttribute = viewComponentAttribute;
|
||||
_nonViewComponentAttribute = nonViewComponentAttribute;
|
||||
_results = results;
|
||||
|
||||
Enabled = _viewComponentAttribute != null;
|
||||
}
|
||||
|
||||
public bool Enabled { get; set; }
|
||||
|
||||
public override void VisitNamedType(INamedTypeSymbol symbol)
|
||||
{
|
||||
if (IsViewComponent(symbol))
|
||||
|
|
@ -75,7 +79,7 @@ namespace Microsoft.CodeAnalysis.Razor
|
|||
|
||||
internal bool IsViewComponent(INamedTypeSymbol symbol)
|
||||
{
|
||||
if (_viewComponentAttribute == null)
|
||||
if (!Enabled)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
|
@ -94,7 +98,7 @@ namespace Microsoft.CodeAnalysis.Razor
|
|||
|
||||
private static bool AttributeIsDefined(INamedTypeSymbol type, INamedTypeSymbol queryAttribute)
|
||||
{
|
||||
if (type == null)
|
||||
if (type == null || queryAttribute == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,8 @@ namespace Microsoft.CodeAnalysis.Razor
|
|||
|
||||
public const string Task = "System.Threading.Tasks.Task";
|
||||
|
||||
public const string IDictionary = "System.Collections.Generic.IDictionary`2";
|
||||
|
||||
public const string ViewComponentNameKey = "ViewComponentName";
|
||||
|
||||
public const string AsyncMethodName = "InvokeAsync";
|
||||
|
|
|
|||
|
|
@ -505,7 +505,9 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.IntegrationTests
|
|||
}
|
||||
else
|
||||
{
|
||||
b.Features.Add(new DefaultTagHelperFeature());
|
||||
b.Features.Add(new CompilationTagHelperFeature());
|
||||
b.Features.Add(new DefaultTagHelperDescriptorProvider() { DesignTime = true });
|
||||
b.Features.Add(new ViewComponentTagHelperDescriptorProvider());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -524,7 +526,9 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.IntegrationTests
|
|||
}
|
||||
else
|
||||
{
|
||||
b.Features.Add(new DefaultTagHelperFeature());
|
||||
b.Features.Add(new CompilationTagHelperFeature());
|
||||
b.Features.Add(new DefaultTagHelperDescriptorProvider());
|
||||
b.Features.Add(new ViewComponentTagHelperDescriptorProvider());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -154,7 +154,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions
|
|||
{
|
||||
return RazorEngine.Create(b =>
|
||||
{
|
||||
b.Features.Add(new TagHelperFeature(tagHelpers));
|
||||
b.Features.Add(new TestTagHelperFeature(tagHelpers));
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -205,30 +205,5 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions
|
|||
Node = node;
|
||||
}
|
||||
}
|
||||
|
||||
private class TagHelperFeature : RazorEngineFeatureBase, ITagHelperFeature
|
||||
{
|
||||
public TagHelperFeature(TagHelperDescriptor[] tagHelpers)
|
||||
{
|
||||
Resolver = new TagHelperDescriptorResolver(tagHelpers);
|
||||
}
|
||||
|
||||
public ITagHelperDescriptorResolver Resolver { get; }
|
||||
}
|
||||
|
||||
private class TagHelperDescriptorResolver : ITagHelperDescriptorResolver
|
||||
{
|
||||
public TagHelperDescriptorResolver(TagHelperDescriptor[] tagHelpers)
|
||||
{
|
||||
TagHelpers = tagHelpers;
|
||||
}
|
||||
|
||||
public TagHelperDescriptor[] TagHelpers { get; }
|
||||
|
||||
public IEnumerable<TagHelperDescriptor> Resolve(IList<RazorDiagnostic> errors)
|
||||
{
|
||||
return TagHelpers;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
using Microsoft.CodeAnalysis.Razor;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Razor.Extensions
|
||||
|
|
|
|||
|
|
@ -1,12 +1,10 @@
|
|||
// 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.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
using Microsoft.AspNetCore.Razor.Language.Intermediate;
|
||||
using Microsoft.AspNetCore.Razor.Language.Legacy;
|
||||
using Microsoft.CodeAnalysis.Razor;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Razor.Extensions
|
||||
|
|
@ -295,7 +293,7 @@ public class __Generated__TagCloudViewComponentTagHelper : Microsoft.AspNetCore.
|
|||
{
|
||||
b.Features.Add(new MvcViewDocumentClassifierPass());
|
||||
|
||||
b.Features.Add(new TagHelperFeature(tagHelpers));
|
||||
b.Features.Add(new TestTagHelperFeature(tagHelpers));
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -363,30 +361,5 @@ public class __Generated__TagCloudViewComponentTagHelper : Microsoft.AspNetCore.
|
|||
Node = node;
|
||||
}
|
||||
}
|
||||
|
||||
private class TagHelperFeature : RazorEngineFeatureBase, ITagHelperFeature
|
||||
{
|
||||
public TagHelperFeature(TagHelperDescriptor[] tagHelpers)
|
||||
{
|
||||
Resolver = new TagHelperDescriptorResolver(tagHelpers);
|
||||
}
|
||||
|
||||
public ITagHelperDescriptorResolver Resolver { get; }
|
||||
}
|
||||
|
||||
private class TagHelperDescriptorResolver : ITagHelperDescriptorResolver
|
||||
{
|
||||
public TagHelperDescriptorResolver(TagHelperDescriptor[] tagHelpers)
|
||||
{
|
||||
TagHelpers = tagHelpers;
|
||||
}
|
||||
|
||||
public TagHelperDescriptor[] TagHelpers { get; }
|
||||
|
||||
public IEnumerable<TagHelperDescriptor> Resolve(IList<RazorDiagnostic> errors)
|
||||
{
|
||||
return TagHelpers;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -318,12 +318,11 @@ namespace Microsoft.AspNetCore.Razor.Language
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public void Execute_NoopsWhenNoResolver()
|
||||
public void Execute_NoopsWhenNoFeature()
|
||||
{
|
||||
// Arrange
|
||||
var engine = RazorEngine.Create(builder =>
|
||||
{
|
||||
builder.Features.Add(Mock.Of<ITagHelperFeature>());
|
||||
});
|
||||
var phase = new DefaultRazorTagHelperBinderPhase()
|
||||
{
|
||||
|
|
@ -401,76 +400,6 @@ namespace Microsoft.AspNetCore.Razor.Language
|
|||
Assert.Empty(context.TagHelpers);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Execute_RecreatesSyntaxTreeOnResolverErrors()
|
||||
{
|
||||
// Arrange
|
||||
var resolverError = RazorDiagnostic.Create(new RazorError("Test error", new SourceLocation(19, 1, 17), length: 12));
|
||||
var engine = RazorEngine.Create(builder =>
|
||||
{
|
||||
var resolver = new ErrorLoggingTagHelperDescriptorResolver(resolverError, tagName: "test");
|
||||
builder.Features.Add(Mock.Of<ITagHelperFeature>(f => f.Resolver == resolver));
|
||||
});
|
||||
|
||||
var phase = new DefaultRazorTagHelperBinderPhase()
|
||||
{
|
||||
Engine = engine,
|
||||
};
|
||||
|
||||
var sourceDocument = CreateTestSourceDocument();
|
||||
var codeDocument = RazorCodeDocument.Create(sourceDocument);
|
||||
var originalTree = RazorSyntaxTree.Parse(sourceDocument);
|
||||
|
||||
var initialError = RazorDiagnostic.Create(new RazorError("Initial test error", SourceLocation.Zero, length: 1));
|
||||
var erroredOriginalTree = RazorSyntaxTree.Create(
|
||||
originalTree.Root,
|
||||
originalTree.Source,
|
||||
new[] { initialError },
|
||||
originalTree.Options);
|
||||
codeDocument.SetSyntaxTree(erroredOriginalTree);
|
||||
|
||||
// Act
|
||||
phase.Execute(codeDocument);
|
||||
|
||||
// Assert
|
||||
var outputTree = codeDocument.GetSyntaxTree();
|
||||
Assert.Empty(originalTree.Diagnostics);
|
||||
Assert.NotSame(erroredOriginalTree, outputTree);
|
||||
Assert.Equal(new[] { initialError, resolverError }, outputTree.Diagnostics);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Execute_CombinesDiagnosticsFromTagHelperDescriptor()
|
||||
{
|
||||
// Arrange
|
||||
var resolverError = RazorDiagnostic.Create(new RazorError("Test error", new SourceLocation(19, 1, 17), length: 12));
|
||||
var engine = RazorEngine.Create(builder =>
|
||||
{
|
||||
var resolver = new ErrorLoggingTagHelperDescriptorResolver(resolverError, tagName: null);
|
||||
builder.Features.Add(Mock.Of<ITagHelperFeature>(f => f.Resolver == resolver));
|
||||
});
|
||||
|
||||
var descriptorError = RazorDiagnosticFactory.CreateTagHelper_InvalidTargetedTagNameNullOrWhitespace();
|
||||
|
||||
var phase = new DefaultRazorTagHelperBinderPhase()
|
||||
{
|
||||
Engine = engine,
|
||||
};
|
||||
|
||||
var sourceDocument = CreateTestSourceDocument();
|
||||
var codeDocument = RazorCodeDocument.Create(sourceDocument);
|
||||
var originalTree = RazorSyntaxTree.Parse(sourceDocument);
|
||||
codeDocument.SetSyntaxTree(originalTree);
|
||||
|
||||
// Act
|
||||
phase.Execute(codeDocument);
|
||||
|
||||
// Assert
|
||||
var outputTree = codeDocument.GetSyntaxTree();
|
||||
Assert.Empty(originalTree.Diagnostics);
|
||||
Assert.Equal(new[] { resolverError, descriptorError }, outputTree.Diagnostics);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Execute_CombinesErrorsOnRewritingErrors()
|
||||
{
|
||||
|
|
@ -1490,27 +1419,5 @@ namespace Microsoft.AspNetCore.Razor.Language
|
|||
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
private class ErrorLoggingTagHelperDescriptorResolver : ITagHelperDescriptorResolver
|
||||
{
|
||||
private readonly RazorDiagnostic _error;
|
||||
private readonly string _tagName;
|
||||
|
||||
public ErrorLoggingTagHelperDescriptorResolver(RazorDiagnostic error, string tagName = null)
|
||||
{
|
||||
_error = error;
|
||||
_tagName = tagName;
|
||||
}
|
||||
|
||||
public IEnumerable<TagHelperDescriptor> Resolve(IList<RazorDiagnostic> errors)
|
||||
{
|
||||
errors.Add(_error);
|
||||
|
||||
return new[] { CreateTagHelperDescriptor(
|
||||
tagName: _tagName,
|
||||
typeName: null,
|
||||
assemblyName: "TestAssembly") };
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,39 @@
|
|||
// 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 Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.Language
|
||||
{
|
||||
public class HtmlCaseTest
|
||||
{
|
||||
public static TheoryData HtmlConversionData
|
||||
{
|
||||
get
|
||||
{
|
||||
return new TheoryData<string, string>
|
||||
{
|
||||
{ "SomeThing", "some-thing" },
|
||||
{ "someOtherThing", "some-other-thing" },
|
||||
{ "capsONInside", "caps-on-inside" },
|
||||
{ "CAPSOnOUTSIDE", "caps-on-outside" },
|
||||
{ "ALLCAPS", "allcaps" },
|
||||
{ "One1Two2Three3", "one1-two2-three3" },
|
||||
{ "ONE1TWO2THREE3", "one1two2three3" },
|
||||
{ "First_Second_ThirdHi", "first_second_third-hi" }
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(HtmlConversionData))]
|
||||
public void ToHtmlCase_ReturnsExpectedConversions(string input, string expectedOutput)
|
||||
{
|
||||
// Arrange, Act
|
||||
var output = HtmlCase.ToHtmlCase(input);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(output, expectedOutput);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
// 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.Collections.Generic;
|
||||
using Microsoft.AspNetCore.Razor.Language.Legacy;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.Language
|
||||
{
|
||||
internal class TestTagHelperDescriptorResolver : ITagHelperDescriptorResolver
|
||||
{
|
||||
public TestTagHelperDescriptorResolver()
|
||||
{
|
||||
}
|
||||
|
||||
public TestTagHelperDescriptorResolver(IEnumerable<TagHelperDescriptor> tagHelpers)
|
||||
{
|
||||
TagHelpers.AddRange(tagHelpers);
|
||||
}
|
||||
|
||||
public List<TagHelperDescriptor> TagHelpers { get; } = new List<TagHelperDescriptor>();
|
||||
|
||||
public IEnumerable<TagHelperDescriptor> Resolve(IList<RazorDiagnostic> errors)
|
||||
{
|
||||
return TagHelpers;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
// 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.Language.Legacy;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.Language
|
||||
|
|
@ -10,16 +9,19 @@ namespace Microsoft.AspNetCore.Razor.Language
|
|||
{
|
||||
public TestTagHelperFeature()
|
||||
{
|
||||
Resolver = new TestTagHelperDescriptorResolver();
|
||||
TagHelpers = new List<TagHelperDescriptor>();
|
||||
}
|
||||
|
||||
public TestTagHelperFeature(IEnumerable<TagHelperDescriptor> tagHelpers)
|
||||
{
|
||||
Resolver = new TestTagHelperDescriptorResolver(tagHelpers);
|
||||
TagHelpers = new List<TagHelperDescriptor>(tagHelpers);
|
||||
}
|
||||
|
||||
public List<TagHelperDescriptor> TagHelpers => ((TestTagHelperDescriptorResolver)Resolver).TagHelpers;
|
||||
public List<TagHelperDescriptor> TagHelpers { get; }
|
||||
|
||||
public ITagHelperDescriptorResolver Resolver { get; }
|
||||
public IReadOnlyList<TagHelperDescriptor> GetDescriptors()
|
||||
{
|
||||
return TagHelpers.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1970,35 +1970,6 @@ namespace Microsoft.CodeAnalysis.Razor.Workspaces
|
|||
Assert.Equal(expectedDiagnostics, descriptor.GetAllDiagnostics());
|
||||
}
|
||||
|
||||
public static TheoryData HtmlConversionData
|
||||
{
|
||||
get
|
||||
{
|
||||
return new TheoryData<string, string>
|
||||
{
|
||||
{ "SomeThing", "some-thing" },
|
||||
{ "someOtherThing", "some-other-thing" },
|
||||
{ "capsONInside", "caps-on-inside" },
|
||||
{ "CAPSOnOUTSIDE", "caps-on-outside" },
|
||||
{ "ALLCAPS", "allcaps" },
|
||||
{ "One1Two2Three3", "one1-two2-three3" },
|
||||
{ "ONE1TWO2THREE3", "one1two2three3" },
|
||||
{ "First_Second_ThirdHi", "first_second_third-hi" }
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(HtmlConversionData))]
|
||||
public void ToHtmlCase_ReturnsExpectedConversions(string input, string expectedOutput)
|
||||
{
|
||||
// Arrange, Act
|
||||
var output = DefaultTagHelperDescriptorFactory.ToHtmlCase(input);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(output, expectedOutput);
|
||||
}
|
||||
|
||||
public static TheoryData TagOutputHintData
|
||||
{
|
||||
get
|
||||
|
|
|
|||
|
|
@ -14,6 +14,29 @@ namespace Microsoft.CodeAnalysis.Razor
|
|||
{
|
||||
public static class TestCompilation
|
||||
{
|
||||
private static IEnumerable<MetadataReference> _metadataReferences;
|
||||
|
||||
public static IEnumerable<MetadataReference> MetadataReferences
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_metadataReferences == null)
|
||||
{
|
||||
var currentAssembly = typeof(TestCompilation).GetTypeInfo().Assembly;
|
||||
var dependencyContext = DependencyContext.Load(currentAssembly);
|
||||
|
||||
_metadataReferences = dependencyContext.CompileLibraries
|
||||
.SelectMany(l => l.ResolveReferencePaths())
|
||||
.Select(assemblyPath => MetadataReference.CreateFromFile(assemblyPath))
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
return _metadataReferences;
|
||||
}
|
||||
}
|
||||
|
||||
public static string AssemblyName => "TestAssembly";
|
||||
|
||||
public static Compilation Create(SyntaxTree syntaxTree = null)
|
||||
{
|
||||
IEnumerable<SyntaxTree> syntaxTrees = null;
|
||||
|
|
@ -23,12 +46,7 @@ namespace Microsoft.CodeAnalysis.Razor
|
|||
syntaxTrees = new[] { syntaxTree };
|
||||
}
|
||||
|
||||
var currentAssembly = typeof(TestCompilation).GetTypeInfo().Assembly;
|
||||
var dependencyContext = DependencyContext.Load(currentAssembly);
|
||||
|
||||
var references = dependencyContext.CompileLibraries.SelectMany(l => l.ResolveReferencePaths())
|
||||
.Select(assemblyPath => MetadataReference.CreateFromFile(assemblyPath));
|
||||
var compilation = CSharpCompilation.Create("TestAssembly", syntaxTrees, references);
|
||||
var compilation = CSharpCompilation.Create(AssemblyName, syntaxTrees, MetadataReferences);
|
||||
|
||||
EnsureValidCompilation(compilation);
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ using System.Collections.Generic;
|
|||
using System.Reflection;
|
||||
using Xunit;
|
||||
using Microsoft.AspNetCore.Razor.Language.Legacy;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.CodeAnalysis.Razor.Workspaces
|
||||
{
|
||||
|
|
@ -122,6 +123,140 @@ namespace Microsoft.CodeAnalysis.Razor.Workspaces
|
|||
// Assert
|
||||
Assert.Equal(expectedDescriptor, descriptor, TagHelperDescriptorComparer.CaseSensitive);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateDescriptor_AddsDiagnostic_ForViewComponentWithNoInvokeMethod()
|
||||
{
|
||||
// Arrange
|
||||
var testCompilation = TestCompilation.Create();
|
||||
var factory = new ViewComponentTagHelperDescriptorFactory(testCompilation);
|
||||
|
||||
var viewComponent = testCompilation.GetTypeByMetadataName(typeof(ViewComponentWithoutInvokeMethod).FullName);
|
||||
|
||||
// Act
|
||||
var descriptor = factory.CreateDescriptor(viewComponent);
|
||||
|
||||
// Assert
|
||||
var diagnostic = Assert.Single(descriptor.GetAllDiagnostics());
|
||||
Assert.Equal(ViewComponentDiagnosticFactory.ViewComponent_CannotFindMethod.Id, diagnostic.Id);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateDescriptor_ForViewComponentWithInvokeAsync_UnderstandsGenericTask()
|
||||
{
|
||||
// Arrange
|
||||
var testCompilation = TestCompilation.Create();
|
||||
var factory = new ViewComponentTagHelperDescriptorFactory(testCompilation);
|
||||
|
||||
var viewComponent = testCompilation.GetTypeByMetadataName(typeof(AsyncViewComponentWithGenericTask).FullName);
|
||||
|
||||
// Act
|
||||
var descriptor = factory.CreateDescriptor(viewComponent);
|
||||
|
||||
// Assert
|
||||
Assert.Empty(descriptor.GetAllDiagnostics());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateDescriptor_ForViewComponentWithInvokeAsync_UnderstandsNonGenericTask()
|
||||
{
|
||||
// Arrange
|
||||
var testCompilation = TestCompilation.Create();
|
||||
var factory = new ViewComponentTagHelperDescriptorFactory(testCompilation);
|
||||
|
||||
var viewComponent = testCompilation.GetTypeByMetadataName(typeof(AsyncViewComponentWithNonGenericTask).FullName);
|
||||
|
||||
// Act
|
||||
var descriptor = factory.CreateDescriptor(viewComponent);
|
||||
|
||||
// Assert
|
||||
Assert.Empty(descriptor.GetAllDiagnostics());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateDescriptor_ForViewComponentWithInvokeAsync_DoesNotUnderstandVoid()
|
||||
{
|
||||
// Arrange
|
||||
var testCompilation = TestCompilation.Create();
|
||||
var factory = new ViewComponentTagHelperDescriptorFactory(testCompilation);
|
||||
|
||||
var viewComponent = testCompilation.GetTypeByMetadataName(typeof(AsyncViewComponentWithString).FullName);
|
||||
|
||||
// Act
|
||||
var descriptor = factory.CreateDescriptor(viewComponent);
|
||||
|
||||
// Assert
|
||||
var diagnostic = Assert.Single(descriptor.GetAllDiagnostics());
|
||||
Assert.Equal(ViewComponentDiagnosticFactory.ViewComponent_AsyncMethod_ShouldReturnTask.Id, diagnostic.Id);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateDescriptor_ForViewComponentWithInvokeAsync_DoesNotUnderstandString()
|
||||
{
|
||||
// Arrange
|
||||
var testCompilation = TestCompilation.Create();
|
||||
var factory = new ViewComponentTagHelperDescriptorFactory(testCompilation);
|
||||
|
||||
var viewComponent = testCompilation.GetTypeByMetadataName(typeof(AsyncViewComponentWithString).FullName);
|
||||
|
||||
// Act
|
||||
var descriptor = factory.CreateDescriptor(viewComponent);
|
||||
|
||||
// Assert
|
||||
var diagnostic = Assert.Single(descriptor.GetAllDiagnostics());
|
||||
Assert.Equal(ViewComponentDiagnosticFactory.ViewComponent_AsyncMethod_ShouldReturnTask.Id, diagnostic.Id);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateDescriptor_ForViewComponentWithInvoke_DoesNotUnderstandVoid()
|
||||
{
|
||||
// Arrange
|
||||
var testCompilation = TestCompilation.Create();
|
||||
var factory = new ViewComponentTagHelperDescriptorFactory(testCompilation);
|
||||
|
||||
var viewComponent = testCompilation.GetTypeByMetadataName(typeof(SyncViewComponentWithVoid).FullName);
|
||||
|
||||
// Act
|
||||
var descriptor = factory.CreateDescriptor(viewComponent);
|
||||
|
||||
// Assert
|
||||
var diagnostic = Assert.Single(descriptor.GetAllDiagnostics());
|
||||
Assert.Equal(ViewComponentDiagnosticFactory.ViewComponent_SyncMethod_ShouldReturnValue.Id, diagnostic.Id);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateDescriptor_ForViewComponentWithInvoke_DoesNotUnderstandNonGenericTask()
|
||||
{
|
||||
// Arrange
|
||||
var testCompilation = TestCompilation.Create();
|
||||
var factory = new ViewComponentTagHelperDescriptorFactory(testCompilation);
|
||||
|
||||
var viewComponent = testCompilation.GetTypeByMetadataName(typeof(SyncViewComponentWithNonGenericTask).FullName);
|
||||
|
||||
// Act
|
||||
var descriptor = factory.CreateDescriptor(viewComponent);
|
||||
|
||||
// Assert
|
||||
var diagnostic = Assert.Single(descriptor.GetAllDiagnostics());
|
||||
Assert.Equal(ViewComponentDiagnosticFactory.ViewComponent_SyncMethod_CannotReturnTask.Id, diagnostic.Id);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateDescriptor_ForViewComponentWithInvoke_DoesNotUnderstandGenericTask()
|
||||
{
|
||||
// Arrange
|
||||
var testCompilation = TestCompilation.Create();
|
||||
var factory = new ViewComponentTagHelperDescriptorFactory(testCompilation);
|
||||
|
||||
var viewComponent = testCompilation.GetTypeByMetadataName(typeof(SyncViewComponentWithGenericTask).FullName);
|
||||
|
||||
// Act
|
||||
var descriptor = factory.CreateDescriptor(viewComponent);
|
||||
|
||||
// Assert
|
||||
var diagnostic = Assert.Single(descriptor.GetAllDiagnostics());
|
||||
Assert.Equal(ViewComponentDiagnosticFactory.ViewComponent_SyncMethod_CannotReturnTask.Id, diagnostic.Id);
|
||||
}
|
||||
}
|
||||
|
||||
public class StringParameterViewComponent
|
||||
|
|
@ -145,4 +280,43 @@ namespace Microsoft.CodeAnalysis.Razor.Workspaces
|
|||
{
|
||||
public string Invoke(List<string> Foo, Dictionary<string, int> Bar) => null;
|
||||
}
|
||||
|
||||
public class ViewComponentWithoutInvokeMethod
|
||||
{
|
||||
}
|
||||
|
||||
public class AsyncViewComponentWithGenericTask
|
||||
{
|
||||
public Task<string> InvokeAsync() => null;
|
||||
}
|
||||
|
||||
public class AsyncViewComponentWithNonGenericTask
|
||||
{
|
||||
public Task InvokeAsync() => null;
|
||||
}
|
||||
|
||||
public class AsyncViewComponentWithVoid
|
||||
{
|
||||
public void InvokeAsync() { }
|
||||
}
|
||||
|
||||
public class AsyncViewComponentWithString
|
||||
{
|
||||
public string InvokeAsync() => null;
|
||||
}
|
||||
|
||||
public class SyncViewComponentWithVoid
|
||||
{
|
||||
public void Invoke() { }
|
||||
}
|
||||
|
||||
public class SyncViewComponentWithNonGenericTask
|
||||
{
|
||||
public Task Invoke() => null;
|
||||
}
|
||||
|
||||
public class SyncViewComponentWithGenericTask
|
||||
{
|
||||
public Task<string> Invoke() => null;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
// 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;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.CodeAnalysis.Razor
|
||||
{
|
||||
// This is just a basic integration test. There are detailed tests for the VCTH visitor and descriptor factory.
|
||||
public class ViewComponentTagHelperDescriptorProviderTest
|
||||
{
|
||||
[Fact]
|
||||
public void DescriptorProvider_FindsVCTH()
|
||||
{
|
||||
// Arrange
|
||||
var code = @"
|
||||
public class StringParameterViewComponent
|
||||
{
|
||||
public string Invoke(string foo, string bar) => null;
|
||||
}
|
||||
";
|
||||
|
||||
var testCompilation = TestCompilation.Create(CSharpSyntaxTree.ParseText(code));
|
||||
|
||||
var context = TagHelperDescriptorProviderContext.Create();
|
||||
context.SetCompilation(testCompilation);
|
||||
|
||||
var provider = new ViewComponentTagHelperDescriptorProvider()
|
||||
{
|
||||
Engine = RazorEngine.CreateEmpty(b => { }),
|
||||
ForceEnabled = true,
|
||||
};
|
||||
|
||||
var expectedDescriptor = TagHelperDescriptorBuilder.Create(
|
||||
"__Generated__StringParameterViewComponentTagHelper",
|
||||
TestCompilation.AssemblyName)
|
||||
.TagMatchingRule(rule =>
|
||||
rule
|
||||
.RequireTagName("vc:string-parameter")
|
||||
.RequireAttribute(attribute => attribute.Name("foo"))
|
||||
.RequireAttribute(attribute => attribute.Name("bar")))
|
||||
.BindAttribute(attribute =>
|
||||
attribute
|
||||
.Name("foo")
|
||||
.PropertyName("foo")
|
||||
.TypeName(typeof(string).FullName))
|
||||
.BindAttribute(attribute =>
|
||||
attribute
|
||||
.Name("bar")
|
||||
.PropertyName("bar")
|
||||
.TypeName(typeof(string).FullName))
|
||||
.AddMetadata(ViewComponentTypes.ViewComponentNameKey, "StringParameter")
|
||||
.Build();
|
||||
|
||||
// Act
|
||||
provider.Execute(context);
|
||||
|
||||
// Assert
|
||||
var descriptor = context.Results.FirstOrDefault(d => TagHelperDescriptorComparer.CaseSensitive.Equals(d, expectedDescriptor));
|
||||
Assert.NotNull(descriptor);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue