From 8f9ff1abd9b03838e967358de583fbae2c9fb693 Mon Sep 17 00:00:00 2001 From: "N. Taylor Mullen" Date: Tue, 7 Mar 2017 11:04:31 -0800 Subject: [PATCH] Clean up TagHelperDescriptor APIs. - Removed all design time descriptors and put their API surface into their corresponding descriptor. Part of removing the design time API surface was removing the tracking of ``, it wasn't used so there's on need to track it until we need it. - Removed the Type requirement from `TagHelperDescriptor`. With this separation we'll be able to have abstract `TagHelper`s that aren't based on a class implementation. - Removed Prefix from the `TagHelperDescriptor` API surface. It was a legacy requirement based on how the Razor parser was put together. We can work around this now. - Stripped correlation information from the immediate `TagHelperDescriptor` API surface. Instead this information is now tracked in `TagMatchingRule`s. This change means that you will not have multiple `TagHelperDescriptor`s per `TagHelper`; instead it's all tracked in a single descriptor. A side effect of this change was the transformation of `IsIndexer` => 3 new properties. - Renamed many descriptor types and property names. - Added builder APIs to construct TagHelpers since they're inherently immutable in their API surface. - Added `ITagHelperDescriptorBuilder` to represent `TagHelper`s that are built from an `ITagHelper` implementing class. It re-introduces the `TypeName` association of a `TagHelper`. - Added `ITagHelperBoundAttributeDescriptorBuilder` to represent that an attribute was associated with a property. - Added validation methods to the descriptor builders to enable consumers to validate the current state of the builder and add diagnostics as necessary. - Moved descriptors away from RazorError. - Updated the various comparers to understand the descriptors new API. - Added a new `RazorDiagnosticFactory` abstraction to handle `RazorDiagnostic`s and their corresponding errors/ids etc. This new API should allow for easy addition of new `RazorDiagnostic` errors. - Updated the `DefaultTagHelperDescriptorFactory` to construct `TagHelperDescriptor`s using the new builder APIs and in the new descriptor format (1 descriptor per type). - Updated `ViewComponentTagHelperDescriptorFactory` to construct `TagHelperDescriptor`s with the builder API. - With both factory implementations code was duplicated because the ViewComponent work will be moving outside of Razor once we have the proper hooks. - Updated `TagHelper` binding bits to capture a binding result in order to query which rules appy to a given tag name. Addressed feedback - Update tests to react to new `TagHelperDescriptor` API. - Remove case sensitive comparers and some cleanup - Added TagHelperDescriptorJsonConverter, RazorDiagnosticJsonConverter and added serialization tests --- ...=> CaseSensitiveBoundAttributeComparer.cs} | 0 ...sitiveBoundAttributeDescriptorComparer.cs} | 0 ...iveRequiredAttributeDescriptorComparer.cs} | 0 .../BoundAttributeDescriptor.cs | 53 + .../BoundAttributeDescriptorComparer.cs | 90 + .../DesignTimeCSharpRenderer.cs | 4 +- .../PageStructureCSharpRenderer.cs | 9 +- .../CodeGeneration/RuntimeCSharpRenderer.cs | 20 +- .../DefaultRazorIRLoweringPhase.cs | 48 +- ...agHelperBoundAttributeDescriptorBuilder.cs | 281 ++ .../ITagHelperDescriptorBuilder.cs | 263 ++ .../Intermediate/CreateTagHelperIRNode.cs | 1 - .../SetPreallocatedTagHelperPropertyIRNode.cs | 7 +- .../SetTagHelperPropertyIRNode.cs | 6 +- .../RequiredAttributeDescriptorComparer.cs | 81 + .../Legacy/TagHelperBinding.cs | 25 + .../Legacy/TagHelperBlock.cs | 6 +- .../Legacy/TagHelperBlockBuilder.cs | 15 +- .../Legacy/TagHelperBlockRewriter.cs | 61 +- .../Legacy/TagHelperChunkGenerator.cs | 15 - .../Legacy/TagHelperDescriptorComparer.cs | 139 +- .../Legacy/TagHelperDescriptorProvider.cs | 110 +- .../Legacy/TagHelperParseTreeRewriter.cs | 118 +- ...lperRequiredAttributeDescriptorComparer.cs | 58 - .../TypeBasedTagHelperDescriptorComparer.cs | 64 - .../Properties/AssemblyInfo.cs | 1 + .../Properties/LegacyResources.Designer.cs | 510 +-- .../Properties/Resources.Designer.cs | 262 +- .../RazorCodeDocumentExtensions.cs | 22 + .../RazorDiagnosticDescriptor.cs | 2 +- .../RazorDiagnosticFactory.cs | 268 ++ ...catedTagHelperAttributeOptimizationPass.cs | 6 +- .../RequiredAttributeDescriptor.cs | 75 + .../RequiredAttributeDescriptorBuilder.cs | 141 + .../Resources.resx | 39 + .../TagHelperAttributeDescriptor.cs | 156 - .../TagHelperAttributeDesignTimeDescriptor.cs | 21 - .../TagHelperBinderSyntaxTreePass.cs | 83 +- .../TagHelperDescriptor.cs | 257 +- .../TagHelperDescriptorMatchingConventions.cs | 67 + .../TagHelperDesignTimeDescriptor.cs | 29 - .../TagHelperRequiredAttributeDescriptor.cs | 82 - ...agHelperRequiredAttributeNameComparison.cs | 21 - ...gHelperRequiredAttributeValueComparison.cs | 31 - .../TagMatchingRule.cs | 46 + .../TagMatchingRuleBuilder.cs | 194 ++ .../TagMatchingRuleComparer.cs | 86 + .../TagHelpers/TagHelperDescriptorComparer.cs | 7 +- .../TagHelpers/TagHelperBlockBuilder.cs | 2 +- .../DefaultTagHelperResolver.cs | 15 +- .../DefaultTagHelperDescriptorFactory.cs | 1203 ++----- .../Properties/Resources.Designer.cs | 308 +- .../ViewComponentResources.Designer.cs | 30 +- .../RazorDiagnosticFactory.cs | 185 ++ .../RequiredAttributeParser.cs | 304 ++ .../Resources.resx | 49 +- .../TagHelpers.cs | 12 +- ...ViewComponentTagHelperDescriptorFactory.cs | 97 +- .../XmlMemberDocumentation.cs | 80 - .../DefaultTagHelperResolver.cs | 112 +- ...VisualStudio.LanguageServices.Razor.csproj | 1 + .../Properties/Resources.Designer.cs | 20 +- .../RazorDiagnosticJsonConverter.cs | 83 + .../Resources.resx | 3 + .../TagHelperDescriptorJsonConverter.cs | 189 ++ .../DefaultDirectiveIRPassTest.cs | 4 +- ...aultRazorIRLoweringPhaseIntegrationTest.cs | 80 +- .../CodeGenerationIntegrationTest.cs | 30 +- .../InstrumentationPassIntegrationTest.cs | 81 +- .../TagHelpersIntegrationTest.cs | 97 +- .../TestTagHelperDescriptors.cs | 875 +++--- ...aseSensitiveTagHelperDescriptorComparer.cs | 95 - ...lperRequiredAttributeDescriptorComparer.cs | 42 - .../Legacy/RazorEditorParserTest.cs | 40 +- .../TagHelperAttributeDescriptorComparer.cs | 56 - ...erAttributeDesignTimeDescriptorComparer.cs | 47 - .../Legacy/TagHelperBlockRewriterTest.cs | 411 +-- .../Legacy/TagHelperDescriptorProviderTest.cs | 433 +-- .../TagHelperDesignTimeDescriptorComparer.cs | 47 - .../Legacy/TagHelperParseTreeRewriterTest.cs | 777 ++--- .../Legacy/TagHelperRewritingTestBase.cs | 22 +- .../TagHelperBinderSyntaxTreePassTest.cs | 391 ++- .../TagHelperDescriptorTest.cs | 557 ---- ...agHelperRequiredAttributeDescriptorTest.cs | 137 +- ...aseSensitiveTagHelperDescriptorComparer.cs | 97 - ...lperRequiredAttributeDescriptorComparer.cs | 44 - .../TagHelperAttributeDescriptorComparer.cs | 57 - ...erAttributeDesignTimeDescriptorComparer.cs | 48 - .../TagHelperDesignTimeDescriptorComparer.cs | 48 - .../DefaultTagHelperDescriptorFactoryTest.cs | 2783 +++++++---------- .../TagHelperDescriptorFactoryTagHelpers.cs | 2 +- .../TagHelperTypeVisitorTest.cs | 5 +- .../TestCompilation.cs | 5 +- ...ComponentTagHelperDescriptorFactoryTest.cs | 197 +- .../TagHelperDescriptorSerializationTest.cs | 188 ++ .../RazorInfo/TagHelperViewModel.cs | 5 +- 96 files changed, 6595 insertions(+), 7679 deletions(-) rename shared/Microsoft.AspNetCore.Razor.TagHelpers.Testing.Sources/{CaseSensitiveTagHelperAttributeComparer.cs => CaseSensitiveBoundAttributeComparer.cs} (100%) rename shared/Microsoft.AspNetCore.Razor.TagHelpers.Testing.Sources/{TagHelperAttributeDescriptorComparer.cs => CaseSensitiveBoundAttributeDescriptorComparer.cs} (100%) rename shared/Microsoft.AspNetCore.Razor.TagHelpers.Testing.Sources/{CaseSensitiveTagHelperRequiredAttributeDescriptorComparer.cs => CaseSensitiveRequiredAttributeDescriptorComparer.cs} (100%) create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/BoundAttributeDescriptor.cs create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/BoundAttributeDescriptorComparer.cs create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/ITagHelperBoundAttributeDescriptorBuilder.cs create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/ITagHelperDescriptorBuilder.cs create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/Legacy/RequiredAttributeDescriptorComparer.cs create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/Legacy/TagHelperBinding.cs delete mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/Legacy/TagHelperRequiredAttributeDescriptorComparer.cs delete mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/Legacy/TypeBasedTagHelperDescriptorComparer.cs create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/RazorDiagnosticFactory.cs create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/RequiredAttributeDescriptor.cs create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/RequiredAttributeDescriptorBuilder.cs delete mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/TagHelperAttributeDescriptor.cs delete mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/TagHelperAttributeDesignTimeDescriptor.cs create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/TagHelperDescriptorMatchingConventions.cs delete mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/TagHelperDesignTimeDescriptor.cs delete mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/TagHelperRequiredAttributeDescriptor.cs delete mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/TagHelperRequiredAttributeNameComparison.cs delete mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/TagHelperRequiredAttributeValueComparison.cs create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/TagMatchingRule.cs create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/TagMatchingRuleBuilder.cs create mode 100644 src/Microsoft.AspNetCore.Razor.Evolution/TagMatchingRuleComparer.cs create mode 100644 src/Microsoft.CodeAnalysis.Razor/RazorDiagnosticFactory.cs create mode 100644 src/Microsoft.CodeAnalysis.Razor/RequiredAttributeParser.cs delete mode 100644 src/Microsoft.CodeAnalysis.Razor/XmlMemberDocumentation.cs create mode 100644 src/Microsoft.VisualStudio.LanguageServices.Razor/RazorDiagnosticJsonConverter.cs create mode 100644 src/Microsoft.VisualStudio.LanguageServices.Razor/TagHelperDescriptorJsonConverter.cs delete mode 100644 test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/CaseSensitiveTagHelperDescriptorComparer.cs delete mode 100644 test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/CaseSensitiveTagHelperRequiredAttributeDescriptorComparer.cs delete mode 100644 test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/TagHelperAttributeDescriptorComparer.cs delete mode 100644 test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/TagHelperAttributeDesignTimeDescriptorComparer.cs delete mode 100644 test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/TagHelperDesignTimeDescriptorComparer.cs delete mode 100644 test/Microsoft.AspNetCore.Razor.Evolution.Test/TagHelperDescriptorTest.cs delete mode 100644 test/Microsoft.CodeAnalysis.Razor.Test/Comparers/CaseSensitiveTagHelperDescriptorComparer.cs delete mode 100644 test/Microsoft.CodeAnalysis.Razor.Test/Comparers/CaseSensitiveTagHelperRequiredAttributeDescriptorComparer.cs delete mode 100644 test/Microsoft.CodeAnalysis.Razor.Test/Comparers/TagHelperAttributeDescriptorComparer.cs delete mode 100644 test/Microsoft.CodeAnalysis.Razor.Test/Comparers/TagHelperAttributeDesignTimeDescriptorComparer.cs delete mode 100644 test/Microsoft.CodeAnalysis.Razor.Test/Comparers/TagHelperDesignTimeDescriptorComparer.cs create mode 100644 test/Microsoft.VisualStudio.LanguageServices.Razor.Test/TagHelperDescriptorSerializationTest.cs diff --git a/shared/Microsoft.AspNetCore.Razor.TagHelpers.Testing.Sources/CaseSensitiveTagHelperAttributeComparer.cs b/shared/Microsoft.AspNetCore.Razor.TagHelpers.Testing.Sources/CaseSensitiveBoundAttributeComparer.cs similarity index 100% rename from shared/Microsoft.AspNetCore.Razor.TagHelpers.Testing.Sources/CaseSensitiveTagHelperAttributeComparer.cs rename to shared/Microsoft.AspNetCore.Razor.TagHelpers.Testing.Sources/CaseSensitiveBoundAttributeComparer.cs diff --git a/shared/Microsoft.AspNetCore.Razor.TagHelpers.Testing.Sources/TagHelperAttributeDescriptorComparer.cs b/shared/Microsoft.AspNetCore.Razor.TagHelpers.Testing.Sources/CaseSensitiveBoundAttributeDescriptorComparer.cs similarity index 100% rename from shared/Microsoft.AspNetCore.Razor.TagHelpers.Testing.Sources/TagHelperAttributeDescriptorComparer.cs rename to shared/Microsoft.AspNetCore.Razor.TagHelpers.Testing.Sources/CaseSensitiveBoundAttributeDescriptorComparer.cs diff --git a/shared/Microsoft.AspNetCore.Razor.TagHelpers.Testing.Sources/CaseSensitiveTagHelperRequiredAttributeDescriptorComparer.cs b/shared/Microsoft.AspNetCore.Razor.TagHelpers.Testing.Sources/CaseSensitiveRequiredAttributeDescriptorComparer.cs similarity index 100% rename from shared/Microsoft.AspNetCore.Razor.TagHelpers.Testing.Sources/CaseSensitiveTagHelperRequiredAttributeDescriptorComparer.cs rename to shared/Microsoft.AspNetCore.Razor.TagHelpers.Testing.Sources/CaseSensitiveRequiredAttributeDescriptorComparer.cs diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/BoundAttributeDescriptor.cs b/src/Microsoft.AspNetCore.Razor.Evolution/BoundAttributeDescriptor.cs new file mode 100644 index 0000000000..768a4eddf2 --- /dev/null +++ b/src/Microsoft.AspNetCore.Razor.Evolution/BoundAttributeDescriptor.cs @@ -0,0 +1,53 @@ +// 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; + +namespace Microsoft.AspNetCore.Razor.Evolution +{ + /// + /// A metadata class describing a tag helper attribute. + /// + public abstract class BoundAttributeDescriptor + { + protected BoundAttributeDescriptor(string kind) + { + Kind = kind; + } + + public string Kind { get; } + + public bool IsIndexerStringProperty { get; protected set; } + + public bool IsEnum { get; protected set; } + + public bool IsStringProperty { get; protected set; } + + public string Name { get; protected set; } + + public string IndexerNamePrefix { get; set; } + + public string TypeName { get; protected set; } + + public string IndexerTypeName { get; protected set; } + + public string Documentation { get; protected set; } + + public string DisplayName { get; protected set; } + + public IReadOnlyList Diagnostics { get; protected set; } + + public IReadOnlyDictionary Metadata { get; protected set; } + + public bool HasAnyErrors + { + get + { + var anyErrors = Diagnostics.Any(diagnostic => diagnostic.Severity == RazorDiagnosticSeverity.Error); + + return anyErrors; + } + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/BoundAttributeDescriptorComparer.cs b/src/Microsoft.AspNetCore.Razor.Evolution/BoundAttributeDescriptorComparer.cs new file mode 100644 index 0000000000..43ef4abb66 --- /dev/null +++ b/src/Microsoft.AspNetCore.Razor.Evolution/BoundAttributeDescriptorComparer.cs @@ -0,0 +1,90 @@ +// 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 Microsoft.Extensions.Internal; + +namespace Microsoft.AspNetCore.Razor.Evolution +{ + internal class BoundAttributeDescriptorComparer : IEqualityComparer + { + /// + /// A default instance of the . + /// + public static readonly BoundAttributeDescriptorComparer Default = new BoundAttributeDescriptorComparer(); + + /// + /// A default instance of the that does case-sensitive comparison. + /// + internal static readonly BoundAttributeDescriptorComparer CaseSensitive = + new BoundAttributeDescriptorComparer(caseSensitive: true); + + private readonly StringComparer _stringComparer; + private readonly StringComparison _stringComparison; + + private BoundAttributeDescriptorComparer(bool caseSensitive = false) + { + if (caseSensitive) + { + _stringComparer = StringComparer.Ordinal; + _stringComparison = StringComparison.Ordinal; + } + else + { + _stringComparer = StringComparer.OrdinalIgnoreCase; + _stringComparison = StringComparison.OrdinalIgnoreCase; + } + } + + public virtual bool Equals(BoundAttributeDescriptor descriptorX, BoundAttributeDescriptor descriptorY) + { + if (object.ReferenceEquals(descriptorX, descriptorY)) + { + return true; + } + + if (descriptorX == null ^ descriptorY == null) + { + return false; + } + + return descriptorX != null && + string.Equals(descriptorX.Kind, descriptorY.Kind, StringComparison.Ordinal) && + descriptorX.IsIndexerStringProperty == descriptorY.IsIndexerStringProperty && + descriptorX.IsEnum == descriptorY.IsEnum && + string.Equals(descriptorX.Name, descriptorY.Name, _stringComparison) && + string.Equals(descriptorX.IndexerNamePrefix, descriptorY.IndexerNamePrefix, _stringComparison) && + string.Equals(descriptorX.TypeName, descriptorY.TypeName, StringComparison.Ordinal) && + string.Equals(descriptorX.IndexerTypeName, descriptorY.IndexerTypeName, StringComparison.Ordinal) && + string.Equals(descriptorX.Documentation, descriptorY.Documentation, StringComparison.Ordinal) && + string.Equals(descriptorX.DisplayName, descriptorY.DisplayName, StringComparison.Ordinal) && + Enumerable.SequenceEqual(descriptorX.Diagnostics, descriptorY.Diagnostics) && + Enumerable.SequenceEqual( + descriptorX.Metadata.OrderBy(propertyX => propertyX.Key, StringComparer.Ordinal), + descriptorY.Metadata.OrderBy(propertyY => propertyY.Key, StringComparer.Ordinal)); + } + + public virtual int GetHashCode(BoundAttributeDescriptor descriptor) + { + if (descriptor == null) + { + throw new ArgumentNullException(nameof(descriptor)); + } + + var hashCodeCombiner = HashCodeCombiner.Start(); + hashCodeCombiner.Add(descriptor.Kind); + hashCodeCombiner.Add(descriptor.IsIndexerStringProperty); + hashCodeCombiner.Add(descriptor.IsEnum); + hashCodeCombiner.Add(descriptor.Name, _stringComparer); + hashCodeCombiner.Add(descriptor.IndexerNamePrefix, _stringComparer); + hashCodeCombiner.Add(descriptor.TypeName, StringComparer.Ordinal); + hashCodeCombiner.Add(descriptor.IndexerTypeName, StringComparer.Ordinal); + hashCodeCombiner.Add(descriptor.Documentation, StringComparer.Ordinal); + hashCodeCombiner.Add(descriptor.DisplayName, StringComparer.Ordinal); + + return hashCodeCombiner.CombinedHash; + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/DesignTimeCSharpRenderer.cs b/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/DesignTimeCSharpRenderer.cs index 98933e212a..33ecbfc972 100644 --- a/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/DesignTimeCSharpRenderer.cs +++ b/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/DesignTimeCSharpRenderer.cs @@ -191,7 +191,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution.CodeGeneration { var tagHelperVariableName = GetTagHelperVariableName(node.TagHelperTypeName); var tagHelperRenderingContext = Context.TagHelperRenderingContext; - var propertyValueAccessor = GetTagHelperPropertyAccessor(tagHelperVariableName, node.AttributeName, node.Descriptor); + var propertyValueAccessor = GetTagHelperPropertyAccessor(node.IsIndexerNameMatch, tagHelperVariableName, node.AttributeName, node.Descriptor); string previousValueAccessor; if (tagHelperRenderingContext.RenderedBoundAttributes.TryGetValue(node.AttributeName, out previousValueAccessor)) @@ -208,7 +208,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution.CodeGeneration tagHelperRenderingContext.RenderedBoundAttributes[node.AttributeName] = propertyValueAccessor; } - if (node.Descriptor.IsStringProperty) + if (node.Descriptor.IsStringProperty || (node.IsIndexerNameMatch && node.Descriptor.IsIndexerStringProperty)) { VisitDefault(node); diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/PageStructureCSharpRenderer.cs b/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/PageStructureCSharpRenderer.cs index 756bcf2269..05d1572ba1 100644 --- a/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/PageStructureCSharpRenderer.cs +++ b/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/PageStructureCSharpRenderer.cs @@ -83,15 +83,16 @@ namespace Microsoft.AspNetCore.Razor.Evolution.CodeGeneration protected static string GetTagHelperVariableName(string tagHelperTypeName) => "__" + tagHelperTypeName.Replace('.', '_'); protected static string GetTagHelperPropertyAccessor( + bool isIndexerNameMatch, string tagHelperVariableName, string attributeName, - TagHelperAttributeDescriptor descriptor) + BoundAttributeDescriptor descriptor) { - var propertyAccessor = $"{tagHelperVariableName}.{descriptor.PropertyName}"; + var propertyAccessor = $"{tagHelperVariableName}.{descriptor.Metadata[ITagHelperBoundAttributeDescriptorBuilder.PropertyNameKey]}"; - if (descriptor.IsIndexer) + if (isIndexerNameMatch) { - var dictionaryKey = attributeName.Substring(descriptor.Name.Length); + var dictionaryKey = attributeName.Substring(descriptor.IndexerNamePrefix.Length); propertyAccessor += $"[\"{dictionaryKey}\"]"; } diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/RuntimeCSharpRenderer.cs b/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/RuntimeCSharpRenderer.cs index c904c64841..1d7e494a9b 100644 --- a/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/RuntimeCSharpRenderer.cs +++ b/src/Microsoft.AspNetCore.Razor.Evolution/CodeGeneration/RuntimeCSharpRenderer.cs @@ -373,7 +373,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution.CodeGeneration public override void VisitSetPreallocatedTagHelperProperty(SetPreallocatedTagHelperPropertyIRNode node) { var tagHelperVariableName = GetTagHelperVariableName(node.TagHelperTypeName); - var propertyValueAccessor = GetTagHelperPropertyAccessor(tagHelperVariableName, node.AttributeName, node.Descriptor); + var propertyValueAccessor = GetTagHelperPropertyAccessor(node.IsIndexerNameMatch, tagHelperVariableName, node.AttributeName, node.Descriptor); var attributeValueAccessor = $"{node.VariableName}.Value" /* ORIGINAL: TagHelperAttributeValuePropertyName */; Context.Writer .WriteStartAssignment(propertyValueAccessor) @@ -391,17 +391,18 @@ namespace Microsoft.AspNetCore.Razor.Evolution.CodeGeneration { var tagHelperVariableName = GetTagHelperVariableName(node.TagHelperTypeName); var tagHelperRenderingContext = Context.TagHelperRenderingContext; + var propertyName = node.Descriptor.Metadata[ITagHelperBoundAttributeDescriptorBuilder.PropertyNameKey]; // Ensure that the property we're trying to set has initialized its dictionary bound properties. - if (node.Descriptor.IsIndexer && - tagHelperRenderingContext.VerifiedPropertyDictionaries.Add(node.Descriptor.PropertyName)) + if (node.IsIndexerNameMatch && + tagHelperRenderingContext.VerifiedPropertyDictionaries.Add(propertyName)) { // Throw a reasonable Exception at runtime if the dictionary property is null. Context.Writer .Write("if (") .Write(tagHelperVariableName) .Write(".") - .Write(node.Descriptor.PropertyName) + .Write(propertyName) .WriteLine(" == null)"); using (Context.Writer.BuildScope()) { @@ -415,13 +416,13 @@ namespace Microsoft.AspNetCore.Razor.Evolution.CodeGeneration .WriteParameterSeparator() .WriteStringLiteral(node.TagHelperTypeName) .WriteParameterSeparator() - .WriteStringLiteral(node.Descriptor.PropertyName) + .WriteStringLiteral(propertyName) .WriteEndMethodInvocation(endLine: false) // End of method call .WriteEndMethodInvocation(); // End of new expression / throw statement } } - var propertyValueAccessor = GetTagHelperPropertyAccessor(tagHelperVariableName, node.AttributeName, node.Descriptor); + var propertyValueAccessor = GetTagHelperPropertyAccessor(node.IsIndexerNameMatch, tagHelperVariableName, node.AttributeName, node.Descriptor); string previousValueAccessor; if (tagHelperRenderingContext.RenderedBoundAttributes.TryGetValue(node.AttributeName, out previousValueAccessor)) @@ -438,7 +439,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution.CodeGeneration tagHelperRenderingContext.RenderedBoundAttributes[node.AttributeName] = propertyValueAccessor; } - if (node.Descriptor.IsStringProperty) + if (node.Descriptor.IsStringProperty || (node.IsIndexerNameMatch && node.Descriptor.IsIndexerStringProperty)) { Context.Writer.WriteMethodInvocation("BeginWriteTagHelperAttribute" /* ORIGINAL: BeginWriteTagHelperAttributeMethodName */); @@ -683,8 +684,11 @@ namespace Microsoft.AspNetCore.Razor.Evolution.CodeGeneration else if (node is TemplateIRNode) { var attributeValueNode = (SetTagHelperPropertyIRNode)node.Parent; + var expectedTypeName = attributeValueNode.IsIndexerNameMatch ? + attributeValueNode.Descriptor.IndexerTypeName : + attributeValueNode.Descriptor.TypeName; var error = new RazorError( - LegacyResources.FormatTagHelpers_InlineMarkupBlocks_NotSupported_InAttributes(attributeValueNode.Descriptor.TypeName), + LegacyResources.FormatTagHelpers_InlineMarkupBlocks_NotSupported_InAttributes(expectedTypeName), new SourceLocation(documentLocation.AbsoluteIndex, documentLocation.CharacterIndex, documentLocation.Length), documentLocation.Length); Context.Diagnostics.Add(RazorDiagnostic.Create(error)); diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/DefaultRazorIRLoweringPhase.cs b/src/Microsoft.AspNetCore.Razor.Evolution/DefaultRazorIRLoweringPhase.cs index a1feb35873..f34d90d590 100644 --- a/src/Microsoft.AspNetCore.Razor.Evolution/DefaultRazorIRLoweringPhase.cs +++ b/src/Microsoft.AspNetCore.Razor.Evolution/DefaultRazorIRLoweringPhase.cs @@ -18,8 +18,8 @@ namespace Microsoft.AspNetCore.Razor.Evolution ThrowForMissingDependency(syntaxTree); var builder = RazorIRBuilder.Document(); - var document = (DocumentIRNode)builder.Current; + document.Options = syntaxTree.Options; var namespaces = new HashSet(); @@ -56,7 +56,8 @@ namespace Microsoft.AspNetCore.Razor.Evolution } } - var visitor = new MainSourceVisitor(document, builder, namespaces) + var tagHelperPrefix = codeDocument.GetTagHelperPrefix(); + var visitor = new MainSourceVisitor(document, builder, namespaces, tagHelperPrefix) { FileName = syntaxTree.Source.FileName, }; @@ -220,10 +221,12 @@ namespace Microsoft.AspNetCore.Razor.Evolution private class MainSourceVisitor : LoweringVisitor { private DeclareTagHelperFieldsIRNode _tagHelperFields; + private readonly string _tagHelperPrefix; - public MainSourceVisitor(DocumentIRNode document, RazorIRBuilder builder, HashSet namespaces) + public MainSourceVisitor(DocumentIRNode document, RazorIRBuilder builder, HashSet namespaces, string tagHelperPrefix) : base(document, builder, namespaces) { + _tagHelperPrefix = tagHelperPrefix; } public override void VisitDirectiveToken(DirectiveTokenChunkGenerator chunkGenerator, Span span) @@ -467,9 +470,9 @@ namespace Microsoft.AspNetCore.Razor.Evolution }); var tagName = tagHelperBlock.TagName; - if (tagHelperBlock.Descriptors.First().Prefix != null) + if (_tagHelperPrefix != null) { - tagName = tagName.Substring(tagHelperBlock.Descriptors.First().Prefix.Length); + tagName = tagName.Substring(_tagHelperPrefix.Length); } _builder.Push(new InitializeTagHelperStructureIRNode() @@ -482,8 +485,8 @@ namespace Microsoft.AspNetCore.Razor.Evolution _builder.Pop(); // Pop InitializeTagHelperStructureIRNode - AddTagHelperCreation(tagHelperBlock.Descriptors); - AddTagHelperAttributes(tagHelperBlock.Attributes, tagHelperBlock.Descriptors); + AddTagHelperCreation(tagHelperBlock.Binding); + AddTagHelperAttributes(tagHelperBlock.Attributes, tagHelperBlock.Binding); AddExecuteTagHelpers(); _builder.Pop(); // Pop TagHelperIRNode @@ -497,19 +500,22 @@ namespace Microsoft.AspNetCore.Razor.Evolution _document.Children.Add(_tagHelperFields); } - foreach (var descriptor in block.Descriptors) + foreach (var descriptor in block.Binding.Descriptors) { - _tagHelperFields.UsedTagHelperTypeNames.Add(descriptor.TypeName); + var typeName = descriptor.Metadata[ITagHelperDescriptorBuilder.TypeNameKey]; + _tagHelperFields.UsedTagHelperTypeNames.Add(typeName); } } - private void AddTagHelperCreation(IEnumerable descriptors) + private void AddTagHelperCreation(TagHelperBinding tagHelperBinding) { + var descriptors = tagHelperBinding.Descriptors; foreach (var descriptor in descriptors) { + var typeName = descriptor.Metadata[ITagHelperDescriptorBuilder.TypeNameKey]; var createTagHelper = new CreateTagHelperIRNode() { - TagHelperTypeName = descriptor.TypeName, + TagHelperTypeName = typeName, Descriptor = descriptor }; @@ -517,14 +523,15 @@ namespace Microsoft.AspNetCore.Razor.Evolution } } - private void AddTagHelperAttributes(IList attributes, IEnumerable descriptors) + private void AddTagHelperAttributes(IList attributes, TagHelperBinding tagHelperBinding) { + var descriptors = tagHelperBinding.Descriptors; var renderedBoundAttributeNames = new HashSet(StringComparer.OrdinalIgnoreCase); foreach (var attribute in attributes) { var attributeValueNode = attribute.Value; var associatedDescriptors = descriptors.Where(descriptor => - descriptor.Attributes.Any(attributeDescriptor => attributeDescriptor.IsNameMatch(attribute.Name))); + descriptor.BoundAttributes.Any(attributeDescriptor => attributeDescriptor.CanMatchName(attribute.Name))); if (associatedDescriptors.Any() && renderedBoundAttributeNames.Add(attribute.Name)) { @@ -537,16 +544,21 @@ namespace Microsoft.AspNetCore.Razor.Evolution foreach (var associatedDescriptor in associatedDescriptors) { - var associatedAttributeDescriptor = associatedDescriptor.Attributes.First( - attributeDescriptor => attributeDescriptor.IsNameMatch(attribute.Name)); + var associatedAttributeDescriptor = associatedDescriptor.BoundAttributes.First( + attributeDescriptor => attributeDescriptor.CanMatchName(attribute.Name)); + var tagHelperTypeName = associatedDescriptor.Metadata[ITagHelperDescriptorBuilder.TypeNameKey]; + var attributePropertyName = associatedAttributeDescriptor.Metadata[ITagHelperBoundAttributeDescriptorBuilder.PropertyNameKey]; + var setTagHelperProperty = new SetTagHelperPropertyIRNode() { - PropertyName = associatedAttributeDescriptor.PropertyName, + PropertyName = attributePropertyName, AttributeName = attribute.Name, - TagHelperTypeName = associatedDescriptor.TypeName, + TagHelperTypeName = tagHelperTypeName, Descriptor = associatedAttributeDescriptor, + Binding = tagHelperBinding, ValueStyle = attribute.ValueStyle, - Source = BuildSourceSpanFromNode(attributeValueNode) + Source = BuildSourceSpanFromNode(attributeValueNode), + IsIndexerNameMatch = associatedAttributeDescriptor.IsIndexerNameMatch(attribute.Name), }; _builder.Push(setTagHelperProperty); diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/ITagHelperBoundAttributeDescriptorBuilder.cs b/src/Microsoft.AspNetCore.Razor.Evolution/ITagHelperBoundAttributeDescriptorBuilder.cs new file mode 100644 index 0000000000..2c3c4efc04 --- /dev/null +++ b/src/Microsoft.AspNetCore.Razor.Evolution/ITagHelperBoundAttributeDescriptorBuilder.cs @@ -0,0 +1,281 @@ +// 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; + +namespace Microsoft.AspNetCore.Razor.Evolution +{ + public sealed class ITagHelperBoundAttributeDescriptorBuilder + { + public static readonly string DescriptorKind = "ITagHelper"; + public static readonly string PropertyNameKey = "ITagHelper.PropertyName"; + + private static readonly IReadOnlyDictionary PrimitiveDisplayTypeNameLookups = new Dictionary(StringComparer.Ordinal) + { + [typeof(byte).FullName] = "byte", + [typeof(sbyte).FullName] = "sbyte", + [typeof(int).FullName] = "int", + [typeof(uint).FullName] = "uint", + [typeof(short).FullName] = "short", + [typeof(ushort).FullName] = "ushort", + [typeof(long).FullName] = "long", + [typeof(ulong).FullName] = "ulong", + [typeof(float).FullName] = "float", + [typeof(double).FullName] = "double", + [typeof(char).FullName] = "char", + [typeof(bool).FullName] = "bool", + [typeof(object).FullName] = "object", + [typeof(string).FullName] = "string", + [typeof(decimal).FullName] = "decimal", + }; + + private static ICollection InvalidNonWhitespaceAttributeNameCharacters { get; } = new HashSet( + new[] { '@', '!', '<', '/', '?', '[', '>', ']', '=', '"', '\'', '*' }); + + private bool _isEnum; + private string _indexerValueTypeName; + private string _name; + private string _propertyName; + private string _typeName; + private string _documentation; + private string _indexerNamePrefix; + private readonly string _containingTypeName; + private readonly Dictionary _metadata; + private HashSet _diagnostics; + + private ITagHelperBoundAttributeDescriptorBuilder(string containingTypeName) + { + _containingTypeName = containingTypeName; + _metadata = new Dictionary(); + } + + public static ITagHelperBoundAttributeDescriptorBuilder Create(string containingTypeName) + { + return new ITagHelperBoundAttributeDescriptorBuilder(containingTypeName); + } + + public ITagHelperBoundAttributeDescriptorBuilder Name(string name) + { + _name = name; + + return this; + } + + public ITagHelperBoundAttributeDescriptorBuilder PropertyName(string propertyName) + { + _propertyName = propertyName; + + return this; + } + + public ITagHelperBoundAttributeDescriptorBuilder TypeName(string typeName) + { + _typeName = typeName; + + return this; + } + + public ITagHelperBoundAttributeDescriptorBuilder AsEnum() + { + _isEnum = true; + + return this; + } + + public ITagHelperBoundAttributeDescriptorBuilder AsDictionary(string attributeNamePrefix, string valueTypeName) + { + _indexerNamePrefix = attributeNamePrefix; + _indexerValueTypeName = valueTypeName; + + return this; + } + + public ITagHelperBoundAttributeDescriptorBuilder Documentation(string documentation) + { + _documentation = documentation; + + return this; + } + + public ITagHelperBoundAttributeDescriptorBuilder AddMetadata(string key, string value) + { + _metadata[key] = value; + + return this; + } + + public ITagHelperBoundAttributeDescriptorBuilder AddDiagnostic(RazorDiagnostic diagnostic) + { + EnsureDiagnostics(); + _diagnostics.Add(diagnostic); + + return this; + } + + public BoundAttributeDescriptor Build() + { + var validationDiagnostics = Validate(); + var diagnostics = new HashSet(validationDiagnostics); + if (_diagnostics != null) + { + diagnostics.UnionWith(_diagnostics); + } + + if (!PrimitiveDisplayTypeNameLookups.TryGetValue(_typeName, out var simpleName)) + { + simpleName = _typeName; + } + + var displayName = $"{simpleName} {_containingTypeName}.{_propertyName}"; + var descriptor = new ITagHelperBoundAttributeDescriptor( + _isEnum, + _name, + _propertyName, + _typeName, + _indexerNamePrefix, + _indexerValueTypeName, + _documentation, + displayName, + _metadata, + diagnostics); + + return descriptor; + } + + public void Reset() + { + _name = null; + _propertyName = null; + _typeName = null; + _documentation = null; + _isEnum = false; + _indexerNamePrefix = null; + _indexerValueTypeName = null; + _metadata.Clear(); + _diagnostics?.Clear(); + } + + private IEnumerable Validate() + { + // data-* attributes are explicitly not implemented by user agents and are not intended for use on + // the server; therefore it's invalid for TagHelpers to bind to them. + const string DataDashPrefix = "data-"; + + if (string.IsNullOrWhiteSpace(_name)) + { + if (_indexerNamePrefix == null) + { + var diagnostic = RazorDiagnosticFactory.CreateTagHelper_InvalidBoundAttributeNullOrWhitespace( + _containingTypeName, + _propertyName); + + yield return diagnostic; + } + } + else + { + if (_name.StartsWith(DataDashPrefix, StringComparison.OrdinalIgnoreCase)) + { + var diagnostic = RazorDiagnosticFactory.CreateTagHelper_InvalidBoundAttributeNameStartsWith( + _containingTypeName, + _propertyName, + _name); + + yield return diagnostic; + } + + foreach (var character in _name) + { + if (char.IsWhiteSpace(character) || InvalidNonWhitespaceAttributeNameCharacters.Contains(character)) + { + var diagnostic = RazorDiagnosticFactory.CreateTagHelper_InvalidBoundAttributeName( + _containingTypeName, + _propertyName, + _name, + character); + + yield return diagnostic; + } + } + } + + if (_indexerNamePrefix != null) + { + if (_indexerNamePrefix.StartsWith(DataDashPrefix, StringComparison.OrdinalIgnoreCase)) + { + var diagnostic = RazorDiagnosticFactory.CreateTagHelper_InvalidBoundAttributePrefixStartsWith( + _containingTypeName, + _propertyName, + _indexerNamePrefix); + + yield return diagnostic; + } + else if (_indexerNamePrefix.Length > 0 && string.IsNullOrWhiteSpace(_indexerNamePrefix)) + { + var diagnostic = RazorDiagnosticFactory.CreateTagHelper_InvalidBoundAttributeNullOrWhitespace( + _containingTypeName, + _propertyName); + + yield return diagnostic; + } + else + { + foreach (var character in _indexerNamePrefix) + { + if (char.IsWhiteSpace(character) || InvalidNonWhitespaceAttributeNameCharacters.Contains(character)) + { + var diagnostic = RazorDiagnosticFactory.CreateTagHelper_InvalidBoundAttributePrefix( + _containingTypeName, + _propertyName, + _indexerNamePrefix, + character); + + yield return diagnostic; + } + } + } + } + } + + private void EnsureDiagnostics() + { + if (_diagnostics == null) + { + _diagnostics = new HashSet(); + } + } + + private class ITagHelperBoundAttributeDescriptor : BoundAttributeDescriptor + { + public ITagHelperBoundAttributeDescriptor( + bool isEnum, + string name, + string propertyName, + string typeName, + string dictionaryAttributeNamePrefix, + string dictionaryValueTypeName, + string documentation, + string displayName, + Dictionary metadata, + IEnumerable diagnostics) : base(DescriptorKind) + { + IsEnum = isEnum; + IsIndexerStringProperty = dictionaryValueTypeName == typeof(string).FullName || dictionaryValueTypeName == "string"; + IsStringProperty = typeName == typeof(string).FullName || typeName == "string"; + Name = name; + TypeName = typeName; + IndexerNamePrefix = dictionaryAttributeNamePrefix; + IndexerTypeName = dictionaryValueTypeName; + Documentation = documentation; + DisplayName = displayName; + Diagnostics = new List(diagnostics); + Metadata = new Dictionary(metadata) + { + [PropertyNameKey] = propertyName + }; + } + } + } +} diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/ITagHelperDescriptorBuilder.cs b/src/Microsoft.AspNetCore.Razor.Evolution/ITagHelperDescriptorBuilder.cs new file mode 100644 index 0000000000..46fced9122 --- /dev/null +++ b/src/Microsoft.AspNetCore.Razor.Evolution/ITagHelperDescriptorBuilder.cs @@ -0,0 +1,263 @@ +// 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 Microsoft.AspNetCore.Razor.Evolution.Legacy; + +namespace Microsoft.AspNetCore.Razor.Evolution +{ + public sealed class ITagHelperDescriptorBuilder + { + public static readonly string DescriptorKind = "ITagHelper"; + public static readonly string TypeNameKey = "ITagHelper.TypeName"; + + private static ICollection InvalidNonWhitespaceAllowedChildCharacters { get; } = new HashSet( + new[] { '@', '!', '<', '/', '?', '[', '>', ']', '=', '"', '\'', '*' }); + + private string _documentation; + private string _tagOutputHint; + private HashSet _allowedChildTags; + private HashSet _attributeDescriptors; + private HashSet _tagMatchingRules; + private readonly string _assemblyName; + private readonly string _typeName; + private readonly Dictionary _metadata; + private HashSet _diagnostics; + + private ITagHelperDescriptorBuilder(string typeName, string assemblyName) + { + _typeName = typeName; + _assemblyName = assemblyName; + _metadata = new Dictionary(StringComparer.Ordinal); + } + + public static ITagHelperDescriptorBuilder Create(string typeName, string assemblyName) + { + return new ITagHelperDescriptorBuilder(typeName, assemblyName); + } + + public ITagHelperDescriptorBuilder BindAttribute(BoundAttributeDescriptor descriptor) + { + if (descriptor == null) + { + throw new ArgumentNullException(nameof(descriptor)); + } + + EnsureAttributeDescriptors(); + _attributeDescriptors.Add(descriptor); + + return this; + } + + public ITagHelperDescriptorBuilder BindAttribute(Action configure) + { + if (configure == null) + { + throw new ArgumentNullException(nameof(configure)); + } + + var builder = ITagHelperBoundAttributeDescriptorBuilder.Create(_typeName); + + configure(builder); + + var attributeDescriptor = builder.Build(); + + return BindAttribute(attributeDescriptor); + } + + public ITagHelperDescriptorBuilder TagMatchingRule(TagMatchingRule rule) + { + if (rule == null) + { + throw new ArgumentNullException(nameof(rule)); + } + + EnsureTagMatchingRules(); + _tagMatchingRules.Add(rule); + + return this; + } + + public ITagHelperDescriptorBuilder TagMatchingRule(Action configure) + { + if (configure == null) + { + throw new ArgumentNullException(nameof(configure)); + } + + var builder = TagMatchingRuleBuilder.Create(); + + configure(builder); + + var rule = builder.Build(); + + return TagMatchingRule(rule); + } + + public ITagHelperDescriptorBuilder AllowChildTag(string allowedChild) + { + EnsureAllowedChildTags(); + _allowedChildTags.Add(allowedChild); + + return this; + } + + public ITagHelperDescriptorBuilder TagOutputHint(string hint) + { + _tagOutputHint = hint; + + return this; + } + + public ITagHelperDescriptorBuilder Documentation(string documentation) + { + _documentation = documentation; + + return this; + } + + public ITagHelperDescriptorBuilder AddMetadata(string key, string value) + { + _metadata[key] = value; + + return this; + } + + public ITagHelperDescriptorBuilder AddDiagnostic(RazorDiagnostic diagnostic) + { + EnsureDiagnostics(); + _diagnostics.Add(diagnostic); + + return this; + } + + public TagHelperDescriptor Build() + { + var validationDiagnostics = Validate(); + var diagnostics = new HashSet(validationDiagnostics); + if (_diagnostics != null) + { + diagnostics.UnionWith(_diagnostics); + } + + var descriptor = new ITagHelperDescriptor( + _typeName, + _assemblyName, + _typeName /* Name */, + _typeName /* DisplayName */, + _documentation, + _tagOutputHint, + _tagMatchingRules ?? Enumerable.Empty(), + _attributeDescriptors ?? Enumerable.Empty(), + _allowedChildTags ?? Enumerable.Empty(), + _metadata, + diagnostics); + + return descriptor; + } + + public void Reset() + { + _documentation = null; + _tagOutputHint = null; + _allowedChildTags?.Clear(); + _attributeDescriptors?.Clear(); + _tagMatchingRules?.Clear(); + _metadata.Clear(); + _diagnostics?.Clear(); + } + + private IEnumerable Validate() + { + if (_allowedChildTags != null) + { + foreach (var name in _allowedChildTags) + { + if (string.IsNullOrWhiteSpace(name)) + { + var diagnostic = RazorDiagnosticFactory.CreateTagHelper_InvalidRestrictedChildNullOrWhitespace(_typeName); + + yield return diagnostic; + } + else if (name != TagHelperDescriptorProvider.ElementCatchAllTarget) + { + foreach (var character in name) + { + if (char.IsWhiteSpace(character) || InvalidNonWhitespaceAllowedChildCharacters.Contains(character)) + { + var diagnostic = RazorDiagnosticFactory.CreateTagHelper_InvalidRestrictedChild(name, _typeName, character); + + yield return diagnostic; + } + } + } + } + } + } + + private void EnsureAttributeDescriptors() + { + if (_attributeDescriptors == null) + { + _attributeDescriptors = new HashSet(BoundAttributeDescriptorComparer.Default); + } + } + + private void EnsureTagMatchingRules() + { + if (_tagMatchingRules == null) + { + _tagMatchingRules = new HashSet(TagMatchingRuleComparer.Default); + } + } + + private void EnsureAllowedChildTags() + { + if (_allowedChildTags == null) + { + _allowedChildTags = new HashSet(StringComparer.OrdinalIgnoreCase); + } + } + + private void EnsureDiagnostics() + { + if (_diagnostics == null) + { + _diagnostics = new HashSet(); + } + } + + private class ITagHelperDescriptor : TagHelperDescriptor + { + public ITagHelperDescriptor( + string typeName, + string assemblyName, + string name, + string displayName, + string documentation, + string tagOutputHint, + IEnumerable tagMatchingRules, + IEnumerable attributeDescriptors, + IEnumerable allowedChildTags, + Dictionary metadata, + IEnumerable diagnostics) : base(DescriptorKind) + { + Name = typeName; + AssemblyName = assemblyName; + DisplayName = displayName; + Documentation = documentation; + TagOutputHint = tagOutputHint; + TagMatchingRules = new List(tagMatchingRules); + BoundAttributes = new List(attributeDescriptors); + AllowedChildTags = new List(allowedChildTags); + Diagnostics = new List(diagnostics); + Metadata = new Dictionary(metadata) + { + [TypeNameKey] = typeName + }; + } + } + } +} diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/CreateTagHelperIRNode.cs b/src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/CreateTagHelperIRNode.cs index 393ee87721..59435401fd 100644 --- a/src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/CreateTagHelperIRNode.cs +++ b/src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/CreateTagHelperIRNode.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using Microsoft.AspNetCore.Razor.Evolution.Legacy; namespace Microsoft.AspNetCore.Razor.Evolution.Intermediate { diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/SetPreallocatedTagHelperPropertyIRNode.cs b/src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/SetPreallocatedTagHelperPropertyIRNode.cs index b283af0e1b..9b08e39941 100644 --- a/src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/SetPreallocatedTagHelperPropertyIRNode.cs +++ b/src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/SetPreallocatedTagHelperPropertyIRNode.cs @@ -2,6 +2,7 @@ // 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.Evolution.Legacy; namespace Microsoft.AspNetCore.Razor.Evolution.Intermediate { @@ -21,7 +22,11 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Intermediate public string PropertyName { get; set; } - public TagHelperAttributeDescriptor Descriptor { get; set; } + public BoundAttributeDescriptor Descriptor { get; set; } + + public TagHelperBinding Binding { get; set; } + + public bool IsIndexerNameMatch { get; set; } public override void Accept(RazorIRNodeVisitor visitor) { diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/SetTagHelperPropertyIRNode.cs b/src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/SetTagHelperPropertyIRNode.cs index 83b307d178..1bcf84d6cc 100644 --- a/src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/SetTagHelperPropertyIRNode.cs +++ b/src/Microsoft.AspNetCore.Razor.Evolution/Intermediate/SetTagHelperPropertyIRNode.cs @@ -23,7 +23,11 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Intermediate internal HtmlAttributeValueStyle ValueStyle { get; set; } - public TagHelperAttributeDescriptor Descriptor { get; set; } + public BoundAttributeDescriptor Descriptor { get; set; } + + public TagHelperBinding Binding { get; set; } + + public bool IsIndexerNameMatch { get; set; } public override void Accept(RazorIRNodeVisitor visitor) { diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/RequiredAttributeDescriptorComparer.cs b/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/RequiredAttributeDescriptorComparer.cs new file mode 100644 index 0000000000..46ea8f704c --- /dev/null +++ b/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/RequiredAttributeDescriptorComparer.cs @@ -0,0 +1,81 @@ +// 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 Microsoft.Extensions.Internal; + +namespace Microsoft.AspNetCore.Razor.Evolution.Legacy +{ + /// + /// An used to check equality between + /// two s. + /// + internal class RequiredAttributeDescriptorComparer : IEqualityComparer + { + /// + /// A default instance of the . + /// + public static readonly RequiredAttributeDescriptorComparer Default = + new RequiredAttributeDescriptorComparer(); + + /// + /// A default instance of the that does case-sensitive comparison. + /// + internal static readonly RequiredAttributeDescriptorComparer CaseSensitive = + new RequiredAttributeDescriptorComparer(caseSensitive: true); + + private readonly StringComparer _stringComparer; + private readonly StringComparison _stringComparison; + + private RequiredAttributeDescriptorComparer(bool caseSensitive = false) + { + if (caseSensitive) + { + _stringComparer = StringComparer.Ordinal; + _stringComparison = StringComparison.Ordinal; + } + else + { + _stringComparer = StringComparer.OrdinalIgnoreCase; + _stringComparison = StringComparison.OrdinalIgnoreCase; + } + } + + /// + public virtual bool Equals( + RequiredAttributeDescriptor descriptorX, + RequiredAttributeDescriptor descriptorY) + { + if (object.ReferenceEquals(descriptorX, descriptorY)) + { + return true; + } + + if (descriptorX == null ^ descriptorY == null) + { + return false; + } + + return descriptorX != null && + descriptorX.NameComparison == descriptorY.NameComparison && + descriptorX.ValueComparison == descriptorY.ValueComparison && + string.Equals(descriptorX.Name, descriptorY.Name, _stringComparison) && + string.Equals(descriptorX.Value, descriptorY.Value, StringComparison.Ordinal) && + Enumerable.SequenceEqual(descriptorX.Diagnostics, descriptorY.Diagnostics); + } + + /// + public virtual int GetHashCode(RequiredAttributeDescriptor descriptor) + { + var hashCodeCombiner = HashCodeCombiner.Start(); + hashCodeCombiner.Add(descriptor.NameComparison); + hashCodeCombiner.Add(descriptor.ValueComparison); + hashCodeCombiner.Add(descriptor.Name, _stringComparer); + hashCodeCombiner.Add(descriptor.Value, StringComparer.Ordinal); + + return hashCodeCombiner.CombinedHash; + } + } +} diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/TagHelperBinding.cs b/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/TagHelperBinding.cs new file mode 100644 index 0000000000..7859bb4478 --- /dev/null +++ b/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/TagHelperBinding.cs @@ -0,0 +1,25 @@ +// 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.Evolution.Legacy +{ + public sealed class TagHelperBinding + { + public IReadOnlyDictionary> _mappings; + + internal TagHelperBinding(IReadOnlyDictionary> mappings) + { + _mappings = mappings; + Descriptors = _mappings.Keys; + } + + public IEnumerable Descriptors { get; } + + public IEnumerable GetBoundRules(TagHelperDescriptor descriptor) + { + return _mappings[descriptor]; + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/TagHelperBlock.cs b/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/TagHelperBlock.cs index 0b2b5a9585..8b48259544 100644 --- a/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/TagHelperBlock.cs +++ b/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/TagHelperBlock.cs @@ -25,7 +25,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy : base(source.Type, source.Children, source.ChunkGenerator) { TagName = source.TagName; - Descriptors = source.Descriptors; + Binding = source.BindingResult; Attributes = new List(source.Attributes); _start = source.Start; TagMode = source.TagMode; @@ -61,9 +61,9 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy public TagMode TagMode { get; } /// - /// s for the HTML element. + /// bindings for the HTML element. /// - public IEnumerable Descriptors { get; } + public TagHelperBinding Binding { get; } /// /// The HTML attributes. diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/TagHelperBlockBuilder.cs b/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/TagHelperBlockBuilder.cs index d171a0080a..8b37f2a381 100644 --- a/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/TagHelperBlockBuilder.cs +++ b/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/TagHelperBlockBuilder.cs @@ -19,7 +19,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy : base(original) { TagName = original.TagName; - Descriptors = original.Descriptors; + BindingResult = original.Binding; Attributes = new List(original.Attributes); } @@ -31,22 +31,21 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy /// HTML syntax of the element in the Razor source. /// Starting location of the . /// Attributes of the . - /// The s associated with the current HTML - /// tag. + /// public TagHelperBlockBuilder( string tagName, TagMode tagMode, SourceLocation start, IList attributes, - IEnumerable descriptors) + TagHelperBinding bindingResult) { TagName = tagName; TagMode = tagMode; Start = start; - Descriptors = descriptors; + BindingResult = bindingResult; Attributes = new List(attributes); Type = BlockType.Tag; - ChunkGenerator = new TagHelperChunkGenerator(descriptors); + ChunkGenerator = new TagHelperChunkGenerator(); } // Internal for testing @@ -60,7 +59,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy TagMode = tagMode; Attributes = attributes; Type = BlockType.Tag; - ChunkGenerator = new TagHelperChunkGenerator(tagHelperDescriptors: null); + ChunkGenerator = new TagHelperChunkGenerator(); // Children is IList, no AddRange foreach (var child in children) @@ -89,7 +88,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy /// /// s for the HTML element. /// - public IEnumerable Descriptors { get; } + public TagHelperBinding BindingResult { get; } /// /// The HTML attributes. diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/TagHelperBlockRewriter.cs b/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/TagHelperBlockRewriter.cs index 0472199bca..c8955919c5 100644 --- a/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/TagHelperBlockRewriter.cs +++ b/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/TagHelperBlockRewriter.cs @@ -17,28 +17,24 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy string tagName, bool validStructure, Block tag, - IEnumerable descriptors, + TagHelperBinding bindingResult, ErrorSink errorSink) { // There will always be at least one child for the '<'. var start = tag.Children.First().Start; - var attributes = GetTagAttributes(tagName, validStructure, tag, descriptors, errorSink); - var tagMode = GetTagMode(tagName, tag, descriptors, errorSink); + var attributes = GetTagAttributes(tagName, validStructure, tag, bindingResult, errorSink); + var tagMode = GetTagMode(tagName, tag, bindingResult, errorSink); - return new TagHelperBlockBuilder(tagName, tagMode, start, attributes, descriptors); + return new TagHelperBlockBuilder(tagName, tagMode, start, attributes, bindingResult); } private static IList GetTagAttributes( string tagName, bool validStructure, Block tagBlock, - IEnumerable descriptors, + TagHelperBinding bindingResult, ErrorSink errorSink) { - // Ignore all but one descriptor per type since this method uses the TagHelperDescriptors only to get the - // contained TagHelperAttributeDescriptor's. - descriptors = descriptors.Distinct(TypeBasedTagHelperDescriptorComparer.Default); - var attributes = new List(); // We skip the first child "" or "/>". @@ -52,11 +48,11 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy TryParseResult result; if (child.IsBlock) { - result = TryParseBlock(tagName, (Block)child, descriptors, errorSink); + result = TryParseBlock(tagName, (Block)child, bindingResult.Descriptors, errorSink); } else { - result = TryParseSpan((Span)child, descriptors, errorSink); + result = TryParseSpan((Span)child, bindingResult.Descriptors, errorSink); } // Only want to track the attribute if we succeeded in parsing its corresponding Block/Span. @@ -77,7 +73,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy LegacyResources.FormatRewriterError_EmptyTagHelperBoundAttribute( result.AttributeName, tagName, - GetPropertyType(result.AttributeName, descriptors)), + GetPropertyType(result.AttributeName, bindingResult.Descriptors)), result.AttributeName.Length); } @@ -118,7 +114,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy private static TagMode GetTagMode( string tagName, Block beginTagBlock, - IEnumerable descriptors, + TagHelperBinding bindingResult, ErrorSink errorSink) { var childSpan = beginTagBlock.FindLastDescendentSpan(); @@ -129,12 +125,15 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy return TagMode.SelfClosing; } - var baseDescriptor = descriptors.FirstOrDefault( - descriptor => descriptor.TagStructure != TagStructure.Unspecified); - var resolvedTagStructure = baseDescriptor?.TagStructure ?? TagStructure.Unspecified; - if (resolvedTagStructure == TagStructure.WithoutEndTag) + foreach (var descriptor in bindingResult.Descriptors) { - return TagMode.StartTagOnly; + var boundRules = bindingResult.GetBoundRules(descriptor); + var nonDefaultRule = boundRules.FirstOrDefault(rule => rule.TagStructure != TagStructure.Unspecified); + + if (nonDefaultRule?.TagStructure == TagStructure.WithoutEndTag) + { + return TagMode.StartTagOnly; + } } return TagMode.StartTagAndEndTag; @@ -656,8 +655,16 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy private static string GetPropertyType(string name, IEnumerable descriptors) { var firstBoundAttribute = FindFirstBoundAttribute(name, descriptors); + var isBoundToIndexer = firstBoundAttribute.IsIndexerNameMatch(name); - return firstBoundAttribute?.TypeName; + if (isBoundToIndexer) + { + return firstBoundAttribute?.IndexerTypeName; + } + else + { + return firstBoundAttribute?.TypeName; + } } // Create a TryParseResult for given name, filling in binding details. @@ -665,10 +672,12 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy { var firstBoundAttribute = FindFirstBoundAttribute(name, descriptors); var isBoundAttribute = firstBoundAttribute != null; - var isBoundNonStringAttribute = isBoundAttribute && !firstBoundAttribute.IsStringProperty; + var isBoundNonStringAttribute = isBoundAttribute && + !(firstBoundAttribute.IsStringProperty || + (firstBoundAttribute.IsIndexerNameMatch(name) && firstBoundAttribute.IsIndexerStringProperty)); var isMissingDictionaryKey = isBoundAttribute && - firstBoundAttribute.IsIndexer && - name.Length == firstBoundAttribute.Name.Length; + firstBoundAttribute.IndexerNamePrefix != null && + name.Length == firstBoundAttribute.IndexerNamePrefix.Length; return new TryParseResult { @@ -680,15 +689,13 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy } // Finds first TagHelperAttributeDescriptor matching given name. - private static TagHelperAttributeDescriptor FindFirstBoundAttribute( + private static BoundAttributeDescriptor FindFirstBoundAttribute( string name, IEnumerable descriptors) { - // Non-indexers (exact HTML attribute name matches) have higher precedence than indexers (prefix matches). - // Attributes already sorted to ensure this precedence. var firstBoundAttribute = descriptors - .SelectMany(descriptor => descriptor.Attributes) - .FirstOrDefault(attributeDescriptor => attributeDescriptor.IsNameMatch(name)); + .SelectMany(descriptor => descriptor.BoundAttributes) + .FirstOrDefault(attributeDescriptor => attributeDescriptor.CanMatchName(name)); return firstBoundAttribute; } diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/TagHelperChunkGenerator.cs b/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/TagHelperChunkGenerator.cs index b9c42fb6fc..c6977108f1 100644 --- a/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/TagHelperChunkGenerator.cs +++ b/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/TagHelperChunkGenerator.cs @@ -1,25 +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; - namespace Microsoft.AspNetCore.Razor.Evolution.Legacy { internal class TagHelperChunkGenerator : ParentChunkGenerator { - private IEnumerable _tagHelperDescriptors; - - /// - /// Instantiates a new . - /// - /// - /// s associated with the current HTML tag. - /// - public TagHelperChunkGenerator(IEnumerable tagHelperDescriptors) - { - _tagHelperDescriptors = tagHelperDescriptors; - } - public override void GenerateStartParentChunk(Block target, ChunkGeneratorContext context) { //var tagHelperBlock = target as TagHelperBlock; diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/TagHelperDescriptorComparer.cs b/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/TagHelperDescriptorComparer.cs index 5c4e4ca403..7e429d5dad 100644 --- a/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/TagHelperDescriptorComparer.cs +++ b/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/TagHelperDescriptorComparer.cs @@ -8,10 +8,6 @@ using Microsoft.Extensions.Internal; namespace Microsoft.AspNetCore.Razor.Evolution.Legacy { - /// - /// An used to check equality between - /// two s. - /// internal class TagHelperDescriptorComparer : IEqualityComparer { /// @@ -20,27 +16,34 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy public static readonly TagHelperDescriptorComparer Default = new TagHelperDescriptorComparer(); /// - /// An instance of that only compares - /// . + /// A default instance of the that does case-sensitive comparison. /// - public static readonly TagHelperDescriptorComparer TypeName = new TypeNameTagHelperDescriptorComparer(); + internal static readonly TagHelperDescriptorComparer CaseSensitive = + new TagHelperDescriptorComparer(caseSensitive: true); - /// - /// Initializes a new instance. - /// - protected TagHelperDescriptorComparer() + private readonly StringComparer _stringComparer; + private readonly StringComparison _stringComparison; + private readonly BoundAttributeDescriptorComparer _boundAttributeComparer; + private readonly TagMatchingRuleComparer _tagMatchingRuleComparer; + + private TagHelperDescriptorComparer(bool caseSensitive = false) { + if (caseSensitive) + { + _stringComparer = StringComparer.Ordinal; + _stringComparison = StringComparison.Ordinal; + _boundAttributeComparer = BoundAttributeDescriptorComparer.CaseSensitive; + _tagMatchingRuleComparer = TagMatchingRuleComparer.CaseSensitive; + } + else + { + _stringComparer = StringComparer.OrdinalIgnoreCase; + _stringComparison = StringComparison.OrdinalIgnoreCase; + _boundAttributeComparer = BoundAttributeDescriptorComparer.Default; + _tagMatchingRuleComparer = TagMatchingRuleComparer.Default; + } } - /// - /// - /// Determines equality based on , - /// , , - /// , , - /// and . - /// Ignores because it can be inferred directly from - /// and . - /// public virtual bool Equals(TagHelperDescriptor descriptorX, TagHelperDescriptor descriptorY) { if (descriptorX == descriptorY) @@ -49,28 +52,30 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy } return descriptorX != null && - string.Equals(descriptorX.TypeName, descriptorY.TypeName, StringComparison.Ordinal) && - string.Equals(descriptorX.TagName, descriptorY.TagName, StringComparison.OrdinalIgnoreCase) && + string.Equals(descriptorX.Kind, descriptorY.Kind, StringComparison.Ordinal) && string.Equals(descriptorX.AssemblyName, descriptorY.AssemblyName, StringComparison.Ordinal) && - string.Equals( - descriptorX.RequiredParent, - descriptorY.RequiredParent, - StringComparison.OrdinalIgnoreCase) && Enumerable.SequenceEqual( - descriptorX.RequiredAttributes.OrderBy(attribute => attribute.Name, StringComparer.OrdinalIgnoreCase), - descriptorY.RequiredAttributes.OrderBy(attribute => attribute.Name, StringComparer.OrdinalIgnoreCase), - TagHelperRequiredAttributeDescriptorComparer.Default) && - (descriptorX.AllowedChildren == descriptorY.AllowedChildren || - (descriptorX.AllowedChildren != null && - descriptorY.AllowedChildren != null && + descriptorX.BoundAttributes.OrderBy(attribute => attribute.Name, _stringComparer), + descriptorY.BoundAttributes.OrderBy(attribute => attribute.Name, _stringComparer), + _boundAttributeComparer) && Enumerable.SequenceEqual( - descriptorX.AllowedChildren.OrderBy(child => child, StringComparer.OrdinalIgnoreCase), - descriptorY.AllowedChildren.OrderBy(child => child, StringComparer.OrdinalIgnoreCase), - StringComparer.OrdinalIgnoreCase))) && - descriptorX.TagStructure == descriptorY.TagStructure && + descriptorX.TagMatchingRules.OrderBy(rule => rule.TagName, _stringComparer), + descriptorY.TagMatchingRules.OrderBy(rule => rule.TagName, _stringComparer), + _tagMatchingRuleComparer) && + (descriptorX.AllowedChildTags == descriptorY.AllowedChildTags || + (descriptorX.AllowedChildTags != null && + descriptorY.AllowedChildTags != null && Enumerable.SequenceEqual( - descriptorX.PropertyBag.OrderBy(propertyX => propertyX.Key, StringComparer.Ordinal), - descriptorY.PropertyBag.OrderBy(propertyY => propertyY.Key, StringComparer.Ordinal)); + descriptorX.AllowedChildTags.OrderBy(child => child, _stringComparer), + descriptorY.AllowedChildTags.OrderBy(child => child, _stringComparer), + _stringComparer))) && + string.Equals(descriptorX.Documentation, descriptorY.Documentation, StringComparison.Ordinal) && + string.Equals(descriptorX.DisplayName, descriptorY.DisplayName, StringComparison.Ordinal) && + string.Equals(descriptorX.TagOutputHint, descriptorY.TagOutputHint, _stringComparison) && + Enumerable.SequenceEqual(descriptorX.Diagnostics, descriptorY.Diagnostics) && + Enumerable.SequenceEqual( + descriptorX.Metadata.OrderBy(metadataX => metadataX.Key, StringComparer.Ordinal), + descriptorY.Metadata.OrderBy(metadataY => metadataY.Key, StringComparer.Ordinal)); } /// @@ -82,59 +87,35 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy } var hashCodeCombiner = HashCodeCombiner.Start(); - hashCodeCombiner.Add(descriptor.TypeName, StringComparer.Ordinal); - hashCodeCombiner.Add(descriptor.TagName, StringComparer.OrdinalIgnoreCase); + hashCodeCombiner.Add(descriptor.Kind); hashCodeCombiner.Add(descriptor.AssemblyName, StringComparer.Ordinal); - hashCodeCombiner.Add(descriptor.RequiredParent, StringComparer.OrdinalIgnoreCase); - hashCodeCombiner.Add(descriptor.TagStructure); - var attributes = descriptor.RequiredAttributes.OrderBy( - attribute => attribute.Name, - StringComparer.OrdinalIgnoreCase); - foreach (var attribute in attributes) + var boundAttributes = descriptor.BoundAttributes.OrderBy(attribute => attribute.Name, _stringComparer); + foreach (var attribute in boundAttributes) { - hashCodeCombiner.Add(TagHelperRequiredAttributeDescriptorComparer.Default.GetHashCode(attribute)); + hashCodeCombiner.Add(_boundAttributeComparer.GetHashCode(attribute)); } - if (descriptor.AllowedChildren != null) + var rules = descriptor.TagMatchingRules.OrderBy(rule => rule.TagName, _stringComparer); + foreach (var rule in rules) { - var allowedChildren = descriptor.AllowedChildren.OrderBy(child => child, StringComparer.OrdinalIgnoreCase); + hashCodeCombiner.Add(_tagMatchingRuleComparer.GetHashCode(rule)); + } + + hashCodeCombiner.Add(descriptor.Documentation, StringComparer.Ordinal); + hashCodeCombiner.Add(descriptor.DisplayName, StringComparer.Ordinal); + hashCodeCombiner.Add(descriptor.TagOutputHint, _stringComparer); + + if (descriptor.AllowedChildTags != null) + { + var allowedChildren = descriptor.AllowedChildTags.OrderBy(child => child, _stringComparer); foreach (var child in allowedChildren) { - hashCodeCombiner.Add(child, StringComparer.OrdinalIgnoreCase); + hashCodeCombiner.Add(child, _stringComparer); } } return hashCodeCombiner.CombinedHash; } - - private class TypeNameTagHelperDescriptorComparer : TagHelperDescriptorComparer - { - public override bool Equals(TagHelperDescriptor descriptorX, TagHelperDescriptor descriptorY) - { - if (object.ReferenceEquals(descriptorX, descriptorY)) - { - return true; - } - else if (descriptorX == null ^ descriptorY == null) - { - return false; - } - else - { - return string.Equals(descriptorX.TypeName, descriptorY.TypeName, StringComparison.Ordinal); - } - } - - public override int GetHashCode(TagHelperDescriptor descriptor) - { - if (descriptor == null) - { - throw new ArgumentNullException(nameof(descriptor)); - } - - return descriptor.TypeName == null ? 0 : StringComparer.Ordinal.GetHashCode(descriptor.TypeName); - } - } } } \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/TagHelperDescriptorProvider.cs b/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/TagHelperDescriptorProvider.cs index 744e400fb1..2853c3a16b 100644 --- a/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/TagHelperDescriptorProvider.cs +++ b/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/TagHelperDescriptorProvider.cs @@ -15,14 +15,16 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy public const string ElementCatchAllTarget = "*"; private IDictionary> _registrations; - private string _tagHelperPrefix; + private readonly string _tagHelperPrefix; /// /// Instantiates a new instance of the . /// + /// The tag helper prefix being used by the document. /// The descriptors that the will pull from. - public TagHelperDescriptorProvider(IEnumerable descriptors) + public TagHelperDescriptorProvider(string tagHelperPrefix, IEnumerable descriptors) { + _tagHelperPrefix = tagHelperPrefix; _registrations = new Dictionary>(StringComparer.OrdinalIgnoreCase); // Populate our registrations @@ -42,7 +44,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy /// s that apply to the given . /// Will return an empty if no s are /// found. - public IEnumerable GetDescriptors( + public TagHelperBinding GetTagHelperBinding( string tagName, IEnumerable> attributes, string parentTagName) @@ -52,14 +54,13 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy !tagName.StartsWith(_tagHelperPrefix, StringComparison.OrdinalIgnoreCase))) { // The tagName doesn't have the tag helper prefix, we can short circuit. - return Enumerable.Empty(); + return null; } - HashSet catchAllDescriptors; IEnumerable descriptors; // Ensure there's a HashSet to use. - if (!_registrations.TryGetValue(ElementCatchAllTarget, out catchAllDescriptors)) + if (!_registrations.TryGetValue(ElementCatchAllTarget, out HashSet catchAllDescriptors)) { descriptors = new HashSet(TagHelperDescriptorComparer.Default); } @@ -70,64 +71,87 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy // If we have a tag name associated with the requested name, we need to combine matchingDescriptors // with all the catch-all descriptors. - HashSet matchingDescriptors; - if (_registrations.TryGetValue(tagName, out matchingDescriptors)) + if (_registrations.TryGetValue(tagName, out HashSet matchingDescriptors)) { descriptors = matchingDescriptors.Concat(descriptors); } - var applicableDescriptors = new List(); + var tagNameWithoutPrefix = _tagHelperPrefix != null ? tagName.Substring(_tagHelperPrefix.Length) : tagName; + Dictionary> applicableDescriptorMappings = null; foreach (var descriptor in descriptors) { - if (HasRequiredAttributes(descriptor, attributes) && - HasRequiredParentTag(descriptor, parentTagName)) + var applicableRules = descriptor.TagMatchingRules.Where( + rule => MatchesRule(rule, attributes, tagNameWithoutPrefix, parentTagName)); + + if (applicableRules.Any()) { - applicableDescriptors.Add(descriptor); + if (applicableDescriptorMappings == null) + { + applicableDescriptorMappings = new Dictionary>(); + } + + applicableDescriptorMappings[descriptor] = applicableRules; } } - return applicableDescriptors.Distinct(TagHelperDescriptorComparer.TypeName); + if (applicableDescriptorMappings == null) + { + return null; + } + + var tagMappingResult = new TagHelperBinding(applicableDescriptorMappings); + + return tagMappingResult; } - private bool HasRequiredParentTag( - TagHelperDescriptor descriptor, + private bool MatchesRule( + TagMatchingRule rule, + IEnumerable> tagAttributes, + string tagNameWithoutPrefix, string parentTagName) { - return descriptor.RequiredParent == null || - string.Equals(parentTagName, descriptor.RequiredParent, StringComparison.OrdinalIgnoreCase); - } + // Verify tag name + if (rule.TagName != ElementCatchAllTarget && + rule.TagName != null && + !string.Equals(tagNameWithoutPrefix, rule.TagName, StringComparison.OrdinalIgnoreCase)) + { + return false; + } - private bool HasRequiredAttributes( - TagHelperDescriptor descriptor, - IEnumerable> attributes) - { - return descriptor.RequiredAttributes.All( - requiredAttribute => attributes.Any( - attribute => requiredAttribute.IsMatch(attribute.Key, attribute.Value))); + // Verify parent tag + if (rule.ParentTag != null && !string.Equals(parentTagName, rule.ParentTag, StringComparison.OrdinalIgnoreCase)) + { + return false; + } + + if (!rule.Attributes.All( + requiredAttribute => tagAttributes.Any( + attribute => requiredAttribute.IsMatch(attribute.Key, attribute.Value)))) + { + return false; + } + + return true; } private void Register(TagHelperDescriptor descriptor) { - HashSet descriptorSet; - - if (_tagHelperPrefix == null) + foreach (var rule in descriptor.TagMatchingRules) { - _tagHelperPrefix = descriptor.Prefix; + var registrationKey = + string.Equals(rule.TagName, ElementCatchAllTarget, StringComparison.Ordinal) ? + ElementCatchAllTarget : + _tagHelperPrefix + rule.TagName; + + // Ensure there's a HashSet to add the descriptor to. + if (!_registrations.TryGetValue(registrationKey, out HashSet descriptorSet)) + { + descriptorSet = new HashSet(TagHelperDescriptorComparer.Default); + _registrations[registrationKey] = descriptorSet; + } + + descriptorSet.Add(descriptor); } - - var registrationKey = - string.Equals(descriptor.TagName, ElementCatchAllTarget, StringComparison.Ordinal) ? - ElementCatchAllTarget : - descriptor.FullTagName; - - // Ensure there's a HashSet to add the descriptor to. - if (!_registrations.TryGetValue(registrationKey, out descriptorSet)) - { - descriptorSet = new HashSet(TagHelperDescriptorComparer.Default); - _registrations[registrationKey] = descriptorSet; - } - - descriptorSet.Add(descriptor); } } } \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/TagHelperParseTreeRewriter.cs b/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/TagHelperParseTreeRewriter.cs index 14f4ea972b..bee30a3eeb 100644 --- a/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/TagHelperParseTreeRewriter.cs +++ b/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/TagHelperParseTreeRewriter.cs @@ -36,6 +36,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy "wbr" }; + private readonly string _tagHelperPrefix; private readonly List> _htmlAttributeTracker; private readonly StringBuilder _attributeValueBuilder; private readonly TagHelperDescriptorProvider _provider; @@ -45,8 +46,9 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy private BlockBuilder _currentBlock; private string _currentParentTagName; - public TagHelperParseTreeRewriter(TagHelperDescriptorProvider provider) + public TagHelperParseTreeRewriter(string tagHelperPrefix, TagHelperDescriptorProvider provider) { + _tagHelperPrefix = tagHelperPrefix; _provider = provider; _trackerStack = new Stack(); _blockStack = new Stack(); @@ -172,7 +174,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy return false; } - var descriptors = Enumerable.Empty(); + TagHelperBinding tagHelperBinding; if (!IsPotentialTagHelper(tagName, tagBlock)) { @@ -187,10 +189,10 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy // We're now in a start tag block, we first need to see if the tag block is a tag helper. var providedAttributes = GetAttributeNameValuePairs(tagBlock); - descriptors = _provider.GetDescriptors(tagName, providedAttributes, _currentParentTagName); + tagHelperBinding = _provider.GetTagHelperBinding(tagName, providedAttributes, _currentParentTagName); // If there aren't any TagHelperDescriptors registered then we aren't a TagHelper - if (!descriptors.Any()) + if (tagHelperBinding == null) { // If the current tag matches the current TagHelper scope it means the parent TagHelper matched // all the required attributes but the current one did not; therefore, we need to increment the @@ -206,7 +208,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy } ValidateParentAllowsTagHelper(tagName, tagBlock, errorSink); - ValidateDescriptors(descriptors, tagName, tagBlock, errorSink); + ValidateBinding(tagHelperBinding, tagName, tagBlock, errorSink); // We're in a start TagHelper block. var validTagStructure = ValidateTagSyntax(tagName, tagBlock, errorSink); @@ -215,7 +217,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy tagName, validTagStructure, tagBlock, - descriptors, + tagHelperBinding, errorSink); // Track the original start tag so the editor knows where each piece of the TagHelperBlock lies @@ -252,32 +254,38 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy } else { - descriptors = _provider.GetDescriptors( + tagHelperBinding = _provider.GetTagHelperBinding( tagName, attributes: Enumerable.Empty>(), parentTagName: _currentParentTagName); // If there are not TagHelperDescriptors associated with the end tag block that also have no // required attributes then it means we can't be a TagHelper, bail out. - if (!descriptors.Any()) + if (tagHelperBinding == null) { return false; } - var invalidDescriptor = descriptors.FirstOrDefault( - descriptor => descriptor.TagStructure == TagStructure.WithoutEndTag); - if (invalidDescriptor != null) + foreach (var descriptor in tagHelperBinding.Descriptors) { - // End tag TagHelper that states it shouldn't have an end tag. - errorSink.OnError( - SourceLocationTracker.Advance(tagBlock.Start, " rule.TagStructure == TagStructure.WithoutEndTag); - return false; + if (invalidRule != null) + { + var typeName = descriptor.Metadata[ITagHelperDescriptorBuilder.TypeNameKey]; + + // End tag TagHelper that states it shouldn't have an end tag. + errorSink.OnError( + SourceLocationTracker.Advance(tagBlock.Start, " 0; } private void ValidateParentAllowsContent(Span child, ErrorSink errorSink) @@ -536,8 +544,8 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy errorSink.OnError(errorStart, errorMessage, tagName.Length); } - private static void ValidateDescriptors( - IEnumerable descriptors, + private static void ValidateBinding( + TagHelperBinding bindingResult, string tagName, Block tagBlock, ErrorSink errorSink) @@ -545,24 +553,32 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy // Ensure that all descriptors associated with this tag have appropriate TagStructures. Cannot have // multiple descriptors that expect different TagStructures (other than TagStructure.Unspecified). TagHelperDescriptor baseDescriptor = null; - foreach (var descriptor in descriptors) + TagStructure? baseStructure = null; + foreach (var descriptor in bindingResult.Descriptors) { - if (descriptor.TagStructure != TagStructure.Unspecified) + var boundRules = bindingResult.GetBoundRules(descriptor); + foreach (var rule in boundRules) { - // Can't have a set of TagHelpers that expect different structures. - if (baseDescriptor != null && baseDescriptor.TagStructure != descriptor.TagStructure) + if (rule.TagStructure != TagStructure.Unspecified) { - errorSink.OnError( - tagBlock.Start, - LegacyResources.FormatTagHelperParseTreeRewriter_InconsistentTagStructure( - baseDescriptor.TypeName, - descriptor.TypeName, - tagName, - nameof(TagHelperDescriptor.TagStructure)), - tagBlock.Length); - } + // Can't have a set of TagHelpers that expect different structures. + if (baseStructure.HasValue && baseStructure != rule.TagStructure) + { + var baseDescriptorTypeName = baseDescriptor.Metadata[ITagHelperDescriptorBuilder.TypeNameKey]; + var descriptorTypeName = descriptor.Metadata[ITagHelperDescriptorBuilder.TypeNameKey]; + errorSink.OnError( + tagBlock.Start, + LegacyResources.FormatTagHelperParseTreeRewriter_InconsistentTagStructure( + baseDescriptorTypeName, + descriptorTypeName, + tagName, + nameof(TagMatchingRule.TagStructure)), + tagBlock.Length); + } - baseDescriptor = descriptor; + baseDescriptor = descriptor; + baseStructure = rule.TagStructure; + } } } } @@ -680,7 +696,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy private void TrackTagHelperBlock(TagHelperBlockBuilder builder) { - _currentTagHelperTracker = new TagHelperBlockTracker(builder); + _currentTagHelperTracker = new TagHelperBlockTracker(_tagHelperPrefix, builder); PushTrackerStack(_currentTagHelperTracker); TrackBlock(builder); @@ -830,19 +846,22 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy private class TagHelperBlockTracker : TagBlockTracker { - private IEnumerable _prefixedAllowedChildren; + private IReadOnlyList _prefixedAllowedChildren; + private readonly string _tagHelperPrefix; - public TagHelperBlockTracker(TagHelperBlockBuilder builder) + public TagHelperBlockTracker(string tagHelperPrefix, TagHelperBlockBuilder builder) : base(builder.TagName, isTagHelper: true, depth: 0) { + _tagHelperPrefix = tagHelperPrefix; Builder = builder; - if (Builder.Descriptors.Any(descriptor => descriptor.AllowedChildren != null)) + if (Builder.BindingResult.Descriptors.Any(descriptor => descriptor.AllowedChildTags != null)) { - AllowedChildren = Builder.Descriptors - .Where(descriptor => descriptor.AllowedChildren != null) - .SelectMany(descriptor => descriptor.AllowedChildren) - .Distinct(StringComparer.OrdinalIgnoreCase); + AllowedChildren = Builder.BindingResult.Descriptors + .Where(descriptor => descriptor.AllowedChildTags != null) + .SelectMany(descriptor => descriptor.AllowedChildTags) + .Distinct(StringComparer.OrdinalIgnoreCase) + .ToList(); } } @@ -850,18 +869,17 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy public uint OpenMatchingTags { get; set; } - public IEnumerable AllowedChildren { get; } + public IReadOnlyList AllowedChildren { get; } - public IEnumerable PrefixedAllowedChildren + public IReadOnlyList PrefixedAllowedChildren { get { if (AllowedChildren != null && _prefixedAllowedChildren == null) { - Debug.Assert(Builder.Descriptors.Count() >= 1); + Debug.Assert(Builder.BindingResult.Descriptors.Count() >= 1); - var prefix = Builder.Descriptors.First().Prefix; - _prefixedAllowedChildren = AllowedChildren.Select(allowedChild => prefix + allowedChild); + _prefixedAllowedChildren = AllowedChildren.Select(allowedChild => _tagHelperPrefix + allowedChild).ToList(); } return _prefixedAllowedChildren; diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/TagHelperRequiredAttributeDescriptorComparer.cs b/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/TagHelperRequiredAttributeDescriptorComparer.cs deleted file mode 100644 index aeffbf27d2..0000000000 --- a/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/TagHelperRequiredAttributeDescriptorComparer.cs +++ /dev/null @@ -1,58 +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; -using System.Collections.Generic; -using Microsoft.Extensions.Internal; - -namespace Microsoft.AspNetCore.Razor.Evolution.Legacy -{ - /// - /// An used to check equality between - /// two s. - /// - internal class TagHelperRequiredAttributeDescriptorComparer : IEqualityComparer - { - /// - /// A default instance of the . - /// - public static readonly TagHelperRequiredAttributeDescriptorComparer Default = - new TagHelperRequiredAttributeDescriptorComparer(); - - /// - /// Initializes a new instance. - /// - protected TagHelperRequiredAttributeDescriptorComparer() - { - } - - /// - public virtual bool Equals( - TagHelperRequiredAttributeDescriptor descriptorX, - TagHelperRequiredAttributeDescriptor descriptorY) - { - if (descriptorX == descriptorY) - { - return true; - } - - return descriptorX != null && - descriptorX.NameComparison == descriptorY.NameComparison && - descriptorX.ValueComparison == descriptorY.ValueComparison && - string.Equals(descriptorX.Name, descriptorY.Name, StringComparison.OrdinalIgnoreCase) && - string.Equals(descriptorX.Value, descriptorY.Value, StringComparison.Ordinal); - } - - /// - public virtual int GetHashCode(TagHelperRequiredAttributeDescriptor descriptor) - { - var hashCodeCombiner = HashCodeCombiner.Start(); - hashCodeCombiner.Add(descriptor.NameComparison); - hashCodeCombiner.Add(descriptor.ValueComparison); - hashCodeCombiner.Add(descriptor.Name, StringComparer.OrdinalIgnoreCase); - hashCodeCombiner.Add(descriptor.Value, StringComparer.Ordinal); - - return hashCodeCombiner.CombinedHash; - } - } -} diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/TypeBasedTagHelperDescriptorComparer.cs b/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/TypeBasedTagHelperDescriptorComparer.cs deleted file mode 100644 index 82ab897d63..0000000000 --- a/src/Microsoft.AspNetCore.Razor.Evolution/Legacy/TypeBasedTagHelperDescriptorComparer.cs +++ /dev/null @@ -1,64 +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; -using System.Collections.Generic; -using Microsoft.Extensions.Internal; - -namespace Microsoft.AspNetCore.Razor.Evolution.Legacy -{ - /// - /// An that checks equality between two - /// s using only their s and - /// s. - /// - /// - /// This class is intended for scenarios where Reflection-based information is all important i.e. - /// , , and related - /// properties are not relevant. - /// - internal class TypeBasedTagHelperDescriptorComparer : IEqualityComparer - { - /// - /// A default instance of the . - /// - public static readonly TypeBasedTagHelperDescriptorComparer Default = - new TypeBasedTagHelperDescriptorComparer(); - - private TypeBasedTagHelperDescriptorComparer() - { - } - - /// - /// - /// Determines equality based on and - /// . - /// - public bool Equals(TagHelperDescriptor descriptorX, TagHelperDescriptor descriptorY) - { - if (descriptorX == descriptorY) - { - return true; - } - - return descriptorX != null && - string.Equals(descriptorX.AssemblyName, descriptorY.AssemblyName, StringComparison.Ordinal) && - string.Equals(descriptorX.TypeName, descriptorY.TypeName, StringComparison.Ordinal); - } - - /// - public int GetHashCode(TagHelperDescriptor descriptor) - { - if (descriptor == null) - { - throw new ArgumentNullException(nameof(descriptor)); - } - - var hashCodeCombiner = HashCodeCombiner.Start(); - hashCodeCombiner.Add(descriptor.AssemblyName, StringComparer.Ordinal); - hashCodeCombiner.Add(descriptor.TypeName, StringComparer.Ordinal); - - return hashCodeCombiner; - } - } -} diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/Properties/AssemblyInfo.cs b/src/Microsoft.AspNetCore.Razor.Evolution/Properties/AssemblyInfo.cs index 2043ddeaab..5ab099a91d 100644 --- a/src/Microsoft.AspNetCore.Razor.Evolution/Properties/AssemblyInfo.cs +++ b/src/Microsoft.AspNetCore.Razor.Evolution/Properties/AssemblyInfo.cs @@ -9,5 +9,6 @@ using System.Runtime.CompilerServices; [assembly: InternalsVisibleTo("Microsoft.CodeAnalysis.Razor.Workspaces, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] [assembly: InternalsVisibleTo("Microsoft.CodeAnalysis.Remote.Razor, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] [assembly: InternalsVisibleTo("Microsoft.VisualStudio.LanguageServices.Razor, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] +[assembly: InternalsVisibleTo("Microsoft.VisualStudio.LanguageServices.Razor.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] [assembly: InternalsVisibleTo("Microsoft.VisualStudio.RazorExtension, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")] diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/Properties/LegacyResources.Designer.cs b/src/Microsoft.AspNetCore.Razor.Evolution/Properties/LegacyResources.Designer.cs index 3fa9fc3ed1..49827c79eb 100644 --- a/src/Microsoft.AspNetCore.Razor.Evolution/Properties/LegacyResources.Designer.cs +++ b/src/Microsoft.AspNetCore.Razor.Evolution/Properties/LegacyResources.Designer.cs @@ -15,512 +15,448 @@ namespace Microsoft.AspNetCore.Razor.Evolution /// internal static string Argument_Cannot_Be_Null_Or_Empty { - get { return GetString("Argument_Cannot_Be_Null_Or_Empty"); } + get => GetString("Argument_Cannot_Be_Null_Or_Empty"); } /// /// Value cannot be null or an empty string. /// internal static string FormatArgument_Cannot_Be_Null_Or_Empty() - { - return GetString("Argument_Cannot_Be_Null_Or_Empty"); - } + => GetString("Argument_Cannot_Be_Null_Or_Empty"); /// /// Value must be between {0} and {1}. /// internal static string Argument_Must_Be_Between { - get { return GetString("Argument_Must_Be_Between"); } + get => GetString("Argument_Must_Be_Between"); } /// /// Value must be between {0} and {1}. /// internal static string FormatArgument_Must_Be_Between(object p0, object p1) - { - return string.Format(CultureInfo.CurrentCulture, GetString("Argument_Must_Be_Between"), p0, p1); - } + => string.Format(CultureInfo.CurrentCulture, GetString("Argument_Must_Be_Between"), p0, p1); /// /// Value must be a value from the "{0}" enumeration. /// internal static string Argument_Must_Be_Enum_Member { - get { return GetString("Argument_Must_Be_Enum_Member"); } + get => GetString("Argument_Must_Be_Enum_Member"); } /// /// Value must be a value from the "{0}" enumeration. /// internal static string FormatArgument_Must_Be_Enum_Member(object p0) - { - return string.Format(CultureInfo.CurrentCulture, GetString("Argument_Must_Be_Enum_Member"), p0); - } + => string.Format(CultureInfo.CurrentCulture, GetString("Argument_Must_Be_Enum_Member"), p0); /// /// Value must be greater than {0}. /// internal static string Argument_Must_Be_GreaterThan { - get { return GetString("Argument_Must_Be_GreaterThan"); } + get => GetString("Argument_Must_Be_GreaterThan"); } /// /// Value must be greater than {0}. /// internal static string FormatArgument_Must_Be_GreaterThan(object p0) - { - return string.Format(CultureInfo.CurrentCulture, GetString("Argument_Must_Be_GreaterThan"), p0); - } + => string.Format(CultureInfo.CurrentCulture, GetString("Argument_Must_Be_GreaterThan"), p0); /// /// Value must be greater than or equal to {0}. /// internal static string Argument_Must_Be_GreaterThanOrEqualTo { - get { return GetString("Argument_Must_Be_GreaterThanOrEqualTo"); } + get => GetString("Argument_Must_Be_GreaterThanOrEqualTo"); } /// /// Value must be greater than or equal to {0}. /// internal static string FormatArgument_Must_Be_GreaterThanOrEqualTo(object p0) - { - return string.Format(CultureInfo.CurrentCulture, GetString("Argument_Must_Be_GreaterThanOrEqualTo"), p0); - } + => string.Format(CultureInfo.CurrentCulture, GetString("Argument_Must_Be_GreaterThanOrEqualTo"), p0); /// /// Value must be less than {0}. /// internal static string Argument_Must_Be_LessThan { - get { return GetString("Argument_Must_Be_LessThan"); } + get => GetString("Argument_Must_Be_LessThan"); } /// /// Value must be less than {0}. /// internal static string FormatArgument_Must_Be_LessThan(object p0) - { - return string.Format(CultureInfo.CurrentCulture, GetString("Argument_Must_Be_LessThan"), p0); - } + => string.Format(CultureInfo.CurrentCulture, GetString("Argument_Must_Be_LessThan"), p0); /// /// Value must be less than or equal to {0}. /// internal static string Argument_Must_Be_LessThanOrEqualTo { - get { return GetString("Argument_Must_Be_LessThanOrEqualTo"); } + get => GetString("Argument_Must_Be_LessThanOrEqualTo"); } /// /// Value must be less than or equal to {0}. /// internal static string FormatArgument_Must_Be_LessThanOrEqualTo(object p0) - { - return string.Format(CultureInfo.CurrentCulture, GetString("Argument_Must_Be_LessThanOrEqualTo"), p0); - } + => string.Format(CultureInfo.CurrentCulture, GetString("Argument_Must_Be_LessThanOrEqualTo"), p0); /// /// Value cannot be an empty string. It must either be null or a non-empty string. /// internal static string Argument_Must_Be_Null_Or_Non_Empty { - get { return GetString("Argument_Must_Be_Null_Or_Non_Empty"); } + get => GetString("Argument_Must_Be_Null_Or_Non_Empty"); } /// /// Value cannot be an empty string. It must either be null or a non-empty string. /// internal static string FormatArgument_Must_Be_Null_Or_Non_Empty() - { - return GetString("Argument_Must_Be_Null_Or_Non_Empty"); - } + => GetString("Argument_Must_Be_Null_Or_Non_Empty"); /// /// code /// internal static string BlockName_Code { - get { return GetString("BlockName_Code"); } + get => GetString("BlockName_Code"); } /// /// code /// internal static string FormatBlockName_Code() - { - return GetString("BlockName_Code"); - } + => GetString("BlockName_Code"); /// /// explicit expression /// internal static string BlockName_ExplicitExpression { - get { return GetString("BlockName_ExplicitExpression"); } + get => GetString("BlockName_ExplicitExpression"); } /// /// explicit expression /// internal static string FormatBlockName_ExplicitExpression() - { - return GetString("BlockName_ExplicitExpression"); - } + => GetString("BlockName_ExplicitExpression"); /// /// Block cannot be built because a Type has not been specified in the BlockBuilder /// internal static string Block_Type_Not_Specified { - get { return GetString("Block_Type_Not_Specified"); } + get => GetString("Block_Type_Not_Specified"); } /// /// Block cannot be built because a Type has not been specified in the BlockBuilder /// internal static string FormatBlock_Type_Not_Specified() - { - return GetString("Block_Type_Not_Specified"); - } + => GetString("Block_Type_Not_Specified"); /// /// <<character literal>> /// internal static string CSharpSymbol_CharacterLiteral { - get { return GetString("CSharpSymbol_CharacterLiteral"); } + get => GetString("CSharpSymbol_CharacterLiteral"); } /// /// <<character literal>> /// internal static string FormatCSharpSymbol_CharacterLiteral() - { - return GetString("CSharpSymbol_CharacterLiteral"); - } + => GetString("CSharpSymbol_CharacterLiteral"); /// /// <<comment>> /// internal static string CSharpSymbol_Comment { - get { return GetString("CSharpSymbol_Comment"); } + get => GetString("CSharpSymbol_Comment"); } /// /// <<comment>> /// internal static string FormatCSharpSymbol_Comment() - { - return GetString("CSharpSymbol_Comment"); - } + => GetString("CSharpSymbol_Comment"); /// /// <<identifier>> /// internal static string CSharpSymbol_Identifier { - get { return GetString("CSharpSymbol_Identifier"); } + get => GetString("CSharpSymbol_Identifier"); } /// /// <<identifier>> /// internal static string FormatCSharpSymbol_Identifier() - { - return GetString("CSharpSymbol_Identifier"); - } + => GetString("CSharpSymbol_Identifier"); /// /// <<integer literal>> /// internal static string CSharpSymbol_IntegerLiteral { - get { return GetString("CSharpSymbol_IntegerLiteral"); } + get => GetString("CSharpSymbol_IntegerLiteral"); } /// /// <<integer literal>> /// internal static string FormatCSharpSymbol_IntegerLiteral() - { - return GetString("CSharpSymbol_IntegerLiteral"); - } + => GetString("CSharpSymbol_IntegerLiteral"); /// /// <<keyword>> /// internal static string CSharpSymbol_Keyword { - get { return GetString("CSharpSymbol_Keyword"); } + get => GetString("CSharpSymbol_Keyword"); } /// /// <<keyword>> /// internal static string FormatCSharpSymbol_Keyword() - { - return GetString("CSharpSymbol_Keyword"); - } + => GetString("CSharpSymbol_Keyword"); /// /// <<newline sequence>> /// internal static string CSharpSymbol_Newline { - get { return GetString("CSharpSymbol_Newline"); } + get => GetString("CSharpSymbol_Newline"); } /// /// <<newline sequence>> /// internal static string FormatCSharpSymbol_Newline() - { - return GetString("CSharpSymbol_Newline"); - } + => GetString("CSharpSymbol_Newline"); /// /// <<real literal>> /// internal static string CSharpSymbol_RealLiteral { - get { return GetString("CSharpSymbol_RealLiteral"); } + get => GetString("CSharpSymbol_RealLiteral"); } /// /// <<real literal>> /// internal static string FormatCSharpSymbol_RealLiteral() - { - return GetString("CSharpSymbol_RealLiteral"); - } + => GetString("CSharpSymbol_RealLiteral"); /// /// <<string literal>> /// internal static string CSharpSymbol_StringLiteral { - get { return GetString("CSharpSymbol_StringLiteral"); } + get => GetString("CSharpSymbol_StringLiteral"); } /// /// <<string literal>> /// internal static string FormatCSharpSymbol_StringLiteral() - { - return GetString("CSharpSymbol_StringLiteral"); - } + => GetString("CSharpSymbol_StringLiteral"); /// /// <<white space>> /// internal static string CSharpSymbol_Whitespace { - get { return GetString("CSharpSymbol_Whitespace"); } + get => GetString("CSharpSymbol_Whitespace"); } /// /// <<white space>> /// internal static string FormatCSharpSymbol_Whitespace() - { - return GetString("CSharpSymbol_Whitespace"); - } + => GetString("CSharpSymbol_Whitespace"); /// /// The '{0}' directive expects an identifier. /// internal static string DirectiveExpectsIdentifier { - get { return GetString("DirectiveExpectsIdentifier"); } + get => GetString("DirectiveExpectsIdentifier"); } /// /// The '{0}' directive expects an identifier. /// internal static string FormatDirectiveExpectsIdentifier(object p0) - { - return string.Format(CultureInfo.CurrentCulture, GetString("DirectiveExpectsIdentifier"), p0); - } + => string.Format(CultureInfo.CurrentCulture, GetString("DirectiveExpectsIdentifier"), p0); /// /// The '{0}' directive expects a type name. /// internal static string DirectiveExpectsTypeName { - get { return GetString("DirectiveExpectsTypeName"); } + get => GetString("DirectiveExpectsTypeName"); } /// /// The '{0}' directive expects a type name. /// internal static string FormatDirectiveExpectsTypeName(object p0) - { - return string.Format(CultureInfo.CurrentCulture, GetString("DirectiveExpectsTypeName"), p0); - } + => string.Format(CultureInfo.CurrentCulture, GetString("DirectiveExpectsTypeName"), p0); /// /// "EndBlock" was called without a matching call to "StartBlock". /// internal static string EndBlock_Called_Without_Matching_StartBlock { - get { return GetString("EndBlock_Called_Without_Matching_StartBlock"); } + get => GetString("EndBlock_Called_Without_Matching_StartBlock"); } /// /// "EndBlock" was called without a matching call to "StartBlock". /// internal static string FormatEndBlock_Called_Without_Matching_StartBlock() - { - return GetString("EndBlock_Called_Without_Matching_StartBlock"); - } + => GetString("EndBlock_Called_Without_Matching_StartBlock"); /// /// "{0}" character /// internal static string ErrorComponent_Character { - get { return GetString("ErrorComponent_Character"); } + get => GetString("ErrorComponent_Character"); } /// /// "{0}" character /// internal static string FormatErrorComponent_Character(object p0) - { - return string.Format(CultureInfo.CurrentCulture, GetString("ErrorComponent_Character"), p0); - } + => string.Format(CultureInfo.CurrentCulture, GetString("ErrorComponent_Character"), p0); /// /// end of file /// internal static string ErrorComponent_EndOfFile { - get { return GetString("ErrorComponent_EndOfFile"); } + get => GetString("ErrorComponent_EndOfFile"); } /// /// end of file /// internal static string FormatErrorComponent_EndOfFile() - { - return GetString("ErrorComponent_EndOfFile"); - } + => GetString("ErrorComponent_EndOfFile"); /// /// line break /// internal static string ErrorComponent_Newline { - get { return GetString("ErrorComponent_Newline"); } + get => GetString("ErrorComponent_Newline"); } /// /// line break /// internal static string FormatErrorComponent_Newline() - { - return GetString("ErrorComponent_Newline"); - } + => GetString("ErrorComponent_Newline"); /// /// space or line break /// internal static string ErrorComponent_Whitespace { - get { return GetString("ErrorComponent_Whitespace"); } + get => GetString("ErrorComponent_Whitespace"); } /// /// space or line break /// internal static string FormatErrorComponent_Whitespace() - { - return GetString("ErrorComponent_Whitespace"); - } + => GetString("ErrorComponent_Whitespace"); /// /// <<newline sequence>> /// internal static string HtmlSymbol_NewLine { - get { return GetString("HtmlSymbol_NewLine"); } + get => GetString("HtmlSymbol_NewLine"); } /// /// <<newline sequence>> /// internal static string FormatHtmlSymbol_NewLine() - { - return GetString("HtmlSymbol_NewLine"); - } + => GetString("HtmlSymbol_NewLine"); /// /// <<razor comment>> /// internal static string HtmlSymbol_RazorComment { - get { return GetString("HtmlSymbol_RazorComment"); } + get => GetString("HtmlSymbol_RazorComment"); } /// /// <<razor comment>> /// internal static string FormatHtmlSymbol_RazorComment() - { - return GetString("HtmlSymbol_RazorComment"); - } + => GetString("HtmlSymbol_RazorComment"); /// /// <<text>> /// internal static string HtmlSymbol_Text { - get { return GetString("HtmlSymbol_Text"); } + get => GetString("HtmlSymbol_Text"); } /// /// <<text>> /// internal static string FormatHtmlSymbol_Text() - { - return GetString("HtmlSymbol_Text"); - } + => GetString("HtmlSymbol_Text"); /// /// <<white space>> /// internal static string HtmlSymbol_WhiteSpace { - get { return GetString("HtmlSymbol_WhiteSpace"); } + get => GetString("HtmlSymbol_WhiteSpace"); } /// /// <<white space>> /// internal static string FormatHtmlSymbol_WhiteSpace() - { - return GetString("HtmlSymbol_WhiteSpace"); - } + => GetString("HtmlSymbol_WhiteSpace"); /// /// Cannot use built-in RazorComment handler, language characteristics does not define the CommentStart, CommentStar and CommentBody known symbol types or parser does not override TokenizerBackedParser.OutputSpanBeforeRazorComment /// internal static string Language_Does_Not_Support_RazorComment { - get { return GetString("Language_Does_Not_Support_RazorComment"); } + get => GetString("Language_Does_Not_Support_RazorComment"); } /// /// Cannot use built-in RazorComment handler, language characteristics does not define the CommentStart, CommentStar and CommentBody known symbol types or parser does not override TokenizerBackedParser.OutputSpanBeforeRazorComment /// internal static string FormatLanguage_Does_Not_Support_RazorComment() - { - return GetString("Language_Does_Not_Support_RazorComment"); - } + => GetString("Language_Does_Not_Support_RazorComment"); /// /// The "@" character must be followed by a ":", "(", or a C# identifier. If you intended to switch to markup, use an HTML start tag, for example: @@ -531,7 +467,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution /// internal static string ParseError_AtInCode_Must_Be_Followed_By_Colon_Paren_Or_Identifier_Start { - get { return GetString("ParseError_AtInCode_Must_Be_Followed_By_Colon_Paren_Or_Identifier_Start"); } + get => GetString("ParseError_AtInCode_Must_Be_Followed_By_Colon_Paren_Or_Identifier_Start"); } /// @@ -542,185 +478,161 @@ namespace Microsoft.AspNetCore.Razor.Evolution /// } /// internal static string FormatParseError_AtInCode_Must_Be_Followed_By_Colon_Paren_Or_Identifier_Start() - { - return GetString("ParseError_AtInCode_Must_Be_Followed_By_Colon_Paren_Or_Identifier_Start"); - } + => GetString("ParseError_AtInCode_Must_Be_Followed_By_Colon_Paren_Or_Identifier_Start"); /// /// End of file was reached before the end of the block comment. All comments started with "/*" sequence must be terminated with a matching "*/" sequence. /// internal static string ParseError_BlockComment_Not_Terminated { - get { return GetString("ParseError_BlockComment_Not_Terminated"); } + get => GetString("ParseError_BlockComment_Not_Terminated"); } /// /// End of file was reached before the end of the block comment. All comments started with "/*" sequence must be terminated with a matching "*/" sequence. /// internal static string FormatParseError_BlockComment_Not_Terminated() - { - return GetString("ParseError_BlockComment_Not_Terminated"); - } + => GetString("ParseError_BlockComment_Not_Terminated"); /// /// Directive '{0}' must have a value. /// internal static string ParseError_DirectiveMustHaveValue { - get { return GetString("ParseError_DirectiveMustHaveValue"); } + get => GetString("ParseError_DirectiveMustHaveValue"); } /// /// Directive '{0}' must have a value. /// internal static string FormatParseError_DirectiveMustHaveValue(object p0) - { - return string.Format(CultureInfo.CurrentCulture, GetString("ParseError_DirectiveMustHaveValue"), p0); - } + => string.Format(CultureInfo.CurrentCulture, GetString("ParseError_DirectiveMustHaveValue"), p0); /// /// An opening "{0}" is missing the corresponding closing "{1}". /// internal static string ParseError_Expected_CloseBracket_Before_EOF { - get { return GetString("ParseError_Expected_CloseBracket_Before_EOF"); } + get => GetString("ParseError_Expected_CloseBracket_Before_EOF"); } /// /// An opening "{0}" is missing the corresponding closing "{1}". /// internal static string FormatParseError_Expected_CloseBracket_Before_EOF(object p0, object p1) - { - return string.Format(CultureInfo.CurrentCulture, GetString("ParseError_Expected_CloseBracket_Before_EOF"), p0, p1); - } + => string.Format(CultureInfo.CurrentCulture, GetString("ParseError_Expected_CloseBracket_Before_EOF"), p0, p1); /// /// The {0} block is missing a closing "{1}" character. Make sure you have a matching "{1}" character for all the "{2}" characters within this block, and that none of the "{1}" characters are being interpreted as markup. /// internal static string ParseError_Expected_EndOfBlock_Before_EOF { - get { return GetString("ParseError_Expected_EndOfBlock_Before_EOF"); } + get => GetString("ParseError_Expected_EndOfBlock_Before_EOF"); } /// /// The {0} block is missing a closing "{1}" character. Make sure you have a matching "{1}" character for all the "{2}" characters within this block, and that none of the "{1}" characters are being interpreted as markup. /// internal static string FormatParseError_Expected_EndOfBlock_Before_EOF(object p0, object p1, object p2) - { - return string.Format(CultureInfo.CurrentCulture, GetString("ParseError_Expected_EndOfBlock_Before_EOF"), p0, p1, p2); - } + => string.Format(CultureInfo.CurrentCulture, GetString("ParseError_Expected_EndOfBlock_Before_EOF"), p0, p1, p2); /// /// Expected "{0}". /// internal static string ParseError_Expected_X { - get { return GetString("ParseError_Expected_X"); } + get => GetString("ParseError_Expected_X"); } /// /// Expected "{0}". /// internal static string FormatParseError_Expected_X(object p0) - { - return string.Format(CultureInfo.CurrentCulture, GetString("ParseError_Expected_X"), p0); - } + => string.Format(CultureInfo.CurrentCulture, GetString("ParseError_Expected_X"), p0); /// /// The {0} directive is not supported. /// internal static string ParseError_HelperDirectiveNotAvailable { - get { return GetString("ParseError_HelperDirectiveNotAvailable"); } + get => GetString("ParseError_HelperDirectiveNotAvailable"); } /// /// The {0} directive is not supported. /// internal static string FormatParseError_HelperDirectiveNotAvailable(object p0) - { - return string.Format(CultureInfo.CurrentCulture, GetString("ParseError_HelperDirectiveNotAvailable"), p0); - } + => string.Format(CultureInfo.CurrentCulture, GetString("ParseError_HelperDirectiveNotAvailable"), p0); /// /// Optional quote around the directive '{0}' is missing the corresponding opening or closing quote. /// internal static string ParseError_IncompleteQuotesAroundDirective { - get { return GetString("ParseError_IncompleteQuotesAroundDirective"); } + get => GetString("ParseError_IncompleteQuotesAroundDirective"); } /// /// Optional quote around the directive '{0}' is missing the corresponding opening or closing quote. /// internal static string FormatParseError_IncompleteQuotesAroundDirective(object p0) - { - return string.Format(CultureInfo.CurrentCulture, GetString("ParseError_IncompleteQuotesAroundDirective"), p0); - } + => string.Format(CultureInfo.CurrentCulture, GetString("ParseError_IncompleteQuotesAroundDirective"), p0); /// /// The 'inherits' keyword must be followed by a type name on the same line. /// internal static string ParseError_InheritsKeyword_Must_Be_Followed_By_TypeName { - get { return GetString("ParseError_InheritsKeyword_Must_Be_Followed_By_TypeName"); } + get => GetString("ParseError_InheritsKeyword_Must_Be_Followed_By_TypeName"); } /// /// The 'inherits' keyword must be followed by a type name on the same line. /// internal static string FormatParseError_InheritsKeyword_Must_Be_Followed_By_TypeName() - { - return GetString("ParseError_InheritsKeyword_Must_Be_Followed_By_TypeName"); - } + => GetString("ParseError_InheritsKeyword_Must_Be_Followed_By_TypeName"); /// /// Inline markup blocks (@<p>Content</p>) cannot be nested. Only one level of inline markup is allowed. /// internal static string ParseError_InlineMarkup_Blocks_Cannot_Be_Nested { - get { return GetString("ParseError_InlineMarkup_Blocks_Cannot_Be_Nested"); } + get => GetString("ParseError_InlineMarkup_Blocks_Cannot_Be_Nested"); } /// /// Inline markup blocks (@<p>Content</p>) cannot be nested. Only one level of inline markup is allowed. /// internal static string FormatParseError_InlineMarkup_Blocks_Cannot_Be_Nested() - { - return GetString("ParseError_InlineMarkup_Blocks_Cannot_Be_Nested"); - } + => GetString("ParseError_InlineMarkup_Blocks_Cannot_Be_Nested"); /// /// Markup in a code block must start with a tag and all start tags must be matched with end tags. Do not use unclosed tags like "<br>". Instead use self-closing tags like "<br/>". /// internal static string ParseError_MarkupBlock_Must_Start_With_Tag { - get { return GetString("ParseError_MarkupBlock_Must_Start_With_Tag"); } + get => GetString("ParseError_MarkupBlock_Must_Start_With_Tag"); } /// /// Markup in a code block must start with a tag and all start tags must be matched with end tags. Do not use unclosed tags like "<br>". Instead use self-closing tags like "<br/>". /// internal static string FormatParseError_MarkupBlock_Must_Start_With_Tag() - { - return GetString("ParseError_MarkupBlock_Must_Start_With_Tag"); - } + => GetString("ParseError_MarkupBlock_Must_Start_With_Tag"); /// /// The "{0}" element was not closed. All elements must be either self-closing or have a matching end tag. /// internal static string ParseError_MissingEndTag { - get { return GetString("ParseError_MissingEndTag"); } + get => GetString("ParseError_MissingEndTag"); } /// /// The "{0}" element was not closed. All elements must be either self-closing or have a matching end tag. /// internal static string FormatParseError_MissingEndTag(object p0) - { - return string.Format(CultureInfo.CurrentCulture, GetString("ParseError_MissingEndTag"), p0); - } + => string.Format(CultureInfo.CurrentCulture, GetString("ParseError_MissingEndTag"), p0); /// /// Sections cannot be empty. The "@section" keyword must be followed by a block of markup surrounded by "{}". For example: @@ -731,7 +643,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution /// internal static string ParseError_MissingOpenBraceAfterSection { - get { return GetString("ParseError_MissingOpenBraceAfterSection"); } + get => GetString("ParseError_MissingOpenBraceAfterSection"); } /// @@ -742,9 +654,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution /// } /// internal static string FormatParseError_MissingOpenBraceAfterSection() - { - return GetString("ParseError_MissingOpenBraceAfterSection"); - } + => GetString("ParseError_MissingOpenBraceAfterSection"); /// /// Namespace imports and type aliases cannot be placed within code blocks. They must immediately follow an "@" character in markup. It is recommended that you put them at the top of the page, as in the following example: @@ -756,7 +666,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution /// internal static string ParseError_NamespaceImportAndTypeAlias_Cannot_Exist_Within_CodeBlock { - get { return GetString("ParseError_NamespaceImportAndTypeAlias_Cannot_Exist_Within_CodeBlock"); } + get => GetString("ParseError_NamespaceImportAndTypeAlias_Cannot_Exist_Within_CodeBlock"); } /// @@ -768,73 +678,63 @@ namespace Microsoft.AspNetCore.Razor.Evolution /// } /// internal static string FormatParseError_NamespaceImportAndTypeAlias_Cannot_Exist_Within_CodeBlock() - { - return GetString("ParseError_NamespaceImportAndTypeAlias_Cannot_Exist_Within_CodeBlock"); - } + => GetString("ParseError_NamespaceImportAndTypeAlias_Cannot_Exist_Within_CodeBlock"); /// /// Outer tag is missing a name. The first character of a markup block must be an HTML tag with a valid name. /// internal static string ParseError_OuterTagMissingName { - get { return GetString("ParseError_OuterTagMissingName"); } + get => GetString("ParseError_OuterTagMissingName"); } /// /// Outer tag is missing a name. The first character of a markup block must be an HTML tag with a valid name. /// internal static string FormatParseError_OuterTagMissingName() - { - return GetString("ParseError_OuterTagMissingName"); - } + => GetString("ParseError_OuterTagMissingName"); /// /// End of file was reached before the end of the block comment. All comments that start with the "@*" sequence must be terminated with a matching "*@" sequence. /// internal static string ParseError_RazorComment_Not_Terminated { - get { return GetString("ParseError_RazorComment_Not_Terminated"); } + get => GetString("ParseError_RazorComment_Not_Terminated"); } /// /// End of file was reached before the end of the block comment. All comments that start with the "@*" sequence must be terminated with a matching "*@" sequence. /// internal static string FormatParseError_RazorComment_Not_Terminated() - { - return GetString("ParseError_RazorComment_Not_Terminated"); - } + => GetString("ParseError_RazorComment_Not_Terminated"); /// /// "{0}" is a reserved word and cannot be used in implicit expressions. An explicit expression ("@()") must be used. /// internal static string ParseError_ReservedWord { - get { return GetString("ParseError_ReservedWord"); } + get => GetString("ParseError_ReservedWord"); } /// /// "{0}" is a reserved word and cannot be used in implicit expressions. An explicit expression ("@()") must be used. /// internal static string FormatParseError_ReservedWord(object p0) - { - return string.Format(CultureInfo.CurrentCulture, GetString("ParseError_ReservedWord"), p0); - } + => string.Format(CultureInfo.CurrentCulture, GetString("ParseError_ReservedWord"), p0); /// /// Section blocks ("{0}") cannot be nested. Only one level of section blocks are allowed. /// internal static string ParseError_Sections_Cannot_Be_Nested { - get { return GetString("ParseError_Sections_Cannot_Be_Nested"); } + get => GetString("ParseError_Sections_Cannot_Be_Nested"); } /// /// Section blocks ("{0}") cannot be nested. Only one level of section blocks are allowed. /// internal static string FormatParseError_Sections_Cannot_Be_Nested(object p0) - { - return string.Format(CultureInfo.CurrentCulture, GetString("ParseError_Sections_Cannot_Be_Nested"), p0); - } + => string.Format(CultureInfo.CurrentCulture, GetString("ParseError_Sections_Cannot_Be_Nested"), p0); /// /// Expected a "{0}" but found a "{1}". Block statements must be enclosed in "{{" and "}}". You cannot use single-statement control-flow statements in CSHTML pages. For example, the following is not allowed: @@ -850,7 +750,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution /// internal static string ParseError_SingleLine_ControlFlowStatements_Not_Allowed { - get { return GetString("ParseError_SingleLine_ControlFlowStatements_Not_Allowed"); } + get => GetString("ParseError_SingleLine_ControlFlowStatements_Not_Allowed"); } /// @@ -866,473 +766,413 @@ namespace Microsoft.AspNetCore.Razor.Evolution /// }} /// internal static string FormatParseError_SingleLine_ControlFlowStatements_Not_Allowed(object p0, object p1) - { - return string.Format(CultureInfo.CurrentCulture, GetString("ParseError_SingleLine_ControlFlowStatements_Not_Allowed"), p0, p1); - } + => string.Format(CultureInfo.CurrentCulture, GetString("ParseError_SingleLine_ControlFlowStatements_Not_Allowed"), p0, p1); /// /// "<text>" and "</text>" tags cannot contain attributes. /// internal static string ParseError_TextTagCannotContainAttributes { - get { return GetString("ParseError_TextTagCannotContainAttributes"); } + get => GetString("ParseError_TextTagCannotContainAttributes"); } /// /// "<text>" and "</text>" tags cannot contain attributes. /// internal static string FormatParseError_TextTagCannotContainAttributes() - { - return GetString("ParseError_TextTagCannotContainAttributes"); - } + => GetString("ParseError_TextTagCannotContainAttributes"); /// /// Encountered end tag "{0}" with no matching start tag. Are your start/end tags properly balanced? /// internal static string ParseError_UnexpectedEndTag { - get { return GetString("ParseError_UnexpectedEndTag"); } + get => GetString("ParseError_UnexpectedEndTag"); } /// /// Encountered end tag "{0}" with no matching start tag. Are your start/end tags properly balanced? /// internal static string FormatParseError_UnexpectedEndTag(object p0) - { - return string.Format(CultureInfo.CurrentCulture, GetString("ParseError_UnexpectedEndTag"), p0); - } + => string.Format(CultureInfo.CurrentCulture, GetString("ParseError_UnexpectedEndTag"), p0); /// /// Unexpected {0} after section keyword. Section names must start with an "_" or alphabetic character, and the remaining characters must be either "_" or alphanumeric. /// internal static string ParseError_Unexpected_Character_At_Section_Name_Start { - get { return GetString("ParseError_Unexpected_Character_At_Section_Name_Start"); } + get => GetString("ParseError_Unexpected_Character_At_Section_Name_Start"); } /// /// Unexpected {0} after section keyword. Section names must start with an "_" or alphabetic character, and the remaining characters must be either "_" or alphanumeric. /// internal static string FormatParseError_Unexpected_Character_At_Section_Name_Start(object p0) - { - return string.Format(CultureInfo.CurrentCulture, GetString("ParseError_Unexpected_Character_At_Section_Name_Start"), p0); - } + => string.Format(CultureInfo.CurrentCulture, GetString("ParseError_Unexpected_Character_At_Section_Name_Start"), p0); /// /// "{0}" is not valid at the start of a code block. Only identifiers, keywords, comments, "(" and "{{" are valid. /// internal static string ParseError_Unexpected_Character_At_Start_Of_CodeBlock_CS { - get { return GetString("ParseError_Unexpected_Character_At_Start_Of_CodeBlock_CS"); } + get => GetString("ParseError_Unexpected_Character_At_Start_Of_CodeBlock_CS"); } /// /// "{0}" is not valid at the start of a code block. Only identifiers, keywords, comments, "(" and "{{" are valid. /// internal static string FormatParseError_Unexpected_Character_At_Start_Of_CodeBlock_CS(object p0) - { - return string.Format(CultureInfo.CurrentCulture, GetString("ParseError_Unexpected_Character_At_Start_Of_CodeBlock_CS"), p0); - } + => string.Format(CultureInfo.CurrentCulture, GetString("ParseError_Unexpected_Character_At_Start_Of_CodeBlock_CS"), p0); /// /// End-of-file was found after the "@" character. "@" must be followed by a valid code block. If you want to output an "@", escape it using the sequence: "@@" /// internal static string ParseError_Unexpected_EndOfFile_At_Start_Of_CodeBlock { - get { return GetString("ParseError_Unexpected_EndOfFile_At_Start_Of_CodeBlock"); } + get => GetString("ParseError_Unexpected_EndOfFile_At_Start_Of_CodeBlock"); } /// /// End-of-file was found after the "@" character. "@" must be followed by a valid code block. If you want to output an "@", escape it using the sequence: "@@" /// internal static string FormatParseError_Unexpected_EndOfFile_At_Start_Of_CodeBlock() - { - return GetString("ParseError_Unexpected_EndOfFile_At_Start_Of_CodeBlock"); - } + => GetString("ParseError_Unexpected_EndOfFile_At_Start_Of_CodeBlock"); /// /// End-of-file was found after the "@" character. "@" must be followed by a valid code block. If you want to output an "@", escape it using the sequence: "@@" /// internal static string ParseError_Unexpected_EndOfFile_At_Start_Of_CodeBlock1 { - get { return GetString("ParseError_Unexpected_EndOfFile_At_Start_Of_CodeBlock1"); } + get => GetString("ParseError_Unexpected_EndOfFile_At_Start_Of_CodeBlock1"); } /// /// End-of-file was found after the "@" character. "@" must be followed by a valid code block. If you want to output an "@", escape it using the sequence: "@@" /// internal static string FormatParseError_Unexpected_EndOfFile_At_Start_Of_CodeBlock1() - { - return GetString("ParseError_Unexpected_EndOfFile_At_Start_Of_CodeBlock1"); - } + => GetString("ParseError_Unexpected_EndOfFile_At_Start_Of_CodeBlock1"); /// /// Unexpected "{" after "@" character. Once inside the body of a code block (@if {}, @{}, etc.) you do not need to use "@{" to switch to code. /// internal static string ParseError_Unexpected_Nested_CodeBlock { - get { return GetString("ParseError_Unexpected_Nested_CodeBlock"); } + get => GetString("ParseError_Unexpected_Nested_CodeBlock"); } /// /// Unexpected "{" after "@" character. Once inside the body of a code block (@if {}, @{}, etc.) you do not need to use "@{" to switch to code. /// internal static string FormatParseError_Unexpected_Nested_CodeBlock() - { - return GetString("ParseError_Unexpected_Nested_CodeBlock"); - } + => GetString("ParseError_Unexpected_Nested_CodeBlock"); /// /// A space or line break was encountered after the "@" character. Only valid identifiers, keywords, comments, "(" and "{" are valid at the start of a code block and they must occur immediately following "@" with no space in between. /// internal static string ParseError_Unexpected_WhiteSpace_At_Start_Of_CodeBlock_CS { - get { return GetString("ParseError_Unexpected_WhiteSpace_At_Start_Of_CodeBlock_CS"); } + get => GetString("ParseError_Unexpected_WhiteSpace_At_Start_Of_CodeBlock_CS"); } /// /// A space or line break was encountered after the "@" character. Only valid identifiers, keywords, comments, "(" and "{" are valid at the start of a code block and they must occur immediately following "@" with no space in between. /// internal static string FormatParseError_Unexpected_WhiteSpace_At_Start_Of_CodeBlock_CS() - { - return GetString("ParseError_Unexpected_WhiteSpace_At_Start_Of_CodeBlock_CS"); - } + => GetString("ParseError_Unexpected_WhiteSpace_At_Start_Of_CodeBlock_CS"); /// /// End of file or an unexpected character was reached before the "{0}" tag could be parsed. Elements inside markup blocks must be complete. They must either be self-closing ("<br />") or have matching end tags ("<p>Hello</p>"). If you intended to display a "<" character, use the "&lt;" HTML entity. /// internal static string ParseError_UnfinishedTag { - get { return GetString("ParseError_UnfinishedTag"); } + get => GetString("ParseError_UnfinishedTag"); } /// /// End of file or an unexpected character was reached before the "{0}" tag could be parsed. Elements inside markup blocks must be complete. They must either be self-closing ("<br />") or have matching end tags ("<p>Hello</p>"). If you intended to display a "<" character, use the "&lt;" HTML entity. /// internal static string FormatParseError_UnfinishedTag(object p0) - { - return string.Format(CultureInfo.CurrentCulture, GetString("ParseError_UnfinishedTag"), p0); - } + => string.Format(CultureInfo.CurrentCulture, GetString("ParseError_UnfinishedTag"), p0); /// /// Unterminated string literal. Strings that start with a quotation mark (") must be terminated before the end of the line. However, strings that start with @ and a quotation mark (@") can span multiple lines. /// internal static string ParseError_Unterminated_String_Literal { - get { return GetString("ParseError_Unterminated_String_Literal"); } + get => GetString("ParseError_Unterminated_String_Literal"); } /// /// Unterminated string literal. Strings that start with a quotation mark (") must be terminated before the end of the line. However, strings that start with @ and a quotation mark (@") can span multiple lines. /// internal static string FormatParseError_Unterminated_String_Literal() - { - return GetString("ParseError_Unterminated_String_Literal"); - } + => GetString("ParseError_Unterminated_String_Literal"); /// /// Cannot complete the tree, StartBlock must be called at least once. /// internal static string ParserContext_CannotCompleteTree_NoRootBlock { - get { return GetString("ParserContext_CannotCompleteTree_NoRootBlock"); } + get => GetString("ParserContext_CannotCompleteTree_NoRootBlock"); } /// /// Cannot complete the tree, StartBlock must be called at least once. /// internal static string FormatParserContext_CannotCompleteTree_NoRootBlock() - { - return GetString("ParserContext_CannotCompleteTree_NoRootBlock"); - } + => GetString("ParserContext_CannotCompleteTree_NoRootBlock"); /// /// Cannot complete the tree, there are still open blocks. /// internal static string ParserContext_CannotCompleteTree_OutstandingBlocks { - get { return GetString("ParserContext_CannotCompleteTree_OutstandingBlocks"); } + get => GetString("ParserContext_CannotCompleteTree_OutstandingBlocks"); } /// /// Cannot complete the tree, there are still open blocks. /// internal static string FormatParserContext_CannotCompleteTree_OutstandingBlocks() - { - return GetString("ParserContext_CannotCompleteTree_OutstandingBlocks"); - } + => GetString("ParserContext_CannotCompleteTree_OutstandingBlocks"); /// /// Cannot finish span, there is no current block. Call StartBlock at least once before finishing a span /// internal static string ParserContext_NoCurrentBlock { - get { return GetString("ParserContext_NoCurrentBlock"); } + get => GetString("ParserContext_NoCurrentBlock"); } /// /// Cannot finish span, there is no current block. Call StartBlock at least once before finishing a span /// internal static string FormatParserContext_NoCurrentBlock() - { - return GetString("ParserContext_NoCurrentBlock"); - } + => GetString("ParserContext_NoCurrentBlock"); /// /// Cannot complete action, the parser has finished. Only CompleteParse can be called to extract the final parser results after the parser has finished /// internal static string ParserContext_ParseComplete { - get { return GetString("ParserContext_ParseComplete"); } + get => GetString("ParserContext_ParseComplete"); } /// /// Cannot complete action, the parser has finished. Only CompleteParse can be called to extract the final parser results after the parser has finished /// internal static string FormatParserContext_ParseComplete() - { - return GetString("ParserContext_ParseComplete"); - } + => GetString("ParserContext_ParseComplete"); /// /// Parser was started with a null Context property. The Context property must be set BEFORE calling any methods on the parser. /// internal static string Parser_Context_Not_Set { - get { return GetString("Parser_Context_Not_Set"); } + get => GetString("Parser_Context_Not_Set"); } /// /// Parser was started with a null Context property. The Context property must be set BEFORE calling any methods on the parser. /// internal static string FormatParser_Context_Not_Set() - { - return GetString("Parser_Context_Not_Set"); - } + => GetString("Parser_Context_Not_Set"); /// /// Attribute '{0}' on tag helper element '{1}' requires a value. Tag helper bound attributes of type '{2}' cannot be empty or contain only whitespace. /// internal static string RewriterError_EmptyTagHelperBoundAttribute { - get { return GetString("RewriterError_EmptyTagHelperBoundAttribute"); } + get => GetString("RewriterError_EmptyTagHelperBoundAttribute"); } /// /// Attribute '{0}' on tag helper element '{1}' requires a value. Tag helper bound attributes of type '{2}' cannot be empty or contain only whitespace. /// internal static string FormatRewriterError_EmptyTagHelperBoundAttribute(object p0, object p1, object p2) - { - return string.Format(CultureInfo.CurrentCulture, GetString("RewriterError_EmptyTagHelperBoundAttribute"), p0, p1, p2); - } + => string.Format(CultureInfo.CurrentCulture, GetString("RewriterError_EmptyTagHelperBoundAttribute"), p0, p1, p2); /// /// @section Header { ... } /// internal static string SectionExample_CS { - get { return GetString("SectionExample_CS"); } + get => GetString("SectionExample_CS"); } /// /// @section Header { ... } /// internal static string FormatSectionExample_CS() - { - return GetString("SectionExample_CS"); - } + => GetString("SectionExample_CS"); /// /// Cannot perform '{1}' operations on '{0}' instances with different file paths. /// internal static string SourceLocationFilePathDoesNotMatch { - get { return GetString("SourceLocationFilePathDoesNotMatch"); } + get => GetString("SourceLocationFilePathDoesNotMatch"); } /// /// Cannot perform '{1}' operations on '{0}' instances with different file paths. /// internal static string FormatSourceLocationFilePathDoesNotMatch(object p0, object p1) - { - return string.Format(CultureInfo.CurrentCulture, GetString("SourceLocationFilePathDoesNotMatch"), p0, p1); - } + => string.Format(CultureInfo.CurrentCulture, GetString("SourceLocationFilePathDoesNotMatch"), p0, p1); /// /// <<unknown>> /// internal static string Symbol_Unknown { - get { return GetString("Symbol_Unknown"); } + get => GetString("Symbol_Unknown"); } /// /// <<unknown>> /// internal static string FormatSymbol_Unknown() - { - return GetString("Symbol_Unknown"); - } + => GetString("Symbol_Unknown"); /// /// The tag helper attribute '{0}' in element '{1}' is missing a key. The syntax is '<{1} {0}{{ key }}="value">'. /// internal static string TagHelperBlockRewriter_IndexerAttributeNameMustIncludeKey { - get { return GetString("TagHelperBlockRewriter_IndexerAttributeNameMustIncludeKey"); } + get => GetString("TagHelperBlockRewriter_IndexerAttributeNameMustIncludeKey"); } /// /// The tag helper attribute '{0}' in element '{1}' is missing a key. The syntax is '<{1} {0}{{ key }}="value">'. /// internal static string FormatTagHelperBlockRewriter_IndexerAttributeNameMustIncludeKey(object p0, object p1) - { - return string.Format(CultureInfo.CurrentCulture, GetString("TagHelperBlockRewriter_IndexerAttributeNameMustIncludeKey"), p0, p1); - } + => string.Format(CultureInfo.CurrentCulture, GetString("TagHelperBlockRewriter_IndexerAttributeNameMustIncludeKey"), p0, p1); /// /// TagHelper attributes must be well-formed. /// internal static string TagHelperBlockRewriter_TagHelperAttributeListMustBeWellFormed { - get { return GetString("TagHelperBlockRewriter_TagHelperAttributeListMustBeWellFormed"); } + get => GetString("TagHelperBlockRewriter_TagHelperAttributeListMustBeWellFormed"); } /// /// TagHelper attributes must be well-formed. /// internal static string FormatTagHelperBlockRewriter_TagHelperAttributeListMustBeWellFormed() - { - return GetString("TagHelperBlockRewriter_TagHelperAttributeListMustBeWellFormed"); - } + => GetString("TagHelperBlockRewriter_TagHelperAttributeListMustBeWellFormed"); /// /// The parent <{0}> tag helper does not allow non-tag content. Only child tag helper(s) targeting tag name(s) '{1}' are allowed. /// internal static string TagHelperParseTreeRewriter_CannotHaveNonTagContent { - get { return GetString("TagHelperParseTreeRewriter_CannotHaveNonTagContent"); } + get => GetString("TagHelperParseTreeRewriter_CannotHaveNonTagContent"); } /// /// The parent <{0}> tag helper does not allow non-tag content. Only child tag helper(s) targeting tag name(s) '{1}' are allowed. /// internal static string FormatTagHelperParseTreeRewriter_CannotHaveNonTagContent(object p0, object p1) - { - return string.Format(CultureInfo.CurrentCulture, GetString("TagHelperParseTreeRewriter_CannotHaveNonTagContent"), p0, p1); - } + => string.Format(CultureInfo.CurrentCulture, GetString("TagHelperParseTreeRewriter_CannotHaveNonTagContent"), p0, p1); /// /// Found an end tag (</{0}>) for tag helper '{1}' with tag structure that disallows an end tag ('{2}'). /// internal static string TagHelperParseTreeRewriter_EndTagTagHelperMustNotHaveAnEndTag { - get { return GetString("TagHelperParseTreeRewriter_EndTagTagHelperMustNotHaveAnEndTag"); } + get => GetString("TagHelperParseTreeRewriter_EndTagTagHelperMustNotHaveAnEndTag"); } /// /// Found an end tag (</{0}>) for tag helper '{1}' with tag structure that disallows an end tag ('{2}'). /// internal static string FormatTagHelperParseTreeRewriter_EndTagTagHelperMustNotHaveAnEndTag(object p0, object p1, object p2) - { - return string.Format(CultureInfo.CurrentCulture, GetString("TagHelperParseTreeRewriter_EndTagTagHelperMustNotHaveAnEndTag"), p0, p1, p2); - } + => string.Format(CultureInfo.CurrentCulture, GetString("TagHelperParseTreeRewriter_EndTagTagHelperMustNotHaveAnEndTag"), p0, p1, p2); /// /// Tag helpers '{0}' and '{1}' targeting element '{2}' must not expect different {3} values. /// internal static string TagHelperParseTreeRewriter_InconsistentTagStructure { - get { return GetString("TagHelperParseTreeRewriter_InconsistentTagStructure"); } + get => GetString("TagHelperParseTreeRewriter_InconsistentTagStructure"); } /// /// Tag helpers '{0}' and '{1}' targeting element '{2}' must not expect different {3} values. /// internal static string FormatTagHelperParseTreeRewriter_InconsistentTagStructure(object p0, object p1, object p2, object p3) - { - return string.Format(CultureInfo.CurrentCulture, GetString("TagHelperParseTreeRewriter_InconsistentTagStructure"), p0, p1, p2, p3); - } + => string.Format(CultureInfo.CurrentCulture, GetString("TagHelperParseTreeRewriter_InconsistentTagStructure"), p0, p1, p2, p3); /// /// The <{0}> tag is not allowed by parent <{1}> tag helper. Only child tags with name(s) '{2}' are allowed. /// internal static string TagHelperParseTreeRewriter_InvalidNestedTag { - get { return GetString("TagHelperParseTreeRewriter_InvalidNestedTag"); } + get => GetString("TagHelperParseTreeRewriter_InvalidNestedTag"); } /// /// The <{0}> tag is not allowed by parent <{1}> tag helper. Only child tags with name(s) '{2}' are allowed. /// internal static string FormatTagHelperParseTreeRewriter_InvalidNestedTag(object p0, object p1, object p2) - { - return string.Format(CultureInfo.CurrentCulture, GetString("TagHelperParseTreeRewriter_InvalidNestedTag"), p0, p1, p2); - } + => string.Format(CultureInfo.CurrentCulture, GetString("TagHelperParseTreeRewriter_InvalidNestedTag"), p0, p1, p2); /// /// Found a malformed '{0}' tag helper. Tag helpers must have a start and end tag or be self closing. /// internal static string TagHelpersParseTreeRewriter_FoundMalformedTagHelper { - get { return GetString("TagHelpersParseTreeRewriter_FoundMalformedTagHelper"); } + get => GetString("TagHelpersParseTreeRewriter_FoundMalformedTagHelper"); } /// /// Found a malformed '{0}' tag helper. Tag helpers must have a start and end tag or be self closing. /// internal static string FormatTagHelpersParseTreeRewriter_FoundMalformedTagHelper(object p0) - { - return string.Format(CultureInfo.CurrentCulture, GetString("TagHelpersParseTreeRewriter_FoundMalformedTagHelper"), p0); - } + => string.Format(CultureInfo.CurrentCulture, GetString("TagHelpersParseTreeRewriter_FoundMalformedTagHelper"), p0); /// /// Missing close angle for tag helper '{0}'. /// internal static string TagHelpersParseTreeRewriter_MissingCloseAngle { - get { return GetString("TagHelpersParseTreeRewriter_MissingCloseAngle"); } + get => GetString("TagHelpersParseTreeRewriter_MissingCloseAngle"); } /// /// Missing close angle for tag helper '{0}'. /// internal static string FormatTagHelpersParseTreeRewriter_MissingCloseAngle(object p0) - { - return string.Format(CultureInfo.CurrentCulture, GetString("TagHelpersParseTreeRewriter_MissingCloseAngle"), p0); - } + => string.Format(CultureInfo.CurrentCulture, GetString("TagHelpersParseTreeRewriter_MissingCloseAngle"), p0); /// /// Tag Helper '{0}'s attributes must have names. /// internal static string TagHelpers_AttributesMustHaveAName { - get { return GetString("TagHelpers_AttributesMustHaveAName"); } + get => GetString("TagHelpers_AttributesMustHaveAName"); } /// /// Tag Helper '{0}'s attributes must have names. /// internal static string FormatTagHelpers_AttributesMustHaveAName(object p0) - { - return string.Format(CultureInfo.CurrentCulture, GetString("TagHelpers_AttributesMustHaveAName"), p0); - } + => string.Format(CultureInfo.CurrentCulture, GetString("TagHelpers_AttributesMustHaveAName"), p0); /// /// The tag helper '{0}' must not have C# in the element's attribute declaration area. /// internal static string TagHelpers_CannotHaveCSharpInTagDeclaration { - get { return GetString("TagHelpers_CannotHaveCSharpInTagDeclaration"); } + get => GetString("TagHelpers_CannotHaveCSharpInTagDeclaration"); } /// /// The tag helper '{0}' must not have C# in the element's attribute declaration area. /// internal static string FormatTagHelpers_CannotHaveCSharpInTagDeclaration(object p0) - { - return string.Format(CultureInfo.CurrentCulture, GetString("TagHelpers_CannotHaveCSharpInTagDeclaration"), p0); - } + => string.Format(CultureInfo.CurrentCulture, GetString("TagHelpers_CannotHaveCSharpInTagDeclaration"), p0); /// /// Code blocks (e.g. @{{var variable = 23;}}) must not appear in non-string tag helper attribute values. @@ -1340,7 +1180,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution /// internal static string TagHelpers_CodeBlocks_NotSupported_InAttributes { - get { return GetString("TagHelpers_CodeBlocks_NotSupported_InAttributes"); } + get => GetString("TagHelpers_CodeBlocks_NotSupported_InAttributes"); } /// @@ -1348,9 +1188,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution /// Already in an expression (code) context. If necessary an explicit expression (e.g. @(@readonly)) may be used. /// internal static string FormatTagHelpers_CodeBlocks_NotSupported_InAttributes() - { - return GetString("TagHelpers_CodeBlocks_NotSupported_InAttributes"); - } + => GetString("TagHelpers_CodeBlocks_NotSupported_InAttributes"); /// /// Inline markup blocks (e.g. @<p>content</p>) must not appear in non-string tag helper attribute values. @@ -1358,7 +1196,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution /// internal static string TagHelpers_InlineMarkupBlocks_NotSupported_InAttributes { - get { return GetString("TagHelpers_InlineMarkupBlocks_NotSupported_InAttributes"); } + get => GetString("TagHelpers_InlineMarkupBlocks_NotSupported_InAttributes"); } /// @@ -1366,57 +1204,49 @@ namespace Microsoft.AspNetCore.Razor.Evolution /// Expected a '{0}' attribute value, not a string. /// internal static string FormatTagHelpers_InlineMarkupBlocks_NotSupported_InAttributes(object p0) - { - return string.Format(CultureInfo.CurrentCulture, GetString("TagHelpers_InlineMarkupBlocks_NotSupported_InAttributes"), p0); - } + => string.Format(CultureInfo.CurrentCulture, GetString("TagHelpers_InlineMarkupBlocks_NotSupported_InAttributes"), p0); /// /// In order to put a symbol back, it must have been the symbol which ended at the current position. The specified symbol ends at {0}, but the current position is {1} /// internal static string TokenizerView_CannotPutBack { - get { return GetString("TokenizerView_CannotPutBack"); } + get => GetString("TokenizerView_CannotPutBack"); } /// /// In order to put a symbol back, it must have been the symbol which ended at the current position. The specified symbol ends at {0}, but the current position is {1} /// internal static string FormatTokenizerView_CannotPutBack(object p0, object p1) - { - return string.Format(CultureInfo.CurrentCulture, GetString("TokenizerView_CannotPutBack"), p0, p1); - } + => string.Format(CultureInfo.CurrentCulture, GetString("TokenizerView_CannotPutBack"), p0, p1); /// /// Unexpected literal following the '{0}' directive. Expected '{1}'. /// internal static string UnexpectedDirectiveLiteral { - get { return GetString("UnexpectedDirectiveLiteral"); } + get => GetString("UnexpectedDirectiveLiteral"); } /// /// Unexpected literal following the '{0}' directive. Expected '{1}'. /// internal static string FormatUnexpectedDirectiveLiteral(object p0, object p1) - { - return string.Format(CultureInfo.CurrentCulture, GetString("UnexpectedDirectiveLiteral"), p0, p1); - } + => string.Format(CultureInfo.CurrentCulture, GetString("UnexpectedDirectiveLiteral"), p0, p1); /// /// Unexpected end of file following the '{0}' directive. Expected '{1}'. /// internal static string UnexpectedEOFAfterDirective { - get { return GetString("UnexpectedEOFAfterDirective"); } + get => GetString("UnexpectedEOFAfterDirective"); } /// /// Unexpected end of file following the '{0}' directive. Expected '{1}'. /// internal static string FormatUnexpectedEOFAfterDirective(object p0, object p1) - { - return string.Format(CultureInfo.CurrentCulture, GetString("UnexpectedEOFAfterDirective"), p0, p1); - } + => string.Format(CultureInfo.CurrentCulture, GetString("UnexpectedEOFAfterDirective"), p0, p1); private static string GetString(string name, params string[] formatterNames) { diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/Properties/Resources.Designer.cs b/src/Microsoft.AspNetCore.Razor.Evolution/Properties/Resources.Designer.cs index 9ec06f2f43..dc78770483 100644 --- a/src/Microsoft.AspNetCore.Razor.Evolution/Properties/Resources.Designer.cs +++ b/src/Microsoft.AspNetCore.Razor.Evolution/Properties/Resources.Designer.cs @@ -15,225 +15,379 @@ namespace Microsoft.AspNetCore.Razor.Evolution /// internal static string ArgumentCannotBeNullOrEmpty { - get { return GetString("ArgumentCannotBeNullOrEmpty"); } + get => GetString("ArgumentCannotBeNullOrEmpty"); } /// /// Value cannot be null or an empty string. /// internal static string FormatArgumentCannotBeNullOrEmpty() - { - return GetString("ArgumentCannotBeNullOrEmpty"); - } + => GetString("ArgumentCannotBeNullOrEmpty"); /// /// The '{0}' feature requires a '{1}' provided by the '{2}'. /// internal static string FeatureDependencyMissing { - get { return GetString("FeatureDependencyMissing"); } + get => GetString("FeatureDependencyMissing"); } /// /// The '{0}' feature requires a '{1}' provided by the '{2}'. /// internal static string FormatFeatureDependencyMissing(object p0, object p1, object p2) - { - return string.Format(CultureInfo.CurrentCulture, GetString("FeatureDependencyMissing"), p0, p1, p2); - } + => string.Format(CultureInfo.CurrentCulture, GetString("FeatureDependencyMissing"), p0, p1, p2); /// /// Invalid tag helper directive look up text '{0}'. The correct look up text format is: "typeName, assemblyName". /// internal static string InvalidTagHelperLookupText { - get { return GetString("InvalidTagHelperLookupText"); } + get => GetString("InvalidTagHelperLookupText"); } /// /// Invalid tag helper directive look up text '{0}'. The correct look up text format is: "typeName, assemblyName". /// internal static string FormatInvalidTagHelperLookupText(object p0) - { - return string.Format(CultureInfo.CurrentCulture, GetString("InvalidTagHelperLookupText"), p0); - } + => string.Format(CultureInfo.CurrentCulture, GetString("InvalidTagHelperLookupText"), p0); /// /// Invalid tag helper directive '{0}' value. '{1}' is not allowed in prefix '{2}'. /// internal static string InvalidTagHelperPrefixValue { - get { return GetString("InvalidTagHelperPrefixValue"); } + get => GetString("InvalidTagHelperPrefixValue"); } /// /// Invalid tag helper directive '{0}' value. '{1}' is not allowed in prefix '{2}'. /// internal static string FormatInvalidTagHelperPrefixValue(object p0, object p1, object p2) - { - return string.Format(CultureInfo.CurrentCulture, GetString("InvalidTagHelperPrefixValue"), p0, p1, p2); - } + => string.Format(CultureInfo.CurrentCulture, GetString("InvalidTagHelperPrefixValue"), p0, p1, p2); /// /// The '{0}' operation is not valid when the builder is empty. /// internal static string IRBuilder_PopInvalid { - get { return GetString("IRBuilder_PopInvalid"); } + get => GetString("IRBuilder_PopInvalid"); } /// /// The '{0}' operation is not valid when the builder is empty. /// internal static string FormatIRBuilder_PopInvalid(object p0) - { - return string.Format(CultureInfo.CurrentCulture, GetString("IRBuilder_PopInvalid"), p0); - } + => string.Format(CultureInfo.CurrentCulture, GetString("IRBuilder_PopInvalid"), p0); /// /// The specified encoding '{0}' does not match the content's encoding '{1}'. /// internal static string MismatchedContentEncoding { - get { return GetString("MismatchedContentEncoding"); } + get => GetString("MismatchedContentEncoding"); } /// /// The specified encoding '{0}' does not match the content's encoding '{1}'. /// internal static string FormatMismatchedContentEncoding(object p0, object p1) - { - return string.Format(CultureInfo.CurrentCulture, GetString("MismatchedContentEncoding"), p0, p1); - } + => string.Format(CultureInfo.CurrentCulture, GetString("MismatchedContentEncoding"), p0, p1); /// /// The '{0}' phase requires a '{1}' provided by the '{2}'. /// internal static string PhaseDependencyMissing { - get { return GetString("PhaseDependencyMissing"); } + get => GetString("PhaseDependencyMissing"); } /// /// The '{0}' phase requires a '{1}' provided by the '{2}'. /// internal static string FormatPhaseDependencyMissing(object p0, object p1, object p2) - { - return string.Format(CultureInfo.CurrentCulture, GetString("PhaseDependencyMissing"), p0, p1, p2); - } + => string.Format(CultureInfo.CurrentCulture, GetString("PhaseDependencyMissing"), p0, p1, p2); /// /// The phase must be initialized by setting the '{0}' property. /// internal static string PhaseMustBeInitialized { - get { return GetString("PhaseMustBeInitialized"); } + get => GetString("PhaseMustBeInitialized"); } /// /// The phase must be initialized by setting the '{0}' property. /// internal static string FormatPhaseMustBeInitialized(object p0) - { - return string.Format(CultureInfo.CurrentCulture, GetString("PhaseMustBeInitialized"), p0); - } + => string.Format(CultureInfo.CurrentCulture, GetString("PhaseMustBeInitialized"), p0); /// /// Tag helper directive assembly name cannot be null or empty. /// internal static string TagHelperAssemblyNameCannotBeEmptyOrNull { - get { return GetString("TagHelperAssemblyNameCannotBeEmptyOrNull"); } + get => GetString("TagHelperAssemblyNameCannotBeEmptyOrNull"); } /// /// Tag helper directive assembly name cannot be null or empty. /// internal static string FormatTagHelperAssemblyNameCannotBeEmptyOrNull() - { - return GetString("TagHelperAssemblyNameCannotBeEmptyOrNull"); - } + => GetString("TagHelperAssemblyNameCannotBeEmptyOrNull"); /// /// The assembly '{0}' could not be resolved or contains no tag helpers. /// internal static string TagHelperAssemblyCouldNotBeResolved { - get { return GetString("TagHelperAssemblyCouldNotBeResolved"); } + get => GetString("TagHelperAssemblyCouldNotBeResolved"); } /// /// The assembly '{0}' could not be resolved or contains no tag helpers. /// internal static string FormatTagHelperAssemblyCouldNotBeResolved(object p0) - { - return string.Format(CultureInfo.CurrentCulture, GetString("TagHelperAssemblyCouldNotBeResolved"), p0); - } + => string.Format(CultureInfo.CurrentCulture, GetString("TagHelperAssemblyCouldNotBeResolved"), p0); /// /// Path must begin with a forward slash '/'. /// internal static string RazorProject_PathMustStartWithForwardSlash { - get { return GetString("RazorProject_PathMustStartWithForwardSlash"); } + get => GetString("RazorProject_PathMustStartWithForwardSlash"); } /// /// Path must begin with a forward slash '/'. /// internal static string FormatRazorProject_PathMustStartWithForwardSlash() - { - return GetString("RazorProject_PathMustStartWithForwardSlash"); - } + => GetString("RazorProject_PathMustStartWithForwardSlash"); /// /// The method '{0}' has already been invoked. /// internal static string DirectiveDescriptor_BeginOptionalsAlreadyInvoked { - get { return GetString("DirectiveDescriptor_BeginOptionalsAlreadyInvoked"); } + get => GetString("DirectiveDescriptor_BeginOptionalsAlreadyInvoked"); } /// /// The method '{0}' has already been invoked. /// internal static string FormatDirectiveDescriptor_BeginOptionalsAlreadyInvoked(object p0) - { - return string.Format(CultureInfo.CurrentCulture, GetString("DirectiveDescriptor_BeginOptionalsAlreadyInvoked"), p0); - } + => string.Format(CultureInfo.CurrentCulture, GetString("DirectiveDescriptor_BeginOptionalsAlreadyInvoked"), p0); /// /// The document of kind '{0}' does not have a '{1}'. The document classifier must set a value for '{2}'. /// internal static string DocumentMissingTarget { - get { return GetString("DocumentMissingTarget"); } + get => GetString("DocumentMissingTarget"); } /// /// The document of kind '{0}' does not have a '{1}'. The document classifier must set a value for '{2}'. /// internal static string FormatDocumentMissingTarget(object p0, object p1, object p2) - { - return string.Format(CultureInfo.CurrentCulture, GetString("DocumentMissingTarget"), p0, p1, p2); - } + => string.Format(CultureInfo.CurrentCulture, GetString("DocumentMissingTarget"), p0, p1, p2); /// /// The item '{0}' could not be found. /// internal static string RazorTemplateEngine_ItemCouldNotBeFound { - get { return GetString("RazorTemplateEngine_ItemCouldNotBeFound"); } + get => GetString("RazorTemplateEngine_ItemCouldNotBeFound"); } /// /// The item '{0}' could not be found. /// internal static string FormatRazorTemplateEngine_ItemCouldNotBeFound(object p0) + => string.Format(CultureInfo.CurrentCulture, GetString("RazorTemplateEngine_ItemCouldNotBeFound"), p0); + + /// + /// Invalid tag helper bound property '{0}.{1}'. Tag helpers cannot bind to HTML attributes with name '{2}' because the name contains a '{3}' character. + /// + internal static string InvalidBoundAttributeName { - return string.Format(CultureInfo.CurrentCulture, GetString("RazorTemplateEngine_ItemCouldNotBeFound"), p0); + get => GetString("InvalidBoundAttributeName"); } + /// + /// Invalid tag helper bound property '{0}.{1}'. Tag helpers cannot bind to HTML attributes with name '{2}' because the name contains a '{3}' character. + /// + internal static string FormatInvalidBoundAttributeName(object p0, object p1, object p2, object p3) + => string.Format(CultureInfo.CurrentCulture, GetString("InvalidBoundAttributeName"), p0, p1, p2, p3); + + /// + /// Invalid tag helper bound property '{0}.{1}'. Tag helpers cannot bind to HTML attributes with name '{2}' because the name starts with '{3}'. + /// + internal static string InvalidBoundAttributeNameStartsWith + { + get => GetString("InvalidBoundAttributeNameStartsWith"); + } + + /// + /// Invalid tag helper bound property '{0}.{1}'. Tag helpers cannot bind to HTML attributes with name '{2}' because the name starts with '{3}'. + /// + internal static string FormatInvalidBoundAttributeNameStartsWith(object p0, object p1, object p2, object p3) + => string.Format(CultureInfo.CurrentCulture, GetString("InvalidBoundAttributeNameStartsWith"), p0, p1, p2, p3); + + /// + /// Invalid tag helper bound property '{0}.{1}'. Tag helpers cannot bind to HTML attributes with a null or empty name. + /// + internal static string InvalidBoundAttributeNullOrWhitespace + { + get => GetString("InvalidBoundAttributeNullOrWhitespace"); + } + + /// + /// Invalid tag helper bound property '{0}.{1}'. Tag helpers cannot bind to HTML attributes with a null or empty name. + /// + internal static string FormatInvalidBoundAttributeNullOrWhitespace(object p0, object p1) + => string.Format(CultureInfo.CurrentCulture, GetString("InvalidBoundAttributeNullOrWhitespace"), p0, p1); + + /// + /// Invalid tag helper bound property '{0}.{1}'. Tag helpers cannot bind to HTML attributes with prefix '{2}' because the prefix contains a '{3}' character. + /// + internal static string InvalidBoundAttributePrefix + { + get => GetString("InvalidBoundAttributePrefix"); + } + + /// + /// Invalid tag helper bound property '{0}.{1}'. Tag helpers cannot bind to HTML attributes with prefix '{2}' because the prefix contains a '{3}' character. + /// + internal static string FormatInvalidBoundAttributePrefix(object p0, object p1, object p2, object p3) + => string.Format(CultureInfo.CurrentCulture, GetString("InvalidBoundAttributePrefix"), p0, p1, p2, p3); + + /// + /// Invalid tag helper bound property '{0}.{1}'. Tag helpers cannot bind to HTML attributes with prefix '{2}' because the prefix starts with '{3}'. + /// + internal static string InvalidBoundAttributePrefixStartsWith + { + get => GetString("InvalidBoundAttributePrefixStartsWith"); + } + + /// + /// Invalid tag helper bound property '{0}.{1}'. Tag helpers cannot bind to HTML attributes with prefix '{2}' because the prefix starts with '{3}'. + /// + internal static string FormatInvalidBoundAttributePrefixStartsWith(object p0, object p1, object p2, object p3) + => string.Format(CultureInfo.CurrentCulture, GetString("InvalidBoundAttributePrefixStartsWith"), p0, p1, p2, p3); + + /// + /// Invalid restricted child '{0}' for tag helper '{1}'. Tag helpers cannot restrict child elements that contain a '{2}' character. + /// + internal static string InvalidRestrictedChild + { + get => GetString("InvalidRestrictedChild"); + } + + /// + /// Invalid restricted child '{0}' for tag helper '{1}'. Tag helpers cannot restrict child elements that contain a '{2}' character. + /// + internal static string FormatInvalidRestrictedChild(object p0, object p1, object p2) + => string.Format(CultureInfo.CurrentCulture, GetString("InvalidRestrictedChild"), p0, p1, p2); + + /// + /// Invalid restricted child for tag helper '{0}'. Name cannot be null or whitespace. + /// + internal static string InvalidRestrictedChildNullOrWhitespace + { + get => GetString("InvalidRestrictedChildNullOrWhitespace"); + } + + /// + /// Invalid restricted child for tag helper '{0}'. Name cannot be null or whitespace. + /// + internal static string FormatInvalidRestrictedChildNullOrWhitespace(object p0) + => string.Format(CultureInfo.CurrentCulture, GetString("InvalidRestrictedChildNullOrWhitespace"), p0); + + /// + /// Tag helpers cannot target attribute name '{0}' because it contains a '{1}' character. + /// + internal static string InvalidTargetedAttributeName + { + get => GetString("InvalidTargetedAttributeName"); + } + + /// + /// Tag helpers cannot target attribute name '{0}' because it contains a '{1}' character. + /// + internal static string FormatInvalidTargetedAttributeName(object p0, object p1) + => string.Format(CultureInfo.CurrentCulture, GetString("InvalidTargetedAttributeName"), p0, p1); + + /// + /// Targeted attribute name cannot be null or whitespace. + /// + internal static string InvalidTargetedAttributeNameNullOrWhitespace + { + get => GetString("InvalidTargetedAttributeNameNullOrWhitespace"); + } + + /// + /// Targeted attribute name cannot be null or whitespace. + /// + internal static string FormatInvalidTargetedAttributeNameNullOrWhitespace() + => GetString("InvalidTargetedAttributeNameNullOrWhitespace"); + + /// + /// Tag helpers cannot target parent tag name '{0}' because it contains a '{1}' character. + /// + internal static string InvalidTargetedParentTagName + { + get => GetString("InvalidTargetedParentTagName"); + } + + /// + /// Tag helpers cannot target parent tag name '{0}' because it contains a '{1}' character. + /// + internal static string FormatInvalidTargetedParentTagName(object p0, object p1) + => string.Format(CultureInfo.CurrentCulture, GetString("InvalidTargetedParentTagName"), p0, p1); + + /// + /// Targeted parent tag name cannot be null or whitespace. + /// + internal static string InvalidTargetedParentTagNameNullOrWhitespace + { + get => GetString("InvalidTargetedParentTagNameNullOrWhitespace"); + } + + /// + /// Targeted parent tag name cannot be null or whitespace. + /// + internal static string FormatInvalidTargetedParentTagNameNullOrWhitespace() + => GetString("InvalidTargetedParentTagNameNullOrWhitespace"); + + /// + /// Tag helpers cannot target tag name '{0}' because it contains a '{1}' character. + /// + internal static string InvalidTargetedTagName + { + get => GetString("InvalidTargetedTagName"); + } + + /// + /// Tag helpers cannot target tag name '{0}' because it contains a '{1}' character. + /// + internal static string FormatInvalidTargetedTagName(object p0, object p1) + => string.Format(CultureInfo.CurrentCulture, GetString("InvalidTargetedTagName"), p0, p1); + + /// + /// Targeted tag name cannot be null or whitespace. + /// + internal static string InvalidTargetedTagNameNullOrWhitespace + { + get => GetString("InvalidTargetedTagNameNullOrWhitespace"); + } + + /// + /// Targeted tag name cannot be null or whitespace. + /// + internal static string FormatInvalidTargetedTagNameNullOrWhitespace() + => GetString("InvalidTargetedTagNameNullOrWhitespace"); + private static string GetString(string name, params string[] formatterNames) { var value = _resourceManager.GetString(name); diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/RazorCodeDocumentExtensions.cs b/src/Microsoft.AspNetCore.Razor.Evolution/RazorCodeDocumentExtensions.cs index 6e6416289c..ea2e4dce74 100644 --- a/src/Microsoft.AspNetCore.Razor.Evolution/RazorCodeDocumentExtensions.cs +++ b/src/Microsoft.AspNetCore.Razor.Evolution/RazorCodeDocumentExtensions.cs @@ -9,6 +9,28 @@ namespace Microsoft.AspNetCore.Razor.Evolution { public static class RazorCodeDocumentExtensions { + private static object TagHelperPrefixKey = new object(); + + public static string GetTagHelperPrefix(this RazorCodeDocument document) + { + if (document == null) + { + throw new ArgumentNullException(nameof(document)); + } + + return document.Items[TagHelperPrefixKey] as string; + } + + public static void SetTagHelperPrefix(this RazorCodeDocument document, string tagHelperPrefix) + { + if (document == null) + { + throw new ArgumentNullException(nameof(document)); + } + + document.Items[TagHelperPrefixKey] = tagHelperPrefix; + } + public static RazorSyntaxTree GetSyntaxTree(this RazorCodeDocument document) { if (document == null) diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/RazorDiagnosticDescriptor.cs b/src/Microsoft.AspNetCore.Razor.Evolution/RazorDiagnosticDescriptor.cs index e29e48cea9..8749b74f1e 100644 --- a/src/Microsoft.AspNetCore.Razor.Evolution/RazorDiagnosticDescriptor.cs +++ b/src/Microsoft.AspNetCore.Razor.Evolution/RazorDiagnosticDescriptor.cs @@ -6,7 +6,7 @@ using System.Diagnostics; namespace Microsoft.AspNetCore.Razor.Evolution { - [DebuggerDisplay("{Error} {Id}: {GetMessageFormat()}")] + [DebuggerDisplay("Error {Id}: {GetMessageFormat()}")] public sealed class RazorDiagnosticDescriptor : IEquatable { private readonly Func _messageFormat; diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/RazorDiagnosticFactory.cs b/src/Microsoft.AspNetCore.Razor.Evolution/RazorDiagnosticFactory.cs new file mode 100644 index 0000000000..23ad044a5a --- /dev/null +++ b/src/Microsoft.AspNetCore.Razor.Evolution/RazorDiagnosticFactory.cs @@ -0,0 +1,268 @@ +// 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.Evolution; + +namespace Microsoft.AspNetCore.Razor.Evolution +{ + internal static class RazorDiagnosticFactory + { + private const string DiagnosticPrefix = "RZ"; + + #region General Errors + + /* + * General Errors ID Offset = 0 + */ + + #endregion + + #region Language Errors + + /* + * Language Errors ID Offset = 1000 + */ + + #endregion + + #region Semantic Errors + + /* + * Semantic Errors ID Offset = 2000 + */ + + #endregion + + #region TagHelper Errors + + /* + * TagHelper Errors ID Offset = 3000 + */ + + private static readonly RazorDiagnosticDescriptor TagHelper_InvalidRestrictedChildNullOrWhitespace = + new RazorDiagnosticDescriptor( + $"{DiagnosticPrefix}3000", + () => Resources.InvalidRestrictedChildNullOrWhitespace, + RazorDiagnosticSeverity.Error); + public static RazorDiagnostic CreateTagHelper_InvalidRestrictedChildNullOrWhitespace(string tagHelperType) + { + var diagnostic = RazorDiagnostic.Create( + TagHelper_InvalidRestrictedChildNullOrWhitespace, + new SourceSpan(SourceLocation.Undefined, contentLength: 0), + tagHelperType); + + return diagnostic; + } + + private static readonly RazorDiagnosticDescriptor TagHelper_InvalidRestrictedChild = + new RazorDiagnosticDescriptor( + $"{DiagnosticPrefix}3001", + () => Resources.InvalidRestrictedChild, + RazorDiagnosticSeverity.Error); + public static RazorDiagnostic CreateTagHelper_InvalidRestrictedChild(string restrictedChild, string tagHelperType, char invalidCharacter) + { + var diagnostic = RazorDiagnostic.Create( + TagHelper_InvalidRestrictedChild, + new SourceSpan(SourceLocation.Undefined, contentLength: 0), + restrictedChild, + tagHelperType, + invalidCharacter); + + return diagnostic; + } + + private static readonly RazorDiagnosticDescriptor TagHelper_InvalidBoundAttributeNullOrWhitespace = + new RazorDiagnosticDescriptor( + $"{DiagnosticPrefix}3002", + () => Resources.InvalidBoundAttributeNullOrWhitespace, + RazorDiagnosticSeverity.Error); + public static RazorDiagnostic CreateTagHelper_InvalidBoundAttributeNullOrWhitespace(string containingTypeName, string propertyName) + { + var diagnostic = RazorDiagnostic.Create( + TagHelper_InvalidBoundAttributeNullOrWhitespace, + new SourceSpan(SourceLocation.Undefined, contentLength: 0), + containingTypeName, + propertyName); + + return diagnostic; + } + + private static readonly RazorDiagnosticDescriptor TagHelper_InvalidBoundAttributeName = + new RazorDiagnosticDescriptor( + $"{DiagnosticPrefix}3003", + () => Resources.InvalidBoundAttributeName, + RazorDiagnosticSeverity.Error); + public static RazorDiagnostic CreateTagHelper_InvalidBoundAttributeName( + string containingTypeName, + string propertyName, + string invalidName, + char invalidCharacter) + { + var diagnostic = RazorDiagnostic.Create( + TagHelper_InvalidBoundAttributeName, + new SourceSpan(SourceLocation.Undefined, contentLength: 0), + containingTypeName, + propertyName, + invalidName, + invalidCharacter); + + return diagnostic; + } + + private static readonly RazorDiagnosticDescriptor TagHelper_InvalidBoundAttributeNameStartsWith = + new RazorDiagnosticDescriptor( + $"{DiagnosticPrefix}3004", + () => Resources.InvalidBoundAttributeNameStartsWith, + RazorDiagnosticSeverity.Error); + public static RazorDiagnostic CreateTagHelper_InvalidBoundAttributeNameStartsWith( + string containingTypeName, + string propertyName, + string invalidName) + { + var diagnostic = RazorDiagnostic.Create( + TagHelper_InvalidBoundAttributeNameStartsWith, + new SourceSpan(SourceLocation.Undefined, contentLength: 0), + containingTypeName, + propertyName, + invalidName, + "data-"); + + return diagnostic; + } + + private static readonly RazorDiagnosticDescriptor TagHelper_InvalidBoundAttributePrefix = + new RazorDiagnosticDescriptor( + $"{DiagnosticPrefix}3005", + () => Resources.InvalidBoundAttributePrefix, + RazorDiagnosticSeverity.Error); + public static RazorDiagnostic CreateTagHelper_InvalidBoundAttributePrefix( + string containingTypeName, + string propertyName, + string invalidName, + char invalidCharacter) + { + var diagnostic = RazorDiagnostic.Create( + TagHelper_InvalidBoundAttributePrefix, + new SourceSpan(SourceLocation.Undefined, contentLength: 0), + containingTypeName, + propertyName, + invalidName, + invalidCharacter); + + return diagnostic; + } + + private static readonly RazorDiagnosticDescriptor TagHelper_InvalidBoundAttributePrefixStartsWith = + new RazorDiagnosticDescriptor( + $"{DiagnosticPrefix}3006", + () => Resources.InvalidBoundAttributePrefixStartsWith, + RazorDiagnosticSeverity.Error); + public static RazorDiagnostic CreateTagHelper_InvalidBoundAttributePrefixStartsWith( + string containingTypeName, + string propertyName, + string invalidName) + { + var diagnostic = RazorDiagnostic.Create( + TagHelper_InvalidBoundAttributePrefixStartsWith, + new SourceSpan(SourceLocation.Undefined, contentLength: 0), + containingTypeName, + propertyName, + invalidName, + "data-"); + + return diagnostic; + } + + private static readonly RazorDiagnosticDescriptor TagHelper_InvalidTargetedTagNameNullOrWhitespace = + new RazorDiagnosticDescriptor( + $"{DiagnosticPrefix}3007", + () => Resources.InvalidTargetedTagNameNullOrWhitespace, + RazorDiagnosticSeverity.Error); + public static RazorDiagnostic CreateTagHelper_InvalidTargetedTagNameNullOrWhitespace() + { + var diagnostic = RazorDiagnostic.Create( + TagHelper_InvalidTargetedTagNameNullOrWhitespace, + new SourceSpan(SourceLocation.Undefined, contentLength: 0)); + + return diagnostic; + } + + private static readonly RazorDiagnosticDescriptor TagHelper_InvalidTargetedTagName = + new RazorDiagnosticDescriptor( + $"{DiagnosticPrefix}3008", + () => Resources.InvalidTargetedTagName, + RazorDiagnosticSeverity.Error); + public static RazorDiagnostic CreateTagHelper_InvalidTargetedTagName(string invalidTagName, char invalidCharacter) + { + var diagnostic = RazorDiagnostic.Create( + TagHelper_InvalidTargetedTagName, + new SourceSpan(SourceLocation.Undefined, contentLength: 0), + invalidTagName, + invalidCharacter); + + return diagnostic; + } + + private static readonly RazorDiagnosticDescriptor TagHelper_InvalidTargetedParentTagNameNullOrWhitespace = + new RazorDiagnosticDescriptor( + $"{DiagnosticPrefix}3009", + () => Resources.InvalidTargetedParentTagNameNullOrWhitespace, + RazorDiagnosticSeverity.Error); + public static RazorDiagnostic CreateTagHelper_InvalidTargetedParentTagNameNullOrWhitespace() + { + var diagnostic = RazorDiagnostic.Create( + TagHelper_InvalidTargetedParentTagNameNullOrWhitespace, + new SourceSpan(SourceLocation.Undefined, contentLength: 0)); + + return diagnostic; + } + + private static readonly RazorDiagnosticDescriptor TagHelper_InvalidTargetedParentTagName = + new RazorDiagnosticDescriptor( + $"{DiagnosticPrefix}3010", + () => Resources.InvalidTargetedParentTagName, + RazorDiagnosticSeverity.Error); + public static RazorDiagnostic CreateTagHelper_InvalidTargetedParentTagName(string invalidTagName, char invalidCharacter) + { + var diagnostic = RazorDiagnostic.Create( + TagHelper_InvalidTargetedParentTagName, + new SourceSpan(SourceLocation.Undefined, contentLength: 0), + invalidTagName, + invalidCharacter); + + return diagnostic; + } + + private static readonly RazorDiagnosticDescriptor TagHelper_InvalidTargetedAttributeNameNullOrWhitespace = + new RazorDiagnosticDescriptor( + $"{DiagnosticPrefix}3009", + () => Resources.InvalidTargetedAttributeNameNullOrWhitespace, + RazorDiagnosticSeverity.Error); + public static RazorDiagnostic CreateTagHelper_InvalidTargetedAttributeNameNullOrWhitespace() + { + var diagnostic = RazorDiagnostic.Create( + TagHelper_InvalidTargetedAttributeNameNullOrWhitespace, + new SourceSpan(SourceLocation.Undefined, contentLength: 0)); + + return diagnostic; + } + + private static readonly RazorDiagnosticDescriptor TagHelper_InvalidTargetedAttributeName = + new RazorDiagnosticDescriptor( + $"{DiagnosticPrefix}3010", + () => Resources.InvalidTargetedAttributeName, + RazorDiagnosticSeverity.Error); + public static RazorDiagnostic CreateTagHelper_InvalidTargetedAttributeName(string invalidAttributeName, char invalidCharacter) + { + var diagnostic = RazorDiagnostic.Create( + TagHelper_InvalidTargetedAttributeName, + new SourceSpan(SourceLocation.Undefined, contentLength: 0), + invalidAttributeName, + invalidCharacter); + + return diagnostic; + } + + #endregion + } +} diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/RazorPreallocatedTagHelperAttributeOptimizationPass.cs b/src/Microsoft.AspNetCore.Razor.Evolution/RazorPreallocatedTagHelperAttributeOptimizationPass.cs index 211923a91b..044397af78 100644 --- a/src/Microsoft.AspNetCore.Razor.Evolution/RazorPreallocatedTagHelperAttributeOptimizationPass.cs +++ b/src/Microsoft.AspNetCore.Razor.Evolution/RazorPreallocatedTagHelperAttributeOptimizationPass.cs @@ -88,7 +88,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution public override void VisitSetTagHelperProperty(SetTagHelperPropertyIRNode node) { - if (!node.Descriptor.IsStringProperty || + if (!(node.Descriptor.IsStringProperty || (node.IsIndexerNameMatch && node.Descriptor.IsIndexerStringProperty)) || node.Children.Count != 1 || !(node.Children.First() is HtmlContentIRNode)) { @@ -139,7 +139,9 @@ namespace Microsoft.AspNetCore.Razor.Evolution TagHelperTypeName = node.TagHelperTypeName, PropertyName = node.PropertyName, Descriptor = node.Descriptor, - Parent = node.Parent + Binding = node.Binding, + Parent = node.Parent, + IsIndexerNameMatch = node.IsIndexerNameMatch, }; var nodeIndex = node.Parent.Children.IndexOf(node); diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/RequiredAttributeDescriptor.cs b/src/Microsoft.AspNetCore.Razor.Evolution/RequiredAttributeDescriptor.cs new file mode 100644 index 0000000000..e26443e6ba --- /dev/null +++ b/src/Microsoft.AspNetCore.Razor.Evolution/RequiredAttributeDescriptor.cs @@ -0,0 +1,75 @@ +// 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.Diagnostics; +using System.Linq; + +namespace Microsoft.AspNetCore.Razor.Evolution +{ + public abstract class RequiredAttributeDescriptor + { + public string Name { get; protected set; } + + public NameComparisonMode NameComparison { get; protected set; } + + public string Value { get; protected set; } + + public ValueComparisonMode ValueComparison { get; protected set; } + + public IReadOnlyList Diagnostics { get; protected set; } + + public bool HasAnyErrors + { + get + { + var anyErrors = Diagnostics.Any(diagnostic => diagnostic.Severity == RazorDiagnosticSeverity.Error); + + return anyErrors; + } + } + + /// + /// Acceptable comparison modes. + /// + public enum NameComparisonMode + { + /// + /// HTML attribute name case insensitively matches . + /// + FullMatch, + + /// + /// HTML attribute name case insensitively starts with . + /// + PrefixMatch, + } + + /// + /// Acceptable comparison modes. + /// + public enum ValueComparisonMode + { + /// + /// HTML attribute value always matches . + /// + None, + + /// + /// HTML attribute value case sensitively matches . + /// + FullMatch, + + /// + /// HTML attribute value case sensitively starts with . + /// + PrefixMatch, + + /// + /// HTML attribute value case sensitively ends with . + /// + SuffixMatch, + } + } +} diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/RequiredAttributeDescriptorBuilder.cs b/src/Microsoft.AspNetCore.Razor.Evolution/RequiredAttributeDescriptorBuilder.cs new file mode 100644 index 0000000000..1b0ea7968a --- /dev/null +++ b/src/Microsoft.AspNetCore.Razor.Evolution/RequiredAttributeDescriptorBuilder.cs @@ -0,0 +1,141 @@ +// 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; + +namespace Microsoft.AspNetCore.Razor.Evolution +{ + public sealed class RequiredAttributeDescriptorBuilder + { + private static ICollection InvalidNonWhitespaceAttributeNameCharacters { get; } = new HashSet( + new[] { '@', '!', '<', '/', '?', '[', '>', ']', '=', '"', '\'', '*' }); + + private string _name; + private RequiredAttributeDescriptor.NameComparisonMode _nameComparison; + private string _value; + private RequiredAttributeDescriptor.ValueComparisonMode _valueComparison; + private HashSet _diagnostics; + + private RequiredAttributeDescriptorBuilder() + { + } + + public static RequiredAttributeDescriptorBuilder Create() + { + return new RequiredAttributeDescriptorBuilder(); + } + + public RequiredAttributeDescriptorBuilder Name(string name) + { + _name = name; + + return this; + } + + public RequiredAttributeDescriptorBuilder NameComparisonMode(RequiredAttributeDescriptor.NameComparisonMode nameComparison) + { + _nameComparison = nameComparison; + + return this; + } + + public RequiredAttributeDescriptorBuilder Value(string value) + { + _value = value; + + return this; + } + + public RequiredAttributeDescriptorBuilder ValueComparisonMode(RequiredAttributeDescriptor.ValueComparisonMode valueComparison) + { + _valueComparison = valueComparison; + + return this; + } + + public RequiredAttributeDescriptorBuilder AddDiagnostic(RazorDiagnostic diagnostic) + { + EnsureDiagnostics(); + _diagnostics.Add(diagnostic); + + return this; + } + + public RequiredAttributeDescriptor Build() + { + var validationDiagnostics = Validate(); + var diagnostics = new HashSet(validationDiagnostics); + if (_diagnostics != null) + { + diagnostics.UnionWith(_diagnostics); + } + + var rule = new DefaultTagHelperRequiredAttributeDescriptor( + _name, + _nameComparison, + _value, + _valueComparison, + diagnostics); + + return rule; + } + + public void Reset() + { + _name = null; + _value = null; + _nameComparison = default(RequiredAttributeDescriptor.NameComparisonMode); + _valueComparison = default(RequiredAttributeDescriptor.ValueComparisonMode); + _diagnostics?.Clear(); + } + + private IEnumerable Validate() + { + if (string.IsNullOrWhiteSpace(_name)) + { + var diagnostic = RazorDiagnosticFactory.CreateTagHelper_InvalidTargetedAttributeNameNullOrWhitespace(); + + yield return diagnostic; + } + else + { + foreach (var character in _name) + { + if (char.IsWhiteSpace(character) || InvalidNonWhitespaceAttributeNameCharacters.Contains(character)) + { + var diagnostic = RazorDiagnosticFactory.CreateTagHelper_InvalidTargetedAttributeName(_name, character); + + yield return diagnostic; + } + } + } + } + + private void EnsureDiagnostics() + { + if (_diagnostics == null) + { + _diagnostics = new HashSet(); + } + } + + private class DefaultTagHelperRequiredAttributeDescriptor : RequiredAttributeDescriptor + { + public DefaultTagHelperRequiredAttributeDescriptor( + string name, + NameComparisonMode nameComparison, + string value, + ValueComparisonMode valueComparison, + IEnumerable diagnostics) + { + Name = name; + NameComparison = nameComparison; + Value = value; + ValueComparison = valueComparison; + Diagnostics = new List(diagnostics); + } + } + } +} diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/Resources.resx b/src/Microsoft.AspNetCore.Razor.Evolution/Resources.resx index 2701fa6d07..6db3b53ac4 100644 --- a/src/Microsoft.AspNetCore.Razor.Evolution/Resources.resx +++ b/src/Microsoft.AspNetCore.Razor.Evolution/Resources.resx @@ -159,4 +159,43 @@ The item '{0}' could not be found. + + Invalid tag helper bound property '{0}.{1}'. Tag helpers cannot bind to HTML attributes with name '{2}' because the name contains a '{3}' character. + + + Invalid tag helper bound property '{0}.{1}'. Tag helpers cannot bind to HTML attributes with name '{2}' because the name starts with '{3}'. + + + Invalid tag helper bound property '{0}.{1}'. Tag helpers cannot bind to HTML attributes with a null or empty name. + + + Invalid tag helper bound property '{0}.{1}'. Tag helpers cannot bind to HTML attributes with prefix '{2}' because the prefix contains a '{3}' character. + + + Invalid tag helper bound property '{0}.{1}'. Tag helpers cannot bind to HTML attributes with prefix '{2}' because the prefix starts with '{3}'. + + + Invalid restricted child '{0}' for tag helper '{1}'. Tag helpers cannot restrict child elements that contain a '{2}' character. + + + Invalid restricted child for tag helper '{0}'. Name cannot be null or whitespace. + + + Tag helpers cannot target attribute name '{0}' because it contains a '{1}' character. + + + Targeted attribute name cannot be null or whitespace. + + + Tag helpers cannot target parent tag name '{0}' because it contains a '{1}' character. + + + Targeted parent tag name cannot be null or whitespace. + + + Tag helpers cannot target tag name '{0}' because it contains a '{1}' character. + + + Targeted tag name cannot be null or whitespace. + \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/TagHelperAttributeDescriptor.cs b/src/Microsoft.AspNetCore.Razor.Evolution/TagHelperAttributeDescriptor.cs deleted file mode 100644 index 3f9a8aef88..0000000000 --- a/src/Microsoft.AspNetCore.Razor.Evolution/TagHelperAttributeDescriptor.cs +++ /dev/null @@ -1,156 +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; -using System.Reflection; - -namespace Microsoft.AspNetCore.Razor.Evolution -{ - /// - /// A metadata class describing a tag helper attribute. - /// - public class TagHelperAttributeDescriptor - { - private string _typeName; - private string _name; - private string _propertyName; - - /// - /// Instantiates a new instance of the class. - /// - public TagHelperAttributeDescriptor() - { - } - - // Internal for testing i.e. for easy TagHelperAttributeDescriptor creation when PropertyInfo is available. - internal TagHelperAttributeDescriptor(string name, PropertyInfo propertyInfo) - { - Name = name; - PropertyName = propertyInfo.Name; - TypeName = propertyInfo.PropertyType.FullName; - IsEnum = propertyInfo.PropertyType.GetTypeInfo().IsEnum; - } - - /// - /// Gets an indication whether this is used for dictionary indexer - /// assignments. - /// - /// - /// If true this should be associated with all HTML - /// attributes that have names starting with . Otherwise this - /// is used for property assignment and is only associated with an - /// HTML attribute that has the exact . - /// - /// - /// HTML attribute names are matched case-insensitively, regardless of . - /// - public bool IsIndexer { get; set; } - - /// - /// Gets or sets an indication whether this property is an . - /// - public bool IsEnum { get; set; } - - /// - /// Gets or sets an indication whether this property is of type or, if - /// is true, whether the indexer's value is of type . - /// - /// - /// If true the is for . This causes the Razor parser - /// to allow empty values for HTML attributes matching this . If - /// false empty values for such matching attributes lead to errors. - /// - public bool IsStringProperty { get; set; } - - /// - /// The HTML attribute name or, if is true, the prefix for matching attribute - /// names. - /// - public string Name - { - get - { - return _name; - } - set - { - if (value == null) - { - throw new ArgumentNullException(nameof(value)); - } - - _name = value; - } - } - - - /// - /// The name of the CLR property that corresponds to the HTML attribute. - /// - public string PropertyName - { - get - { - return _propertyName; - } - set - { - if (value == null) - { - throw new ArgumentNullException(nameof(value)); - } - - _propertyName = value; - } - } - - /// - /// The full name of the named (see ) property's or, if - /// is true, the full name of the indexer's value . - /// - public string TypeName - { - get - { - return _typeName; - } - set - { - if (value == null) - { - throw new ArgumentNullException(nameof(value)); - } - - _typeName = value; - IsStringProperty = string.Equals(TypeName, typeof(string).FullName, StringComparison.Ordinal); - } - } - - /// - /// The that contains design time information about - /// this attribute. - /// - public TagHelperAttributeDesignTimeDescriptor DesignTimeDescriptor { get; set; } - - /// - /// Determines whether HTML attribute matches this - /// . - /// - /// Name of the HTML attribute to check. - /// - /// true if this matches . - /// false otherwise. - /// - public bool IsNameMatch(string name) - { - if (IsIndexer) - { - return name.StartsWith(Name, StringComparison.OrdinalIgnoreCase); - } - else - { - return string.Equals(name, Name, StringComparison.OrdinalIgnoreCase); - } - } - } -} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/TagHelperAttributeDesignTimeDescriptor.cs b/src/Microsoft.AspNetCore.Razor.Evolution/TagHelperAttributeDesignTimeDescriptor.cs deleted file mode 100644 index dfadeda0b0..0000000000 --- a/src/Microsoft.AspNetCore.Razor.Evolution/TagHelperAttributeDesignTimeDescriptor.cs +++ /dev/null @@ -1,21 +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. - -namespace Microsoft.AspNetCore.Razor.Evolution -{ - /// - /// A metadata class containing information about tag helper use. - /// - public class TagHelperAttributeDesignTimeDescriptor - { - /// - /// A summary of how to use a tag helper. - /// - public string Summary { get; set; } - - /// - /// Remarks about how to use a tag helper. - /// - public string Remarks { get; set; } - } -} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/TagHelperBinderSyntaxTreePass.cs b/src/Microsoft.AspNetCore.Razor.Evolution/TagHelperBinderSyntaxTreePass.cs index 0b229ddeaa..be0c732154 100644 --- a/src/Microsoft.AspNetCore.Razor.Evolution/TagHelperBinderSyntaxTreePass.cs +++ b/src/Microsoft.AspNetCore.Razor.Evolution/TagHelperBinderSyntaxTreePass.cs @@ -42,7 +42,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution visitor.VisitBlock(import.Root); } } - + visitor.VisitBlock(syntaxTree.Root); var errorList = new List(); @@ -52,6 +52,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution var directives = visitor.Directives; descriptors = ProcessDirectives(directives, descriptors, errorSink); + var tagHelperPrefix = ProcessTagHelperPrefix(directives, codeDocument, errorSink); var root = syntaxTree.Root; if (descriptors.Count == 0) @@ -64,20 +65,51 @@ namespace Microsoft.AspNetCore.Razor.Evolution } else { - var descriptorProvider = new TagHelperDescriptorProvider(descriptors); - var rewriter = new TagHelperParseTreeRewriter(descriptorProvider); + var descriptorProvider = new TagHelperDescriptorProvider(tagHelperPrefix, descriptors); + var rewriter = new TagHelperParseTreeRewriter(tagHelperPrefix, descriptorProvider); root = rewriter.Rewrite(root, errorSink); } // Temporary code while we're still using legacy diagnostics in the SyntaxTree. errorList.AddRange(errorSink.Errors.Select(error => RazorDiagnostic.Create(error))); + errorList.AddRange(descriptors.SelectMany(d => d.GetAllDiagnostics())); + var diagnostics = CombineErrors(syntaxTree.Diagnostics, errorList); var newSyntaxTree = RazorSyntaxTree.Create(root, syntaxTree.Source, diagnostics, syntaxTree.Options); return newSyntaxTree; } + // Internal for testing + internal string ProcessTagHelperPrefix(List directives, RazorCodeDocument codeDocument, ErrorSink errorSink) + { + // We only support a single prefix directive. + TagHelperDirectiveDescriptor prefixDirective = null; + for (var i = 0; i < directives.Count; i++) + { + if (directives[i].DirectiveType == TagHelperDirectiveType.TagHelperPrefix) + { + // We only expect to see a single one of these per file, but that's enforced at another level. + prefixDirective = directives[i]; + } + } + + var prefix = prefixDirective?.DirectiveText; + if (prefix != null && !IsValidTagHelperPrefix(prefix, prefixDirective.Location, errorSink)) + { + prefix = null; + } + + if (!string.IsNullOrEmpty(prefix)) + { + codeDocument.SetTagHelperPrefix(prefixDirective.DirectiveText); + return prefixDirective.DirectiveText; + } + + return null; + } + internal IReadOnlyList ProcessDirectives( IReadOnlyList directives, IReadOnlyList tagHelpers, @@ -85,9 +117,6 @@ namespace Microsoft.AspNetCore.Razor.Evolution { var matches = new HashSet(TagHelperDescriptorComparer.Default); - // We only support a single prefix directive. - TagHelperDirectiveDescriptor prefixDirective = null; - for (var i = 0; i < directives.Count; i++) { var directive = directives[i]; @@ -156,24 +185,11 @@ namespace Microsoft.AspNetCore.Razor.Evolution } } - break; - - case TagHelperDirectiveType.TagHelperPrefix: - - // We only expect to see a single one of these per file, but that's enforced at another level. - prefixDirective = directive; - break; } } - var prefix = prefixDirective?.DirectiveText; - if (prefix != null && !IsValidTagHelperPrefix(prefix, prefixDirective.Location, errorSink)) - { - prefix = null; - } - - return PrefixDescriptors(prefix, matches); + return matches.ToArray(); } private bool AssemblyContainsTagHelpers(string assemblyName, IReadOnlyList tagHelpers) @@ -254,21 +270,6 @@ namespace Microsoft.AspNetCore.Razor.Evolution return true; } - private static IReadOnlyList PrefixDescriptors( - string prefix, - IEnumerable descriptors) - { - if (!string.IsNullOrEmpty(prefix)) - { - return descriptors.Select(descriptor => new TagHelperDescriptor(descriptor) - { - Prefix = prefix - }).ToList(); - } - - return descriptors.ToList(); - } - private static bool MatchesDirective(TagHelperDescriptor descriptor, ParsedDirective lookupInfo) { if (!string.Equals(descriptor.AssemblyName, lookupInfo.AssemblyName, StringComparison.Ordinal)) @@ -276,6 +277,14 @@ namespace Microsoft.AspNetCore.Razor.Evolution return false; } + if (descriptor.Kind != ITagHelperDescriptorBuilder.DescriptorKind) + { + // We only understand TagHelperDescriptors generated from ITagHelpers. + return false; + } + + var descriptorTypeName = descriptor.Metadata[ITagHelperDescriptorBuilder.TypeNameKey]; + if (lookupInfo.TypePattern.EndsWith("*", StringComparison.Ordinal)) { if (lookupInfo.TypePattern.Length == 1) @@ -286,10 +295,10 @@ namespace Microsoft.AspNetCore.Razor.Evolution var lookupTypeName = lookupInfo.TypePattern.Substring(0, lookupInfo.TypePattern.Length - 1); - return descriptor.TypeName.StartsWith(lookupTypeName, StringComparison.Ordinal); + return descriptorTypeName.StartsWith(lookupTypeName, StringComparison.Ordinal); } - return string.Equals(descriptor.TypeName, lookupInfo.TypePattern, StringComparison.Ordinal); + return string.Equals(descriptorTypeName, lookupInfo.TypePattern, StringComparison.Ordinal); } private static int GetErrorLength(string directiveText) diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/TagHelperDescriptor.cs b/src/Microsoft.AspNetCore.Razor.Evolution/TagHelperDescriptor.cs index 0f6fb23e08..0babe18bc2 100644 --- a/src/Microsoft.AspNetCore.Razor.Evolution/TagHelperDescriptor.cs +++ b/src/Microsoft.AspNetCore.Razor.Evolution/TagHelperDescriptor.cs @@ -1,251 +1,64 @@ // 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; namespace Microsoft.AspNetCore.Razor.Evolution { - /// - /// A metadata class describing a tag helper. - /// - public class TagHelperDescriptor + public abstract class TagHelperDescriptor { - private string _prefix = string.Empty; - private string _tagName; - private string _typeName; - private string _assemblyName; - private IDictionary _propertyBag; - private IEnumerable _attributes = - Enumerable.Empty(); - private IEnumerable _requiredAttributes = - Enumerable.Empty(); + private IEnumerable _allDiagnostics; - /// - /// Creates a new . - /// - public TagHelperDescriptor() + protected TagHelperDescriptor(string kind) { + Kind = kind; } - /// - /// Creates a shallow copy of the given . - /// - /// The to copy. - public TagHelperDescriptor(TagHelperDescriptor descriptor) - { - Prefix = descriptor.Prefix; - TagName = descriptor.TagName; - TypeName = descriptor.TypeName; - AssemblyName = descriptor.AssemblyName; - Attributes = descriptor.Attributes; - RequiredAttributes = descriptor.RequiredAttributes; - AllowedChildren = descriptor.AllowedChildren; - RequiredParent = descriptor.RequiredParent; - TagStructure = descriptor.TagStructure; - DesignTimeDescriptor = descriptor.DesignTimeDescriptor; + public string Kind { get; } - foreach (var property in descriptor.PropertyBag) - { - PropertyBag.Add(property.Key, property.Value); - } - } + public string Name { get; protected set; } - /// - /// Text used as a required prefix when matching HTML start and end tags in the Razor source to available - /// tag helpers. - /// - public string Prefix + public IEnumerable TagMatchingRules { get; protected set; } + + public string AssemblyName { get; protected set; } + + public IEnumerable BoundAttributes { get; protected set; } + + public IEnumerable AllowedChildTags { get; protected set; } + + public string Documentation { get; protected set; } + + public string DisplayName { get; protected set; } + + public string TagOutputHint { get; protected set; } + + public IReadOnlyList Diagnostics { get; protected set; } + + public IReadOnlyDictionary Metadata { get; protected set; } + + public bool HasAnyErrors { get { - return _prefix; - } - set - { - if (value == null) - { - throw new ArgumentNullException(nameof(value)); - } + var allDiagnostics = GetAllDiagnostics(); + var anyErrors = allDiagnostics.Any(diagnostic => diagnostic.Severity == RazorDiagnosticSeverity.Error); - _prefix = value; + return anyErrors; } } - /// - /// The tag name that the tag helper should target. - /// - public string TagName + public virtual IEnumerable GetAllDiagnostics() { - get + if (_allDiagnostics == null) { - return _tagName; + var attributeDiagnostics = BoundAttributes.SelectMany(attribute => attribute.Diagnostics); + var ruleDiagnostics = TagMatchingRules.SelectMany(rule => rule.GetAllDiagnostics()); + var combinedDiagnostics = attributeDiagnostics.Concat(ruleDiagnostics).Concat(Diagnostics); + _allDiagnostics = combinedDiagnostics.ToArray(); } - set - { - if (value == null) - { - throw new ArgumentNullException(nameof(value)); - } - _tagName = value; - } - } - - /// - /// The full tag name that is required for the tag helper to target an HTML element. - /// - /// This is equivalent to and concatenated. - public string FullTagName - { - get - { - return Prefix + TagName; - } - } - - /// - /// The full name of the tag helper class. - /// - public string TypeName - { - get - { - return _typeName; - } - set - { - if (value == null) - { - throw new ArgumentNullException(nameof(value)); - } - - _typeName = value; - } - } - - /// - /// The name of the assembly containing the tag helper class. - /// - public string AssemblyName - { - get - { - return _assemblyName; - } - set - { - if (value == null) - { - throw new ArgumentNullException(nameof(value)); - } - - _assemblyName = value; - } - } - - /// - /// The list of attributes the tag helper expects. - /// - public IEnumerable Attributes - { - get - { - return _attributes; - } - set - { - if (value == null) - { - throw new ArgumentNullException(nameof(value)); - } - - _attributes = value; - } - } - - /// - /// The list of required attribute names the tag helper expects to target an element. - /// - /// - /// * at the end of an attribute name acts as a prefix match. - /// - public IEnumerable RequiredAttributes - { - get - { - return _requiredAttributes; - } - set - { - if (value == null) - { - throw new ArgumentNullException(nameof(value)); - } - - _requiredAttributes = value; - } - } - - /// - /// Get the names of elements allowed as children. - /// - /// null indicates all children are allowed. - public IEnumerable AllowedChildren { get; set; } - - /// - /// Get the name of the HTML element required as the immediate parent. - /// - /// null indicates no restriction on parent tag. - public string RequiredParent { get; set; } - - /// - /// The expected tag structure. - /// - /// - /// If and no other tag helpers applying to the same element specify - /// their the behavior is used: - /// - /// - /// <my-tag-helper></my-tag-helper> - /// <!-- OR --> - /// <my-tag-helper /> - /// - /// Otherwise, if another tag helper applying to the same element does specify their behavior, that behavior - /// is used. - /// - /// - /// If HTML elements can be written in the following formats: - /// - /// <my-tag-helper> - /// <!-- OR --> - /// <my-tag-helper /> - /// - /// - /// - public TagStructure TagStructure { get; set; } - - /// - /// The that contains design time information about this - /// tag helper. - /// - public TagHelperDesignTimeDescriptor DesignTimeDescriptor { get; set; } - - /// - /// A dictionary containing additional information about the . - /// - public IDictionary PropertyBag - { - get - { - if (_propertyBag == null) - { - _propertyBag = new Dictionary(); - } - - return _propertyBag; - } + return _allDiagnostics; } } } diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/TagHelperDescriptorMatchingConventions.cs b/src/Microsoft.AspNetCore.Razor.Evolution/TagHelperDescriptorMatchingConventions.cs new file mode 100644 index 0000000000..b57dc141ce --- /dev/null +++ b/src/Microsoft.AspNetCore.Razor.Evolution/TagHelperDescriptorMatchingConventions.cs @@ -0,0 +1,67 @@ +// 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.Diagnostics; + +namespace Microsoft.AspNetCore.Razor.Evolution +{ + internal static class TagHelperDescriptorMatchingConventions + { + public static bool CanMatchName(this BoundAttributeDescriptor descriptor, string name) + { + return IsFullNameMatch(descriptor, name) || IsIndexerNameMatch(descriptor, name); + } + + public static bool IsFullNameMatch(this BoundAttributeDescriptor descriptor, string name) + { + return string.Equals(descriptor.Name, name, StringComparison.OrdinalIgnoreCase); + } + + public static bool IsIndexerNameMatch(this BoundAttributeDescriptor descriptor, string name) + { + return descriptor.IndexerNamePrefix != null && + !IsFullNameMatch(descriptor, name) && + name.StartsWith(descriptor.IndexerNamePrefix, StringComparison.OrdinalIgnoreCase); + } + + public static bool IsMatch(this RequiredAttributeDescriptor descriptor, string attributeName, string attributeValue) + { + var nameMatches = false; + if (descriptor.NameComparison == RequiredAttributeDescriptor.NameComparisonMode.FullMatch) + { + nameMatches = string.Equals(descriptor.Name, attributeName, StringComparison.OrdinalIgnoreCase); + } + else if (descriptor.NameComparison == RequiredAttributeDescriptor.NameComparisonMode.PrefixMatch) + { + // attributeName cannot equal the Name if comparing as a PrefixMatch. + nameMatches = attributeName.Length != descriptor.Name.Length && + attributeName.StartsWith(descriptor.Name, StringComparison.OrdinalIgnoreCase); + } + else + { + Debug.Assert(false, "Unknown name comparison."); + } + + if (!nameMatches) + { + return false; + } + + switch (descriptor.ValueComparison) + { + case RequiredAttributeDescriptor.ValueComparisonMode.None: + return true; + case RequiredAttributeDescriptor.ValueComparisonMode.PrefixMatch: // Value starts with + return attributeValue.StartsWith(descriptor.Value, StringComparison.Ordinal); + case RequiredAttributeDescriptor.ValueComparisonMode.SuffixMatch: // Value ends with + return attributeValue.EndsWith(descriptor.Value, StringComparison.Ordinal); + case RequiredAttributeDescriptor.ValueComparisonMode.FullMatch: // Value equals + return string.Equals(attributeValue, descriptor.Value, StringComparison.Ordinal); + default: + Debug.Assert(false, "Unknown value comparison."); + return false; + } + } + } +} diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/TagHelperDesignTimeDescriptor.cs b/src/Microsoft.AspNetCore.Razor.Evolution/TagHelperDesignTimeDescriptor.cs deleted file mode 100644 index 49d781d5bc..0000000000 --- a/src/Microsoft.AspNetCore.Razor.Evolution/TagHelperDesignTimeDescriptor.cs +++ /dev/null @@ -1,29 +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. - -namespace Microsoft.AspNetCore.Razor.Evolution -{ - /// - /// A metadata class containing design time information about a tag helper. - /// - public class TagHelperDesignTimeDescriptor - { - /// - /// A summary of how to use a tag helper. - /// - public string Summary { get; set; } - - /// - /// Remarks about how to use a tag helper. - /// - public string Remarks { get; set; } - - /// - /// The HTML element a tag helper may output. - /// - /// - /// In IDEs supporting IntelliSense, may override the HTML information provided at design time. - /// - public string OutputElementHint { get; set; } - } -} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/TagHelperRequiredAttributeDescriptor.cs b/src/Microsoft.AspNetCore.Razor.Evolution/TagHelperRequiredAttributeDescriptor.cs deleted file mode 100644 index 9b4b144cd6..0000000000 --- a/src/Microsoft.AspNetCore.Razor.Evolution/TagHelperRequiredAttributeDescriptor.cs +++ /dev/null @@ -1,82 +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; -using System.Diagnostics; -using System.Linq; - -namespace Microsoft.AspNetCore.Razor.Evolution -{ - /// - /// A metadata class describing a required tag helper attribute. - /// - public class TagHelperRequiredAttributeDescriptor - { - /// - /// The HTML attribute name. - /// - public string Name { get; set; } - - /// - /// The comparison method to use for when determining if an HTML attribute name matches. - /// - public TagHelperRequiredAttributeNameComparison NameComparison { get; set; } - - /// - /// The HTML attribute value. - /// - public string Value { get; set; } - - /// - /// The comparison method to use for when determining if an HTML attribute value matches. - /// - public TagHelperRequiredAttributeValueComparison ValueComparison { get; set; } - - /// - /// Determines if the current matches the given - /// and . - /// - /// An HTML attribute name. - /// An HTML attribute value. - /// true if the current matches - /// and ; false otherwise. - public bool IsMatch(string attributeName, string attributeValue) - { - var nameMatches = false; - if (NameComparison == TagHelperRequiredAttributeNameComparison.FullMatch) - { - nameMatches = string.Equals(Name, attributeName, StringComparison.OrdinalIgnoreCase); - } - else if (NameComparison == TagHelperRequiredAttributeNameComparison.PrefixMatch) - { - // attributeName cannot equal the Name if comparing as a PrefixMatch. - nameMatches = attributeName.Length != Name.Length && - attributeName.StartsWith(Name, StringComparison.OrdinalIgnoreCase); - } - else - { - Debug.Assert(false, "Unknown name comparison."); - } - - if (!nameMatches) - { - return false; - } - - switch (ValueComparison) - { - case TagHelperRequiredAttributeValueComparison.None: - return true; - case TagHelperRequiredAttributeValueComparison.PrefixMatch: // Value starts with - return attributeValue.StartsWith(Value, StringComparison.Ordinal); - case TagHelperRequiredAttributeValueComparison.SuffixMatch: // Value ends with - return attributeValue.EndsWith(Value, StringComparison.Ordinal); - case TagHelperRequiredAttributeValueComparison.FullMatch: // Value equals - return string.Equals(attributeValue, Value, StringComparison.Ordinal); - default: - Debug.Assert(false, "Unknown value comparison."); - return false; - } - } - } -} diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/TagHelperRequiredAttributeNameComparison.cs b/src/Microsoft.AspNetCore.Razor.Evolution/TagHelperRequiredAttributeNameComparison.cs deleted file mode 100644 index 7e413e1381..0000000000 --- a/src/Microsoft.AspNetCore.Razor.Evolution/TagHelperRequiredAttributeNameComparison.cs +++ /dev/null @@ -1,21 +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. - -namespace Microsoft.AspNetCore.Razor.Evolution -{ - /// - /// Acceptable comparison modes. - /// - public enum TagHelperRequiredAttributeNameComparison - { - /// - /// HTML attribute name case insensitively matches . - /// - FullMatch, - - /// - /// HTML attribute name case insensitively starts with . - /// - PrefixMatch, - } -} diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/TagHelperRequiredAttributeValueComparison.cs b/src/Microsoft.AspNetCore.Razor.Evolution/TagHelperRequiredAttributeValueComparison.cs deleted file mode 100644 index 2d4bc41289..0000000000 --- a/src/Microsoft.AspNetCore.Razor.Evolution/TagHelperRequiredAttributeValueComparison.cs +++ /dev/null @@ -1,31 +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. - -namespace Microsoft.AspNetCore.Razor.Evolution -{ - /// - /// Acceptable comparison modes. - /// - public enum TagHelperRequiredAttributeValueComparison - { - /// - /// HTML attribute value always matches . - /// - None, - - /// - /// HTML attribute value case sensitively matches . - /// - FullMatch, - - /// - /// HTML attribute value case sensitively starts with . - /// - PrefixMatch, - - /// - /// HTML attribute value case sensitively ends with . - /// - SuffixMatch, - } -} diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/TagMatchingRule.cs b/src/Microsoft.AspNetCore.Razor.Evolution/TagMatchingRule.cs new file mode 100644 index 0000000000..3de80d4e2c --- /dev/null +++ b/src/Microsoft.AspNetCore.Razor.Evolution/TagMatchingRule.cs @@ -0,0 +1,46 @@ +// 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; + +namespace Microsoft.AspNetCore.Razor.Evolution +{ + public abstract class TagMatchingRule + { + private IEnumerable _allDiagnostics; + + public string TagName { get; protected set; } + + public IEnumerable Attributes { get; protected set; } + + public string ParentTag { get; protected set; } + + public TagStructure TagStructure { get; protected set; } + + public IReadOnlyList Diagnostics { get; protected set; } + + public bool HasAnyErrors + { + get + { + var allDiagnostics = GetAllDiagnostics(); + var anyErrors = allDiagnostics.Any(diagnostic => diagnostic.Severity == RazorDiagnosticSeverity.Error); + + return anyErrors; + } + } + + public virtual IEnumerable GetAllDiagnostics() + { + if (_allDiagnostics == null) + { + var attributeDiagnostics = Attributes.SelectMany(attribute => attribute.Diagnostics); + var combinedDiagnostics = Diagnostics.Concat(attributeDiagnostics); + _allDiagnostics = combinedDiagnostics.ToArray(); + } + + return _allDiagnostics; + } + } +} diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/TagMatchingRuleBuilder.cs b/src/Microsoft.AspNetCore.Razor.Evolution/TagMatchingRuleBuilder.cs new file mode 100644 index 0000000000..3d914717c6 --- /dev/null +++ b/src/Microsoft.AspNetCore.Razor.Evolution/TagMatchingRuleBuilder.cs @@ -0,0 +1,194 @@ +// 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 Microsoft.AspNetCore.Razor.Evolution.Legacy; + +namespace Microsoft.AspNetCore.Razor.Evolution +{ + public sealed class TagMatchingRuleBuilder + { + private static ICollection InvalidNonWhitespaceTagNameCharacters { get; } = new HashSet( + new[] { '@', '!', '<', '/', '?', '[', '>', ']', '=', '"', '\'', '*' }); + + private string _tagName; + private string _parentTag; + private TagStructure _tagStructure; + private HashSet _requiredAttributeDescriptors; + private HashSet _diagnostics; + + private TagMatchingRuleBuilder() + { + } + + public static TagMatchingRuleBuilder Create() + { + return new TagMatchingRuleBuilder(); + } + + public TagMatchingRuleBuilder RequireTagName(string tagName) + { + _tagName = tagName; + + return this; + } + + public TagMatchingRuleBuilder RequireParentTag(string parentTag) + { + _parentTag = parentTag; + + return this; + } + + public TagMatchingRuleBuilder RequireTagStructure(TagStructure tagStructure) + { + _tagStructure = tagStructure; + + return this; + } + + public TagMatchingRuleBuilder RequireAttribute(RequiredAttributeDescriptor requiredAttributeDescriptor) + { + if (requiredAttributeDescriptor == null) + { + throw new ArgumentNullException(nameof(requiredAttributeDescriptor)); + } + + EnsureRequiredAttributeDescriptors(); + _requiredAttributeDescriptors.Add(requiredAttributeDescriptor); + + return this; + } + + public TagMatchingRuleBuilder RequireAttribute(Action configure) + { + if (configure == null) + { + throw new ArgumentNullException(nameof(configure)); + } + + var builder = RequiredAttributeDescriptorBuilder.Create(); + + configure(builder); + + var requiredAttributeDescriptor = builder.Build(); + + return RequireAttribute(requiredAttributeDescriptor); + } + + public TagMatchingRuleBuilder AddDiagnostic(RazorDiagnostic diagnostic) + { + EnsureDiagnostics(); + _diagnostics.Add(diagnostic); + + return this; + } + + public TagMatchingRule Build() + { + var validationDiagnostics = Validate(); + var diagnostics = new HashSet(validationDiagnostics); + if (_diagnostics != null) + { + diagnostics.UnionWith(_diagnostics); + } + + var rule = new DefaultTagMatchingRule( + _tagName, + _parentTag, + _tagStructure, + _requiredAttributeDescriptors ?? Enumerable.Empty(), + diagnostics); + + return rule; + } + + public void Reset() + { + _tagName = null; + _parentTag = null; + _tagStructure = default(TagStructure); + _requiredAttributeDescriptors?.Clear(); + _diagnostics?.Clear(); + } + + private IEnumerable Validate() + { + if (string.IsNullOrWhiteSpace(_tagName)) + { + var diagnostic = RazorDiagnosticFactory.CreateTagHelper_InvalidTargetedTagNameNullOrWhitespace(); + + yield return diagnostic; + } + else if (_tagName != TagHelperDescriptorProvider.ElementCatchAllTarget) + { + foreach (var character in _tagName) + { + if (char.IsWhiteSpace(character) || InvalidNonWhitespaceTagNameCharacters.Contains(character)) + { + var diagnostic = RazorDiagnosticFactory.CreateTagHelper_InvalidTargetedTagName(_tagName, character); + + yield return diagnostic; + } + } + } + + if (_parentTag != null) + { + if (string.IsNullOrWhiteSpace(_parentTag)) + { + var diagnostic = RazorDiagnosticFactory.CreateTagHelper_InvalidTargetedParentTagNameNullOrWhitespace(); + + AddDiagnostic(diagnostic); + } + else + { + foreach (var character in _parentTag) + { + if (char.IsWhiteSpace(character) || InvalidNonWhitespaceTagNameCharacters.Contains(character)) + { + var diagnostic = RazorDiagnosticFactory.CreateTagHelper_InvalidTargetedParentTagName(_parentTag, character); + + AddDiagnostic(diagnostic); + } + } + } + } + } + + private void EnsureRequiredAttributeDescriptors() + { + if (_requiredAttributeDescriptors == null) + { + _requiredAttributeDescriptors = new HashSet(RequiredAttributeDescriptorComparer.Default); + } + } + + private void EnsureDiagnostics() + { + if (_diagnostics == null) + { + _diagnostics = new HashSet(); + } + } + + private class DefaultTagMatchingRule : TagMatchingRule + { + public DefaultTagMatchingRule( + string tagName, + string parentTag, + TagStructure tagStructure, + IEnumerable requiredAttributeDescriptors, + IEnumerable diagnostics) + { + TagName = tagName; + ParentTag = parentTag; + TagStructure = tagStructure; + Attributes = new List(requiredAttributeDescriptors); + Diagnostics = new List(diagnostics); + } + } + } +} diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/TagMatchingRuleComparer.cs b/src/Microsoft.AspNetCore.Razor.Evolution/TagMatchingRuleComparer.cs new file mode 100644 index 0000000000..2de76358d6 --- /dev/null +++ b/src/Microsoft.AspNetCore.Razor.Evolution/TagMatchingRuleComparer.cs @@ -0,0 +1,86 @@ +// 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 Microsoft.AspNetCore.Razor.Evolution.Legacy; +using Microsoft.Extensions.Internal; + +namespace Microsoft.AspNetCore.Razor.Evolution +{ + internal class TagMatchingRuleComparer : IEqualityComparer + { + /// + /// A default instance of the . + /// + public static readonly TagMatchingRuleComparer Default = new TagMatchingRuleComparer(); + + /// + /// A default instance of the that does case-sensitive comparison. + /// + internal static readonly TagMatchingRuleComparer CaseSensitive = + new TagMatchingRuleComparer(caseSensitive: true); + + private readonly StringComparer _stringComparer; + private readonly StringComparison _stringComparison; + private readonly RequiredAttributeDescriptorComparer _requiredAttributeComparer; + + private TagMatchingRuleComparer(bool caseSensitive = false) + { + if (caseSensitive) + { + _stringComparer = StringComparer.Ordinal; + _stringComparison = StringComparison.Ordinal; + _requiredAttributeComparer = RequiredAttributeDescriptorComparer.CaseSensitive; + } + else + { + _stringComparer = StringComparer.OrdinalIgnoreCase; + _stringComparison = StringComparison.OrdinalIgnoreCase; + _requiredAttributeComparer = RequiredAttributeDescriptorComparer.Default; + } + } + + public virtual bool Equals(TagMatchingRule ruleX, TagMatchingRule ruleY) + { + if (object.ReferenceEquals(ruleX, ruleY)) + { + return true; + } + + if (ruleX == null ^ ruleY == null) + { + return false; + } + + return ruleX != null && + string.Equals(ruleX.TagName, ruleY.TagName, _stringComparison) && + string.Equals(ruleX.ParentTag, ruleY.ParentTag, _stringComparison) && + ruleX.TagStructure == ruleY.TagStructure && + Enumerable.SequenceEqual(ruleX.Attributes, ruleY.Attributes, _requiredAttributeComparer) && + Enumerable.SequenceEqual(ruleX.Diagnostics, ruleY.Diagnostics); + } + + public virtual int GetHashCode(TagMatchingRule rule) + { + if (rule == null) + { + throw new ArgumentNullException(nameof(rule)); + } + + var hashCodeCombiner = HashCodeCombiner.Start(); + hashCodeCombiner.Add(rule.TagName, _stringComparer); + hashCodeCombiner.Add(rule.ParentTag, _stringComparer); + hashCodeCombiner.Add(rule.TagStructure); + + var attributes = rule.Attributes.OrderBy(attribute => attribute.Name, _stringComparer); + foreach (var attribute in attributes) + { + hashCodeCombiner.Add(_requiredAttributeComparer.GetHashCode(attribute)); + } + + return hashCodeCombiner.CombinedHash; + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Razor/Compilation/TagHelpers/TagHelperDescriptorComparer.cs b/src/Microsoft.AspNetCore.Razor/Compilation/TagHelpers/TagHelperDescriptorComparer.cs index 5bc2238387..cde2d5ba31 100644 --- a/src/Microsoft.AspNetCore.Razor/Compilation/TagHelpers/TagHelperDescriptorComparer.cs +++ b/src/Microsoft.AspNetCore.Razor/Compilation/TagHelpers/TagHelperDescriptorComparer.cs @@ -37,11 +37,16 @@ namespace Microsoft.AspNetCore.Razor.Compilation.TagHelpers /// public virtual bool Equals(TagHelperDescriptor descriptorX, TagHelperDescriptor descriptorY) { - if (descriptorX == descriptorY) + if (object.ReferenceEquals(descriptorX, descriptorY)) { return true; } + if (descriptorX == null ^ descriptorY == null) + { + return false; + } + return descriptorX != null && string.Equals(descriptorX.TypeName, descriptorY.TypeName, StringComparison.Ordinal) && string.Equals(descriptorX.TagName, descriptorY.TagName, StringComparison.OrdinalIgnoreCase) && diff --git a/src/Microsoft.AspNetCore.Razor/Parser/TagHelpers/TagHelperBlockBuilder.cs b/src/Microsoft.AspNetCore.Razor/Parser/TagHelpers/TagHelperBlockBuilder.cs index 3063c315ae..d1990de5c9 100644 --- a/src/Microsoft.AspNetCore.Razor/Parser/TagHelpers/TagHelperBlockBuilder.cs +++ b/src/Microsoft.AspNetCore.Razor/Parser/TagHelpers/TagHelperBlockBuilder.cs @@ -91,7 +91,7 @@ namespace Microsoft.AspNetCore.Razor.Parser.TagHelpers public TagMode TagMode { get; } /// - /// s for the HTML element. + /// bindings for the HTML element. /// public IEnumerable Descriptors { get; } diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/DefaultTagHelperResolver.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/DefaultTagHelperResolver.cs index f9a6d66a23..961638b907 100644 --- a/src/Microsoft.CodeAnalysis.Razor.Workspaces/DefaultTagHelperResolver.cs +++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/DefaultTagHelperResolver.cs @@ -21,9 +21,10 @@ namespace Microsoft.CodeAnalysis.Razor public override TagHelperResolutionResult GetTagHelpers(Compilation compilation, IEnumerable assemblyNameFilters) { var descriptors = new List(); - var errors = new ErrorSink(); - VisitTagHelpers(compilation, assemblyNameFilters, descriptors, errors); + VisitTagHelpers(compilation, assemblyNameFilters, descriptors); + + var errors = new ErrorSink(); VisitViewComponents(compilation, assemblyNameFilters, descriptors, errors); var diagnostics = new List(); @@ -37,7 +38,7 @@ namespace Microsoft.CodeAnalysis.Razor return resolutionResult; } - private void VisitTagHelpers(Compilation compilation, IEnumerable assemblyNameFilters, List results, ErrorSink errors) + private void VisitTagHelpers(Compilation compilation, IEnumerable assemblyNameFilters, List results) { var types = new List(); var visitor = TagHelperTypeVisitor.Create(compilation, types); @@ -50,8 +51,12 @@ namespace Microsoft.CodeAnalysis.Razor { if (assemblyNameFilters.Contains(type.ContainingAssembly.Identity.Name)) { - var descriptors = factory.CreateDescriptors(type, errors); - results.AddRange(descriptors); + var descriptor = factory.CreateDescriptor(type); + + if (descriptor != null) + { + results.Add(descriptor); + } } } } diff --git a/src/Microsoft.CodeAnalysis.Razor/DefaultTagHelperDescriptorFactory.cs b/src/Microsoft.CodeAnalysis.Razor/DefaultTagHelperDescriptorFactory.cs index 203f27ea5d..7804f051a0 100644 --- a/src/Microsoft.CodeAnalysis.Razor/DefaultTagHelperDescriptorFactory.cs +++ b/src/Microsoft.CodeAnalysis.Razor/DefaultTagHelperDescriptorFactory.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.ComponentModel; -using System.Diagnostics; using System.Linq; using System.Text.RegularExpressions; using Microsoft.AspNetCore.Razor.Evolution; @@ -18,7 +17,6 @@ namespace Microsoft.CodeAnalysis.Razor private const string DataDashPrefix = "data-"; private const string TagHelperNameEnding = "TagHelper"; private const string HtmlCaseRegexReplacement = "-$1$2"; - private const char RequiredAttributeWildcardSuffix = '*'; // 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) @@ -61,87 +59,38 @@ namespace Microsoft.CodeAnalysis.Razor protected bool DesignTime { get; } /// - public virtual IEnumerable CreateDescriptors( - INamedTypeSymbol type, - ErrorSink errorSink) + public virtual TagHelperDescriptor CreateDescriptor(INamedTypeSymbol type) { if (type == null) { throw new ArgumentNullException(nameof(type)); } - if (errorSink == null) - { - throw new ArgumentNullException(nameof(errorSink)); - } - if (ShouldSkipDescriptorCreation(type)) { - return Enumerable.Empty(); - } - - var attributeDescriptors = GetAttributeDescriptors(type, errorSink); - var targetElementAttributes = GetValidHtmlTargetElementAttributes(type, errorSink); - var allowedChildren = GetAllowedChildren(type, errorSink); - - var tagHelperDescriptors = - BuildTagHelperDescriptors( - type, - type.ContainingAssembly.Identity.Name, - attributeDescriptors, - targetElementAttributes, - allowedChildren); - - return tagHelperDescriptors.Distinct(TagHelperDescriptorComparer.Default); - } - - private IEnumerable GetValidHtmlTargetElementAttributes( - INamedTypeSymbol typeSymbol, - ErrorSink errorSink) - { - var targetElementAttributes = typeSymbol.GetAttributes().Where(a => a.AttributeClass == _htmlTargetElementAttributeSymbol); - return targetElementAttributes.Where(a => ValidHtmlTargetElementAttributeNames(a, errorSink)); - } - - private IEnumerable BuildTagHelperDescriptors( - INamedTypeSymbol type, - string assemblyName, - IEnumerable attributeDescriptors, - IEnumerable targetElementAttributes, - IEnumerable allowedChildren) - { - TagHelperDesignTimeDescriptor designTimeDescriptor = null; - if (DesignTime) - { - XmlMemberDocumentation documentation = null; - var xml = type.GetDocumentationCommentXml(); - if (!string.IsNullOrEmpty(xml)) - { - documentation = new XmlMemberDocumentation(xml); - } - - string outputElementHint = null; - var outputElementHintAttribute = type.GetAttributes().Where(a => a.AttributeClass == _outputElementHintAttributeSymbol).FirstOrDefault(); - if (outputElementHintAttribute != null) - { - outputElementHint = (string)(outputElementHintAttribute.ConstructorArguments[0]).Value; - } - - var remarks = documentation?.GetRemarks(); - var summary = documentation?.GetSummary(); - - if (outputElementHint != null || summary != null || remarks != null) - { - designTimeDescriptor = new TagHelperDesignTimeDescriptor() - { - OutputElementHint = outputElementHint, - Remarks = remarks, - Summary = summary, - }; - } + return null; } var typeName = GetFullName(type); + var assemblyName = type.ContainingAssembly.Identity.Name; + var descriptorBuilder = ITagHelperDescriptorBuilder.Create(typeName, assemblyName); + + AddBoundAttributes(type, descriptorBuilder); + AddTagMatchingRules(type, descriptorBuilder); + AddAllowedChildren(type, descriptorBuilder); + AddDocumentation(type, descriptorBuilder); + AddTagOutputHint(type, descriptorBuilder); + + var descriptor = descriptorBuilder.Build(); + + return descriptor; + } + + private void AddTagMatchingRules(INamedTypeSymbol type, ITagHelperDescriptorBuilder descriptorBuilder) + { + var targetElementAttributes = type + .GetAttributes() + .Where(attribute => attribute.AttributeClass == _htmlTargetElementAttributeSymbol); // If there isn't an attribute specifying the tag name derive it from the name if (!targetElementAttributes.Any()) @@ -153,121 +102,243 @@ namespace Microsoft.CodeAnalysis.Razor name = name.Substring(0, name.Length - TagHelperNameEnding.Length); } - return new[] + descriptorBuilder.TagMatchingRule(ruleBuilder => { - BuildTagHelperDescriptor( - ToHtmlCase(name), - typeName, - assemblyName, - attributeDescriptors, - requiredAttributeDescriptors: Enumerable.Empty(), - allowedChildren: allowedChildren, - tagStructure: default(TagStructure), - parentTag: null, - designTimeDescriptor: designTimeDescriptor) - }; + var htmlCasedName = ToHtmlCase(name); + ruleBuilder.RequireTagName(htmlCasedName); + }); + + return; } - return targetElementAttributes.Select( - attribute => - BuildTagHelperDescriptor( - typeName, - assemblyName, - attributeDescriptors, - attribute, - allowedChildren, - designTimeDescriptor)); + foreach (var targetElementAttribute in targetElementAttributes) + { + descriptorBuilder.TagMatchingRule(ruleBuilder => + { + var tagName = HtmlTargetElementAttribute_Tag(targetElementAttribute); + ruleBuilder.RequireTagName(tagName); + + var parentTag = HtmlTargetElementAttribute_ParentTag(targetElementAttribute); + ruleBuilder.RequireParentTag(parentTag); + + var tagStructure = HtmlTargetElementAttribute_TagStructure(targetElementAttribute); + ruleBuilder.RequireTagStructure(tagStructure); + + var requiredAttributeString = HtmlTargetElementAttribute_Attributes(targetElementAttribute); + RequiredAttributeParser.AddRequiredAttributes(requiredAttributeString, ruleBuilder); + }); + } } - private IEnumerable GetAllowedChildren(INamedTypeSymbol type, ErrorSink errorSink) + private void AddBoundAttributes(INamedTypeSymbol type, ITagHelperDescriptorBuilder builder) + { + var accessibleProperties = GetAccessibleProperties(type); + foreach (var property in accessibleProperties) + { + if (ShouldSkipDescriptorCreation(property)) + { + continue; + } + + builder.BindAttribute(attributeBuilder => + { + ConfigureBoundAttribute(attributeBuilder, property, type); + }); + } + } + + private void AddAllowedChildren(INamedTypeSymbol type, ITagHelperDescriptorBuilder builder) { var restrictChildrenAttribute = type.GetAttributes().Where(a => a.AttributeClass == _restrictChildrenAttributeSymbol).FirstOrDefault(); if (restrictChildrenAttribute == null) { - return null; + return; } - var allowedChildren = new List(); - allowedChildren.Add((string)restrictChildrenAttribute.ConstructorArguments[0].Value); + builder.AllowChildTag((string)restrictChildrenAttribute.ConstructorArguments[0].Value); if (restrictChildrenAttribute.ConstructorArguments.Length == 2) { foreach (var value in restrictChildrenAttribute.ConstructorArguments[1].Values) { - allowedChildren.Add((string)value.Value); + builder.AllowChildTag((string)value.Value); } } + } - var validAllowedChildren = GetValidAllowedChildren(allowedChildren, GetFullName(type), errorSink); - - if (validAllowedChildren.Any()) + private void AddDocumentation(INamedTypeSymbol type, ITagHelperDescriptorBuilder builder) + { + if (!DesignTime) { - return validAllowedChildren; + return; + } + + var xml = type.GetDocumentationCommentXml(); + + if (!string.IsNullOrEmpty(xml)) + { + builder.Documentation(xml); + } + } + + private void AddTagOutputHint(INamedTypeSymbol type, ITagHelperDescriptorBuilder builder) + { + if (!DesignTime) + { + return; + } + string outputElementHint = null; + var outputElementHintAttribute = type.GetAttributes().Where(a => a.AttributeClass == _outputElementHintAttributeSymbol).FirstOrDefault(); + if (outputElementHintAttribute != null) + { + outputElementHint = (string)(outputElementHintAttribute.ConstructorArguments[0]).Value; + builder.TagOutputHint(outputElementHint); + } + } + + private void ConfigureBoundAttribute( + ITagHelperBoundAttributeDescriptorBuilder builder, + IPropertySymbol property, + INamedTypeSymbol containingType) + { + var attributeNameAttribute = property + .GetAttributes() + .Where(a => a.AttributeClass == _htmlAttributeNameAttributeSymbol) + .FirstOrDefault(); + + bool hasExplicitName; + string attributeName; + if (attributeNameAttribute == null || + attributeNameAttribute.ConstructorArguments.Length == 0 || + string.IsNullOrEmpty((string)attributeNameAttribute.ConstructorArguments[0].Value)) + { + hasExplicitName = false; + attributeName = ToHtmlCase(property.Name); } else { - // All allowed children were invalid, return null to indicate that any child is acceptable. - return null; + hasExplicitName = true; + attributeName = (string)attributeNameAttribute.ConstructorArguments[0].Value; } - } - // Internal for unit testing - internal static IEnumerable GetValidAllowedChildren( - IEnumerable allowedChildren, - string tagHelperName, - ErrorSink errorSink) - { - var validAllowedChildren = new List(); + var hasPublicSetter = property.SetMethod != null && property.SetMethod.DeclaredAccessibility == Accessibility.Public; + var typeName = GetFullName(property.Type); + builder + .TypeName(typeName) + .PropertyName(property.Name); - foreach (var name in allowedChildren) + if (hasPublicSetter) { - if (string.IsNullOrWhiteSpace(name)) + builder.Name(attributeName); + + if (property.Type.TypeKind == TypeKind.Enum) { - var whitespaceError = Resources.FormatTagHelperDescriptorFactory_InvalidRestrictChildrenAttributeNameNullWhitespace( - TagHelperTypes.RestrictChildrenAttribute, - tagHelperName); - errorSink.OnError(SourceLocation.Zero, whitespaceError, length: 0); + builder.AsEnum(); } - else if (TryValidateName( - name, - invalidCharacter => Resources.FormatTagHelperDescriptorFactory_InvalidRestrictChildrenAttributeName( - TagHelperTypes.RestrictChildrenAttribute, - name, - tagHelperName, - invalidCharacter), - errorSink)) + + if (DesignTime) { - validAllowedChildren.Add(name); + var xml = property.GetDocumentationCommentXml(); + + if (!string.IsNullOrEmpty(xml)) + { + builder.Documentation(xml); + } + } + } + else if (hasExplicitName && !IsPotentialDictionaryProperty(property)) + { + // Specified HtmlAttributeNameAttribute.Name though property has no public setter. + var diagnostic = RazorDiagnosticFactory.CreateTagHelper_InvalidAttributeNameNullOrEmpty(GetFullName(containingType), property.Name); + builder.AddDiagnostic(diagnostic); + } + + ConfigureDictionaryBoundAttribute(builder, property, containingType, attributeNameAttribute, attributeName, hasPublicSetter); + } + + private void ConfigureDictionaryBoundAttribute( + ITagHelperBoundAttributeDescriptorBuilder builder, + IPropertySymbol property, + INamedTypeSymbol containingType, + AttributeData attributeNameAttribute, + string attributeName, + bool hasPublicSetter) + { + string dictionaryAttributePrefix = null; + var dictionaryAttributePrefixSet = false; + + if (attributeNameAttribute != null) + { + foreach (var argument in attributeNameAttribute.NamedArguments) + { + if (argument.Key == TagHelperTypes.HtmlAttributeName.DictionaryAttributePrefix) + { + dictionaryAttributePrefix = (string)argument.Value.Value; + dictionaryAttributePrefixSet = true; + break; + } } } - return validAllowedChildren; + var dictionaryArgumentTypes = GetDictionaryArgumentTypes(property); + if (dictionaryArgumentTypes != null) + { + var prefix = dictionaryAttributePrefix; + if (attributeNameAttribute == null || !dictionaryAttributePrefixSet) + { + prefix = attributeName + "-"; + } + + if (prefix != null) + { + var dictionaryValueType = dictionaryArgumentTypes[1]; + var dictionaryValueTypeName = GetFullName(dictionaryValueType); + builder.AsDictionary(prefix, dictionaryValueTypeName); + } + } + + var dictionaryKeyType = dictionaryArgumentTypes?[0]; + + if (dictionaryKeyType?.SpecialType != SpecialType.System_String) + { + if (dictionaryAttributePrefix != null) + { + // DictionaryAttributePrefix is not supported unless associated with an + // IDictionary property. + var diagnostic = RazorDiagnosticFactory.CreateTagHelper_InvalidAttributePrefixNotNull(GetFullName(containingType), property.Name); + builder.AddDiagnostic(diagnostic); + } + + return; + } + else if (!hasPublicSetter && attributeNameAttribute != null && !dictionaryAttributePrefixSet) + { + // Must set DictionaryAttributePrefix when using HtmlAttributeNameAttribute with a dictionary property + // that lacks a public setter. + var diagnostic = RazorDiagnosticFactory.CreateTagHelper_InvalidAttributePrefixNull(GetFullName(containingType), property.Name); + builder.AddDiagnostic(diagnostic); + + return; + } } - private static TagHelperDescriptor BuildTagHelperDescriptor( - string typeName, - string assemblyName, - IEnumerable attributeDescriptors, - AttributeData targetElementAttribute, - IEnumerable allowedChildren, - TagHelperDesignTimeDescriptor designTimeDescriptor) + private IReadOnlyList GetDictionaryArgumentTypes(IPropertySymbol property) { - IEnumerable requiredAttributeDescriptors; - TryGetRequiredAttributeDescriptors( - HtmlTargetElementAttribute_Attributes(targetElementAttribute), - errorSink: null, - descriptors: out requiredAttributeDescriptors); + INamedTypeSymbol dictionaryType; + if ((property.Type as INamedTypeSymbol)?.ConstructedFrom == _iDictionarySymbol) + { + dictionaryType = (INamedTypeSymbol)property.Type; + } + else if (property.Type.AllInterfaces.Any(s => s.ConstructedFrom == _iDictionarySymbol)) + { + dictionaryType = property.Type.AllInterfaces.First(s => s.ConstructedFrom == _iDictionarySymbol); + } + else + { + dictionaryType = null; + } - return BuildTagHelperDescriptor( - HtmlTargetElementAttribute_Tag(targetElementAttribute), - typeName, - assemblyName, - attributeDescriptors, - requiredAttributeDescriptors, - allowedChildren, - HtmlTargetElementAttribute_ParentTag(targetElementAttribute), - HtmlTargetElementAttribute_TagStructure(targetElementAttribute), - designTimeDescriptor); + return dictionaryType?.TypeArguments; } private static string HtmlTargetElementAttribute_Attributes(AttributeData attibute) @@ -321,238 +392,11 @@ namespace Microsoft.CodeAnalysis.Razor return TagStructure.Unspecified; } - private static TagHelperDescriptor BuildTagHelperDescriptor( - string tagName, - string typeName, - string assemblyName, - IEnumerable attributeDescriptors, - IEnumerable requiredAttributeDescriptors, - IEnumerable allowedChildren, - string parentTag, - TagStructure tagStructure, - TagHelperDesignTimeDescriptor designTimeDescriptor) + private bool IsPotentialDictionaryProperty(IPropertySymbol property) { - return new TagHelperDescriptor - { - TagName = tagName, - TypeName = typeName, - AssemblyName = assemblyName, - Attributes = attributeDescriptors, - RequiredAttributes = requiredAttributeDescriptors, - AllowedChildren = allowedChildren, - RequiredParent = parentTag, - TagStructure = tagStructure, - DesignTimeDescriptor = designTimeDescriptor - }; - } - - /// - /// Internal for testing. - /// - internal static bool ValidHtmlTargetElementAttributeNames( - AttributeData attribute, - ErrorSink errorSink) - { - var validTagName = ValidateName(HtmlTargetElementAttribute_Tag(attribute), targetingAttributes: false, errorSink: errorSink); - IEnumerable requiredAttributeDescriptors; - var validRequiredAttributes = TryGetRequiredAttributeDescriptors(HtmlTargetElementAttribute_Attributes(attribute), errorSink, out requiredAttributeDescriptors); - var validParentTagName = ValidateParentTagName(HtmlTargetElementAttribute_ParentTag(attribute), errorSink); - - return validTagName && validRequiredAttributes && validParentTagName; - } - - /// - /// Internal for unit testing. - /// - internal static bool ValidateParentTagName(string parentTag, ErrorSink errorSink) - { - if (parentTag == null) - { - return true; - } - else if (string.IsNullOrWhiteSpace(parentTag)) - { - var error = Resources.FormatHtmlTargetElementAttribute_NameCannotBeNullOrWhitespace( - Resources.TagHelperDescriptorFactory_ParentTag); - errorSink.OnError(SourceLocation.Zero, error, length: 0); - return false; - } - else if (!TryValidateName( - parentTag, - invalidCharacter => Resources.FormatHtmlTargetElementAttribute_InvalidName( - Resources.TagHelperDescriptorFactory_ParentTag.ToLower(), - parentTag, - invalidCharacter), - errorSink)) - { - return false; - } - - return true; - } - - private static bool TryGetRequiredAttributeDescriptors( - string requiredAttributes, - ErrorSink errorSink, - out IEnumerable descriptors) - { - var parser = new RequiredAttributeParser(requiredAttributes); - - return parser.TryParse(errorSink, out descriptors); - } - - private static bool ValidateName(string name, bool targetingAttributes, ErrorSink errorSink) - { - if (!targetingAttributes && - string.Equals( - name, - TagHelperDescriptorProvider.ElementCatchAllTarget, - StringComparison.OrdinalIgnoreCase)) - { - // '*' as the entire name is OK in the HtmlTargetElement catch-all case. - return true; - } - - var targetName = targetingAttributes ? - Resources.TagHelperDescriptorFactory_Attribute : - Resources.TagHelperDescriptorFactory_Tag; - - if (string.IsNullOrWhiteSpace(name)) - { - var error = Resources.FormatHtmlTargetElementAttribute_NameCannotBeNullOrWhitespace(targetName); - errorSink.OnError(SourceLocation.Zero, error, length: 0); - return false; - } - else if (!TryValidateName( - name, - invalidCharacter => Resources.FormatHtmlTargetElementAttribute_InvalidName( - targetName.ToLower(), - name, - invalidCharacter), - errorSink)) - { - return false; - } - - return true; - } - - private static bool TryValidateName( - string name, - Func characterErrorBuilder, - ErrorSink errorSink) - { - var validName = true; - - foreach (var character in name) - { - if (char.IsWhiteSpace(character) || - InvalidNonWhitespaceNameCharacters.Contains(character)) - { - var error = characterErrorBuilder(character); - errorSink.OnError(SourceLocation.Zero, error, length: 0); - - validName = false; - } - } - - return validName; - } - - private IEnumerable GetAttributeDescriptors(INamedTypeSymbol type, ErrorSink errorSink) - { - var attributeDescriptors = new List(); - - // Keep indexer descriptors separate to avoid sorting the combined list later. - var indexerDescriptors = new List(); - - var accessibleProperties = GetAccessibleProperties(type); - foreach (var property in accessibleProperties) - { - if (ShouldSkipDescriptorCreation(property)) - { - continue; - } - - var attributeNameAttribute = property - .GetAttributes() - .Where(a => a.AttributeClass == _htmlAttributeNameAttributeSymbol) - .FirstOrDefault(); - - bool hasExplicitName; - string attributeName; - if (attributeNameAttribute == null || - attributeNameAttribute.ConstructorArguments.Length == 0 || - string.IsNullOrEmpty((string)attributeNameAttribute.ConstructorArguments[0].Value)) - { - hasExplicitName = false; - attributeName = ToHtmlCase(property.Name); - } - else - { - hasExplicitName = true; - attributeName = (string)attributeNameAttribute.ConstructorArguments[0].Value; - } - - TagHelperAttributeDescriptor mainDescriptor = null; - if (property.SetMethod != null && property.SetMethod.DeclaredAccessibility == Accessibility.Public) - { - mainDescriptor = ToAttributeDescriptor(property, attributeName); - if (!ValidateTagHelperAttributeDescriptor(mainDescriptor, type, errorSink)) - { - // HtmlAttributeNameAttribute.Name is invalid. Ignore this property completely. - continue; - } - } - else if (hasExplicitName) - { - // Specified HtmlAttributeNameAttribute.Name though property has no public setter. - errorSink.OnError( - SourceLocation.Zero, - Resources.FormatTagHelperDescriptorFactory_InvalidAttributeNameNotNullOrEmpty( - GetFullName(type), - property.Name, - TagHelperTypes.HtmlAttributeNameAttribute, - TagHelperTypes.HtmlAttributeName.Name), - length: 0); - continue; - } - - bool isInvalid; - var indexerDescriptor = ToIndexerAttributeDescriptor( - property, - attributeNameAttribute, - parentType: type, - errorSink: errorSink, - defaultPrefix: attributeName + "-", - isInvalid: out isInvalid); - if (indexerDescriptor != null && - !ValidateTagHelperAttributeDescriptor(indexerDescriptor, type, errorSink)) - { - isInvalid = true; - } - - if (isInvalid) - { - // The property type or HtmlAttributeNameAttribute.DictionaryAttributePrefix (or perhaps the - // HTML-casing of the property name) is invalid. Ignore this property completely. - continue; - } - - if (mainDescriptor != null) - { - attributeDescriptors.Add(mainDescriptor); - } - - if (indexerDescriptor != null) - { - indexerDescriptors.Add(indexerDescriptor); - } - } - - attributeDescriptors.AddRange(indexerDescriptors); - - return attributeDescriptors; + return + ((property.Type as INamedTypeSymbol)?.ConstructedFrom == _iDictionarySymbol || property.Type.AllInterfaces.Any(s => s.ConstructedFrom == _iDictionarySymbol)) && + GetDictionaryArgumentTypes(property)?[0].SpecialType == SpecialType.System_String; } private IEnumerable GetAccessibleProperties(INamedTypeSymbol typeSymbol) @@ -569,6 +413,9 @@ namespace Microsoft.CodeAnalysis.Razor property.GetMethod != null && property.GetMethod.DeclaredAccessibility == Accessibility.Public && property.GetAttributes().Where(a => a.AttributeClass == _htmlAttributeNotBoundAttributeSymbol).FirstOrDefault() == null && + (property.GetAttributes().Any(a => a.AttributeClass == _htmlAttributeNameAttributeSymbol) || + property.SetMethod != null && property.SetMethod.DeclaredAccessibility == Accessibility.Public || + IsPotentialDictionaryProperty(property)) && !accessibleProperties.ContainsKey(property.Name)) { accessibleProperties.Add(property.Name, property); @@ -582,278 +429,6 @@ namespace Microsoft.CodeAnalysis.Razor return accessibleProperties.Values; } - // Internal for testing. - internal static bool ValidateTagHelperAttributeDescriptor( - TagHelperAttributeDescriptor attributeDescriptor, - INamedTypeSymbol parentType, - ErrorSink errorSink) - { - string nameOrPrefix; - if (attributeDescriptor.IsIndexer) - { - nameOrPrefix = Resources.TagHelperDescriptorFactory_Prefix; - } - else if (string.IsNullOrEmpty(attributeDescriptor.Name)) - { - errorSink.OnError( - SourceLocation.Zero, - Resources.FormatTagHelperDescriptorFactory_InvalidAttributeNameNullOrEmpty( - GetFullName(parentType), - attributeDescriptor.PropertyName), - length: 0); - - return false; - } - else - { - nameOrPrefix = Resources.TagHelperDescriptorFactory_Name; - } - - return ValidateTagHelperAttributeNameOrPrefix( - attributeDescriptor.Name, - parentType, - attributeDescriptor.PropertyName, - errorSink, - nameOrPrefix); - } - - private static bool ValidateTagHelperAttributeNameOrPrefix( - string attributeNameOrPrefix, - INamedTypeSymbol parentType, - string propertyName, - ErrorSink errorSink, - string nameOrPrefix) - { - if (string.IsNullOrEmpty(attributeNameOrPrefix)) - { - // ValidateTagHelperAttributeDescriptor validates Name is non-null and non-empty. The empty string is - // valid for DictionaryAttributePrefix and null is impossible at this point because it means "don't - // create a descriptor". (Empty DictionaryAttributePrefix is a corner case which would bind every - // attribute of a target element. Likely not particularly useful but unclear what minimum length - // should be required and what scenarios a minimum length would break.) - return true; - } - - if (string.IsNullOrWhiteSpace(attributeNameOrPrefix)) - { - // Provide a single error if the entire name is whitespace, not an error per character. - errorSink.OnError( - SourceLocation.Zero, - Resources.FormatTagHelperDescriptorFactory_InvalidAttributeNameOrPrefixWhitespace( - GetFullName(parentType), - propertyName, - nameOrPrefix), - length: 0); - - return false; - } - - // data-* attributes are explicitly not implemented by user agents and are not intended for use on - // the server; therefore it's invalid for TagHelpers to bind to them. - if (attributeNameOrPrefix.StartsWith(DataDashPrefix, StringComparison.OrdinalIgnoreCase)) - { - errorSink.OnError( - SourceLocation.Zero, - Resources.FormatTagHelperDescriptorFactory_InvalidAttributeNameOrPrefixStart( - GetFullName(parentType), - propertyName, - nameOrPrefix, - attributeNameOrPrefix, - DataDashPrefix), - length: 0); - - return false; - } - - var isValid = true; - foreach (var character in attributeNameOrPrefix) - { - if (char.IsWhiteSpace(character) || InvalidNonWhitespaceNameCharacters.Contains(character)) - { - errorSink.OnError( - SourceLocation.Zero, - Resources.FormatTagHelperDescriptorFactory_InvalidAttributeNameOrPrefixCharacter( - GetFullName(parentType), - propertyName, - nameOrPrefix, - attributeNameOrPrefix, - character), - length: 0); - - isValid = false; - } - } - - return isValid; - } - - private TagHelperAttributeDescriptor ToAttributeDescriptor(IPropertySymbol property, string attributeName) - { - return ToAttributeDescriptor( - property, - attributeName, - GetFullName(property.Type), - isIndexer: false, - isStringProperty: property.Type.SpecialType == SpecialType.System_String); - } - - private TagHelperAttributeDescriptor ToIndexerAttributeDescriptor( - IPropertySymbol property, - AttributeData attributeNameAttribute, - INamedTypeSymbol parentType, - ErrorSink errorSink, - string defaultPrefix, - out bool isInvalid) - { - isInvalid = false; - var hasPublicSetter = property.SetMethod != null && property.SetMethod.DeclaredAccessibility == Accessibility.Public; - - - string dictionaryAttributePrefix = null; - bool dictionaryAttributePrefixSet = false; - - if (attributeNameAttribute != null) - { - foreach (var argument in attributeNameAttribute.NamedArguments) - { - if (argument.Key == TagHelperTypes.HtmlAttributeName.DictionaryAttributePrefix) - { - dictionaryAttributePrefix = (string)argument.Value.Value; - dictionaryAttributePrefixSet = true; - break; - } - } - } - - INamedTypeSymbol dictionaryType; - if ((property.Type as INamedTypeSymbol)?.ConstructedFrom == _iDictionarySymbol) - { - dictionaryType = (INamedTypeSymbol)property.Type; - } - else if (property.Type.AllInterfaces.Any(s => s.ConstructedFrom == _iDictionarySymbol)) - { - dictionaryType = property.Type.AllInterfaces.First(s => s.ConstructedFrom == _iDictionarySymbol); - } - else - { - dictionaryType = null; - } - - if (dictionaryType == null || - dictionaryType.TypeArguments[0].SpecialType != SpecialType.System_String) - { - if (dictionaryAttributePrefix != null) - { - // DictionaryAttributePrefix is not supported unless associated with an - // IDictionary property. - isInvalid = true; - errorSink.OnError( - SourceLocation.Zero, - Resources.FormatTagHelperDescriptorFactory_InvalidAttributePrefixNotNull( - GetFullName(parentType), - property.Name, - TagHelperTypes.HtmlAttributeNameAttribute, - TagHelperTypes.HtmlAttributeName.DictionaryAttributePrefix, - "IDictionary"), - length: 0); - } - else if (attributeNameAttribute != null && !hasPublicSetter) - { - // Associated an HtmlAttributeNameAttribute with a non-dictionary property that lacks a public - // setter. - isInvalid = true; - errorSink.OnError( - SourceLocation.Zero, - Resources.FormatTagHelperDescriptorFactory_InvalidAttributeNameAttribute( - GetFullName(parentType), - property.Name, - TagHelperTypes.HtmlAttributeNameAttribute, - "IDictionary"), - length: 0); - } - - return null; - } - else if ( - !hasPublicSetter && - attributeNameAttribute != null && - !dictionaryAttributePrefixSet) - { - // Must set DictionaryAttributePrefix when using HtmlAttributeNameAttribute with a dictionary property - // that lacks a public setter. - isInvalid = true; - errorSink.OnError( - SourceLocation.Zero, - Resources.FormatTagHelperDescriptorFactory_InvalidAttributePrefixNull( - GetFullName(parentType), - property.Name, - TagHelperTypes.HtmlAttributeNameAttribute, - TagHelperTypes.HtmlAttributeName.DictionaryAttributePrefix, - "IDictionary"), - length: 0); - - return null; - } - - // Potential prefix case. Use default prefix (based on name)? - var useDefault = attributeNameAttribute == null || !dictionaryAttributePrefixSet; - - var prefix = useDefault ? defaultPrefix : dictionaryAttributePrefix; - if (prefix == null) - { - // DictionaryAttributePrefix explicitly set to null. Ignore. - return null; - } - - return ToAttributeDescriptor( - property, - attributeName: prefix, - typeName: dictionaryType == null ? null : GetFullName(dictionaryType.TypeArguments[1]), - isIndexer: true, - isStringProperty: dictionaryType == null ? false : dictionaryType.TypeArguments[1].SpecialType == SpecialType.System_String); - } - - private TagHelperAttributeDescriptor ToAttributeDescriptor( - IPropertySymbol property, - string attributeName, - string typeName, - bool isIndexer, - bool isStringProperty) - { - TagHelperAttributeDesignTimeDescriptor designTimeDescriptor = null; - if (DesignTime) - { - XmlMemberDocumentation documentation = null; - var xml = property.GetDocumentationCommentXml(); - if (!string.IsNullOrEmpty(xml)) - { - documentation = new XmlMemberDocumentation(xml); - } - - var remarks = documentation?.GetRemarks(); - var summary = documentation?.GetSummary(); - if (summary != null || remarks != null) - { - designTimeDescriptor = new TagHelperAttributeDesignTimeDescriptor() - { - Remarks = remarks, - Summary = summary, - }; - } - } - - return new TagHelperAttributeDescriptor - { - Name = attributeName, - PropertyName = property.Name, - IsEnum = property.Type.TypeKind == TypeKind.Enum, - TypeName = typeName, - IsStringProperty = isStringProperty, - IsIndexer = isIndexer, - DesignTimeDescriptor = designTimeDescriptor, - }; - } - private bool ShouldSkipDescriptorCreation(ISymbol symbol) { if (DesignTime) @@ -892,327 +467,5 @@ namespace Microsoft.CodeAnalysis.Razor } private static string GetFullName(ITypeSymbol type) => type.ToDisplayString(FullNameTypeDisplayFormat); - - // Internal for testing - internal class RequiredAttributeParser - { - private static readonly IReadOnlyDictionary CssValueComparisons = - new Dictionary - { - { '=', TagHelperRequiredAttributeValueComparison.FullMatch }, - { '^', TagHelperRequiredAttributeValueComparison.PrefixMatch }, - { '$', TagHelperRequiredAttributeValueComparison.SuffixMatch } - }; - private static readonly char[] InvalidPlainAttributeNameCharacters = { ' ', '\t', ',', RequiredAttributeWildcardSuffix }; - private static readonly char[] InvalidCssAttributeNameCharacters = (new[] { ' ', '\t', ',', ']' }) - .Concat(CssValueComparisons.Keys) - .ToArray(); - private static readonly char[] InvalidCssQuotelessValueCharacters = { ' ', '\t', ']' }; - - private int _index; - private string _requiredAttributes; - - public RequiredAttributeParser(string requiredAttributes) - { - _requiredAttributes = requiredAttributes; - } - - private char Current => _requiredAttributes[_index]; - - private bool AtEnd => _index >= _requiredAttributes.Length; - - public bool TryParse( - ErrorSink errorSink, - out IEnumerable requiredAttributeDescriptors) - { - if (string.IsNullOrEmpty(_requiredAttributes)) - { - requiredAttributeDescriptors = Enumerable.Empty(); - return true; - } - - requiredAttributeDescriptors = null; - var descriptors = new List(); - - PassOptionalWhitespace(); - - do - { - TagHelperRequiredAttributeDescriptor descriptor; - if (At('[')) - { - descriptor = ParseCssSelector(errorSink); - } - else - { - descriptor = ParsePlainSelector(errorSink); - } - - if (descriptor == null) - { - // Failed to create the descriptor due to an invalid required attribute. - return false; - } - else - { - descriptors.Add(descriptor); - } - - PassOptionalWhitespace(); - - if (At(',')) - { - _index++; - - if (!EnsureNotAtEnd(errorSink)) - { - return false; - } - } - else if (!AtEnd) - { - errorSink.OnError( - SourceLocation.Zero, - Resources.FormatTagHelperDescriptorFactory_InvalidRequiredAttributeCharacter(Current, _requiredAttributes), - length: 0); - return false; - } - - PassOptionalWhitespace(); - } - while (!AtEnd); - - requiredAttributeDescriptors = descriptors; - return true; - } - - private TagHelperRequiredAttributeDescriptor ParsePlainSelector(ErrorSink errorSink) - { - var nameEndIndex = _requiredAttributes.IndexOfAny(InvalidPlainAttributeNameCharacters, _index); - string attributeName; - - var nameComparison = TagHelperRequiredAttributeNameComparison.FullMatch; - if (nameEndIndex == -1) - { - attributeName = _requiredAttributes.Substring(_index); - _index = _requiredAttributes.Length; - } - else - { - attributeName = _requiredAttributes.Substring(_index, nameEndIndex - _index); - _index = nameEndIndex; - - if (_requiredAttributes[nameEndIndex] == RequiredAttributeWildcardSuffix) - { - nameComparison = TagHelperRequiredAttributeNameComparison.PrefixMatch; - - // Move past wild card - _index++; - } - } - - TagHelperRequiredAttributeDescriptor descriptor = null; - if (ValidateName(attributeName, targetingAttributes: true, errorSink: errorSink)) - { - descriptor = new TagHelperRequiredAttributeDescriptor - { - Name = attributeName, - NameComparison = nameComparison - }; - } - - return descriptor; - } - - private string ParseCssAttributeName(ErrorSink errorSink) - { - var nameStartIndex = _index; - var nameEndIndex = _requiredAttributes.IndexOfAny(InvalidCssAttributeNameCharacters, _index); - nameEndIndex = nameEndIndex == -1 ? _requiredAttributes.Length : nameEndIndex; - _index = nameEndIndex; - - var attributeName = _requiredAttributes.Substring(nameStartIndex, nameEndIndex - nameStartIndex); - - return attributeName; - } - - private TagHelperRequiredAttributeValueComparison? ParseCssValueComparison(ErrorSink errorSink) - { - Debug.Assert(!AtEnd); - TagHelperRequiredAttributeValueComparison valueComparison; - - if (CssValueComparisons.TryGetValue(Current, out valueComparison)) - { - var op = Current; - _index++; - - if (op != '=' && At('=')) - { - // Two length operator (ex: ^=). Move past the second piece - _index++; - } - else if (op != '=') // We're at an incomplete operator (ex: [foo^] - { - errorSink.OnError( - SourceLocation.Zero, - Resources.FormatTagHelperDescriptorFactory_PartialRequiredAttributeOperator(_requiredAttributes, op), - length: 0); - return null; - } - } - else if (!At(']')) - { - errorSink.OnError( - SourceLocation.Zero, - Resources.FormatTagHelperDescriptorFactory_InvalidRequiredAttributeOperator(Current, _requiredAttributes), - length: 0); - return null; - } - - return valueComparison; - } - - private string ParseCssValue(ErrorSink errorSink) - { - int valueStart; - int valueEnd; - if (At('\'') || At('"')) - { - var quote = Current; - - // Move past the quote - _index++; - - valueStart = _index; - valueEnd = _requiredAttributes.IndexOf(quote, _index); - if (valueEnd == -1) - { - errorSink.OnError( - SourceLocation.Zero, - Resources.FormatTagHelperDescriptorFactory_InvalidRequiredAttributeMismatchedQuotes(_requiredAttributes, quote), - length: 0); - return null; - } - _index = valueEnd + 1; - } - else - { - valueStart = _index; - var valueEndIndex = _requiredAttributes.IndexOfAny(InvalidCssQuotelessValueCharacters, _index); - valueEnd = valueEndIndex == -1 ? _requiredAttributes.Length : valueEndIndex; - _index = valueEnd; - } - - var value = _requiredAttributes.Substring(valueStart, valueEnd - valueStart); - - return value; - } - - private TagHelperRequiredAttributeDescriptor ParseCssSelector(ErrorSink errorSink) - { - Debug.Assert(At('[')); - - // Move past '['. - _index++; - PassOptionalWhitespace(); - - var attributeName = ParseCssAttributeName(errorSink); - - PassOptionalWhitespace(); - - if (!EnsureNotAtEnd(errorSink)) - { - return null; - } - - if (!ValidateName(attributeName, targetingAttributes: true, errorSink: errorSink)) - { - // Couldn't parse a valid attribute name. - return null; - } - - var valueComparison = ParseCssValueComparison(errorSink); - - if (!valueComparison.HasValue) - { - return null; - } - - PassOptionalWhitespace(); - - if (!EnsureNotAtEnd(errorSink)) - { - return null; - } - - var value = ParseCssValue(errorSink); - - if (value == null) - { - // Couldn't parse value - return null; - } - - PassOptionalWhitespace(); - - if (At(']')) - { - // Move past the ending bracket. - _index++; - } - else if (AtEnd) - { - errorSink.OnError( - SourceLocation.Zero, - Resources.FormatTagHelperDescriptorFactory_CouldNotFindMatchingEndBrace(_requiredAttributes), - length: 0); - return null; - } - else - { - errorSink.OnError( - SourceLocation.Zero, - Resources.FormatTagHelperDescriptorFactory_InvalidRequiredAttributeCharacter(Current, _requiredAttributes), - length: 0); - return null; - } - - return new TagHelperRequiredAttributeDescriptor - { - Name = attributeName, - NameComparison = TagHelperRequiredAttributeNameComparison.FullMatch, - Value = value, - ValueComparison = valueComparison.Value, - }; - } - - private bool EnsureNotAtEnd(ErrorSink errorSink) - { - if (AtEnd) - { - errorSink.OnError( - SourceLocation.Zero, - Resources.FormatTagHelperDescriptorFactory_CouldNotFindMatchingEndBrace(_requiredAttributes), - length: 0); - - return false; - } - - return true; - } - - private bool At(char c) - { - return !AtEnd && Current == c; - } - - private void PassOptionalWhitespace() - { - while (!AtEnd && (Current == ' ' || Current == '\t')) - { - _index++; - } - } - } } } \ No newline at end of file diff --git a/src/Microsoft.CodeAnalysis.Razor/Properties/Resources.Designer.cs b/src/Microsoft.CodeAnalysis.Razor/Properties/Resources.Designer.cs index 3b1139091e..4c8094d092 100644 --- a/src/Microsoft.CodeAnalysis.Razor/Properties/Resources.Designer.cs +++ b/src/Microsoft.CodeAnalysis.Razor/Properties/Resources.Designer.cs @@ -10,373 +10,117 @@ namespace Microsoft.CodeAnalysis.Razor private static readonly ResourceManager _resourceManager = new ResourceManager("Microsoft.CodeAnalysis.Razor.Resources", typeof(Resources).GetTypeInfo().Assembly); - /// - /// {0} cannot be null or an empty string. - /// - internal static string Argument_Cannot_Be_Null_Or_Empty - { - get { return GetString("Argument_Cannot_Be_Null_Or_Empty"); } - } - - /// - /// {0} cannot be null or an empty string. - /// - internal static string FormatArgument_Cannot_Be_Null_Or_Empty(object p0) - { - return string.Format(CultureInfo.CurrentCulture, GetString("Argument_Cannot_Be_Null_Or_Empty"), p0); - } - - /// - /// Tag helpers cannot target {0} name '{1}' because it contains a '{2}' character. - /// - internal static string HtmlTargetElementAttribute_InvalidName - { - get { return GetString("HtmlTargetElementAttribute_InvalidName"); } - } - - /// - /// Tag helpers cannot target {0} name '{1}' because it contains a '{2}' character. - /// - internal static string FormatHtmlTargetElementAttribute_InvalidName(object p0, object p1, object p2) - { - return string.Format(CultureInfo.CurrentCulture, GetString("HtmlTargetElementAttribute_InvalidName"), p0, p1, p2); - } - - /// - /// {0} name cannot be null or whitespace. - /// - internal static string HtmlTargetElementAttribute_NameCannotBeNullOrWhitespace - { - get { return GetString("HtmlTargetElementAttribute_NameCannotBeNullOrWhitespace"); } - } - - /// - /// {0} name cannot be null or whitespace. - /// - internal static string FormatHtmlTargetElementAttribute_NameCannotBeNullOrWhitespace(object p0) - { - return string.Format(CultureInfo.CurrentCulture, GetString("HtmlTargetElementAttribute_NameCannotBeNullOrWhitespace"), p0); - } - - /// - /// Attribute - /// - internal static string TagHelperDescriptorFactory_Attribute - { - get { return GetString("TagHelperDescriptorFactory_Attribute"); } - } - - /// - /// Attribute - /// - internal static string FormatTagHelperDescriptorFactory_Attribute() - { - return GetString("TagHelperDescriptorFactory_Attribute"); - } - /// /// Could not find matching ']' for required attribute '{0}'. /// internal static string TagHelperDescriptorFactory_CouldNotFindMatchingEndBrace { - get { return GetString("TagHelperDescriptorFactory_CouldNotFindMatchingEndBrace"); } + get => GetString("TagHelperDescriptorFactory_CouldNotFindMatchingEndBrace"); } /// /// Could not find matching ']' for required attribute '{0}'. /// internal static string FormatTagHelperDescriptorFactory_CouldNotFindMatchingEndBrace(object p0) - { - return string.Format(CultureInfo.CurrentCulture, GetString("TagHelperDescriptorFactory_CouldNotFindMatchingEndBrace"), p0); - } - - /// - /// Invalid tag helper bound property '{0}.{1}'. An '{2}' must not be associated with a property with no public setter unless its type implements '{3}'. - /// - internal static string TagHelperDescriptorFactory_InvalidAttributeNameAttribute - { - get { return GetString("TagHelperDescriptorFactory_InvalidAttributeNameAttribute"); } - } - - /// - /// Invalid tag helper bound property '{0}.{1}'. An '{2}' must not be associated with a property with no public setter unless its type implements '{3}'. - /// - internal static string FormatTagHelperDescriptorFactory_InvalidAttributeNameAttribute(object p0, object p1, object p2, object p3) - { - return string.Format(CultureInfo.CurrentCulture, GetString("TagHelperDescriptorFactory_InvalidAttributeNameAttribute"), p0, p1, p2, p3); - } - - /// - /// Invalid tag helper bound property '{0}.{1}'. '{2}.{3}' must be null or empty if property has no public setter. - /// - internal static string TagHelperDescriptorFactory_InvalidAttributeNameNotNullOrEmpty - { - get { return GetString("TagHelperDescriptorFactory_InvalidAttributeNameNotNullOrEmpty"); } - } - - /// - /// Invalid tag helper bound property '{0}.{1}'. '{2}.{3}' must be null or empty if property has no public setter. - /// - internal static string FormatTagHelperDescriptorFactory_InvalidAttributeNameNotNullOrEmpty(object p0, object p1, object p2, object p3) - { - return string.Format(CultureInfo.CurrentCulture, GetString("TagHelperDescriptorFactory_InvalidAttributeNameNotNullOrEmpty"), p0, p1, p2, p3); - } - - /// - /// Invalid tag helper bound property '{0}.{1}'. Tag helpers cannot bind to HTML attributes with a null or empty name. - /// - internal static string TagHelperDescriptorFactory_InvalidAttributeNameNullOrEmpty - { - get { return GetString("TagHelperDescriptorFactory_InvalidAttributeNameNullOrEmpty"); } - } - - /// - /// Invalid tag helper bound property '{0}.{1}'. Tag helpers cannot bind to HTML attributes with a null or empty name. - /// - internal static string FormatTagHelperDescriptorFactory_InvalidAttributeNameNullOrEmpty(object p0, object p1) - { - return string.Format(CultureInfo.CurrentCulture, GetString("TagHelperDescriptorFactory_InvalidAttributeNameNullOrEmpty"), p0, p1); - } - - /// - /// Invalid tag helper bound property '{0}.{1}'. Tag helpers cannot bind to HTML attributes with {2} '{3}' because {2} contains a '{4}' character. - /// - internal static string TagHelperDescriptorFactory_InvalidAttributeNameOrPrefixCharacter - { - get { return GetString("TagHelperDescriptorFactory_InvalidAttributeNameOrPrefixCharacter"); } - } - - /// - /// Invalid tag helper bound property '{0}.{1}'. Tag helpers cannot bind to HTML attributes with {2} '{3}' because {2} contains a '{4}' character. - /// - internal static string FormatTagHelperDescriptorFactory_InvalidAttributeNameOrPrefixCharacter(object p0, object p1, object p2, object p3, object p4) - { - return string.Format(CultureInfo.CurrentCulture, GetString("TagHelperDescriptorFactory_InvalidAttributeNameOrPrefixCharacter"), p0, p1, p2, p3, p4); - } - - /// - /// Invalid tag helper bound property '{0}.{1}'. Tag helpers cannot bind to HTML attributes with {2} '{3}' because {2} starts with '{4}'. - /// - internal static string TagHelperDescriptorFactory_InvalidAttributeNameOrPrefixStart - { - get { return GetString("TagHelperDescriptorFactory_InvalidAttributeNameOrPrefixStart"); } - } - - /// - /// Invalid tag helper bound property '{0}.{1}'. Tag helpers cannot bind to HTML attributes with {2} '{3}' because {2} starts with '{4}'. - /// - internal static string FormatTagHelperDescriptorFactory_InvalidAttributeNameOrPrefixStart(object p0, object p1, object p2, object p3, object p4) - { - return string.Format(CultureInfo.CurrentCulture, GetString("TagHelperDescriptorFactory_InvalidAttributeNameOrPrefixStart"), p0, p1, p2, p3, p4); - } - - /// - /// Invalid tag helper bound property '{0}.{1}'. Tag helpers cannot bind to HTML attributes with a whitespace {2}. - /// - internal static string TagHelperDescriptorFactory_InvalidAttributeNameOrPrefixWhitespace - { - get { return GetString("TagHelperDescriptorFactory_InvalidAttributeNameOrPrefixWhitespace"); } - } - - /// - /// Invalid tag helper bound property '{0}.{1}'. Tag helpers cannot bind to HTML attributes with a whitespace {2}. - /// - internal static string FormatTagHelperDescriptorFactory_InvalidAttributeNameOrPrefixWhitespace(object p0, object p1, object p2) - { - return string.Format(CultureInfo.CurrentCulture, GetString("TagHelperDescriptorFactory_InvalidAttributeNameOrPrefixWhitespace"), p0, p1, p2); - } + => string.Format(CultureInfo.CurrentCulture, GetString("TagHelperDescriptorFactory_CouldNotFindMatchingEndBrace"), p0); /// /// Invalid tag helper bound property '{0}.{1}'. '{2}.{3}' must be null unless property type implements '{4}'. /// internal static string TagHelperDescriptorFactory_InvalidAttributePrefixNotNull { - get { return GetString("TagHelperDescriptorFactory_InvalidAttributePrefixNotNull"); } + get => GetString("TagHelperDescriptorFactory_InvalidAttributePrefixNotNull"); } /// /// Invalid tag helper bound property '{0}.{1}'. '{2}.{3}' must be null unless property type implements '{4}'. /// internal static string FormatTagHelperDescriptorFactory_InvalidAttributePrefixNotNull(object p0, object p1, object p2, object p3, object p4) + => string.Format(CultureInfo.CurrentCulture, GetString("TagHelperDescriptorFactory_InvalidAttributePrefixNotNull"), p0, p1, p2, p3, p4); + + /// + /// Invalid tag helper bound property '{0}.{1}'. '{2}.{3}' must be null or empty if property has no public setter. + /// + internal static string TagHelperDescriptorFactory_InvalidAttributeNameNotNullOrEmpty { - return string.Format(CultureInfo.CurrentCulture, GetString("TagHelperDescriptorFactory_InvalidAttributePrefixNotNull"), p0, p1, p2, p3, p4); + get => GetString("TagHelperDescriptorFactory_InvalidAttributeNameNotNullOrEmpty"); } + /// + /// Invalid tag helper bound property '{0}.{1}'. '{2}.{3}' must be null or empty if property has no public setter. + /// + internal static string FormatTagHelperDescriptorFactory_InvalidAttributeNameNotNullOrEmpty(object p0, object p1, object p2, object p3) + => string.Format(CultureInfo.CurrentCulture, GetString("TagHelperDescriptorFactory_InvalidAttributeNameNotNullOrEmpty"), p0, p1, p2, p3); + /// /// Invalid tag helper bound property '{0}.{1}'. '{2}.{3}' must not be null if property has no public setter and its type implements '{4}'. /// internal static string TagHelperDescriptorFactory_InvalidAttributePrefixNull { - get { return GetString("TagHelperDescriptorFactory_InvalidAttributePrefixNull"); } + get => GetString("TagHelperDescriptorFactory_InvalidAttributePrefixNull"); } /// /// Invalid tag helper bound property '{0}.{1}'. '{2}.{3}' must not be null if property has no public setter and its type implements '{4}'. /// internal static string FormatTagHelperDescriptorFactory_InvalidAttributePrefixNull(object p0, object p1, object p2, object p3, object p4) - { - return string.Format(CultureInfo.CurrentCulture, GetString("TagHelperDescriptorFactory_InvalidAttributePrefixNull"), p0, p1, p2, p3, p4); - } + => string.Format(CultureInfo.CurrentCulture, GetString("TagHelperDescriptorFactory_InvalidAttributePrefixNull"), p0, p1, p2, p3, p4); /// /// Invalid required attribute character '{0}' in required attribute '{1}'. Separate required attributes with commas. /// internal static string TagHelperDescriptorFactory_InvalidRequiredAttributeCharacter { - get { return GetString("TagHelperDescriptorFactory_InvalidRequiredAttributeCharacter"); } + get => GetString("TagHelperDescriptorFactory_InvalidRequiredAttributeCharacter"); } /// /// Invalid required attribute character '{0}' in required attribute '{1}'. Separate required attributes with commas. /// internal static string FormatTagHelperDescriptorFactory_InvalidRequiredAttributeCharacter(object p0, object p1) - { - return string.Format(CultureInfo.CurrentCulture, GetString("TagHelperDescriptorFactory_InvalidRequiredAttributeCharacter"), p0, p1); - } + => string.Format(CultureInfo.CurrentCulture, GetString("TagHelperDescriptorFactory_InvalidRequiredAttributeCharacter"), p0, p1); /// /// Required attribute '{0}' has mismatched quotes '{1}' around value. /// internal static string TagHelperDescriptorFactory_InvalidRequiredAttributeMismatchedQuotes { - get { return GetString("TagHelperDescriptorFactory_InvalidRequiredAttributeMismatchedQuotes"); } + get => GetString("TagHelperDescriptorFactory_InvalidRequiredAttributeMismatchedQuotes"); } /// /// Required attribute '{0}' has mismatched quotes '{1}' around value. /// internal static string FormatTagHelperDescriptorFactory_InvalidRequiredAttributeMismatchedQuotes(object p0, object p1) - { - return string.Format(CultureInfo.CurrentCulture, GetString("TagHelperDescriptorFactory_InvalidRequiredAttributeMismatchedQuotes"), p0, p1); - } + => string.Format(CultureInfo.CurrentCulture, GetString("TagHelperDescriptorFactory_InvalidRequiredAttributeMismatchedQuotes"), p0, p1); /// /// Invalid character '{0}' in required attribute '{1}'. Expected supported CSS operator or ']'. /// internal static string TagHelperDescriptorFactory_InvalidRequiredAttributeOperator { - get { return GetString("TagHelperDescriptorFactory_InvalidRequiredAttributeOperator"); } + get => GetString("TagHelperDescriptorFactory_InvalidRequiredAttributeOperator"); } /// /// Invalid character '{0}' in required attribute '{1}'. Expected supported CSS operator or ']'. /// internal static string FormatTagHelperDescriptorFactory_InvalidRequiredAttributeOperator(object p0, object p1) - { - return string.Format(CultureInfo.CurrentCulture, GetString("TagHelperDescriptorFactory_InvalidRequiredAttributeOperator"), p0, p1); - } - - /// - /// Invalid '{0}' tag name '{1}' for tag helper '{2}'. Tag helpers cannot restrict child elements that contain a '{3}' character. - /// - internal static string TagHelperDescriptorFactory_InvalidRestrictChildrenAttributeName - { - get { return GetString("TagHelperDescriptorFactory_InvalidRestrictChildrenAttributeName"); } - } - - /// - /// Invalid '{0}' tag name '{1}' for tag helper '{2}'. Tag helpers cannot restrict child elements that contain a '{3}' character. - /// - internal static string FormatTagHelperDescriptorFactory_InvalidRestrictChildrenAttributeName(object p0, object p1, object p2, object p3) - { - return string.Format(CultureInfo.CurrentCulture, GetString("TagHelperDescriptorFactory_InvalidRestrictChildrenAttributeName"), p0, p1, p2, p3); - } - - /// - /// Invalid '{0}' tag name for tag helper '{1}'. Name cannot be null or whitespace. - /// - internal static string TagHelperDescriptorFactory_InvalidRestrictChildrenAttributeNameNullWhitespace - { - get { return GetString("TagHelperDescriptorFactory_InvalidRestrictChildrenAttributeNameNullWhitespace"); } - } - - /// - /// Invalid '{0}' tag name for tag helper '{1}'. Name cannot be null or whitespace. - /// - internal static string FormatTagHelperDescriptorFactory_InvalidRestrictChildrenAttributeNameNullWhitespace(object p0, object p1) - { - return string.Format(CultureInfo.CurrentCulture, GetString("TagHelperDescriptorFactory_InvalidRestrictChildrenAttributeNameNullWhitespace"), p0, p1); - } - - /// - /// name - /// - internal static string TagHelperDescriptorFactory_Name - { - get { return GetString("TagHelperDescriptorFactory_Name"); } - } - - /// - /// name - /// - internal static string FormatTagHelperDescriptorFactory_Name() - { - return GetString("TagHelperDescriptorFactory_Name"); - } - - /// - /// Parent Tag - /// - internal static string TagHelperDescriptorFactory_ParentTag - { - get { return GetString("TagHelperDescriptorFactory_ParentTag"); } - } - - /// - /// Parent Tag - /// - internal static string FormatTagHelperDescriptorFactory_ParentTag() - { - return GetString("TagHelperDescriptorFactory_ParentTag"); - } + => string.Format(CultureInfo.CurrentCulture, GetString("TagHelperDescriptorFactory_InvalidRequiredAttributeOperator"), p0, p1); /// /// Required attribute '{0}' has a partial CSS operator. '{1}' must be followed by an equals. /// internal static string TagHelperDescriptorFactory_PartialRequiredAttributeOperator { - get { return GetString("TagHelperDescriptorFactory_PartialRequiredAttributeOperator"); } + get => GetString("TagHelperDescriptorFactory_PartialRequiredAttributeOperator"); } /// /// Required attribute '{0}' has a partial CSS operator. '{1}' must be followed by an equals. /// internal static string FormatTagHelperDescriptorFactory_PartialRequiredAttributeOperator(object p0, object p1) - { - return string.Format(CultureInfo.CurrentCulture, GetString("TagHelperDescriptorFactory_PartialRequiredAttributeOperator"), p0, p1); - } - - /// - /// prefix - /// - internal static string TagHelperDescriptorFactory_Prefix - { - get { return GetString("TagHelperDescriptorFactory_Prefix"); } - } - - /// - /// prefix - /// - internal static string FormatTagHelperDescriptorFactory_Prefix() - { - return GetString("TagHelperDescriptorFactory_Prefix"); - } - - /// - /// Tag - /// - internal static string TagHelperDescriptorFactory_Tag - { - get { return GetString("TagHelperDescriptorFactory_Tag"); } - } - - /// - /// Tag - /// - internal static string FormatTagHelperDescriptorFactory_Tag() - { - return GetString("TagHelperDescriptorFactory_Tag"); - } + => string.Format(CultureInfo.CurrentCulture, GetString("TagHelperDescriptorFactory_PartialRequiredAttributeOperator"), p0, p1); private static string GetString(string name, params string[] formatterNames) { diff --git a/src/Microsoft.CodeAnalysis.Razor/Properties/ViewComponentResources.Designer.cs b/src/Microsoft.CodeAnalysis.Razor/Properties/ViewComponentResources.Designer.cs index d2f1b85b09..343bd246be 100644 --- a/src/Microsoft.CodeAnalysis.Razor/Properties/ViewComponentResources.Designer.cs +++ b/src/Microsoft.CodeAnalysis.Razor/Properties/ViewComponentResources.Designer.cs @@ -15,80 +15,70 @@ namespace Microsoft.CodeAnalysis.Razor /// internal static string ViewComponent_AmbiguousMethods { - get { return GetString("ViewComponent_AmbiguousMethods"); } + get => GetString("ViewComponent_AmbiguousMethods"); } /// /// View component '{0}' must have exactly one public method named '{1}' or '{2}'. /// internal static string FormatViewComponent_AmbiguousMethods(object p0, object p1, object p2) - { - return string.Format(CultureInfo.CurrentCulture, GetString("ViewComponent_AmbiguousMethods"), p0, p1, p2); - } + => string.Format(CultureInfo.CurrentCulture, GetString("ViewComponent_AmbiguousMethods"), p0, p1, p2); /// /// Method '{0}' of view component '{1}' should be declared to return {2}&lt;T&gt;. /// internal static string ViewComponent_AsyncMethod_ShouldReturnTask { - get { return GetString("ViewComponent_AsyncMethod_ShouldReturnTask"); } + get => GetString("ViewComponent_AsyncMethod_ShouldReturnTask"); } /// /// Method '{0}' of view component '{1}' should be declared to return {2}&lt;T&gt;. /// internal static string FormatViewComponent_AsyncMethod_ShouldReturnTask(object p0, object p1, object p2) - { - return string.Format(CultureInfo.CurrentCulture, GetString("ViewComponent_AsyncMethod_ShouldReturnTask"), p0, p1, p2); - } + => string.Format(CultureInfo.CurrentCulture, GetString("ViewComponent_AsyncMethod_ShouldReturnTask"), p0, p1, p2); /// /// Could not find an '{0}' or '{1}' method for the view component '{2}'. /// internal static string ViewComponent_CannotFindMethod { - get { return GetString("ViewComponent_CannotFindMethod"); } + get => GetString("ViewComponent_CannotFindMethod"); } /// /// Could not find an '{0}' or '{1}' method for the view component '{2}'. /// internal static string FormatViewComponent_CannotFindMethod(object p0, object p1, object p2) - { - return string.Format(CultureInfo.CurrentCulture, GetString("ViewComponent_CannotFindMethod"), p0, p1, p2); - } + => string.Format(CultureInfo.CurrentCulture, GetString("ViewComponent_CannotFindMethod"), p0, p1, p2); /// /// Method '{0}' of view component '{1}' cannot return a {2}. /// internal static string ViewComponent_SyncMethod_CannotReturnTask { - get { return GetString("ViewComponent_SyncMethod_CannotReturnTask"); } + get => GetString("ViewComponent_SyncMethod_CannotReturnTask"); } /// /// Method '{0}' of view component '{1}' cannot return a {2}. /// internal static string FormatViewComponent_SyncMethod_CannotReturnTask(object p0, object p1, object p2) - { - return string.Format(CultureInfo.CurrentCulture, GetString("ViewComponent_SyncMethod_CannotReturnTask"), p0, p1, p2); - } + => string.Format(CultureInfo.CurrentCulture, GetString("ViewComponent_SyncMethod_CannotReturnTask"), p0, p1, p2); /// /// Method '{0}' of view component '{1}' should be declared to return a value. /// internal static string ViewComponent_SyncMethod_ShouldReturnValue { - get { return GetString("ViewComponent_SyncMethod_ShouldReturnValue"); } + get => GetString("ViewComponent_SyncMethod_ShouldReturnValue"); } /// /// Method '{0}' of view component '{1}' should be declared to return a value. /// internal static string FormatViewComponent_SyncMethod_ShouldReturnValue(object p0, object p1) - { - return string.Format(CultureInfo.CurrentCulture, GetString("ViewComponent_SyncMethod_ShouldReturnValue"), p0, p1); - } + => string.Format(CultureInfo.CurrentCulture, GetString("ViewComponent_SyncMethod_ShouldReturnValue"), p0, p1); private static string GetString(string name, params string[] formatterNames) { diff --git a/src/Microsoft.CodeAnalysis.Razor/RazorDiagnosticFactory.cs b/src/Microsoft.CodeAnalysis.Razor/RazorDiagnosticFactory.cs new file mode 100644 index 0000000000..ae28bf4106 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Razor/RazorDiagnosticFactory.cs @@ -0,0 +1,185 @@ +// 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.Evolution; + +namespace Microsoft.CodeAnalysis.Razor +{ + internal static class RazorDiagnosticFactory + { + private const string DiagnosticPrefix = "RZ"; + + /* + * Razor.Evolution starts at 0, 1000, 2000, 3000. Therefore, we should offset by 500 to ensure we can easily + * maintain this list of diagnostic descriptors in conjunction with the one in Razor.Evolution. + */ + + #region General Errors + + /* + * General Errors ID Offset = 500 + */ + + #endregion + + #region Language Errors + + /* + * Language Errors ID Offset = 1500 + */ + + #endregion + + #region Semantic Errors + + /* + * Semantic Errors ID Offset = 2500 + */ + + #endregion + + #region TagHelper Errors + + /* + * TagHelper Errors ID Offset = 3500 + */ + + private static readonly RazorDiagnosticDescriptor TagHelper_InvalidAttributeNameNullOrEmpty = + new RazorDiagnosticDescriptor( + $"{DiagnosticPrefix}3500", + () => Resources.TagHelperDescriptorFactory_InvalidAttributeNameNotNullOrEmpty, + RazorDiagnosticSeverity.Error); + public static RazorDiagnostic CreateTagHelper_InvalidAttributeNameNullOrEmpty(string containingTypeName, string boundPropertyName) + { + var diagnostic = RazorDiagnostic.Create( + TagHelper_InvalidAttributeNameNullOrEmpty, + new SourceSpan(SourceLocation.Undefined, contentLength: 0), + containingTypeName, + boundPropertyName, + TagHelperTypes.HtmlAttributeNameAttribute, + TagHelperTypes.HtmlAttributeName.Name); + + return diagnostic; + } + + private static readonly RazorDiagnosticDescriptor TagHelper_InvalidAttributePrefixNotNull = + new RazorDiagnosticDescriptor( + $"{DiagnosticPrefix}3501", + () => Resources.TagHelperDescriptorFactory_InvalidAttributePrefixNotNull, + RazorDiagnosticSeverity.Error); + public static RazorDiagnostic CreateTagHelper_InvalidAttributePrefixNotNull(string containingTypeName, string boundPropertyName) + { + var diagnostic = RazorDiagnostic.Create( + TagHelper_InvalidAttributePrefixNotNull, + new SourceSpan(SourceLocation.Undefined, contentLength: 0), + containingTypeName, + boundPropertyName, + TagHelperTypes.HtmlAttributeNameAttribute, + TagHelperTypes.HtmlAttributeName.DictionaryAttributePrefix, + "IDictionary"); + + return diagnostic; + } + + private static readonly RazorDiagnosticDescriptor TagHelper_InvalidAttributePrefixNull = + new RazorDiagnosticDescriptor( + $"{DiagnosticPrefix}3502", + () => Resources.TagHelperDescriptorFactory_InvalidAttributePrefixNull, + RazorDiagnosticSeverity.Error); + public static RazorDiagnostic CreateTagHelper_InvalidAttributePrefixNull(string containingTypeName, string boundPropertyName) + { + var diagnostic = RazorDiagnostic.Create( + TagHelper_InvalidAttributePrefixNull, + new SourceSpan(SourceLocation.Undefined, contentLength: 0), + containingTypeName, + boundPropertyName, + TagHelperTypes.HtmlAttributeNameAttribute, + TagHelperTypes.HtmlAttributeName.DictionaryAttributePrefix, + "IDictionary"); + + return diagnostic; + } + + private static readonly RazorDiagnosticDescriptor TagHelper_InvalidRequiredAttributeCharacter = + new RazorDiagnosticDescriptor( + $"{DiagnosticPrefix}3503", + () => Resources.TagHelperDescriptorFactory_InvalidRequiredAttributeCharacter, + RazorDiagnosticSeverity.Error); + public static RazorDiagnostic CreateTagHelper_InvalidRequiredAttributeCharacter(char invalidCharacter, string requiredAttributes) + { + var diagnostic = RazorDiagnostic.Create( + TagHelper_InvalidRequiredAttributeCharacter, + new SourceSpan(SourceLocation.Undefined, contentLength: 0), + invalidCharacter, + requiredAttributes); + + return diagnostic; + } + + private static readonly RazorDiagnosticDescriptor TagHelper_PartialRequiredAttributeOperator = + new RazorDiagnosticDescriptor( + $"{DiagnosticPrefix}3504", + () => Resources.TagHelperDescriptorFactory_PartialRequiredAttributeOperator, + RazorDiagnosticSeverity.Error); + public static RazorDiagnostic CreateTagHelper_PartialRequiredAttributeOperator(char partialOperator, string requiredAttributes) + { + var diagnostic = RazorDiagnostic.Create( + TagHelper_PartialRequiredAttributeOperator, + new SourceSpan(SourceLocation.Undefined, contentLength: 0), + requiredAttributes, + partialOperator); + + return diagnostic; + } + + private static readonly RazorDiagnosticDescriptor TagHelper_InvalidRequiredAttributeOperator = + new RazorDiagnosticDescriptor( + $"{DiagnosticPrefix}3505", + () => Resources.TagHelperDescriptorFactory_InvalidRequiredAttributeOperator, + RazorDiagnosticSeverity.Error); + public static RazorDiagnostic CreateTagHelper_InvalidRequiredAttributeOperator(char invalidOperator, string requiredAttributes) + { + var diagnostic = RazorDiagnostic.Create( + TagHelper_InvalidRequiredAttributeOperator, + new SourceSpan(SourceLocation.Undefined, contentLength: 0), + invalidOperator, + requiredAttributes); + + return diagnostic; + } + + private static readonly RazorDiagnosticDescriptor TagHelper_InvalidRequiredAttributeMismatchedQuotes = + new RazorDiagnosticDescriptor( + $"{DiagnosticPrefix}3506", + () => Resources.TagHelperDescriptorFactory_InvalidRequiredAttributeMismatchedQuotes, + RazorDiagnosticSeverity.Error); + public static RazorDiagnostic CreateTagHelper_InvalidRequiredAttributeMismatchedQuotes(char quote, string requiredAttributes) + { + var diagnostic = RazorDiagnostic.Create( + TagHelper_InvalidRequiredAttributeMismatchedQuotes, + new SourceSpan(SourceLocation.Undefined, contentLength: 0), + requiredAttributes, + quote); + + return diagnostic; + } + + private static readonly RazorDiagnosticDescriptor TagHelper_CouldNotFindMatchingEndBrace = + new RazorDiagnosticDescriptor( + $"{DiagnosticPrefix}3507", + () => Resources.TagHelperDescriptorFactory_CouldNotFindMatchingEndBrace, + RazorDiagnosticSeverity.Error); + public static RazorDiagnostic CreateTagHelper_CouldNotFindMatchingEndBrace(string requiredAttributes) + { + var diagnostic = RazorDiagnostic.Create( + TagHelper_CouldNotFindMatchingEndBrace, + new SourceSpan(SourceLocation.Undefined, contentLength: 0), + requiredAttributes); + + return diagnostic; + } + + + #endregion + } +} diff --git a/src/Microsoft.CodeAnalysis.Razor/RequiredAttributeParser.cs b/src/Microsoft.CodeAnalysis.Razor/RequiredAttributeParser.cs new file mode 100644 index 0000000000..c6dbfb9c87 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Razor/RequiredAttributeParser.cs @@ -0,0 +1,304 @@ +// 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.Diagnostics; +using System.Linq; +using Microsoft.AspNetCore.Razor.Evolution; + +namespace Microsoft.CodeAnalysis.Razor +{ + // Internal for testing + internal static class RequiredAttributeParser + { + public static void AddRequiredAttributes(string requiredAttributes, TagMatchingRuleBuilder ruleBuilder) + { + var requiredAttributeParser = new DefaultRequiredAttributeParser(requiredAttributes); + requiredAttributeParser.AddRequiredAttributes(ruleBuilder); + } + + private class DefaultRequiredAttributeParser + { + private const char RequiredAttributeWildcardSuffix = '*'; + + private static readonly IReadOnlyDictionary CssValueComparisons = + new Dictionary + { + { '=', RequiredAttributeDescriptor.ValueComparisonMode.FullMatch }, + { '^', RequiredAttributeDescriptor.ValueComparisonMode.PrefixMatch }, + { '$', RequiredAttributeDescriptor.ValueComparisonMode.SuffixMatch } + }; + private static readonly char[] InvalidPlainAttributeNameCharacters = { ' ', '\t', ',', RequiredAttributeWildcardSuffix }; + private static readonly char[] InvalidCssAttributeNameCharacters = (new[] { ' ', '\t', ',', ']' }) + .Concat(CssValueComparisons.Keys) + .ToArray(); + private static readonly char[] InvalidCssQuotelessValueCharacters = { ' ', '\t', ']' }; + + private int _index; + private string _requiredAttributes; + + public DefaultRequiredAttributeParser(string requiredAttributes) + { + _requiredAttributes = requiredAttributes; + } + + private char Current => _requiredAttributes[_index]; + + private bool AtEnd => _index >= _requiredAttributes.Length; + + public void AddRequiredAttributes(TagMatchingRuleBuilder ruleBuilder) + { + if (string.IsNullOrEmpty(_requiredAttributes)) + { + return; + } + var descriptors = new List(); + + PassOptionalWhitespace(); + + do + { + var successfulParse = true; + ruleBuilder.RequireAttribute(attributeBuilder => + { + if (At('[')) + { + if (!TryParseCssSelector(attributeBuilder)) + { + successfulParse = false; + return; + } + } + else + { + ParsePlainSelector(attributeBuilder); + } + + PassOptionalWhitespace(); + + if (At(',')) + { + _index++; + + if (!EnsureNotAtEnd(attributeBuilder)) + { + successfulParse = false; + return; + } + } + else if (!AtEnd) + { + var diagnostic = RazorDiagnosticFactory.CreateTagHelper_InvalidRequiredAttributeCharacter(Current, _requiredAttributes); + attributeBuilder.AddDiagnostic(diagnostic); + successfulParse = false; + return; + } + + PassOptionalWhitespace(); + }); + + if (!successfulParse) + { + break; + } + } + while (!AtEnd); + } + + private void ParsePlainSelector(RequiredAttributeDescriptorBuilder attributeBuilder) + { + var nameEndIndex = _requiredAttributes.IndexOfAny(InvalidPlainAttributeNameCharacters, _index); + string attributeName; + + var nameComparison = RequiredAttributeDescriptor.NameComparisonMode.FullMatch; + if (nameEndIndex == -1) + { + attributeName = _requiredAttributes.Substring(_index); + _index = _requiredAttributes.Length; + } + else + { + attributeName = _requiredAttributes.Substring(_index, nameEndIndex - _index); + _index = nameEndIndex; + + if (_requiredAttributes[nameEndIndex] == RequiredAttributeWildcardSuffix) + { + nameComparison = RequiredAttributeDescriptor.NameComparisonMode.PrefixMatch; + + // Move past wild card + _index++; + } + } + + attributeBuilder.Name(attributeName); + attributeBuilder.NameComparisonMode(nameComparison); + } + + private void ParseCssAttributeName(RequiredAttributeDescriptorBuilder builder) + { + var nameStartIndex = _index; + var nameEndIndex = _requiredAttributes.IndexOfAny(InvalidCssAttributeNameCharacters, _index); + nameEndIndex = nameEndIndex == -1 ? _requiredAttributes.Length : nameEndIndex; + _index = nameEndIndex; + + var attributeName = _requiredAttributes.Substring(nameStartIndex, nameEndIndex - nameStartIndex); + + builder.Name(attributeName); + } + + private bool TryParseCssValueComparison(RequiredAttributeDescriptorBuilder builder, out RequiredAttributeDescriptor.ValueComparisonMode valueComparison) + { + Debug.Assert(!AtEnd); + + if (CssValueComparisons.TryGetValue(Current, out valueComparison)) + { + var op = Current; + _index++; + + if (op != '=' && At('=')) + { + // Two length operator (ex: ^=). Move past the second piece + _index++; + } + else if (op != '=') // We're at an incomplete operator (ex: [foo^] + { + var diagnostic = RazorDiagnosticFactory.CreateTagHelper_PartialRequiredAttributeOperator(op, _requiredAttributes); + builder.AddDiagnostic(diagnostic); + + return false; + } + } + else if (!At(']')) + { + var diagnostic = RazorDiagnosticFactory.CreateTagHelper_InvalidRequiredAttributeOperator(Current, _requiredAttributes); + builder.AddDiagnostic(diagnostic); + + return false; + } + + builder.ValueComparisonMode(valueComparison); + + return true; + } + + private bool TryParseCssValue(RequiredAttributeDescriptorBuilder builder) + { + int valueStart; + int valueEnd; + if (At('\'') || At('"')) + { + var quote = Current; + + // Move past the quote + _index++; + + valueStart = _index; + valueEnd = _requiredAttributes.IndexOf(quote, _index); + if (valueEnd == -1) + { + var diagnostic = RazorDiagnosticFactory.CreateTagHelper_InvalidRequiredAttributeMismatchedQuotes(quote, _requiredAttributes); + builder.AddDiagnostic(diagnostic); + + return false; + } + _index = valueEnd + 1; + } + else + { + valueStart = _index; + var valueEndIndex = _requiredAttributes.IndexOfAny(InvalidCssQuotelessValueCharacters, _index); + valueEnd = valueEndIndex == -1 ? _requiredAttributes.Length : valueEndIndex; + _index = valueEnd; + } + + var value = _requiredAttributes.Substring(valueStart, valueEnd - valueStart); + + builder.Value(value); + + return true; + } + + private bool TryParseCssSelector(RequiredAttributeDescriptorBuilder attributeBuilder) + { + Debug.Assert(At('[')); + + // Move past '['. + _index++; + PassOptionalWhitespace(); + + ParseCssAttributeName(attributeBuilder); + + PassOptionalWhitespace(); + + if (!EnsureNotAtEnd(attributeBuilder)) + { + return false; + } + + if (!TryParseCssValueComparison(attributeBuilder, out RequiredAttributeDescriptor.ValueComparisonMode valueComparison)) + { + return false; + } + + PassOptionalWhitespace(); + + if (!EnsureNotAtEnd(attributeBuilder)) + { + return false; + } + + if (valueComparison != RequiredAttributeDescriptor.ValueComparisonMode.None && !TryParseCssValue(attributeBuilder)) + { + return false; + } + + PassOptionalWhitespace(); + + if (At(']')) + { + // Move past the ending bracket. + _index++; + return true; + } + else if (AtEnd) + { + var diagnostic = RazorDiagnosticFactory.CreateTagHelper_CouldNotFindMatchingEndBrace(_requiredAttributes); + attributeBuilder.AddDiagnostic(diagnostic); + } + else + { + var diagnostic = RazorDiagnosticFactory.CreateTagHelper_InvalidRequiredAttributeCharacter(Current, _requiredAttributes); + attributeBuilder.AddDiagnostic(diagnostic); + } + + return false; + } + + private bool EnsureNotAtEnd(RequiredAttributeDescriptorBuilder builder) + { + if (AtEnd) + { + var diagnostic = RazorDiagnosticFactory.CreateTagHelper_CouldNotFindMatchingEndBrace(_requiredAttributes); + builder.AddDiagnostic(diagnostic); + + return false; + } + + return true; + } + + private bool At(char c) + { + return !AtEnd && Current == c; + } + + private void PassOptionalWhitespace() + { + while (!AtEnd && (Current == ' ' || Current == '\t')) + { + _index++; + } + } + } + } +} diff --git a/src/Microsoft.CodeAnalysis.Razor/Resources.resx b/src/Microsoft.CodeAnalysis.Razor/Resources.resx index ee2c26d650..edfa6f2970 100644 --- a/src/Microsoft.CodeAnalysis.Razor/Resources.resx +++ b/src/Microsoft.CodeAnalysis.Razor/Resources.resx @@ -117,42 +117,15 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - {0} cannot be null or an empty string. - - - Tag helpers cannot target {0} name '{1}' because it contains a '{2}' character. - - - {0} name cannot be null or whitespace. - - - Attribute - Could not find matching ']' for required attribute '{0}'. - - Invalid tag helper bound property '{0}.{1}'. An '{2}' must not be associated with a property with no public setter unless its type implements '{3}'. + + Invalid tag helper bound property '{0}.{1}'. '{2}.{3}' must be null unless property type implements '{4}'. Invalid tag helper bound property '{0}.{1}'. '{2}.{3}' must be null or empty if property has no public setter. - - Invalid tag helper bound property '{0}.{1}'. Tag helpers cannot bind to HTML attributes with a null or empty name. - - - Invalid tag helper bound property '{0}.{1}'. Tag helpers cannot bind to HTML attributes with {2} '{3}' because {2} contains a '{4}' character. - - - Invalid tag helper bound property '{0}.{1}'. Tag helpers cannot bind to HTML attributes with {2} '{3}' because {2} starts with '{4}'. - - - Invalid tag helper bound property '{0}.{1}'. Tag helpers cannot bind to HTML attributes with a whitespace {2}. - - - Invalid tag helper bound property '{0}.{1}'. '{2}.{3}' must be null unless property type implements '{4}'. - Invalid tag helper bound property '{0}.{1}'. '{2}.{3}' must not be null if property has no public setter and its type implements '{4}'. @@ -165,25 +138,7 @@ Invalid character '{0}' in required attribute '{1}'. Expected supported CSS operator or ']'. - - Invalid '{0}' tag name '{1}' for tag helper '{2}'. Tag helpers cannot restrict child elements that contain a '{3}' character. - - - Invalid '{0}' tag name for tag helper '{1}'. Name cannot be null or whitespace. - - - name - - - Parent Tag - Required attribute '{0}' has a partial CSS operator. '{1}' must be followed by an equals. - - prefix - - - Tag - \ No newline at end of file diff --git a/src/Microsoft.CodeAnalysis.Razor/TagHelpers.cs b/src/Microsoft.CodeAnalysis.Razor/TagHelpers.cs index 32e6de8055..5f9944e7f1 100644 --- a/src/Microsoft.CodeAnalysis.Razor/TagHelpers.cs +++ b/src/Microsoft.CodeAnalysis.Razor/TagHelpers.cs @@ -32,8 +32,11 @@ namespace Microsoft.CodeAnalysis.Razor foreach (var type in types) { - var descriptors = factory.CreateDescriptors(type, errors); - results.AddRange(descriptors); + var descriptor = factory.CreateDescriptor(type); + if (descriptor != null) + { + results.Add(descriptor); + } } } @@ -52,7 +55,10 @@ namespace Microsoft.CodeAnalysis.Razor { var descriptor = factory.CreateDescriptor(type); - results.Add(descriptor); + if (descriptor != null) + { + results.Add(descriptor); + } } catch (Exception ex) { diff --git a/src/Microsoft.CodeAnalysis.Razor/ViewComponentTagHelperDescriptorFactory.cs b/src/Microsoft.CodeAnalysis.Razor/ViewComponentTagHelperDescriptorFactory.cs index 966ac5865b..3489a2fd39 100644 --- a/src/Microsoft.CodeAnalysis.Razor/ViewComponentTagHelperDescriptorFactory.cs +++ b/src/Microsoft.CodeAnalysis.Razor/ViewComponentTagHelperDescriptorFactory.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; -using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Threading.Tasks; @@ -36,66 +35,69 @@ namespace Microsoft.CodeAnalysis.Razor var shortName = GetShortName(type); var tagName = $"vc:{DefaultTagHelperDescriptorFactory.ToHtmlCase(shortName)}"; var typeName = $"__Generated__{shortName}ViewComponentTagHelper"; - - var descriptor = new TagHelperDescriptor + var descriptorBuilder = ITagHelperDescriptorBuilder.Create(typeName, assemblyName); + var methodParameters = GetInvokeMethodParameters(type); + descriptorBuilder.TagMatchingRule(ruleBuilder => { - TagName = tagName, - TypeName = typeName, - AssemblyName = assemblyName - }; + ruleBuilder.RequireTagName(tagName); + AddRequiredAttributes(methodParameters, ruleBuilder); + }); - SetAttributeDescriptors(type, descriptor); + AddBoundAttributes(methodParameters, descriptorBuilder); - descriptor.PropertyBag.Add(ViewComponentTypes.ViewComponentNameKey, shortName); + descriptorBuilder.AddMetadata(ViewComponentTypes.ViewComponentNameKey, shortName); + var descriptor = descriptorBuilder.Build(); return descriptor; } - private void SetAttributeDescriptors(INamedTypeSymbol type, TagHelperDescriptor descriptor) + private void AddRequiredAttributes(ImmutableArray methodParameters, TagMatchingRuleBuilder builder) { - var methodParameters = GetInvokeMethodParameters(type); - var attributeDescriptors = new List(); - var indexerDescriptors = new List(); - var requiredAttributeDescriptors = new List(); + foreach (var parameter in methodParameters) + { + if (GetIndexerValueTypeName(parameter) == null) + { + // Set required attributes only for non-indexer attributes. Indexer attributes can't be required attributes + // because there are two ways of setting values for the attribute. + builder.RequireAttribute(attributeBuilder => + { + var lowerKebabName = DefaultTagHelperDescriptorFactory.ToHtmlCase(parameter.Name); + attributeBuilder.Name(lowerKebabName); + }); + } + } + } + private void AddBoundAttributes(ImmutableArray methodParameters, ITagHelperDescriptorBuilder builder) + { foreach (var parameter in methodParameters) { var lowerKebabName = DefaultTagHelperDescriptorFactory.ToHtmlCase(parameter.Name); var typeName = parameter.Type.ToDisplayString(FullNameTypeDisplayFormat); - var attributeDescriptor = new TagHelperAttributeDescriptor + builder.BindAttribute(attributeBuilder => { - Name = lowerKebabName, - PropertyName = parameter.Name, - TypeName = typeName - }; + attributeBuilder + .Name(lowerKebabName) + .PropertyName(parameter.Name) + .TypeName(typeName); - attributeDescriptor.IsEnum = parameter.Type.TypeKind == TypeKind.Enum; - attributeDescriptor.IsIndexer = false; - - attributeDescriptors.Add(attributeDescriptor); - - var indexerDescriptor = GetIndexerAttributeDescriptor(parameter, lowerKebabName); - if (indexerDescriptor != null) - { - indexerDescriptors.Add(indexerDescriptor); - } - else - { - // Set required attributes only for non-indexer attributes. Indexer attributes can't be required attributes - // because there are two ways of setting values for the attribute. - requiredAttributeDescriptors.Add(new TagHelperRequiredAttributeDescriptor + if (parameter.Type.TypeKind == TypeKind.Enum) { - Name = lowerKebabName - }); - } + attributeBuilder.AsEnum(); + } + else + { + var dictionaryValueType = GetIndexerValueTypeName(parameter); + if (dictionaryValueType != null) + { + attributeBuilder.AsDictionary(lowerKebabName + "-", dictionaryValueType); + } + } + }); } - - attributeDescriptors.AddRange(indexerDescriptors); - descriptor.Attributes = attributeDescriptors; - descriptor.RequiredAttributes = requiredAttributeDescriptors; } - private TagHelperAttributeDescriptor GetIndexerAttributeDescriptor(IParameterSymbol parameter, string name) + private string GetIndexerValueTypeName(IParameterSymbol parameter) { INamedTypeSymbol dictionaryType; if ((parameter.Type as INamedTypeSymbol)?.ConstructedFrom == _iDictionarySymbol) @@ -117,16 +119,9 @@ namespace Microsoft.CodeAnalysis.Razor } var type = dictionaryType.TypeArguments[1]; - var descriptor = new TagHelperAttributeDescriptor - { - Name = name + "-", - PropertyName = parameter.Name, - TypeName = type.ToDisplayString(FullNameTypeDisplayFormat), - IsEnum = type.TypeKind == TypeKind.Enum, - IsIndexer = true - }; + var typeName = type.ToDisplayString(FullNameTypeDisplayFormat); - return descriptor; + return typeName; } private ImmutableArray GetInvokeMethodParameters(INamedTypeSymbol componentType) diff --git a/src/Microsoft.CodeAnalysis.Razor/XmlMemberDocumentation.cs b/src/Microsoft.CodeAnalysis.Razor/XmlMemberDocumentation.cs deleted file mode 100644 index cec3ebed7f..0000000000 --- a/src/Microsoft.CodeAnalysis.Razor/XmlMemberDocumentation.cs +++ /dev/null @@ -1,80 +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; -using System.Diagnostics; -using System.Text; -using System.Xml.Linq; - -namespace Microsoft.CodeAnalysis.Razor -{ - /// - /// Extracts summary and remarks XML documentation from XML member documentation. - /// - internal class XmlMemberDocumentation - { - private readonly XElement _element; - - public XmlMemberDocumentation(string content) - { - if (string.IsNullOrEmpty(content)) - { - throw new ArgumentException(Resources.FormatArgument_Cannot_Be_Null_Or_Empty(nameof(content))); - } - - // the structure of the XML is defined by: https://msdn.microsoft.com/en-us/library/fsbx0t7x.aspx - // we expect the root node of the content we are passed to always be 'member' or 'doc'. - _element = XElement.Parse(content); - } - - /// - /// Retrieves the <summary> documentation. - /// - /// <summary> documentation. - public string GetSummary() - { - var summaryElement = _element.Element("summary"); - if (summaryElement != null) - { - var summaryValue = GetElementValue(summaryElement); - - return summaryValue; - } - - return null; - } - - /// - /// Retrieves the <remarks> documentation. - /// - /// <remarks> documentation. - public string GetRemarks() - { - var remarksElement = _element.Element("remarks"); - - if (remarksElement != null) - { - var remarksValue = GetElementValue(remarksElement); - - return remarksValue; - } - - return null; - } - - private static string GetElementValue(XElement element) - { - var stringBuilder = new StringBuilder(); - var node = element.FirstNode; - - while (node != null) - { - stringBuilder.Append(node.ToString(SaveOptions.DisableFormatting)); - - node = node.NextNode; - } - - return stringBuilder.ToString().Trim(); - } - } -} \ No newline at end of file diff --git a/src/Microsoft.VisualStudio.LanguageServices.Razor/DefaultTagHelperResolver.cs b/src/Microsoft.VisualStudio.LanguageServices.Razor/DefaultTagHelperResolver.cs index c67a99756f..59eeb40485 100644 --- a/src/Microsoft.VisualStudio.LanguageServices.Razor/DefaultTagHelperResolver.cs +++ b/src/Microsoft.VisualStudio.LanguageServices.Razor/DefaultTagHelperResolver.cs @@ -50,11 +50,12 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor // Per https://github.com/dotnet/roslyn/issues/12770 - there's currently no support for documentation in the OOP host // until that's available we add the documentation on the VS side by looking up each symbol again. var compilation = await project.GetCompilationAsync().ConfigureAwait(false); - AddXmlDocumentation(compilation, result.Descriptors); - return result; + var documentedTagHelpers = GetDocumentedTagHelpers(compilation, result.Descriptors); + var documentedResult = new TagHelperResolutionResult(documentedTagHelpers, result.Diagnostics); + + return documentedResult; } } - } } @@ -82,42 +83,123 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor } } - private void AddXmlDocumentation(Compilation compilation, IReadOnlyList tagHelpers) + private IReadOnlyList GetDocumentedTagHelpers(Compilation compilation, IReadOnlyList tagHelpers) { + var documentedTagHelpers = new List(); for (var i = 0; i < tagHelpers.Count; i++) { var tagHelper = tagHelpers[i]; - tagHelper.DesignTimeDescriptor = tagHelper.DesignTimeDescriptor ?? new TagHelperDesignTimeDescriptor(); - var symbol = compilation.GetTypeByMetadataName(tagHelper.TypeName); + if (tagHelper.Documentation != null) + { + documentedTagHelpers.Add(tagHelper); + continue; + } + + var typeName = tagHelper.Metadata[ITagHelperDescriptorBuilder.TypeNameKey]; + var symbol = compilation.GetTypeByMetadataName(typeName); if (symbol != null) { + var tagHelperBuilder = ShallowCopy(typeName, tagHelper); var xml = symbol.GetDocumentationCommentXml(); + if (!string.IsNullOrEmpty(xml)) { - var documentation = new XmlMemberDocumentation(xml); - tagHelper.DesignTimeDescriptor.Summary = documentation.GetSummary(); - tagHelper.DesignTimeDescriptor.Remarks = documentation.GetRemarks(); + tagHelperBuilder.Documentation(xml); } - foreach (var attribute in tagHelper.Attributes) + foreach (var attribute in tagHelper.BoundAttributes) { - attribute.DesignTimeDescriptor = attribute.DesignTimeDescriptor ?? new TagHelperAttributeDesignTimeDescriptor(); + var propertyName = attribute.Metadata[ITagHelperBoundAttributeDescriptorBuilder.PropertyNameKey]; - var attributeSymbol = symbol.GetMembers(attribute.PropertyName).FirstOrDefault(); + var resolvedAttribute = attribute; + var attributeSymbol = symbol.GetMembers(propertyName).FirstOrDefault(); if (attributeSymbol != null) { xml = attributeSymbol.GetDocumentationCommentXml(); if (!string.IsNullOrEmpty(xml)) { - var documentation = new XmlMemberDocumentation(xml); - attribute.DesignTimeDescriptor.Summary = documentation.GetSummary(); - attribute.DesignTimeDescriptor.Remarks = documentation.GetRemarks(); + var attributeBuilder = ShallowCopy(typeName, resolvedAttribute); + attributeBuilder.Documentation(xml); + resolvedAttribute = attributeBuilder.Build(); } } + + tagHelperBuilder.BindAttribute(resolvedAttribute); } + + tagHelper = tagHelperBuilder.Build(); + } + + documentedTagHelpers.Add(tagHelper); + } + + return documentedTagHelpers; + } + + private ITagHelperBoundAttributeDescriptorBuilder ShallowCopy(string tagHelperTypeName, BoundAttributeDescriptor attribute) + { + var builder = ITagHelperBoundAttributeDescriptorBuilder.Create(tagHelperTypeName); + + if (attribute.IsEnum) + { + builder.AsEnum(); + } + + if (attribute.IndexerNamePrefix != null) + { + builder.AsDictionary(attribute.IndexerNamePrefix, attribute.IndexerTypeName); + } + + builder.Name(attribute.Name); + builder.TypeName(attribute.TypeName); + + var propertyName = attribute.Metadata[ITagHelperBoundAttributeDescriptorBuilder.PropertyNameKey]; + builder.PropertyName(propertyName); + + foreach (var metadata in attribute.Metadata) + { + builder.AddMetadata(metadata.Key, metadata.Value); + } + + foreach (var diagnostic in attribute.Diagnostics) + { + builder.AddDiagnostic(diagnostic); + } + + return builder; + } + + private ITagHelperDescriptorBuilder ShallowCopy(string tagHelperTypeName, TagHelperDescriptor tagHelper) + { + var builder = ITagHelperDescriptorBuilder.Create(tagHelperTypeName, tagHelper.AssemblyName); + + foreach (var rule in tagHelper.TagMatchingRules) + { + builder.TagMatchingRule(rule); + } + + if (tagHelper.AllowedChildTags != null) + { + foreach (var allowedChild in tagHelper.AllowedChildTags) + { + builder.AllowChildTag(allowedChild); } } + + builder.TagOutputHint(tagHelper.TagOutputHint); + + foreach (var metadata in tagHelper.Metadata) + { + builder.AddMetadata(metadata.Key, metadata.Value); + } + + foreach (var diagnostic in tagHelper.Diagnostics) + { + builder.AddDiagnostic(diagnostic); + } + + return builder; } private IVsActivityLog GetActivityLog() diff --git a/src/Microsoft.VisualStudio.LanguageServices.Razor/Microsoft.VisualStudio.LanguageServices.Razor.csproj b/src/Microsoft.VisualStudio.LanguageServices.Razor/Microsoft.VisualStudio.LanguageServices.Razor.csproj index 9846325f70..40f0a7d387 100644 --- a/src/Microsoft.VisualStudio.LanguageServices.Razor/Microsoft.VisualStudio.LanguageServices.Razor.csproj +++ b/src/Microsoft.VisualStudio.LanguageServices.Razor/Microsoft.VisualStudio.LanguageServices.Razor.csproj @@ -37,6 +37,7 @@ + diff --git a/src/Microsoft.VisualStudio.LanguageServices.Razor/Properties/Resources.Designer.cs b/src/Microsoft.VisualStudio.LanguageServices.Razor/Properties/Resources.Designer.cs index 597444de46..562ff5a7ab 100644 --- a/src/Microsoft.VisualStudio.LanguageServices.Razor/Properties/Resources.Designer.cs +++ b/src/Microsoft.VisualStudio.LanguageServices.Razor/Properties/Resources.Designer.cs @@ -10,21 +10,33 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor private static readonly ResourceManager _resourceManager = new ResourceManager("Microsoft.VisualStudio.LanguageServices.Razor.Resources", typeof(Resources).GetTypeInfo().Assembly); + /// + /// Deserialization of {0} type '{1}' is not supported. + /// + internal static string RazorDiagnosticJsonConverter_UnsupportedRazorDiagnosticType + { + get => GetString("RazorDiagnosticJsonConverter_UnsupportedRazorDiagnosticType"); + } + + /// + /// Deserialization of {0} type '{1}' is not supported. + /// + internal static string FormatRazorDiagnosticJsonConverter_UnsupportedRazorDiagnosticType(object p0, object p1) + => string.Format(CultureInfo.CurrentCulture, GetString("RazorDiagnosticJsonConverter_UnsupportedRazorDiagnosticType"), p0, p1); + /// /// An unexpected exception occurred when invoking '{0}.{1}' on the Razor language service. /// internal static string UnexpectedException { - get { return GetString("UnexpectedException"); } + get => GetString("UnexpectedException"); } /// /// An unexpected exception occurred when invoking '{0}.{1}' on the Razor language service. /// internal static string FormatUnexpectedException(object p0, object p1) - { - return string.Format(CultureInfo.CurrentCulture, GetString("UnexpectedException"), p0, p1); - } + => string.Format(CultureInfo.CurrentCulture, GetString("UnexpectedException"), p0, p1); private static string GetString(string name, params string[] formatterNames) { diff --git a/src/Microsoft.VisualStudio.LanguageServices.Razor/RazorDiagnosticJsonConverter.cs b/src/Microsoft.VisualStudio.LanguageServices.Razor/RazorDiagnosticJsonConverter.cs new file mode 100644 index 0000000000..204a84ab56 --- /dev/null +++ b/src/Microsoft.VisualStudio.LanguageServices.Razor/RazorDiagnosticJsonConverter.cs @@ -0,0 +1,83 @@ +// 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.Evolution; +using Microsoft.AspNetCore.Razor.Evolution.Legacy; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace Microsoft.VisualStudio.LanguageServices.Razor +{ + public class RazorDiagnosticJsonConverter : JsonConverter + { + public static readonly RazorDiagnosticJsonConverter Instance = new RazorDiagnosticJsonConverter(); + private const string RazorDiagnosticMessageKey = "Message"; + private const string RazorDiagnosticTypeNameKey = "TypeName"; + + public override bool CanConvert(Type objectType) + { + return typeof(RazorDiagnostic).IsAssignableFrom(objectType); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + if (reader.TokenType != JsonToken.StartObject) + { + return null; + } + + var diagnostic = JObject.Load(reader); + var span = diagnostic[nameof(RazorDiagnostic.Span)].Value(); + var absoluteIndex = span[nameof(SourceSpan.AbsoluteIndex)].Value(); + var lineIndex = span[nameof(SourceSpan.LineIndex)].Value(); + var characterIndex = span[nameof(SourceSpan.CharacterIndex)].Value(); + var length = span[nameof(SourceSpan.Length)].Value(); + var filePath = span[nameof(SourceSpan.FilePath)].Value(); + var message = diagnostic[RazorDiagnosticMessageKey].Value(); + var typeName = diagnostic[RazorDiagnosticTypeNameKey].Value(); + + if (string.Equals(typeName, typeof(DefaultRazorDiagnostic).FullName, StringComparison.Ordinal)) + { + var id = diagnostic[nameof(RazorDiagnostic.Id)].Value(); + var severity = diagnostic[nameof(RazorDiagnostic.Severity)].Value(); + + var descriptor = new RazorDiagnosticDescriptor(id, () => message, (RazorDiagnosticSeverity)severity); + var sourceSpan = new SourceSpan(filePath, absoluteIndex, lineIndex, characterIndex, length); + + return RazorDiagnostic.Create(descriptor, sourceSpan); + } + else if (string.Equals(typeName, typeof(LegacyRazorDiagnostic).FullName, StringComparison.Ordinal)) + { + var error = new RazorError(message, absoluteIndex, lineIndex, characterIndex, length); + + return RazorDiagnostic.Create(error); + } + + throw new NotSupportedException( + Resources.FormatRazorDiagnosticJsonConverter_UnsupportedRazorDiagnosticType(typeof(RazorDiagnostic).Name, typeName)); + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + var diagnostic = (RazorDiagnostic)value; + + writer.WriteStartObject(); + WriteProperty(writer, nameof(RazorDiagnostic.Id), diagnostic.Id); + WriteProperty(writer, nameof(RazorDiagnostic.Severity), (int)diagnostic.Severity); + WriteProperty(writer, RazorDiagnosticMessageKey, diagnostic.GetMessage()); + WriteProperty(writer, RazorDiagnosticTypeNameKey, diagnostic.GetType().FullName); + + writer.WritePropertyName(nameof(RazorDiagnostic.Span)); + serializer.Serialize(writer, diagnostic.Span); + + writer.WriteEndObject(); + } + + private void WriteProperty(JsonWriter writer, string key, T value) + { + writer.WritePropertyName(key); + writer.WriteValue(value); + } + } +} diff --git a/src/Microsoft.VisualStudio.LanguageServices.Razor/Resources.resx b/src/Microsoft.VisualStudio.LanguageServices.Razor/Resources.resx index 6783beb144..b4189aac42 100644 --- a/src/Microsoft.VisualStudio.LanguageServices.Razor/Resources.resx +++ b/src/Microsoft.VisualStudio.LanguageServices.Razor/Resources.resx @@ -117,6 +117,9 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + Deserialization of {0} type '{1}' is not supported. + An unexpected exception occurred when invoking '{0}.{1}' on the Razor language service. diff --git a/src/Microsoft.VisualStudio.LanguageServices.Razor/TagHelperDescriptorJsonConverter.cs b/src/Microsoft.VisualStudio.LanguageServices.Razor/TagHelperDescriptorJsonConverter.cs new file mode 100644 index 0000000000..1163a6ec3d --- /dev/null +++ b/src/Microsoft.VisualStudio.LanguageServices.Razor/TagHelperDescriptorJsonConverter.cs @@ -0,0 +1,189 @@ +// 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.Evolution; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace Microsoft.VisualStudio.LanguageServices.Razor +{ + internal class TagHelperDescriptorJsonConverter : JsonConverter + { + public static readonly TagHelperDescriptorJsonConverter Instance = new TagHelperDescriptorJsonConverter(); + + public override bool CanWrite => false; + + public override bool CanConvert(Type objectType) + { + return typeof(TagHelperDescriptor).IsAssignableFrom(objectType); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + if (reader.TokenType != JsonToken.StartObject) + { + return null; + } + + var descriptor = JObject.Load(reader); + var descriptorKind = descriptor[nameof(TagHelperDescriptor.Kind)].Value(); + var typeName = descriptor[nameof(TagHelperDescriptor.Name)].Value(); + var assemblyName = descriptor[nameof(TagHelperDescriptor.AssemblyName)].Value(); + var tagMatchingRules = descriptor[nameof(TagHelperDescriptor.TagMatchingRules)].Value(); + var boundAttributes = descriptor[nameof(TagHelperDescriptor.BoundAttributes)].Value(); + var childTags = descriptor[nameof(TagHelperDescriptor.AllowedChildTags)].Value(); + var documentation = descriptor[nameof(TagHelperDescriptor.Documentation)].Value(); + var tagOutputHint = descriptor[nameof(TagHelperDescriptor.TagOutputHint)].Value(); + var diagnostics = descriptor[nameof(TagHelperDescriptor.Diagnostics)].Value(); + var metadata = descriptor[nameof(TagHelperDescriptor.Metadata)].Value(); + + var builder = ITagHelperDescriptorBuilder.Create(typeName, assemblyName); + + builder + .Documentation(documentation) + .TagOutputHint(tagOutputHint); + + foreach (var tagMatchingRule in tagMatchingRules) + { + var rule = tagMatchingRule.Value(); + builder.TagMatchingRule(b => ReadTagMatchingRule(b, rule, serializer)); + } + + foreach (var boundAttribute in boundAttributes) + { + var attribute = boundAttribute.Value(); + builder.BindAttribute(b => ReadBoundAttribute(b, attribute, serializer)); + } + + foreach (var childTag in childTags) + { + var tagValue = childTag.Value(); + builder.AllowChildTag(tagValue); + } + + foreach (var diagnostic in diagnostics) + { + var diagnosticReader = diagnostic.CreateReader(); + var diagnosticObject = serializer.Deserialize(diagnosticReader); + builder.AddDiagnostic(diagnosticObject); + } + + var metadataReader = metadata.CreateReader(); + var metadataValue = serializer.Deserialize>(metadataReader); + foreach (var item in metadataValue) + { + builder.AddMetadata(item.Key, item.Value); + } + + return builder.Build(); + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + // We should never get here because CanWrite returns false. + // We want the default serializer to handle TagHelperDescriptor serialization. + throw new NotImplementedException(); + } + + private void ReadTagMatchingRule(TagMatchingRuleBuilder builder, JObject rule, JsonSerializer serializer) + { + var tagName = rule[nameof(TagMatchingRule.TagName)].Value(); + var attributes = rule[nameof(TagMatchingRule.Attributes)].Value(); + var parentTag = rule[nameof(TagMatchingRule.ParentTag)].Value(); + var tagStructure = rule[nameof(TagMatchingRule.TagStructure)].Value(); + var diagnostics = rule[nameof(TagMatchingRule.Diagnostics)].Value(); + + builder + .RequireTagName(tagName) + .RequireParentTag(parentTag) + .RequireTagStructure((TagStructure)tagStructure); + + foreach (var attribute in attributes) + { + var attibuteValue = attribute.Value(); + builder.RequireAttribute(b => ReadRequiredAttribute(b, attibuteValue, serializer)); + } + + foreach (var diagnostic in diagnostics) + { + var diagnosticReader = diagnostic.CreateReader(); + var diagnosticObject = serializer.Deserialize(diagnosticReader); + builder.AddDiagnostic(diagnosticObject); + } + } + + private void ReadRequiredAttribute(RequiredAttributeDescriptorBuilder builder, JObject attribute, JsonSerializer serializer) + { + var name = attribute[nameof(RequiredAttributeDescriptor.Name)].Value(); + var nameComparison = attribute[nameof(RequiredAttributeDescriptor.NameComparison)].Value(); + var value = attribute[nameof(RequiredAttributeDescriptor.Value)].Value(); + var valueComparison = attribute[nameof(RequiredAttributeDescriptor.ValueComparison)].Value(); + var diagnostics = attribute[nameof(RequiredAttributeDescriptor.Diagnostics)].Value(); + + builder + .Name(name) + .NameComparisonMode((RequiredAttributeDescriptor.NameComparisonMode)nameComparison) + .Value(value) + .ValueComparisonMode((RequiredAttributeDescriptor.ValueComparisonMode)valueComparison); + + foreach (var diagnostic in diagnostics) + { + var diagnosticReader = diagnostic.CreateReader(); + var diagnosticObject = serializer.Deserialize(diagnosticReader); + builder.AddDiagnostic(diagnosticObject); + } + } + + private void ReadBoundAttribute(ITagHelperBoundAttributeDescriptorBuilder builder, JObject attribute, JsonSerializer serializer) + { + var descriptorKind = attribute[nameof(BoundAttributeDescriptor.Kind)].Value(); + if (descriptorKind != ITagHelperBoundAttributeDescriptorBuilder.DescriptorKind) + { + throw new NotSupportedException(); + } + + var name = attribute[nameof(BoundAttributeDescriptor.Name)].Value(); + var typeName = attribute[nameof(BoundAttributeDescriptor.TypeName)].Value(); + var isEnum = attribute[nameof(BoundAttributeDescriptor.IsEnum)].Value(); + var indexerNamePrefix = attribute[nameof(BoundAttributeDescriptor.IndexerNamePrefix)].Value(); + var indexerTypeName = attribute[nameof(BoundAttributeDescriptor.IndexerTypeName)].Value(); + var documentation = attribute[nameof(BoundAttributeDescriptor.Documentation)].Value(); + var diagnostics = attribute[nameof(BoundAttributeDescriptor.Diagnostics)].Value(); + var metadata = attribute[nameof(BoundAttributeDescriptor.Metadata)].Value(); + + builder + .Name(name) + .TypeName(typeName) + .Documentation(documentation); + + if (indexerNamePrefix != null) + { + builder.AsDictionary(indexerNamePrefix, indexerTypeName); + } + + if (isEnum) + { + builder.AsEnum(); + } + + foreach (var diagnostic in diagnostics) + { + var diagnosticReader = diagnostic.CreateReader(); + var diagnosticObject = serializer.Deserialize(diagnosticReader); + builder.AddDiagnostic(diagnosticObject); + } + + var metadataReader = metadata.CreateReader(); + var metadataValue = serializer.Deserialize>(metadataReader); + foreach (var item in metadataValue) + { + builder.AddMetadata(item.Key, item.Value); + } + + var propertyName = metadataValue[ITagHelperBoundAttributeDescriptorBuilder.PropertyNameKey]; + builder.PropertyName(propertyName); + } + } +} diff --git a/test/Microsoft.AspNetCore.Razor.Evolution.Test/DefaultDirectiveIRPassTest.cs b/test/Microsoft.AspNetCore.Razor.Evolution.Test/DefaultDirectiveIRPassTest.cs index 2469b7754d..56a1101022 100644 --- a/test/Microsoft.AspNetCore.Razor.Evolution.Test/DefaultDirectiveIRPassTest.cs +++ b/test/Microsoft.AspNetCore.Razor.Evolution.Test/DefaultDirectiveIRPassTest.cs @@ -155,7 +155,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution { var phase = engine.Phases[i]; phase.Execute(codeDocument); - + if (phase is IRazorDocumentClassifierPhase) { break; @@ -168,4 +168,4 @@ namespace Microsoft.AspNetCore.Razor.Evolution return irDocument; } } -} +} \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Razor.Evolution.Test/DefaultRazorIRLoweringPhaseIntegrationTest.cs b/test/Microsoft.AspNetCore.Razor.Evolution.Test/DefaultRazorIRLoweringPhaseIntegrationTest.cs index c6f2d70d60..9d6fff7a3e 100644 --- a/test/Microsoft.AspNetCore.Razor.Evolution.Test/DefaultRazorIRLoweringPhaseIntegrationTest.cs +++ b/test/Microsoft.AspNetCore.Razor.Evolution.Test/DefaultRazorIRLoweringPhaseIntegrationTest.cs @@ -174,12 +174,10 @@ namespace Microsoft.AspNetCore.Razor.Evolution "); var tagHelpers = new[] { - new TagHelperDescriptor - { - TagName = "span", - TypeName = "SpanTagHelper", - AssemblyName = "TestAssembly", - } + CreateTagHelperDescriptor( + tagName: "span", + typeName: "SpanTagHelper", + assemblyName: "TestAssembly") }; // Act @@ -221,12 +219,10 @@ namespace Microsoft.AspNetCore.Razor.Evolution "); var tagHelpers = new[] { - new TagHelperDescriptor - { - TagName = "span", - TypeName = "SpanTagHelper", - AssemblyName = "TestAssembly", - } + CreateTagHelperDescriptor( + tagName: "span", + typeName: "SpanTagHelper", + assemblyName: "TestAssembly") }; // Act @@ -273,12 +269,10 @@ namespace Microsoft.AspNetCore.Razor.Evolution }"); var tagHelpers = new[] { - new TagHelperDescriptor - { - TagName = "span", - TypeName = "SpanTagHelper", - AssemblyName = "TestAssembly", - } + CreateTagHelperDescriptor( + tagName: "span", + typeName: "SpanTagHelper", + assemblyName: "TestAssembly") }; // Act @@ -299,7 +293,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution n, c1 => DirectiveToken(DirectiveTokenKind.Member, "test", c1), c1 => Html(Environment.NewLine, c1), - c1 => + c1 => { var tagHelperNode = Assert.IsType(c1); Children( @@ -324,24 +318,23 @@ namespace Microsoft.AspNetCore.Razor.Evolution // Arrange var codeDocument = TestRazorCodeDocument.Create(@"@addTagHelper *, TestAssembly "); - var descriptor = new TagHelperDescriptor + var tagHelpers = new[] { - TagName = "input", - TypeName = "InputTagHelper", - AssemblyName = "TestAssembly", - Attributes = new[] - { - new TagHelperAttributeDescriptor + CreateTagHelperDescriptor( + tagName: "input", + typeName: "InputTagHelper", + assemblyName: "TestAssembly", + attributes: new Action[] { - Name = "bound", - PropertyName = "FooProp", - TypeName = "System.String" - } - } + builder => builder + .Name("bound") + .PropertyName("FooProp") + .TypeName("System.String"), + }) }; // Act - var irDocument = Lower(codeDocument, tagHelpers: new[] { descriptor }); + var irDocument = Lower(codeDocument, tagHelpers: tagHelpers); // Assert Children( @@ -484,5 +477,28 @@ namespace Microsoft.AspNetCore.Razor.Evolution return irDocument; } + + private static TagHelperDescriptor CreateTagHelperDescriptor( + string tagName, + string typeName, + string assemblyName, + IEnumerable> attributes = null) + { + var builder = ITagHelperDescriptorBuilder.Create(typeName, assemblyName); + + if (attributes != null) + { + foreach (var attributeBuilder in attributes) + { + builder.BindAttribute(attributeBuilder); + } + } + + builder.TagMatchingRule(ruleBuilder => ruleBuilder.RequireTagName(tagName)); + + var descriptor = builder.Build(); + + return descriptor; + } } } diff --git a/test/Microsoft.AspNetCore.Razor.Evolution.Test/IntegrationTests/CodeGenerationIntegrationTest.cs b/test/Microsoft.AspNetCore.Razor.Evolution.Test/IntegrationTests/CodeGenerationIntegrationTest.cs index d1f13cf38b..9604e3a959 100644 --- a/test/Microsoft.AspNetCore.Razor.Evolution.Test/IntegrationTests/CodeGenerationIntegrationTest.cs +++ b/test/Microsoft.AspNetCore.Razor.Evolution.Test/IntegrationTests/CodeGenerationIntegrationTest.cs @@ -666,7 +666,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution.IntegrationTests public void BasicTagHelpers_Prefixed_Runtime() { // Arrange, Act & Assert - RunRuntimeTagHelpersTest(TestTagHelperDescriptors.PrefixedPAndInputTagHelperDescriptors); + RunRuntimeTagHelpersTest(TestTagHelperDescriptors.DefaultPAndInputTagHelperDescriptors, tagHelperPrefix: "THS"); } [Fact] @@ -690,13 +690,6 @@ namespace Microsoft.AspNetCore.Razor.Evolution.IntegrationTests RunRuntimeTagHelpersTest(TestTagHelperDescriptors.DefaultPAndInputTagHelperDescriptors); } - [Fact] - public void DuplicateTargetTagHelper_Runtime() - { - // Arrange, Act & Assert - RunRuntimeTagHelpersTest(TestTagHelperDescriptors.DuplicateTargetTagHelperDescriptors); - } - [Fact] public void EmptyAttributeTagHelpers_Runtime() { @@ -1480,7 +1473,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution.IntegrationTests public void BasicTagHelpers_Prefixed_DesignTime() { // Arrange, Act & Assert - RunDesignTimeTagHelpersTest(TestTagHelperDescriptors.PrefixedPAndInputTagHelperDescriptors); + RunDesignTimeTagHelpersTest(TestTagHelperDescriptors.DefaultPAndInputTagHelperDescriptors, tagHelperPrefix: "THS"); } [Fact] @@ -1490,13 +1483,6 @@ namespace Microsoft.AspNetCore.Razor.Evolution.IntegrationTests RunDesignTimeTagHelpersTest(TestTagHelperDescriptors.DefaultPAndInputTagHelperDescriptors); } - [Fact] - public void DuplicateTargetTagHelper_DesignTime() - { - // Arrange, Act & Assert - RunDesignTimeTagHelpersTest(TestTagHelperDescriptors.DuplicateTargetTagHelperDescriptors); - } - [Fact] public void EmptyAttributeTagHelpers_DesignTime() { @@ -1616,7 +1602,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution.IntegrationTests return codeDocument; } - private void RunRuntimeTagHelpersTest(IEnumerable descriptors) + private void RunRuntimeTagHelpersTest(IEnumerable descriptors, string tagHelperPrefix = null) { // Arrange var engine = RazorEngine.Create( @@ -1626,6 +1612,10 @@ namespace Microsoft.AspNetCore.Razor.Evolution.IntegrationTests builder.AddTagHelpers(descriptors); }); var document = CreateCodeDocument(); + if (tagHelperPrefix != null) + { + document.SetTagHelperPrefix(tagHelperPrefix); + } // Act engine.Process(document); @@ -1635,7 +1625,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution.IntegrationTests AssertCSharpDocumentMatchesBaseline(document.GetCSharpDocument()); } - private void RunDesignTimeTagHelpersTest(IEnumerable descriptors) + private void RunDesignTimeTagHelpersTest(IEnumerable descriptors, string tagHelperPrefix = null) { // Arrange var engine = RazorEngine.CreateDesignTime( @@ -1645,6 +1635,10 @@ namespace Microsoft.AspNetCore.Razor.Evolution.IntegrationTests builder.AddTagHelpers(descriptors); }); var document = CreateCodeDocument(); + if (tagHelperPrefix != null) + { + document.SetTagHelperPrefix(tagHelperPrefix); + } // Act engine.Process(document); diff --git a/test/Microsoft.AspNetCore.Razor.Evolution.Test/IntegrationTests/InstrumentationPassIntegrationTest.cs b/test/Microsoft.AspNetCore.Razor.Evolution.Test/IntegrationTests/InstrumentationPassIntegrationTest.cs index ef96bead23..1b3510d521 100644 --- a/test/Microsoft.AspNetCore.Razor.Evolution.Test/IntegrationTests/InstrumentationPassIntegrationTest.cs +++ b/test/Microsoft.AspNetCore.Razor.Evolution.Test/IntegrationTests/InstrumentationPassIntegrationTest.cs @@ -1,6 +1,8 @@ // 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 Xunit; namespace Microsoft.AspNetCore.Razor.Evolution.IntegrationTests @@ -13,39 +15,29 @@ namespace Microsoft.AspNetCore.Razor.Evolution.IntegrationTests // Arrange var descriptors = new[] { - new TagHelperDescriptor - { - TagName = "p", - TypeName = "PTagHelper", - AssemblyName = "TestAssembly", - }, - new TagHelperDescriptor - { - TagName = "form", - TypeName = "FormTagHelper", - AssemblyName = "TestAssembly", - }, - new TagHelperDescriptor - { - TagName = "input", - TypeName = "InputTagHelper", - AssemblyName = "TestAssembly", - Attributes = new[] + CreateTagHelperDescriptor( + tagName: "p", + typeName: "PTagHelper", + assemblyName: "TestAssembly"), + CreateTagHelperDescriptor( + tagName: "form", + typeName: "FormTagHelper", + assemblyName: "TestAssembly"), + CreateTagHelperDescriptor( + tagName: "input", + typeName: "InputTagHelper", + assemblyName: "TestAssembly", + attributes: new Action[] { - new TagHelperAttributeDescriptor - { - Name = "value", - PropertyName = "FooProp", - TypeName = "System.String" // Gets preallocated - }, - new TagHelperAttributeDescriptor - { - Name = "date", - PropertyName = "BarProp", - TypeName = "System.DateTime" // Doesn't get preallocated - } - } - } + builder => builder + .Name("value") + .PropertyName("FooProp") + .TypeName("System.String"), // Gets preallocated + builder => builder + .Name("date") + .PropertyName("BarProp") + .TypeName("System.DateTime"), // Doesn't get preallocated + }) }; var engine = RazorEngine.Create(b => @@ -53,7 +45,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution.IntegrationTests b.AddTagHelpers(descriptors); b.Features.Add(new DefaultInstrumentationPass()); }); - + var document = CreateCodeDocument(); // Act @@ -66,5 +58,28 @@ namespace Microsoft.AspNetCore.Razor.Evolution.IntegrationTests AssertCSharpDocumentMatchesBaseline(csharpDocument); Assert.Empty(csharpDocument.Diagnostics); } + + private static TagHelperDescriptor CreateTagHelperDescriptor( + string tagName, + string typeName, + string assemblyName, + IEnumerable> attributes = null) + { + var builder = ITagHelperDescriptorBuilder.Create(typeName, assemblyName); + + if (attributes != null) + { + foreach (var attributeBuilder in attributes) + { + builder.BindAttribute(attributeBuilder); + } + } + + builder.TagMatchingRule(ruleBuilder => ruleBuilder.RequireTagName(tagName)); + + var descriptor = builder.Build(); + + return descriptor; + } } } diff --git a/test/Microsoft.AspNetCore.Razor.Evolution.Test/IntegrationTests/TagHelpersIntegrationTest.cs b/test/Microsoft.AspNetCore.Razor.Evolution.Test/IntegrationTests/TagHelpersIntegrationTest.cs index b3292094fd..27b0934228 100644 --- a/test/Microsoft.AspNetCore.Razor.Evolution.Test/IntegrationTests/TagHelpersIntegrationTest.cs +++ b/test/Microsoft.AspNetCore.Razor.Evolution.Test/IntegrationTests/TagHelpersIntegrationTest.cs @@ -1,6 +1,8 @@ // 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 Xunit; namespace Microsoft.AspNetCore.Razor.Evolution.IntegrationTests @@ -13,12 +15,10 @@ namespace Microsoft.AspNetCore.Razor.Evolution.IntegrationTests // Arrange var descriptors = new[] { - new TagHelperDescriptor - { - TagName = "input", - TypeName = "InputTagHelper", - AssemblyName = "TestAssembly", - } + CreateTagHelperDescriptor( + tagName: "input", + typeName: "InputTagHelper", + assemblyName: "TestAssembly") }; var engine = RazorEngine.Create(builder => builder.AddTagHelpers(descriptors)); @@ -37,18 +37,17 @@ namespace Microsoft.AspNetCore.Razor.Evolution.IntegrationTests // Arrange var descriptors = new[] { - new TagHelperDescriptor - { - TagName = "input", - TypeName = "InputTagHelper", - AssemblyName = "TestAssembly", - Attributes = new[] { new TagHelperAttributeDescriptor + CreateTagHelperDescriptor( + tagName: "input", + typeName: "InputTagHelper", + assemblyName: "TestAssembly", + attributes: new Action[] { - Name = "bound", - PropertyName = "FooProp", - TypeName = "System.String" - } } - } + builder => builder + .Name("bound") + .PropertyName("FooProp") + .TypeName("System.String"), + }) }; var engine = RazorEngine.Create(builder => builder.AddTagHelpers(descriptors)); @@ -67,30 +66,25 @@ namespace Microsoft.AspNetCore.Razor.Evolution.IntegrationTests // Arrange var descriptors = new[] { - new TagHelperDescriptor - { - TagName = "p", - TypeName = "PTagHelper", - AssemblyName = "TestAssembly", - }, - new TagHelperDescriptor - { - TagName = "form", - TypeName = "FormTagHelper", - AssemblyName = "TestAssembly", - }, - new TagHelperDescriptor - { - TagName = "input", - TypeName = "InputTagHelper", - AssemblyName = "TestAssembly", - Attributes = new[] { new TagHelperAttributeDescriptor + CreateTagHelperDescriptor( + tagName: "p", + typeName: "PTagHelper", + assemblyName: "TestAssembly"), + CreateTagHelperDescriptor( + tagName: "form", + typeName: "FormTagHelper", + assemblyName: "TestAssembly"), + CreateTagHelperDescriptor( + tagName: "input", + typeName: "InputTagHelper", + assemblyName: "TestAssembly", + attributes: new Action[] { - Name = "value", - PropertyName = "FooProp", - TypeName = "System.String" - } } - } + builder => builder + .Name("value") + .PropertyName("FooProp") + .TypeName("System.String"), + }) }; var engine = RazorEngine.Create(builder => builder.AddTagHelpers(descriptors)); @@ -102,5 +96,28 @@ namespace Microsoft.AspNetCore.Razor.Evolution.IntegrationTests // Assert AssertIRMatchesBaseline(document.GetIRDocument()); } + + private static TagHelperDescriptor CreateTagHelperDescriptor( + string tagName, + string typeName, + string assemblyName, + IEnumerable> attributes = null) + { + var builder = ITagHelperDescriptorBuilder.Create(typeName, assemblyName); + + if (attributes != null) + { + foreach (var attributeBuilder in attributes) + { + builder.BindAttribute(attributeBuilder); + } + } + + builder.TagMatchingRule(ruleBuilder => ruleBuilder.RequireTagName(tagName)); + + var descriptor = builder.Build(); + + return descriptor; + } } } diff --git a/test/Microsoft.AspNetCore.Razor.Evolution.Test/IntegrationTests/TestTagHelperDescriptors.cs b/test/Microsoft.AspNetCore.Razor.Evolution.Test/IntegrationTests/TestTagHelperDescriptors.cs index d4b8d017c9..a849f094b7 100644 --- a/test/Microsoft.AspNetCore.Razor.Evolution.Test/IntegrationTests/TestTagHelperDescriptors.cs +++ b/test/Microsoft.AspNetCore.Razor.Evolution.Test/IntegrationTests/TestTagHelperDescriptors.cs @@ -9,50 +9,35 @@ namespace Microsoft.AspNetCore.Razor.Evolution.IntegrationTests { public class TestTagHelperDescriptors { - internal static IEnumerable DefaultPAndInputTagHelperDescriptors { get; } - = BuildPAndInputTagHelperDescriptors(prefix: string.Empty); - internal static IEnumerable PrefixedPAndInputTagHelperDescriptors { get; } - = BuildPAndInputTagHelperDescriptors(prefix: "THS"); - internal static IEnumerable SimpleTagHelperDescriptors { get { return new[] { - new TagHelperDescriptor - { - TagName = "span", - TypeName = "SpanTagHelper", - AssemblyName = "TestAssembly", - }, - new TagHelperDescriptor - { - TagName = "div", - TypeName = "DivTagHelper", - AssemblyName = "TestAssembly", - }, - new TagHelperDescriptor - { - TagName = "input", - TypeName = "InputTagHelper", - AssemblyName = "TestAssembly", - Attributes = new[] + CreateTagHelperDescriptor( + tagName: "span", + typeName: "SpanTagHelper", + assemblyName: "TestAssembly"), + CreateTagHelperDescriptor( + tagName: "div", + typeName: "DivTagHelper", + assemblyName: "TestAssembly"), + CreateTagHelperDescriptor( + tagName: "input", + typeName: "InputTagHelper", + assemblyName: "TestAssembly", + attributes: new Action[] { - new TagHelperAttributeDescriptor - { - Name = "value", - PropertyName = "FooProp", - TypeName = "System.String" - }, - new TagHelperAttributeDescriptor - { - Name = "bound", - PropertyName = "BoundProp", - TypeName = "System.String" - } - } - } + builder => builder + .Name("value") + .PropertyName("FooProp") + .TypeName("System.String"), + builder => builder + .Name("bound") + .PropertyName("BoundProp") + .TypeName("System.String"), + }) }; } } @@ -66,113 +51,93 @@ namespace Microsoft.AspNetCore.Razor.Evolution.IntegrationTests return new[] { - new TagHelperDescriptor - { - TagName = "a", - TypeName = "TestNamespace.ATagHelper", - AssemblyName = "TestAssembly", - RequiredAttributes = new[] + CreateTagHelperDescriptor( + tagName: "a", + typeName: "TestNamespace.ATagHelper", + assemblyName: "TestAssembly", + ruleBuilders: new Action[] { - new TagHelperRequiredAttributeDescriptor - { - Name = "href", - NameComparison = TagHelperRequiredAttributeNameComparison.FullMatch, - Value = "~/", - ValueComparison = TagHelperRequiredAttributeValueComparison.FullMatch, - } - }, - }, - new TagHelperDescriptor - { - TagName = "a", - TypeName = "TestNamespace.ATagHelperMultipleSelectors", - AssemblyName = "TestAssembly", - RequiredAttributes = new[] + builder => builder + .RequireAttribute(attribute => attribute + .Name("href") + .NameComparisonMode(RequiredAttributeDescriptor.NameComparisonMode.FullMatch) + .Value("~/") + .ValueComparisonMode(RequiredAttributeDescriptor.ValueComparisonMode.FullMatch)), + }), + CreateTagHelperDescriptor( + tagName: "a", + typeName: "TestNamespace.ATagHelperMultipleSelectors", + assemblyName: "TestAssembly", + ruleBuilders: new Action[] { - new TagHelperRequiredAttributeDescriptor - { - Name = "href", - NameComparison = TagHelperRequiredAttributeNameComparison.FullMatch, - Value = "~/", - ValueComparison = TagHelperRequiredAttributeValueComparison.PrefixMatch, - }, - new TagHelperRequiredAttributeDescriptor - { - Name = "href", - NameComparison = TagHelperRequiredAttributeNameComparison.FullMatch, - Value = "?hello=world", - ValueComparison = TagHelperRequiredAttributeValueComparison.SuffixMatch, - } - }, - }, - new TagHelperDescriptor - { - TagName = "input", - TypeName = "TestNamespace.InputTagHelper", - AssemblyName = "TestAssembly", - Attributes = new TagHelperAttributeDescriptor[] + builder => builder + .RequireAttribute(attribute => attribute + .Name("href") + .NameComparisonMode(RequiredAttributeDescriptor.NameComparisonMode.FullMatch) + .Value("~/") + .ValueComparisonMode(RequiredAttributeDescriptor.ValueComparisonMode.PrefixMatch)) + .RequireAttribute(attribute => attribute + .Name("href") + .NameComparisonMode(RequiredAttributeDescriptor.NameComparisonMode.FullMatch) + .Value("?hello=world") + .ValueComparisonMode(RequiredAttributeDescriptor.ValueComparisonMode.SuffixMatch)), + }), + CreateTagHelperDescriptor( + tagName: "input", + typeName: "TestNamespace.InputTagHelper", + assemblyName: "TestAssembly", + attributes: new Action[] { - new TagHelperAttributeDescriptor("type", inputTypePropertyInfo), + builder => BuildBoundAttributeDescriptorFromPropertyInfo(builder, "type", inputTypePropertyInfo), }, - RequiredAttributes = new[] + ruleBuilders: new Action[] { - new TagHelperRequiredAttributeDescriptor - { - Name = "type", - NameComparison = TagHelperRequiredAttributeNameComparison.FullMatch, - Value = "text", - ValueComparison = TagHelperRequiredAttributeValueComparison.FullMatch, - } - }, - }, - new TagHelperDescriptor - { - TagName = "input", - TypeName = "TestNamespace.InputTagHelper2", - AssemblyName = "TestAssembly", - Attributes = new TagHelperAttributeDescriptor[] + builder => builder + .RequireAttribute(attribute => attribute + .Name("type") + .NameComparisonMode(RequiredAttributeDescriptor.NameComparisonMode.FullMatch) + .Value("text") + .ValueComparisonMode(RequiredAttributeDescriptor.ValueComparisonMode.FullMatch)), + }), + CreateTagHelperDescriptor( + tagName: "input", + typeName: "TestNamespace.InputTagHelper2", + assemblyName: "TestAssembly", + attributes: new Action[] { - new TagHelperAttributeDescriptor("type", inputTypePropertyInfo), + builder => BuildBoundAttributeDescriptorFromPropertyInfo(builder, "type", inputTypePropertyInfo), }, - RequiredAttributes = new[] + ruleBuilders: new Action[] { - new TagHelperRequiredAttributeDescriptor - { - Name = "ty", - NameComparison = TagHelperRequiredAttributeNameComparison.PrefixMatch, - } - }, - }, - new TagHelperDescriptor - { - TagName = "*", - TypeName = "TestNamespace.CatchAllTagHelper", - AssemblyName = "TestAssembly", - RequiredAttributes = new[] + builder => builder + .RequireAttribute(attribute => attribute + .Name("ty") + .NameComparisonMode(RequiredAttributeDescriptor.NameComparisonMode.PrefixMatch)), + }), + CreateTagHelperDescriptor( + tagName: "*", + typeName: "TestNamespace.CatchAllTagHelper", + assemblyName: "TestAssembly", + ruleBuilders: new Action[] { - new TagHelperRequiredAttributeDescriptor - { - Name = "href", - NameComparison = TagHelperRequiredAttributeNameComparison.FullMatch, - Value = "~/", - ValueComparison = TagHelperRequiredAttributeValueComparison.PrefixMatch, - } - }, - }, - new TagHelperDescriptor - { - TagName = "*", - TypeName = "TestNamespace.CatchAllTagHelper2", - AssemblyName = "TestAssembly", - RequiredAttributes = new[] + builder => builder + .RequireAttribute(attribute => attribute + .Name("href") + .NameComparisonMode(RequiredAttributeDescriptor.NameComparisonMode.FullMatch) + .Value("~/") + .ValueComparisonMode(RequiredAttributeDescriptor.ValueComparisonMode.PrefixMatch)), + }), + CreateTagHelperDescriptor( + tagName: "*", + typeName: "TestNamespace.CatchAllTagHelper2", + assemblyName: "TestAssembly", + ruleBuilders: new Action[] { - new TagHelperRequiredAttributeDescriptor - { - Name = "type", - NameComparison = TagHelperRequiredAttributeNameComparison.FullMatch, - } - }, - } + builder => builder + .RequireAttribute(attribute => attribute + .Name("type") + .NameComparisonMode(RequiredAttributeDescriptor.NameComparisonMode.FullMatch)), + }), }; } } @@ -183,38 +148,30 @@ namespace Microsoft.AspNetCore.Razor.Evolution.IntegrationTests { return new[] { - new TagHelperDescriptor - { - TagName = "*", - TypeName = "TestNamespace.CatchAllTagHelper", - AssemblyName = "TestAssembly", - Attributes = new[] + CreateTagHelperDescriptor( + tagName: "*", + typeName: "TestNamespace.CatchAllTagHelper", + assemblyName: "TestAssembly", + attributes: new Action[] { - new TagHelperAttributeDescriptor - { - Name = "catch-all", - PropertyName = "CatchAll", - IsEnum = true, - TypeName = typeof(MyEnum).FullName - }, - } - }, - new TagHelperDescriptor - { - TagName = "input", - TypeName = "TestNamespace.InputTagHelper", - AssemblyName = "TestAssembly", - Attributes = new[] + builder => builder + .Name("catch-all") + .PropertyName("CatchAll") + .AsEnum() + .TypeName(typeof(MyEnum).FullName), + }), + CreateTagHelperDescriptor( + tagName: "input", + typeName: "TestNamespace.InputTagHelper", + assemblyName: "TestAssembly", + attributes: new Action[] { - new TagHelperAttributeDescriptor - { - Name = "value", - PropertyName = "Value", - IsEnum = true, - TypeName = typeof(MyEnum).FullName - }, - } - }, + builder => builder + .Name("value") + .PropertyName("Value") + .AsEnum() + .TypeName(typeof(MyEnum).FullName), + }), }; } } @@ -225,52 +182,41 @@ namespace Microsoft.AspNetCore.Razor.Evolution.IntegrationTests { return new[] { - new TagHelperDescriptor - { - TagName = "*", - TypeName = "TestNamespace.CatchAllTagHelper", - AssemblyName = "TestAssembly", - Attributes = new[] + CreateTagHelperDescriptor( + tagName: "*", + typeName: "TestNamespace.CatchAllTagHelper", + assemblyName: "TestAssembly", + attributes: new Action[] { - new TagHelperAttributeDescriptor - { - Name = "[item]", - PropertyName = "ListItems", - TypeName = typeof(List).FullName - }, - new TagHelperAttributeDescriptor - { - Name = "[(item)]", - PropertyName = "ArrayItems", - TypeName = typeof(string[]).FullName - }, - new TagHelperAttributeDescriptor - { - Name = "(click)", - PropertyName = "Event1", - TypeName = typeof(Action).FullName - }, - new TagHelperAttributeDescriptor - { - Name = "(^click)", - PropertyName = "Event2", - TypeName = typeof(Action).FullName - }, - new TagHelperAttributeDescriptor - { - Name = "*something", - PropertyName = "StringProperty1", - TypeName = typeof(string).FullName - }, - new TagHelperAttributeDescriptor - { - Name = "#local", - PropertyName = "StringProperty2", - TypeName = typeof(string).FullName - }, + builder => builder + .Name("[item]") + .PropertyName("ListItems") + .TypeName(typeof(List).FullName), + builder => builder + .Name("[(item)]") + .PropertyName("ArrayItems") + .TypeName(typeof(string[]).FullName), + builder => builder + .Name("(click)") + .PropertyName("Event1") + .TypeName(typeof(Action).FullName), + builder => builder + .Name("(^click)") + .PropertyName("Event2") + .TypeName(typeof(Action).FullName), + builder => builder + .Name("*something") + .PropertyName("StringProperty1") + .TypeName(typeof(string).FullName), + builder => builder + .Name("#local") + .PropertyName("StringProperty2") + .TypeName(typeof(string).FullName), }, - RequiredAttributes = new[] { new TagHelperRequiredAttributeDescriptor { Name = "bound" } }, - }, + ruleBuilders: new Action[] + { + builder => builder.RequireAttribute(attribute => attribute.Name("bound")), + }), }; } } @@ -281,54 +227,42 @@ namespace Microsoft.AspNetCore.Razor.Evolution.IntegrationTests { return new[] { - new TagHelperDescriptor - { - TagName = "*", - TypeName = "TestNamespace.CatchAllTagHelper", - AssemblyName = "TestAssembly", - Attributes = new[] + CreateTagHelperDescriptor( + tagName: "*", + typeName: "TestNamespace.CatchAllTagHelper", + assemblyName: "TestAssembly", + attributes: new Action[] { - new TagHelperAttributeDescriptor - { - Name = "catchall-bound-string", - PropertyName = "BoundRequiredString", - TypeName = typeof(string).FullName, - IsStringProperty = true - } + builder => builder + .Name("catchall-bound-string") + .PropertyName("BoundRequiredString") + .TypeName(typeof(string).FullName), }, - RequiredAttributes = new[] + ruleBuilders: new Action[] { - new TagHelperRequiredAttributeDescriptor { Name = "catchall-unbound-required" } - }, - }, - new TagHelperDescriptor - { - TagName = "input", - TypeName = "TestNamespace.InputTagHelper", - AssemblyName = "TestAssembly", - Attributes = new[] + builder => builder.RequireAttribute(attribute => attribute.Name("catchall-unbound-required")), + }), + CreateTagHelperDescriptor( + tagName: "input", + typeName: "TestNamespace.InputTagHelper", + assemblyName: "TestAssembly", + attributes: new Action[] { - new TagHelperAttributeDescriptor - { - Name = "input-bound-required-string", - PropertyName = "BoundRequiredString", - TypeName = typeof(string).FullName, - IsStringProperty = true - }, - new TagHelperAttributeDescriptor - { - Name = "input-bound-string", - PropertyName = "BoundString", - TypeName = typeof(string).FullName, - IsStringProperty = true - } + builder => builder + .Name("input-bound-required-string") + .PropertyName("BoundRequiredString") + .TypeName(typeof(string).FullName), + builder => builder + .Name("input-bound-string") + .PropertyName("BoundString") + .TypeName(typeof(string).FullName), }, - RequiredAttributes = new[] + ruleBuilders: new Action[] { - new TagHelperRequiredAttributeDescriptor { Name = "input-bound-required-string" }, - new TagHelperRequiredAttributeDescriptor { Name = "input-unbound-required" } - }, - } + builder => builder + .RequireAttribute(attribute => attribute.Name("input-bound-required-string")) + .RequireAttribute(attribute => attribute.Name("input-unbound-required")), + }), }; } } @@ -339,82 +273,17 @@ namespace Microsoft.AspNetCore.Razor.Evolution.IntegrationTests { return new[] { - new TagHelperDescriptor - { - TagName = "input", - TypeName = "TestNamespace.InputTagHelper", - AssemblyName = "TestAssembly", - Attributes = new[] + CreateTagHelperDescriptor( + tagName: "input", + typeName: "TestNamespace.InputTagHelper", + assemblyName: "TestAssembly", + attributes: new Action[] { - new TagHelperAttributeDescriptor - { - Name = "bound", - PropertyName = "Bound", - TypeName = typeof(string).FullName, - IsStringProperty = true - } - } - } - }; - } - } - - internal static IEnumerable DuplicateTargetTagHelperDescriptors - { - get - { - var inputTypePropertyInfo = typeof(TestType).GetProperty("Type"); - var inputCheckedPropertyInfo = typeof(TestType).GetProperty("Checked"); - return new[] - { - new TagHelperDescriptor - { - TagName = "*", - TypeName = "TestNamespace.CatchAllTagHelper", - AssemblyName = "TestAssembly", - Attributes = new TagHelperAttributeDescriptor[] - { - new TagHelperAttributeDescriptor("type", inputTypePropertyInfo), - new TagHelperAttributeDescriptor("checked", inputCheckedPropertyInfo) - }, - RequiredAttributes = new[] { new TagHelperRequiredAttributeDescriptor { Name = "type" } }, - }, - new TagHelperDescriptor - { - TagName = "*", - TypeName = "TestNamespace.CatchAllTagHelper", - AssemblyName = "TestAssembly", - Attributes = new TagHelperAttributeDescriptor[] - { - new TagHelperAttributeDescriptor("type", inputTypePropertyInfo), - new TagHelperAttributeDescriptor("checked", inputCheckedPropertyInfo) - }, - RequiredAttributes = new[] { new TagHelperRequiredAttributeDescriptor { Name = "checked" } }, - }, - new TagHelperDescriptor - { - TagName = "input", - TypeName = "TestNamespace.InputTagHelper", - AssemblyName = "TestAssembly", - Attributes = new TagHelperAttributeDescriptor[] - { - new TagHelperAttributeDescriptor("type", inputTypePropertyInfo), - new TagHelperAttributeDescriptor("checked", inputCheckedPropertyInfo) - }, - RequiredAttributes = new[] { new TagHelperRequiredAttributeDescriptor { Name = "type" } }, - }, - new TagHelperDescriptor - { - TagName = "input", - TypeName = "TestNamespace.InputTagHelper", - AssemblyName = "TestAssembly", - Attributes = new TagHelperAttributeDescriptor[] - { - new TagHelperAttributeDescriptor("type", inputTypePropertyInfo), - new TagHelperAttributeDescriptor("checked", inputCheckedPropertyInfo) - }, - RequiredAttributes = new[] { new TagHelperRequiredAttributeDescriptor { Name = "checked" } }, - } + builder => builder + .Name("bound") + .PropertyName("Bound") + .TypeName(typeof(string).FullName) + }), }; } } @@ -427,47 +296,49 @@ namespace Microsoft.AspNetCore.Razor.Evolution.IntegrationTests var inputCheckedPropertyInfo = typeof(TestType).GetProperty("Checked"); return new[] { - new TagHelperDescriptor - { - TagName = "p", - TypeName = "TestNamespace.PTagHelper", - AssemblyName = "TestAssembly", - RequiredAttributes = new[] { new TagHelperRequiredAttributeDescriptor { Name = "class" } }, - }, - new TagHelperDescriptor - { - TagName = "input", - TypeName = "TestNamespace.InputTagHelper", - AssemblyName = "TestAssembly", - Attributes = new TagHelperAttributeDescriptor[] + CreateTagHelperDescriptor( + tagName: "p", + typeName: "TestNamespace.PTagHelper", + assemblyName: "TestAssembly", + ruleBuilders: new Action[] { - new TagHelperAttributeDescriptor("type", inputTypePropertyInfo) - }, - RequiredAttributes = new[] { new TagHelperRequiredAttributeDescriptor { Name = "type" } }, - }, - new TagHelperDescriptor - { - TagName = "input", - TypeName = "TestNamespace.InputTagHelper2", - AssemblyName = "TestAssembly", - Attributes = new TagHelperAttributeDescriptor[] + builder => builder.RequireAttribute(attribute => attribute.Name("class")), + }), + CreateTagHelperDescriptor( + tagName: "input", + typeName: "TestNamespace.InputTagHelper", + assemblyName: "TestAssembly", + attributes: new Action[] { - new TagHelperAttributeDescriptor("type", inputTypePropertyInfo), - new TagHelperAttributeDescriptor("checked", inputCheckedPropertyInfo) + builder => BuildBoundAttributeDescriptorFromPropertyInfo(builder, "type", inputTypePropertyInfo), }, - RequiredAttributes = new[] + ruleBuilders: new Action[] { - new TagHelperRequiredAttributeDescriptor { Name = "type" }, - new TagHelperRequiredAttributeDescriptor { Name = "checked" } + builder => builder.RequireAttribute(attribute => attribute.Name("type")), + }), + CreateTagHelperDescriptor( + tagName: "input", + typeName: "TestNamespace.InputTagHelper2", + assemblyName: "TestAssembly", + attributes: new Action[] + { + builder => BuildBoundAttributeDescriptorFromPropertyInfo(builder, "type", inputTypePropertyInfo), + builder => BuildBoundAttributeDescriptorFromPropertyInfo(builder, "checked", inputCheckedPropertyInfo), }, - }, - new TagHelperDescriptor - { - TagName = "*", - TypeName = "TestNamespace.CatchAllTagHelper", - AssemblyName = "TestAssembly", - RequiredAttributes = new[] { new TagHelperRequiredAttributeDescriptor { Name = "catchAll" } }, - } + ruleBuilders: new Action[] + { + builder => builder + .RequireAttribute(attribute => attribute.Name("type")) + .RequireAttribute(attribute => attribute.Name("checked")), + }), + CreateTagHelperDescriptor( + tagName: "*", + typeName: "TestNamespace.CatchAllTagHelper", + assemblyName: "TestAssembly", + ruleBuilders: new Action[] + { + builder => builder.RequireAttribute(attribute => attribute.Name("catchAll")), + }), }; } } @@ -478,91 +349,48 @@ namespace Microsoft.AspNetCore.Razor.Evolution.IntegrationTests { return new[] { - new TagHelperDescriptor - { - TagName = "input", - TypeName = "TestNamespace.InputTagHelper1", - AssemblyName = "TestAssembly", - Attributes = new[] + CreateTagHelperDescriptor( + tagName: "input", + typeName: "TestNamespace.InputTagHelper1", + assemblyName: "TestAssembly", + attributes: new Action[] { - new TagHelperAttributeDescriptor - { - Name = "int-prefix-grabber", - PropertyName = "IntProperty", - TypeName = typeof(int).FullName - }, - new TagHelperAttributeDescriptor - { - Name = "int-dictionary", - PropertyName = "IntDictionaryProperty", - TypeName = typeof(IDictionary).FullName - }, - new TagHelperAttributeDescriptor - { - Name = "string-dictionary", - PropertyName = "StringDictionaryProperty", - TypeName = "Namespace.DictionaryWithoutParameterlessConstructor" - }, - new TagHelperAttributeDescriptor - { - Name = "string-prefix-grabber", - PropertyName = "StringProperty", - TypeName = typeof(string).FullName, - IsStringProperty = true - }, - new TagHelperAttributeDescriptor - { - Name = "int-prefix-", - PropertyName = "IntDictionaryProperty", - TypeName = typeof(int).FullName, - IsIndexer = true - }, - new TagHelperAttributeDescriptor - { - Name = "string-prefix-", - PropertyName = "StringDictionaryProperty", - TypeName = typeof(string).FullName, - IsIndexer = true, - IsStringProperty = true - } - } - }, - new TagHelperDescriptor - { - TagName = "input", - TypeName = "TestNamespace.InputTagHelper2", - AssemblyName = "TestAssembly", - Attributes = new[] + builder => builder + .Name("int-prefix-grabber") + .PropertyName("IntProperty") + .TypeName(typeof(int).FullName), + builder => builder + .Name("int-dictionary") + .PropertyName("IntDictionaryProperty") + .TypeName(typeof(IDictionary).FullName) + .AsDictionary("int-prefix-", typeof(int).FullName), + builder => builder + .Name("string-prefix-grabber") + .PropertyName("StringProperty") + .TypeName(typeof(string).FullName), + builder => builder + .Name("string-dictionary") + .PropertyName("StringDictionaryProperty") + .TypeName("Namespace.DictionaryWithoutParameterlessConstructor") + .AsDictionary("string-prefix-", typeof(string).FullName), + }), + CreateTagHelperDescriptor( + tagName: "input", + typeName: "TestNamespace.InputTagHelper2", + assemblyName: "TestAssembly", + attributes: new Action[] { - new TagHelperAttributeDescriptor - { - Name = "int-dictionary", - PropertyName = "IntDictionaryProperty", - TypeName = typeof(int).FullName - }, - new TagHelperAttributeDescriptor - { - Name = "string-dictionary", - PropertyName = "StringDictionaryProperty", - TypeName = "Namespace.DictionaryWithoutParameterlessConstructor" - }, - new TagHelperAttributeDescriptor - { - Name = "int-prefix-", - PropertyName = "IntDictionaryProperty", - TypeName = typeof(int).FullName, - IsIndexer = true - }, - new TagHelperAttributeDescriptor - { - Name = "string-prefix-", - PropertyName = "StringDictionaryProperty", - TypeName = typeof(string).FullName, - IsIndexer = true, - IsStringProperty = true - } - } - } + builder => builder + .Name("int-dictionary") + .PropertyName("IntDictionaryProperty") + .TypeName(typeof(int).FullName) + .AsDictionary("int-prefix-", typeof(int).FullName), + builder => builder + .Name("string-dictionary") + .PropertyName("StringDictionaryProperty") + .TypeName("Namespace.DictionaryWithoutParameterlessConstructor") + .AsDictionary("string-prefix-", typeof(string).FullName), + }), }; } } @@ -574,71 +402,120 @@ namespace Microsoft.AspNetCore.Razor.Evolution.IntegrationTests var propertyInfo = typeof(TestType).GetProperty("BoundProperty"); return new[] { - new TagHelperDescriptor - { - TagName = "MyTagHelper", - TypeName = "TestNamespace.MyTagHelper", - AssemblyName = "TestAssembly", - Attributes = new [] + CreateTagHelperDescriptor( + tagName: "MyTagHelper", + typeName: "TestNamespace.MyTagHelper", + assemblyName: "TestAssembly", + attributes: new Action[] { - new TagHelperAttributeDescriptor("BoundProperty", propertyInfo) - } - }, - new TagHelperDescriptor - { - TagName = "NestedTagHelper", - TypeName = "TestNamespace.NestedTagHelper", - AssemblyName = "TestAssembly" - } + builder => BuildBoundAttributeDescriptorFromPropertyInfo(builder, "BoundProperty", propertyInfo), + }), + CreateTagHelperDescriptor( + tagName: "NestedTagHelper", + typeName: "TestNamespace.NestedTagHelper", + assemblyName: "TestAssembly"), }; } } - private static IEnumerable BuildPAndInputTagHelperDescriptors(string prefix) + internal static IEnumerable DefaultPAndInputTagHelperDescriptors { - var pAgePropertyInfo = typeof(TestType).GetProperty("Age"); - var inputTypePropertyInfo = typeof(TestType).GetProperty("Type"); - var checkedPropertyInfo = typeof(TestType).GetProperty("Checked"); - - return new[] + get { - new TagHelperDescriptor + var pAgePropertyInfo = typeof(TestType).GetProperty("Age"); + var inputTypePropertyInfo = typeof(TestType).GetProperty("Type"); + var checkedPropertyInfo = typeof(TestType).GetProperty("Checked"); + + return new[] { - Prefix = prefix, - TagName = "p", - TypeName = "TestNamespace.PTagHelper", - AssemblyName = "TestAssembly", - Attributes = new TagHelperAttributeDescriptor[] - { - new TagHelperAttributeDescriptor("age", pAgePropertyInfo) - }, - TagStructure = TagStructure.NormalOrSelfClosing - }, - new TagHelperDescriptor + CreateTagHelperDescriptor( + tagName: "p", + typeName: "TestNamespace.PTagHelper", + assemblyName: "TestAssembly", + attributes: new Action[] + { + builder => BuildBoundAttributeDescriptorFromPropertyInfo(builder, "age", pAgePropertyInfo), + }, + ruleBuilders: new Action[] + { + builder => builder.RequireTagStructure(TagStructure.NormalOrSelfClosing) + }), + CreateTagHelperDescriptor( + tagName: "input", + typeName: "TestNamespace.InputTagHelper", + assemblyName: "TestAssembly", + attributes: new Action[] + { + builder => BuildBoundAttributeDescriptorFromPropertyInfo(builder, "type", inputTypePropertyInfo), + }, + ruleBuilders: new Action[] + { + builder => builder.RequireTagStructure(TagStructure.WithoutEndTag) + }), + CreateTagHelperDescriptor( + tagName: "input", + typeName: "TestNamespace.InputTagHelper2", + assemblyName: "TestAssembly", + attributes: new Action[] + { + builder => BuildBoundAttributeDescriptorFromPropertyInfo(builder, "type", inputTypePropertyInfo), + builder => BuildBoundAttributeDescriptorFromPropertyInfo(builder, "checked", checkedPropertyInfo), + }), + }; + } + } + + private static TagHelperDescriptor CreateTagHelperDescriptor( + string tagName, + string typeName, + string assemblyName, + IEnumerable> attributes = null, + IEnumerable> ruleBuilders = null) + { + var builder = ITagHelperDescriptorBuilder.Create(typeName, assemblyName); + + if (attributes != null) + { + foreach (var attributeBuilder in attributes) { - Prefix = prefix, - TagName = "input", - TypeName = "TestNamespace.InputTagHelper", - AssemblyName = "TestAssembly", - Attributes = new TagHelperAttributeDescriptor[] - { - new TagHelperAttributeDescriptor("type", inputTypePropertyInfo) - }, - TagStructure = TagStructure.WithoutEndTag - }, - new TagHelperDescriptor - { - Prefix = prefix, - TagName = "input", - TypeName = "TestNamespace.InputTagHelper2", - AssemblyName = "TestAssembly", - Attributes = new TagHelperAttributeDescriptor[] - { - new TagHelperAttributeDescriptor("type", inputTypePropertyInfo), - new TagHelperAttributeDescriptor("checked", checkedPropertyInfo) - }, + builder.BindAttribute(attributeBuilder); } - }; + } + + if (ruleBuilders != null) + { + foreach (var ruleBuilder in ruleBuilders) + { + builder.TagMatchingRule(innerRuleBuilder => { + innerRuleBuilder.RequireTagName(tagName); + ruleBuilder(innerRuleBuilder); + }); + } + } + else + { + builder.TagMatchingRule(ruleBuilder => ruleBuilder.RequireTagName(tagName)); + } + + var descriptor = builder.Build(); + + return descriptor; + } + + private static void BuildBoundAttributeDescriptorFromPropertyInfo( + ITagHelperBoundAttributeDescriptorBuilder builder, + string name, + PropertyInfo propertyInfo) + { + builder + .Name(name) + .PropertyName(propertyInfo.Name) + .TypeName(propertyInfo.PropertyType.FullName); + + if (propertyInfo.PropertyType.GetTypeInfo().IsEnum) + { + builder.AsEnum(); + } } private class TestType diff --git a/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/CaseSensitiveTagHelperDescriptorComparer.cs b/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/CaseSensitiveTagHelperDescriptorComparer.cs deleted file mode 100644 index 263ff352b5..0000000000 --- a/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/CaseSensitiveTagHelperDescriptorComparer.cs +++ /dev/null @@ -1,95 +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; -using System.Linq; -using Microsoft.Extensions.Internal; -using Xunit; - -namespace Microsoft.AspNetCore.Razor.Evolution.Legacy -{ - internal class CaseSensitiveTagHelperDescriptorComparer : TagHelperDescriptorComparer - { - public new static readonly CaseSensitiveTagHelperDescriptorComparer Default = - new CaseSensitiveTagHelperDescriptorComparer(); - - private CaseSensitiveTagHelperDescriptorComparer() - : base() - { - } - - public override bool Equals(TagHelperDescriptor descriptorX, TagHelperDescriptor descriptorY) - { - if (descriptorX == descriptorY) - { - return true; - } - - Assert.True(base.Equals(descriptorX, descriptorY)); - - // Normal comparer doesn't care about the case, required attribute order, allowed children order, - // attributes or prefixes. In tests we do. - Assert.Equal(descriptorX.TagName, descriptorY.TagName, StringComparer.Ordinal); - Assert.Equal(descriptorX.Prefix, descriptorY.Prefix, StringComparer.Ordinal); - Assert.Equal( - descriptorX.RequiredAttributes, - descriptorY.RequiredAttributes, - CaseSensitiveTagHelperRequiredAttributeDescriptorComparer.Default); - Assert.Equal(descriptorX.RequiredParent, descriptorY.RequiredParent, StringComparer.Ordinal); - - if (descriptorX.AllowedChildren != descriptorY.AllowedChildren) - { - Assert.Equal(descriptorX.AllowedChildren, descriptorY.AllowedChildren, StringComparer.Ordinal); - } - - Assert.Equal( - descriptorX.Attributes, - descriptorY.Attributes, - TagHelperAttributeDescriptorComparer.Default); - Assert.Equal( - descriptorX.DesignTimeDescriptor, - descriptorY.DesignTimeDescriptor, - TagHelperDesignTimeDescriptorComparer.Default); - - return true; - } - - public override int GetHashCode(TagHelperDescriptor descriptor) - { - var hashCodeCombiner = HashCodeCombiner.Start(); - hashCodeCombiner.Add(base.GetHashCode(descriptor)); - hashCodeCombiner.Add(descriptor.TagName, StringComparer.Ordinal); - hashCodeCombiner.Add(descriptor.Prefix, StringComparer.Ordinal); - - if (descriptor.DesignTimeDescriptor != null) - { - hashCodeCombiner.Add( - TagHelperDesignTimeDescriptorComparer.Default.GetHashCode(descriptor.DesignTimeDescriptor)); - } - - foreach (var requiredAttribute in descriptor.RequiredAttributes.OrderBy(attribute => attribute.Name)) - { - hashCodeCombiner.Add( - CaseSensitiveTagHelperRequiredAttributeDescriptorComparer.Default.GetHashCode(requiredAttribute)); - } - - if (descriptor.AllowedChildren != null) - { - foreach (var child in descriptor.AllowedChildren.OrderBy(child => child)) - { - hashCodeCombiner.Add(child, StringComparer.Ordinal); - } - } - - var orderedAttributeHashCodes = descriptor.Attributes - .Select(attribute => TagHelperAttributeDescriptorComparer.Default.GetHashCode(attribute)) - .OrderBy(hashcode => hashcode); - foreach (var attributeHashCode in orderedAttributeHashCodes) - { - hashCodeCombiner.Add(attributeHashCode); - } - - return hashCodeCombiner.CombinedHash; - } - } -} \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/CaseSensitiveTagHelperRequiredAttributeDescriptorComparer.cs b/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/CaseSensitiveTagHelperRequiredAttributeDescriptorComparer.cs deleted file mode 100644 index 6fed1a2944..0000000000 --- a/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/CaseSensitiveTagHelperRequiredAttributeDescriptorComparer.cs +++ /dev/null @@ -1,42 +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; -using Microsoft.Extensions.Internal; -using Xunit; - -namespace Microsoft.AspNetCore.Razor.Evolution.Legacy -{ - internal class CaseSensitiveTagHelperRequiredAttributeDescriptorComparer : TagHelperRequiredAttributeDescriptorComparer - { - public new static readonly CaseSensitiveTagHelperRequiredAttributeDescriptorComparer Default = - new CaseSensitiveTagHelperRequiredAttributeDescriptorComparer(); - - private CaseSensitiveTagHelperRequiredAttributeDescriptorComparer() - : base() - { - } - - public override bool Equals(TagHelperRequiredAttributeDescriptor descriptorX, TagHelperRequiredAttributeDescriptor descriptorY) - { - if (descriptorX == descriptorY) - { - return true; - } - - Assert.True(base.Equals(descriptorX, descriptorY)); - Assert.Equal(descriptorX.Name, descriptorY.Name, StringComparer.Ordinal); - - return true; - } - - public override int GetHashCode(TagHelperRequiredAttributeDescriptor descriptor) - { - var hashCodeCombiner = HashCodeCombiner.Start(); - hashCodeCombiner.Add(base.GetHashCode(descriptor)); - hashCodeCombiner.Add(descriptor.Name, StringComparer.Ordinal); - - return hashCodeCombiner.CombinedHash; - } - } -} \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/RazorEditorParserTest.cs b/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/RazorEditorParserTest.cs index ade7ce6e82..61837b4f9a 100644 --- a/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/RazorEditorParserTest.cs +++ b/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/RazorEditorParserTest.cs @@ -86,11 +86,9 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy // Arrange var descriptors = new[] { - new TagHelperDescriptor - { - TagName = "p", - TypeName = "PTagHelper" - }, + ITagHelperDescriptorBuilder.Create("PTagHelper", "TestAssembly") + .TagMatchingRule(rule => rule.RequireTagName("p")) + .Build() }; var parser = new RazorEditorParser(CreateTemplateEngine(@"C:\This\Is\A\Test\Path"), @"C:\This\Is\A\Test\Path"); @@ -233,27 +231,17 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy // Arrange var descriptors = new[] { - new TagHelperDescriptor - { - TagName = "p", - TypeName = "PTagHelper", - AssemblyName = "Test", - Attributes = new[] - { - new TagHelperAttributeDescriptor - { - Name = "obj-attr", - TypeName = typeof(object).FullName, - PropertyName = "ObjectAttribute", - }, - new TagHelperAttributeDescriptor - { - Name = "str-attr", - TypeName = typeof(string).FullName, - PropertyName = "StringAttribute", - }, - } - }, + ITagHelperDescriptorBuilder.Create("PTagHelper", "Test") + .TagMatchingRule(rule => rule.RequireTagName("p")) + .BindAttribute(attribute => attribute + .Name("obj-attr") + .TypeName(typeof(object).FullName) + .PropertyName("ObjectAttribute")) + .BindAttribute(attribute => attribute + .Name("str-attr") + .TypeName(typeof(string).FullName) + .PropertyName("StringAttribute")) + .Build() }; var parser = new RazorEditorParser(CreateTemplateEngine(@"C:\This\Is\A\Test\Path", descriptors), @"C:\This\Is\A\Test\Path"); diff --git a/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/TagHelperAttributeDescriptorComparer.cs b/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/TagHelperAttributeDescriptorComparer.cs deleted file mode 100644 index c50eb84dd1..0000000000 --- a/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/TagHelperAttributeDescriptorComparer.cs +++ /dev/null @@ -1,56 +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; -using System.Collections.Generic; -using Microsoft.Extensions.Internal; -using Xunit; - -namespace Microsoft.AspNetCore.Razor.Evolution.Legacy -{ - internal class TagHelperAttributeDescriptorComparer : IEqualityComparer - { - public static readonly TagHelperAttributeDescriptorComparer Default = - new TagHelperAttributeDescriptorComparer(); - - private TagHelperAttributeDescriptorComparer() - { - } - - public bool Equals(TagHelperAttributeDescriptor descriptorX, TagHelperAttributeDescriptor descriptorY) - { - if (descriptorX == descriptorY) - { - return true; - } - - Assert.NotNull(descriptorX); - Assert.NotNull(descriptorY); - Assert.Equal(descriptorX.IsIndexer, descriptorY.IsIndexer); - Assert.Equal(descriptorX.Name, descriptorY.Name, StringComparer.Ordinal); - Assert.Equal(descriptorX.PropertyName, descriptorY.PropertyName, StringComparer.Ordinal); - Assert.Equal(descriptorX.TypeName, descriptorY.TypeName, StringComparer.Ordinal); - Assert.Equal(descriptorX.IsEnum, descriptorY.IsEnum); - Assert.Equal(descriptorX.IsStringProperty, descriptorY.IsStringProperty); - - return TagHelperAttributeDesignTimeDescriptorComparer.Default.Equals( - descriptorX.DesignTimeDescriptor, - descriptorY.DesignTimeDescriptor); - } - - public int GetHashCode(TagHelperAttributeDescriptor descriptor) - { - var hashCodeCombiner = HashCodeCombiner.Start(); - hashCodeCombiner.Add(descriptor.IsIndexer); - hashCodeCombiner.Add(descriptor.Name, StringComparer.Ordinal); - hashCodeCombiner.Add(descriptor.PropertyName, StringComparer.Ordinal); - hashCodeCombiner.Add(descriptor.TypeName, StringComparer.Ordinal); - hashCodeCombiner.Add(descriptor.IsEnum); - hashCodeCombiner.Add(descriptor.IsStringProperty); - hashCodeCombiner.Add(TagHelperAttributeDesignTimeDescriptorComparer.Default.GetHashCode( - descriptor.DesignTimeDescriptor)); - - return hashCodeCombiner; - } - } -} \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/TagHelperAttributeDesignTimeDescriptorComparer.cs b/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/TagHelperAttributeDesignTimeDescriptorComparer.cs deleted file mode 100644 index c54668476c..0000000000 --- a/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/TagHelperAttributeDesignTimeDescriptorComparer.cs +++ /dev/null @@ -1,47 +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; -using System.Collections.Generic; -using Microsoft.Extensions.Internal; -using Xunit; - -namespace Microsoft.AspNetCore.Razor.Evolution.Legacy -{ - internal class TagHelperAttributeDesignTimeDescriptorComparer : - IEqualityComparer - { - public static readonly TagHelperAttributeDesignTimeDescriptorComparer Default = - new TagHelperAttributeDesignTimeDescriptorComparer(); - - private TagHelperAttributeDesignTimeDescriptorComparer() - { - } - - public bool Equals( - TagHelperAttributeDesignTimeDescriptor descriptorX, - TagHelperAttributeDesignTimeDescriptor descriptorY) - { - if (descriptorX == descriptorY) - { - return true; - } - - Assert.NotNull(descriptorX); - Assert.NotNull(descriptorY); - Assert.Equal(descriptorX.Summary, descriptorY.Summary, StringComparer.Ordinal); - Assert.Equal(descriptorX.Remarks, descriptorY.Remarks, StringComparer.Ordinal); - - return true; - } - - public int GetHashCode(TagHelperAttributeDesignTimeDescriptor descriptor) - { - var hashCodeCombiner = HashCodeCombiner.Start(); - hashCodeCombiner.Add(descriptor.Summary, StringComparer.Ordinal); - hashCodeCombiner.Add(descriptor.Remarks, StringComparer.Ordinal); - - return hashCodeCombiner; - } - } -} diff --git a/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/TagHelperBlockRewriterTest.cs b/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/TagHelperBlockRewriterTest.cs index 02d7d88ab5..fadd35cf33 100644 --- a/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/TagHelperBlockRewriterTest.cs +++ b/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/TagHelperBlockRewriterTest.cs @@ -111,54 +111,44 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy // Arrange var descriptors = new[] { - new TagHelperDescriptor - { - TagName = "*", - TypeName = "CatchAllTagHelper", - AssemblyName = "SomeAssembly", - Attributes = new[] - { - new TagHelperAttributeDescriptor - { - Name = "[item]", - PropertyName = "ListItems", - TypeName = typeof(List).FullName - }, - new TagHelperAttributeDescriptor - { - Name = "[(item)]", - PropertyName = "ArrayItems", - TypeName = typeof(string[]).FullName - }, - new TagHelperAttributeDescriptor - { - Name = "(click)", - PropertyName = "Event1", - TypeName = typeof(Action).FullName - }, - new TagHelperAttributeDescriptor - { - Name = "(^click)", - PropertyName = "Event2", - TypeName = typeof(Action).FullName - }, - new TagHelperAttributeDescriptor - { - Name = "*something", - PropertyName = "StringProperty1", - TypeName = typeof(string).FullName - }, - new TagHelperAttributeDescriptor - { - Name = "#local", - PropertyName = "StringProperty2", - TypeName = typeof(string).FullName - }, - }, - RequiredAttributes = new[] { new TagHelperRequiredAttributeDescriptor { Name = "bound" } }, - }, + ITagHelperDescriptorBuilder.Create("CatchAllTagHelper", "SomeAssembly") + .TagMatchingRule(rule => + rule + .RequireTagName("*") + .RequireAttribute(attribute => attribute.Name("bound"))) + .BindAttribute(attribute => + attribute + .Name("[item]") + .PropertyName("ListItems") + .TypeName(typeof(List).Namespace + "List")) + .BindAttribute(attribute => + attribute + .Name("[(item)]") + .PropertyName("ArrayItems") + .TypeName(typeof(string[]).Namespace + "System.String[]")) + .BindAttribute(attribute => + attribute + .Name("(click)") + .PropertyName("Event1") + .TypeName(typeof(Action).FullName)) + .BindAttribute(attribute => + attribute + .Name("(^click)") + .PropertyName("Event2") + .TypeName(typeof(Action).FullName)) + .BindAttribute(attribute => + attribute + .Name("*something") + .PropertyName("StringProperty1") + .TypeName(typeof(string).FullName)) + .BindAttribute(attribute => + attribute + .Name("#local") + .PropertyName("StringProperty2") + .TypeName(typeof(string).FullName)) + .Build() }; - var descriptorProvider = new TagHelperDescriptorProvider(descriptors); + var descriptorProvider = new TagHelperDescriptorProvider(null, descriptors); // Act & Assert EvaluateData(descriptorProvider, documentContent, (MarkupBlock)expectedOutput, expectedErrors: new RazorError[0]); @@ -225,16 +215,15 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy { // Arrange var descriptors = new TagHelperDescriptor[] - { - new TagHelperDescriptor - { - TagName = "input", - TypeName = "InputTagHelper", - AssemblyName = "SomeAssembly", - TagStructure = TagStructure.WithoutEndTag, - } - }; - var descriptorProvider = new TagHelperDescriptorProvider(descriptors); + { + ITagHelperDescriptorBuilder.Create("InputTagHelper", "SomeAssembly") + .TagMatchingRule(rule => + rule + .RequireTagName("input") + .RequireTagStructure(TagStructure.WithoutEndTag)) + .Build() + }; + var descriptorProvider = new TagHelperDescriptorProvider(null, descriptors); // Act & Assert EvaluateData(descriptorProvider, documentContent, (MarkupBlock)expectedOutput, expectedErrors: new RazorError[0]); @@ -317,31 +306,29 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy [MemberData(nameof(TagStructureCompatibilityData))] public void Rewrite_AllowsCompatibleTagStructures( string documentContent, - int structure1, - int structure2, + TagStructure structure1, + TagStructure structure2, object expectedOutput) { // Arrange var factory = new SpanFactory(); var blockFactory = new BlockFactory(factory); var descriptors = new TagHelperDescriptor[] - { - new TagHelperDescriptor - { - TagName = "input", - TypeName = "InputTagHelper1", - AssemblyName = "SomeAssembly", - TagStructure = (TagStructure)structure1 - }, - new TagHelperDescriptor - { - TagName = "input", - TypeName = "InputTagHelper2", - AssemblyName = "SomeAssembly", - TagStructure = (TagStructure)structure2 - } - }; - var descriptorProvider = new TagHelperDescriptorProvider(descriptors); + { + ITagHelperDescriptorBuilder.Create("InputTagHelper1", "SomeAssembly") + .TagMatchingRule(rule => + rule + .RequireTagName("input") + .RequireTagStructure(structure1)) + .Build(), + ITagHelperDescriptorBuilder.Create("InputTagHelper2", "SomeAssembly") + .TagMatchingRule(rule => + rule + .RequireTagName("input") + .RequireTagStructure(structure2)) + .Build() + }; + var descriptorProvider = new TagHelperDescriptorProvider(null, descriptors); // Act & Assert EvaluateData(descriptorProvider, documentContent, (MarkupBlock)expectedOutput, expectedErrors: new RazorError[0]); @@ -1199,43 +1186,33 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy [Theory] [MemberData(nameof(CodeTagHelperAttributesData))] - public void TagHelperParseTreeRewriter_CreatesMarkupCodeSpansForNonStringTagHelperAttributes( + public void Rewrite_CreatesMarkupCodeSpansForNonStringTagHelperAttributes( string documentContent, object expectedOutput) { // Arrange var descriptors = new TagHelperDescriptor[] { - new TagHelperDescriptor - { - TagName = "person", - TypeName = "PersonTagHelper", - AssemblyName = "personAssembly", - Attributes = new[] - { - new TagHelperAttributeDescriptor - { - Name = "age", - PropertyName = "Age", - TypeName = typeof(int).FullName - }, - new TagHelperAttributeDescriptor - { - Name = "birthday", - PropertyName = "BirthDay", - TypeName = typeof(DateTime).FullName - }, - new TagHelperAttributeDescriptor - { - Name = "name", - PropertyName = "Name", - TypeName = typeof(string).FullName, - IsStringProperty = true - } - } - } + ITagHelperDescriptorBuilder.Create("PersonTagHelper", "personAssembly") + .TagMatchingRule(rule => rule.RequireTagName("person")) + .BindAttribute(attribute => + attribute + .Name("age") + .PropertyName("Age") + .TypeName(typeof(int).FullName)) + .BindAttribute(attribute => + attribute + .Name("birthday") + .PropertyName("BirthDay") + .TypeName(typeof(DateTime).FullName)) + .BindAttribute(attribute => + attribute + .Name("name") + .PropertyName("Name") + .TypeName(typeof(string).FullName)) + .Build() }; - var providerContext = new TagHelperDescriptorProvider(descriptors); + var providerContext = new TagHelperDescriptorProvider(null, descriptors); // Act & Assert EvaluateData(providerContext, @@ -2253,31 +2230,22 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy { // Arrange var descriptors = new TagHelperDescriptor[] - { - new TagHelperDescriptor - { - TagName = "myth", - TypeName = "mythTagHelper", - AssemblyName = "SomeAssembly", - Attributes = new[] - { - new TagHelperAttributeDescriptor - { - Name = "bound", - PropertyName = "Bound", - TypeName = typeof(bool).FullName - }, - new TagHelperAttributeDescriptor - { - Name = "name", - PropertyName = "Name", - TypeName = typeof(string).FullName, - IsStringProperty = true - } - } - } - }; - var descriptorProvider = new TagHelperDescriptorProvider(descriptors); + { + ITagHelperDescriptorBuilder.Create("mythTagHelper", "SomeAssembly") + .TagMatchingRule(rule => rule.RequireTagName("myth")) + .BindAttribute(attribute => + attribute + .Name("bound") + .PropertyName("Bound") + .TypeName(typeof(bool).FullName)) + .BindAttribute(attribute => + attribute + .Name("name") + .PropertyName("Name") + .TypeName(typeof(string).FullName)) + .Build() + }; + var descriptorProvider = new TagHelperDescriptorProvider(null, descriptors); // Act & Assert EvaluateData(descriptorProvider, documentContent, (MarkupBlock)expectedOutput, (RazorError[])expectedErrors); @@ -3002,7 +2970,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy new[] { new RazorError( - string.Format(errorFormat, "int-dictionary", "input", typeof(IDictionary).FullName), + string.Format(errorFormat, "int-dictionary", "input", typeof(IDictionary).Namespace + ".IDictionary"), absoluteIndex: 7, lineIndex: 0, columnIndex: 7, @@ -3022,7 +2990,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy new[] { new RazorError( - string.Format(errorFormat, "string-dictionary", "input", typeof(IDictionary).FullName), + string.Format(errorFormat, "string-dictionary", "input", typeof(IDictionary).Namespace + ".IDictionary"), absoluteIndex: 7, lineIndex: 0, columnIndex: 7, @@ -3911,126 +3879,63 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy { // Arrange var descriptors = new TagHelperDescriptor[] - { - new TagHelperDescriptor - { - TagName = "input", - TypeName = "InputTagHelper1", - AssemblyName = "SomeAssembly", - Attributes = new[] - { - new TagHelperAttributeDescriptor - { - Name = "bound-required-string", - PropertyName = "BoundRequiredString", - TypeName = typeof(string).FullName, - IsStringProperty = true - } - }, - RequiredAttributes = new[] - { - new TagHelperRequiredAttributeDescriptor { Name = "unbound-required" } - } - }, - new TagHelperDescriptor - { - TagName = "input", - TypeName = "InputTagHelper1", - AssemblyName = "SomeAssembly", - Attributes = new[] - { - new TagHelperAttributeDescriptor - { - Name = "bound-required-string", - PropertyName = "BoundRequiredString", - TypeName = typeof(string).FullName, - IsStringProperty = true - } - }, - RequiredAttributes = new[] - { - new TagHelperRequiredAttributeDescriptor { Name = "bound-required-string" } - } - }, - new TagHelperDescriptor - { - TagName = "input", - TypeName = "InputTagHelper2", - AssemblyName = "SomeAssembly", - Attributes = new[] - { - new TagHelperAttributeDescriptor - { - Name = "bound-required-int", - PropertyName = "BoundRequiredInt", - TypeName = typeof(int).FullName - } - }, - RequiredAttributes = new[] - { - new TagHelperRequiredAttributeDescriptor { Name = "bound-required-int" } - } - }, - new TagHelperDescriptor - { - TagName = "input", - TypeName = "InputTagHelper3", - AssemblyName = "SomeAssembly", - Attributes = new[] - { - new TagHelperAttributeDescriptor - { - Name = "int-dictionary", - PropertyName ="DictionaryOfIntProperty", - TypeName = typeof(IDictionary).FullName - }, - new TagHelperAttributeDescriptor - { - Name = "string-dictionary", - PropertyName = "DictionaryOfStringProperty", - TypeName = typeof(IDictionary).FullName - }, - new TagHelperAttributeDescriptor - { - Name = "int-prefix-", - PropertyName = "DictionaryOfIntProperty", - TypeName = typeof(int).FullName, - IsIndexer = true - }, - new TagHelperAttributeDescriptor - { - Name = "string-prefix-", - PropertyName = "DictionaryOfStringProperty", - TypeName = typeof(string).FullName, - IsIndexer = true, - IsStringProperty = true - } - } - }, - new TagHelperDescriptor - { - TagName = "p", - TypeName = "PTagHelper", - AssemblyName = "SomeAssembly", - Attributes = new[] - { - new TagHelperAttributeDescriptor - { - Name = "bound-string", - PropertyName = "BoundRequiredString", - TypeName = typeof(string).FullName, - IsStringProperty = true - }, - new TagHelperAttributeDescriptor - { - Name = "bound-int", - PropertyName = "BoundRequiredString", - TypeName = typeof(int).FullName - } - } - } - }; - var descriptorProvider = new TagHelperDescriptorProvider(descriptors); + { + ITagHelperDescriptorBuilder.Create("InputTagHelper1", "SomeAssembly") + .TagMatchingRule(rule => + rule + .RequireTagName("input") + .RequireAttribute(attribute => attribute.Name("unbound-required"))) + .TagMatchingRule(rule => + rule + .RequireTagName("input") + .RequireAttribute(attribute => attribute.Name("bound-required-string"))) + .BindAttribute(attribute => + attribute + .Name("bound-required-string") + .PropertyName("BoundRequiredString") + .TypeName(typeof(string).FullName)) + .Build(), + ITagHelperDescriptorBuilder.Create("InputTagHelper2", "SomeAssembly") + .TagMatchingRule(rule => + rule + .RequireTagName("input") + .RequireAttribute(attribute => attribute.Name("bound-required-int"))) + .BindAttribute(attribute => + attribute + .Name("bound-required-int") + .PropertyName("BoundRequiredInt") + .TypeName(typeof(int).FullName)) + .Build(), + ITagHelperDescriptorBuilder.Create("InputTagHelper3", "SomeAssembly") + .TagMatchingRule(rule => rule.RequireTagName("input")) + .BindAttribute(attribute => + attribute + .Name("int-dictionary") + .PropertyName("DictionaryOfIntProperty") + .TypeName(typeof(IDictionary).Namespace + ".IDictionary") + .AsDictionary("int-prefix-", typeof(int).FullName)) + .BindAttribute(attribute => + attribute + .Name("string-dictionary") + .PropertyName("DictionaryOfStringProperty") + .TypeName(typeof(IDictionary).Namespace + ".IDictionary") + .AsDictionary("string-prefix-", typeof(string).FullName)) + .Build(), + ITagHelperDescriptorBuilder.Create("PTagHelper", "SomeAssembly") + .TagMatchingRule(rule => rule.RequireTagName("p")) + .BindAttribute(attribute => + attribute + .Name("bound-string") + .PropertyName("BoundRequiredString") + .TypeName(typeof(string).FullName)) + .BindAttribute(attribute => + attribute + .Name("bound-int") + .PropertyName("BoundRequiredString") + .TypeName(typeof(int).FullName)) + .Build(), + }; + var descriptorProvider = new TagHelperDescriptorProvider(null, descriptors); // Act & Assert EvaluateData(descriptorProvider, documentContent, (MarkupBlock)expectedOutput, (RazorError[])expectedErrors); diff --git a/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/TagHelperDescriptorProviderTest.cs b/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/TagHelperDescriptorProviderTest.cs index bd0ac4c64c..840c7d02f0 100644 --- a/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/TagHelperDescriptorProviderTest.cs +++ b/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/TagHelperDescriptorProviderTest.cs @@ -14,27 +14,22 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy { get { - var strongPParent = new TagHelperDescriptor - { - TagName = "strong", - TypeName = "StrongTagHelper", - AssemblyName = "SomeAssembly", - RequiredParent = "p", - }; - var strongDivParent = new TagHelperDescriptor - { - TagName = "strong", - TypeName = "StrongTagHelper", - AssemblyName = "SomeAssembly", - RequiredParent = "div", - }; - var catchAllPParent = new TagHelperDescriptor - { - TagName = "*", - TypeName = "CatchAllTagHelper", - AssemblyName = "SomeAssembly", - RequiredParent = "p", - }; + var strongPDivParent = ITagHelperDescriptorBuilder.Create("StrongTagHelper", "SomeAssembly") + .TagMatchingRule(rule => + rule + .RequireTagName("strong") + .RequireParentTag("p")) + .TagMatchingRule(rule => + rule + .RequireTagName("strong") + .RequireParentTag("div")) + .Build(); + var catchAllPParent = ITagHelperDescriptorBuilder.Create("CatchAllTagHelper", "SomeAssembly") + .TagMatchingRule(rule => + rule + .RequireTagName("*") + .RequireParentTag("p")) + .Build(); return new TheoryData< string, // tagName @@ -45,25 +40,25 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy { "strong", "p", - new[] { strongPParent, strongDivParent }, - new[] { strongPParent } + new[] { strongPDivParent }, + new[] { strongPDivParent } }, { "strong", "div", - new[] { strongPParent, strongDivParent, catchAllPParent }, - new[] { strongDivParent } + new[] { strongPDivParent, catchAllPParent }, + new[] { strongPDivParent } }, { "strong", "p", - new[] { strongPParent, strongDivParent, catchAllPParent }, - new[] { strongPParent, catchAllPParent } + new[] { strongPDivParent, catchAllPParent }, + new[] { strongPDivParent, catchAllPParent } }, { "custom", "p", - new[] { strongPParent, strongDivParent, catchAllPParent }, + new[] { strongPDivParent, catchAllPParent }, new[] { catchAllPParent } }, }; @@ -72,93 +67,73 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy [Theory] [MemberData(nameof(RequiredParentData))] - public void GetDescriptors_ReturnsDescriptorsParentTags( + public void GetTagHelperBinding_ReturnsBindingResultWithDescriptorsParentTags( string tagName, string parentTagName, object availableDescriptors, object expectedDescriptors) { // Arrange - var provider = new TagHelperDescriptorProvider((IEnumerable)availableDescriptors); + var provider = new TagHelperDescriptorProvider(null, (IEnumerable)availableDescriptors); // Act - var resolvedDescriptors = provider.GetDescriptors( + var bindingResult = provider.GetTagHelperBinding( tagName, attributes: Enumerable.Empty>(), parentTagName: parentTagName); // Assert - Assert.Equal((IEnumerable)expectedDescriptors, resolvedDescriptors, CaseSensitiveTagHelperDescriptorComparer.Default); + Assert.Equal((IEnumerable)expectedDescriptors, bindingResult.Descriptors, TagHelperDescriptorComparer.CaseSensitive); } public static TheoryData RequiredAttributeData { get { - var divDescriptor = new TagHelperDescriptor - { - TagName = "div", - TypeName = "DivTagHelper", - AssemblyName = "SomeAssembly", - RequiredAttributes = new[] { new TagHelperRequiredAttributeDescriptor { Name = "style" } } - }; - var inputDescriptor = new TagHelperDescriptor - { - TagName = "input", - TypeName = "InputTagHelper", - AssemblyName = "SomeAssembly", - RequiredAttributes = new[] - { - new TagHelperRequiredAttributeDescriptor { Name = "class" }, - new TagHelperRequiredAttributeDescriptor { Name = "style" } - } - }; - var inputWildcardPrefixDescriptor = new TagHelperDescriptor - { - TagName = "input", - TypeName = "InputWildCardAttribute", - AssemblyName = "SomeAssembly", - RequiredAttributes = new[] - { - new TagHelperRequiredAttributeDescriptor - { - Name = "nodashprefix", - NameComparison = TagHelperRequiredAttributeNameComparison.PrefixMatch, - } - } - }; - var catchAllDescriptor = new TagHelperDescriptor - { - TagName = TagHelperDescriptorProvider.ElementCatchAllTarget, - TypeName = "CatchAllTagHelper", - AssemblyName = "SomeAssembly", - RequiredAttributes = new[] { new TagHelperRequiredAttributeDescriptor { Name = "class" } } - }; - var catchAllDescriptor2 = new TagHelperDescriptor - { - TagName = TagHelperDescriptorProvider.ElementCatchAllTarget, - TypeName = "CatchAllTagHelper2", - AssemblyName = "SomeAssembly", - RequiredAttributes = new[] - { - new TagHelperRequiredAttributeDescriptor { Name = "custom" }, - new TagHelperRequiredAttributeDescriptor { Name = "class" } - } - }; - var catchAllWildcardPrefixDescriptor = new TagHelperDescriptor - { - TagName = TagHelperDescriptorProvider.ElementCatchAllTarget, - TypeName = "CatchAllWildCardAttribute", - AssemblyName = "SomeAssembly", - RequiredAttributes = new[] - { - new TagHelperRequiredAttributeDescriptor - { - Name = "prefix-", - NameComparison = TagHelperRequiredAttributeNameComparison.PrefixMatch, - } - } - }; + var divDescriptor = ITagHelperDescriptorBuilder.Create("DivTagHelper", "SomeAssembly") + .TagMatchingRule(rule => + rule + .RequireTagName("div") + .RequireAttribute(attribute => attribute.Name("style"))) + .Build(); + var inputDescriptor = ITagHelperDescriptorBuilder.Create("InputTagHelper", "SomeAssembly") + .TagMatchingRule(rule => + rule + .RequireTagName("input") + .RequireAttribute(attribute => attribute.Name("class")) + .RequireAttribute(attribute => attribute.Name("style"))) + .Build(); + var inputWildcardPrefixDescriptor = ITagHelperDescriptorBuilder.Create("InputWildCardAttribute", "SomeAssembly") + .TagMatchingRule(rule => + rule + .RequireTagName("input") + .RequireAttribute(attribute => + attribute + .Name("nodashprefix") + .NameComparisonMode(RequiredAttributeDescriptor.NameComparisonMode.PrefixMatch))) + .Build(); + var catchAllDescriptor = ITagHelperDescriptorBuilder.Create("CatchAllTagHelper", "SomeAssembly") + .TagMatchingRule(rule => + rule + .RequireTagName(TagHelperDescriptorProvider.ElementCatchAllTarget) + .RequireAttribute(attribute => attribute.Name("class"))) + .Build(); + var catchAllDescriptor2 = ITagHelperDescriptorBuilder.Create("CatchAllTagHelper2", "SomeAssembly") + .TagMatchingRule(rule => + rule + .RequireTagName(TagHelperDescriptorProvider.ElementCatchAllTarget) + .RequireAttribute(attribute => attribute.Name("custom")) + .RequireAttribute(attribute => attribute.Name("class"))) + .Build(); + var catchAllWildcardPrefixDescriptor = ITagHelperDescriptorBuilder.Create("CatchAllWildCardAttribute", "SomeAssembly") + .TagMatchingRule(rule => + rule + .RequireTagName(TagHelperDescriptorProvider.ElementCatchAllTarget) + .RequireAttribute(attribute => + attribute + .Name("prefix-") + .NameComparisonMode(RequiredAttributeDescriptor.NameComparisonMode.PrefixMatch))) + .Build(); var defaultAvailableDescriptors = new[] { divDescriptor, inputDescriptor, catchAllDescriptor, catchAllDescriptor2 }; var defaultWildcardDescriptors = @@ -176,7 +151,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy "div", new[] { kvp("custom") }, defaultAvailableDescriptors, - Enumerable.Empty() + null }, { "div", new[] { kvp("style") }, defaultAvailableDescriptors, new[] { divDescriptor } }, { "div", new[] { kvp("class") }, defaultAvailableDescriptors, new[] { catchAllDescriptor } }, @@ -214,19 +189,19 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy "input", new[] { kvp("prefixABCnodashprefix") }, defaultWildcardDescriptors, - Enumerable.Empty() + null }, { "input", new[] { kvp("prefix-") }, defaultWildcardDescriptors, - Enumerable.Empty() + null }, { "input", new[] { kvp("nodashprefix") }, defaultWildcardDescriptors, - Enumerable.Empty() + null }, { "input", @@ -258,292 +233,222 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy [Theory] [MemberData(nameof(RequiredAttributeData))] - public void GetDescriptors_ReturnsDescriptorsWithRequiredAttributes( + public void GetTagHelperBinding_ReturnsBindingResultDescriptorsWithRequiredAttributes( string tagName, IEnumerable> providedAttributes, object availableDescriptors, object expectedDescriptors) { // Arrange - var provider = new TagHelperDescriptorProvider((IEnumerable)availableDescriptors); + var provider = new TagHelperDescriptorProvider(null, (IEnumerable)availableDescriptors); // Act - var resolvedDescriptors = provider.GetDescriptors(tagName, providedAttributes, parentTagName: "p").ToArray(); + var bindingResult = provider.GetTagHelperBinding(tagName, providedAttributes, parentTagName: "p"); // Assert - Assert.Equal((IEnumerable)expectedDescriptors, resolvedDescriptors, CaseSensitiveTagHelperDescriptorComparer.Default); + Assert.Equal((IEnumerable)expectedDescriptors, bindingResult?.Descriptors, TagHelperDescriptorComparer.CaseSensitive); } [Fact] - public void GetDescriptors_ReturnsEmptyDescriptorsWithPrefixAsTagName() + public void GetTagHelperBinding_ReturnsNullBindingResultPrefixAsTagName() { // Arrange - var catchAllDescriptor = CreatePrefixedDescriptor( - "th", - TagHelperDescriptorProvider.ElementCatchAllTarget, - "foo1"); + var catchAllDescriptor = ITagHelperDescriptorBuilder.Create("foo1", "SomeAssembly") + .TagMatchingRule(rule => rule.RequireTagName(TagHelperDescriptorProvider.ElementCatchAllTarget)) + .Build(); var descriptors = new[] { catchAllDescriptor }; - var provider = new TagHelperDescriptorProvider(descriptors); + var provider = new TagHelperDescriptorProvider("th", descriptors); // Act - var resolvedDescriptors = provider.GetDescriptors( + var bindingResult = provider.GetTagHelperBinding( tagName: "th", attributes: Enumerable.Empty>(), parentTagName: "p"); // Assert - Assert.Empty(resolvedDescriptors); + Assert.Null(bindingResult); } [Fact] - public void GetDescriptors_DeduplicatesTagHelpersByTypeName() + public void GetTagHelperBinding_ReturnsBindingResultCatchAllDescriptorsForPrefixedTags() { // Arrange - var descriptors = new[] - { - new TagHelperDescriptor - { - AssemblyName = "TestAssembly", - TagName = "form", - TypeName = "TestFormTagHelper", - RequiredAttributes = new List() - { - new TagHelperRequiredAttributeDescriptor() - { - Name = "a", - NameComparison = TagHelperRequiredAttributeNameComparison.FullMatch - } - }, - }, - new TagHelperDescriptor - { - AssemblyName = "TestAssembly", - TagName = "form", - TypeName = "TestFormTagHelper", - RequiredAttributes = new List() - { - new TagHelperRequiredAttributeDescriptor() - { - Name = "b", - NameComparison = TagHelperRequiredAttributeNameComparison.FullMatch - } - }, - }, - }; - var provider = new TagHelperDescriptorProvider(descriptors); - - // Act - var resolvedDescriptors = provider.GetDescriptors( - tagName: "form", - attributes: new List>() - { - new KeyValuePair("a", "hi" ), - new KeyValuePair("b", "there"), - }, - parentTagName: "p"); - - // Assert - Assert.Same(descriptors[0], Assert.Single(resolvedDescriptors)); - } - - [Fact] - public void GetDescriptors_OnlyUnderstandsSinglePrefix() - { - // Arrange - var divDescriptor = CreatePrefixedDescriptor("th:", "div", "foo1"); - var spanDescriptor = CreatePrefixedDescriptor("th2:", "span", "foo2"); - var descriptors = new[] { divDescriptor, spanDescriptor }; - var provider = new TagHelperDescriptorProvider(descriptors); - - // Act - var retrievedDescriptorsDiv = provider.GetDescriptors( - tagName: "th:div", - attributes: Enumerable.Empty>(), - parentTagName: "p"); - var retrievedDescriptorsSpan = provider.GetDescriptors( - tagName: "th2:span", - attributes: Enumerable.Empty>(), - parentTagName: "p"); - - // Assert - var descriptor = Assert.Single(retrievedDescriptorsDiv); - Assert.Same(divDescriptor, descriptor); - Assert.Empty(retrievedDescriptorsSpan); - } - - [Fact] - public void GetDescriptors_ReturnsCatchAllDescriptorsForPrefixedTags() - { - // Arrange - var catchAllDescriptor = CreatePrefixedDescriptor("th:", TagHelperDescriptorProvider.ElementCatchAllTarget, "foo1"); + var catchAllDescriptor = ITagHelperDescriptorBuilder.Create("foo1", "SomeAssembly") + .TagMatchingRule(rule => rule.RequireTagName(TagHelperDescriptorProvider.ElementCatchAllTarget)) + .Build(); var descriptors = new[] { catchAllDescriptor }; - var provider = new TagHelperDescriptorProvider(descriptors); + var provider = new TagHelperDescriptorProvider("th:", descriptors); // Act - var retrievedDescriptorsDiv = provider.GetDescriptors( + var bindingResultDiv = provider.GetTagHelperBinding( tagName: "th:div", attributes: Enumerable.Empty>(), parentTagName: "p"); - var retrievedDescriptorsSpan = provider.GetDescriptors( + var bindingResultSpan = provider.GetTagHelperBinding( tagName: "th:span", attributes: Enumerable.Empty>(), parentTagName: "p"); // Assert - var descriptor = Assert.Single(retrievedDescriptorsDiv); + var descriptor = Assert.Single(bindingResultDiv.Descriptors); Assert.Same(catchAllDescriptor, descriptor); - descriptor = Assert.Single(retrievedDescriptorsSpan); + descriptor = Assert.Single(bindingResultSpan.Descriptors); Assert.Same(catchAllDescriptor, descriptor); } [Fact] - public void GetDescriptors_ReturnsDescriptorsForPrefixedTags() + public void GetTagHelperBinding_ReturnsBindingResultDescriptorsForPrefixedTags() { // Arrange - var divDescriptor = CreatePrefixedDescriptor("th:", "div", "foo1"); + var divDescriptor = ITagHelperDescriptorBuilder.Create("foo1", "SomeAssembly") + .TagMatchingRule(rule => rule.RequireTagName("div")) + .Build(); var descriptors = new[] { divDescriptor }; - var provider = new TagHelperDescriptorProvider(descriptors); + var provider = new TagHelperDescriptorProvider("th:", descriptors); // Act - var retrievedDescriptors = provider.GetDescriptors( + var bindingResult = provider.GetTagHelperBinding( tagName: "th:div", attributes: Enumerable.Empty>(), parentTagName: "p"); // Assert - var descriptor = Assert.Single(retrievedDescriptors); + var descriptor = Assert.Single(bindingResult.Descriptors); Assert.Same(divDescriptor, descriptor); } [Theory] [InlineData("*")] [InlineData("div")] - public void GetDescriptors_ReturnsNothingForUnprefixedTags(string tagName) + public void GetTagHelperBinding_ReturnsNullForUnprefixedTags(string tagName) { // Arrange - var divDescriptor = CreatePrefixedDescriptor("th:", tagName, "foo1"); + var divDescriptor = ITagHelperDescriptorBuilder.Create("foo1", "SomeAssembly") + .TagMatchingRule(rule => rule.RequireTagName(tagName)) + .Build(); var descriptors = new[] { divDescriptor }; - var provider = new TagHelperDescriptorProvider(descriptors); + var provider = new TagHelperDescriptorProvider("th:", descriptors); // Act - var retrievedDescriptorsDiv = provider.GetDescriptors( + var bindingResult = provider.GetTagHelperBinding( tagName: "div", attributes: Enumerable.Empty>(), parentTagName: "p"); // Assert - Assert.Empty(retrievedDescriptorsDiv); + Assert.Null(bindingResult); } [Fact] public void GetDescriptors_ReturnsNothingForUnregisteredTags() { // Arrange - var divDescriptor = new TagHelperDescriptor - { - TagName = "div", - TypeName = "foo1", - AssemblyName = "SomeAssembly", - }; - var spanDescriptor = new TagHelperDescriptor - { - TagName = "span", - TypeName = "foo2", - AssemblyName = "SomeAssembly", - }; + var divDescriptor = ITagHelperDescriptorBuilder.Create("foo1", "SomeAssembly") + .TagMatchingRule(rule => rule.RequireTagName("div")) + .Build(); + var spanDescriptor = ITagHelperDescriptorBuilder.Create("foo2", "SomeAssembly") + .TagMatchingRule(rule => rule.RequireTagName("span")) + .Build(); var descriptors = new TagHelperDescriptor[] { divDescriptor, spanDescriptor }; - var provider = new TagHelperDescriptorProvider(descriptors); + var provider = new TagHelperDescriptorProvider(null, descriptors); // Act - var retrievedDescriptors = provider.GetDescriptors( + var tagHelperBinding = provider.GetTagHelperBinding( tagName: "foo", attributes: Enumerable.Empty>(), parentTagName: "p"); // Assert - Assert.Empty(retrievedDescriptors); + Assert.Null(tagHelperBinding); } [Fact] public void GetDescriptors_ReturnsCatchAllsWithEveryTagName() { // Arrange - var divDescriptor = new TagHelperDescriptor - { - TagName = "div", - TypeName = "foo1", - AssemblyName = "SomeAssembly", - }; - var spanDescriptor = new TagHelperDescriptor - { - TagName = "span", - TypeName = "foo2", - AssemblyName = "SomeAssembly", - }; - var catchAllDescriptor = new TagHelperDescriptor - { - TagName = TagHelperDescriptorProvider.ElementCatchAllTarget, - TypeName = "foo3", - AssemblyName = "SomeAssembly", - }; + var divDescriptor = ITagHelperDescriptorBuilder.Create("foo1", "SomeAssembly") + .TagMatchingRule(rule => rule.RequireTagName("div")) + .Build(); + var spanDescriptor = ITagHelperDescriptorBuilder.Create("foo2", "SomeAssembly") + .TagMatchingRule(rule => rule.RequireTagName("span")) + .Build(); + var catchAllDescriptor = ITagHelperDescriptorBuilder.Create("foo3", "SomeAssembly") + .TagMatchingRule(rule => rule.RequireTagName(TagHelperDescriptorProvider.ElementCatchAllTarget)) + .Build(); var descriptors = new TagHelperDescriptor[] { divDescriptor, spanDescriptor, catchAllDescriptor }; - var provider = new TagHelperDescriptorProvider(descriptors); + var provider = new TagHelperDescriptorProvider(null, descriptors); // Act - var divDescriptors = provider.GetDescriptors( + var divBinding = provider.GetTagHelperBinding( tagName: "div", attributes: Enumerable.Empty>(), parentTagName: "p"); - var spanDescriptors = provider.GetDescriptors( + var spanBinding = provider.GetTagHelperBinding( tagName: "span", attributes: Enumerable.Empty>(), parentTagName: "p"); // Assert // For divs - Assert.Equal(2, divDescriptors.Count()); - Assert.Contains(divDescriptor, divDescriptors); - Assert.Contains(catchAllDescriptor, divDescriptors); + Assert.Equal(2, divBinding.Descriptors.Count()); + Assert.Contains(divDescriptor, divBinding.Descriptors); + Assert.Contains(catchAllDescriptor, divBinding.Descriptors); // For spans - Assert.Equal(2, spanDescriptors.Count()); - Assert.Contains(spanDescriptor, spanDescriptors); - Assert.Contains(catchAllDescriptor, spanDescriptors); + Assert.Equal(2, spanBinding.Descriptors.Count()); + Assert.Contains(spanDescriptor, spanBinding.Descriptors); + Assert.Contains(catchAllDescriptor, spanBinding.Descriptors); } [Fact] public void GetDescriptors_DuplicateDescriptorsAreNotPartOfTagHelperDescriptorPool() { // Arrange - var divDescriptor = new TagHelperDescriptor - { - TagName = "div", - TypeName = "foo1", - AssemblyName = "SomeAssembly", - }; + var divDescriptor = ITagHelperDescriptorBuilder.Create("foo1", "SomeAssembly") + .TagMatchingRule(rule => rule.RequireTagName("div")) + .Build(); var descriptors = new TagHelperDescriptor[] { divDescriptor, divDescriptor }; - var provider = new TagHelperDescriptorProvider(descriptors); + var provider = new TagHelperDescriptorProvider(null, descriptors); // Act - var retrievedDescriptors = provider.GetDescriptors( + var bindingResult = provider.GetTagHelperBinding( tagName: "div", attributes: Enumerable.Empty>(), parentTagName: "p"); // Assert - var descriptor = Assert.Single(retrievedDescriptors); + var descriptor = Assert.Single(bindingResult.Descriptors); Assert.Same(divDescriptor, descriptor); } - private static TagHelperDescriptor CreatePrefixedDescriptor(string prefix, string tagName, string typeName) + [Fact] + public void GetTagHelperBinding_DescriptorWithMultipleRules_CorrectlySelectsMatchingRules() { - return new TagHelperDescriptor - { - Prefix = prefix, - TagName = tagName, - TypeName = typeName, - AssemblyName = "SomeAssembly" - }; + // Arrange + var multiRuleDescriptor = ITagHelperDescriptorBuilder.Create("foo", "SomeAssembly") + .TagMatchingRule(rule => rule + .RequireTagName(TagHelperDescriptorProvider.ElementCatchAllTarget) + .RequireParentTag("body")) + .TagMatchingRule(rule => rule + .RequireTagName("div")) + .TagMatchingRule(rule => rule + .RequireTagName("span")) + .Build(); + var descriptors = new TagHelperDescriptor[] { multiRuleDescriptor }; + var provider = new TagHelperDescriptorProvider(null, descriptors); + + // Act + var binding = provider.GetTagHelperBinding( + tagName: "div", + attributes: Enumerable.Empty>(), + parentTagName: "p"); + + // Assert + var boundDescriptor = Assert.Single(binding.Descriptors); + Assert.Same(multiRuleDescriptor, boundDescriptor); + var boundRules = binding.GetBoundRules(boundDescriptor); + var boundRule = Assert.Single(boundRules); + Assert.Equal("div", boundRule.TagName); } } } \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/TagHelperDesignTimeDescriptorComparer.cs b/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/TagHelperDesignTimeDescriptorComparer.cs deleted file mode 100644 index 6d45cb8b50..0000000000 --- a/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/TagHelperDesignTimeDescriptorComparer.cs +++ /dev/null @@ -1,47 +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; -using System.Collections.Generic; -using Microsoft.Extensions.Internal; -using Xunit; - -namespace Microsoft.AspNetCore.Razor.Evolution.Legacy -{ - internal class TagHelperDesignTimeDescriptorComparer : IEqualityComparer - { - public static readonly TagHelperDesignTimeDescriptorComparer Default = - new TagHelperDesignTimeDescriptorComparer(); - - private TagHelperDesignTimeDescriptorComparer() - { - } - - public bool Equals(TagHelperDesignTimeDescriptor descriptorX, TagHelperDesignTimeDescriptor descriptorY) - { - if (descriptorX == descriptorY) - { - return true; - } - - Assert.NotNull(descriptorX); - Assert.NotNull(descriptorY); - Assert.Equal(descriptorX.Summary, descriptorY.Summary, StringComparer.Ordinal); - Assert.Equal(descriptorX.Remarks, descriptorY.Remarks, StringComparer.Ordinal); - Assert.Equal(descriptorX.OutputElementHint, descriptorY.OutputElementHint, StringComparer.Ordinal); - - return true; - } - - public int GetHashCode(TagHelperDesignTimeDescriptor descriptor) - { - var hashCodeCombiner = HashCodeCombiner.Start(); - - hashCodeCombiner.Add(descriptor.Summary, StringComparer.Ordinal); - hashCodeCombiner.Add(descriptor.Remarks, StringComparer.Ordinal); - hashCodeCombiner.Add(descriptor.OutputElementHint, StringComparer.Ordinal); - - return hashCodeCombiner; - } - } -} diff --git a/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/TagHelperParseTreeRewriterTest.cs b/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/TagHelperParseTreeRewriterTest.cs index baba797bfe..099e52f4a2 100644 --- a/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/TagHelperParseTreeRewriterTest.cs +++ b/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/TagHelperParseTreeRewriterTest.cs @@ -52,7 +52,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy var errorSink = new ErrorSink(); var parseResult = ParseDocument(documentContent); var document = parseResult.Root; - var parseTreeRewriter = new TagHelperParseTreeRewriter(provider: null); + var parseTreeRewriter = new TagHelperParseTreeRewriter(null, provider: null); // Assert - Guard var rootBlock = Assert.IsType(document); @@ -108,8 +108,8 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy new MarkupBlock( new MarkupTagHelperBlock("p", new MarkupTagHelperBlock("strong")), - blockFactory.MarkupTagBlock("")), - new[] { errorFormatUnclosed(4, "strong") } + new MarkupTagHelperBlock("strong")), + new[] { errorFormatUnclosed(4, "strong"), errorFormatUnclosed(16, "strong") } }, { "<

<

", @@ -160,35 +160,18 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy // Arrange var descriptors = new TagHelperDescriptor[] { - new TagHelperDescriptor - { - TagName = "strong", - TypeName = "StrongTagHelper", - AssemblyName = "SomeAssembly", - RequiredParent = "p", - }, - new TagHelperDescriptor - { - TagName = "strong", - TypeName = "StrongTagHelper", - AssemblyName = "SomeAssembly", - RequiredParent = "div", - }, - new TagHelperDescriptor - { - TagName = "*", - TypeName = "CatchALlTagHelper", - AssemblyName = "SomeAssembly", - RequiredParent = "p", - }, - new TagHelperDescriptor - { - TagName = "p", - TypeName = "PTagHelper", - AssemblyName = "SomeAssembly" - } + ITagHelperDescriptorBuilder.Create("StrongTagHelper", "SomeAssembly") + .TagMatchingRule(rule => rule.RequireTagName("strong")) + .TagMatchingRule(rule => rule.RequireTagName("div")) + .Build(), + ITagHelperDescriptorBuilder.Create("CatchALlTagHelper", "SomeAssembly") + .TagMatchingRule(rule => rule.RequireTagName("*")) + .Build(), + ITagHelperDescriptorBuilder.Create("PTagHelper", "SomeAssembly") + .TagMatchingRule(rule => rule.RequireTagName("p")) + .Build(), }; - var descriptorProvider = new TagHelperDescriptorProvider(descriptors); + var descriptorProvider = new TagHelperDescriptorProvider(null, descriptors); // Act & Assert EvaluateData(descriptorProvider, documentContent, (MarkupBlock)expectedOutput, (RazorError[])expectedErrors); @@ -275,35 +258,27 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy // Arrange var descriptors = new TagHelperDescriptor[] { - new TagHelperDescriptor - { - TagName = "input", - TypeName = "InputTagHelper", - AssemblyName = "SomeAssembly", - TagStructure = TagStructure.WithoutEndTag, - }, - new TagHelperDescriptor - { - TagName = "strong", - TypeName = "StrongTagHelper", - AssemblyName = "SomeAssembly", - RequiredParent = "p", - }, - new TagHelperDescriptor - { - TagName = "strong", - TypeName = "StrongTagHelper", - AssemblyName = "SomeAssembly", - RequiredParent = "input", - }, - new TagHelperDescriptor - { - TagName = "p", - TypeName = "PTagHelper", - AssemblyName = "SomeAssembly" - } + ITagHelperDescriptorBuilder.Create("InputTagHelper", "SomeAssembly") + .TagMatchingRule(rule => + rule + .RequireTagName("input") + .RequireTagStructure(TagStructure.WithoutEndTag)) + .Build(), + ITagHelperDescriptorBuilder.Create("StrongTagHelper", "SomeAssembly") + .TagMatchingRule(rule => + rule + .RequireTagName("strong") + .RequireParentTag("p")) + .TagMatchingRule(rule => + rule + .RequireTagName("strong") + .RequireParentTag("input")) + .Build(), + ITagHelperDescriptorBuilder.Create("PTagHelper", "SomeAssembly") + .TagMatchingRule(rule => rule.RequireTagName("p")) + .Build(), }; - var descriptorProvider = new TagHelperDescriptorProvider(descriptors); + var descriptorProvider = new TagHelperDescriptorProvider(null, descriptors); // Act & Assert EvaluateData(descriptorProvider, documentContent, (MarkupBlock)expectedOutput, expectedErrors: new RazorError[0]); @@ -365,28 +340,21 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy // Arrange var descriptors = new TagHelperDescriptor[] { - new TagHelperDescriptor - { - TagName = "strong", - TypeName = "StrongTagHelper", - AssemblyName = "SomeAssembly", - RequiredParent = "p", - }, - new TagHelperDescriptor - { - TagName = "strong", - TypeName = "StrongTagHelper", - AssemblyName = "SomeAssembly", - RequiredParent = "div", - }, - new TagHelperDescriptor - { - TagName = "p", - TypeName = "PTagHelper", - AssemblyName = "SomeAssembly" - } + ITagHelperDescriptorBuilder.Create("StrongTagHelper", "SomeAssembly") + .TagMatchingRule(rule => + rule + .RequireTagName("strong") + .RequireParentTag("p")) + .TagMatchingRule(rule => + rule + .RequireTagName("strong") + .RequireParentTag("div")) + .Build(), + ITagHelperDescriptorBuilder.Create("PTagHelper", "SomeAssembly") + .TagMatchingRule(rule => rule.RequireTagName("p")) + .Build(), }; - var descriptorProvider = new TagHelperDescriptorProvider(descriptors); + var descriptorProvider = new TagHelperDescriptorProvider(null, descriptors); // Act & Assert EvaluateData(descriptorProvider, documentContent, (MarkupBlock)expectedOutput, expectedErrors: new RazorError[0]); @@ -401,31 +369,24 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy new MarkupTagHelperBlock("th:p", new MarkupTagHelperBlock("th:strong"))); var descriptors = new TagHelperDescriptor[] - { - new TagHelperDescriptor - { - TagName = "p", - TypeName = "PTagHelper", - AssemblyName = "SomeAssembly", - AllowedChildren = new[] { "strong" }, - Prefix = "th:" - }, - new TagHelperDescriptor - { - TagName = "strong", - TypeName = "StrongTagHelper", - AssemblyName = "SomeAssembly", - Prefix = "th:" - } - }; - var descriptorProvider = new TagHelperDescriptorProvider(descriptors); + { + ITagHelperDescriptorBuilder.Create("PTagHelper", "SomeAssembly") + .TagMatchingRule(rule => rule.RequireTagName("p")) + .AllowChildTag("strong") + .Build(), + ITagHelperDescriptorBuilder.Create("StrongTagHelper", "SomeAssembly") + .TagMatchingRule(rule => rule.RequireTagName("strong")) + .Build(), + }; + var descriptorProvider = new TagHelperDescriptorProvider("th:", descriptors); // Act & Assert EvaluateData( descriptorProvider, documentContent, expectedOutput, - expectedErrors: Enumerable.Empty()); + expectedErrors: Enumerable.Empty(), + tagHelperPrefix: "th:"); } public static TheoryData InvalidHtmlScriptBlockData @@ -720,16 +681,13 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy blockFactory.MarkupTagBlock(""), factory.Markup(Environment.NewLine))); var descriptors = new TagHelperDescriptor[] - { - new TagHelperDescriptor - { - TagName = "p", - TypeName = "PTagHelper", - AssemblyName = "SomeAssembly", - AllowedChildren = new[] { "br" }, - } - }; - var descriptorProvider = new TagHelperDescriptorProvider(descriptors); + { + ITagHelperDescriptorBuilder.Create("PTagHelper", "SomeAssembly") + .TagMatchingRule(rule => rule.RequireTagName("p")) + .AllowChildTag("br") + .Build() + }; + var descriptorProvider = new TagHelperDescriptorProvider(null, descriptors); // Act & Assert EvaluateData(descriptorProvider, documentContent, expectedOutput, expectedErrors); @@ -760,17 +718,16 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy blockFactory.MarkupTagBlock(""), blockFactory.MarkupTagBlock(""))); var descriptors = new TagHelperDescriptor[] - { - new TagHelperDescriptor - { - TagName = "strong", - TypeName = "StrongTagHelper", - AssemblyName = "SomeAssembly", - RequiredAttributes = new[] { new TagHelperRequiredAttributeDescriptor { Name = "required" } }, - AllowedChildren = new[] { "br" } - } - }; - var descriptorProvider = new TagHelperDescriptorProvider(descriptors); + { + ITagHelperDescriptorBuilder.Create("StrongTagHelper", "SomeAssembly") + .TagMatchingRule(rule => + rule + .RequireTagName("strong") + .RequireAttribute(attribute => attribute.Name("required"))) + .AllowChildTag("br") + .Build() + }; + var descriptorProvider = new TagHelperDescriptorProvider(null, descriptors); // Act & Assert EvaluateData(descriptorProvider, documentContent, expectedOutput, expectedErrors); @@ -788,35 +745,26 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy factory.Markup("Hello World")), new MarkupTagHelperBlock("br", TagMode.StartTagOnly))); var descriptors = new TagHelperDescriptor[] - { - new TagHelperDescriptor - { - TagName = "p", - TypeName = "PTagHelper1", - AssemblyName = "SomeAssembly", - AllowedChildren = new[] { "strong", "br" } - }, - new TagHelperDescriptor - { - TagName = "p", - TypeName = "PTagHelper2", - AssemblyName = "SomeAssembly" - }, - new TagHelperDescriptor - { - TagName = "strong", - TypeName = "StrongTagHelper", - AssemblyName = "SomeAssembly" - }, - new TagHelperDescriptor - { - TagName = "br", - TypeName = "BRTagHelper", - AssemblyName = "SomeAssembly", - TagStructure = TagStructure.WithoutEndTag - } - }; - var descriptorProvider = new TagHelperDescriptorProvider(descriptors); + { + ITagHelperDescriptorBuilder.Create("PTagHelper1", "SomeAssembly") + .TagMatchingRule(rule => rule.RequireTagName("p")) + .AllowChildTag("strong") + .AllowChildTag("br") + .Build(), + ITagHelperDescriptorBuilder.Create("PTagHelper2", "SomeAssembly") + .TagMatchingRule(rule => rule.RequireTagName("p")) + .Build(), + ITagHelperDescriptorBuilder.Create("StrongTagHelper", "SomeAssembly") + .TagMatchingRule(rule => rule.RequireTagName("strong")) + .Build(), + ITagHelperDescriptorBuilder.Create("BRTagHelper", "SomeAssembly") + .TagMatchingRule(rule => + rule + .RequireTagName("br") + .RequireTagStructure(TagStructure.WithoutEndTag)) + .Build(), + }; + var descriptorProvider = new TagHelperDescriptorProvider(null, descriptors); // Act & Assert EvaluateData(descriptorProvider, documentContent, expectedOutput, expectedErrors: new RazorError[0]); @@ -834,36 +782,26 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy factory.Markup("Hello World")), new MarkupTagHelperBlock("br", TagMode.StartTagOnly))); var descriptors = new TagHelperDescriptor[] - { - new TagHelperDescriptor - { - TagName = "p", - TypeName = "PTagHelper1", - AssemblyName = "SomeAssembly", - AllowedChildren = new[] { "strong" } - }, - new TagHelperDescriptor - { - TagName = "p", - TypeName = "PTagHelper2", - AssemblyName = "SomeAssembly", - AllowedChildren = new[] { "br" } - }, - new TagHelperDescriptor - { - TagName = "strong", - TypeName = "StrongTagHelper", - AssemblyName = "SomeAssembly" - }, - new TagHelperDescriptor - { - TagName = "br", - TypeName = "BRTagHelper", - AssemblyName = "SomeAssembly", - TagStructure = TagStructure.WithoutEndTag - } - }; - var descriptorProvider = new TagHelperDescriptorProvider(descriptors); + { + ITagHelperDescriptorBuilder.Create("PTagHelper1", "SomeAssembly") + .TagMatchingRule(rule => rule.RequireTagName("p")) + .AllowChildTag("strong") + .Build(), + ITagHelperDescriptorBuilder.Create("PTagHelper2", "SomeAssembly") + .TagMatchingRule(rule => rule.RequireTagName("p")) + .AllowChildTag("br") + .Build(), + ITagHelperDescriptorBuilder.Create("StrongTagHelper", "SomeAssembly") + .TagMatchingRule(rule => rule.RequireTagName("strong")) + .Build(), + ITagHelperDescriptorBuilder.Create("BRTagHelper", "SomeAssembly") + .TagMatchingRule(rule => + rule + .RequireTagName("br") + .RequireTagStructure(TagStructure.WithoutEndTag)) + .Build(), + }; + var descriptorProvider = new TagHelperDescriptorProvider(null, descriptors); // Act & Assert EvaluateData(descriptorProvider, documentContent, expectedOutput, expectedErrors: new RazorError[0]); @@ -1083,31 +1021,28 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy object expectedErrors) { // Arrange + var pTagHelperBuilder = ITagHelperDescriptorBuilder.Create("PTagHelper", "SomeAssembly") + .TagMatchingRule(rule => rule.RequireTagName("p")); + var strongTagHelperBuilder = ITagHelperDescriptorBuilder.Create("StrongTagHelper", "SomeAssembly") + .TagMatchingRule(rule => rule.RequireTagName("strong")); + + foreach (var childTag in allowedChildren) + { + pTagHelperBuilder.AllowChildTag(childTag); + strongTagHelperBuilder.AllowChildTag(childTag); + } var descriptors = new TagHelperDescriptor[] - { - new TagHelperDescriptor - { - TagName = "p", - TypeName = "PTagHelper", - AssemblyName = "SomeAssembly", - AllowedChildren = allowedChildren - }, - new TagHelperDescriptor - { - TagName = "strong", - TypeName = "StrongTagHelper", - AssemblyName = "SomeAssembly", - AllowedChildren = allowedChildren - }, - new TagHelperDescriptor - { - TagName = "br", - TypeName = "BRTagHelper", - AssemblyName = "SomeAssembly", - TagStructure = TagStructure.WithoutEndTag - } - }; - var descriptorProvider = new TagHelperDescriptorProvider(descriptors); + { + pTagHelperBuilder.Build(), + strongTagHelperBuilder.Build(), + ITagHelperDescriptorBuilder.Create("BRTagHelper", "SomeAssembly") + .TagMatchingRule(rule => + rule + .RequireTagName("br") + .RequireTagStructure(TagStructure.WithoutEndTag)) + .Build(), + }; + var descriptorProvider = new TagHelperDescriptorProvider(null, descriptors); // Act & Assert EvaluateData(descriptorProvider, documentContent, (MarkupBlock)expectedOutput, (RazorError[])expectedErrors); @@ -1119,25 +1054,19 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy // Arrange var documentContent = "

"; var descriptors = new TagHelperDescriptor[] - { - new TagHelperDescriptor - { - TagName = "p", - TypeName = "PTagHelper", - AssemblyName = "SomeAssembly", - AllowedChildren = new[] { "custom" }, - }, - new TagHelperDescriptor - { - TagName = "*", - TypeName = "CatchAllTagHelper", - AssemblyName = "SomeAssembly", - } - }; + { + ITagHelperDescriptorBuilder.Create("PTagHelper", "SomeAssembly") + .TagMatchingRule(rule => rule.RequireTagName("p")) + .AllowChildTag("custom") + .Build(), + ITagHelperDescriptorBuilder.Create("CatchAllTagHelper", "SomeAssembly") + .TagMatchingRule(rule => rule.RequireTagName("*")) + .Build(), + }; var expectedOutput = new MarkupBlock( new MarkupTagHelperBlock("p", BlockFactory.MarkupTagBlock(""; var descriptors = new TagHelperDescriptor[] - { - new TagHelperDescriptor - { - TagName = "p", - TypeName = "PTagHelper", - AssemblyName = "SomeAssembly", - AllowedChildren = new[] { "custom" }, - Prefix = "th:", - }, - new TagHelperDescriptor - { - TagName = "*", - TypeName = "CatchAllTagHelper", - AssemblyName = "SomeAssembly", - Prefix = "th:", - } - }; + { + ITagHelperDescriptorBuilder.Create("PTagHelper", "SomeAssembly") + .TagMatchingRule(rule => rule.RequireTagName("p")) + .AllowChildTag("custom") + .Build(), + ITagHelperDescriptorBuilder.Create("CatchAllTagHelper", "SomeAssembly") + .TagMatchingRule(rule => rule.RequireTagName("*")) + .Build(), + }; var expectedOutput = new MarkupBlock( new MarkupTagHelperBlock("th:p", BlockFactory.MarkupTagBlock(""; var expectedOutput = new MarkupBlock(new MarkupTagHelperBlock("input", TagMode.StartTagOnly)); var descriptors = new TagHelperDescriptor[] - { - new TagHelperDescriptor - { - TagName = "input", - TypeName = "InputTagHelper", - AssemblyName = "SomeAssembly", - TagStructure = TagStructure.WithoutEndTag - } - }; - var descriptorProvider = new TagHelperDescriptorProvider(descriptors); + { + ITagHelperDescriptorBuilder.Create("InputTagHelper", "SomeAssembly") + .TagMatchingRule(rule => + rule + .RequireTagName("input") + .RequireTagStructure(TagStructure.WithoutEndTag)) + .Build() + }; + var descriptorProvider = new TagHelperDescriptorProvider(null, descriptors); // Act & Assert EvaluateData(descriptorProvider, documentContent, expectedOutput, expectedErrors: new RazorError[0]); @@ -1233,16 +1153,15 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy var documentContent = ""; var expectedOutput = new MarkupBlock(blockFactory.MarkupTagBlock("")); var descriptors = new TagHelperDescriptor[] - { - new TagHelperDescriptor - { - TagName = "input", - TypeName = "InputTagHelper", - AssemblyName = "SomeAssembly", - TagStructure = TagStructure.WithoutEndTag - } - }; - var descriptorProvider = new TagHelperDescriptorProvider(descriptors); + { + ITagHelperDescriptorBuilder.Create("InputTagHelper", "SomeAssembly") + .TagMatchingRule(rule => + rule + .RequireTagName("input") + .RequireTagStructure(TagStructure.WithoutEndTag)) + .Build() + }; + var descriptorProvider = new TagHelperDescriptorProvider(null, descriptors); // Act & Assert EvaluateData(descriptorProvider, documentContent, expectedOutput, expectedErrors: new[] { expectedError }); @@ -1259,7 +1178,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy "InputTagHelper1", "InputTagHelper2", "input", - nameof(TagHelperDescriptor.TagStructure)), + nameof(TagMatchingRule.TagStructure)), absoluteIndex: 0, lineIndex: 0, columnIndex: 0, @@ -1267,23 +1186,21 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy var documentContent = ""; var expectedOutput = new MarkupBlock(new MarkupTagHelperBlock("input", TagMode.StartTagOnly)); var descriptors = new TagHelperDescriptor[] - { - new TagHelperDescriptor - { - TagName = "input", - TypeName = "InputTagHelper1", - AssemblyName = "SomeAssembly", - TagStructure = TagStructure.WithoutEndTag - }, - new TagHelperDescriptor - { - TagName = "input", - TypeName = "InputTagHelper2", - AssemblyName = "SomeAssembly", - TagStructure = TagStructure.NormalOrSelfClosing - } - }; - var descriptorProvider = new TagHelperDescriptorProvider(descriptors); + { + ITagHelperDescriptorBuilder.Create("InputTagHelper1", "SomeAssembly") + .TagMatchingRule(rule => + rule + .RequireTagName("input") + .RequireTagStructure(TagStructure.WithoutEndTag)) + .Build(), + ITagHelperDescriptorBuilder.Create("InputTagHelper2", "SomeAssembly") + .TagMatchingRule(rule => + rule + .RequireTagName("input") + .RequireTagStructure(TagStructure.NormalOrSelfClosing)) + .Build() + }; + var descriptorProvider = new TagHelperDescriptorProvider(null, descriptors); // Act & Assert EvaluateData(descriptorProvider, documentContent, expectedOutput, expectedErrors: new[] { expectedError }); @@ -1692,34 +1609,28 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy { // Arrange var descriptors = new TagHelperDescriptor[] - { - new TagHelperDescriptor - { - TagName = "p", - TypeName = "pTagHelper", - AssemblyName = "SomeAssembly", - RequiredAttributes = new[] { new TagHelperRequiredAttributeDescriptor { Name = "class" } } - }, - new TagHelperDescriptor - { - TagName = "div", - TypeName = "divTagHelper", - AssemblyName = "SomeAssembly", - RequiredAttributes = new[] - { - new TagHelperRequiredAttributeDescriptor { Name = "class" }, - new TagHelperRequiredAttributeDescriptor { Name = "style" } - } - }, - new TagHelperDescriptor - { - TagName = "*", - TypeName = "catchAllTagHelper", - AssemblyName = "SomeAssembly", - RequiredAttributes = new[] { new TagHelperRequiredAttributeDescriptor { Name = "catchAll" } } - } - }; - var descriptorProvider = new TagHelperDescriptorProvider(descriptors); + { + ITagHelperDescriptorBuilder.Create("pTagHelper", "SomeAssembly") + .TagMatchingRule(rule => + rule + .RequireTagName("p") + .RequireAttribute(attribute => attribute.Name("class"))) + .Build(), + ITagHelperDescriptorBuilder.Create("divTagHelper", "SomeAssembly") + .TagMatchingRule(rule => + rule + .RequireTagName("div") + .RequireAttribute(attribute => attribute.Name("class")) + .RequireAttribute(attribute => attribute.Name("style"))) + .Build(), + ITagHelperDescriptorBuilder.Create("catchAllTagHelper", "SomeAssembly") + .TagMatchingRule(rule => + rule + .RequireTagName("*") + .RequireAttribute(attribute => attribute.Name("catchAll"))) + .Build() + }; + var descriptorProvider = new TagHelperDescriptorProvider(null, descriptors); // Act & Assert EvaluateData(descriptorProvider, documentContent, (MarkupBlock)expectedOutput, expectedErrors: new RazorError[0]); @@ -1959,23 +1870,21 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy { // Arrange var descriptors = new TagHelperDescriptor[] - { - new TagHelperDescriptor - { - TagName = "p", - TypeName = "pTagHelper", - AssemblyName = "SomeAssembly", - RequiredAttributes = new[] { new TagHelperRequiredAttributeDescriptor { Name = "class" } } - }, - new TagHelperDescriptor - { - TagName = "*", - TypeName = "catchAllTagHelper", - AssemblyName = "SomeAssembly", - RequiredAttributes = new[] { new TagHelperRequiredAttributeDescriptor { Name = "catchAll" } } - } - }; - var descriptorProvider = new TagHelperDescriptorProvider(descriptors); + { + ITagHelperDescriptorBuilder.Create("pTagHelper", "SomeAssembly") + .TagMatchingRule(rule => + rule + .RequireTagName("p") + .RequireAttribute(attribute => attribute.Name("class"))) + .Build(), + ITagHelperDescriptorBuilder.Create("catchAllTagHelper", "SomeAssembly") + .TagMatchingRule(rule => + rule + .RequireTagName("*") + .RequireAttribute(attribute => attribute.Name("catchAll"))) + .Build(), + }; + var descriptorProvider = new TagHelperDescriptorProvider(null, descriptors); // Act & Assert EvaluateData(descriptorProvider, documentContent, (MarkupBlock)expectedOutput, expectedErrors: new RazorError[0]); @@ -2183,16 +2092,15 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy { // Arrange var descriptors = new TagHelperDescriptor[] - { - new TagHelperDescriptor - { - TagName = "p", - TypeName = "pTagHelper", - AssemblyName = "SomeAssembly", - RequiredAttributes = new[] { new TagHelperRequiredAttributeDescriptor { Name = "class" } } - } - }; - var descriptorProvider = new TagHelperDescriptorProvider(descriptors); + { + ITagHelperDescriptorBuilder.Create("pTagHelper", "SomeAssembly") + .TagMatchingRule(rule => + rule + .RequireTagName("p") + .RequireAttribute(attribute => attribute.Name("class"))) + .Build(), + }; + var descriptorProvider = new TagHelperDescriptorProvider(null, descriptors); // Act & Assert EvaluateData(descriptorProvider, documentContent, (MarkupBlock)expectedOutput, (RazorError[])expectedErrors); @@ -2206,81 +2114,39 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy var blockFactory = new BlockFactory(factory); var availableDescriptorsColon = new TagHelperDescriptor[] { - new TagHelperDescriptor - { - Prefix = "th:", - TagName = "myth", - TypeName = "mythTagHelper", - AssemblyName = "SomeAssembly" - }, - new TagHelperDescriptor - { - Prefix = "th:", - TagName = "myth2", - TypeName = "mythTagHelper2", - AssemblyName = "SomeAssembly", - Attributes = new [] - { - new TagHelperAttributeDescriptor - { - Name = "bound", - PropertyName = "Bound", - TypeName = typeof(bool).FullName - } - } - } - }; - var availableDescriptorsText = new TagHelperDescriptor[] - { - new TagHelperDescriptor - { - Prefix = "PREFIX", - TagName = "myth", - TypeName = "mythTagHelper", - AssemblyName = "SomeAssembly" - }, - new TagHelperDescriptor - { - Prefix = "PREFIX", - TagName = "myth2", - TypeName = "mythTagHelper2", - AssemblyName = "SomeAssembly", - Attributes = new [] - { - new TagHelperAttributeDescriptor - { - Name = "bound", - PropertyName = "Bound", - TypeName = typeof(bool).FullName - }, - } - } + ITagHelperDescriptorBuilder.Create("mythTagHelper", "SomeAssembly") + .TagMatchingRule(rule => rule.RequireTagName("myth")) + .Build(), + ITagHelperDescriptorBuilder.Create("mythTagHelper2", "SomeAssembly") + .TagMatchingRule(rule => rule.RequireTagName("myth2")) + .BindAttribute(attribute => + attribute + .Name("bound") + .PropertyName("Bound") + .TypeName(typeof(bool).FullName)) + .Build() }; var availableDescriptorsCatchAll = new TagHelperDescriptor[] { - new TagHelperDescriptor - { - Prefix = "myth", - TagName = "*", - TypeName = "mythTagHelper", - AssemblyName = "SomeAssembly" - } + ITagHelperDescriptorBuilder.Create("mythTagHelper", "SomeAssembly") + .TagMatchingRule(rule => rule.RequireTagName("*")) + .Build(), }; // documentContent, expectedOutput, availableDescriptors return new TheoryData> { { - "", - new MarkupBlock(blockFactory.MarkupTagBlock("")), + "", + new MarkupBlock(blockFactory.MarkupTagBlock("")), availableDescriptorsCatchAll }, { - "words and spaces", + "words and spaces", new MarkupBlock( - blockFactory.MarkupTagBlock(""), + blockFactory.MarkupTagBlock(""), factory.Markup("words and spaces"), - blockFactory.MarkupTagBlock("")), + blockFactory.MarkupTagBlock("")), availableDescriptorsCatchAll }, { @@ -2289,24 +2155,12 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy new MarkupTagHelperBlock("th:myth", tagMode: TagMode.SelfClosing)), availableDescriptorsColon }, - { - "", - new MarkupBlock( - new MarkupTagHelperBlock("PREFIXmyth", tagMode: TagMode.SelfClosing)), - availableDescriptorsText - }, { "", new MarkupBlock( new MarkupTagHelperBlock("th:myth")), availableDescriptorsColon }, - { - "", - new MarkupBlock( - new MarkupTagHelperBlock("PREFIXmyth")), - availableDescriptorsText - }, { "", new MarkupBlock( @@ -2316,27 +2170,12 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy blockFactory.MarkupTagBlock(""))), availableDescriptorsColon }, - { - "", - new MarkupBlock( - new MarkupTagHelperBlock( - "PREFIXmyth", - blockFactory.MarkupTagBlock(""), - blockFactory.MarkupTagBlock(""))), - availableDescriptorsText - }, { "", new MarkupBlock( blockFactory.EscapedMarkupTagBlock("<", "th:myth />")), availableDescriptorsColon }, - { - "", - new MarkupBlock( - blockFactory.EscapedMarkupTagBlock("<", "PREFIXmyth />")), - availableDescriptorsText - }, { "", new MarkupBlock( @@ -2344,13 +2183,6 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy blockFactory.EscapedMarkupTagBlock("")), availableDescriptorsColon }, - { - "", - new MarkupBlock( - blockFactory.EscapedMarkupTagBlock("<", "PREFIXmyth>"), - blockFactory.EscapedMarkupTagBlock("")), - availableDescriptorsText - }, { "", new MarkupBlock( @@ -2363,18 +2195,6 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy })), availableDescriptorsColon }, - { - "", - new MarkupBlock( - new MarkupTagHelperBlock( - "PREFIXmyth", - tagMode: TagMode.SelfClosing, - attributes: new List - { - new TagHelperAttributeNode("class", factory.Markup("btn")) - })), - availableDescriptorsText - }, { "", new MarkupBlock( @@ -2387,18 +2207,6 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy })), availableDescriptorsColon }, - { - "", - new MarkupBlock( - new MarkupTagHelperBlock( - "PREFIXmyth2", - tagMode: TagMode.SelfClosing, - attributes: new List - { - new TagHelperAttributeNode("class", factory.Markup("btn")) - })), - availableDescriptorsText - }, { "words and spaces", new MarkupBlock( @@ -2411,18 +2219,6 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy children: factory.Markup("words and spaces"))), availableDescriptorsColon }, - { - "words and spaces", - new MarkupBlock( - new MarkupTagHelperBlock( - "PREFIXmyth", - attributes: new List - { - new TagHelperAttributeNode("class", factory.Markup("btn")) - }, - children: factory.Markup("words and spaces"))), - availableDescriptorsText - }, { "", new MarkupBlock( @@ -2445,58 +2241,6 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy })), availableDescriptorsColon }, - { - "", - new MarkupBlock( - new MarkupTagHelperBlock( - "PREFIXmyth2", - tagMode: TagMode.SelfClosing, - attributes: new List - { - { - new TagHelperAttributeNode( - "bound", - new MarkupBlock( - new MarkupBlock( - new ExpressionBlock( - factory.CodeTransition(), - factory.Code("DateTime.Now") - .AsImplicitExpression(CSharpCodeParser.DefaultKeywords, acceptTrailingDot: true) - .Accepts(AcceptedCharacters.AnyExceptNewline))))) - } - })), - availableDescriptorsText - }, - { - "", - new MarkupBlock( - new MarkupTagHelperBlock( - "PREFIXmyth2", - tagMode: TagMode.SelfClosing, - attributes: new List - { - { - new TagHelperAttributeNode( - "bound", - new MarkupBlock( - new MarkupBlock( - factory.CodeMarkup("@"), - factory - .CodeMarkup("@") - .With(SpanChunkGenerator.Null)), - new MarkupBlock( - factory - .EmptyHtml() - .As(SpanKind.Code) - .AsCodeMarkup(), - new ExpressionBlock( - factory.CSharpCodeMarkup("@"), - factory.CSharpCodeMarkup("DateTime.Now") - .With(new ExpressionChunkGenerator()))))) - } - })), - availableDescriptorsText - }, }; } } @@ -2509,14 +2253,15 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy object availableDescriptors) { // Arrange - var descriptorProvider = new TagHelperDescriptorProvider((IEnumerable)availableDescriptors); + var descriptorProvider = new TagHelperDescriptorProvider("th:", (IEnumerable)availableDescriptors); // Act & Assert EvaluateData( descriptorProvider, documentContent, (MarkupBlock)expectedOutput, - expectedErrors: Enumerable.Empty()); + expectedErrors: Enumerable.Empty(), + tagHelperPrefix: "th:"); } public static TheoryData OptOut_WithAttributeTextTagData diff --git a/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/TagHelperRewritingTestBase.cs b/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/TagHelperRewritingTestBase.cs index 3081f1b726..33be250599 100644 --- a/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/TagHelperRewritingTestBase.cs +++ b/test/Microsoft.AspNetCore.Razor.Evolution.Test/Legacy/TagHelperRewritingTestBase.cs @@ -1,12 +1,8 @@ // 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.IO; using System.Linq; -using System.Text; -using Microsoft.AspNetCore.Razor.Evolution; namespace Microsoft.AspNetCore.Razor.Evolution.Legacy { @@ -41,27 +37,25 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy foreach (var tagName in tagNames) { - descriptors.Add( - new TagHelperDescriptor - { - TagName = tagName, - TypeName = tagName + "taghelper", - AssemblyName = "SomeAssembly" - }); + var descriptor = ITagHelperDescriptorBuilder.Create(tagName + "taghelper", "SomeAssembly") + .TagMatchingRule(rule => rule.RequireTagName(tagName)) + .Build(); + descriptors.Add(descriptor); } - return new TagHelperDescriptorProvider(descriptors); + return new TagHelperDescriptorProvider(null, descriptors); } internal void EvaluateData( TagHelperDescriptorProvider provider, string documentContent, MarkupBlock expectedOutput, - IEnumerable expectedErrors) + IEnumerable expectedErrors, + string tagHelperPrefix = null) { var syntaxTree = ParseDocument(documentContent); var errorSink = new ErrorSink(); - var parseTreeRewriter = new TagHelperParseTreeRewriter(provider); + var parseTreeRewriter = new TagHelperParseTreeRewriter(tagHelperPrefix, provider); var actualTree = parseTreeRewriter.Rewrite(syntaxTree.Root, errorSink); var allErrors = syntaxTree.Diagnostics.Concat(errorSink.Errors.Select(error => RazorDiagnostic.Create(error))); diff --git a/test/Microsoft.AspNetCore.Razor.Evolution.Test/TagHelperBinderSyntaxTreePassTest.cs b/test/Microsoft.AspNetCore.Razor.Evolution.Test/TagHelperBinderSyntaxTreePassTest.cs index d6b793d7d1..4edbc0f227 100644 --- a/test/Microsoft.AspNetCore.Razor.Evolution.Test/TagHelperBinderSyntaxTreePassTest.cs +++ b/test/Microsoft.AspNetCore.Razor.Evolution.Test/TagHelperBinderSyntaxTreePassTest.cs @@ -7,6 +7,7 @@ using Microsoft.AspNetCore.Razor.Evolution.Legacy; using Xunit; using System.Linq; using Moq; +using System.Text; namespace Microsoft.AspNetCore.Razor.Evolution { @@ -20,16 +21,14 @@ namespace Microsoft.AspNetCore.Razor.Evolution { builder.AddTagHelpers(new[] { - new TagHelperDescriptor - { - AssemblyName = "TestAssembly", - TagName = "form", - }, - new TagHelperDescriptor - { - AssemblyName = "TestAssembly", - TagName = "input", - } + CreateTagHelperDescriptor( + tagName: "form", + typeName: null, + assemblyName: "TestAssembly"), + CreateTagHelperDescriptor( + tagName: "input", + typeName: null, + assemblyName: "TestAssembly"), }); }); @@ -59,39 +58,25 @@ namespace Microsoft.AspNetCore.Razor.Evolution public void Execute_DirectiveWithoutQuotes_RewritesTagHelpers_TagHelperMatchesElementTwice() { // Arrange + var descriptor = CreateTagHelperDescriptor( + tagName: "form", + typeName: "TestFormTagHelper", + assemblyName: "TestAssembly", + ruleBuilders: new Action[] + { + ruleBuilder => ruleBuilder + .RequireAttribute(attribute => attribute + .Name("a") + .NameComparisonMode(RequiredAttributeDescriptor.NameComparisonMode.FullMatch)), + ruleBuilder => ruleBuilder + .RequireAttribute(attribute => attribute + .Name("b") + .NameComparisonMode(RequiredAttributeDescriptor.NameComparisonMode.FullMatch)), + }); + var engine = RazorEngine.Create(builder => { - builder.AddTagHelpers(new[] - { - new TagHelperDescriptor - { - AssemblyName = "TestAssembly", - TagName = "form", - TypeName = "TestFormTagHelper", - RequiredAttributes = new List() - { - new TagHelperRequiredAttributeDescriptor() - { - Name = "a", - NameComparison = TagHelperRequiredAttributeNameComparison.FullMatch - } - }, - }, - new TagHelperDescriptor - { - AssemblyName = "TestAssembly", - TagName = "form", - TypeName = "TestFormTagHelper", - RequiredAttributes = new List() - { - new TagHelperRequiredAttributeDescriptor() - { - Name = "b", - NameComparison = TagHelperRequiredAttributeNameComparison.FullMatch - } - }, - } - }); + builder.AddTagHelpers(new[] { descriptor }); }); var pass = new TagHelperBinderSyntaxTreePass() @@ -117,46 +102,32 @@ namespace Microsoft.AspNetCore.Razor.Evolution var formTagHelper = Assert.IsType(rewrittenTree.Root.Children[2]); Assert.Equal("form", formTagHelper.TagName); - Assert.Single(formTagHelper.Descriptors); + Assert.Equal(2, formTagHelper.Binding.GetBoundRules(descriptor).Count()); } [Fact] public void Execute_DirectiveWithQuotes_RewritesTagHelpers_TagHelperMatchesElementTwice() { // Arrange + var descriptor = CreateTagHelperDescriptor( + tagName: "form", + typeName: "TestFormTagHelper", + assemblyName: "TestAssembly", + ruleBuilders: new Action[] + { + ruleBuilder => ruleBuilder + .RequireAttribute(attribute => attribute + .Name("a") + .NameComparisonMode(RequiredAttributeDescriptor.NameComparisonMode.FullMatch)), + ruleBuilder => ruleBuilder + .RequireAttribute(attribute => attribute + .Name("b") + .NameComparisonMode(RequiredAttributeDescriptor.NameComparisonMode.FullMatch)), + }); + var engine = RazorEngine.Create(builder => { - builder.AddTagHelpers(new[] - { - new TagHelperDescriptor - { - AssemblyName = "TestAssembly", - TagName = "form", - TypeName = "TestFormTagHelper", - RequiredAttributes = new List() - { - new TagHelperRequiredAttributeDescriptor() - { - Name = "a", - NameComparison = TagHelperRequiredAttributeNameComparison.FullMatch - } - }, - }, - new TagHelperDescriptor - { - AssemblyName = "TestAssembly", - TagName = "form", - TypeName = "TestFormTagHelper", - RequiredAttributes = new List() - { - new TagHelperRequiredAttributeDescriptor() - { - Name = "b", - NameComparison = TagHelperRequiredAttributeNameComparison.FullMatch - } - }, - } - }); + builder.AddTagHelpers(new[] { descriptor }); }); var pass = new TagHelperBinderSyntaxTreePass() @@ -182,7 +153,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution var formTagHelper = Assert.IsType(rewrittenTree.Root.Children[2]); Assert.Equal("form", formTagHelper.TagName); - Assert.Single(formTagHelper.Descriptors); + Assert.Equal(2, formTagHelper.Binding.GetBoundRules(descriptor).Count()); } [Fact] @@ -298,7 +269,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution 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); + var resolver = new ErrorLoggingTagHelperDescriptorResolver(resolverError, tagName: "test"); builder.Features.Add(Mock.Of(f => f.Resolver == resolver)); }); @@ -327,6 +298,36 @@ namespace Microsoft.AspNetCore.Razor.Evolution 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 pass = new TagHelperBinderSyntaxTreePass() + { + Engine = engine, + }; + + var sourceDocument = CreateTestSourceDocument(); + var codeDocument = RazorCodeDocument.Create(sourceDocument); + var originalTree = RazorSyntaxTree.Parse(sourceDocument); + + // Act + var outputTree = pass.Execute(codeDocument, originalTree); + + // Assert + Assert.Empty(originalTree.Diagnostics); + Assert.Equal(new[] { resolverError, descriptorError }, outputTree.Diagnostics); + } + [Fact] public void Execute_CombinesErrorsOnRewritingErrors() { @@ -335,16 +336,14 @@ namespace Microsoft.AspNetCore.Razor.Evolution { builder.AddTagHelpers(new[] { - new TagHelperDescriptor - { - TagName = "form", - AssemblyName= "TestAssembly", - }, - new TagHelperDescriptor - { - TagName = "input", - AssemblyName= "TestAssembly", - } + CreateTagHelperDescriptor( + tagName: "form", + typeName: null, + assemblyName: "TestAssembly"), + CreateTagHelperDescriptor( + tagName: "input", + typeName: null, + assemblyName: "TestAssembly"), }); }); @@ -634,12 +633,10 @@ namespace Microsoft.AspNetCore.Razor.Evolution { get { - return new TagHelperDescriptor - { - TagName = "valid_plain", - TypeName = "Microsoft.AspNetCore.Razor.TagHelpers.ValidPlainTagHelper", - AssemblyName = AssemblyA, - }; + return CreateTagHelperDescriptor( + tagName: "valid_plain", + typeName: "Microsoft.AspNetCore.Razor.TagHelpers.ValidPlainTagHelper", + assemblyName: AssemblyA); } } @@ -647,48 +644,34 @@ namespace Microsoft.AspNetCore.Razor.Evolution { get { - return new TagHelperDescriptor - { - TagName = "valid_inherited", - TypeName = "Microsoft.AspNetCore.Razor.TagHelpers.ValidInheritedTagHelper", - AssemblyName = AssemblyA - }; + return CreateTagHelperDescriptor( + tagName: "valid_inherited", + typeName: "Microsoft.AspNetCore.Razor.TagHelpers.ValidInheritedTagHelper", + assemblyName: AssemblyA); } } - private static TagHelperDescriptor[] AllTagHelpers => new[] - { - Valid_PlainTagHelperDescriptor, - Valid_InheritedTagHelperDescriptor, - String_TagHelperDescriptor - }; - private static TagHelperDescriptor String_TagHelperDescriptor { get { // We're treating 'string' as a TagHelper so we can test TagHelpers in multiple assemblies without // building a separate assembly with a single TagHelper. - return new TagHelperDescriptor - { - TagName = "string", - TypeName = "System.String", - AssemblyName = AssemblyB, - }; + return CreateTagHelperDescriptor( + tagName: "string", + typeName: "System.String", + assemblyName: AssemblyB); } } - public static TheoryData ProcessDirectives_TagHelperPrefixData + public static TheoryData ProcessTagHelperPrefixData { get { - return new TheoryData< - IEnumerable, // tagHelpers - IEnumerable, // directiveDescriptors - IEnumerable> // expectedDescriptors + // directiveDescriptors, expected prefix + return new TheoryData, string> { { - AllTagHelpers, new [] { CreateTagHelperDirectiveDescriptor("", TagHelperDirectiveType.TagHelperPrefix), @@ -696,10 +679,9 @@ namespace Microsoft.AspNetCore.Razor.Evolution "Microsoft.AspNetCore.Razor.TagHelpers.ValidPlain*, " + AssemblyA, TagHelperDirectiveType.AddTagHelper), }, - new [] { Valid_PlainTagHelperDescriptor } + null }, { - AllTagHelpers, new [] { CreateTagHelperDirectiveDescriptor("th:", TagHelperDirectiveType.TagHelperPrefix), @@ -707,23 +689,17 @@ namespace Microsoft.AspNetCore.Razor.Evolution "Microsoft.AspNetCore.Razor.TagHelpers.ValidPlain*, " + AssemblyA, TagHelperDirectiveType.AddTagHelper), }, - new [] { CreatePrefixedValidPlainDescriptor("th:") } + "th:" }, { - AllTagHelpers, new [] { CreateTagHelperDirectiveDescriptor("*, " + AssemblyA, TagHelperDirectiveType.AddTagHelper), CreateTagHelperDirectiveDescriptor("th:", TagHelperDirectiveType.TagHelperPrefix) }, - new [] - { - CreatePrefixedValidPlainDescriptor("th:"), - CreatePrefixedValidInheritedDescriptor("th:") - } + "th:" }, { - AllTagHelpers, new [] { CreateTagHelperDirectiveDescriptor("th-", TagHelperDirectiveType.TagHelperPrefix), @@ -734,14 +710,9 @@ namespace Microsoft.AspNetCore.Razor.Evolution "Microsoft.AspNetCore.Razor.TagHelpers.ValidInherited*, " + AssemblyA, TagHelperDirectiveType.AddTagHelper) }, - new [] - { - CreatePrefixedValidPlainDescriptor("th-"), - CreatePrefixedValidInheritedDescriptor("th-") - } + "th-" }, { - AllTagHelpers, new [] { CreateTagHelperDirectiveDescriptor("", TagHelperDirectiveType.TagHelperPrefix), @@ -752,10 +723,9 @@ namespace Microsoft.AspNetCore.Razor.Evolution "Microsoft.AspNetCore.Razor.TagHelpers.ValidInherited*, " + AssemblyA, TagHelperDirectiveType.AddTagHelper) }, - new [] { Valid_PlainTagHelperDescriptor, Valid_InheritedTagHelperDescriptor } + null }, { - AllTagHelpers, new [] { CreateTagHelperDirectiveDescriptor("th", TagHelperDirectiveType.TagHelperPrefix), @@ -766,15 +736,9 @@ namespace Microsoft.AspNetCore.Razor.Evolution "*, " + AssemblyB, TagHelperDirectiveType.AddTagHelper), }, - new [] - { - CreatePrefixedValidPlainDescriptor("th"), - CreatePrefixedValidInheritedDescriptor("th"), - CreatePrefixedStringDescriptor("th") - } + "th" }, { - AllTagHelpers, new [] { CreateTagHelperDirectiveDescriptor( @@ -785,44 +749,30 @@ namespace Microsoft.AspNetCore.Razor.Evolution "*, " + AssemblyB, TagHelperDirectiveType.AddTagHelper), }, - new [] - { - CreatePrefixedValidPlainDescriptor("th:-"), - CreatePrefixedValidInheritedDescriptor("th:-"), - CreatePrefixedStringDescriptor("th:-") - } + "th:-" }, }; } } [Theory] - [MemberData(nameof(ProcessDirectives_TagHelperPrefixData))] - public void ProcessDirectives_AppliesDirectives_WithTagHelperPrefix( - object tagHelpers, + [MemberData(nameof(ProcessTagHelperPrefixData))] + public void ProcessTagHelperPrefix_ParsesPrefixFromDirectives_SetsOnCodeDocument( object directiveDescriptors, - object expectedDescriptors) + string expectedPrefix) { // Arrange var errorSink = new ErrorSink(); var pass = new TagHelperBinderSyntaxTreePass(); - - var expected = (IEnumerable)expectedDescriptors; + var document = RazorCodeDocument.Create(new DefaultRazorSourceDocument("Test content", encoding: Encoding.UTF8, fileName: "TestFile")); // Act - var results = pass.ProcessDirectives( - ((IEnumerable)directiveDescriptors).ToArray(), - ((IEnumerable)tagHelpers).ToArray(), - errorSink); + var prefix = pass.ProcessTagHelperPrefix(((IEnumerable)directiveDescriptors).ToList(), document, errorSink); // Assert Assert.Empty(errorSink.Errors); - Assert.Equal(expected.Count(), results.Count()); - - foreach (var expectedDescriptor in expected) - { - Assert.Contains(expectedDescriptor, results, TagHelperDescriptorComparer.Default); - } + Assert.Equal(expectedPrefix, prefix); + Assert.Equal(expectedPrefix, document.GetTagHelperPrefix()); } public static TheoryData ProcessDirectivesData @@ -874,7 +824,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution new [] { CreateTagHelperDirectiveDescriptor( - Valid_PlainTagHelperDescriptor.TypeName + ", " + AssemblyA, + Valid_PlainTagHelperDescriptor.Name + ", " + AssemblyA, TagHelperDirectiveType.AddTagHelper), CreateTagHelperDirectiveDescriptor("*, " + AssemblyA, TagHelperDirectiveType.AddTagHelper) }, @@ -886,7 +836,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution { CreateTagHelperDirectiveDescriptor("*, " + AssemblyA, TagHelperDirectiveType.AddTagHelper), CreateTagHelperDirectiveDescriptor( - Valid_PlainTagHelperDescriptor.TypeName + ", " + AssemblyA, + Valid_PlainTagHelperDescriptor.Name + ", " + AssemblyA, TagHelperDirectiveType.RemoveTagHelper) }, new [] { Valid_InheritedTagHelperDescriptor } @@ -897,7 +847,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution { CreateTagHelperDirectiveDescriptor("*, " + AssemblyA, TagHelperDirectiveType.AddTagHelper), CreateTagHelperDirectiveDescriptor( - Valid_PlainTagHelperDescriptor.TypeName + ", " + AssemblyA, + Valid_PlainTagHelperDescriptor.Name + ", " + AssemblyA, TagHelperDirectiveType.RemoveTagHelper), CreateTagHelperDirectiveDescriptor("*, " + AssemblyA, TagHelperDirectiveType.AddTagHelper) }, @@ -967,7 +917,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution new [] { CreateTagHelperDirectiveDescriptor("*, " + AssemblyA, TagHelperDirectiveType.AddTagHelper), - CreateTagHelperDirectiveDescriptor("System." + String_TagHelperDescriptor.TypeName + ", " + AssemblyB, TagHelperDirectiveType.AddTagHelper) + CreateTagHelperDirectiveDescriptor("System." + String_TagHelperDescriptor.Name + ", " + AssemblyB, TagHelperDirectiveType.AddTagHelper) }, new [] { Valid_PlainTagHelperDescriptor } }, @@ -992,7 +942,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution CreateTagHelperDirectiveDescriptor( "?Microsoft*, " + AssemblyA, TagHelperDirectiveType.RemoveTagHelper), CreateTagHelperDirectiveDescriptor( - "System." + String_TagHelperDescriptor.TypeName + ", " + AssemblyB, TagHelperDirectiveType.RemoveTagHelper) + "System." + String_TagHelperDescriptor.Name + ", " + AssemblyB, TagHelperDirectiveType.RemoveTagHelper) }, new [] { @@ -1012,7 +962,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution CreateTagHelperDirectiveDescriptor( "TagHelper*, " + AssemblyA, TagHelperDirectiveType.RemoveTagHelper), CreateTagHelperDirectiveDescriptor( - "System." + String_TagHelperDescriptor.TypeName + ", " + AssemblyB, TagHelperDirectiveType.RemoveTagHelper) + "System." + String_TagHelperDescriptor.Name + ", " + AssemblyB, TagHelperDirectiveType.RemoveTagHelper) }, new [] { @@ -1081,8 +1031,8 @@ namespace Microsoft.AspNetCore.Razor.Evolution new [] { CreateTagHelperDirectiveDescriptor("*, " + AssemblyA, TagHelperDirectiveType.AddTagHelper), - CreateTagHelperDirectiveDescriptor(Valid_PlainTagHelperDescriptor.TypeName + ", " + AssemblyA, TagHelperDirectiveType.RemoveTagHelper), - CreateTagHelperDirectiveDescriptor(Valid_InheritedTagHelperDescriptor.TypeName + ", " + AssemblyA, TagHelperDirectiveType.RemoveTagHelper), + CreateTagHelperDirectiveDescriptor(Valid_PlainTagHelperDescriptor.Name + ", " + AssemblyA, TagHelperDirectiveType.RemoveTagHelper), + CreateTagHelperDirectiveDescriptor(Valid_InheritedTagHelperDescriptor.Name + ", " + AssemblyA, TagHelperDirectiveType.RemoveTagHelper), } }, { @@ -1111,9 +1061,9 @@ namespace Microsoft.AspNetCore.Razor.Evolution { CreateTagHelperDirectiveDescriptor("*, " + AssemblyA, TagHelperDirectiveType.AddTagHelper), CreateTagHelperDirectiveDescriptor("*, " + AssemblyB, TagHelperDirectiveType.AddTagHelper), - CreateTagHelperDirectiveDescriptor(Valid_PlainTagHelperDescriptor.TypeName + ", " + AssemblyA, TagHelperDirectiveType.RemoveTagHelper), - CreateTagHelperDirectiveDescriptor(Valid_InheritedTagHelperDescriptor.TypeName + ", " + AssemblyA, TagHelperDirectiveType.RemoveTagHelper), - CreateTagHelperDirectiveDescriptor(String_TagHelperDescriptor.TypeName + ", " + AssemblyB, TagHelperDirectiveType.RemoveTagHelper) + CreateTagHelperDirectiveDescriptor(Valid_PlainTagHelperDescriptor.Name + ", " + AssemblyA, TagHelperDirectiveType.RemoveTagHelper), + CreateTagHelperDirectiveDescriptor(Valid_InheritedTagHelperDescriptor.Name + ", " + AssemblyA, TagHelperDirectiveType.RemoveTagHelper), + CreateTagHelperDirectiveDescriptor(String_TagHelperDescriptor.Name + ", " + AssemblyB, TagHelperDirectiveType.RemoveTagHelper) } }, { @@ -1121,7 +1071,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution new [] { CreateTagHelperDirectiveDescriptor("*, " + AssemblyA, TagHelperDirectiveType.RemoveTagHelper), - CreateTagHelperDirectiveDescriptor(Valid_PlainTagHelperDescriptor.TypeName + ", " + AssemblyA, TagHelperDirectiveType.RemoveTagHelper), + CreateTagHelperDirectiveDescriptor(Valid_PlainTagHelperDescriptor.Name + ", " + AssemblyA, TagHelperDirectiveType.RemoveTagHelper), } }, { @@ -1143,8 +1093,8 @@ namespace Microsoft.AspNetCore.Razor.Evolution new [] { CreateTagHelperDirectiveDescriptor("Mic*, " + AssemblyA, TagHelperDirectiveType.AddTagHelper), - CreateTagHelperDirectiveDescriptor(Valid_PlainTagHelperDescriptor.TypeName + ", " + AssemblyA, TagHelperDirectiveType.RemoveTagHelper), - CreateTagHelperDirectiveDescriptor(Valid_InheritedTagHelperDescriptor.TypeName + ", " + AssemblyA, TagHelperDirectiveType.RemoveTagHelper) + CreateTagHelperDirectiveDescriptor(Valid_PlainTagHelperDescriptor.Name + ", " + AssemblyA, TagHelperDirectiveType.RemoveTagHelper), + CreateTagHelperDirectiveDescriptor(Valid_InheritedTagHelperDescriptor.Name + ", " + AssemblyA, TagHelperDirectiveType.RemoveTagHelper) } }, { @@ -1220,7 +1170,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution get { var assemblyName = Valid_PlainTagHelperDescriptor.AssemblyName; - var typeName = Valid_PlainTagHelperDescriptor.TypeName; + var typeName = Valid_PlainTagHelperDescriptor.Name; return new TheoryData { $"{typeName},{assemblyName}", @@ -1246,12 +1196,12 @@ namespace Microsoft.AspNetCore.Razor.Evolution var directives = new[] { - new TagHelperDirectiveDescriptor() - { - DirectiveText = directiveText, - DirectiveType = TagHelperDirectiveType.AddTagHelper, - } - }; + new TagHelperDirectiveDescriptor() + { + DirectiveText = directiveText, + DirectiveType = TagHelperDirectiveType.AddTagHelper, + } + }; // Act var results = pass.ProcessDirectives( @@ -1311,26 +1261,29 @@ namespace Microsoft.AspNetCore.Razor.Evolution private static TagHelperDescriptor CreatePrefixedValidPlainDescriptor(string prefix) { - return new TagHelperDescriptor(Valid_PlainTagHelperDescriptor) - { - Prefix = prefix, - }; + return Valid_PlainTagHelperDescriptor; + //return new TagHelperDescriptor(Valid_PlainTagHelperDescriptor) + //{ + // Prefix = prefix, + //}; } private static TagHelperDescriptor CreatePrefixedValidInheritedDescriptor(string prefix) { - return new TagHelperDescriptor(Valid_InheritedTagHelperDescriptor) - { - Prefix = prefix, - }; + return Valid_InheritedTagHelperDescriptor; + //return new TagHelperDescriptor(Valid_InheritedTagHelperDescriptor) + //{ + // Prefix = prefix, + //}; } private static TagHelperDescriptor CreatePrefixedStringDescriptor(string prefix) { - return new TagHelperDescriptor(String_TagHelperDescriptor) - { - Prefix = prefix, - }; + return String_TagHelperDescriptor; + //return new TagHelperDescriptor(String_TagHelperDescriptor) + //{ + // Prefix = prefix, + //}; } private static TagHelperDirectiveDescriptor CreateTagHelperDirectiveDescriptor( @@ -1357,20 +1310,62 @@ namespace Microsoft.AspNetCore.Razor.Evolution return sourceDocument; } + private static TagHelperDescriptor CreateTagHelperDescriptor( + string tagName, + string typeName, + string assemblyName, + IEnumerable> attributes = null, + IEnumerable> ruleBuilders = null) + { + var builder = ITagHelperDescriptorBuilder.Create(typeName, assemblyName); + + if (attributes != null) + { + foreach (var attributeBuilder in attributes) + { + builder.BindAttribute(attributeBuilder); + } + } + + if (ruleBuilders != null) + { + foreach (var ruleBuilder in ruleBuilders) + { + builder.TagMatchingRule(innerRuleBuilder => { + innerRuleBuilder.RequireTagName(tagName); + ruleBuilder(innerRuleBuilder); + }); + } + } + else + { + builder.TagMatchingRule(ruleBuilder => ruleBuilder.RequireTagName(tagName)); + } + + var descriptor = builder.Build(); + + return descriptor; + } + private class ErrorLoggingTagHelperDescriptorResolver : ITagHelperDescriptorResolver { private readonly RazorDiagnostic _error; + private readonly string _tagName; - public ErrorLoggingTagHelperDescriptorResolver(RazorDiagnostic error) + public ErrorLoggingTagHelperDescriptorResolver(RazorDiagnostic error, string tagName = null) { _error = error; + _tagName = tagName; } public IEnumerable Resolve(IList errors) { errors.Add(_error); - return new[] { new TagHelperDescriptor() { AssemblyName = "TestAssembly" } }; + return new[] { CreateTagHelperDescriptor( + tagName: _tagName, + typeName: null, + assemblyName: "TestAssembly") }; } } } diff --git a/test/Microsoft.AspNetCore.Razor.Evolution.Test/TagHelperDescriptorTest.cs b/test/Microsoft.AspNetCore.Razor.Evolution.Test/TagHelperDescriptorTest.cs deleted file mode 100644 index 53389a9abf..0000000000 --- a/test/Microsoft.AspNetCore.Razor.Evolution.Test/TagHelperDescriptorTest.cs +++ /dev/null @@ -1,557 +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; -using System.Collections.Generic; -using Microsoft.AspNetCore.Razor.Evolution.Legacy; -using Newtonsoft.Json; -using Xunit; - -namespace Microsoft.AspNetCore.Razor.Evolution -{ - public class TagHelperDescriptorTest - { - [Fact] - public void Constructor_CorrectlyCreatesCopy() - { - // Arrange - var descriptor = new TagHelperDescriptor - { - Prefix = "prefix", - TagName = "tag-name", - TypeName = "TypeName", - AssemblyName = "AsssemblyName", - Attributes = new List - { - new TagHelperAttributeDescriptor - { - Name = "test-attribute", - PropertyName = "TestAttribute", - TypeName = "string" - } - }, - RequiredAttributes = new List - { - new TagHelperRequiredAttributeDescriptor - { - Name = "test-required-attribute" - } - }, - AllowedChildren = new[] { "child" }, - RequiredParent = "required parent", - TagStructure = TagStructure.NormalOrSelfClosing, - DesignTimeDescriptor = new TagHelperDesignTimeDescriptor() - }; - - descriptor.PropertyBag.Add("foo", "bar"); - - // Act - var copyDescriptor = new TagHelperDescriptor(descriptor); - - // Assert - Assert.Equal(descriptor, copyDescriptor, CaseSensitiveTagHelperDescriptorComparer.Default); - Assert.Same(descriptor.Attributes, copyDescriptor.Attributes); - Assert.Same(descriptor.RequiredAttributes, copyDescriptor.RequiredAttributes); - } - - [Fact] - public void TagHelperDescriptor_CanBeSerialized() - { - // Arrange - var descriptor = new TagHelperDescriptor - { - Prefix = "prefix:", - TagName = "tag name", - TypeName = "type name", - AssemblyName = "assembly name", - RequiredAttributes = new[] - { - new TagHelperRequiredAttributeDescriptor - { - Name = "required attribute one", - NameComparison = TagHelperRequiredAttributeNameComparison.PrefixMatch - }, - new TagHelperRequiredAttributeDescriptor - { - Name = "required attribute two", - NameComparison = TagHelperRequiredAttributeNameComparison.FullMatch, - Value = "something", - ValueComparison = TagHelperRequiredAttributeValueComparison.PrefixMatch, - } - }, - AllowedChildren = new[] { "allowed child one" }, - RequiredParent = "parent name", - DesignTimeDescriptor = new TagHelperDesignTimeDescriptor - { - Summary = "usage summary", - Remarks = "usage remarks", - OutputElementHint = "some-tag" - }, - }; - - var expectedSerializedDescriptor = - $"{{\"{ nameof(TagHelperDescriptor.Prefix) }\":\"prefix:\"," + - $"\"{ nameof(TagHelperDescriptor.TagName) }\":\"tag name\"," + - $"\"{ nameof(TagHelperDescriptor.FullTagName) }\":\"prefix:tag name\"," + - $"\"{ nameof(TagHelperDescriptor.TypeName) }\":\"type name\"," + - $"\"{ nameof(TagHelperDescriptor.AssemblyName) }\":\"assembly name\"," + - $"\"{ nameof(TagHelperDescriptor.Attributes) }\":[]," + - $"\"{ nameof(TagHelperDescriptor.RequiredAttributes) }\":" + - $"[{{\"{ nameof(TagHelperRequiredAttributeDescriptor.Name)}\":\"required attribute one\"," + - $"\"{ nameof(TagHelperRequiredAttributeDescriptor.NameComparison) }\":1," + - $"\"{ nameof(TagHelperRequiredAttributeDescriptor.Value) }\":null," + - $"\"{ nameof(TagHelperRequiredAttributeDescriptor.ValueComparison) }\":0}}," + - $"{{\"{ nameof(TagHelperRequiredAttributeDescriptor.Name)}\":\"required attribute two\"," + - $"\"{ nameof(TagHelperRequiredAttributeDescriptor.NameComparison) }\":0," + - $"\"{ nameof(TagHelperRequiredAttributeDescriptor.Value) }\":\"something\"," + - $"\"{ nameof(TagHelperRequiredAttributeDescriptor.ValueComparison) }\":2}}]," + - $"\"{ nameof(TagHelperDescriptor.AllowedChildren) }\":[\"allowed child one\"]," + - $"\"{ nameof(TagHelperDescriptor.RequiredParent) }\":\"parent name\"," + - $"\"{ nameof(TagHelperDescriptor.TagStructure) }\":0," + - $"\"{ nameof(TagHelperDescriptor.DesignTimeDescriptor) }\":{{" + - $"\"{ nameof(TagHelperDesignTimeDescriptor.Summary) }\":\"usage summary\"," + - $"\"{ nameof(TagHelperDesignTimeDescriptor.Remarks) }\":\"usage remarks\"," + - $"\"{ nameof(TagHelperDesignTimeDescriptor.OutputElementHint) }\":\"some-tag\"}}," + - $"\"{ nameof(TagHelperDescriptor.PropertyBag) }\":{{}}}}"; - - // Act - var serializedDescriptor = JsonConvert.SerializeObject(descriptor); - - // Assert - Assert.Equal(expectedSerializedDescriptor, serializedDescriptor, StringComparer.Ordinal); - } - - [Fact] - public void TagHelperDescriptor_WithAttributes_CanBeSerialized() - { - // Arrange - var descriptor = new TagHelperDescriptor - { - Prefix = "prefix:", - TagName = "tag name", - TypeName = "type name", - AssemblyName = "assembly name", - Attributes = new[] - { - new TagHelperAttributeDescriptor - { - Name = "attribute one", - PropertyName = "property name", - TypeName = "property type name", - IsEnum = true, - }, - new TagHelperAttributeDescriptor - { - Name = "attribute two", - PropertyName = "property name", - TypeName = typeof(string).FullName, - IsStringProperty = true - }, - }, - TagStructure = TagStructure.NormalOrSelfClosing - }; - - var expectedSerializedDescriptor = - $"{{\"{ nameof(TagHelperDescriptor.Prefix) }\":\"prefix:\"," + - $"\"{ nameof(TagHelperDescriptor.TagName) }\":\"tag name\"," + - $"\"{ nameof(TagHelperDescriptor.FullTagName) }\":\"prefix:tag name\"," + - $"\"{ nameof(TagHelperDescriptor.TypeName) }\":\"type name\"," + - $"\"{ nameof(TagHelperDescriptor.AssemblyName) }\":\"assembly name\"," + - $"\"{ nameof(TagHelperDescriptor.Attributes) }\":[" + - $"{{\"{ nameof(TagHelperAttributeDescriptor.IsIndexer) }\":false," + - $"\"{ nameof(TagHelperAttributeDescriptor.IsEnum) }\":true," + - $"\"{ nameof(TagHelperAttributeDescriptor.IsStringProperty) }\":false," + - $"\"{ nameof(TagHelperAttributeDescriptor.Name) }\":\"attribute one\"," + - $"\"{ nameof(TagHelperAttributeDescriptor.PropertyName) }\":\"property name\"," + - $"\"{ nameof(TagHelperAttributeDescriptor.TypeName) }\":\"property type name\"," + - $"\"{ nameof(TagHelperAttributeDescriptor.DesignTimeDescriptor) }\":null}}," + - $"{{\"{ nameof(TagHelperAttributeDescriptor.IsIndexer) }\":false," + - $"\"{ nameof(TagHelperAttributeDescriptor.IsEnum) }\":false," + - $"\"{ nameof(TagHelperAttributeDescriptor.IsStringProperty) }\":true," + - $"\"{ nameof(TagHelperAttributeDescriptor.Name) }\":\"attribute two\"," + - $"\"{ nameof(TagHelperAttributeDescriptor.PropertyName) }\":\"property name\"," + - $"\"{ nameof(TagHelperAttributeDescriptor.TypeName) }\":\"{ typeof(string).FullName }\"," + - $"\"{ nameof(TagHelperAttributeDescriptor.DesignTimeDescriptor) }\":null}}]," + - $"\"{ nameof(TagHelperDescriptor.RequiredAttributes) }\":[]," + - $"\"{ nameof(TagHelperDescriptor.AllowedChildren) }\":null," + - $"\"{ nameof(TagHelperDescriptor.RequiredParent) }\":null," + - $"\"{ nameof(TagHelperDescriptor.TagStructure) }\":1," + - $"\"{ nameof(TagHelperDescriptor.DesignTimeDescriptor) }\":null," + - $"\"{ nameof(TagHelperDescriptor.PropertyBag) }\":{{}}}}"; - - // Act - var serializedDescriptor = JsonConvert.SerializeObject(descriptor); - - // Assert - Assert.Equal(expectedSerializedDescriptor, serializedDescriptor, StringComparer.Ordinal); - } - - [Fact] - public void TagHelperDescriptor_WithIndexerAttributes_CanBeSerialized() - { - // Arrange - var descriptor = new TagHelperDescriptor - { - Prefix = "prefix:", - TagName = "tag name", - TypeName = "type name", - AssemblyName = "assembly name", - Attributes = new[] - { - new TagHelperAttributeDescriptor - { - Name = "attribute one", - PropertyName = "property name", - TypeName = "property type name", - IsIndexer = true, - IsEnum = true, - }, - new TagHelperAttributeDescriptor - { - Name = "attribute two", - PropertyName = "property name", - TypeName = typeof(string).FullName, - IsIndexer = true, - IsEnum = false, - IsStringProperty = true - }, - }, - AllowedChildren = new[] { "allowed child one", "allowed child two" }, - RequiredParent = "parent name" - }; - - var expectedSerializedDescriptor = - $"{{\"{ nameof(TagHelperDescriptor.Prefix) }\":\"prefix:\"," + - $"\"{ nameof(TagHelperDescriptor.TagName) }\":\"tag name\"," + - $"\"{ nameof(TagHelperDescriptor.FullTagName) }\":\"prefix:tag name\"," + - $"\"{ nameof(TagHelperDescriptor.TypeName) }\":\"type name\"," + - $"\"{ nameof(TagHelperDescriptor.AssemblyName) }\":\"assembly name\"," + - $"\"{ nameof(TagHelperDescriptor.Attributes) }\":[" + - $"{{\"{ nameof(TagHelperAttributeDescriptor.IsIndexer) }\":true," + - $"\"{ nameof(TagHelperAttributeDescriptor.IsEnum) }\":true," + - $"\"{ nameof(TagHelperAttributeDescriptor.IsStringProperty) }\":false," + - $"\"{ nameof(TagHelperAttributeDescriptor.Name) }\":\"attribute one\"," + - $"\"{ nameof(TagHelperAttributeDescriptor.PropertyName) }\":\"property name\"," + - $"\"{ nameof(TagHelperAttributeDescriptor.TypeName) }\":\"property type name\"," + - $"\"{ nameof(TagHelperAttributeDescriptor.DesignTimeDescriptor) }\":null}}," + - $"{{\"{ nameof(TagHelperAttributeDescriptor.IsIndexer) }\":true," + - $"\"{ nameof(TagHelperAttributeDescriptor.IsEnum) }\":false," + - $"\"{ nameof(TagHelperAttributeDescriptor.IsStringProperty) }\":true," + - $"\"{ nameof(TagHelperAttributeDescriptor.Name) }\":\"attribute two\"," + - $"\"{ nameof(TagHelperAttributeDescriptor.PropertyName) }\":\"property name\"," + - $"\"{ nameof(TagHelperAttributeDescriptor.TypeName) }\":\"{ typeof(string).FullName }\"," + - $"\"{ nameof(TagHelperAttributeDescriptor.DesignTimeDescriptor) }\":null}}]," + - $"\"{ nameof(TagHelperDescriptor.RequiredAttributes) }\":[]," + - $"\"{ nameof(TagHelperDescriptor.AllowedChildren) }\":[\"allowed child one\",\"allowed child two\"]," + - $"\"{ nameof(TagHelperDescriptor.RequiredParent) }\":\"parent name\"," + - $"\"{ nameof(TagHelperDescriptor.TagStructure) }\":0," + - $"\"{ nameof(TagHelperDescriptor.DesignTimeDescriptor) }\":null," + - $"\"{ nameof(TagHelperDescriptor.PropertyBag) }\":{{}}}}"; - - // Act - var serializedDescriptor = JsonConvert.SerializeObject(descriptor); - - // Assert - Assert.Equal(expectedSerializedDescriptor, serializedDescriptor, StringComparer.Ordinal); - } - - [Fact] - public void TagHelperDescriptor_WithPropertyBagElements_CanBeSerialized() - { - // Arrange - var descriptor = new TagHelperDescriptor - { - Prefix = "prefix:", - TagName = "tag name", - TypeName = "type name", - AssemblyName = "assembly name" - }; - - descriptor.PropertyBag.Add("key one", "value one"); - descriptor.PropertyBag.Add("key two", "value two"); - - var expectedSerializedDescriptor = - $"{{\"{ nameof(TagHelperDescriptor.Prefix) }\":\"prefix:\"," + - $"\"{ nameof(TagHelperDescriptor.TagName) }\":\"tag name\"," + - $"\"{ nameof(TagHelperDescriptor.FullTagName) }\":\"prefix:tag name\"," + - $"\"{ nameof(TagHelperDescriptor.TypeName) }\":\"type name\"," + - $"\"{ nameof(TagHelperDescriptor.AssemblyName) }\":\"assembly name\"," + - $"\"{ nameof(TagHelperDescriptor.Attributes) }\":[]," + - $"\"{ nameof(TagHelperDescriptor.RequiredAttributes) }\":[]," + - $"\"{ nameof(TagHelperDescriptor.AllowedChildren) }\":null," + - $"\"{ nameof(TagHelperDescriptor.RequiredParent) }\":null," + - $"\"{ nameof(TagHelperDescriptor.TagStructure) }\":0," + - $"\"{ nameof(TagHelperDescriptor.DesignTimeDescriptor) }\":null," + - $"\"{ nameof(TagHelperDescriptor.PropertyBag) }\":" + - "{\"key one\":\"value one\",\"key two\":\"value two\"}}"; - - // Act - var serializedDescriptor = JsonConvert.SerializeObject(descriptor); - - // Assert - Assert.Equal(expectedSerializedDescriptor, serializedDescriptor); - } - - [Fact] - public void TagHelperDescriptor_CanBeDeserialized() - { - // Arrange - var serializedDescriptor = - $"{{\"{nameof(TagHelperDescriptor.Prefix)}\":\"prefix:\"," + - $"\"{nameof(TagHelperDescriptor.TagName)}\":\"tag name\"," + - $"\"{nameof(TagHelperDescriptor.FullTagName)}\":\"prefix:tag name\"," + - $"\"{nameof(TagHelperDescriptor.TypeName)}\":\"type name\"," + - $"\"{nameof(TagHelperDescriptor.AssemblyName)}\":\"assembly name\"," + - $"\"{nameof(TagHelperDescriptor.Attributes)}\":[]," + - $"\"{ nameof(TagHelperDescriptor.RequiredAttributes) }\":" + - $"[{{\"{ nameof(TagHelperRequiredAttributeDescriptor.Name)}\":\"required attribute one\"," + - $"\"{ nameof(TagHelperRequiredAttributeDescriptor.NameComparison) }\":1," + - $"\"{ nameof(TagHelperRequiredAttributeDescriptor.Value) }\":null," + - $"\"{ nameof(TagHelperRequiredAttributeDescriptor.ValueComparison) }\":0}}," + - $"{{\"{ nameof(TagHelperRequiredAttributeDescriptor.Name)}\":\"required attribute two\"," + - $"\"{ nameof(TagHelperRequiredAttributeDescriptor.NameComparison) }\":0," + - $"\"{ nameof(TagHelperRequiredAttributeDescriptor.Value) }\":\"something\"," + - $"\"{ nameof(TagHelperRequiredAttributeDescriptor.ValueComparison) }\":2}}]," + - $"\"{ nameof(TagHelperDescriptor.AllowedChildren) }\":[\"allowed child one\",\"allowed child two\"]," + - $"\"{ nameof(TagHelperDescriptor.RequiredParent) }\":\"parent name\"," + - $"\"{nameof(TagHelperDescriptor.TagStructure)}\":2," + - $"\"{ nameof(TagHelperDescriptor.DesignTimeDescriptor) }\":{{" + - $"\"{ nameof(TagHelperDesignTimeDescriptor.Summary) }\":\"usage summary\"," + - $"\"{ nameof(TagHelperDesignTimeDescriptor.Remarks) }\":\"usage remarks\"," + - $"\"{ nameof(TagHelperDesignTimeDescriptor.OutputElementHint) }\":\"some-tag\"}}}}"; - var expectedDescriptor = new TagHelperDescriptor - { - Prefix = "prefix:", - TagName = "tag name", - TypeName = "type name", - AssemblyName = "assembly name", - RequiredAttributes = new[] - { - new TagHelperRequiredAttributeDescriptor - { - Name = "required attribute one", - NameComparison = TagHelperRequiredAttributeNameComparison.PrefixMatch - }, - new TagHelperRequiredAttributeDescriptor - { - Name = "required attribute two", - NameComparison = TagHelperRequiredAttributeNameComparison.FullMatch, - Value = "something", - ValueComparison = TagHelperRequiredAttributeValueComparison.PrefixMatch, - } - }, - AllowedChildren = new[] { "allowed child one", "allowed child two" }, - RequiredParent = "parent name", - DesignTimeDescriptor = new TagHelperDesignTimeDescriptor - { - Summary = "usage summary", - Remarks = "usage remarks", - OutputElementHint = "some-tag" - } - }; - - // Act - var descriptor = JsonConvert.DeserializeObject(serializedDescriptor); - - // Assert - Assert.NotNull(descriptor); - Assert.Equal(expectedDescriptor.Prefix, descriptor.Prefix, StringComparer.Ordinal); - Assert.Equal(expectedDescriptor.TagName, descriptor.TagName, StringComparer.Ordinal); - Assert.Equal(expectedDescriptor.FullTagName, descriptor.FullTagName, StringComparer.Ordinal); - Assert.Equal(expectedDescriptor.TypeName, descriptor.TypeName, StringComparer.Ordinal); - Assert.Equal(expectedDescriptor.AssemblyName, descriptor.AssemblyName, StringComparer.Ordinal); - Assert.Empty(descriptor.Attributes); - Assert.Equal(expectedDescriptor.RequiredAttributes, descriptor.RequiredAttributes, TagHelperRequiredAttributeDescriptorComparer.Default); - Assert.Equal( - expectedDescriptor.DesignTimeDescriptor, - descriptor.DesignTimeDescriptor, - TagHelperDesignTimeDescriptorComparer.Default); - Assert.Empty(descriptor.PropertyBag); - } - - [Fact] - public void TagHelperDescriptor_WithAttributes_CanBeDeserialized() - { - // Arrange - var serializedDescriptor = - $"{{\"{ nameof(TagHelperDescriptor.Prefix) }\":\"prefix:\"," + - $"\"{ nameof(TagHelperDescriptor.TagName) }\":\"tag name\"," + - $"\"{ nameof(TagHelperDescriptor.FullTagName) }\":\"prefix:tag name\"," + - $"\"{ nameof(TagHelperDescriptor.TypeName) }\":\"type name\"," + - $"\"{ nameof(TagHelperDescriptor.AssemblyName) }\":\"assembly name\"," + - $"\"{ nameof(TagHelperDescriptor.Attributes) }\":[" + - $"{{\"{ nameof(TagHelperAttributeDescriptor.IsIndexer) }\":false," + - $"\"{ nameof(TagHelperAttributeDescriptor.IsEnum) }\":true," + - $"\"{ nameof(TagHelperAttributeDescriptor.IsStringProperty) }\":false," + - $"\"{ nameof(TagHelperAttributeDescriptor.Name) }\":\"attribute one\"," + - $"\"{ nameof(TagHelperAttributeDescriptor.PropertyName) }\":\"property name\"," + - $"\"{ nameof(TagHelperAttributeDescriptor.TypeName) }\":\"property type name\"," + - $"\"{ nameof(TagHelperAttributeDescriptor.DesignTimeDescriptor) }\":null}}," + - $"{{\"{ nameof(TagHelperAttributeDescriptor.IsIndexer) }\":false," + - $"\"{ nameof(TagHelperAttributeDescriptor.IsEnum) }\":false," + - $"\"{ nameof(TagHelperAttributeDescriptor.IsStringProperty) }\":true," + - $"\"{ nameof(TagHelperAttributeDescriptor.Name) }\":\"attribute two\"," + - $"\"{ nameof(TagHelperAttributeDescriptor.PropertyName) }\":\"property name\"," + - $"\"{ nameof(TagHelperAttributeDescriptor.TypeName) }\":\"{ typeof(string).FullName }\"," + - $"\"{ nameof(TagHelperAttributeDescriptor.DesignTimeDescriptor) }\":null}}]," + - $"\"{ nameof(TagHelperDescriptor.RequiredAttributes) }\":[]," + - $"\"{ nameof(TagHelperDescriptor.AllowedChildren) }\":null," + - $"\"{ nameof(TagHelperDescriptor.RequiredParent) }\":null," + - $"\"{nameof(TagHelperDescriptor.TagStructure)}\":0," + - $"\"{ nameof(TagHelperDescriptor.DesignTimeDescriptor) }\":null}}"; - var expectedDescriptor = new TagHelperDescriptor - { - Prefix = "prefix:", - TagName = "tag name", - TypeName = "type name", - AssemblyName = "assembly name", - Attributes = new[] - { - new TagHelperAttributeDescriptor - { - Name = "attribute one", - PropertyName = "property name", - TypeName = "property type name", - IsEnum = true, - }, - new TagHelperAttributeDescriptor - { - Name = "attribute two", - PropertyName = "property name", - TypeName = typeof(string).FullName, - IsEnum = false, - IsStringProperty = true - }, - }, - AllowedChildren = new[] { "allowed child one", "allowed child two" } - }; - - // Act - var descriptor = JsonConvert.DeserializeObject(serializedDescriptor); - - // Assert - Assert.NotNull(descriptor); - Assert.Equal(expectedDescriptor.Prefix, descriptor.Prefix, StringComparer.Ordinal); - Assert.Equal(expectedDescriptor.TagName, descriptor.TagName, StringComparer.Ordinal); - Assert.Equal(expectedDescriptor.FullTagName, descriptor.FullTagName, StringComparer.Ordinal); - Assert.Equal(expectedDescriptor.TypeName, descriptor.TypeName, StringComparer.Ordinal); - Assert.Equal(expectedDescriptor.AssemblyName, descriptor.AssemblyName, StringComparer.Ordinal); - Assert.Equal(expectedDescriptor.Attributes, descriptor.Attributes, TagHelperAttributeDescriptorComparer.Default); - Assert.Empty(descriptor.RequiredAttributes); - Assert.Empty(descriptor.PropertyBag); - } - - [Fact] - public void TagHelperDescriptor_WithIndexerAttributes_CanBeDeserialized() - { - // Arrange - var serializedDescriptor = - $"{{\"{ nameof(TagHelperDescriptor.Prefix) }\":\"prefix:\"," + - $"\"{ nameof(TagHelperDescriptor.TagName) }\":\"tag name\"," + - $"\"{ nameof(TagHelperDescriptor.FullTagName) }\":\"prefix:tag name\"," + - $"\"{ nameof(TagHelperDescriptor.TypeName) }\":\"type name\"," + - $"\"{ nameof(TagHelperDescriptor.AssemblyName) }\":\"assembly name\"," + - $"\"{ nameof(TagHelperDescriptor.Attributes) }\":[" + - $"{{\"{ nameof(TagHelperAttributeDescriptor.IsIndexer) }\":true," + - $"\"{ nameof(TagHelperAttributeDescriptor.IsEnum) }\":true," + - $"\"{ nameof(TagHelperAttributeDescriptor.IsStringProperty) }\":false," + - $"\"{ nameof(TagHelperAttributeDescriptor.Name) }\":\"attribute one\"," + - $"\"{ nameof(TagHelperAttributeDescriptor.PropertyName) }\":\"property name\"," + - $"\"{ nameof(TagHelperAttributeDescriptor.TypeName) }\":\"property type name\"," + - $"\"{ nameof(TagHelperAttributeDescriptor.DesignTimeDescriptor) }\":null}}," + - $"{{\"{ nameof(TagHelperAttributeDescriptor.IsIndexer) }\":true," + - $"\"{ nameof(TagHelperAttributeDescriptor.IsEnum) }\":false," + - $"\"{ nameof(TagHelperAttributeDescriptor.IsStringProperty) }\":true," + - $"\"{ nameof(TagHelperAttributeDescriptor.Name) }\":\"attribute two\"," + - $"\"{ nameof(TagHelperAttributeDescriptor.PropertyName) }\":\"property name\"," + - $"\"{ nameof(TagHelperAttributeDescriptor.TypeName) }\":\"{ typeof(string).FullName }\"," + - $"\"{ nameof(TagHelperAttributeDescriptor.DesignTimeDescriptor) }\":null}}]," + - $"\"{ nameof(TagHelperDescriptor.RequiredAttributes) }\":[]," + - $"\"{ nameof(TagHelperDescriptor.AllowedChildren) }\":null," + - $"\"{ nameof(TagHelperDescriptor.RequiredParent) }\":null," + - $"\"{nameof(TagHelperDescriptor.TagStructure)}\":1," + - $"\"{ nameof(TagHelperDescriptor.DesignTimeDescriptor) }\":null," + - $"\"{ nameof(TagHelperDescriptor.PropertyBag) }\":{{}}}}"; - - var expectedDescriptor = new TagHelperDescriptor - { - Prefix = "prefix:", - TagName = "tag name", - TypeName = "type name", - AssemblyName = "assembly name", - Attributes = new[] - { - new TagHelperAttributeDescriptor - { - Name = "attribute one", - PropertyName = "property name", - TypeName = "property type name", - IsIndexer = true, - IsEnum = true, - }, - new TagHelperAttributeDescriptor - { - Name = "attribute two", - PropertyName = "property name", - TypeName = typeof(string).FullName, - IsIndexer = true, - IsEnum = false, - IsStringProperty = true - } - }, - TagStructure = TagStructure.NormalOrSelfClosing - }; - - // Act - var descriptor = JsonConvert.DeserializeObject(serializedDescriptor); - - // Assert - Assert.NotNull(descriptor); - Assert.Equal(expectedDescriptor.Prefix, descriptor.Prefix, StringComparer.Ordinal); - Assert.Equal(expectedDescriptor.TagName, descriptor.TagName, StringComparer.Ordinal); - Assert.Equal(expectedDescriptor.FullTagName, descriptor.FullTagName, StringComparer.Ordinal); - Assert.Equal(expectedDescriptor.TypeName, descriptor.TypeName, StringComparer.Ordinal); - Assert.Equal(expectedDescriptor.AssemblyName, descriptor.AssemblyName, StringComparer.Ordinal); - Assert.Equal(expectedDescriptor.Attributes, descriptor.Attributes, TagHelperAttributeDescriptorComparer.Default); - Assert.Empty(descriptor.RequiredAttributes); - Assert.Empty(descriptor.PropertyBag); - } - - [Fact] - public void TagHelperDescriptor_WithPropertyBagElements_CanBeDeserialized() - { - // Arrange - var serializedDescriptor = - $"{{\"{nameof(TagHelperDescriptor.Prefix)}\":\"prefix:\"," + - $"\"{nameof(TagHelperDescriptor.TagName)}\":\"tag name\"," + - $"\"{nameof(TagHelperDescriptor.TypeName)}\":\"type name\"," + - $"\"{nameof(TagHelperDescriptor.AssemblyName)}\":\"assembly name\"," + - $"\"{ nameof(TagHelperDescriptor.PropertyBag) }\":" + - "{\"key one\":\"value one\",\"key two\":\"value two\"}}"; - var expectedDescriptor = new TagHelperDescriptor - { - Prefix = "prefix:", - TagName = "tag name", - TypeName = "type name", - AssemblyName = "assembly name" - }; - - expectedDescriptor.PropertyBag.Add("key one", "value one"); - expectedDescriptor.PropertyBag.Add("key two", "value two"); - - // Act - var descriptor = JsonConvert.DeserializeObject(serializedDescriptor); - - // Assert - Assert.NotNull(descriptor); - Assert.Equal(expectedDescriptor.Prefix, descriptor.Prefix, StringComparer.Ordinal); - Assert.Equal(expectedDescriptor.TagName, descriptor.TagName, StringComparer.Ordinal); - Assert.Equal(expectedDescriptor.TypeName, descriptor.TypeName, StringComparer.Ordinal); - Assert.Equal(expectedDescriptor.AssemblyName, descriptor.AssemblyName, StringComparer.Ordinal); - Assert.Empty(descriptor.Attributes); - Assert.Empty(descriptor.RequiredAttributes); - Assert.Equal(expectedDescriptor.PropertyBag["key one"], descriptor.PropertyBag["key one"]); - Assert.Equal(expectedDescriptor.PropertyBag["key two"], descriptor.PropertyBag["key two"]); - } - } -} diff --git a/test/Microsoft.AspNetCore.Razor.Evolution.Test/TagHelperRequiredAttributeDescriptorTest.cs b/test/Microsoft.AspNetCore.Razor.Evolution.Test/TagHelperRequiredAttributeDescriptorTest.cs index 921fe662f2..ebda335c4b 100644 --- a/test/Microsoft.AspNetCore.Razor.Evolution.Test/TagHelperRequiredAttributeDescriptorTest.cs +++ b/test/Microsoft.AspNetCore.Razor.Evolution.Test/TagHelperRequiredAttributeDescriptorTest.cs @@ -9,144 +9,127 @@ namespace Microsoft.AspNetCore.Razor.Evolution get { // requiredAttributeDescriptor, attributeName, attributeValue, expectedResult - return new TheoryData + return new TheoryData { { - new TagHelperRequiredAttributeDescriptor - { - Name = "key" - }, + RequiredAttributeDescriptorBuilder.Create().Name("key").Build(), "KeY", "value", true }, { - new TagHelperRequiredAttributeDescriptor - { - Name = "key" - }, + RequiredAttributeDescriptorBuilder.Create().Name("key").Build(), "keys", "value", false }, { - new TagHelperRequiredAttributeDescriptor - { - Name = "route-", - NameComparison = TagHelperRequiredAttributeNameComparison.PrefixMatch, - }, + RequiredAttributeDescriptorBuilder.Create() + .Name("route-") + .NameComparisonMode(RequiredAttributeDescriptor.NameComparisonMode.PrefixMatch) + .Build(), "ROUTE-area", "manage", true }, { - new TagHelperRequiredAttributeDescriptor - { - Name = "route-", - NameComparison = TagHelperRequiredAttributeNameComparison.PrefixMatch, - }, + RequiredAttributeDescriptorBuilder.Create() + .Name("route-") + .NameComparisonMode(RequiredAttributeDescriptor.NameComparisonMode.PrefixMatch) + .Build(), "routearea", "manage", false }, { - new TagHelperRequiredAttributeDescriptor - { - Name = "route-", - NameComparison = TagHelperRequiredAttributeNameComparison.PrefixMatch, - }, + RequiredAttributeDescriptorBuilder.Create() + .Name("route-") + .NameComparisonMode(RequiredAttributeDescriptor.NameComparisonMode.PrefixMatch) + .Build(), "route-", "manage", false }, { - new TagHelperRequiredAttributeDescriptor - { - Name = "key", - NameComparison = TagHelperRequiredAttributeNameComparison.FullMatch, - }, + RequiredAttributeDescriptorBuilder.Create() + .Name("key") + .NameComparisonMode(RequiredAttributeDescriptor.NameComparisonMode.FullMatch) + .Build(), "KeY", "value", true }, { - new TagHelperRequiredAttributeDescriptor - { - Name = "key", - NameComparison = TagHelperRequiredAttributeNameComparison.FullMatch, - }, + RequiredAttributeDescriptorBuilder.Create() + .Name("key") + .NameComparisonMode(RequiredAttributeDescriptor.NameComparisonMode.FullMatch) + .Build(), "keys", "value", false }, { - new TagHelperRequiredAttributeDescriptor - { - Name = "key", - NameComparison = TagHelperRequiredAttributeNameComparison.FullMatch, - Value = "value", - ValueComparison = TagHelperRequiredAttributeValueComparison.FullMatch, - }, + RequiredAttributeDescriptorBuilder.Create() + .Name("key") + .NameComparisonMode(RequiredAttributeDescriptor.NameComparisonMode.FullMatch) + .Value("value") + .ValueComparisonMode(RequiredAttributeDescriptor.ValueComparisonMode.FullMatch) + .Build(), "key", "value", true }, { - new TagHelperRequiredAttributeDescriptor - { - Name = "key", - NameComparison = TagHelperRequiredAttributeNameComparison.FullMatch, - Value = "value", - ValueComparison = TagHelperRequiredAttributeValueComparison.FullMatch, - }, + RequiredAttributeDescriptorBuilder.Create() + .Name("key") + .NameComparisonMode(RequiredAttributeDescriptor.NameComparisonMode.FullMatch) + .Value("value") + .ValueComparisonMode(RequiredAttributeDescriptor.ValueComparisonMode.FullMatch) + .Build(), "key", "Value", false }, { - new TagHelperRequiredAttributeDescriptor - { - Name = "class", - NameComparison = TagHelperRequiredAttributeNameComparison.FullMatch, - Value = "btn", - ValueComparison = TagHelperRequiredAttributeValueComparison.PrefixMatch, - }, + RequiredAttributeDescriptorBuilder.Create() + .Name("class") + .NameComparisonMode(RequiredAttributeDescriptor.NameComparisonMode.FullMatch) + .Value("btn") + .ValueComparisonMode(RequiredAttributeDescriptor.ValueComparisonMode.PrefixMatch) + .Build(), "class", "btn btn-success", true }, { - new TagHelperRequiredAttributeDescriptor - { - Name = "class", - NameComparison = TagHelperRequiredAttributeNameComparison.FullMatch, - Value = "btn", - ValueComparison = TagHelperRequiredAttributeValueComparison.PrefixMatch, - }, + RequiredAttributeDescriptorBuilder.Create() + .Name("class") + .NameComparisonMode(RequiredAttributeDescriptor.NameComparisonMode.FullMatch) + .Value("btn") + .ValueComparisonMode(RequiredAttributeDescriptor.ValueComparisonMode.PrefixMatch) + .Build(), "class", "BTN btn-success", false }, { - new TagHelperRequiredAttributeDescriptor - { - Name = "href", - NameComparison = TagHelperRequiredAttributeNameComparison.FullMatch, - Value = "#navigate", - ValueComparison = TagHelperRequiredAttributeValueComparison.SuffixMatch, - }, + RequiredAttributeDescriptorBuilder.Create() + .Name("href") + .NameComparisonMode(RequiredAttributeDescriptor.NameComparisonMode.FullMatch) + .Value("#navigate") + .ValueComparisonMode(RequiredAttributeDescriptor.ValueComparisonMode.SuffixMatch) + .Build(), "href", "/home/index#navigate", true }, { - new TagHelperRequiredAttributeDescriptor - { - Name = "href", - NameComparison = TagHelperRequiredAttributeNameComparison.FullMatch, - Value = "#navigate", - ValueComparison = TagHelperRequiredAttributeValueComparison.SuffixMatch, - }, + RequiredAttributeDescriptorBuilder.Create() + .Name("href") + .NameComparisonMode(RequiredAttributeDescriptor.NameComparisonMode.FullMatch) + .Value("#navigate") + .ValueComparisonMode(RequiredAttributeDescriptor.ValueComparisonMode.SuffixMatch) + .Build(), "href", "/home/index#NAVigate", false @@ -164,7 +147,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution bool expectedResult) { // Act - var result = ((TagHelperRequiredAttributeDescriptor)requiredAttributeDescriptor).IsMatch(attributeName, attributeValue); + var result = ((RequiredAttributeDescriptor)requiredAttributeDescriptor).IsMatch(attributeName, attributeValue); // Assert Assert.Equal(expectedResult, result); diff --git a/test/Microsoft.CodeAnalysis.Razor.Test/Comparers/CaseSensitiveTagHelperDescriptorComparer.cs b/test/Microsoft.CodeAnalysis.Razor.Test/Comparers/CaseSensitiveTagHelperDescriptorComparer.cs deleted file mode 100644 index c63d50c266..0000000000 --- a/test/Microsoft.CodeAnalysis.Razor.Test/Comparers/CaseSensitiveTagHelperDescriptorComparer.cs +++ /dev/null @@ -1,97 +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; -using System.Linq; -using Microsoft.AspNetCore.Razor.Evolution; -using Microsoft.AspNetCore.Razor.Evolution.Legacy; -using Microsoft.Extensions.Internal; -using Xunit; - -namespace Microsoft.CodeAnalysis.Razor.Workspaces.Test.Comparers -{ - internal class CaseSensitiveTagHelperDescriptorComparer : TagHelperDescriptorComparer - { - public new static readonly CaseSensitiveTagHelperDescriptorComparer Default = - new CaseSensitiveTagHelperDescriptorComparer(); - - private CaseSensitiveTagHelperDescriptorComparer() - : base() - { - } - - public override bool Equals(TagHelperDescriptor descriptorX, TagHelperDescriptor descriptorY) - { - if (descriptorX == descriptorY) - { - return true; - } - - Assert.True(base.Equals(descriptorX, descriptorY)); - - // Normal comparer doesn't care about the case, required attribute order, allowed children order, - // attributes or prefixes. In tests we do. - Assert.Equal(descriptorX.TagName, descriptorY.TagName, StringComparer.Ordinal); - Assert.Equal(descriptorX.Prefix, descriptorY.Prefix, StringComparer.Ordinal); - Assert.Equal( - descriptorX.RequiredAttributes, - descriptorY.RequiredAttributes, - CaseSensitiveTagHelperRequiredAttributeDescriptorComparer.Default); - Assert.Equal(descriptorX.RequiredParent, descriptorY.RequiredParent, StringComparer.Ordinal); - - if (descriptorX.AllowedChildren != descriptorY.AllowedChildren) - { - Assert.Equal(descriptorX.AllowedChildren, descriptorY.AllowedChildren, StringComparer.Ordinal); - } - - Assert.Equal( - descriptorX.Attributes, - descriptorY.Attributes, - TagHelperAttributeDescriptorComparer.Default); - Assert.Equal( - descriptorX.DesignTimeDescriptor, - descriptorY.DesignTimeDescriptor, - TagHelperDesignTimeDescriptorComparer.Default); - - return true; - } - - public override int GetHashCode(TagHelperDescriptor descriptor) - { - var hashCodeCombiner = HashCodeCombiner.Start(); - hashCodeCombiner.Add(base.GetHashCode(descriptor)); - hashCodeCombiner.Add(descriptor.TagName, StringComparer.Ordinal); - hashCodeCombiner.Add(descriptor.Prefix, StringComparer.Ordinal); - - if (descriptor.DesignTimeDescriptor != null) - { - hashCodeCombiner.Add( - TagHelperDesignTimeDescriptorComparer.Default.GetHashCode(descriptor.DesignTimeDescriptor)); - } - - foreach (var requiredAttribute in descriptor.RequiredAttributes.OrderBy(attribute => attribute.Name)) - { - hashCodeCombiner.Add( - CaseSensitiveTagHelperRequiredAttributeDescriptorComparer.Default.GetHashCode(requiredAttribute)); - } - - if (descriptor.AllowedChildren != null) - { - foreach (var child in descriptor.AllowedChildren.OrderBy(child => child)) - { - hashCodeCombiner.Add(child, StringComparer.Ordinal); - } - } - - var orderedAttributeHashCodes = descriptor.Attributes - .Select(attribute => TagHelperAttributeDescriptorComparer.Default.GetHashCode(attribute)) - .OrderBy(hashcode => hashcode); - foreach (var attributeHashCode in orderedAttributeHashCodes) - { - hashCodeCombiner.Add(attributeHashCode); - } - - return hashCodeCombiner.CombinedHash; - } - } -} \ No newline at end of file diff --git a/test/Microsoft.CodeAnalysis.Razor.Test/Comparers/CaseSensitiveTagHelperRequiredAttributeDescriptorComparer.cs b/test/Microsoft.CodeAnalysis.Razor.Test/Comparers/CaseSensitiveTagHelperRequiredAttributeDescriptorComparer.cs deleted file mode 100644 index f5c521deed..0000000000 --- a/test/Microsoft.CodeAnalysis.Razor.Test/Comparers/CaseSensitiveTagHelperRequiredAttributeDescriptorComparer.cs +++ /dev/null @@ -1,44 +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; -using Microsoft.AspNetCore.Razor.Evolution; -using Microsoft.AspNetCore.Razor.Evolution.Legacy; -using Microsoft.Extensions.Internal; -using Xunit; - -namespace Microsoft.CodeAnalysis.Razor.Workspaces.Test.Comparers -{ - internal class CaseSensitiveTagHelperRequiredAttributeDescriptorComparer : TagHelperRequiredAttributeDescriptorComparer - { - public new static readonly CaseSensitiveTagHelperRequiredAttributeDescriptorComparer Default = - new CaseSensitiveTagHelperRequiredAttributeDescriptorComparer(); - - private CaseSensitiveTagHelperRequiredAttributeDescriptorComparer() - : base() - { - } - - public override bool Equals(TagHelperRequiredAttributeDescriptor descriptorX, TagHelperRequiredAttributeDescriptor descriptorY) - { - if (descriptorX == descriptorY) - { - return true; - } - - Assert.True(base.Equals(descriptorX, descriptorY)); - Assert.Equal(descriptorX.Name, descriptorY.Name, StringComparer.Ordinal); - - return true; - } - - public override int GetHashCode(TagHelperRequiredAttributeDescriptor descriptor) - { - var hashCodeCombiner = HashCodeCombiner.Start(); - hashCodeCombiner.Add(base.GetHashCode(descriptor)); - hashCodeCombiner.Add(descriptor.Name, StringComparer.Ordinal); - - return hashCodeCombiner.CombinedHash; - } - } -} \ No newline at end of file diff --git a/test/Microsoft.CodeAnalysis.Razor.Test/Comparers/TagHelperAttributeDescriptorComparer.cs b/test/Microsoft.CodeAnalysis.Razor.Test/Comparers/TagHelperAttributeDescriptorComparer.cs deleted file mode 100644 index d913abba3e..0000000000 --- a/test/Microsoft.CodeAnalysis.Razor.Test/Comparers/TagHelperAttributeDescriptorComparer.cs +++ /dev/null @@ -1,57 +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; -using System.Collections.Generic; -using Microsoft.AspNetCore.Razor.Evolution; -using Microsoft.Extensions.Internal; -using Xunit; - -namespace Microsoft.CodeAnalysis.Razor.Workspaces.Test.Comparers -{ - internal class TagHelperAttributeDescriptorComparer : IEqualityComparer - { - public static readonly TagHelperAttributeDescriptorComparer Default = - new TagHelperAttributeDescriptorComparer(); - - private TagHelperAttributeDescriptorComparer() - { - } - - public bool Equals(TagHelperAttributeDescriptor descriptorX, TagHelperAttributeDescriptor descriptorY) - { - if (descriptorX == descriptorY) - { - return true; - } - - Assert.NotNull(descriptorX); - Assert.NotNull(descriptorY); - Assert.Equal(descriptorX.IsIndexer, descriptorY.IsIndexer); - Assert.Equal(descriptorX.Name, descriptorY.Name, StringComparer.Ordinal); - Assert.Equal(descriptorX.PropertyName, descriptorY.PropertyName, StringComparer.Ordinal); - Assert.Equal(descriptorX.TypeName, descriptorY.TypeName, StringComparer.Ordinal); - Assert.Equal(descriptorX.IsEnum, descriptorY.IsEnum); - Assert.Equal(descriptorX.IsStringProperty, descriptorY.IsStringProperty); - - return TagHelperAttributeDesignTimeDescriptorComparer.Default.Equals( - descriptorX.DesignTimeDescriptor, - descriptorY.DesignTimeDescriptor); - } - - public int GetHashCode(TagHelperAttributeDescriptor descriptor) - { - var hashCodeCombiner = HashCodeCombiner.Start(); - hashCodeCombiner.Add(descriptor.IsIndexer); - hashCodeCombiner.Add(descriptor.Name, StringComparer.Ordinal); - hashCodeCombiner.Add(descriptor.PropertyName, StringComparer.Ordinal); - hashCodeCombiner.Add(descriptor.TypeName, StringComparer.Ordinal); - hashCodeCombiner.Add(descriptor.IsEnum); - hashCodeCombiner.Add(descriptor.IsStringProperty); - hashCodeCombiner.Add(TagHelperAttributeDesignTimeDescriptorComparer.Default.GetHashCode( - descriptor.DesignTimeDescriptor)); - - return hashCodeCombiner; - } - } -} \ No newline at end of file diff --git a/test/Microsoft.CodeAnalysis.Razor.Test/Comparers/TagHelperAttributeDesignTimeDescriptorComparer.cs b/test/Microsoft.CodeAnalysis.Razor.Test/Comparers/TagHelperAttributeDesignTimeDescriptorComparer.cs deleted file mode 100644 index a463531891..0000000000 --- a/test/Microsoft.CodeAnalysis.Razor.Test/Comparers/TagHelperAttributeDesignTimeDescriptorComparer.cs +++ /dev/null @@ -1,48 +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; -using System.Collections.Generic; -using Microsoft.AspNetCore.Razor.Evolution; -using Microsoft.Extensions.Internal; -using Xunit; - -namespace Microsoft.CodeAnalysis.Razor.Workspaces.Test.Comparers -{ - internal class TagHelperAttributeDesignTimeDescriptorComparer : - IEqualityComparer - { - public static readonly TagHelperAttributeDesignTimeDescriptorComparer Default = - new TagHelperAttributeDesignTimeDescriptorComparer(); - - private TagHelperAttributeDesignTimeDescriptorComparer() - { - } - - public bool Equals( - TagHelperAttributeDesignTimeDescriptor descriptorX, - TagHelperAttributeDesignTimeDescriptor descriptorY) - { - if (descriptorX == descriptorY) - { - return true; - } - - Assert.NotNull(descriptorX); - Assert.NotNull(descriptorY); - Assert.Equal(descriptorX.Summary, descriptorY.Summary, StringComparer.Ordinal); - Assert.Equal(descriptorX.Remarks, descriptorY.Remarks, StringComparer.Ordinal); - - return true; - } - - public int GetHashCode(TagHelperAttributeDesignTimeDescriptor descriptor) - { - var hashCodeCombiner = HashCodeCombiner.Start(); - hashCodeCombiner.Add(descriptor.Summary, StringComparer.Ordinal); - hashCodeCombiner.Add(descriptor.Remarks, StringComparer.Ordinal); - - return hashCodeCombiner; - } - } -} diff --git a/test/Microsoft.CodeAnalysis.Razor.Test/Comparers/TagHelperDesignTimeDescriptorComparer.cs b/test/Microsoft.CodeAnalysis.Razor.Test/Comparers/TagHelperDesignTimeDescriptorComparer.cs deleted file mode 100644 index 52a7ef7805..0000000000 --- a/test/Microsoft.CodeAnalysis.Razor.Test/Comparers/TagHelperDesignTimeDescriptorComparer.cs +++ /dev/null @@ -1,48 +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; -using System.Collections.Generic; -using Microsoft.AspNetCore.Razor.Evolution; -using Microsoft.Extensions.Internal; -using Xunit; - -namespace Microsoft.CodeAnalysis.Razor.Workspaces.Test.Comparers -{ - internal class TagHelperDesignTimeDescriptorComparer : IEqualityComparer - { - public static readonly TagHelperDesignTimeDescriptorComparer Default = - new TagHelperDesignTimeDescriptorComparer(); - - private TagHelperDesignTimeDescriptorComparer() - { - } - - public bool Equals(TagHelperDesignTimeDescriptor descriptorX, TagHelperDesignTimeDescriptor descriptorY) - { - if (descriptorX == descriptorY) - { - return true; - } - - Assert.NotNull(descriptorX); - Assert.NotNull(descriptorY); - Assert.Equal(descriptorX.Summary, descriptorY.Summary, StringComparer.Ordinal); - Assert.Equal(descriptorX.Remarks, descriptorY.Remarks, StringComparer.Ordinal); - Assert.Equal(descriptorX.OutputElementHint, descriptorY.OutputElementHint, StringComparer.Ordinal); - - return true; - } - - public int GetHashCode(TagHelperDesignTimeDescriptor descriptor) - { - var hashCodeCombiner = HashCodeCombiner.Start(); - - hashCodeCombiner.Add(descriptor.Summary, StringComparer.Ordinal); - hashCodeCombiner.Add(descriptor.Remarks, StringComparer.Ordinal); - hashCodeCombiner.Add(descriptor.OutputElementHint, StringComparer.Ordinal); - - return hashCodeCombiner; - } - } -} diff --git a/test/Microsoft.CodeAnalysis.Razor.Test/DefaultTagHelperDescriptorFactoryTest.cs b/test/Microsoft.CodeAnalysis.Razor.Test/DefaultTagHelperDescriptorFactoryTest.cs index 9d12fe88d2..ee5260cf68 100644 --- a/test/Microsoft.CodeAnalysis.Razor.Test/DefaultTagHelperDescriptorFactoryTest.cs +++ b/test/Microsoft.CodeAnalysis.Razor.Test/DefaultTagHelperDescriptorFactoryTest.cs @@ -1,16 +1,15 @@ // 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.Evolution; using Microsoft.AspNetCore.Razor.Evolution.Legacy; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Razor.Workspaces.Test; -using Microsoft.CodeAnalysis.Razor.Workspaces.Test.Comparers; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; using Xunit; namespace Microsoft.CodeAnalysis.Razor.Workspaces @@ -28,124 +27,268 @@ namespace Microsoft.CodeAnalysis.Razor.Workspaces { get { - Func error = (message) => new RazorError(message, SourceLocation.Zero, 0); - - return new TheoryData + return new TheoryData { - { "name,", error(Resources.FormatTagHelperDescriptorFactory_CouldNotFindMatchingEndBrace("name,")) }, - { " ", error(Resources.FormatHtmlTargetElementAttribute_NameCannotBeNullOrWhitespace("Attribute")) }, - { "n@me", error(Resources.FormatHtmlTargetElementAttribute_InvalidName("attribute", "n@me", '@')) }, - { "name extra", error(Resources.FormatTagHelperDescriptorFactory_InvalidRequiredAttributeCharacter('e', "name extra")) }, - { "[[ ", error(Resources.FormatTagHelperDescriptorFactory_CouldNotFindMatchingEndBrace("[[ ")) }, - { "[ ", error(Resources.FormatTagHelperDescriptorFactory_CouldNotFindMatchingEndBrace("[ ")) }, + { + "name,", + new[] + { + RequiredAttributeDescriptorBuilder.Create() + .Name("name") + .AddDiagnostic(RazorDiagnosticFactory.CreateTagHelper_CouldNotFindMatchingEndBrace("name,")) + .Build(), + } + }, + { + " ", + new[] + { + RequiredAttributeDescriptorBuilder.Create() + .Name(string.Empty) + .AddDiagnostic(AspNetCore.Razor.Evolution.RazorDiagnosticFactory.CreateTagHelper_InvalidTargetedAttributeNameNullOrWhitespace()) + .Build(), + } + }, + { + "n@me", + new[] + { + RequiredAttributeDescriptorBuilder.Create() + .Name("n@me") + .AddDiagnostic(AspNetCore.Razor.Evolution.RazorDiagnosticFactory.CreateTagHelper_InvalidTargetedAttributeName("n@me", '@')) + .Build(), + } + }, + { + "name extra", + new[] + { + RequiredAttributeDescriptorBuilder.Create() + .Name("name") + .AddDiagnostic(RazorDiagnosticFactory.CreateTagHelper_InvalidRequiredAttributeCharacter('e', "name extra")) + .Build(), + } + }, + { + "[[ ", + new[] + { + RequiredAttributeDescriptorBuilder.Create() + .Name("[") + .AddDiagnostic(RazorDiagnosticFactory.CreateTagHelper_CouldNotFindMatchingEndBrace("[[ ")) + .Build(), + } + }, + { + "[ ", + new[] + { + RequiredAttributeDescriptorBuilder.Create() + .Name("") + .AddDiagnostic(RazorDiagnosticFactory.CreateTagHelper_CouldNotFindMatchingEndBrace("[ ")) + .Build(), + } + }, { "[name='unended]", - error(Resources.FormatTagHelperDescriptorFactory_InvalidRequiredAttributeMismatchedQuotes("[name='unended]", '\'')) + new[] + { + RequiredAttributeDescriptorBuilder.Create() + .Name("name") + .ValueComparisonMode(RequiredAttributeDescriptor.ValueComparisonMode.FullMatch) + .AddDiagnostic(RazorDiagnosticFactory.CreateTagHelper_InvalidRequiredAttributeMismatchedQuotes('\'', "[name='unended]")) + .Build(), + } }, { "[name='unended", - error(Resources.FormatTagHelperDescriptorFactory_InvalidRequiredAttributeMismatchedQuotes("[name='unended", '\'')) + new[] + { + RequiredAttributeDescriptorBuilder.Create() + .Name("name") + .ValueComparisonMode(RequiredAttributeDescriptor.ValueComparisonMode.FullMatch) + .AddDiagnostic(RazorDiagnosticFactory.CreateTagHelper_InvalidRequiredAttributeMismatchedQuotes('\'', "[name='unended")) + .Build(), + } + }, + { + "[name", + new[] + { + RequiredAttributeDescriptorBuilder.Create() + .Name("name") + .AddDiagnostic(RazorDiagnosticFactory.CreateTagHelper_CouldNotFindMatchingEndBrace("[name")) + .Build(), + } + }, + { + "[ ]", + new[] + { + RequiredAttributeDescriptorBuilder.Create() + .Name(string.Empty) + .AddDiagnostic(AspNetCore.Razor.Evolution.RazorDiagnosticFactory.CreateTagHelper_InvalidTargetedAttributeNameNullOrWhitespace()) + .Build(), + } + }, + { + "[n@me]", + new[] + { + RequiredAttributeDescriptorBuilder.Create() + .Name("n@me") + .AddDiagnostic(AspNetCore.Razor.Evolution.RazorDiagnosticFactory.CreateTagHelper_InvalidTargetedAttributeName("n@me", '@')) + .Build(), + } + }, + { + "[name@]", + new[] + { + RequiredAttributeDescriptorBuilder.Create() + .Name("name@") + .AddDiagnostic(AspNetCore.Razor.Evolution.RazorDiagnosticFactory.CreateTagHelper_InvalidTargetedAttributeName("name@", '@')) + .Build(), + } + }, + { + "[name^]", + new[] + { + RequiredAttributeDescriptorBuilder.Create() + .Name("name") + .AddDiagnostic(RazorDiagnosticFactory.CreateTagHelper_PartialRequiredAttributeOperator('^', "[name^]")) + .Build(), + } + }, + { + "[name='value'", + new[] + { + RequiredAttributeDescriptorBuilder.Create() + .Name("name") + .Value("value") + .ValueComparisonMode(RequiredAttributeDescriptor.ValueComparisonMode.FullMatch) + .AddDiagnostic(RazorDiagnosticFactory.CreateTagHelper_CouldNotFindMatchingEndBrace("[name='value'")) + .Build(), + } + }, + { + "[name ", + new[] + { + RequiredAttributeDescriptorBuilder.Create() + .Name("name") + .AddDiagnostic(RazorDiagnosticFactory.CreateTagHelper_CouldNotFindMatchingEndBrace("[name ")) + .Build(), + } + }, + { + "[name extra]", + new[] + { + RequiredAttributeDescriptorBuilder.Create() + .Name("name") + .AddDiagnostic(RazorDiagnosticFactory.CreateTagHelper_InvalidRequiredAttributeOperator('e', "[name extra]")) + .Build(), + } + }, + { + "[name=value ", + new[] + { + RequiredAttributeDescriptorBuilder.Create() + .Name("name") + .Value("value") + .ValueComparisonMode(RequiredAttributeDescriptor.ValueComparisonMode.FullMatch) + .AddDiagnostic(RazorDiagnosticFactory.CreateTagHelper_CouldNotFindMatchingEndBrace("[name=value ")) + .Build(), + } }, - { "[name", error(Resources.FormatTagHelperDescriptorFactory_CouldNotFindMatchingEndBrace("[name")) }, - { "[ ]", error(Resources.FormatHtmlTargetElementAttribute_NameCannotBeNullOrWhitespace("Attribute")) }, - { "[n@me]", error(Resources.FormatHtmlTargetElementAttribute_InvalidName("attribute", "n@me", '@')) }, - { "[name@]", error(Resources.FormatHtmlTargetElementAttribute_InvalidName("attribute", "name@", '@')) }, - { "[name^]", error(Resources.FormatTagHelperDescriptorFactory_PartialRequiredAttributeOperator("[name^]", '^')) }, - { "[name='value'", error(Resources.FormatTagHelperDescriptorFactory_CouldNotFindMatchingEndBrace("[name='value'")) }, - { "[name ", error(Resources.FormatTagHelperDescriptorFactory_CouldNotFindMatchingEndBrace("[name ")) }, - { "[name extra]", error(Resources.FormatTagHelperDescriptorFactory_InvalidRequiredAttributeOperator('e', "[name extra]")) }, - { "[name=value ", error(Resources.FormatTagHelperDescriptorFactory_CouldNotFindMatchingEndBrace("[name=value ")) }, }; } } [Theory] [MemberData(nameof(RequiredAttributeParserErrorData))] - public void RequiredAttributeParser_ParsesRequiredAttributesAndLogsErrorCorrectly( + public void RequiredAttributeParser_ParsesRequiredAttributesAndLogsDiagnosticsCorrectly( string requiredAttributes, - object expectedError) + IEnumerable expectedDescriptors) { // Arrange - var parser = new DefaultTagHelperDescriptorFactory.RequiredAttributeParser(requiredAttributes); - var errorSink = new ErrorSink(); - IEnumerable descriptors; + var ruleBuilder = TagMatchingRuleBuilder.Create(); // Act - var parsedCorrectly = parser.TryParse(errorSink, out descriptors); + RequiredAttributeParser.AddRequiredAttributes(requiredAttributes, ruleBuilder); // Assert - Assert.False(parsedCorrectly); - Assert.Null(descriptors); - var error = Assert.Single(errorSink.Errors); - Assert.Equal((RazorError)expectedError, error); + var descriptors = ruleBuilder.Build().Attributes; + Assert.Equal(expectedDescriptors, descriptors, RequiredAttributeDescriptorComparer.CaseSensitive); } public static TheoryData RequiredAttributeParserData { get { - Func plain = - (name, nameComparison) => new TagHelperRequiredAttributeDescriptor - { - Name = name, - NameComparison = nameComparison - }; - Func css = - (name, value, valueComparison) => new TagHelperRequiredAttributeDescriptor - { - Name = name, - NameComparison = TagHelperRequiredAttributeNameComparison.FullMatch, - Value = value, - ValueComparison = valueComparison, - }; + Func plain = + (name, nameComparison) => RequiredAttributeDescriptorBuilder.Create() + .Name(name) + .NameComparisonMode(nameComparison) + .Build(); + Func css = + (name, value, valueComparison) => RequiredAttributeDescriptorBuilder.Create() + .Name(name) + .Value(value) + .ValueComparisonMode(valueComparison) + .Build(); - return new TheoryData> + return new TheoryData> { - { null, Enumerable.Empty() }, - { string.Empty, Enumerable.Empty() }, - { "name", new[] { plain("name", TagHelperRequiredAttributeNameComparison.FullMatch) } }, - { "name-*", new[] { plain("name-", TagHelperRequiredAttributeNameComparison.PrefixMatch) } }, - { " name-* ", new[] { plain("name-", TagHelperRequiredAttributeNameComparison.PrefixMatch) } }, + { null, Enumerable.Empty() }, + { string.Empty, Enumerable.Empty() }, + { "name", new[] { plain("name", RequiredAttributeDescriptor.NameComparisonMode.FullMatch) } }, + { "name-*", new[] { plain("name-", RequiredAttributeDescriptor.NameComparisonMode.PrefixMatch) } }, + { " name-* ", new[] { plain("name-", RequiredAttributeDescriptor.NameComparisonMode.PrefixMatch) } }, { "asp-route-*,valid , name-* ,extra", new[] { - plain("asp-route-", TagHelperRequiredAttributeNameComparison.PrefixMatch), - plain("valid", TagHelperRequiredAttributeNameComparison.FullMatch), - plain("name-", TagHelperRequiredAttributeNameComparison.PrefixMatch), - plain("extra", TagHelperRequiredAttributeNameComparison.FullMatch), + plain("asp-route-", RequiredAttributeDescriptor.NameComparisonMode.PrefixMatch), + plain("valid", RequiredAttributeDescriptor.NameComparisonMode.FullMatch), + plain("name-", RequiredAttributeDescriptor.NameComparisonMode.PrefixMatch), + plain("extra", RequiredAttributeDescriptor.NameComparisonMode.FullMatch), } }, - { "[name]", new[] { css("name", "", TagHelperRequiredAttributeValueComparison.None) } }, - { "[ name ]", new[] { css("name", "", TagHelperRequiredAttributeValueComparison.None) } }, - { " [ name ] ", new[] { css("name", "", TagHelperRequiredAttributeValueComparison.None) } }, - { "[name=]", new[] { css("name", "", TagHelperRequiredAttributeValueComparison.FullMatch) } }, - { "[name='']", new[] { css("name", "", TagHelperRequiredAttributeValueComparison.FullMatch) } }, - { "[name ^=]", new[] { css("name", "", TagHelperRequiredAttributeValueComparison.PrefixMatch) } }, - { "[name=hello]", new[] { css("name", "hello", TagHelperRequiredAttributeValueComparison.FullMatch) } }, - { "[name= hello]", new[] { css("name", "hello", TagHelperRequiredAttributeValueComparison.FullMatch) } }, - { "[name='hello']", new[] { css("name", "hello", TagHelperRequiredAttributeValueComparison.FullMatch) } }, - { "[name=\"hello\"]", new[] { css("name", "hello", TagHelperRequiredAttributeValueComparison.FullMatch) } }, - { " [ name $= \" hello\" ] ", new[] { css("name", " hello", TagHelperRequiredAttributeValueComparison.SuffixMatch) } }, + { "[name]", new[] { css("name", null, RequiredAttributeDescriptor.ValueComparisonMode.None) } }, + { "[ name ]", new[] { css("name", null, RequiredAttributeDescriptor.ValueComparisonMode.None) } }, + { " [ name ] ", new[] { css("name", null, RequiredAttributeDescriptor.ValueComparisonMode.None) } }, + { "[name=]", new[] { css("name", "", RequiredAttributeDescriptor.ValueComparisonMode.FullMatch) } }, + { "[name='']", new[] { css("name", "", RequiredAttributeDescriptor.ValueComparisonMode.FullMatch) } }, + { "[name ^=]", new[] { css("name", "", RequiredAttributeDescriptor.ValueComparisonMode.PrefixMatch) } }, + { "[name=hello]", new[] { css("name", "hello", RequiredAttributeDescriptor.ValueComparisonMode.FullMatch) } }, + { "[name= hello]", new[] { css("name", "hello", RequiredAttributeDescriptor.ValueComparisonMode.FullMatch) } }, + { "[name='hello']", new[] { css("name", "hello", RequiredAttributeDescriptor.ValueComparisonMode.FullMatch) } }, + { "[name=\"hello\"]", new[] { css("name", "hello", RequiredAttributeDescriptor.ValueComparisonMode.FullMatch) } }, + { " [ name $= \" hello\" ] ", new[] { css("name", " hello", RequiredAttributeDescriptor.ValueComparisonMode.SuffixMatch) } }, { "[name=\"hello\"],[other^=something ], [val = 'cool']", new[] { - css("name", "hello", TagHelperRequiredAttributeValueComparison.FullMatch), - css("other", "something", TagHelperRequiredAttributeValueComparison.PrefixMatch), - css("val", "cool", TagHelperRequiredAttributeValueComparison.FullMatch) } + css("name", "hello", RequiredAttributeDescriptor.ValueComparisonMode.FullMatch), + css("other", "something", RequiredAttributeDescriptor.ValueComparisonMode.PrefixMatch), + css("val", "cool", RequiredAttributeDescriptor.ValueComparisonMode.FullMatch) } }, { "asp-route-*,[name=\"hello\"],valid ,[other^=something ], name-* ,[val = 'cool'],extra", new[] { - plain("asp-route-", TagHelperRequiredAttributeNameComparison.PrefixMatch), - css("name", "hello", TagHelperRequiredAttributeValueComparison.FullMatch), - plain("valid", TagHelperRequiredAttributeNameComparison.FullMatch), - css("other", "something", TagHelperRequiredAttributeValueComparison.PrefixMatch), - plain("name-", TagHelperRequiredAttributeNameComparison.PrefixMatch), - css("val", "cool", TagHelperRequiredAttributeValueComparison.FullMatch), - plain("extra", TagHelperRequiredAttributeNameComparison.FullMatch), + plain("asp-route-", RequiredAttributeDescriptor.NameComparisonMode.PrefixMatch), + css("name", "hello", RequiredAttributeDescriptor.ValueComparisonMode.FullMatch), + plain("valid", RequiredAttributeDescriptor.NameComparisonMode.FullMatch), + css("other", "something", RequiredAttributeDescriptor.ValueComparisonMode.PrefixMatch), + plain("name-", RequiredAttributeDescriptor.NameComparisonMode.PrefixMatch), + css("val", "cool", RequiredAttributeDescriptor.ValueComparisonMode.FullMatch), + plain("extra", RequiredAttributeDescriptor.NameComparisonMode.FullMatch), } }, }; @@ -156,78 +299,60 @@ namespace Microsoft.CodeAnalysis.Razor.Workspaces [MemberData(nameof(RequiredAttributeParserData))] public void RequiredAttributeParser_ParsesRequiredAttributesCorrectly( string requiredAttributes, - IEnumerable expectedDescriptors) + IEnumerable expectedDescriptors) { // Arrange - var parser = new DefaultTagHelperDescriptorFactory.RequiredAttributeParser(requiredAttributes); - var errorSink = new ErrorSink(); - IEnumerable descriptors; + var ruleBuilder = TagMatchingRuleBuilder.Create(); // Act - var parsedCorrectly = parser.TryParse(errorSink, out descriptors); + RequiredAttributeParser.AddRequiredAttributes(requiredAttributes, ruleBuilder); // Assert - Assert.True(parsedCorrectly); - Assert.Empty(errorSink.Errors); - Assert.Equal(expectedDescriptors, descriptors, CaseSensitiveTagHelperRequiredAttributeDescriptorComparer.Default); + var descriptors = ruleBuilder.Build().Attributes; + Assert.Equal(expectedDescriptors, descriptors, RequiredAttributeDescriptorComparer.CaseSensitive); } public static TheoryData IsEnumData { get { - var attributeDescriptors = new[] - { - new TagHelperAttributeDescriptor - { - Name = "non-enum-property", - PropertyName = nameof(EnumTagHelper.NonEnumProperty), - TypeName = typeof(int).FullName - }, - new TagHelperAttributeDescriptor - { - Name = "enum-property", - PropertyName = nameof(EnumTagHelper.EnumProperty), - TypeName = typeof(CustomEnum).FullName, - IsEnum = true - }, - }; - - // tagHelperType, expectedDescriptors - return new TheoryData + // tagHelperType, expectedDescriptor + return new TheoryData { { typeof(EnumTagHelper), - new[] - { - new TagHelperDescriptor - { - TagName = "enum", - TypeName = typeof(EnumTagHelper).FullName, - AssemblyName = AssemblyName, - Attributes = attributeDescriptors - } - } + ITagHelperDescriptorBuilder.Create(typeof(EnumTagHelper).FullName, AssemblyName) + .TagMatchingRule(ruleBuilder => ruleBuilder.RequireTagName("enum")) + .BindAttribute(builder => + builder + .Name("non-enum-property") + .PropertyName(nameof(EnumTagHelper.NonEnumProperty)) + .TypeName(typeof(int).FullName)) + .BindAttribute(builder => + builder + .Name("enum-property") + .PropertyName(nameof(EnumTagHelper.EnumProperty)) + .TypeName(typeof(CustomEnum).FullName) + .AsEnum()) + .Build() }, { typeof(MultiEnumTagHelper), - new[] - { - new TagHelperDescriptor - { - TagName = "input", - TypeName = typeof(MultiEnumTagHelper).FullName, - AssemblyName = AssemblyName, - Attributes = attributeDescriptors - }, - new TagHelperDescriptor - { - TagName = "p", - TypeName = typeof(MultiEnumTagHelper).FullName, - AssemblyName = AssemblyName, - Attributes = attributeDescriptors - } - } + ITagHelperDescriptorBuilder.Create(typeof(MultiEnumTagHelper).FullName, AssemblyName) + .TagMatchingRule(ruleBuilder => ruleBuilder.RequireTagName("p")) + .TagMatchingRule(ruleBuilder => ruleBuilder.RequireTagName("input")) + .BindAttribute(builder => + builder + .Name("non-enum-property") + .PropertyName(nameof(MultiEnumTagHelper.NonEnumProperty)) + .TypeName(typeof(int).FullName)) + .BindAttribute(builder => + builder + .Name("enum-property") + .PropertyName(nameof(MultiEnumTagHelper.EnumProperty)) + .TypeName(typeof(CustomEnum).FullName) + .AsEnum()) + .Build() } }; } @@ -235,86 +360,47 @@ namespace Microsoft.CodeAnalysis.Razor.Workspaces [Theory] [MemberData(nameof(IsEnumData))] - public void CreateDescriptors_IsEnumIsSetCorrectly( + public void CreateDescriptor_IsEnumIsSetCorrectly( Type tagHelperType, - TagHelperDescriptor[] expectedDescriptors) + TagHelperDescriptor expectedDescriptor) { // Arrange - var errorSink = new ErrorSink(); var factory = new DefaultTagHelperDescriptorFactory(Compilation, designTime: false); var typeSymbol = Compilation.GetTypeByMetadataName(tagHelperType.FullName); // Act - var descriptors = factory.CreateDescriptors(typeSymbol, errorSink); + var descriptor = factory.CreateDescriptor(typeSymbol); // Assert - Assert.Empty(errorSink.Errors); - - // We don't care about order. Mono returns reflected attributes differently so we need to ensure order - // doesn't matter by sorting. - descriptors = descriptors.OrderBy(descriptor => descriptor.TagName); - - Assert.Equal(expectedDescriptors, descriptors, CaseSensitiveTagHelperDescriptorComparer.Default); + Assert.Equal(expectedDescriptor, descriptor, TagHelperDescriptorComparer.CaseSensitive); } public static TheoryData RequiredParentData { get { - // tagHelperType, expectedDescriptors - return new TheoryData + // tagHelperType, expectedDescriptor + return new TheoryData { { typeof(RequiredParentTagHelper), - new[] - { - new TagHelperDescriptor - { - TagName = "input", - TypeName = typeof(RequiredParentTagHelper).FullName, - AssemblyName = AssemblyName, - RequiredParent = "div" - } - } + ITagHelperDescriptorBuilder.Create(typeof(RequiredParentTagHelper).FullName, AssemblyName) + .TagMatchingRule(builder => builder.RequireTagName("input").RequireParentTag("div")) + .Build() }, { typeof(MultiSpecifiedRequiredParentTagHelper), - new[] - { - new TagHelperDescriptor - { - TagName = "input", - TypeName = typeof(MultiSpecifiedRequiredParentTagHelper).FullName, - AssemblyName = AssemblyName, - RequiredParent = "section" - }, - new TagHelperDescriptor - { - TagName = "p", - TypeName = typeof(MultiSpecifiedRequiredParentTagHelper).FullName, - AssemblyName = AssemblyName, - RequiredParent = "div" - } - } + ITagHelperDescriptorBuilder.Create(typeof(MultiSpecifiedRequiredParentTagHelper).FullName, AssemblyName) + .TagMatchingRule(builder => builder.RequireTagName("p").RequireParentTag("div")) + .TagMatchingRule(builder => builder.RequireTagName("input").RequireParentTag("section")) + .Build() }, { typeof(MultiWithUnspecifiedRequiredParentTagHelper), - new[] - { - new TagHelperDescriptor - { - TagName = "input", - TypeName = typeof(MultiWithUnspecifiedRequiredParentTagHelper).FullName, - AssemblyName = AssemblyName, - RequiredParent = "div" - }, - new TagHelperDescriptor - { - TagName = "p", - TypeName = typeof(MultiWithUnspecifiedRequiredParentTagHelper).FullName, - AssemblyName = AssemblyName - } - } + ITagHelperDescriptorBuilder.Create(typeof(MultiWithUnspecifiedRequiredParentTagHelper).FullName, AssemblyName) + .TagMatchingRule(builder => builder.RequireTagName("p")) + .TagMatchingRule(builder => builder.RequireTagName("input").RequireParentTag("div")) + .Build() }, }; } @@ -322,80 +408,51 @@ namespace Microsoft.CodeAnalysis.Razor.Workspaces [Theory] [MemberData(nameof(RequiredParentData))] - public void CreateDescriptors_CreatesDesignTimeDescriptorsWithRequiredParent( + public void CreateDescriptor_CreatesDesignTimeDescriptorsWithRequiredParent( Type tagHelperType, - TagHelperDescriptor[] expectedDescriptors) + TagHelperDescriptor expectedDescriptor) { // Arrange - var errorSink = new ErrorSink(); var factory = new DefaultTagHelperDescriptorFactory(Compilation, designTime: false); var typeSymbol = Compilation.GetTypeByMetadataName(tagHelperType.FullName); // Act - var descriptors = factory.CreateDescriptors(typeSymbol, errorSink); + var descriptor = factory.CreateDescriptor(typeSymbol); // Assert - Assert.Empty(errorSink.Errors); - - // We don't care about order. Mono returns reflected attributes differently so we need to ensure order - // doesn't matter by sorting. - descriptors = descriptors.OrderBy(descriptor => descriptor.TagName); - - Assert.Equal(expectedDescriptors, descriptors, CaseSensitiveTagHelperDescriptorComparer.Default); + Assert.Equal(expectedDescriptor, descriptor, TagHelperDescriptorComparer.CaseSensitive); } public static TheoryData RestrictChildrenData { get { - // tagHelperType, expectedDescriptors - return new TheoryData + // tagHelperType, expectedDescriptor + return new TheoryData { { typeof(RestrictChildrenTagHelper), - new[] - { - new TagHelperDescriptor - { - TagName = "restrict-children", - TypeName = typeof(RestrictChildrenTagHelper).FullName, - AssemblyName = AssemblyName, - AllowedChildren = new[] { "p" }, - } - } + ITagHelperDescriptorBuilder.Create(typeof(RestrictChildrenTagHelper).FullName, AssemblyName) + .TagMatchingRule(builder => builder.RequireTagName("restrict-children")) + .AllowChildTag("p") + .Build() }, { typeof(DoubleRestrictChildrenTagHelper), - new[] - { - new TagHelperDescriptor - { - TagName = "double-restrict-children", - TypeName = typeof(DoubleRestrictChildrenTagHelper).FullName, - AssemblyName = AssemblyName, - AllowedChildren = new[] { "p", "strong" }, - } - } + ITagHelperDescriptorBuilder.Create(typeof(DoubleRestrictChildrenTagHelper).FullName, AssemblyName) + .TagMatchingRule(builder => builder.RequireTagName("double-restrict-children")) + .AllowChildTag("p") + .AllowChildTag("strong") + .Build() }, { typeof(MultiTargetRestrictChildrenTagHelper), - new[] - { - new TagHelperDescriptor - { - TagName = "div", - TypeName = typeof(MultiTargetRestrictChildrenTagHelper).FullName, - AssemblyName = AssemblyName, - AllowedChildren = new[] { "p", "strong" }, - }, - new TagHelperDescriptor - { - TagName = "p", - TypeName = typeof(MultiTargetRestrictChildrenTagHelper).FullName, - AssemblyName = AssemblyName, - AllowedChildren = new[] { "p", "strong" }, - } - } + ITagHelperDescriptorBuilder.Create(typeof(MultiTargetRestrictChildrenTagHelper).FullName, AssemblyName) + .TagMatchingRule(builder => builder.RequireTagName("p")) + .TagMatchingRule(builder => builder.RequireTagName("div")) + .AllowChildTag("p") + .AllowChildTag("strong") + .Build() }, }; } @@ -404,86 +461,56 @@ namespace Microsoft.CodeAnalysis.Razor.Workspaces [Theory] [MemberData(nameof(RestrictChildrenData))] - public void CreateDescriptors_CreatesDescriptorsWithAllowedChildren( + public void CreateDescriptor_CreatesDescriptorsWithAllowedChildren( Type tagHelperType, - TagHelperDescriptor[] expectedDescriptors) + TagHelperDescriptor expectedDescriptor) { // Arrange - var errorSink = new ErrorSink(); var factory = new DefaultTagHelperDescriptorFactory(Compilation, designTime: false); var typeSymbol = Compilation.GetTypeByMetadataName(tagHelperType.FullName); // Act - var descriptors = factory.CreateDescriptors(typeSymbol, errorSink); + var descriptor = factory.CreateDescriptor(typeSymbol); // Assert - Assert.Empty(errorSink.Errors); - - // We don't care about order. Mono returns reflected attributes differently so we need to ensure order - // doesn't matter by sorting. - descriptors = descriptors.OrderBy(descriptor => descriptor.TagName); - - Assert.Equal(expectedDescriptors, descriptors, CaseSensitiveTagHelperDescriptorComparer.Default); + Assert.Equal(expectedDescriptor, descriptor, TagHelperDescriptorComparer.CaseSensitive); } public static TheoryData TagStructureData { get { - // tagHelperType, expectedDescriptors - return new TheoryData + // tagHelperType, expectedDescriptor + return new TheoryData { { typeof(TagStructureTagHelper), - new[] - { - new TagHelperDescriptor - { - TagName = "input", - TypeName = typeof(TagStructureTagHelper).FullName, - AssemblyName = AssemblyName, - TagStructure = TagStructure.WithoutEndTag - } - } + ITagHelperDescriptorBuilder.Create(typeof(TagStructureTagHelper).FullName, AssemblyName) + .TagMatchingRule(builder => builder + .RequireTagName("input") + .RequireTagStructure(TagStructure.WithoutEndTag)) + .Build() }, { typeof(MultiSpecifiedTagStructureTagHelper), - new[] - { - new TagHelperDescriptor - { - TagName = "input", - TypeName = typeof(MultiSpecifiedTagStructureTagHelper).FullName, - AssemblyName = AssemblyName, - TagStructure = TagStructure.WithoutEndTag - }, - new TagHelperDescriptor - { - TagName = "p", - TypeName = typeof(MultiSpecifiedTagStructureTagHelper).FullName, - AssemblyName = AssemblyName, - TagStructure = TagStructure.NormalOrSelfClosing - } - } + ITagHelperDescriptorBuilder.Create(typeof(MultiSpecifiedTagStructureTagHelper).FullName, AssemblyName) + .TagMatchingRule(builder => builder + .RequireTagName("p") + .RequireTagStructure(TagStructure.NormalOrSelfClosing)) + .TagMatchingRule(builder => builder + .RequireTagName("input") + .RequireTagStructure(TagStructure.WithoutEndTag)) + .Build() }, { typeof(MultiWithUnspecifiedTagStructureTagHelper), - new[] - { - new TagHelperDescriptor - { - TagName = "input", - TypeName = typeof(MultiWithUnspecifiedTagStructureTagHelper).FullName, - AssemblyName = AssemblyName, - TagStructure = TagStructure.WithoutEndTag - }, - new TagHelperDescriptor - { - TagName = "p", - TypeName = typeof(MultiWithUnspecifiedTagStructureTagHelper).FullName, - AssemblyName = AssemblyName - } - } + ITagHelperDescriptorBuilder.Create(typeof(MultiWithUnspecifiedTagStructureTagHelper).FullName, AssemblyName) + .TagMatchingRule(builder => builder + .RequireTagName("p")) + .TagMatchingRule(builder => builder + .RequireTagName("input") + .RequireTagStructure(TagStructure.WithoutEndTag)) + .Build() }, }; } @@ -491,498 +518,371 @@ namespace Microsoft.CodeAnalysis.Razor.Workspaces [Theory] [MemberData(nameof(TagStructureData))] - public void CreateDescriptors_CreatesDesignTimeDescriptorsWithTagStructure( + public void CreateDescriptor_CreatesDesignTimeDescriptorsWithTagStructure( Type tagHelperType, - TagHelperDescriptor[] expectedDescriptors) + TagHelperDescriptor expectedDescriptor) { // Arrange - var errorSink = new ErrorSink(); var factory = new DefaultTagHelperDescriptorFactory(Compilation, designTime: false); var typeSymbol = Compilation.GetTypeByMetadataName(tagHelperType.FullName); // Act - var descriptors = factory.CreateDescriptors(typeSymbol, errorSink); + var descriptor = factory.CreateDescriptor(typeSymbol); // Assert - Assert.Empty(errorSink.Errors); - - // We don't care about order. Mono returns reflected attributes differently so we need to ensure order - // doesn't matter by sorting. - descriptors = descriptors.OrderBy(descriptor => descriptor.TagName); - - Assert.Equal(expectedDescriptors, descriptors, CaseSensitiveTagHelperDescriptorComparer.Default); + Assert.Equal(expectedDescriptor, descriptor, TagHelperDescriptorComparer.CaseSensitive); } public static TheoryData EditorBrowsableData { get { - // tagHelperType, designTime, expectedDescriptors - return new TheoryData + // tagHelperType, designTime, expectedDescriptor + return new TheoryData { { typeof(InheritedEditorBrowsableTagHelper), true, - new[] - { - CreateTagHelperDescriptor( - tagName: "inherited-editor-browsable", - typeName: typeof(InheritedEditorBrowsableTagHelper).FullName, - assemblyName: AssemblyName, - attributes: new[] - { - new TagHelperAttributeDescriptor - { - Name = "property", - PropertyName = nameof(InheritedEditorBrowsableTagHelper.Property), - TypeName = typeof(int).FullName - } - }) - } + CreateTagHelperDescriptor( + tagName: "inherited-editor-browsable", + typeName: typeof(InheritedEditorBrowsableTagHelper).FullName, + assemblyName: AssemblyName, + attributes: new Action[] + { + builder => builder + .Name("property") + .PropertyName(nameof(InheritedEditorBrowsableTagHelper.Property)) + .TypeName(typeof(int).FullName), + }) }, - { typeof(EditorBrowsableTagHelper), true, new TagHelperDescriptor[0] }, + { typeof(EditorBrowsableTagHelper), true, null }, { typeof(EditorBrowsableTagHelper), false, - new[] - { - CreateTagHelperDescriptor( - tagName: "editor-browsable", - typeName: typeof(EditorBrowsableTagHelper).FullName, - assemblyName: AssemblyName, - attributes: new[] - { - new TagHelperAttributeDescriptor - { - Name = "property", - PropertyName = nameof(EditorBrowsableTagHelper.Property), - TypeName = typeof(int).FullName - } - }) - } + CreateTagHelperDescriptor( + tagName: "editor-browsable", + typeName: typeof(EditorBrowsableTagHelper).FullName, + assemblyName: AssemblyName, + attributes: new Action[] + { + builder => builder + .Name("property") + .PropertyName(nameof(EditorBrowsableTagHelper.Property)) + .TypeName(typeof(int).FullName), + }) }, { typeof(HiddenPropertyEditorBrowsableTagHelper), true, - new[] - { - CreateTagHelperDescriptor( - tagName: "hidden-property-editor-browsable", - typeName: typeof(HiddenPropertyEditorBrowsableTagHelper).FullName, - assemblyName: AssemblyName, - attributes: new TagHelperAttributeDescriptor[0]) - } + CreateTagHelperDescriptor( + tagName: "hidden-property-editor-browsable", + typeName: typeof(HiddenPropertyEditorBrowsableTagHelper).FullName, + assemblyName: AssemblyName) }, { typeof(HiddenPropertyEditorBrowsableTagHelper), false, - new[] - { - CreateTagHelperDescriptor( - tagName: "hidden-property-editor-browsable", - typeName: typeof(HiddenPropertyEditorBrowsableTagHelper).FullName, - assemblyName: AssemblyName, - attributes: new[] - { - new TagHelperAttributeDescriptor - { - Name = "property", - PropertyName = nameof(HiddenPropertyEditorBrowsableTagHelper.Property), - TypeName = typeof(int).FullName - } - }) - } + CreateTagHelperDescriptor( + tagName: "hidden-property-editor-browsable", + typeName: typeof(HiddenPropertyEditorBrowsableTagHelper).FullName, + assemblyName: AssemblyName, + attributes: new Action[] + { + builder => builder + .Name("property") + .PropertyName(nameof(HiddenPropertyEditorBrowsableTagHelper.Property)) + .TypeName(typeof(int).FullName), + }) }, { typeof(OverriddenEditorBrowsableTagHelper), true, - new[] - { - CreateTagHelperDescriptor( - tagName: "overridden-editor-browsable", - typeName: typeof(OverriddenEditorBrowsableTagHelper).FullName, - assemblyName: AssemblyName, - attributes: new[] - { - new TagHelperAttributeDescriptor - { - Name = "property", - PropertyName = nameof(OverriddenEditorBrowsableTagHelper.Property), - TypeName = typeof(int).FullName - } - }) - } + CreateTagHelperDescriptor( + tagName: "overridden-editor-browsable", + typeName: typeof(OverriddenEditorBrowsableTagHelper).FullName, + assemblyName: AssemblyName, + attributes: new Action[] + { + builder => builder + .Name("property") + .PropertyName(nameof(OverriddenEditorBrowsableTagHelper.Property)) + .TypeName(typeof(int).FullName), + }) }, { typeof(MultiPropertyEditorBrowsableTagHelper), true, - new[] - { - CreateTagHelperDescriptor( - tagName: "multi-property-editor-browsable", - typeName: typeof(MultiPropertyEditorBrowsableTagHelper).FullName, - assemblyName: AssemblyName, - attributes: new[] - { - new TagHelperAttributeDescriptor - { - Name = "property2", - PropertyName = nameof(MultiPropertyEditorBrowsableTagHelper.Property2), - TypeName = typeof(int).FullName - } - }) - } + CreateTagHelperDescriptor( + tagName: "multi-property-editor-browsable", + typeName: typeof(MultiPropertyEditorBrowsableTagHelper).FullName, + assemblyName: AssemblyName, + attributes: new Action[] + { + builder => builder + .Name("property2") + .PropertyName(nameof(MultiPropertyEditorBrowsableTagHelper.Property2)) + .TypeName(typeof(int).FullName), + }) }, { typeof(MultiPropertyEditorBrowsableTagHelper), false, - new[] - { - CreateTagHelperDescriptor( - tagName: "multi-property-editor-browsable", - typeName: typeof(MultiPropertyEditorBrowsableTagHelper).FullName, - assemblyName: AssemblyName, - attributes: new[] - { - new TagHelperAttributeDescriptor - { - Name = "property", - PropertyName = nameof(MultiPropertyEditorBrowsableTagHelper.Property), - TypeName = typeof(int).FullName - }, - new TagHelperAttributeDescriptor - { - Name = "property2", - PropertyName = nameof(MultiPropertyEditorBrowsableTagHelper.Property2), - TypeName = typeof(int).FullName - } - }) - } + CreateTagHelperDescriptor( + tagName: "multi-property-editor-browsable", + typeName: typeof(MultiPropertyEditorBrowsableTagHelper).FullName, + assemblyName: AssemblyName, + attributes: new Action[] + { + builder => builder + .Name("property") + .PropertyName(nameof(MultiPropertyEditorBrowsableTagHelper.Property)) + .TypeName(typeof(int).FullName), + builder => builder + .Name("property2") + .PropertyName(nameof(MultiPropertyEditorBrowsableTagHelper.Property2)) + .TypeName(typeof(int).FullName), + }) }, { typeof(OverriddenPropertyEditorBrowsableTagHelper), true, - new[] - { - CreateTagHelperDescriptor( - tagName: "overridden-property-editor-browsable", - typeName: typeof(OverriddenPropertyEditorBrowsableTagHelper).FullName, - assemblyName: AssemblyName, - attributes: new TagHelperAttributeDescriptor[0]) - } + CreateTagHelperDescriptor( + tagName: "overridden-property-editor-browsable", + typeName: typeof(OverriddenPropertyEditorBrowsableTagHelper).FullName, + assemblyName: AssemblyName) }, { typeof(OverriddenPropertyEditorBrowsableTagHelper), false, - new[] - { - CreateTagHelperDescriptor( - tagName: "overridden-property-editor-browsable", - typeName: typeof(OverriddenPropertyEditorBrowsableTagHelper).FullName, - assemblyName: AssemblyName, - attributes: new[] - { - new TagHelperAttributeDescriptor - { - Name = "property2", - PropertyName = nameof(OverriddenPropertyEditorBrowsableTagHelper.Property2), - TypeName = typeof(int).FullName - }, - new TagHelperAttributeDescriptor - { - Name = "property", - PropertyName = nameof(OverriddenPropertyEditorBrowsableTagHelper.Property), - TypeName = typeof(int).FullName - } - }) - } + CreateTagHelperDescriptor( + tagName: "overridden-property-editor-browsable", + typeName: typeof(OverriddenPropertyEditorBrowsableTagHelper).FullName, + assemblyName: AssemblyName, + attributes: new Action[] + { + builder => builder + .Name("property2") + .PropertyName(nameof(OverriddenPropertyEditorBrowsableTagHelper.Property2)) + .TypeName(typeof(int).FullName), + builder => builder + .Name("property") + .PropertyName(nameof(OverriddenPropertyEditorBrowsableTagHelper.Property)) + .TypeName(typeof(int).FullName), + }) }, { typeof(DefaultEditorBrowsableTagHelper), true, - new[] - { - CreateTagHelperDescriptor( - tagName: "default-editor-browsable", - typeName: typeof(DefaultEditorBrowsableTagHelper).FullName, - assemblyName: AssemblyName, - attributes: new[] - { - new TagHelperAttributeDescriptor - { - Name = "property", - PropertyName = nameof(DefaultEditorBrowsableTagHelper.Property), - TypeName = typeof(int).FullName - } - }) - } + CreateTagHelperDescriptor( + tagName: "default-editor-browsable", + typeName: typeof(DefaultEditorBrowsableTagHelper).FullName, + assemblyName: AssemblyName, + attributes: new Action[] + { + builder => builder + .Name("property") + .PropertyName(nameof(DefaultEditorBrowsableTagHelper.Property)) + .TypeName(typeof(int).FullName), + }) }, - { typeof(MultiEditorBrowsableTagHelper), true, new TagHelperDescriptor[0] } + { typeof(MultiEditorBrowsableTagHelper), true, null } }; } } [Theory] [MemberData(nameof(EditorBrowsableData))] - public void CreateDescriptors_UnderstandsEditorBrowsableAttribute( + public void CreateDescriptor_UnderstandsEditorBrowsableAttribute( Type tagHelperType, bool designTime, - TagHelperDescriptor[] expectedDescriptors) + TagHelperDescriptor expectedDescriptor) { // Arrange - var errorSink = new ErrorSink(); var factory = new DefaultTagHelperDescriptorFactory(Compilation, designTime); var typeSymbol = Compilation.GetTypeByMetadataName(tagHelperType.FullName); // Act - var descriptors = factory.CreateDescriptors(typeSymbol, errorSink); + var descriptor = factory.CreateDescriptor(typeSymbol); // Assert - Assert.Empty(errorSink.Errors); - Assert.Equal(expectedDescriptors, descriptors, CaseSensitiveTagHelperDescriptorComparer.Default); + Assert.Equal(expectedDescriptor, descriptor, TagHelperDescriptorComparer.CaseSensitive); } public static TheoryData AttributeTargetData { get { - var attributes = Enumerable.Empty(); + var attributes = Enumerable.Empty(); - // tagHelperType, expectedDescriptors - return new TheoryData> + // tagHelperType, expectedDescriptor + return new TheoryData { { typeof(AttributeTargetingTagHelper), - new[] - { - CreateTagHelperDescriptor( - TagHelperDescriptorProvider.ElementCatchAllTarget, - typeof(AttributeTargetingTagHelper).FullName, - AssemblyName, - attributes, - requiredAttributes: new[] - { - new TagHelperRequiredAttributeDescriptor { Name = "class" } - }) - } + CreateTagHelperDescriptor( + TagHelperDescriptorProvider.ElementCatchAllTarget, + typeof(AttributeTargetingTagHelper).FullName, + AssemblyName, + ruleBuilders: new Action[] + { + builder => builder.RequireAttribute(attribute => attribute.Name("class")), + }) }, { typeof(MultiAttributeTargetingTagHelper), - new[] - { - CreateTagHelperDescriptor( - TagHelperDescriptorProvider.ElementCatchAllTarget, - typeof(MultiAttributeTargetingTagHelper).FullName, - AssemblyName, - attributes, - requiredAttributes: new[] + CreateTagHelperDescriptor( + TagHelperDescriptorProvider.ElementCatchAllTarget, + typeof(MultiAttributeTargetingTagHelper).FullName, + AssemblyName, + ruleBuilders: new Action[] + { + builder => { - new TagHelperRequiredAttributeDescriptor { Name = "class" }, - new TagHelperRequiredAttributeDescriptor { Name = "style" } - }) - } + builder + .RequireAttribute(attribute => attribute.Name("class")) + .RequireAttribute(attribute => attribute.Name("style")); + }, + }) }, { typeof(MultiAttributeAttributeTargetingTagHelper), - new[] - { - CreateTagHelperDescriptor( - TagHelperDescriptorProvider.ElementCatchAllTarget, - typeof(MultiAttributeAttributeTargetingTagHelper).FullName, - AssemblyName, - attributes, - requiredAttributes: new[] + CreateTagHelperDescriptor( + TagHelperDescriptorProvider.ElementCatchAllTarget, + typeof(MultiAttributeAttributeTargetingTagHelper).FullName, + AssemblyName, + ruleBuilders: new Action[] + { + builder => builder.RequireAttribute(attribute => attribute.Name("custom")), + builder => { - new TagHelperRequiredAttributeDescriptor { Name = "custom" } - }), - CreateTagHelperDescriptor( - TagHelperDescriptorProvider.ElementCatchAllTarget, - typeof(MultiAttributeAttributeTargetingTagHelper).FullName, - AssemblyName, - attributes, - requiredAttributes: new[] - { - new TagHelperRequiredAttributeDescriptor { Name = "class" }, - new TagHelperRequiredAttributeDescriptor { Name = "style" } - }) - } + builder + .RequireAttribute(attribute => attribute.Name("class")) + .RequireAttribute(attribute => attribute.Name("style")); + }, + }) }, { typeof(InheritedAttributeTargetingTagHelper), - new[] - { - CreateTagHelperDescriptor( - TagHelperDescriptorProvider.ElementCatchAllTarget, - typeof(InheritedAttributeTargetingTagHelper).FullName, - AssemblyName, - attributes, - requiredAttributes: new[] - { - new TagHelperRequiredAttributeDescriptor { Name = "style" } - }) - } + CreateTagHelperDescriptor( + TagHelperDescriptorProvider.ElementCatchAllTarget, + typeof(InheritedAttributeTargetingTagHelper).FullName, + AssemblyName, + ruleBuilders: new Action[] + { + builder => builder.RequireAttribute(attribute => attribute.Name("style")), + }) }, { typeof(RequiredAttributeTagHelper), - new[] - { - CreateTagHelperDescriptor( - "input", - typeof(RequiredAttributeTagHelper).FullName, - AssemblyName, - attributes, - requiredAttributes: new[] - { - new TagHelperRequiredAttributeDescriptor { Name = "class" } - }) - } + CreateTagHelperDescriptor( + "input", + typeof(RequiredAttributeTagHelper).FullName, + AssemblyName, + ruleBuilders: new Action[] + { + builder => builder.RequireAttribute(attribute => attribute.Name("class")), + }) }, { typeof(InheritedRequiredAttributeTagHelper), - new[] - { - CreateTagHelperDescriptor( - "div", - typeof(InheritedRequiredAttributeTagHelper).FullName, - AssemblyName, - attributes, - requiredAttributes: new[] - { - new TagHelperRequiredAttributeDescriptor { Name = "class" } - }) - } + CreateTagHelperDescriptor( + "div", + typeof(InheritedRequiredAttributeTagHelper).FullName, + AssemblyName, + ruleBuilders: new Action[] + { + builder => builder.RequireAttribute(attribute => attribute.Name("class")), + }) }, { typeof(MultiAttributeRequiredAttributeTagHelper), - new[] - { - CreateTagHelperDescriptor( - "div", - typeof(MultiAttributeRequiredAttributeTagHelper).FullName, - AssemblyName, - attributes, - requiredAttributes: new[] - { - new TagHelperRequiredAttributeDescriptor { Name = "class" } - }), - CreateTagHelperDescriptor( - "input", - typeof(MultiAttributeRequiredAttributeTagHelper).FullName, - AssemblyName, - attributes, - requiredAttributes: new[] - { - new TagHelperRequiredAttributeDescriptor { Name = "class" } - }) - } + CreateTagHelperDescriptor( + "div", + typeof(MultiAttributeRequiredAttributeTagHelper).FullName, + AssemblyName, + ruleBuilders: new Action[] + { + builder => builder + .RequireTagName("div") + .RequireAttribute(attribute => attribute.Name("class")), + builder => builder + .RequireTagName("input") + .RequireAttribute(attribute => attribute.Name("class")), + }) }, { typeof(MultiAttributeSameTagRequiredAttributeTagHelper), - new[] - { - CreateTagHelperDescriptor( - "input", - typeof(MultiAttributeSameTagRequiredAttributeTagHelper).FullName, - AssemblyName, - attributes, - requiredAttributes: new[] - { - new TagHelperRequiredAttributeDescriptor { Name = "style" } - }), - CreateTagHelperDescriptor( - "input", - typeof(MultiAttributeSameTagRequiredAttributeTagHelper).FullName, - AssemblyName, - attributes, - requiredAttributes: new[] - { - new TagHelperRequiredAttributeDescriptor { Name = "class" } - }) - } + CreateTagHelperDescriptor( + "input", + typeof(MultiAttributeSameTagRequiredAttributeTagHelper).FullName, + AssemblyName, + ruleBuilders: new Action[] + { + builder => builder.RequireAttribute(attribute => attribute.Name("style")), + builder => builder.RequireAttribute(attribute => attribute.Name("class")), + }) }, { typeof(MultiRequiredAttributeTagHelper), - new[] - { - CreateTagHelperDescriptor( - "input", - typeof(MultiRequiredAttributeTagHelper).FullName, - AssemblyName, - attributes, - requiredAttributes: new[] - { - new TagHelperRequiredAttributeDescriptor { Name = "class" }, - new TagHelperRequiredAttributeDescriptor { Name = "style" } - }) - } + CreateTagHelperDescriptor( + "input", + typeof(MultiRequiredAttributeTagHelper).FullName, + AssemblyName, + ruleBuilders: new Action[] + { + builder => builder + .RequireAttribute(attribute => attribute.Name("class")) + .RequireAttribute(attribute => attribute.Name("style")), + }) }, { typeof(MultiTagMultiRequiredAttributeTagHelper), - new[] - { - CreateTagHelperDescriptor( - "div", - typeof(MultiTagMultiRequiredAttributeTagHelper).FullName, - AssemblyName, - attributes, - requiredAttributes: new[] - { - new TagHelperRequiredAttributeDescriptor { Name = "class" }, - new TagHelperRequiredAttributeDescriptor { Name = "style" } - }), - CreateTagHelperDescriptor( - "input", - typeof(MultiTagMultiRequiredAttributeTagHelper).FullName, - AssemblyName, - attributes, - requiredAttributes: new[] { - new TagHelperRequiredAttributeDescriptor { Name = "class" }, - new TagHelperRequiredAttributeDescriptor { Name = "style" } - }), - } + CreateTagHelperDescriptor( + "div", + typeof(MultiTagMultiRequiredAttributeTagHelper).FullName, + AssemblyName, + ruleBuilders: new Action[] + { + builder => builder + .RequireTagName("div") + .RequireAttribute(attribute => attribute.Name("class")) + .RequireAttribute(attribute => attribute.Name("style")), + builder => builder + .RequireTagName("input") + .RequireAttribute(attribute => attribute.Name("class")) + .RequireAttribute(attribute => attribute.Name("style")), + }) }, { typeof(AttributeWildcardTargetingTagHelper), - new[] - { - CreateTagHelperDescriptor( - TagHelperDescriptorProvider.ElementCatchAllTarget, - typeof(AttributeWildcardTargetingTagHelper).FullName, - AssemblyName, - attributes, - requiredAttributes: new[] - { - new TagHelperRequiredAttributeDescriptor - { - Name = "class", - NameComparison = TagHelperRequiredAttributeNameComparison.PrefixMatch, - } - }) - } + CreateTagHelperDescriptor( + TagHelperDescriptorProvider.ElementCatchAllTarget, + typeof(AttributeWildcardTargetingTagHelper).FullName, + AssemblyName, + ruleBuilders: new Action[] + { + builder => builder + .RequireAttribute(attribute => attribute + .Name("class") + .NameComparisonMode(RequiredAttributeDescriptor.NameComparisonMode.PrefixMatch)), + }) }, { typeof(MultiAttributeWildcardTargetingTagHelper), - new[] - { - CreateTagHelperDescriptor( - TagHelperDescriptorProvider.ElementCatchAllTarget, - typeof(MultiAttributeWildcardTargetingTagHelper).FullName, - AssemblyName, - attributes, - requiredAttributes: new[] - { - new TagHelperRequiredAttributeDescriptor - { - Name = "class", - NameComparison = TagHelperRequiredAttributeNameComparison.PrefixMatch, - }, - new TagHelperRequiredAttributeDescriptor - { - Name = "style", - NameComparison = TagHelperRequiredAttributeNameComparison.PrefixMatch, - } - }) - } + CreateTagHelperDescriptor( + TagHelperDescriptorProvider.ElementCatchAllTarget, + typeof(MultiAttributeWildcardTargetingTagHelper).FullName, + AssemblyName, + ruleBuilders: new Action[] + { + builder => builder + .RequireAttribute(attribute => attribute + .Name("class") + .NameComparisonMode(RequiredAttributeDescriptor.NameComparisonMode.PrefixMatch)) + .RequireAttribute(attribute => attribute + .Name("style") + .NameComparisonMode(RequiredAttributeDescriptor.NameComparisonMode.PrefixMatch)), + }) }, }; } @@ -990,29 +890,19 @@ namespace Microsoft.CodeAnalysis.Razor.Workspaces [Theory] [MemberData(nameof(AttributeTargetData))] - public void CreateDescriptors_ReturnsExpectedDescriptors( + public void CreateDescriptor_ReturnsExpectedDescriptors( Type tagHelperType, - IEnumerable expectedDescriptors) + TagHelperDescriptor expectedDescriptor) { // Arrange - var errorSink = new ErrorSink(); var factory = new DefaultTagHelperDescriptorFactory(Compilation, designTime: false); var typeSymbol = Compilation.GetTypeByMetadataName(tagHelperType.FullName); // Act - var descriptors = factory.CreateDescriptors(typeSymbol, errorSink); + var descriptor = factory.CreateDescriptor(typeSymbol); // Assert - Assert.Empty(errorSink.Errors); - - // We don't care about order. Mono returns reflected attributes differently so we need to ensure order - // doesn't matter by sorting. - descriptors = descriptors.OrderBy( - descriptor => CaseSensitiveTagHelperDescriptorComparer.Default.GetHashCode(descriptor)).ToArray(); - expectedDescriptors = expectedDescriptors.OrderBy( - descriptor => CaseSensitiveTagHelperDescriptorComparer.Default.GetHashCode(descriptor)).ToArray(); - - Assert.Equal(expectedDescriptors, descriptors, CaseSensitiveTagHelperDescriptorComparer.Default); + Assert.Equal(expectedDescriptor, descriptor, TagHelperDescriptorComparer.CaseSensitive); } public static TheoryData HtmlCaseData @@ -1036,410 +926,355 @@ namespace Microsoft.CodeAnalysis.Razor.Workspaces [Theory] [MemberData(nameof(HtmlCaseData))] - public void CreateDescriptors_HtmlCasesTagNameAndAttributeName( + public void CreateDescriptor_HtmlCasesTagNameAndAttributeName( Type tagHelperType, string expectedTagName, string expectedAttributeName) { // Arrange - var errorSink = new ErrorSink(); var factory = new DefaultTagHelperDescriptorFactory(Compilation, designTime: false); var typeSymbol = Compilation.GetTypeByMetadataName(tagHelperType.FullName); // Act - var descriptors = factory.CreateDescriptors(typeSymbol, errorSink); + var descriptor = factory.CreateDescriptor(typeSymbol); // Assert - Assert.Empty(errorSink.Errors); - var descriptor = Assert.Single(descriptors); - Assert.Equal(expectedTagName, descriptor.TagName, StringComparer.Ordinal); - var attributeDescriptor = Assert.Single(descriptor.Attributes); + var rule = Assert.Single(descriptor.TagMatchingRules); + Assert.Equal(expectedTagName, rule.TagName, StringComparer.Ordinal); + var attributeDescriptor = Assert.Single(descriptor.BoundAttributes); Assert.Equal(expectedAttributeName, attributeDescriptor.Name); } [Fact] - public void CreateDescriptors_OverridesAttributeNameFromAttribute() + public void CreateDescriptor_OverridesAttributeNameFromAttribute() { // Arrange - var errorSink = new ErrorSink(); var validProperty1 = typeof(OverriddenAttributeTagHelper).GetProperty( nameof(OverriddenAttributeTagHelper.ValidAttribute1)); var validProperty2 = typeof(OverriddenAttributeTagHelper).GetProperty( nameof(OverriddenAttributeTagHelper.ValidAttribute2)); - var expectedDescriptors = new[] - { + var expectedDescriptor = CreateTagHelperDescriptor( "overridden-attribute", typeof(OverriddenAttributeTagHelper).FullName, AssemblyName, - new[] + new Action[] { - CreateTagHelperAttributeDescriptor("SomethingElse", validProperty1), - CreateTagHelperAttributeDescriptor("Something-Else", validProperty2) - }) - }; + builder => builder + .Name("SomethingElse") + .PropertyName(validProperty1.Name) + .TypeName(validProperty1.PropertyType.FullName), + builder => builder + .Name("Something-Else") + .PropertyName(validProperty2.Name) + .TypeName(validProperty2.PropertyType.FullName), + }); var factory = new DefaultTagHelperDescriptorFactory(Compilation, designTime: false); var typeSymbol = Compilation.GetTypeByMetadataName(typeof(OverriddenAttributeTagHelper).FullName); // Act - var descriptors = factory.CreateDescriptors(typeSymbol, errorSink); + var descriptor = factory.CreateDescriptor(typeSymbol); // Assert - Assert.Empty(errorSink.Errors); - Assert.Equal(expectedDescriptors, descriptors, CaseSensitiveTagHelperDescriptorComparer.Default); + Assert.Equal(expectedDescriptor, descriptor, TagHelperDescriptorComparer.CaseSensitive); } [Fact] - public void CreateDescriptors_DoesNotInheritOverridenAttributeName() + public void CreateDescriptor_DoesNotInheritOverridenAttributeName() { // Arrange - var errorSink = new ErrorSink(); var validProperty1 = typeof(InheritedOverriddenAttributeTagHelper).GetProperty( nameof(InheritedOverriddenAttributeTagHelper.ValidAttribute1)); var validProperty2 = typeof(InheritedOverriddenAttributeTagHelper).GetProperty( nameof(InheritedOverriddenAttributeTagHelper.ValidAttribute2)); - var expectedDescriptors = new[] - { + var expectedDescriptor = CreateTagHelperDescriptor( "inherited-overridden-attribute", typeof(InheritedOverriddenAttributeTagHelper).FullName, AssemblyName, - new[] + new Action[] { - CreateTagHelperAttributeDescriptor("valid-attribute1", validProperty1), - CreateTagHelperAttributeDescriptor("Something-Else", validProperty2) - }) - }; + builder => builder + .Name("valid-attribute1") + .PropertyName(validProperty1.Name) + .TypeName(validProperty1.PropertyType.FullName), + builder => builder + .Name("Something-Else") + .PropertyName(validProperty2.Name) + .TypeName(validProperty2.PropertyType.FullName), + }); var factory = new DefaultTagHelperDescriptorFactory(Compilation, designTime: false); var typeSymbol = Compilation.GetTypeByMetadataName(typeof(InheritedOverriddenAttributeTagHelper).FullName); // Act - var descriptors = factory.CreateDescriptors(typeSymbol, errorSink); + var descriptor = factory.CreateDescriptor(typeSymbol); // Assert - Assert.Empty(errorSink.Errors); - Assert.Equal(expectedDescriptors, descriptors, CaseSensitiveTagHelperDescriptorComparer.Default); + Assert.Equal(expectedDescriptor, descriptor, TagHelperDescriptorComparer.CaseSensitive); } [Fact] - public void CreateDescriptors_AllowsOverridenAttributeNameOnUnimplementedVirtual() + public void CreateDescriptor_AllowsOverridenAttributeNameOnUnimplementedVirtual() { // Arrange - var errorSink = new ErrorSink(); var validProperty1 = typeof(InheritedNotOverriddenAttributeTagHelper).GetProperty( nameof(InheritedNotOverriddenAttributeTagHelper.ValidAttribute1)); var validProperty2 = typeof(InheritedNotOverriddenAttributeTagHelper).GetProperty( nameof(InheritedNotOverriddenAttributeTagHelper.ValidAttribute2)); - var expectedDescriptors = new[] - { + var expectedDescriptor = CreateTagHelperDescriptor( "inherited-not-overridden-attribute", typeof(InheritedNotOverriddenAttributeTagHelper).FullName, AssemblyName, - new[] + new Action[] { - CreateTagHelperAttributeDescriptor("SomethingElse", validProperty1), - CreateTagHelperAttributeDescriptor("Something-Else", validProperty2) - }) - }; + builder => builder + .Name("SomethingElse") + .PropertyName(validProperty1.Name) + .TypeName(validProperty1.PropertyType.FullName), + builder => builder + .Name("Something-Else") + .PropertyName(validProperty2.Name) + .TypeName(validProperty2.PropertyType.FullName), + }); var factory = new DefaultTagHelperDescriptorFactory(Compilation, designTime: false); var typeSymbol = Compilation.GetTypeByMetadataName(typeof(InheritedNotOverriddenAttributeTagHelper).FullName); // Act - var descriptors = factory.CreateDescriptors(typeSymbol, errorSink); + var descriptor = factory.CreateDescriptor(typeSymbol); + // Assert - Assert.Empty(errorSink.Errors); - Assert.Equal(expectedDescriptors, descriptors, CaseSensitiveTagHelperDescriptorComparer.Default); + Assert.Equal(expectedDescriptor, descriptor, TagHelperDescriptorComparer.CaseSensitive); } [Fact] - public void CreateDescriptors_BuildsDescriptorsWithInheritedProperties() + public void CreateDescriptor_BuildsDescriptorsWithInheritedProperties() { // Arrange - var errorSink = new ErrorSink(); var expectedDescriptor = CreateTagHelperDescriptor( "inherited-single-attribute", typeof(InheritedSingleAttributeTagHelper).FullName, AssemblyName, - new[] + new Action[] { - new TagHelperAttributeDescriptor - { - Name = "int-attribute", - PropertyName = nameof(InheritedSingleAttributeTagHelper.IntAttribute), - TypeName = typeof(int).FullName - } + builder => builder + .Name("int-attribute") + .PropertyName(nameof(InheritedSingleAttributeTagHelper.IntAttribute)) + .TypeName(typeof(int).FullName) }); var factory = new DefaultTagHelperDescriptorFactory(Compilation, designTime: false); var typeSymbol = Compilation.GetTypeByMetadataName(typeof(InheritedSingleAttributeTagHelper).FullName); // Act - var descriptors = factory.CreateDescriptors(typeSymbol, errorSink); + var descriptor = factory.CreateDescriptor(typeSymbol); // Assert - Assert.Empty(errorSink.Errors); - var descriptor = Assert.Single(descriptors); - Assert.Equal(expectedDescriptor, descriptor, CaseSensitiveTagHelperDescriptorComparer.Default); + Assert.Equal(expectedDescriptor, descriptor, TagHelperDescriptorComparer.CaseSensitive); } [Fact] - public void CreateDescriptors_BuildsDescriptorsWithConventionNames() + public void CreateDescriptor_BuildsDescriptorsWithConventionNames() { // Arrange - var errorSink = new ErrorSink(); var intProperty = typeof(SingleAttributeTagHelper).GetProperty(nameof(SingleAttributeTagHelper.IntAttribute)); var expectedDescriptor = CreateTagHelperDescriptor( "single-attribute", typeof(SingleAttributeTagHelper).FullName, AssemblyName, - new[] + new Action[] { - CreateTagHelperAttributeDescriptor("int-attribute", intProperty) + builder => builder + .Name("int-attribute") + .PropertyName(intProperty.Name) + .TypeName(intProperty.PropertyType.FullName) }); var factory = new DefaultTagHelperDescriptorFactory(Compilation, designTime: false); var typeSymbol = Compilation.GetTypeByMetadataName(typeof(SingleAttributeTagHelper).FullName); // Act - var descriptors = factory.CreateDescriptors(typeSymbol, errorSink); + var descriptor = factory.CreateDescriptor(typeSymbol); // Assert - Assert.Empty(errorSink.Errors); - var descriptor = Assert.Single(descriptors); - Assert.Equal(expectedDescriptor, descriptor, CaseSensitiveTagHelperDescriptorComparer.Default); + Assert.Equal(expectedDescriptor, descriptor, TagHelperDescriptorComparer.CaseSensitive); } [Fact] - public void CreateDescriptors_OnlyAcceptsPropertiesWithGetAndSet() + public void CreateDescriptor_OnlyAcceptsPropertiesWithGetAndSet() { // Arrange - var errorSink = new ErrorSink(); var validProperty = typeof(MissingAccessorTagHelper).GetProperty( nameof(MissingAccessorTagHelper.ValidAttribute)); var expectedDescriptor = CreateTagHelperDescriptor( "missing-accessor", typeof(MissingAccessorTagHelper).FullName, AssemblyName, - new[] + new Action[] { - CreateTagHelperAttributeDescriptor("valid-attribute", validProperty) + builder => builder + .Name("valid-attribute") + .PropertyName(validProperty.Name) + .TypeName(validProperty.PropertyType.FullName) }); var factory = new DefaultTagHelperDescriptorFactory(Compilation, designTime: false); var typeSymbol = Compilation.GetTypeByMetadataName(typeof(MissingAccessorTagHelper).FullName); // Act - var descriptors = factory.CreateDescriptors(typeSymbol, errorSink); + var descriptor = factory.CreateDescriptor(typeSymbol); // Assert - Assert.Empty(errorSink.Errors); - var descriptor = Assert.Single(descriptors); - Assert.Equal(expectedDescriptor, descriptor, CaseSensitiveTagHelperDescriptorComparer.Default); + Assert.Equal(expectedDescriptor, descriptor, TagHelperDescriptorComparer.CaseSensitive); } [Fact] - public void CreateDescriptors_OnlyAcceptsPropertiesWithPublicGetAndSet() + public void CreateDescriptor_OnlyAcceptsPropertiesWithPublicGetAndSet() { // Arrange - var errorSink = new ErrorSink(); var validProperty = typeof(NonPublicAccessorTagHelper).GetProperty( nameof(NonPublicAccessorTagHelper.ValidAttribute)); var expectedDescriptor = CreateTagHelperDescriptor( "non-public-accessor", typeof(NonPublicAccessorTagHelper).FullName, AssemblyName, - new[] + new Action[] { - CreateTagHelperAttributeDescriptor("valid-attribute", validProperty) + builder => builder + .Name("valid-attribute") + .PropertyName(validProperty.Name) + .TypeName(validProperty.PropertyType.FullName) }); var factory = new DefaultTagHelperDescriptorFactory(Compilation, designTime: false); var typeSymbol = Compilation.GetTypeByMetadataName(typeof(NonPublicAccessorTagHelper).FullName); // Act - var descriptors = factory.CreateDescriptors(typeSymbol, errorSink); + var descriptor = factory.CreateDescriptor(typeSymbol); // Assert - Assert.Empty(errorSink.Errors); - var descriptor = Assert.Single(descriptors); - Assert.Equal(expectedDescriptor, descriptor, CaseSensitiveTagHelperDescriptorComparer.Default); + Assert.Equal(expectedDescriptor, descriptor, TagHelperDescriptorComparer.CaseSensitive); } [Fact] - public void CreateDescriptors_DoesNotIncludePropertiesWithNotBound() + public void CreateDescriptor_DoesNotIncludePropertiesWithNotBound() { // Arrange - var errorSink = new ErrorSink(); var expectedDescriptor = CreateTagHelperDescriptor( "not-bound-attribute", typeof(NotBoundAttributeTagHelper).FullName, AssemblyName, - new[] + new Action[] { - new TagHelperAttributeDescriptor - { - Name = "bound-property", - PropertyName = nameof(NotBoundAttributeTagHelper.BoundProperty), - TypeName = typeof(object).FullName - } + builder => builder + .Name("bound-property") + .PropertyName(nameof(NotBoundAttributeTagHelper.BoundProperty)) + .TypeName(typeof(object).FullName) }); var factory = new DefaultTagHelperDescriptorFactory(Compilation, designTime: false); var typeSymbol = Compilation.GetTypeByMetadataName(typeof(NotBoundAttributeTagHelper).FullName); // Act - var descriptors = factory.CreateDescriptors(typeSymbol, - errorSink: errorSink); + var descriptor = factory.CreateDescriptor(typeSymbol); // Assert - Assert.Empty(errorSink.Errors); - var descriptor = Assert.Single(descriptors); - Assert.Equal(expectedDescriptor, descriptor, CaseSensitiveTagHelperDescriptorComparer.Default); - } - - [Fact(Skip = "#364")] - public void CreateDescriptors_AddsErrorForTagHelperWithDuplicateAttributeNames() - { - // Arrange - var errorSink = new ErrorSink(); - var factory = new DefaultTagHelperDescriptorFactory(Compilation, designTime: false); - var typeSymbol = Compilation.GetTypeByMetadataName(typeof(DuplicateAttributeNameTagHelper).FullName); - - // Act - var descriptors = factory.CreateDescriptors(typeSymbol, errorSink); - - // Assert - Assert.Empty(descriptors); - var error = Assert.Single(errorSink.Errors); + Assert.Equal(expectedDescriptor, descriptor, TagHelperDescriptorComparer.CaseSensitive); } [Fact] - public void CreateDescriptors_ResolvesMultipleTagHelperDescriptorsFromSingleType() + public void CreateDescriptor_ResolvesMultipleTagHelperDescriptorsFromSingleType() { // Arrange - var errorSink = new ErrorSink(); - var expectedDescriptors = new[] - { + var expectedDescriptor = CreateTagHelperDescriptor( - "div", + string.Empty, typeof(MultiTagTagHelper).FullName, AssemblyName, - new[] + new Action[] { - new TagHelperAttributeDescriptor - { - Name = "valid-attribute", - PropertyName = nameof(MultiTagTagHelper.ValidAttribute), - TypeName = typeof(string).FullName, - IsStringProperty = true - } - }), - CreateTagHelperDescriptor( - "p", - typeof(MultiTagTagHelper).FullName, - AssemblyName, - new[] + builder => builder + .Name("valid-attribute") + .PropertyName(nameof(MultiTagTagHelper.ValidAttribute)) + .TypeName(typeof(string).FullName), + }, + new Action[] { - new TagHelperAttributeDescriptor - { - Name = "valid-attribute", - PropertyName = nameof(MultiTagTagHelper.ValidAttribute), - TypeName = typeof(string).FullName, - IsStringProperty = true - } - }) - }; + builder => builder.RequireTagName("p"), + builder => builder.RequireTagName("div"), + }); var factory = new DefaultTagHelperDescriptorFactory(Compilation, designTime: false); var typeSymbol = Compilation.GetTypeByMetadataName(typeof(MultiTagTagHelper).FullName); // Act - var descriptors = factory.CreateDescriptors(typeSymbol, errorSink); + var descriptor = factory.CreateDescriptor(typeSymbol); // Assert - Assert.Empty(errorSink.Errors); - - // We don't care about order. Mono returns reflected attributes differently so we need to ensure order - // doesn't matter by sorting. - descriptors = descriptors.OrderBy(descriptor => descriptor.TagName).ToArray(); - - Assert.Equal(expectedDescriptors, descriptors, CaseSensitiveTagHelperDescriptorComparer.Default); + Assert.Equal(expectedDescriptor, descriptor, TagHelperDescriptorComparer.CaseSensitive); } [Fact] - public void CreateDescriptors_DoesNotResolveInheritedTagNames() + public void CreateDescriptor_DoesNotResolveInheritedTagNames() { // Arrange - var errorSink = new ErrorSink(); var validProp = typeof(InheritedMultiTagTagHelper).GetProperty(nameof(InheritedMultiTagTagHelper.ValidAttribute)); var expectedDescriptor = CreateTagHelperDescriptor( "inherited-multi-tag", typeof(InheritedMultiTagTagHelper).FullName, AssemblyName, - new[] + new Action[] { - CreateTagHelperAttributeDescriptor("valid-attribute", validProp) + builder => builder + .Name("valid-attribute") + .PropertyName(validProp.Name) + .TypeName(validProp.PropertyType.FullName), }); var factory = new DefaultTagHelperDescriptorFactory(Compilation, designTime: false); var typeSymbol = Compilation.GetTypeByMetadataName(typeof(InheritedMultiTagTagHelper).FullName); // Act - var descriptors = factory.CreateDescriptors(typeSymbol, errorSink); + var descriptor = factory.CreateDescriptor(typeSymbol); // Assert - Assert.Empty(errorSink.Errors); - var descriptor = Assert.Single(descriptors); - Assert.Equal(expectedDescriptor, descriptor, CaseSensitiveTagHelperDescriptorComparer.Default); + Assert.Equal(expectedDescriptor, descriptor, TagHelperDescriptorComparer.CaseSensitive); } [Fact] - public void CreateDescriptors_IgnoresDuplicateTagNamesFromAttribute() + public void CreateDescriptor_IgnoresDuplicateTagNamesFromAttribute() { // Arrange - var errorSink = new ErrorSink(); - var expectedDescriptors = new[] - { + var expectedDescriptor = CreateTagHelperDescriptor( - "div", + string.Empty, typeof(DuplicateTagNameTagHelper).FullName, - AssemblyName), - CreateTagHelperDescriptor( - "p", - typeof(DuplicateTagNameTagHelper).FullName, - AssemblyName) - }; + AssemblyName, + ruleBuilders: new Action[] + { + builder => builder.RequireTagName("p"), + builder => builder.RequireTagName("div"), + }); var factory = new DefaultTagHelperDescriptorFactory(Compilation, designTime: false); var typeSymbol = Compilation.GetTypeByMetadataName(typeof(DuplicateTagNameTagHelper).FullName); // Act - var descriptors = factory.CreateDescriptors(typeSymbol, errorSink); + var descriptor = factory.CreateDescriptor(typeSymbol); // Assert - Assert.Empty(errorSink.Errors); - - // We don't care about order. Mono returns reflected attributes differently so we need to ensure order - // doesn't matter by sorting. - descriptors = descriptors.OrderBy(descriptor => descriptor.TagName).ToArray(); - - Assert.Equal(expectedDescriptors, descriptors, CaseSensitiveTagHelperDescriptorComparer.Default); + Assert.Equal(expectedDescriptor, descriptor, TagHelperDescriptorComparer.CaseSensitive); } [Fact] - public void CreateDescriptors_OverridesTagNameFromAttribute() + public void CreateDescriptor_OverridesTagNameFromAttribute() { // Arrange - var errorSink = new ErrorSink(); - var expectedDescriptors = new[] - { + var expectedDescriptor = CreateTagHelperDescriptor( "data-condition", typeof(OverrideNameTagHelper).FullName, - AssemblyName), - }; + AssemblyName); var factory = new DefaultTagHelperDescriptorFactory(Compilation, designTime: false); var typeSymbol = Compilation.GetTypeByMetadataName(typeof(OverrideNameTagHelper).FullName); // Act - var descriptors = factory.CreateDescriptors(typeSymbol, errorSink); + var descriptor = factory.CreateDescriptor(typeSymbol); // Assert - Assert.Empty(errorSink.Errors); - Assert.Equal(expectedDescriptors, descriptors, CaseSensitiveTagHelperDescriptorComparer.Default); + Assert.Equal(expectedDescriptor, descriptor, TagHelperDescriptorComparer.CaseSensitive); } // name, expectedErrorMessages @@ -1448,9 +1283,8 @@ namespace Microsoft.CodeAnalysis.Razor.Workspaces get { Func onNameError = - (invalidText, invalidCharacter) => $"Tag helpers cannot target tag name '{ invalidText }' " + - $"because it contains a '{ invalidCharacter }' character."; - var whitespaceErrorString = "Tag name cannot be null or whitespace."; + (invalidText, invalidCharacter) => $"Tag helpers cannot target tag name '{invalidText}' because it contains a '{invalidCharacter}' character."; + var whitespaceErrorString = "Targeted tag name cannot be null or whitespace."; var data = GetInvalidNameOrPrefixData(onNameError, whitespaceErrorString, onDataError: null); data.Add(string.Empty, new[] { whitespaceErrorString }); @@ -1461,33 +1295,32 @@ namespace Microsoft.CodeAnalysis.Razor.Workspaces [Theory] [MemberData(nameof(InvalidNameData))] - public void ValidHtmlTargetElementAttributeNames_CreatesErrorOnInvalidNames( + public void CreateDescriptor_CreatesErrorOnInvalidNames( string name, string[] expectedErrorMessages) { // Arrange - var errorSink = new ErrorSink(); name = name.Replace("\n", "\\n").Replace("\r", "\\r").Replace("\"", "\\\""); var text = $@" -[{typeof(AspNetCore.Razor.TagHelpers.HtmlTargetElementAttribute).FullName}(""{name}"")] -public class DynamicTestTagHelper : {typeof(AspNetCore.Razor.TagHelpers.TagHelper).FullName} -{{ -}}"; + [{typeof(AspNetCore.Razor.TagHelpers.HtmlTargetElementAttribute).FullName}(""{name}"")] + public class DynamicTestTagHelper : {typeof(AspNetCore.Razor.TagHelpers.TagHelper).FullName} + {{ + }}"; var syntaxTree = CSharpSyntaxTree.ParseText(text); var compilation = TestCompilation.Create(syntaxTree); var tagHelperType = compilation.GetTypeByMetadataName("DynamicTestTagHelper"); var attribute = tagHelperType.GetAttributes().Single(); + var factory = new DefaultTagHelperDescriptorFactory(compilation, designTime: false); // Act - DefaultTagHelperDescriptorFactory.ValidHtmlTargetElementAttributeNames(attribute, errorSink); + var descriptor = factory.CreateDescriptor(tagHelperType); // Assert - var errors = errorSink.Errors.ToArray(); - Assert.Equal(expectedErrorMessages.Length, errors.Length); + var rule = Assert.Single(descriptor.TagMatchingRules); + var errorMessages = rule.GetAllDiagnostics().Select(diagnostic => diagnostic.GetMessage()).ToArray(); + Assert.Equal(expectedErrorMessages.Length, errorMessages.Length); for (var i = 0; i < expectedErrorMessages.Length; i++) { - Assert.Equal(0, errors[i].Length); - Assert.Equal(SourceLocation.Zero, errors[i].Location); - Assert.Equal(expectedErrorMessages[i], errors[i].Message, StringComparer.Ordinal); + Assert.Equal(expectedErrorMessages[i], errorMessages[i], StringComparer.Ordinal); } } @@ -1497,21 +1330,21 @@ public class DynamicTestTagHelper : {typeof(AspNetCore.Razor.TagHelpers.TagHelpe { // name, expectedNames return new TheoryData> - { - { "p", new[] { "p" } }, - { " p", new[] { "p" } }, - { "p ", new[] { "p" } }, - { " p ", new[] { "p" } }, - { "p,div", new[] { "p", "div" } }, - { " p,div", new[] { "p", "div" } }, - { "p ,div", new[] { "p", "div" } }, - { " p ,div", new[] { "p", "div" } }, - { "p, div", new[] { "p", "div" } }, - { "p,div ", new[] { "p", "div" } }, - { "p, div ", new[] { "p", "div" } }, - { " p, div ", new[] { "p", "div" } }, - { " p , div ", new[] { "p", "div" } }, - }; + { + { "p", new[] { "p" } }, + { " p", new[] { "p" } }, + { "p ", new[] { "p" } }, + { " p ", new[] { "p" } }, + { "p,div", new[] { "p", "div" } }, + { " p,div", new[] { "p", "div" } }, + { "p ,div", new[] { "p", "div" } }, + { " p ,div", new[] { "p", "div" } }, + { "p, div", new[] { "p", "div" } }, + { "p,div ", new[] { "p", "div" } }, + { "p, div ", new[] { "p", "div" } }, + { " p, div ", new[] { "p", "div" } }, + { " p , div ", new[] { "p", "div" } }, + }; } } @@ -1519,75 +1352,87 @@ public class DynamicTestTagHelper : {typeof(AspNetCore.Razor.TagHelpers.TagHelpe { get { - var errorFormat = "Invalid tag helper bound property '{0}.{1}'. Tag helpers cannot bind to HTML " + - "attributes with name '{2}' because name starts with 'data-'."; - - // type, expectedAttributeDescriptors, expectedErrors - return new TheoryData, string[]> + // type, expectedAttributeDescriptors + return new TheoryData> { { typeof(InvalidBoundAttribute), - Enumerable.Empty(), new[] { - string.Format( - errorFormat, - typeof(InvalidBoundAttribute).FullName, - nameof(InvalidBoundAttribute.DataSomething), - "data-something") + ITagHelperBoundAttributeDescriptorBuilder.Create(typeof(InvalidBoundAttribute).FullName) + .Name("data-something") + .PropertyName(nameof(InvalidBoundAttribute.DataSomething)) + .TypeName(typeof(string).FullName) + .AddDiagnostic( + AspNetCore.Razor.Evolution.RazorDiagnosticFactory.CreateTagHelper_InvalidBoundAttributeNameStartsWith( + typeof(InvalidBoundAttribute).FullName, + nameof(InvalidBoundAttribute.DataSomething), + "data-something")) + .Build() } }, { typeof(InvalidBoundAttributeWithValid), new[] { - CreateTagHelperAttributeDescriptor( - "int-attribute", - typeof(InvalidBoundAttributeWithValid) - .GetProperty(nameof(InvalidBoundAttributeWithValid.IntAttribute))) - }, - new[] - { - string.Format( - errorFormat, - typeof(InvalidBoundAttributeWithValid).FullName, - nameof(InvalidBoundAttributeWithValid.DataSomething), - "data-something") + ITagHelperBoundAttributeDescriptorBuilder.Create(typeof(InvalidBoundAttributeWithValid).FullName) + .Name("data-something") + .PropertyName(nameof(InvalidBoundAttributeWithValid.DataSomething)) + .TypeName(typeof(string).FullName) + .AddDiagnostic( + AspNetCore.Razor.Evolution.RazorDiagnosticFactory.CreateTagHelper_InvalidBoundAttributeNameStartsWith( + typeof(InvalidBoundAttributeWithValid).FullName, + nameof(InvalidBoundAttributeWithValid.DataSomething), + "data-something")) + .Build(), + ITagHelperBoundAttributeDescriptorBuilder.Create(typeof(InvalidBoundAttributeWithValid).FullName) + .Name("int-attribute") + .PropertyName(nameof(InvalidBoundAttributeWithValid.IntAttribute)) + .TypeName(typeof(int).FullName) + .Build(), } }, { typeof(OverriddenInvalidBoundAttributeWithValid), new[] { - CreateTagHelperAttributeDescriptor( - "valid-something", - typeof(OverriddenInvalidBoundAttributeWithValid) - .GetProperty(nameof(OverriddenInvalidBoundAttributeWithValid.DataSomething))) - }, - new string[0] + ITagHelperBoundAttributeDescriptorBuilder.Create(typeof(OverriddenInvalidBoundAttributeWithValid).FullName) + .Name("valid-something") + .PropertyName(nameof(OverriddenInvalidBoundAttributeWithValid.DataSomething)) + .TypeName(typeof(string).FullName) + .Build() + } }, { typeof(OverriddenValidBoundAttributeWithInvalid), - Enumerable.Empty(), new[] { - string.Format( - errorFormat, - typeof(OverriddenValidBoundAttributeWithInvalid).FullName, - nameof(OverriddenValidBoundAttributeWithInvalid.ValidSomething), - "data-something") + ITagHelperBoundAttributeDescriptorBuilder.Create(typeof(OverriddenValidBoundAttributeWithInvalid).FullName) + .Name("data-something") + .PropertyName(nameof(OverriddenValidBoundAttributeWithInvalid.ValidSomething)) + .TypeName(typeof(string).FullName) + .AddDiagnostic( + AspNetCore.Razor.Evolution.RazorDiagnosticFactory.CreateTagHelper_InvalidBoundAttributeNameStartsWith( + typeof(OverriddenValidBoundAttributeWithInvalid).FullName, + nameof(OverriddenValidBoundAttributeWithInvalid.ValidSomething), + "data-something")) + .Build() } }, { typeof(OverriddenValidBoundAttributeWithInvalidUpperCase), - Enumerable.Empty(), new[] { - string.Format( - errorFormat, - typeof(OverriddenValidBoundAttributeWithInvalidUpperCase).FullName, - nameof(OverriddenValidBoundAttributeWithInvalidUpperCase.ValidSomething), - "DATA-SOMETHING") + ITagHelperBoundAttributeDescriptorBuilder.Create(typeof(OverriddenValidBoundAttributeWithInvalidUpperCase).FullName) + .Name("DATA-SOMETHING") + .PropertyName(nameof(OverriddenValidBoundAttributeWithInvalidUpperCase.ValidSomething)) + .TypeName(typeof(string).FullName) + .AddDiagnostic( + AspNetCore.Razor.Evolution.RazorDiagnosticFactory.CreateTagHelper_InvalidBoundAttributeNameStartsWith( + typeof(OverriddenValidBoundAttributeWithInvalidUpperCase).FullName, + nameof(OverriddenValidBoundAttributeWithInvalidUpperCase.ValidSomething), + "DATA-SOMETHING")) + .Build() } }, }; @@ -1596,234 +1441,22 @@ public class DynamicTestTagHelper : {typeof(AspNetCore.Razor.TagHelpers.TagHelpe [Theory] [MemberData(nameof(InvalidTagHelperAttributeDescriptorData))] - public void CreateDescriptors_DoesNotAllowDataDashAttributes( + public void CreateDescriptor_DoesNotAllowDataDashAttributes( Type type, - IEnumerable expectedAttributeDescriptors, - string[] expectedErrors) + IEnumerable expectedAttributeDescriptors) { // Arrange - var errorSink = new ErrorSink(); var factory = new DefaultTagHelperDescriptorFactory(Compilation, designTime: false); var typeSymbol = Compilation.GetTypeByMetadataName(type.FullName); // Act - var descriptors = factory.CreateDescriptors(typeSymbol, errorSink); + var descriptor = factory.CreateDescriptor(typeSymbol); // Assert - var actualErrors = errorSink.Errors.ToArray(); - Assert.Equal(expectedErrors.Length, actualErrors.Length); - - for (var i = 0; i < actualErrors.Length; i++) - { - var actualError = actualErrors[i]; - Assert.Equal(0, actualError.Length); - Assert.Equal(SourceLocation.Zero, actualError.Location); - Assert.Equal(expectedErrors[i], actualError.Message, StringComparer.Ordinal); - } - - var actualDescriptor = Assert.Single(descriptors); Assert.Equal( expectedAttributeDescriptors, - actualDescriptor.Attributes, - TagHelperAttributeDescriptorComparer.Default); - } - - // tagTelperType, expectedAttributeDescriptors, expectedErrorMessages - public static TheoryData, string[]> TagHelperWithPrefixData - { - get - { - Func onError = (typeName, propertyName) => - $"Invalid tag helper bound property '{ typeName }.{ propertyName }'. " + - $"'{ typeof(AspNetCore.Razor.TagHelpers.HtmlAttributeNameAttribute).FullName }." + - $"{ nameof(AspNetCore.Razor.TagHelpers.HtmlAttributeNameAttribute.DictionaryAttributePrefix) }' must be null unless " + - "property type implements 'IDictionary'."; - var dictionaryNamespace = typeof(IDictionary<,>).FullName; - dictionaryNamespace = dictionaryNamespace.Substring(0, dictionaryNamespace.IndexOf('`')); - - // tagTelperType, expectedAttributeDescriptors, expectedErrorMessages - return new TheoryData, string[]> - { - { - typeof(DefaultValidHtmlAttributePrefix), - new[] - { - new TagHelperAttributeDescriptor - { - Name = "dictionary-property", - PropertyName = nameof(DefaultValidHtmlAttributePrefix.DictionaryProperty), - TypeName = $"{dictionaryNamespace}" - }, - new TagHelperAttributeDescriptor - { - Name = "dictionary-property-", - PropertyName = nameof(DefaultValidHtmlAttributePrefix.DictionaryProperty), - TypeName = typeof(string).FullName, - IsIndexer = true - } - }, - new string[0] - }, - { - typeof(SingleValidHtmlAttributePrefix), - new[] - { - new TagHelperAttributeDescriptor - { - Name = "valid-name", - PropertyName = nameof(SingleValidHtmlAttributePrefix.DictionaryProperty), - TypeName = $"{dictionaryNamespace}" - }, - new TagHelperAttributeDescriptor - { - Name = "valid-name-", - PropertyName = nameof(SingleValidHtmlAttributePrefix.DictionaryProperty), - TypeName = typeof(string).FullName, - IsIndexer = true - } - }, - new string[0] - }, - { - typeof(MultipleValidHtmlAttributePrefix), - new[] - { - new TagHelperAttributeDescriptor - { - Name = "valid-name1", - PropertyName = nameof(MultipleValidHtmlAttributePrefix.DictionaryProperty), - TypeName = $"{typeof(Dictionary<,>).Namespace}.Dictionary" - }, - new TagHelperAttributeDescriptor - { - Name = "valid-name2", - PropertyName = nameof(MultipleValidHtmlAttributePrefix.DictionarySubclassProperty), - TypeName = typeof(DictionarySubclass).FullName - }, - new TagHelperAttributeDescriptor - { - Name = "valid-name3", - PropertyName = nameof(MultipleValidHtmlAttributePrefix.DictionaryWithoutParameterlessConstructorProperty), - TypeName = typeof(DictionaryWithoutParameterlessConstructor).FullName - }, - new TagHelperAttributeDescriptor - { - Name = "valid-name4", - PropertyName = nameof(MultipleValidHtmlAttributePrefix.GenericDictionarySubclassProperty), - TypeName = typeof(GenericDictionarySubclass).Namespace + ".GenericDictionarySubclass" - }, - new TagHelperAttributeDescriptor - { - Name = "valid-name5", - PropertyName = nameof(MultipleValidHtmlAttributePrefix.SortedDictionaryProperty), - TypeName = typeof(SortedDictionary).Namespace + ".SortedDictionary" - }, - new TagHelperAttributeDescriptor - { - Name = "valid-name6", - PropertyName = nameof(MultipleValidHtmlAttributePrefix.StringProperty), - TypeName = typeof(string).FullName, - IsStringProperty = true, - }, - new TagHelperAttributeDescriptor - { - Name = "valid-prefix1-", - PropertyName = nameof(MultipleValidHtmlAttributePrefix.DictionaryProperty), - TypeName = typeof(object).FullName, - IsIndexer = true - }, - new TagHelperAttributeDescriptor - { - Name = "valid-prefix2-", - PropertyName = nameof(MultipleValidHtmlAttributePrefix.DictionarySubclassProperty), - TypeName = typeof(string).FullName, - IsIndexer = true - }, - new TagHelperAttributeDescriptor - { - Name = "valid-prefix3-", - PropertyName = nameof(MultipleValidHtmlAttributePrefix.DictionaryWithoutParameterlessConstructorProperty), - TypeName = typeof(string).FullName, - IsIndexer = true - }, - new TagHelperAttributeDescriptor - { - Name = "valid-prefix4-", - PropertyName = nameof(MultipleValidHtmlAttributePrefix.GenericDictionarySubclassProperty), - TypeName = typeof(object).FullName, - IsIndexer = true - }, - new TagHelperAttributeDescriptor - { - Name = "valid-prefix5-", - PropertyName = nameof(MultipleValidHtmlAttributePrefix.SortedDictionaryProperty), - TypeName = typeof(int).FullName, - IsIndexer = true - }, - new TagHelperAttributeDescriptor - { - Name = "get-only-dictionary-property-", - PropertyName = nameof(MultipleValidHtmlAttributePrefix.GetOnlyDictionaryProperty), - TypeName = typeof(int).FullName, - IsIndexer = true - }, - new TagHelperAttributeDescriptor - { - Name = "valid-prefix6", - PropertyName = nameof(MultipleValidHtmlAttributePrefix.GetOnlyDictionaryPropertyWithAttributePrefix), - TypeName = typeof(string).FullName, - IsIndexer = true - } - }, - new string[0] - }, - { - typeof(SingleInvalidHtmlAttributePrefix), - Enumerable.Empty(), - new[] - { - onError( - typeof(SingleInvalidHtmlAttributePrefix).FullName, - nameof(SingleInvalidHtmlAttributePrefix.StringProperty)), - } - }, - { - typeof(MultipleInvalidHtmlAttributePrefix), - new[] - { - new TagHelperAttributeDescriptor - { - Name = "valid-name1", - PropertyName = nameof(MultipleInvalidHtmlAttributePrefix.LongProperty), - TypeName = typeof(long).FullName - } - }, - new[] - { - onError( - typeof(MultipleInvalidHtmlAttributePrefix).FullName, - nameof(MultipleInvalidHtmlAttributePrefix.DictionaryOfIntProperty)), - onError( - typeof(MultipleInvalidHtmlAttributePrefix).FullName, - nameof(MultipleInvalidHtmlAttributePrefix.ReadOnlyDictionaryProperty)), - onError( - typeof(MultipleInvalidHtmlAttributePrefix).FullName, - nameof(MultipleInvalidHtmlAttributePrefix.IntProperty)), - onError( - typeof(MultipleInvalidHtmlAttributePrefix).FullName, - nameof(MultipleInvalidHtmlAttributePrefix.DictionaryOfIntSubclassProperty)), - onError( - typeof(MultipleInvalidHtmlAttributePrefix).FullName, - nameof(MultipleInvalidHtmlAttributePrefix.GetOnlyDictionaryAttributePrefix)), - $"Invalid tag helper bound property '{ typeof(MultipleInvalidHtmlAttributePrefix).FullName }." + - $"{ nameof(MultipleInvalidHtmlAttributePrefix.GetOnlyDictionaryPropertyWithAttributeName) }'. " + - $"'{ typeof(AspNetCore.Razor.TagHelpers.HtmlAttributeNameAttribute).FullName }." + - $"{ nameof(AspNetCore.Razor.TagHelpers.HtmlAttributeNameAttribute.Name) }' must be null or empty if property has " + - "no public setter.", - } - }, - }; - } + descriptor.BoundAttributes, + BoundAttributeDescriptorComparer.Default); } public static TheoryData ValidAttributeNameData @@ -1844,27 +1477,25 @@ public class DynamicTestTagHelper : {typeof(AspNetCore.Razor.TagHelpers.TagHelpe [Theory] [MemberData(nameof(ValidAttributeNameData))] - public void ValidateTagHelperAttributeDescriptor_WithValidName_ReturnsTrue(string name) + public void CreateDescriptor_WithValidAttributeName_HasNoErrors(string name) { // Arrange - var descriptor = new TagHelperAttributeDescriptor - { - Name = name, - PropertyName = "ValidProperty", - TypeName = "PropertyType" - }; - var errorSink = new ErrorSink(); - var typeSymbol = Compilation.GetTypeByMetadataName(typeof(MultiTagTagHelper).FullName); + var text = $@" + public class DynamicTestTagHelper : {typeof(AspNetCore.Razor.TagHelpers.TagHelper).FullName} + {{ + [{typeof(AspNetCore.Razor.TagHelpers.HtmlAttributeNameAttribute).FullName}(""{name}"")] + public string SomeAttribute {{ get; set; }} + }}"; + var syntaxTree = CSharpSyntaxTree.ParseText(text); + var compilation = TestCompilation.Create(syntaxTree); + var tagHelperType = compilation.GetTypeByMetadataName("DynamicTestTagHelper"); + var factory = new DefaultTagHelperDescriptorFactory(compilation, designTime: false); // Act - var result = DefaultTagHelperDescriptorFactory.ValidateTagHelperAttributeDescriptor( - descriptor, - typeSymbol, - errorSink); + var descriptor = factory.CreateDescriptor(tagHelperType); // Assert - Assert.True(result); - Assert.Empty(errorSink.Errors); + Assert.False(descriptor.HasAnyErrors); } public static TheoryData ValidAttributePrefixData @@ -1886,28 +1517,25 @@ public class DynamicTestTagHelper : {typeof(AspNetCore.Razor.TagHelpers.TagHelpe [Theory] [MemberData(nameof(ValidAttributePrefixData))] - public void ValidateTagHelperAttributeDescriptor_WithValidPrefix_ReturnsTrue(string prefix) + public void CreateDescriptor_WithValidAttributePrefix_HasNoErrors(string prefix) { // Arrange - var descriptor = new TagHelperAttributeDescriptor - { - Name = prefix, - PropertyName = "ValidProperty", - TypeName = "PropertyType", - IsIndexer = true - }; - var errorSink = new ErrorSink(); - var typeSymbol = Compilation.GetTypeByMetadataName(typeof(MultiTagTagHelper).FullName); + var text = $@" + public class DynamicTestTagHelper : {typeof(AspNetCore.Razor.TagHelpers.TagHelper).FullName} + {{ + [{typeof(AspNetCore.Razor.TagHelpers.HtmlAttributeNameAttribute).FullName}({nameof(AspNetCore.Razor.TagHelpers.HtmlAttributeNameAttribute.DictionaryAttributePrefix)} = ""{prefix}"")] + public System.Collections.Generic.IDictionary SomeAttribute {{ get; set; }} + }}"; + var syntaxTree = CSharpSyntaxTree.ParseText(text); + var compilation = TestCompilation.Create(syntaxTree); + var tagHelperType = compilation.GetTypeByMetadataName("DynamicTestTagHelper"); + var factory = new DefaultTagHelperDescriptorFactory(compilation, designTime: false); // Act - var result = DefaultTagHelperDescriptorFactory.ValidateTagHelperAttributeDescriptor( - descriptor, - typeSymbol, - errorSink); + var descriptor = factory.CreateDescriptor(tagHelperType); // Assert - Assert.True(result); - Assert.Empty(errorSink.Errors); + Assert.False(descriptor.HasAnyErrors); } // name, expectedErrorMessages @@ -1915,16 +1543,15 @@ public class DynamicTestTagHelper : {typeof(AspNetCore.Razor.TagHelpers.TagHelpe { get { - Func onNameError = (invalidText, invalidCharacter) => "Invalid tag helper " + - $"bound property '{ typeof(MultiTagTagHelper).FullName }.InvalidProperty'. Tag helpers cannot " + - $"bind to HTML attributes with name '{ invalidText }' because name contains a " + - $"'{ invalidCharacter }' character."; - var whitespaceErrorString = "Invalid tag helper bound property " + - $"'{ typeof(MultiTagTagHelper).FullName }.InvalidProperty'. Tag helpers cannot bind to HTML " + - "attributes with a whitespace name."; - Func onDataError = invalidText => "Invalid tag helper bound property " + - $"'{ typeof(MultiTagTagHelper).FullName }.InvalidProperty'. Tag helpers cannot bind to HTML " + - $"attributes with name '{ invalidText }' because name starts with 'data-'."; + Func onNameError = (invalidText, invalidCharacter) => + "Invalid tag helper bound property 'DynamicTestTagHelper.InvalidProperty'. Tag helpers " + + $"cannot bind to HTML attributes with name '{invalidText}' because the name contains a '{invalidCharacter}' character."; + var whitespaceErrorString = + "Invalid tag helper bound property 'DynamicTestTagHelper.InvalidProperty'. Tag helpers cannot " + + "bind to HTML attributes with a null or empty name."; + Func onDataError = invalidText => + "Invalid tag helper bound property 'DynamicTestTagHelper.InvalidProperty'. Tag helpers cannot bind "+ + $"to HTML attributes with name '{invalidText}' because the name starts with 'data-'."; return GetInvalidNameOrPrefixData(onNameError, whitespaceErrorString, onDataError); } @@ -1932,37 +1559,27 @@ public class DynamicTestTagHelper : {typeof(AspNetCore.Razor.TagHelpers.TagHelpe [Theory] [MemberData(nameof(InvalidAttributeNameData))] - public void ValidateTagHelperAttributeDescriptor_WithInvalidName_AddsExpectedErrors( - string name, - string[] expectedErrorMessages) + public void CreateDescriptor_WithInvalidAttributeName_HasErrors(string name, string[] expectedErrorMessages) { // Arrange - var descriptor = new TagHelperAttributeDescriptor - { - Name = name, - PropertyName = "InvalidProperty", - TypeName = "PropertyType" - }; - var errorSink = new ErrorSink(); - var typeSymbol = Compilation.GetTypeByMetadataName(typeof(MultiTagTagHelper).FullName); + name = name.Replace("\n", "\\n").Replace("\r", "\\r").Replace("\"", "\\\""); + var text = $@" + public class DynamicTestTagHelper : {typeof(AspNetCore.Razor.TagHelpers.TagHelper).FullName} + {{ + [{typeof(AspNetCore.Razor.TagHelpers.HtmlAttributeNameAttribute).FullName}(""{name}"")] + public string InvalidProperty {{ get; set; }} + }}"; + var syntaxTree = CSharpSyntaxTree.ParseText(text); + var compilation = TestCompilation.Create(syntaxTree); + var tagHelperType = compilation.GetTypeByMetadataName("DynamicTestTagHelper"); + var factory = new DefaultTagHelperDescriptorFactory(compilation, designTime: false); // Act - var result = DefaultTagHelperDescriptorFactory.ValidateTagHelperAttributeDescriptor( - descriptor, - typeSymbol, - errorSink); + var descriptor = factory.CreateDescriptor(tagHelperType); // Assert - Assert.False(result); - - var errors = errorSink.Errors.ToArray(); - Assert.Equal(expectedErrorMessages.Length, errors.Length); - for (var i = 0; i < expectedErrorMessages.Length; i++) - { - Assert.Equal(0, errors[i].Length); - Assert.Equal(SourceLocation.Zero, errors[i].Location); - Assert.Equal(expectedErrorMessages[i], errors[i].Message, StringComparer.Ordinal); - } + var errorMessages = descriptor.GetAllDiagnostics().Select(diagnostic => diagnostic.GetMessage()); + Assert.Equal(expectedErrorMessages, errorMessages); } // prefix, expectedErrorMessages @@ -1970,16 +1587,14 @@ public class DynamicTestTagHelper : {typeof(AspNetCore.Razor.TagHelpers.TagHelpe { get { - Func onPrefixError = (invalidText, invalidCharacter) => "Invalid tag helper " + - $"bound property '{ typeof(MultiTagTagHelper).FullName }.InvalidProperty'. Tag helpers cannot " + - $"bind to HTML attributes with prefix '{ invalidText }' because prefix contains a " + - $"'{ invalidCharacter }' character."; - var whitespaceErrorString = "Invalid tag helper bound property " + - $"'{ typeof(MultiTagTagHelper).FullName }.InvalidProperty'. Tag helpers cannot bind to HTML " + - "attributes with a whitespace prefix."; - Func onDataError = invalidText => "Invalid tag helper bound property " + - $"'{ typeof(MultiTagTagHelper).FullName }.InvalidProperty'. Tag helpers cannot bind to HTML " + - $"attributes with prefix '{ invalidText }' because prefix starts with 'data-'."; + Func onPrefixError = (invalidText, invalidCharacter) => + "Invalid tag helper bound property 'DynamicTestTagHelper.InvalidProperty'. Tag helpers "+ + $"cannot bind to HTML attributes with prefix '{invalidText}' because the prefix contains a '{invalidCharacter}' character."; + var whitespaceErrorString = + "Invalid tag helper bound property 'DynamicTestTagHelper.InvalidProperty'. Tag helpers cannot bind to HTML attributes with a null or empty name."; + Func onDataError = invalidText => + "Invalid tag helper bound property 'DynamicTestTagHelper.InvalidProperty'. Tag helpers cannot bind to HTML attributes "+ + $"with prefix '{invalidText}' because the prefix starts with 'data-'."; return GetInvalidNameOrPrefixData(onPrefixError, whitespaceErrorString, onDataError); } @@ -1987,38 +1602,27 @@ public class DynamicTestTagHelper : {typeof(AspNetCore.Razor.TagHelpers.TagHelpe [Theory] [MemberData(nameof(InvalidAttributePrefixData))] - public void ValidateTagHelperAttributeDescriptor_WithInvalidPrefix_AddsExpectedErrors( - string prefix, - string[] expectedErrorMessages) + public void CreateDescriptor_WithInvalidAttributePrefix_HasErrors(string prefix, string[] expectedErrorMessages) { // Arrange - var descriptor = new TagHelperAttributeDescriptor - { - Name = prefix, - PropertyName = "InvalidProperty", - TypeName = "ValuesType", - IsIndexer = true - }; - var errorSink = new ErrorSink(); - var typeSymbol = Compilation.GetTypeByMetadataName(typeof(MultiTagTagHelper).FullName); + prefix = prefix.Replace("\n", "\\n").Replace("\r", "\\r").Replace("\"", "\\\""); + var text = $@" + public class DynamicTestTagHelper : {typeof(AspNetCore.Razor.TagHelpers.TagHelper).FullName} + {{ + [{typeof(AspNetCore.Razor.TagHelpers.HtmlAttributeNameAttribute).FullName}({nameof(AspNetCore.Razor.TagHelpers.HtmlAttributeNameAttribute.DictionaryAttributePrefix)} = ""{prefix}"")] + public System.Collections.Generic.IDictionary InvalidProperty {{ get; set; }} + }}"; + var syntaxTree = CSharpSyntaxTree.ParseText(text); + var compilation = TestCompilation.Create(syntaxTree); + var tagHelperType = compilation.GetTypeByMetadataName("DynamicTestTagHelper"); + var factory = new DefaultTagHelperDescriptorFactory(compilation, designTime: false); // Act - var result = DefaultTagHelperDescriptorFactory.ValidateTagHelperAttributeDescriptor( - descriptor, - typeSymbol, - errorSink); + var descriptor = factory.CreateDescriptor(tagHelperType); // Assert - Assert.False(result); - - var errors = errorSink.Errors.ToArray(); - Assert.Equal(expectedErrorMessages.Length, errors.Length); - for (var i = 0; i < expectedErrorMessages.Length; i++) - { - Assert.Equal(0, errors[i].Length); - Assert.Equal(SourceLocation.Zero, errors[i].Location); - Assert.Equal(expectedErrorMessages[i], errors[i].Message, StringComparer.Ordinal); - } + var errorMessages = descriptor.GetAllDiagnostics().Select(diagnostic => diagnostic.GetMessage()); + Assert.Equal(expectedErrorMessages, errorMessages); } public static TheoryData InvalidRestrictChildrenNameData @@ -2026,16 +1630,13 @@ public class DynamicTestTagHelper : {typeof(AspNetCore.Razor.TagHelpers.TagHelpe get { var nullOrWhiteSpaceError = - Resources.FormatTagHelperDescriptorFactory_InvalidRestrictChildrenAttributeNameNullWhitespace( - typeof(AspNetCore.Razor.TagHelpers.RestrictChildrenAttribute).FullName, - "SomeTagHelper"); + AspNetCore.Razor.Evolution.Resources.FormatInvalidRestrictedChildNullOrWhitespace("DynamicTestTagHelper"); return GetInvalidNameOrPrefixData( onNameError: (invalidInput, invalidCharacter) => - Resources.FormatTagHelperDescriptorFactory_InvalidRestrictChildrenAttributeName( - typeof(AspNetCore.Razor.TagHelpers.RestrictChildrenAttribute).FullName, + AspNetCore.Razor.Evolution.Resources.FormatInvalidRestrictedChild( invalidInput, - "SomeTagHelper", + "DynamicTestTagHelper", invalidCharacter), whitespaceErrorString: nullOrWhiteSpaceError, onDataError: null); @@ -2044,18 +1645,26 @@ public class DynamicTestTagHelper : {typeof(AspNetCore.Razor.TagHelpers.TagHelpe [Theory] [MemberData(nameof(InvalidRestrictChildrenNameData))] - public void GetValidAllowedChildren_AddsExpectedErrors(string name, string[] expectedErrorMessages) + public void CreateDescriptor_WithInvalidAllowedChildren_HasErrors(string name, string[] expectedErrorMessages) { // Arrange - var errorSink = new ErrorSink(); - var expectedErrors = expectedErrorMessages.Select( - message => new RazorError(message, SourceLocation.Zero, 0)); + name = name.Replace("\n", "\\n").Replace("\r", "\\r").Replace("\"", "\\\""); + var text = $@" + [{typeof(AspNetCore.Razor.TagHelpers.RestrictChildrenAttribute).FullName}(""{name}"")] + public class DynamicTestTagHelper : {typeof(AspNetCore.Razor.TagHelpers.TagHelper).FullName} + {{ + }}"; + var syntaxTree = CSharpSyntaxTree.ParseText(text); + var compilation = TestCompilation.Create(syntaxTree); + var tagHelperType = compilation.GetTypeByMetadataName("DynamicTestTagHelper"); + var factory = new DefaultTagHelperDescriptorFactory(compilation, designTime: false); // Act - DefaultTagHelperDescriptorFactory.GetValidAllowedChildren(new[] { name }, "SomeTagHelper", errorSink); + var descriptor = factory.CreateDescriptor(tagHelperType); // Assert - Assert.Equal(expectedErrors, errorSink.Errors); + var errorMessages = descriptor.GetAllDiagnostics().Select(diagnostic => diagnostic.GetMessage()); + Assert.Equal(expectedErrorMessages, errorMessages); } public static TheoryData InvalidParentTagData @@ -2063,13 +1672,11 @@ public class DynamicTestTagHelper : {typeof(AspNetCore.Razor.TagHelpers.TagHelpe get { var nullOrWhiteSpaceError = - Resources.FormatHtmlTargetElementAttribute_NameCannotBeNullOrWhitespace( - Resources.TagHelperDescriptorFactory_ParentTag); + AspNetCore.Razor.Evolution.Resources.InvalidTargetedParentTagNameNullOrWhitespace; return GetInvalidNameOrPrefixData( onNameError: (invalidInput, invalidCharacter) => - Resources.FormatHtmlTargetElementAttribute_InvalidName( - Resources.TagHelperDescriptorFactory_ParentTag.ToLower(), + AspNetCore.Razor.Evolution.Resources.FormatInvalidTargetedParentTagName( invalidInput, invalidCharacter), whitespaceErrorString: nullOrWhiteSpaceError, @@ -2079,71 +1686,234 @@ public class DynamicTestTagHelper : {typeof(AspNetCore.Razor.TagHelpers.TagHelpe [Theory] [MemberData(nameof(InvalidParentTagData))] - public void ValidateParentTagName_AddsExpectedErrors(string name, string[] expectedErrorMessages) + public void CreateDescriptor_WithInvalidParentTag_HasErrors(string name, string[] expectedErrorMessages) { // Arrange - var errorSink = new ErrorSink(); - var expectedErrors = expectedErrorMessages.Select( - message => new RazorError(message, SourceLocation.Zero, 0)); + name = name.Replace("\n", "\\n").Replace("\r", "\\r").Replace("\"", "\\\""); + var text = $@" + [{typeof(AspNetCore.Razor.TagHelpers.HtmlTargetElementAttribute).FullName}({nameof(AspNetCore.Razor.TagHelpers.HtmlTargetElementAttribute.ParentTag)} = ""{name}"")] + public class DynamicTestTagHelper : {typeof(AspNetCore.Razor.TagHelpers.TagHelper).FullName} + {{ + }}"; + var syntaxTree = CSharpSyntaxTree.ParseText(text); + var compilation = TestCompilation.Create(syntaxTree); + var tagHelperType = compilation.GetTypeByMetadataName("DynamicTestTagHelper"); + var factory = new DefaultTagHelperDescriptorFactory(compilation, designTime: false); // Act - DefaultTagHelperDescriptorFactory.ValidateParentTagName(name, errorSink); + var descriptor = factory.CreateDescriptor(tagHelperType); // Assert - Assert.Equal(expectedErrors, errorSink.Errors); + var errorMessages = descriptor.GetAllDiagnostics().Select(diagnostic => diagnostic.GetMessage()); + Assert.Equal(expectedErrorMessages, errorMessages); } [Fact] - public void CreateDescriptors_BuildsDescriptorsFromSimpleTypes() + public void CreateDescriptor_BuildsDescriptorsFromSimpleTypes() { // Arrange - var errorSink = new ErrorSink(); var objectAssemblyName = typeof(Enumerable).GetTypeInfo().Assembly.GetName().Name; - var expectedDescriptor = - CreateTagHelperDescriptor("enumerable", "System.Linq.Enumerable", objectAssemblyName); var factory = new DefaultTagHelperDescriptorFactory(Compilation, designTime: false); var typeSymbol = Compilation.GetTypeByMetadataName(typeof(Enumerable).FullName); + var expectedDescriptor = + CreateTagHelperDescriptor("enumerable", "System.Linq.Enumerable", typeSymbol.ContainingAssembly.Identity.Name); // Act - var descriptors = factory.CreateDescriptors(typeSymbol, errorSink); + var descriptor = factory.CreateDescriptor(typeSymbol); // Assert - Assert.Empty(errorSink.Errors); - var descriptor = Assert.Single(descriptors); - Assert.Equal(expectedDescriptor, descriptor, CaseSensitiveTagHelperDescriptorComparer.Default); + Assert.Equal(expectedDescriptor, descriptor, TagHelperDescriptorComparer.CaseSensitive); + } + + public static TheoryData TagHelperWithPrefixData + { + get + { + var dictionaryNamespace = typeof(IDictionary<,>).FullName; + dictionaryNamespace = dictionaryNamespace.Substring(0, dictionaryNamespace.IndexOf('`')); + + // tagTelperType, expectedAttributeDescriptors + return new TheoryData> + { + { + typeof(DefaultValidHtmlAttributePrefix), + new[] + { + ITagHelperBoundAttributeDescriptorBuilder.Create(typeof(DefaultValidHtmlAttributePrefix).FullName) + .Name("dictionary-property") + .PropertyName(nameof(DefaultValidHtmlAttributePrefix.DictionaryProperty)) + .TypeName($"{dictionaryNamespace}") + .AsDictionary("dictionary-property-", typeof(string).FullName) + .Build() + } + }, + { + typeof(SingleValidHtmlAttributePrefix), + new[] + { + ITagHelperBoundAttributeDescriptorBuilder.Create(typeof(SingleValidHtmlAttributePrefix).FullName) + .Name("valid-name") + .PropertyName(nameof(SingleValidHtmlAttributePrefix.DictionaryProperty)) + .TypeName($"{dictionaryNamespace}") + .AsDictionary("valid-name-", typeof(string).FullName) + .Build() + } + }, + { + typeof(MultipleValidHtmlAttributePrefix), + new[] + { + ITagHelperBoundAttributeDescriptorBuilder.Create(typeof(MultipleValidHtmlAttributePrefix).FullName) + .Name("valid-name1") + .PropertyName(nameof(MultipleValidHtmlAttributePrefix.DictionaryProperty)) + .TypeName($"{typeof(Dictionary<,>).Namespace}.Dictionary") + .AsDictionary("valid-prefix1-", typeof(object).FullName) + .Build(), + ITagHelperBoundAttributeDescriptorBuilder.Create(typeof(MultipleValidHtmlAttributePrefix).FullName) + .Name("valid-name2") + .PropertyName(nameof(MultipleValidHtmlAttributePrefix.DictionarySubclassProperty)) + .TypeName(typeof(DictionarySubclass).FullName) + .AsDictionary("valid-prefix2-", typeof(string).FullName) + .Build(), + ITagHelperBoundAttributeDescriptorBuilder.Create(typeof(MultipleValidHtmlAttributePrefix).FullName) + .Name("valid-name3") + .PropertyName(nameof(MultipleValidHtmlAttributePrefix.DictionaryWithoutParameterlessConstructorProperty)) + .TypeName(typeof(DictionaryWithoutParameterlessConstructor).FullName) + .AsDictionary("valid-prefix3-", typeof(string).FullName) + .Build(), + ITagHelperBoundAttributeDescriptorBuilder.Create(typeof(MultipleValidHtmlAttributePrefix).FullName) + .Name("valid-name4") + .PropertyName(nameof(MultipleValidHtmlAttributePrefix.GenericDictionarySubclassProperty)) + .TypeName(typeof(GenericDictionarySubclass).Namespace + ".GenericDictionarySubclass") + .AsDictionary("valid-prefix4-", typeof(object).FullName) + .Build(), + ITagHelperBoundAttributeDescriptorBuilder.Create(typeof(MultipleValidHtmlAttributePrefix).FullName) + .Name("valid-name5") + .PropertyName(nameof(MultipleValidHtmlAttributePrefix.SortedDictionaryProperty)) + .TypeName(typeof(SortedDictionary).Namespace + ".SortedDictionary") + .AsDictionary("valid-prefix5-", typeof(int).FullName) + .Build(), + ITagHelperBoundAttributeDescriptorBuilder.Create(typeof(MultipleValidHtmlAttributePrefix).FullName) + .Name("valid-name6") + .PropertyName(nameof(MultipleValidHtmlAttributePrefix.StringProperty)) + .TypeName(typeof(string).FullName) + .Build(), + ITagHelperBoundAttributeDescriptorBuilder.Create(typeof(MultipleValidHtmlAttributePrefix).FullName) + .PropertyName(nameof(MultipleValidHtmlAttributePrefix.GetOnlyDictionaryProperty)) + .TypeName($"{dictionaryNamespace}") + .AsDictionary("get-only-dictionary-property-", typeof(int).FullName) + .Build(), + ITagHelperBoundAttributeDescriptorBuilder.Create(typeof(MultipleValidHtmlAttributePrefix).FullName) + .PropertyName(nameof(MultipleValidHtmlAttributePrefix.GetOnlyDictionaryPropertyWithAttributePrefix)) + .TypeName($"{dictionaryNamespace}") + .AsDictionary("valid-prefix6", typeof(string).FullName) + .Build() + } + }, + { + typeof(SingleInvalidHtmlAttributePrefix), + new[] + { + ITagHelperBoundAttributeDescriptorBuilder.Create(typeof(SingleInvalidHtmlAttributePrefix).FullName) + .Name("valid-name") + .PropertyName(nameof(SingleInvalidHtmlAttributePrefix.StringProperty)) + .TypeName(typeof(string).FullName) + .AddDiagnostic( + RazorDiagnosticFactory.CreateTagHelper_InvalidAttributePrefixNotNull( + typeof(SingleInvalidHtmlAttributePrefix).FullName, + nameof(SingleInvalidHtmlAttributePrefix.StringProperty))) + .Build(), + } + }, + { + typeof(MultipleInvalidHtmlAttributePrefix), + new[] + { + ITagHelperBoundAttributeDescriptorBuilder.Create(typeof(MultipleInvalidHtmlAttributePrefix).FullName) + .Name("valid-name1") + .PropertyName(nameof(MultipleInvalidHtmlAttributePrefix.LongProperty)) + .TypeName(typeof(long).FullName) + .Build(), + ITagHelperBoundAttributeDescriptorBuilder.Create(typeof(MultipleInvalidHtmlAttributePrefix).FullName) + .Name("valid-name2") + .PropertyName(nameof(MultipleInvalidHtmlAttributePrefix.DictionaryOfIntProperty)) + .TypeName($"{typeof(Dictionary<,>).Namespace}.Dictionary") + .AsDictionary("valid-prefix2-", typeof(string).FullName) + .AddDiagnostic( + RazorDiagnosticFactory.CreateTagHelper_InvalidAttributePrefixNotNull( + typeof(MultipleInvalidHtmlAttributePrefix).FullName, + nameof(MultipleInvalidHtmlAttributePrefix.DictionaryOfIntProperty))) + .Build(), + ITagHelperBoundAttributeDescriptorBuilder.Create(typeof(MultipleInvalidHtmlAttributePrefix).FullName) + .Name("valid-name3") + .PropertyName(nameof(MultipleInvalidHtmlAttributePrefix.ReadOnlyDictionaryProperty)) + .TypeName($"{typeof(IReadOnlyDictionary<,>).Namespace}.IReadOnlyDictionary") + .AddDiagnostic( + RazorDiagnosticFactory.CreateTagHelper_InvalidAttributePrefixNotNull( + typeof(MultipleInvalidHtmlAttributePrefix).FullName, + nameof(MultipleInvalidHtmlAttributePrefix.ReadOnlyDictionaryProperty))) + .Build(), + ITagHelperBoundAttributeDescriptorBuilder.Create(typeof(MultipleInvalidHtmlAttributePrefix).FullName) + .Name("valid-name4") + .PropertyName(nameof(MultipleInvalidHtmlAttributePrefix.IntProperty)) + .TypeName(typeof(int).FullName) + .AddDiagnostic( + RazorDiagnosticFactory.CreateTagHelper_InvalidAttributePrefixNotNull( + typeof(MultipleInvalidHtmlAttributePrefix).FullName, + nameof(MultipleInvalidHtmlAttributePrefix.IntProperty))) + .Build(), + ITagHelperBoundAttributeDescriptorBuilder.Create(typeof(MultipleInvalidHtmlAttributePrefix).FullName) + .Name("valid-name5") + .PropertyName(nameof(MultipleInvalidHtmlAttributePrefix.DictionaryOfIntSubclassProperty)) + .TypeName(typeof(DictionaryOfIntSubclass).FullName) + .AsDictionary("valid-prefix5-", typeof(string).FullName) + .AddDiagnostic( + RazorDiagnosticFactory.CreateTagHelper_InvalidAttributePrefixNotNull( + typeof(MultipleInvalidHtmlAttributePrefix).FullName, + nameof(MultipleInvalidHtmlAttributePrefix.DictionaryOfIntSubclassProperty))) + .Build(), + ITagHelperBoundAttributeDescriptorBuilder.Create(typeof(MultipleInvalidHtmlAttributePrefix).FullName) + .PropertyName(nameof(MultipleInvalidHtmlAttributePrefix.GetOnlyDictionaryAttributePrefix)) + .TypeName($"{dictionaryNamespace}") + .AsDictionary("valid-prefix6", typeof(string).FullName) + .AddDiagnostic( + RazorDiagnosticFactory.CreateTagHelper_InvalidAttributePrefixNotNull( + typeof(MultipleInvalidHtmlAttributePrefix).FullName, + nameof(MultipleInvalidHtmlAttributePrefix.GetOnlyDictionaryAttributePrefix))) + .Build(), + ITagHelperBoundAttributeDescriptorBuilder.Create(typeof(MultipleInvalidHtmlAttributePrefix).FullName) + .PropertyName(nameof(MultipleInvalidHtmlAttributePrefix.GetOnlyDictionaryPropertyWithAttributeName)) + .TypeName($"{dictionaryNamespace}") + .AsDictionary("invalid-name7-", typeof(object).FullName) + .AddDiagnostic( + RazorDiagnosticFactory.CreateTagHelper_InvalidAttributePrefixNull( + typeof(MultipleInvalidHtmlAttributePrefix).FullName, + nameof(MultipleInvalidHtmlAttributePrefix.GetOnlyDictionaryPropertyWithAttributeName))) + .Build(), + } + }, + }; + } } [Theory] [MemberData(nameof(TagHelperWithPrefixData))] - public void CreateDescriptors_WithPrefixes_ReturnsExpectedAttributeDescriptors( + public void CreateDescriptor_WithPrefixes_ReturnsExpectedAttributeDescriptors( Type tagHelperType, - IEnumerable expectedAttributeDescriptors, - string[] expectedErrorMessages) + IEnumerable expectedAttributeDescriptors) { // Arrange - var errorSink = new ErrorSink(); var factory = new DefaultTagHelperDescriptorFactory(Compilation, designTime: false); var typeSymbol = Compilation.GetTypeByMetadataName(tagHelperType.FullName); // Act - var descriptors = factory.CreateDescriptors(typeSymbol, errorSink); + var descriptor = factory.CreateDescriptor(typeSymbol); // Assert - var errors = errorSink.Errors.ToArray(); - Assert.Equal(expectedErrorMessages.Length, errors.Length); - - for (var i = 0; i < errors.Length; i++) - { - Assert.Equal(0, errors[i].Length); - Assert.Equal(SourceLocation.Zero, errors[i].Location); - Assert.Equal(expectedErrorMessages[i], errors[i].Message, StringComparer.Ordinal); - } - - var descriptor = Assert.Single(descriptors); Assert.Equal( expectedAttributeDescriptors, - descriptor.Attributes, - TagHelperAttributeDescriptorComparer.Default); + descriptor.BoundAttributes, + BoundAttributeDescriptorComparer.CaseSensitive); } public static TheoryData HtmlConversionData @@ -2175,206 +1945,163 @@ public class DynamicTestTagHelper : {typeof(AspNetCore.Razor.TagHelpers.TagHelpe Assert.Equal(output, expectedOutput); } - public static TheoryData OutputElementHintData + public static TheoryData TagOutputHintData { get { - // tagHelperType, expectedDescriptors - return new TheoryData + // tagHelperType, expectedDescriptor + return new TheoryData { { - typeof(MulitpleDescriptorTagHelperWithOutputElementHint), - new[] - { - new TagHelperDescriptor - { - TagName = "a", - TypeName = typeof(MulitpleDescriptorTagHelperWithOutputElementHint).FullName, - AssemblyName = AssemblyName, - DesignTimeDescriptor = new TagHelperDesignTimeDescriptor - { - OutputElementHint = "div" - } - }, - new TagHelperDescriptor - { - TagName = "p", - TypeName = typeof(MulitpleDescriptorTagHelperWithOutputElementHint).FullName, - AssemblyName = AssemblyName, - DesignTimeDescriptor = new TagHelperDesignTimeDescriptor - { - OutputElementHint = "div" - } - } - } + typeof(MultipleDescriptorTagHelperWithOutputElementHint), + ITagHelperDescriptorBuilder.Create(typeof(MultipleDescriptorTagHelperWithOutputElementHint).FullName, AssemblyName) + .TagMatchingRule(builder => builder.RequireTagName("a")) + .TagMatchingRule(builder => builder.RequireTagName("p")) + .TagOutputHint("div") + .Build() }, { typeof(InheritedOutputElementHintTagHelper), - new[] - { - new TagHelperDescriptor - { - TagName = "inherited-output-element-hint", - TypeName = typeof(InheritedOutputElementHintTagHelper).FullName, - AssemblyName = AssemblyName, - DesignTimeDescriptor = null - } - } + ITagHelperDescriptorBuilder.Create(typeof(InheritedOutputElementHintTagHelper).FullName, AssemblyName) + .TagMatchingRule(builder => builder.RequireTagName("inherited-output-element-hint")) + .Build() }, { typeof(OutputElementHintTagHelper), - new[] - { - new TagHelperDescriptor - { - TagName = "output-element-hint", - TypeName = typeof(OutputElementHintTagHelper).FullName, - AssemblyName = AssemblyName, - DesignTimeDescriptor = new TagHelperDesignTimeDescriptor - { - OutputElementHint = "hinted-value" - } - } - } + ITagHelperDescriptorBuilder.Create(typeof(OutputElementHintTagHelper).FullName, AssemblyName) + .TagMatchingRule(builder => builder.RequireTagName("output-element-hint")) + .TagOutputHint("hinted-value") + .Build() }, { typeof(OverriddenOutputElementHintTagHelper), - new[] - { - new TagHelperDescriptor - { - TagName = "overridden-output-element-hint", - TypeName = typeof(OverriddenOutputElementHintTagHelper).FullName, - AssemblyName = AssemblyName, - DesignTimeDescriptor = new TagHelperDesignTimeDescriptor - { - OutputElementHint = "overridden" - } - } - } + ITagHelperDescriptorBuilder.Create(typeof(OverriddenOutputElementHintTagHelper).FullName, AssemblyName) + .TagMatchingRule(builder => builder.RequireTagName("overridden-output-element-hint")) + .TagOutputHint("overridden") + .Build() }, }; } } [Theory] - [MemberData(nameof(OutputElementHintData))] - public void CreateDescriptors_CreatesDesignTimeDescriptorsWithOutputElementHint( + [MemberData(nameof(TagOutputHintData))] + public void CreateDescriptor_CreatesDesignTimeDescriptorsWithOutputElementHint( Type tagHelperType, - TagHelperDescriptor[] expectedDescriptors) + TagHelperDescriptor expectedDescriptor) { // Arrange - var errorSink = new ErrorSink(); var factory = new DefaultTagHelperDescriptorFactory(Compilation, designTime: true); var typeSymbol = Compilation.GetTypeByMetadataName(tagHelperType.FullName); // Act - var descriptors = factory.CreateDescriptors(typeSymbol, errorSink); + var descriptor = factory.CreateDescriptor(typeSymbol); // Assert - Assert.Empty(errorSink.Errors); - - // We don't care about order. Mono returns reflected attributes differently so we need to ensure order - // doesn't matter by sorting. - descriptors = descriptors.OrderBy(descriptor => descriptor.TagName); - - Assert.Equal(expectedDescriptors, descriptors, CaseSensitiveTagHelperDescriptorComparer.Default); + Assert.Equal(expectedDescriptor, descriptor, TagHelperDescriptorComparer.CaseSensitive); } [Fact] - public void CreateDescriptors_CapturesRemarksAndSummaryOnTagHelperClass() + public void CreateDescriptor_CapturesDocumentationOnTagHelperClass() { // Arrange var errorSink = new ErrorSink(); var sytnaxTree = CSharpSyntaxTree.ParseText(@" -using Microsoft.AspNetCore.Razor.TagHelpers; + using Microsoft.AspNetCore.Razor.TagHelpers; -/// -/// The summary for . -/// -/// -/// Inherits from . -/// -[" + typeof(AspNetCore.Razor.TagHelpers.OutputElementHintAttribute).FullName + @"(""p"")] -public class DocumentedTagHelper : " + typeof(AspNetCore.Razor.TagHelpers.TagHelper).Name + @" -{ -}"); + /// + /// The summary for . + /// + /// + /// Inherits from . + /// + public class DocumentedTagHelper : " + typeof(AspNetCore.Razor.TagHelpers.TagHelper).Name + @" + { + }"); var compilation = TestCompilation.Create(sytnaxTree); var factory = new DefaultTagHelperDescriptorFactory(compilation, designTime: true); var typeSymbol = compilation.GetTypeByMetadataName("DocumentedTagHelper"); - var expectedDescriptor = new TagHelperDesignTimeDescriptor() - { - OutputElementHint = "p", - Summary = "The summary for .", - Remarks = "Inherits from .", - }; + var expectedDocumentation = +@" + + The summary for . + + + Inherits from . + + +"; // Act - var descriptors = factory.CreateDescriptors(typeSymbol, errorSink); + var descriptor = factory.CreateDescriptor(typeSymbol); // Assert - Assert.Empty(errorSink.Errors); - var descriptor = Assert.Single(descriptors); - Assert.Equal(expectedDescriptor, descriptor.DesignTimeDescriptor, TagHelperDesignTimeDescriptorComparer.Default); + Assert.Equal(expectedDocumentation, descriptor.Documentation); } [Fact] - public void CreateDescriptors_CapturesRemarksAndSummaryOnTagHelperProperties() + public void CreateDescriptor_CapturesDocumentationOnTagHelperProperties() { // Arrange var errorSink = new ErrorSink(); var sytnaxTree = CSharpSyntaxTree.ParseText(@" -using System.Collections.Generic; + using System.Collections.Generic; -public class DocumentedTagHelper : " + typeof(AspNetCore.Razor.TagHelpers.TagHelper).FullName + @" -{ - /// - /// This is of type . - /// - public string SummaryProperty { get; set; } + public class DocumentedTagHelper : " + typeof(AspNetCore.Razor.TagHelpers.TagHelper).FullName + @" + { + /// + /// This is of type . + /// + public string SummaryProperty { get; set; } - /// - /// The may be null. - /// - public int RemarksProperty { get; set; } + /// + /// The may be null. + /// + public int RemarksProperty { get; set; } - /// - /// This is a complex . - /// - /// - /// - /// - public List RemarksAndSummaryProperty { get; set; } -}"); + /// + /// This is a complex . + /// + /// + /// + /// + public List RemarksAndSummaryProperty { get; set; } + }"); var compilation = TestCompilation.Create(sytnaxTree); var factory = new DefaultTagHelperDescriptorFactory(compilation, designTime: true); var typeSymbol = compilation.GetTypeByMetadataName("DocumentedTagHelper"); - var expectedDescriptors = new[] + var expectedDocumentations = new[] { - new TagHelperAttributeDesignTimeDescriptor() - { - Summary = "This is of type .", - }, - new TagHelperAttributeDesignTimeDescriptor() - { - Remarks = "The may be null.", - }, - new TagHelperAttributeDesignTimeDescriptor() - { - Summary = "This is a complex .", - Remarks = "", - } - }; + +@" + + This is of type . + + +", +@" + + The may be null. + + +", +@" + + This is a complex . + + + + + +", + }; // Act - var descriptors = factory.CreateDescriptors(typeSymbol, errorSink); + var descriptor = factory.CreateDescriptor(typeSymbol); // Assert - Assert.Empty(errorSink.Errors); - var attributeDescriptors = descriptors - .SelectMany(descriptor => descriptor.Attributes) - .Select(descriptor => descriptor.DesignTimeDescriptor); - Assert.Equal(expectedDescriptors, attributeDescriptors, TagHelperAttributeDesignTimeDescriptorComparer.Default); + var documentations = descriptor.BoundAttributes.Select(boundAttribute => boundAttribute.Documentation); + Assert.Equal(expectedDocumentations, documentations); } private static TheoryData GetInvalidNameOrPrefixData( @@ -2389,157 +2116,61 @@ public class DocumentedTagHelper : " + typeof(AspNetCore.Razor.TagHelpers.TagHel { "hello!", new[] { onNameError("hello!", "!") } }, { "!hello", new[] { onNameError("!hello", "!") } }, { "he!lo", new[] { onNameError("he!lo", "!") } }, - { - "!he!lo!", - new[] - { - onNameError("!he!lo!", "!"), - onNameError("!he!lo!", "!"), - onNameError("!he!lo!", "!"), - } - }, + { "!he!lo!", new[] { onNameError("!he!lo!", "!") } }, { "@", new[] { onNameError("@", "@") } }, { "hello@", new[] { onNameError("hello@", "@") } }, { "@hello", new[] { onNameError("@hello", "@") } }, { "he@lo", new[] { onNameError("he@lo", "@") } }, - { - "@he@lo@", - new[] - { - onNameError("@he@lo@", "@"), - onNameError("@he@lo@", "@"), - onNameError("@he@lo@", "@"), - } - }, + { "@he@lo@", new[] { onNameError("@he@lo@", "@") } }, { "/", new[] { onNameError("/", "/") } }, { "hello/", new[] { onNameError("hello/", "/") } }, { "/hello", new[] { onNameError("/hello", "/") } }, { "he/lo", new[] { onNameError("he/lo", "/") } }, - { - "/he/lo/", - new[] - { - onNameError("/he/lo/", "/"), - onNameError("/he/lo/", "/"), - onNameError("/he/lo/", "/"), - } - }, + { "/he/lo/", new[] { onNameError("/he/lo/", "/") } }, { "<", new[] { onNameError("<", "<") } }, { "hello<", new[] { onNameError("hello<", "<") } }, { "", new[] { onNameError(">", ">") } }, { "hello>", new[] { onNameError("hello>", ">") } }, { ">hello", new[] { onNameError(">hello", ">") } }, { "he>lo", new[] { onNameError("he>lo", ">") } }, - { - ">he>lo>", - new[] - { - onNameError(">he>lo>", ">"), - onNameError(">he>lo>", ">"), - onNameError(">he>lo>", ">"), - } - }, + { ">he>lo>", new[] { onNameError(">he>lo>", ">") } }, { "]", new[] { onNameError("]", "]") } }, { "hello]", new[] { onNameError("hello]", "]") } }, { "]hello", new[] { onNameError("]hello", "]") } }, { "he]lo", new[] { onNameError("he]lo", "]") } }, - { - "]he]lo]", - new[] - { - onNameError("]he]lo]", "]"), - onNameError("]he]lo]", "]"), - onNameError("]he]lo]", "]"), - } - }, + { "]he]lo]", new[] { onNameError("]he]lo]", "]") } }, { "=", new[] { onNameError("=", "=") } }, { "hello=", new[] { onNameError("hello=", "=") } }, { "=hello", new[] { onNameError("=hello", "=") } }, { "he=lo", new[] { onNameError("he=lo", "=") } }, - { - "=he=lo=", - new[] - { - onNameError("=he=lo=", "="), - onNameError("=he=lo=", "="), - onNameError("=he=lo=", "="), - } - }, + { "=he=lo=", new[] { onNameError("=he=lo=", "=") } }, { "\"", new[] { onNameError("\"", "\"") } }, { "hello\"", new[] { onNameError("hello\"", "\"") } }, { "\"hello", new[] { onNameError("\"hello", "\"") } }, { "he\"lo", new[] { onNameError("he\"lo", "\"") } }, - { - "\"he\"lo\"", - new[] - { - onNameError("\"he\"lo\"", "\""), - onNameError("\"he\"lo\"", "\""), - onNameError("\"he\"lo\"", "\""), - } - }, + { "\"he\"lo\"", new[] { onNameError("\"he\"lo\"", "\"") } }, { "'", new[] { onNameError("'", "'") } }, { "hello'", new[] { onNameError("hello'", "'") } }, { "'hello", new[] { onNameError("'hello", "'") } }, { "he'lo", new[] { onNameError("he'lo", "'") } }, - { - "'he'lo'", - new[] - { - onNameError("'he'lo'", "'"), - onNameError("'he'lo'", "'"), - onNameError("'he'lo'", "'"), - } - }, + { "'he'lo'", new[] { onNameError("'he'lo'", "'") } }, { "hello*", new[] { onNameError("hello*", "*") } }, { "*hello", new[] { onNameError("*hello", "*") } }, { "he*lo", new[] { onNameError("he*lo", "*") } }, - { - "*he*lo*", - new[] - { - onNameError("*he*lo*", "*"), - onNameError("*he*lo*", "*"), - onNameError("*he*lo*", "*"), - } - }, + { "*he*lo*", new[] { onNameError("*he*lo*", "*") } }, { Environment.NewLine, new[] { whitespaceErrorString } }, { "\t", new[] { whitespaceErrorString } }, { " \t ", new[] { whitespaceErrorString } }, @@ -2606,30 +2237,38 @@ public class DocumentedTagHelper : " + typeof(AspNetCore.Razor.TagHelpers.TagHel string tagName, string typeName, string assemblyName, - IEnumerable attributes = null, - IEnumerable requiredAttributes = null) + IEnumerable> attributes = null, + IEnumerable> ruleBuilders = null) { - return new TagHelperDescriptor - { - TagName = tagName, - TypeName = typeName, - AssemblyName = assemblyName, - Attributes = attributes ?? Enumerable.Empty(), - RequiredAttributes = requiredAttributes ?? Enumerable.Empty() - }; - } + var builder = ITagHelperDescriptorBuilder.Create(typeName, assemblyName); - private static TagHelperAttributeDescriptor CreateTagHelperAttributeDescriptor( - string name, - PropertyInfo propertyInfo) - { - return new TagHelperAttributeDescriptor + if (attributes != null) { - Name = name, - PropertyName = propertyInfo.Name, - TypeName = propertyInfo.PropertyType.FullName, - IsStringProperty = propertyInfo.PropertyType.FullName == typeof(string).FullName - }; + foreach (var attributeBuilder in attributes) + { + builder.BindAttribute(attributeBuilder); + } + } + + if (ruleBuilders != null) + { + foreach (var ruleBuilder in ruleBuilders) + { + builder.TagMatchingRule(innerRuleBuilder => + { + innerRuleBuilder.RequireTagName(tagName); + ruleBuilder(innerRuleBuilder); + }); + } + } + else + { + builder.TagMatchingRule(ruleBuilder => ruleBuilder.RequireTagName(tagName)); + } + + var descriptor = builder.Build(); + + return descriptor; } } diff --git a/test/Microsoft.CodeAnalysis.Razor.Test/TagHelperDescriptorFactoryTagHelpers.cs b/test/Microsoft.CodeAnalysis.Razor.Test/TagHelperDescriptorFactoryTagHelpers.cs index 3363ad9081..471fbbcf38 100644 --- a/test/Microsoft.CodeAnalysis.Razor.Test/TagHelperDescriptorFactoryTagHelpers.cs +++ b/test/Microsoft.CodeAnalysis.Razor.Test/TagHelperDescriptorFactoryTagHelpers.cs @@ -424,7 +424,7 @@ namespace Microsoft.CodeAnalysis.Razor.Workspaces.Test [HtmlTargetElement("a")] [HtmlTargetElement("p")] [OutputElementHint("div")] - public class MulitpleDescriptorTagHelperWithOutputElementHint : TagHelper + public class MultipleDescriptorTagHelperWithOutputElementHint : TagHelper { } diff --git a/test/Microsoft.CodeAnalysis.Razor.Test/TagHelperTypeVisitorTest.cs b/test/Microsoft.CodeAnalysis.Razor.Test/TagHelperTypeVisitorTest.cs index 16993b2a11..5af8816ff7 100644 --- a/test/Microsoft.CodeAnalysis.Razor.Test/TagHelperTypeVisitorTest.cs +++ b/test/Microsoft.CodeAnalysis.Razor.Test/TagHelperTypeVisitorTest.cs @@ -1,11 +1,8 @@ // 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 Microsoft.AspNetCore.Razor.TagHelpers; -using Microsoft.CodeAnalysis.Razor.Workspaces.Test; +using System.Collections.Generic; using Xunit; namespace Microsoft.CodeAnalysis.Razor.Workspaces diff --git a/test/Microsoft.CodeAnalysis.Razor.Test/TestCompilation.cs b/test/Microsoft.CodeAnalysis.Razor.Test/TestCompilation.cs index 0119f1c8b9..01887eb697 100644 --- a/test/Microsoft.CodeAnalysis.Razor.Test/TestCompilation.cs +++ b/test/Microsoft.CodeAnalysis.Razor.Test/TestCompilation.cs @@ -1,14 +1,13 @@ // 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.CodeAnalysis.CSharp; +using Microsoft.Extensions.DependencyModel; using System; using System.Collections.Generic; -using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.Extensions.DependencyModel; using Xunit; namespace Microsoft.CodeAnalysis.Razor diff --git a/test/Microsoft.CodeAnalysis.Razor.Test/ViewComponentTagHelperDescriptorFactoryTest.cs b/test/Microsoft.CodeAnalysis.Razor.Test/ViewComponentTagHelperDescriptorFactoryTest.cs index 079653468a..49f32ad2db 100644 --- a/test/Microsoft.CodeAnalysis.Razor.Test/ViewComponentTagHelperDescriptorFactoryTest.cs +++ b/test/Microsoft.CodeAnalysis.Razor.Test/ViewComponentTagHelperDescriptorFactoryTest.cs @@ -1,12 +1,11 @@ // 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.Evolution; using System.Collections.Generic; using System.Reflection; -using Microsoft.AspNetCore.Razor.Evolution; -using Microsoft.CodeAnalysis.Razor.Workspaces.Test; -using Microsoft.CodeAnalysis.Razor.Workspaces.Test.Comparers; using Xunit; +using Microsoft.AspNetCore.Razor.Evolution.Legacy; namespace Microsoft.CodeAnalysis.Razor.Workspaces { @@ -19,45 +18,32 @@ namespace Microsoft.CodeAnalysis.Razor.Workspaces var testCompilation = TestCompilation.Create(); var viewComponent = testCompilation.GetTypeByMetadataName(typeof(StringParameterViewComponent).FullName); var factory = new ViewComponentTagHelperDescriptorFactory(testCompilation); - var expectedDescriptor = new TagHelperDescriptor - { - TagName = "vc:string-parameter", - TypeName = "__Generated__StringParameterViewComponentTagHelper", - AssemblyName = typeof(StringParameterViewComponent).GetTypeInfo().Assembly.GetName().Name, - Attributes = new List - { - new TagHelperAttributeDescriptor - { - Name = "foo", - PropertyName = "foo", - TypeName = typeof(string).FullName - }, - new TagHelperAttributeDescriptor - { - Name = "bar", - PropertyName = "bar", - TypeName = typeof(string).FullName - } - }, - RequiredAttributes = new List - { - new TagHelperRequiredAttributeDescriptor - { - Name = "foo" - }, - new TagHelperRequiredAttributeDescriptor - { - Name = "bar" - } - } - }; - expectedDescriptor.PropertyBag.Add(ViewComponentTypes.ViewComponentNameKey, "StringParameter"); + var expectedDescriptor = ITagHelperDescriptorBuilder.Create( + "__Generated__StringParameterViewComponentTagHelper", + typeof(StringParameterViewComponent).GetTypeInfo().Assembly.GetName().Name) + .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 var descriptor = factory.CreateDescriptor(viewComponent); // Assert - Assert.Equal(expectedDescriptor, descriptor, CaseSensitiveTagHelperDescriptorComparer.Default); + Assert.Equal(expectedDescriptor, descriptor, TagHelperDescriptorComparer.CaseSensitive); } [Fact] @@ -67,60 +53,39 @@ namespace Microsoft.CodeAnalysis.Razor.Workspaces var testCompilation = TestCompilation.Create(); var viewComponent = testCompilation.GetTypeByMetadataName(typeof(VariousParameterViewComponent).FullName); var factory = new ViewComponentTagHelperDescriptorFactory(testCompilation); - var expectedDescriptor = new TagHelperDescriptor - { - TagName = "vc:various-parameter", - TypeName = "__Generated__VariousParameterViewComponentTagHelper", - AssemblyName = typeof(VariousParameterViewComponent).GetTypeInfo().Assembly.GetName().Name, - Attributes = new List - { - new TagHelperAttributeDescriptor - { - Name = "test-enum", - PropertyName = "testEnum", - TypeName = typeof(VariousParameterViewComponent).FullName + "." + nameof(VariousParameterViewComponent.TestEnum), - IsEnum = true - }, - - new TagHelperAttributeDescriptor - { - Name = "test-string", - PropertyName = "testString", - TypeName = typeof(string).FullName - }, - - new TagHelperAttributeDescriptor - { - Name = "baz", - PropertyName = "baz", - TypeName = typeof(int).FullName - } - }, - RequiredAttributes = new List - { - new TagHelperRequiredAttributeDescriptor - { - Name = "test-enum" - }, - - new TagHelperRequiredAttributeDescriptor - { - Name = "test-string" - }, - - new TagHelperRequiredAttributeDescriptor - { - Name = "baz" - } - } - }; - expectedDescriptor.PropertyBag.Add(ViewComponentTypes.ViewComponentNameKey, "VariousParameter"); + var expectedDescriptor = ITagHelperDescriptorBuilder.Create( + "__Generated__VariousParameterViewComponentTagHelper", + typeof(VariousParameterViewComponent).GetTypeInfo().Assembly.GetName().Name) + .TagMatchingRule(rule => + rule + .RequireTagName("vc:various-parameter") + .RequireAttribute(attribute => attribute.Name("test-enum")) + .RequireAttribute(attribute => attribute.Name("test-string")) + .RequireAttribute(attribute => attribute.Name("baz"))) + .BindAttribute(attribute => + attribute + .Name("test-enum") + .PropertyName("testEnum") + .TypeName(typeof(VariousParameterViewComponent).FullName + "." + nameof(VariousParameterViewComponent.TestEnum)) + .AsEnum()) + .BindAttribute(attribute => + attribute + .Name("test-string") + .PropertyName("testString") + .TypeName(typeof(string).FullName)) + .BindAttribute(attribute => + attribute + .Name("baz") + .PropertyName("baz") + .TypeName(typeof(int).FullName)) + .AddMetadata(ViewComponentTypes.ViewComponentNameKey, "VariousParameter") + .Build(); // Act var descriptor = factory.CreateDescriptor(viewComponent); // Assert - Assert.Equal(expectedDescriptor, descriptor, CaseSensitiveTagHelperDescriptorComparer.Default); + Assert.Equal(expectedDescriptor, descriptor, TagHelperDescriptorComparer.CaseSensitive); } [Fact] @@ -130,50 +95,32 @@ namespace Microsoft.CodeAnalysis.Razor.Workspaces var testCompilation = TestCompilation.Create(); var viewComponent = testCompilation.GetTypeByMetadataName(typeof(GenericParameterViewComponent).FullName); var factory = new ViewComponentTagHelperDescriptorFactory(testCompilation); - var expectedDescriptor = new TagHelperDescriptor - { - TagName = "vc:generic-parameter", - TypeName = "__Generated__GenericParameterViewComponentTagHelper", - AssemblyName = typeof(GenericParameterViewComponent).GetTypeInfo().Assembly.GetName().Name, - Attributes = new List - { - new TagHelperAttributeDescriptor - { - Name = "foo", - PropertyName = "Foo", - TypeName = "System.Collections.Generic.List" - }, - - new TagHelperAttributeDescriptor - { - Name = "bar", - PropertyName = "Bar", - TypeName = "System.Collections.Generic.Dictionary" - }, - - new TagHelperAttributeDescriptor - { - Name = "bar-", - PropertyName = "Bar", - TypeName = typeof(int).FullName, - IsIndexer = true - } - }, - RequiredAttributes = new List - { - new TagHelperRequiredAttributeDescriptor - { - Name = "foo" - } - } - }; - expectedDescriptor.PropertyBag.Add(ViewComponentTypes.ViewComponentNameKey, "GenericParameter"); + var expectedDescriptor = ITagHelperDescriptorBuilder.Create( + "__Generated__GenericParameterViewComponentTagHelper", + typeof(GenericParameterViewComponent).GetTypeInfo().Assembly.GetName().Name) + .TagMatchingRule(rule => + rule + .RequireTagName("vc:generic-parameter") + .RequireAttribute(attribute => attribute.Name("foo"))) + .BindAttribute(attribute => + attribute + .Name("foo") + .PropertyName("Foo") + .TypeName("System.Collections.Generic.List")) + .BindAttribute(attribute => + attribute + .Name("bar") + .PropertyName("Bar") + .TypeName("System.Collections.Generic.Dictionary") + .AsDictionary("bar-", typeof(int).FullName)) + .AddMetadata(ViewComponentTypes.ViewComponentNameKey, "GenericParameter") + .Build(); // Act var descriptor = factory.CreateDescriptor(viewComponent); // Assert - Assert.Equal(expectedDescriptor, descriptor, CaseSensitiveTagHelperDescriptorComparer.Default); + Assert.Equal(expectedDescriptor, descriptor, TagHelperDescriptorComparer.CaseSensitive); } } diff --git a/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/TagHelperDescriptorSerializationTest.cs b/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/TagHelperDescriptorSerializationTest.cs new file mode 100644 index 0000000000..3eb9cc4ce9 --- /dev/null +++ b/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/TagHelperDescriptorSerializationTest.cs @@ -0,0 +1,188 @@ +// 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.Evolution; +using Microsoft.AspNetCore.Razor.Evolution.Legacy; +using Newtonsoft.Json; +using Xunit; + +namespace Microsoft.VisualStudio.LanguageServices.Razor +{ + public class TagHelperDescriptorSerializationTest + { + [Fact] + public void TagHelperDescriptor_RoundTripsProperly() + { + // Arrange + var expectedDescriptor = CreateTagHelperDescriptor( + tagName: "tag-name", + typeName: "type name", + assemblyName: "assembly name", + attributes: new Action[] + { + builder => builder + .Name("test-attribute") + .PropertyName("TestAttribute") + .TypeName("string"), + }, + ruleBuilders: new Action[] + { + builder => builder + .RequireAttribute(attribute => attribute + .Name("required-attribute-one") + .NameComparisonMode(RequiredAttributeDescriptor.NameComparisonMode.PrefixMatch)) + .RequireAttribute(attribute => attribute + .Name("required-attribute-two") + .NameComparisonMode(RequiredAttributeDescriptor.NameComparisonMode.FullMatch) + .Value("something") + .ValueComparisonMode(RequiredAttributeDescriptor.ValueComparisonMode.PrefixMatch)) + .RequireParentTag("parent-name") + .RequireTagStructure(TagStructure.WithoutEndTag), + }, + configureAction: builder => + { + builder.AllowChildTag("allowed-child-one"); + builder.AddMetadata("foo", "bar"); + }); + + // Act + var serializedDescriptor = JsonConvert.SerializeObject(expectedDescriptor, TagHelperDescriptorJsonConverter.Instance, RazorDiagnosticJsonConverter.Instance); + var descriptor = JsonConvert.DeserializeObject(serializedDescriptor, TagHelperDescriptorJsonConverter.Instance, RazorDiagnosticJsonConverter.Instance); + + // Assert + Assert.Equal(expectedDescriptor, descriptor, TagHelperDescriptorComparer.Default); + } + + [Fact] + public void TagHelperDescriptor_WithDiagnostic_RoundTripsProperly() + { + // Arrange + var expectedDescriptor = CreateTagHelperDescriptor( + tagName: "tag-name", + typeName: "type name", + assemblyName: "assembly name", + attributes: new Action[] + { + builder => builder + .Name("test-attribute") + .PropertyName("TestAttribute") + .TypeName("string"), + }, + ruleBuilders: new Action[] + { + builder => builder + .RequireAttribute(attribute => attribute + .Name("required-attribute-one") + .NameComparisonMode(RequiredAttributeDescriptor.NameComparisonMode.PrefixMatch)) + .RequireAttribute(attribute => attribute + .Name("required-attribute-two") + .NameComparisonMode(RequiredAttributeDescriptor.NameComparisonMode.FullMatch) + .Value("something") + .ValueComparisonMode(RequiredAttributeDescriptor.ValueComparisonMode.PrefixMatch)) + .RequireParentTag("parent-name"), + }, + configureAction: builder => + { + builder.AllowChildTag("allowed-child-one") + .AddMetadata("foo", "bar") + .AddDiagnostic(RazorDiagnostic.Create( + new RazorDiagnosticDescriptor("id", () => "Test Message 1", RazorDiagnosticSeverity.Error), new SourceSpan(null, 10, 20, 30, 40))) + .AddDiagnostic(RazorDiagnostic.Create(new RazorError("Test Message 2", 10, 20, 30, 40))); + }); + + // Act + var serializedDescriptor = JsonConvert.SerializeObject(expectedDescriptor, TagHelperDescriptorJsonConverter.Instance, RazorDiagnosticJsonConverter.Instance); + var descriptor = JsonConvert.DeserializeObject(serializedDescriptor, TagHelperDescriptorJsonConverter.Instance, RazorDiagnosticJsonConverter.Instance); + + // Assert + Assert.Equal(expectedDescriptor, descriptor, TagHelperDescriptorComparer.Default); + } + + [Fact] + public void TagHelperDescriptor_WithIndexerAttributes_RoundTripsProperly() + { + // Arrange + var expectedDescriptor = CreateTagHelperDescriptor( + tagName: "tag-name", + typeName: "type name", + assemblyName: "assembly name", + attributes: new Action[] + { + builder => builder + .Name("test-attribute") + .PropertyName("TestAttribute") + .TypeName("SomeEnum") + .AsEnum() + .Documentation("Summary"), + builder => builder + .Name("test-attribute2") + .PropertyName("TestAttribute2") + .TypeName("SomeDictionary") + .AsDictionary("dict-prefix-", "string"), + }, + ruleBuilders: new Action[] + { + builder => builder + .RequireAttribute(attribute => attribute + .Name("required-attribute-one") + .NameComparisonMode(RequiredAttributeDescriptor.NameComparisonMode.PrefixMatch)) + }, + configureAction: builder => + { + builder + .AllowChildTag("allowed-child-one") + .AddMetadata("foo", "bar") + .TagOutputHint("Hint"); + }); + + // Act + var serializedDescriptor = JsonConvert.SerializeObject(expectedDescriptor, TagHelperDescriptorJsonConverter.Instance, RazorDiagnosticJsonConverter.Instance); + var descriptor = JsonConvert.DeserializeObject(serializedDescriptor, TagHelperDescriptorJsonConverter.Instance, RazorDiagnosticJsonConverter.Instance); + + // Assert + Assert.Equal(expectedDescriptor, descriptor, TagHelperDescriptorComparer.Default); + } + + private static TagHelperDescriptor CreateTagHelperDescriptor( + string tagName, + string typeName, + string assemblyName, + IEnumerable> attributes = null, + IEnumerable> ruleBuilders = null, + Action configureAction = null) + { + var builder = ITagHelperDescriptorBuilder.Create(typeName, assemblyName); + + if (attributes != null) + { + foreach (var attributeBuilder in attributes) + { + builder.BindAttribute(attributeBuilder); + } + } + + if (ruleBuilders != null) + { + foreach (var ruleBuilder in ruleBuilders) + { + builder.TagMatchingRule(innerRuleBuilder => { + innerRuleBuilder.RequireTagName(tagName); + ruleBuilder(innerRuleBuilder); + }); + } + } + else + { + builder.TagMatchingRule(ruleBuilder => ruleBuilder.RequireTagName(tagName)); + } + + configureAction?.Invoke(builder); + + var descriptor = builder.Build(); + + return descriptor; + } + } +} diff --git a/tooling/Microsoft.VisualStudio.RazorExtension/RazorInfo/TagHelperViewModel.cs b/tooling/Microsoft.VisualStudio.RazorExtension/RazorInfo/TagHelperViewModel.cs index 7ee2183809..eb491acdcd 100644 --- a/tooling/Microsoft.VisualStudio.RazorExtension/RazorInfo/TagHelperViewModel.cs +++ b/tooling/Microsoft.VisualStudio.RazorExtension/RazorInfo/TagHelperViewModel.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. #if RAZOR_EXTENSION_DEVELOPER_MODE +using System.Linq; using Microsoft.AspNetCore.Razor.Evolution; namespace Microsoft.VisualStudio.RazorExtension.RazorInfo @@ -17,9 +18,9 @@ namespace Microsoft.VisualStudio.RazorExtension.RazorInfo public string AssemblyName => _descriptor.AssemblyName; - public string TargetElement => _descriptor.TagName; + public string TargetElement => string.Join(", ", _descriptor.TagMatchingRules.Select(rule => rule.TagName)); - public string TypeName => _descriptor.TypeName; + public string TypeName => _descriptor.Metadata[ITagHelperDescriptorBuilder.TypeNameKey]; } } #endif \ No newline at end of file