diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/Microsoft.AspNetCore.Mvc.Razor.Extensions.csproj b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/Microsoft.AspNetCore.Mvc.Razor.Extensions.csproj
index 33ede5aecf..66f55496b0 100644
--- a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/Microsoft.AspNetCore.Mvc.Razor.Extensions.csproj
+++ b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/Microsoft.AspNetCore.Mvc.Razor.Extensions.csproj
@@ -10,8 +10,15 @@
aspnetcore;aspnetcoremvc;cshtml;razor
+
+
+ ViewComponentTagHelperDescriptorConventions.cs
+
+
+
+
diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/ViewComponentTagHelperPass.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/ViewComponentTagHelperPass.cs
index 396e4dab48..7b78770d54 100644
--- a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/ViewComponentTagHelperPass.cs
+++ b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/ViewComponentTagHelperPass.cs
@@ -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
{
diff --git a/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorTagHelperBinderPhase.cs b/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorTagHelperBinderPhase.cs
index 6221179257..a692fba5ee 100644
--- a/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorTagHelperBinderPhase.cs
+++ b/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorTagHelperBinderPhase.cs
@@ -20,10 +20,10 @@ namespace Microsoft.AspNetCore.Razor.Language
var syntaxTree = codeDocument.GetSyntaxTree();
ThrowForMissingDocumentDependency(syntaxTree);
- var resolver = Engine.Features.OfType().FirstOrDefault()?.Resolver;
- if (resolver == null)
+ var feature = Engine.Features.OfType().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();
- var descriptors = (IReadOnlyList)resolver.Resolve(errorList).ToList();
+ var descriptors = feature.GetDescriptors();
var errorSink = new ErrorSink();
var directives = visitor.Directives;
diff --git a/src/Microsoft.AspNetCore.Razor.Language/HtmlCase.cs b/src/Microsoft.AspNetCore.Razor.Language/HtmlCase.cs
new file mode 100644
index 0000000000..2238795334
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Razor.Language/HtmlCase.cs
@@ -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(
+ "(?
+ /// Converts from pascal/camel case to lower kebab-case.
+ ///
+ ///
+ /// 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
+ ///
+ public static string ToHtmlCase(string name)
+ {
+ return HtmlCaseRegex.Replace(name, HtmlCaseRegexReplacement).ToLowerInvariant();
+ }
+ }
+}
diff --git a/src/Microsoft.AspNetCore.Razor.Language/ITagHelperDescriptorProvider.cs b/src/Microsoft.AspNetCore.Razor.Language/ITagHelperDescriptorProvider.cs
new file mode 100644
index 0000000000..1b061f59ba
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Razor.Language/ITagHelperDescriptorProvider.cs
@@ -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);
+ }
+}
diff --git a/src/Microsoft.AspNetCore.Razor.Language/ITagHelperFeature.cs b/src/Microsoft.AspNetCore.Razor.Language/ITagHelperFeature.cs
index 9fb4b628da..1f33f948dc 100644
--- a/src/Microsoft.AspNetCore.Razor.Language/ITagHelperFeature.cs
+++ b/src/Microsoft.AspNetCore.Razor.Language/ITagHelperFeature.cs
@@ -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 GetDescriptors();
}
}
diff --git a/src/Microsoft.AspNetCore.Razor.Language/Legacy/ITagHelperDescriptorResolver.cs b/src/Microsoft.AspNetCore.Razor.Language/Legacy/ITagHelperDescriptorResolver.cs
deleted file mode 100644
index f6fca3bdcf..0000000000
--- a/src/Microsoft.AspNetCore.Razor.Language/Legacy/ITagHelperDescriptorResolver.cs
+++ /dev/null
@@ -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
-{
- ///
- /// Contract used to resolve s.
- ///
- public interface ITagHelperDescriptorResolver
- {
- IEnumerable Resolve(IList errors);
- }
-}
\ No newline at end of file
diff --git a/src/Microsoft.AspNetCore.Razor.Language/RazorDiagnosticFactory.cs b/src/Microsoft.AspNetCore.Razor.Language/RazorDiagnosticFactory.cs
index 08cc296ccd..6177ff2d62 100644
--- a/src/Microsoft.AspNetCore.Razor.Language/RazorDiagnosticFactory.cs
+++ b/src/Microsoft.AspNetCore.Razor.Language/RazorDiagnosticFactory.cs
@@ -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
diff --git a/src/Microsoft.AspNetCore.Razor.Language/TagHelperDescriptorBuilder.cs b/src/Microsoft.AspNetCore.Razor.Language/TagHelperDescriptorBuilder.cs
index 8da0776897..6303493387 100644
--- a/src/Microsoft.AspNetCore.Razor.Language/TagHelperDescriptorBuilder.cs
+++ b/src/Microsoft.AspNetCore.Razor.Language/TagHelperDescriptorBuilder.cs
@@ -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 InvalidNonWhitespaceAllowedChildCharacters { get; } = new HashSet(
new[] { '@', '!', '<', '/', '?', '[', '>', ']', '=', '"', '\'', '*' });
+
private string _documentation;
private string _tagOutputHint;
private HashSet _allowedChildTags;
diff --git a/src/Microsoft.AspNetCore.Razor.Language/TagHelperDescriptorProviderContext.cs b/src/Microsoft.AspNetCore.Razor.Language/TagHelperDescriptorProviderContext.cs
new file mode 100644
index 0000000000..ef228b9156
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Razor.Language/TagHelperDescriptorProviderContext.cs
@@ -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 Results { get; }
+
+ public static TagHelperDescriptorProviderContext Create()
+ {
+ return new DefaultContext(new List());
+ }
+
+ public static TagHelperDescriptorProviderContext Create(ICollection results)
+ {
+ if (results == null)
+ {
+ throw new ArgumentNullException(nameof(results));
+ }
+
+ return new DefaultContext(results);
+ }
+
+ private class DefaultContext : TagHelperDescriptorProviderContext
+ {
+ public DefaultContext(ICollection results)
+ {
+ Results = results;
+
+ Items = new DefaultItemCollection();
+ }
+
+ public override ItemCollection Items { get; }
+
+ public override ICollection Results { get; }
+ }
+ }
+}
diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/DefaultTagHelperResolver.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/DefaultTagHelperResolver.cs
index 21bd5ae0f6..df4f40cbdb 100644
--- a/src/Microsoft.CodeAnalysis.Razor.Workspaces/DefaultTagHelperResolver.cs
+++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/DefaultTagHelperResolver.cs
@@ -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();
- VisitTagHelpers(compilation, descriptors);
- VisitViewComponents(compilation, descriptors);
+ var providers = new ITagHelperDescriptorProvider[]
+ {
+ new DefaultTagHelperDescriptorProvider() { DesignTime = true, },
+ new ViewComponentTagHelperDescriptorProvider(),
+ };
+
+ var results = new List();
+ 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();
- var resolutionResult = new TagHelperResolutionResult(descriptors, diagnostics);
+ var resolutionResult = new TagHelperResolutionResult(results, diagnostics);
return resolutionResult;
}
-
- private void VisitTagHelpers(Compilation compilation, List results)
- {
- var types = new List();
- 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 results)
- {
- var types = new List();
- 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);
- }
- }
- }
}
}
\ No newline at end of file
diff --git a/src/Microsoft.CodeAnalysis.Razor/CompilationTagHelperFeature.cs b/src/Microsoft.CodeAnalysis.Razor/CompilationTagHelperFeature.cs
new file mode 100644
index 0000000000..bf32fd1a92
--- /dev/null
+++ b/src/Microsoft.CodeAnalysis.Razor/CompilationTagHelperFeature.cs
@@ -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 GetDescriptors()
+ {
+ var results = new List();
+
+ 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().FirstOrDefault();
+ _providers = Engine.Features.OfType().OrderBy(f => f.Order).ToArray();
+ }
+ }
+}
diff --git a/src/Microsoft.CodeAnalysis.Razor/DefaultTagHelperDescriptorFactory.cs b/src/Microsoft.CodeAnalysis.Razor/DefaultTagHelperDescriptorFactory.cs
index 760d9d019d..d4dc817ab1 100644
--- a/src/Microsoft.CodeAnalysis.Razor/DefaultTagHelperDescriptorFactory.cs
+++ b/src/Microsoft.CodeAnalysis.Razor/DefaultTagHelperDescriptorFactory.cs
@@ -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(
- "(? InvalidNonWhitespaceNameCharacters { get; } = new HashSet(
- 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;
}
- ///
- /// Converts from pascal/camel case to lower kebab-case.
- ///
- ///
- /// 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
- ///
- internal static string ToHtmlCase(string name)
- {
- return HtmlCaseRegex.Replace(name, HtmlCaseRegexReplacement).ToLowerInvariant();
- }
-
private static string GetFullName(ITypeSymbol type) => type.ToDisplayString(FullNameTypeDisplayFormat);
}
}
\ No newline at end of file
diff --git a/src/Microsoft.CodeAnalysis.Razor/DefaultTagHelperDescriptorProvider.cs b/src/Microsoft.CodeAnalysis.Razor/DefaultTagHelperDescriptorProvider.cs
new file mode 100644
index 0000000000..b9bf8f4822
--- /dev/null
+++ b/src/Microsoft.CodeAnalysis.Razor/DefaultTagHelperDescriptorProvider.cs
@@ -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();
+ 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);
+ }
+ }
+}
diff --git a/src/Microsoft.CodeAnalysis.Razor/DefaultTagHelperFeature.cs b/src/Microsoft.CodeAnalysis.Razor/DefaultTagHelperFeature.cs
deleted file mode 100644
index 16c8f5e7c1..0000000000
--- a/src/Microsoft.CodeAnalysis.Razor/DefaultTagHelperFeature.cs
+++ /dev/null
@@ -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());
- }
-
- private class InnerResolver : ITagHelperDescriptorResolver
- {
- private readonly IMetadataReferenceFeature _referenceFeature;
-
- public InnerResolver(IMetadataReferenceFeature referenceFeature)
- {
- _referenceFeature = referenceFeature;
- }
- public IEnumerable Resolve(IList errors)
- {
- var compilation = CSharpCompilation.Create("__TagHelpers", references: _referenceFeature.References);
- return TagHelpers.GetTagHelpers(compilation);
- }
- }
- }
-}
diff --git a/src/Microsoft.CodeAnalysis.Razor/TagHelperDescriptorProviderContextExtensions.cs b/src/Microsoft.CodeAnalysis.Razor/TagHelperDescriptorProviderContextExtensions.cs
new file mode 100644
index 0000000000..fda4aece22
--- /dev/null
+++ b/src/Microsoft.CodeAnalysis.Razor/TagHelperDescriptorProviderContextExtensions.cs
@@ -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;
+ }
+ }
+}
diff --git a/src/Microsoft.CodeAnalysis.Razor/TagHelpers.cs b/src/Microsoft.CodeAnalysis.Razor/TagHelpers.cs
deleted file mode 100644
index 0c3da8e2a5..0000000000
--- a/src/Microsoft.CodeAnalysis.Razor/TagHelpers.cs
+++ /dev/null
@@ -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 GetTagHelpers(Compilation compilation)
- {
- var results = new List();
- var errors = new ErrorSink();
-
- VisitTagHelpers(compilation, results, errors);
- VisitViewComponents(compilation, results, errors);
-
- return results;
- }
-
- private static void VisitTagHelpers(Compilation compilation, List results, ErrorSink errors)
- {
- var types = new List();
- 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 results, ErrorSink errors)
- {
- var types = new List();
- 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);
- }
- }
- }
- }
-}
diff --git a/src/Microsoft.CodeAnalysis.Razor/ViewComponentDiagnosticFactory.cs b/src/Microsoft.CodeAnalysis.Razor/ViewComponentDiagnosticFactory.cs
new file mode 100644
index 0000000000..dacfec538a
--- /dev/null
+++ b/src/Microsoft.CodeAnalysis.Razor/ViewComponentDiagnosticFactory.cs
@@ -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;
+ }
+ }
+}
diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/ViewComponentTagHelperDescriptorConventions.cs b/src/Microsoft.CodeAnalysis.Razor/ViewComponentTagHelperDescriptorConventions.cs
similarity index 90%
rename from src/Microsoft.AspNetCore.Mvc.Razor.Extensions/ViewComponentTagHelperDescriptorConventions.cs
rename to src/Microsoft.CodeAnalysis.Razor/ViewComponentTagHelperDescriptorConventions.cs
index 63bb660c65..41a0ac64f1 100644
--- a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/ViewComponentTagHelperDescriptorConventions.cs
+++ b/src/Microsoft.CodeAnalysis.Razor/ViewComponentTagHelperDescriptorConventions.cs
@@ -3,12 +3,12 @@
using Microsoft.AspNetCore.Razor.Language;
-namespace Microsoft.AspNetCore.Mvc.Razor.Extensions
+namespace Microsoft.CodeAnalysis.Razor
{
///
/// A library of methods used to generate s for view components.
///
- public static class ViewComponentTagHelperDescriptorConventions
+ internal static class ViewComponentTagHelperDescriptorConventions
{
///
/// The key in a containing
diff --git a/src/Microsoft.CodeAnalysis.Razor/ViewComponentTagHelperDescriptorFactory.cs b/src/Microsoft.CodeAnalysis.Razor/ViewComponentTagHelperDescriptorFactory.cs
index 52c6902afa..5202b9ca72 100644
--- a/src/Microsoft.CodeAnalysis.Razor/ViewComponentTagHelperDescriptorFactory.cs
+++ b/src/Microsoft.CodeAnalysis.Razor/ViewComponentTagHelperDescriptorFactory.cs
@@ -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()
+ .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.
+ 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.
+ 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 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 GetInvokeMethodParameters(INamedTypeSymbol componentType)
- {
- var methods = componentType.GetMembers()
- .OfType()
- .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.
- 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();
diff --git a/src/Microsoft.CodeAnalysis.Razor/ViewComponentTagHelperDescriptorProvider.cs b/src/Microsoft.CodeAnalysis.Razor/ViewComponentTagHelperDescriptorProvider.cs
new file mode 100644
index 0000000000..52e2dedc39
--- /dev/null
+++ b/src/Microsoft.CodeAnalysis.Razor/ViewComponentTagHelperDescriptorProvider.cs
@@ -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();
+ 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);
+ }
+ }
+}
diff --git a/src/Microsoft.CodeAnalysis.Razor/ViewComponentTypeVisitor.cs b/src/Microsoft.CodeAnalysis.Razor/ViewComponentTypeVisitor.cs
index e689daca6a..8dda01d30b 100644
--- a/src/Microsoft.CodeAnalysis.Razor/ViewComponentTypeVisitor.cs
+++ b/src/Microsoft.CodeAnalysis.Razor/ViewComponentTypeVisitor.cs
@@ -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 _results;
+ private readonly INamedTypeSymbol _viewComponentAttribute;
+ private readonly INamedTypeSymbol _nonViewComponentAttribute;
+ private readonly List _results;
public static ViewComponentTypeVisitor Create(Compilation compilation, List 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;
}
diff --git a/src/Microsoft.CodeAnalysis.Razor/ViewComponentTypes.cs b/src/Microsoft.CodeAnalysis.Razor/ViewComponentTypes.cs
index f7f7a560b3..b48dad9c41 100644
--- a/src/Microsoft.CodeAnalysis.Razor/ViewComponentTypes.cs
+++ b/src/Microsoft.CodeAnalysis.Razor/ViewComponentTypes.cs
@@ -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";
diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/IntegrationTests/CodeGenerationIntegrationTest.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/IntegrationTests/CodeGenerationIntegrationTest.cs
index 8699149f6c..de2293f16b 100644
--- a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/IntegrationTests/CodeGenerationIntegrationTest.cs
+++ b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/IntegrationTests/CodeGenerationIntegrationTest.cs
@@ -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());
}
});
}
diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/ModelExpressionPassTest.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/ModelExpressionPassTest.cs
index e0e9673beb..319212236f 100644
--- a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/ModelExpressionPassTest.cs
+++ b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/ModelExpressionPassTest.cs
@@ -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 Resolve(IList errors)
- {
- return TagHelpers;
- }
- }
}
}
\ No newline at end of file
diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/ViewComponentTagHelperDescriptorConventionsTest.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/ViewComponentTagHelperDescriptorConventionsTest.cs
index f0bd076179..fa6de930b7 100644
--- a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/ViewComponentTagHelperDescriptorConventionsTest.cs
+++ b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/ViewComponentTagHelperDescriptorConventionsTest.cs
@@ -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
diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/ViewComponentTagHelperPassTest.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/ViewComponentTagHelperPassTest.cs
index 93dbbf5312..0964c8b381 100644
--- a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/ViewComponentTagHelperPassTest.cs
+++ b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/ViewComponentTagHelperPassTest.cs
@@ -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 Resolve(IList errors)
- {
- return TagHelpers;
- }
- }
}
}
diff --git a/test/Microsoft.AspNetCore.Razor.Language.Test/DefaultRazorTagHelperBinderPhaseTest.cs b/test/Microsoft.AspNetCore.Razor.Language.Test/DefaultRazorTagHelperBinderPhaseTest.cs
index 2969187cd8..557d390b0a 100644
--- a/test/Microsoft.AspNetCore.Razor.Language.Test/DefaultRazorTagHelperBinderPhaseTest.cs
+++ b/test/Microsoft.AspNetCore.Razor.Language.Test/DefaultRazorTagHelperBinderPhaseTest.cs
@@ -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());
});
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(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(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 Resolve(IList errors)
- {
- errors.Add(_error);
-
- return new[] { CreateTagHelperDescriptor(
- tagName: _tagName,
- typeName: null,
- assemblyName: "TestAssembly") };
- }
- }
}
}
diff --git a/test/Microsoft.AspNetCore.Razor.Language.Test/HtmlCaseTest.cs b/test/Microsoft.AspNetCore.Razor.Language.Test/HtmlCaseTest.cs
new file mode 100644
index 0000000000..fcba8dbdc1
--- /dev/null
+++ b/test/Microsoft.AspNetCore.Razor.Language.Test/HtmlCaseTest.cs
@@ -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
+ {
+ { "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);
+ }
+ }
+}
diff --git a/test/Microsoft.AspNetCore.Razor.Test.Common/Langauge/TestTagHelperDescriptorResolver.cs b/test/Microsoft.AspNetCore.Razor.Test.Common/Langauge/TestTagHelperDescriptorResolver.cs
deleted file mode 100644
index c868483ef7..0000000000
--- a/test/Microsoft.AspNetCore.Razor.Test.Common/Langauge/TestTagHelperDescriptorResolver.cs
+++ /dev/null
@@ -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 tagHelpers)
- {
- TagHelpers.AddRange(tagHelpers);
- }
-
- public List TagHelpers { get; } = new List();
-
- public IEnumerable Resolve(IList errors)
- {
- return TagHelpers;
- }
- }
-}
diff --git a/test/Microsoft.AspNetCore.Razor.Test.Common/Langauge/TestTagHelperFeature.cs b/test/Microsoft.AspNetCore.Razor.Test.Common/Langauge/TestTagHelperFeature.cs
index 682fd06d73..ad4039007e 100644
--- a/test/Microsoft.AspNetCore.Razor.Test.Common/Langauge/TestTagHelperFeature.cs
+++ b/test/Microsoft.AspNetCore.Razor.Test.Common/Langauge/TestTagHelperFeature.cs
@@ -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();
}
public TestTagHelperFeature(IEnumerable tagHelpers)
{
- Resolver = new TestTagHelperDescriptorResolver(tagHelpers);
+ TagHelpers = new List(tagHelpers);
}
- public List TagHelpers => ((TestTagHelperDescriptorResolver)Resolver).TagHelpers;
+ public List TagHelpers { get; }
- public ITagHelperDescriptorResolver Resolver { get; }
+ public IReadOnlyList GetDescriptors()
+ {
+ return TagHelpers.ToArray();
+ }
}
}
diff --git a/test/Microsoft.CodeAnalysis.Razor.Test/DefaultTagHelperDescriptorFactoryTest.cs b/test/Microsoft.CodeAnalysis.Razor.Test/DefaultTagHelperDescriptorFactoryTest.cs
index 5772d3c55b..afded7d9e9 100644
--- a/test/Microsoft.CodeAnalysis.Razor.Test/DefaultTagHelperDescriptorFactoryTest.cs
+++ b/test/Microsoft.CodeAnalysis.Razor.Test/DefaultTagHelperDescriptorFactoryTest.cs
@@ -1970,35 +1970,6 @@ namespace Microsoft.CodeAnalysis.Razor.Workspaces
Assert.Equal(expectedDiagnostics, descriptor.GetAllDiagnostics());
}
- public static TheoryData HtmlConversionData
- {
- get
- {
- return new TheoryData
- {
- { "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
diff --git a/test/Microsoft.CodeAnalysis.Razor.Test/TestCompilation.cs b/test/Microsoft.CodeAnalysis.Razor.Test/TestCompilation.cs
index 01887eb697..ab5869c6cb 100644
--- a/test/Microsoft.CodeAnalysis.Razor.Test/TestCompilation.cs
+++ b/test/Microsoft.CodeAnalysis.Razor.Test/TestCompilation.cs
@@ -14,6 +14,29 @@ namespace Microsoft.CodeAnalysis.Razor
{
public static class TestCompilation
{
+ private static IEnumerable _metadataReferences;
+
+ public static IEnumerable 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 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);
diff --git a/test/Microsoft.CodeAnalysis.Razor.Test/ViewComponentTagHelperDescriptorFactoryTest.cs b/test/Microsoft.CodeAnalysis.Razor.Test/ViewComponentTagHelperDescriptorFactoryTest.cs
index 35a39901a2..075a25e9bf 100644
--- a/test/Microsoft.CodeAnalysis.Razor.Test/ViewComponentTagHelperDescriptorFactoryTest.cs
+++ b/test/Microsoft.CodeAnalysis.Razor.Test/ViewComponentTagHelperDescriptorFactoryTest.cs
@@ -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 Foo, Dictionary Bar) => null;
}
+
+ public class ViewComponentWithoutInvokeMethod
+ {
+ }
+
+ public class AsyncViewComponentWithGenericTask
+ {
+ public Task 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 Invoke() => null;
+ }
}
\ No newline at end of file
diff --git a/test/Microsoft.CodeAnalysis.Razor.Test/ViewComponentTagHelperDescriptorProviderTest.cs b/test/Microsoft.CodeAnalysis.Razor.Test/ViewComponentTagHelperDescriptorProviderTest.cs
new file mode 100644
index 0000000000..9179783cc3
--- /dev/null
+++ b/test/Microsoft.CodeAnalysis.Razor.Test/ViewComponentTagHelperDescriptorProviderTest.cs
@@ -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);
+ }
+ }
+}