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 `<Remarks>`, 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
This commit is contained in:
N. Taylor Mullen 2017-03-07 11:04:31 -08:00 committed by Ajay Bhargav Baaskaran
parent 90b48347a5
commit 8f9ff1abd9
96 changed files with 6595 additions and 7679 deletions

View File

@ -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
{
/// <summary>
/// A metadata class describing a tag helper attribute.
/// </summary>
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<RazorDiagnostic> Diagnostics { get; protected set; }
public IReadOnlyDictionary<string, string> Metadata { get; protected set; }
public bool HasAnyErrors
{
get
{
var anyErrors = Diagnostics.Any(diagnostic => diagnostic.Severity == RazorDiagnosticSeverity.Error);
return anyErrors;
}
}
}
}

View File

@ -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<BoundAttributeDescriptor>
{
/// <summary>
/// A default instance of the <see cref="BoundAttributeDescriptorComparer"/>.
/// </summary>
public static readonly BoundAttributeDescriptorComparer Default = new BoundAttributeDescriptorComparer();
/// <summary>
/// A default instance of the <see cref="BoundAttributeDescriptorComparer"/> that does case-sensitive comparison.
/// </summary>
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;
}
}
}

View File

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

View File

@ -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}\"]";
}

View File

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

View File

@ -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<string>();
@ -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<string> namespaces)
public MainSourceVisitor(DocumentIRNode document, RazorIRBuilder builder, HashSet<string> 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<TagHelperDescriptor> 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<TagHelperAttributeNode> attributes, IEnumerable<TagHelperDescriptor> descriptors)
private void AddTagHelperAttributes(IList<TagHelperAttributeNode> attributes, TagHelperBinding tagHelperBinding)
{
var descriptors = tagHelperBinding.Descriptors;
var renderedBoundAttributeNames = new HashSet<string>(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);

View File

@ -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<string, string> PrimitiveDisplayTypeNameLookups = new Dictionary<string, string>(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<char> InvalidNonWhitespaceAttributeNameCharacters { get; } = new HashSet<char>(
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<string, string> _metadata;
private HashSet<RazorDiagnostic> _diagnostics;
private ITagHelperBoundAttributeDescriptorBuilder(string containingTypeName)
{
_containingTypeName = containingTypeName;
_metadata = new Dictionary<string, string>();
}
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<RazorDiagnostic>(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<RazorDiagnostic> 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<RazorDiagnostic>();
}
}
private class ITagHelperBoundAttributeDescriptor : BoundAttributeDescriptor
{
public ITagHelperBoundAttributeDescriptor(
bool isEnum,
string name,
string propertyName,
string typeName,
string dictionaryAttributeNamePrefix,
string dictionaryValueTypeName,
string documentation,
string displayName,
Dictionary<string, string> metadata,
IEnumerable<RazorDiagnostic> 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<RazorDiagnostic>(diagnostics);
Metadata = new Dictionary<string, string>(metadata)
{
[PropertyNameKey] = propertyName
};
}
}
}
}

View File

@ -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<char> InvalidNonWhitespaceAllowedChildCharacters { get; } = new HashSet<char>(
new[] { '@', '!', '<', '/', '?', '[', '>', ']', '=', '"', '\'', '*' });
private string _documentation;
private string _tagOutputHint;
private HashSet<string> _allowedChildTags;
private HashSet<BoundAttributeDescriptor> _attributeDescriptors;
private HashSet<TagMatchingRule> _tagMatchingRules;
private readonly string _assemblyName;
private readonly string _typeName;
private readonly Dictionary<string, string> _metadata;
private HashSet<RazorDiagnostic> _diagnostics;
private ITagHelperDescriptorBuilder(string typeName, string assemblyName)
{
_typeName = typeName;
_assemblyName = assemblyName;
_metadata = new Dictionary<string, string>(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<ITagHelperBoundAttributeDescriptorBuilder> 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<TagMatchingRuleBuilder> 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<RazorDiagnostic>(validationDiagnostics);
if (_diagnostics != null)
{
diagnostics.UnionWith(_diagnostics);
}
var descriptor = new ITagHelperDescriptor(
_typeName,
_assemblyName,
_typeName /* Name */,
_typeName /* DisplayName */,
_documentation,
_tagOutputHint,
_tagMatchingRules ?? Enumerable.Empty<TagMatchingRule>(),
_attributeDescriptors ?? Enumerable.Empty<BoundAttributeDescriptor>(),
_allowedChildTags ?? Enumerable.Empty<string>(),
_metadata,
diagnostics);
return descriptor;
}
public void Reset()
{
_documentation = null;
_tagOutputHint = null;
_allowedChildTags?.Clear();
_attributeDescriptors?.Clear();
_tagMatchingRules?.Clear();
_metadata.Clear();
_diagnostics?.Clear();
}
private IEnumerable<RazorDiagnostic> 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<BoundAttributeDescriptor>(BoundAttributeDescriptorComparer.Default);
}
}
private void EnsureTagMatchingRules()
{
if (_tagMatchingRules == null)
{
_tagMatchingRules = new HashSet<TagMatchingRule>(TagMatchingRuleComparer.Default);
}
}
private void EnsureAllowedChildTags()
{
if (_allowedChildTags == null)
{
_allowedChildTags = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
}
}
private void EnsureDiagnostics()
{
if (_diagnostics == null)
{
_diagnostics = new HashSet<RazorDiagnostic>();
}
}
private class ITagHelperDescriptor : TagHelperDescriptor
{
public ITagHelperDescriptor(
string typeName,
string assemblyName,
string name,
string displayName,
string documentation,
string tagOutputHint,
IEnumerable<TagMatchingRule> tagMatchingRules,
IEnumerable<BoundAttributeDescriptor> attributeDescriptors,
IEnumerable<string> allowedChildTags,
Dictionary<string, string> metadata,
IEnumerable<RazorDiagnostic> diagnostics) : base(DescriptorKind)
{
Name = typeName;
AssemblyName = assemblyName;
DisplayName = displayName;
Documentation = documentation;
TagOutputHint = tagOutputHint;
TagMatchingRules = new List<TagMatchingRule>(tagMatchingRules);
BoundAttributes = new List<BoundAttributeDescriptor>(attributeDescriptors);
AllowedChildTags = new List<string>(allowedChildTags);
Diagnostics = new List<RazorDiagnostic>(diagnostics);
Metadata = new Dictionary<string, string>(metadata)
{
[TypeNameKey] = typeName
};
}
}
}
}

View File

@ -3,7 +3,6 @@
using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Razor.Evolution.Legacy;
namespace Microsoft.AspNetCore.Razor.Evolution.Intermediate
{

View File

@ -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)
{

View File

@ -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)
{

View File

@ -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
{
/// <summary>
/// An <see cref="IEqualityComparer{TagHelperRequiredAttributeDescriptor}"/> used to check equality between
/// two <see cref="RequiredAttributeDescriptor"/>s.
/// </summary>
internal class RequiredAttributeDescriptorComparer : IEqualityComparer<RequiredAttributeDescriptor>
{
/// <summary>
/// A default instance of the <see cref="RequiredAttributeDescriptorComparer"/>.
/// </summary>
public static readonly RequiredAttributeDescriptorComparer Default =
new RequiredAttributeDescriptorComparer();
/// <summary>
/// A default instance of the <see cref="RequiredAttributeDescriptorComparer"/> that does case-sensitive comparison.
/// </summary>
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;
}
}
/// <inheritdoc />
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);
}
/// <inheritdoc />
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;
}
}
}

View File

@ -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<TagHelperDescriptor, IEnumerable<TagMatchingRule>> _mappings;
internal TagHelperBinding(IReadOnlyDictionary<TagHelperDescriptor, IEnumerable<TagMatchingRule>> mappings)
{
_mappings = mappings;
Descriptors = _mappings.Keys;
}
public IEnumerable<TagHelperDescriptor> Descriptors { get; }
public IEnumerable<TagMatchingRule> GetBoundRules(TagHelperDescriptor descriptor)
{
return _mappings[descriptor];
}
}
}

View File

@ -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<TagHelperAttributeNode>(source.Attributes);
_start = source.Start;
TagMode = source.TagMode;
@ -61,9 +61,9 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
public TagMode TagMode { get; }
/// <summary>
/// <see cref="TagHelperDescriptor"/>s for the HTML element.
/// <see cref="TagHelperDescriptor"/> bindings for the HTML element.
/// </summary>
public IEnumerable<TagHelperDescriptor> Descriptors { get; }
public TagHelperBinding Binding { get; }
/// <summary>
/// The HTML attributes.

View File

@ -19,7 +19,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
: base(original)
{
TagName = original.TagName;
Descriptors = original.Descriptors;
BindingResult = original.Binding;
Attributes = new List<TagHelperAttributeNode>(original.Attributes);
}
@ -31,22 +31,21 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
/// <param name="tagMode">HTML syntax of the element in the Razor source.</param>
/// <param name="start">Starting location of the <see cref="TagHelperBlock"/>.</param>
/// <param name="attributes">Attributes of the <see cref="TagHelperBlock"/>.</param>
/// <param name="descriptors">The <see cref="TagHelperDescriptor"/>s associated with the current HTML
/// tag.</param>
/// <param name="bindingResult"></param>
public TagHelperBlockBuilder(
string tagName,
TagMode tagMode,
SourceLocation start,
IList<TagHelperAttributeNode> attributes,
IEnumerable<TagHelperDescriptor> descriptors)
TagHelperBinding bindingResult)
{
TagName = tagName;
TagMode = tagMode;
Start = start;
Descriptors = descriptors;
BindingResult = bindingResult;
Attributes = new List<TagHelperAttributeNode>(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
/// <summary>
/// <see cref="TagHelperDescriptor"/>s for the HTML element.
/// </summary>
public IEnumerable<TagHelperDescriptor> Descriptors { get; }
public TagHelperBinding BindingResult { get; }
/// <summary>
/// The HTML attributes.

View File

@ -17,28 +17,24 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
string tagName,
bool validStructure,
Block tag,
IEnumerable<TagHelperDescriptor> 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<TagHelperAttributeNode> GetTagAttributes(
string tagName,
bool validStructure,
Block tagBlock,
IEnumerable<TagHelperDescriptor> 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<TagHelperAttributeNode>();
// We skip the first child "<tagname" and take everything up to the ending portion of the tag ">" 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<TagHelperDescriptor> 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<TagHelperDescriptor> 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<TagHelperDescriptor> 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;
}

View File

@ -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<TagHelperDescriptor> _tagHelperDescriptors;
/// <summary>
/// Instantiates a new <see cref="TagHelperChunkGenerator"/>.
/// </summary>
/// <param name="tagHelperDescriptors">
/// <see cref="TagHelperDescriptor"/>s associated with the current HTML tag.
/// </param>
public TagHelperChunkGenerator(IEnumerable<TagHelperDescriptor> tagHelperDescriptors)
{
_tagHelperDescriptors = tagHelperDescriptors;
}
public override void GenerateStartParentChunk(Block target, ChunkGeneratorContext context)
{
//var tagHelperBlock = target as TagHelperBlock;

View File

@ -8,10 +8,6 @@ using Microsoft.Extensions.Internal;
namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
{
/// <summary>
/// An <see cref="IEqualityComparer{TagHelperDescriptor}"/> used to check equality between
/// two <see cref="TagHelperDescriptor"/>s.
/// </summary>
internal class TagHelperDescriptorComparer : IEqualityComparer<TagHelperDescriptor>
{
/// <summary>
@ -20,27 +16,34 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
public static readonly TagHelperDescriptorComparer Default = new TagHelperDescriptorComparer();
/// <summary>
/// An instance of <see cref="TagHelperDescriptorComparer"/> that only compares
/// <see cref="TagHelperDescriptor.TypeName"/>.
/// A default instance of the <see cref="TagHelperDescriptorComparer"/> that does case-sensitive comparison.
/// </summary>
public static readonly TagHelperDescriptorComparer TypeName = new TypeNameTagHelperDescriptorComparer();
internal static readonly TagHelperDescriptorComparer CaseSensitive =
new TagHelperDescriptorComparer(caseSensitive: true);
/// <summary>
/// Initializes a new <see cref="TagHelperDescriptorComparer"/> instance.
/// </summary>
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;
}
}
/// <inheritdoc />
/// <remarks>
/// Determines equality based on <see cref="TagHelperDescriptor.TypeName"/>,
/// <see cref="TagHelperDescriptor.AssemblyName"/>, <see cref="TagHelperDescriptor.TagName"/>,
/// <see cref="TagHelperDescriptor.RequiredAttributes"/>, <see cref="TagHelperDescriptor.AllowedChildren"/>,
/// and <see cref="TagHelperDescriptor.TagStructure"/>.
/// Ignores <see cref="TagHelperDescriptor.DesignTimeDescriptor"/> because it can be inferred directly from
/// <see cref="TagHelperDescriptor.TypeName"/> and <see cref="TagHelperDescriptor.AssemblyName"/>.
/// </remarks>
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));
}
/// <inheritdoc />
@ -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);
}
}
}
}

View File

@ -15,14 +15,16 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
public const string ElementCatchAllTarget = "*";
private IDictionary<string, HashSet<TagHelperDescriptor>> _registrations;
private string _tagHelperPrefix;
private readonly string _tagHelperPrefix;
/// <summary>
/// Instantiates a new instance of the <see cref="TagHelperDescriptorProvider"/>.
/// </summary>
/// <param name="tagHelperPrefix">The tag helper prefix being used by the document.</param>
/// <param name="descriptors">The descriptors that the <see cref="TagHelperDescriptorProvider"/> will pull from.</param>
public TagHelperDescriptorProvider(IEnumerable<TagHelperDescriptor> descriptors)
public TagHelperDescriptorProvider(string tagHelperPrefix, IEnumerable<TagHelperDescriptor> descriptors)
{
_tagHelperPrefix = tagHelperPrefix;
_registrations = new Dictionary<string, HashSet<TagHelperDescriptor>>(StringComparer.OrdinalIgnoreCase);
// Populate our registrations
@ -42,7 +44,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
/// <returns><see cref="TagHelperDescriptor"/>s that apply to the given <paramref name="tagName"/>.
/// Will return an empty <see cref="Enumerable" /> if no <see cref="TagHelperDescriptor"/>s are
/// found.</returns>
public IEnumerable<TagHelperDescriptor> GetDescriptors(
public TagHelperBinding GetTagHelperBinding(
string tagName,
IEnumerable<KeyValuePair<string, string>> 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<TagHelperDescriptor>();
return null;
}
HashSet<TagHelperDescriptor> catchAllDescriptors;
IEnumerable<TagHelperDescriptor> descriptors;
// Ensure there's a HashSet to use.
if (!_registrations.TryGetValue(ElementCatchAllTarget, out catchAllDescriptors))
if (!_registrations.TryGetValue(ElementCatchAllTarget, out HashSet<TagHelperDescriptor> catchAllDescriptors))
{
descriptors = new HashSet<TagHelperDescriptor>(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<TagHelperDescriptor> matchingDescriptors;
if (_registrations.TryGetValue(tagName, out matchingDescriptors))
if (_registrations.TryGetValue(tagName, out HashSet<TagHelperDescriptor> matchingDescriptors))
{
descriptors = matchingDescriptors.Concat(descriptors);
}
var applicableDescriptors = new List<TagHelperDescriptor>();
var tagNameWithoutPrefix = _tagHelperPrefix != null ? tagName.Substring(_tagHelperPrefix.Length) : tagName;
Dictionary<TagHelperDescriptor, IEnumerable<TagMatchingRule>> 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<TagHelperDescriptor, IEnumerable<TagMatchingRule>>();
}
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<KeyValuePair<string, string>> 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<KeyValuePair<string, string>> 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<TagHelperDescriptor> 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<TagHelperDescriptor> descriptorSet))
{
descriptorSet = new HashSet<TagHelperDescriptor>(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<TagHelperDescriptor>(TagHelperDescriptorComparer.Default);
_registrations[registrationKey] = descriptorSet;
}
descriptorSet.Add(descriptor);
}
}
}

View File

@ -36,6 +36,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
"wbr"
};
private readonly string _tagHelperPrefix;
private readonly List<KeyValuePair<string, string>> _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<TagBlockTracker>();
_blockStack = new Stack<BlockBuilder>();
@ -172,7 +174,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
return false;
}
var descriptors = Enumerable.Empty<TagHelperDescriptor>();
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<KeyValuePair<string, string>>(),
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, "</"),
LegacyResources.FormatTagHelperParseTreeRewriter_EndTagTagHelperMustNotHaveAnEndTag(
tagName,
invalidDescriptor.TypeName,
invalidDescriptor.TagStructure),
tagName.Length);
var boundRules = tagHelperBinding.GetBoundRules(descriptor);
var invalidRule = boundRules.FirstOrDefault(rule => 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, "</"),
LegacyResources.FormatTagHelperParseTreeRewriter_EndTagTagHelperMustNotHaveAnEndTag(
tagName,
typeName,
invalidRule.TagStructure),
tagName.Length);
return false;
}
}
// Current tag helper scope does not match the end tag. Attempt to recover the tag
@ -463,7 +471,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
return false;
}
return _currentTagHelperTracker.AllowedChildren != null;
return _currentTagHelperTracker.AllowedChildren != null && _currentTagHelperTracker.AllowedChildren.Count > 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<TagHelperDescriptor> 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<string> _prefixedAllowedChildren;
private IReadOnlyList<string> _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<string> AllowedChildren { get; }
public IReadOnlyList<string> AllowedChildren { get; }
public IEnumerable<string> PrefixedAllowedChildren
public IReadOnlyList<string> 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;

View File

@ -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
{
/// <summary>
/// An <see cref="IEqualityComparer{TagHelperRequiredAttributeDescriptor}"/> used to check equality between
/// two <see cref="TagHelperRequiredAttributeDescriptor"/>s.
/// </summary>
internal class TagHelperRequiredAttributeDescriptorComparer : IEqualityComparer<TagHelperRequiredAttributeDescriptor>
{
/// <summary>
/// A default instance of the <see cref="TagHelperRequiredAttributeDescriptor"/>.
/// </summary>
public static readonly TagHelperRequiredAttributeDescriptorComparer Default =
new TagHelperRequiredAttributeDescriptorComparer();
/// <summary>
/// Initializes a new <see cref="TagHelperRequiredAttributeDescriptor"/> instance.
/// </summary>
protected TagHelperRequiredAttributeDescriptorComparer()
{
}
/// <inheritdoc />
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);
}
/// <inheritdoc />
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;
}
}
}

View File

@ -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
{
/// <summary>
/// An <see cref="IEqualityComparer{TagHelperDescriptor}"/> that checks equality between two
/// <see cref="TagHelperDescriptor"/>s using only their <see cref="TagHelperDescriptor.AssemblyName"/>s and
/// <see cref="TagHelperDescriptor.TypeName"/>s.
/// </summary>
/// <remarks>
/// This class is intended for scenarios where Reflection-based information is all important i.e.
/// <see cref="TagHelperDescriptor.RequiredAttributes"/>, <see cref="TagHelperDescriptor.TagName"/>, and related
/// properties are not relevant.
/// </remarks>
internal class TypeBasedTagHelperDescriptorComparer : IEqualityComparer<TagHelperDescriptor>
{
/// <summary>
/// A default instance of the <see cref="TypeBasedTagHelperDescriptorComparer"/>.
/// </summary>
public static readonly TypeBasedTagHelperDescriptorComparer Default =
new TypeBasedTagHelperDescriptorComparer();
private TypeBasedTagHelperDescriptorComparer()
{
}
/// <inheritdoc />
/// <remarks>
/// Determines equality based on <see cref="TagHelperDescriptor.AssemblyName"/> and
/// <see cref="TagHelperDescriptor.TypeName"/>.
/// </remarks>
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);
}
/// <inheritdoc />
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;
}
}
}

View File

@ -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")]

View File

@ -15,225 +15,379 @@ namespace Microsoft.AspNetCore.Razor.Evolution
/// </summary>
internal static string ArgumentCannotBeNullOrEmpty
{
get { return GetString("ArgumentCannotBeNullOrEmpty"); }
get => GetString("ArgumentCannotBeNullOrEmpty");
}
/// <summary>
/// Value cannot be null or an empty string.
/// </summary>
internal static string FormatArgumentCannotBeNullOrEmpty()
{
return GetString("ArgumentCannotBeNullOrEmpty");
}
=> GetString("ArgumentCannotBeNullOrEmpty");
/// <summary>
/// The '{0}' feature requires a '{1}' provided by the '{2}'.
/// </summary>
internal static string FeatureDependencyMissing
{
get { return GetString("FeatureDependencyMissing"); }
get => GetString("FeatureDependencyMissing");
}
/// <summary>
/// The '{0}' feature requires a '{1}' provided by the '{2}'.
/// </summary>
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);
/// <summary>
/// Invalid tag helper directive look up text '{0}'. The correct look up text format is: "typeName, assemblyName".
/// </summary>
internal static string InvalidTagHelperLookupText
{
get { return GetString("InvalidTagHelperLookupText"); }
get => GetString("InvalidTagHelperLookupText");
}
/// <summary>
/// Invalid tag helper directive look up text '{0}'. The correct look up text format is: "typeName, assemblyName".
/// </summary>
internal static string FormatInvalidTagHelperLookupText(object p0)
{
return string.Format(CultureInfo.CurrentCulture, GetString("InvalidTagHelperLookupText"), p0);
}
=> string.Format(CultureInfo.CurrentCulture, GetString("InvalidTagHelperLookupText"), p0);
/// <summary>
/// Invalid tag helper directive '{0}' value. '{1}' is not allowed in prefix '{2}'.
/// </summary>
internal static string InvalidTagHelperPrefixValue
{
get { return GetString("InvalidTagHelperPrefixValue"); }
get => GetString("InvalidTagHelperPrefixValue");
}
/// <summary>
/// Invalid tag helper directive '{0}' value. '{1}' is not allowed in prefix '{2}'.
/// </summary>
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);
/// <summary>
/// The '{0}' operation is not valid when the builder is empty.
/// </summary>
internal static string IRBuilder_PopInvalid
{
get { return GetString("IRBuilder_PopInvalid"); }
get => GetString("IRBuilder_PopInvalid");
}
/// <summary>
/// The '{0}' operation is not valid when the builder is empty.
/// </summary>
internal static string FormatIRBuilder_PopInvalid(object p0)
{
return string.Format(CultureInfo.CurrentCulture, GetString("IRBuilder_PopInvalid"), p0);
}
=> string.Format(CultureInfo.CurrentCulture, GetString("IRBuilder_PopInvalid"), p0);
/// <summary>
/// The specified encoding '{0}' does not match the content's encoding '{1}'.
/// </summary>
internal static string MismatchedContentEncoding
{
get { return GetString("MismatchedContentEncoding"); }
get => GetString("MismatchedContentEncoding");
}
/// <summary>
/// The specified encoding '{0}' does not match the content's encoding '{1}'.
/// </summary>
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);
/// <summary>
/// The '{0}' phase requires a '{1}' provided by the '{2}'.
/// </summary>
internal static string PhaseDependencyMissing
{
get { return GetString("PhaseDependencyMissing"); }
get => GetString("PhaseDependencyMissing");
}
/// <summary>
/// The '{0}' phase requires a '{1}' provided by the '{2}'.
/// </summary>
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);
/// <summary>
/// The phase must be initialized by setting the '{0}' property.
/// </summary>
internal static string PhaseMustBeInitialized
{
get { return GetString("PhaseMustBeInitialized"); }
get => GetString("PhaseMustBeInitialized");
}
/// <summary>
/// The phase must be initialized by setting the '{0}' property.
/// </summary>
internal static string FormatPhaseMustBeInitialized(object p0)
{
return string.Format(CultureInfo.CurrentCulture, GetString("PhaseMustBeInitialized"), p0);
}
=> string.Format(CultureInfo.CurrentCulture, GetString("PhaseMustBeInitialized"), p0);
/// <summary>
/// Tag helper directive assembly name cannot be null or empty.
/// </summary>
internal static string TagHelperAssemblyNameCannotBeEmptyOrNull
{
get { return GetString("TagHelperAssemblyNameCannotBeEmptyOrNull"); }
get => GetString("TagHelperAssemblyNameCannotBeEmptyOrNull");
}
/// <summary>
/// Tag helper directive assembly name cannot be null or empty.
/// </summary>
internal static string FormatTagHelperAssemblyNameCannotBeEmptyOrNull()
{
return GetString("TagHelperAssemblyNameCannotBeEmptyOrNull");
}
=> GetString("TagHelperAssemblyNameCannotBeEmptyOrNull");
/// <summary>
/// The assembly '{0}' could not be resolved or contains no tag helpers.
/// </summary>
internal static string TagHelperAssemblyCouldNotBeResolved
{
get { return GetString("TagHelperAssemblyCouldNotBeResolved"); }
get => GetString("TagHelperAssemblyCouldNotBeResolved");
}
/// <summary>
/// The assembly '{0}' could not be resolved or contains no tag helpers.
/// </summary>
internal static string FormatTagHelperAssemblyCouldNotBeResolved(object p0)
{
return string.Format(CultureInfo.CurrentCulture, GetString("TagHelperAssemblyCouldNotBeResolved"), p0);
}
=> string.Format(CultureInfo.CurrentCulture, GetString("TagHelperAssemblyCouldNotBeResolved"), p0);
/// <summary>
/// Path must begin with a forward slash '/'.
/// </summary>
internal static string RazorProject_PathMustStartWithForwardSlash
{
get { return GetString("RazorProject_PathMustStartWithForwardSlash"); }
get => GetString("RazorProject_PathMustStartWithForwardSlash");
}
/// <summary>
/// Path must begin with a forward slash '/'.
/// </summary>
internal static string FormatRazorProject_PathMustStartWithForwardSlash()
{
return GetString("RazorProject_PathMustStartWithForwardSlash");
}
=> GetString("RazorProject_PathMustStartWithForwardSlash");
/// <summary>
/// The method '{0}' has already been invoked.
/// </summary>
internal static string DirectiveDescriptor_BeginOptionalsAlreadyInvoked
{
get { return GetString("DirectiveDescriptor_BeginOptionalsAlreadyInvoked"); }
get => GetString("DirectiveDescriptor_BeginOptionalsAlreadyInvoked");
}
/// <summary>
/// The method '{0}' has already been invoked.
/// </summary>
internal static string FormatDirectiveDescriptor_BeginOptionalsAlreadyInvoked(object p0)
{
return string.Format(CultureInfo.CurrentCulture, GetString("DirectiveDescriptor_BeginOptionalsAlreadyInvoked"), p0);
}
=> string.Format(CultureInfo.CurrentCulture, GetString("DirectiveDescriptor_BeginOptionalsAlreadyInvoked"), p0);
/// <summary>
/// The document of kind '{0}' does not have a '{1}'. The document classifier must set a value for '{2}'.
/// </summary>
internal static string DocumentMissingTarget
{
get { return GetString("DocumentMissingTarget"); }
get => GetString("DocumentMissingTarget");
}
/// <summary>
/// The document of kind '{0}' does not have a '{1}'. The document classifier must set a value for '{2}'.
/// </summary>
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);
/// <summary>
/// The item '{0}' could not be found.
/// </summary>
internal static string RazorTemplateEngine_ItemCouldNotBeFound
{
get { return GetString("RazorTemplateEngine_ItemCouldNotBeFound"); }
get => GetString("RazorTemplateEngine_ItemCouldNotBeFound");
}
/// <summary>
/// The item '{0}' could not be found.
/// </summary>
internal static string FormatRazorTemplateEngine_ItemCouldNotBeFound(object p0)
=> string.Format(CultureInfo.CurrentCulture, GetString("RazorTemplateEngine_ItemCouldNotBeFound"), p0);
/// <summary>
/// Invalid tag helper bound property '{0}.{1}'. Tag helpers cannot bind to HTML attributes with name '{2}' because the name contains a '{3}' character.
/// </summary>
internal static string InvalidBoundAttributeName
{
return string.Format(CultureInfo.CurrentCulture, GetString("RazorTemplateEngine_ItemCouldNotBeFound"), p0);
get => GetString("InvalidBoundAttributeName");
}
/// <summary>
/// Invalid tag helper bound property '{0}.{1}'. Tag helpers cannot bind to HTML attributes with name '{2}' because the name contains a '{3}' character.
/// </summary>
internal static string FormatInvalidBoundAttributeName(object p0, object p1, object p2, object p3)
=> string.Format(CultureInfo.CurrentCulture, GetString("InvalidBoundAttributeName"), p0, p1, p2, p3);
/// <summary>
/// Invalid tag helper bound property '{0}.{1}'. Tag helpers cannot bind to HTML attributes with name '{2}' because the name starts with '{3}'.
/// </summary>
internal static string InvalidBoundAttributeNameStartsWith
{
get => GetString("InvalidBoundAttributeNameStartsWith");
}
/// <summary>
/// Invalid tag helper bound property '{0}.{1}'. Tag helpers cannot bind to HTML attributes with name '{2}' because the name starts with '{3}'.
/// </summary>
internal static string FormatInvalidBoundAttributeNameStartsWith(object p0, object p1, object p2, object p3)
=> string.Format(CultureInfo.CurrentCulture, GetString("InvalidBoundAttributeNameStartsWith"), p0, p1, p2, p3);
/// <summary>
/// Invalid tag helper bound property '{0}.{1}'. Tag helpers cannot bind to HTML attributes with a null or empty name.
/// </summary>
internal static string InvalidBoundAttributeNullOrWhitespace
{
get => GetString("InvalidBoundAttributeNullOrWhitespace");
}
/// <summary>
/// Invalid tag helper bound property '{0}.{1}'. Tag helpers cannot bind to HTML attributes with a null or empty name.
/// </summary>
internal static string FormatInvalidBoundAttributeNullOrWhitespace(object p0, object p1)
=> string.Format(CultureInfo.CurrentCulture, GetString("InvalidBoundAttributeNullOrWhitespace"), p0, p1);
/// <summary>
/// Invalid tag helper bound property '{0}.{1}'. Tag helpers cannot bind to HTML attributes with prefix '{2}' because the prefix contains a '{3}' character.
/// </summary>
internal static string InvalidBoundAttributePrefix
{
get => GetString("InvalidBoundAttributePrefix");
}
/// <summary>
/// Invalid tag helper bound property '{0}.{1}'. Tag helpers cannot bind to HTML attributes with prefix '{2}' because the prefix contains a '{3}' character.
/// </summary>
internal static string FormatInvalidBoundAttributePrefix(object p0, object p1, object p2, object p3)
=> string.Format(CultureInfo.CurrentCulture, GetString("InvalidBoundAttributePrefix"), p0, p1, p2, p3);
/// <summary>
/// Invalid tag helper bound property '{0}.{1}'. Tag helpers cannot bind to HTML attributes with prefix '{2}' because the prefix starts with '{3}'.
/// </summary>
internal static string InvalidBoundAttributePrefixStartsWith
{
get => GetString("InvalidBoundAttributePrefixStartsWith");
}
/// <summary>
/// Invalid tag helper bound property '{0}.{1}'. Tag helpers cannot bind to HTML attributes with prefix '{2}' because the prefix starts with '{3}'.
/// </summary>
internal static string FormatInvalidBoundAttributePrefixStartsWith(object p0, object p1, object p2, object p3)
=> string.Format(CultureInfo.CurrentCulture, GetString("InvalidBoundAttributePrefixStartsWith"), p0, p1, p2, p3);
/// <summary>
/// Invalid restricted child '{0}' for tag helper '{1}'. Tag helpers cannot restrict child elements that contain a '{2}' character.
/// </summary>
internal static string InvalidRestrictedChild
{
get => GetString("InvalidRestrictedChild");
}
/// <summary>
/// Invalid restricted child '{0}' for tag helper '{1}'. Tag helpers cannot restrict child elements that contain a '{2}' character.
/// </summary>
internal static string FormatInvalidRestrictedChild(object p0, object p1, object p2)
=> string.Format(CultureInfo.CurrentCulture, GetString("InvalidRestrictedChild"), p0, p1, p2);
/// <summary>
/// Invalid restricted child for tag helper '{0}'. Name cannot be null or whitespace.
/// </summary>
internal static string InvalidRestrictedChildNullOrWhitespace
{
get => GetString("InvalidRestrictedChildNullOrWhitespace");
}
/// <summary>
/// Invalid restricted child for tag helper '{0}'. Name cannot be null or whitespace.
/// </summary>
internal static string FormatInvalidRestrictedChildNullOrWhitespace(object p0)
=> string.Format(CultureInfo.CurrentCulture, GetString("InvalidRestrictedChildNullOrWhitespace"), p0);
/// <summary>
/// Tag helpers cannot target attribute name '{0}' because it contains a '{1}' character.
/// </summary>
internal static string InvalidTargetedAttributeName
{
get => GetString("InvalidTargetedAttributeName");
}
/// <summary>
/// Tag helpers cannot target attribute name '{0}' because it contains a '{1}' character.
/// </summary>
internal static string FormatInvalidTargetedAttributeName(object p0, object p1)
=> string.Format(CultureInfo.CurrentCulture, GetString("InvalidTargetedAttributeName"), p0, p1);
/// <summary>
/// Targeted attribute name cannot be null or whitespace.
/// </summary>
internal static string InvalidTargetedAttributeNameNullOrWhitespace
{
get => GetString("InvalidTargetedAttributeNameNullOrWhitespace");
}
/// <summary>
/// Targeted attribute name cannot be null or whitespace.
/// </summary>
internal static string FormatInvalidTargetedAttributeNameNullOrWhitespace()
=> GetString("InvalidTargetedAttributeNameNullOrWhitespace");
/// <summary>
/// Tag helpers cannot target parent tag name '{0}' because it contains a '{1}' character.
/// </summary>
internal static string InvalidTargetedParentTagName
{
get => GetString("InvalidTargetedParentTagName");
}
/// <summary>
/// Tag helpers cannot target parent tag name '{0}' because it contains a '{1}' character.
/// </summary>
internal static string FormatInvalidTargetedParentTagName(object p0, object p1)
=> string.Format(CultureInfo.CurrentCulture, GetString("InvalidTargetedParentTagName"), p0, p1);
/// <summary>
/// Targeted parent tag name cannot be null or whitespace.
/// </summary>
internal static string InvalidTargetedParentTagNameNullOrWhitespace
{
get => GetString("InvalidTargetedParentTagNameNullOrWhitespace");
}
/// <summary>
/// Targeted parent tag name cannot be null or whitespace.
/// </summary>
internal static string FormatInvalidTargetedParentTagNameNullOrWhitespace()
=> GetString("InvalidTargetedParentTagNameNullOrWhitespace");
/// <summary>
/// Tag helpers cannot target tag name '{0}' because it contains a '{1}' character.
/// </summary>
internal static string InvalidTargetedTagName
{
get => GetString("InvalidTargetedTagName");
}
/// <summary>
/// Tag helpers cannot target tag name '{0}' because it contains a '{1}' character.
/// </summary>
internal static string FormatInvalidTargetedTagName(object p0, object p1)
=> string.Format(CultureInfo.CurrentCulture, GetString("InvalidTargetedTagName"), p0, p1);
/// <summary>
/// Targeted tag name cannot be null or whitespace.
/// </summary>
internal static string InvalidTargetedTagNameNullOrWhitespace
{
get => GetString("InvalidTargetedTagNameNullOrWhitespace");
}
/// <summary>
/// Targeted tag name cannot be null or whitespace.
/// </summary>
internal static string FormatInvalidTargetedTagNameNullOrWhitespace()
=> GetString("InvalidTargetedTagNameNullOrWhitespace");
private static string GetString(string name, params string[] formatterNames)
{
var value = _resourceManager.GetString(name);

View File

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

View File

@ -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<RazorDiagnosticDescriptor>
{
private readonly Func<string> _messageFormat;

View File

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

View File

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

View File

@ -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<RazorDiagnostic> Diagnostics { get; protected set; }
public bool HasAnyErrors
{
get
{
var anyErrors = Diagnostics.Any(diagnostic => diagnostic.Severity == RazorDiagnosticSeverity.Error);
return anyErrors;
}
}
/// <summary>
/// Acceptable <see cref="RequiredAttributeDescriptor.Name"/> comparison modes.
/// </summary>
public enum NameComparisonMode
{
/// <summary>
/// HTML attribute name case insensitively matches <see cref="RequiredAttributeDescriptor.Name"/>.
/// </summary>
FullMatch,
/// <summary>
/// HTML attribute name case insensitively starts with <see cref="RequiredAttributeDescriptor.Name"/>.
/// </summary>
PrefixMatch,
}
/// <summary>
/// Acceptable <see cref="RequiredAttributeDescriptor.Value"/> comparison modes.
/// </summary>
public enum ValueComparisonMode
{
/// <summary>
/// HTML attribute value always matches <see cref="RequiredAttributeDescriptor.Value"/>.
/// </summary>
None,
/// <summary>
/// HTML attribute value case sensitively matches <see cref="RequiredAttributeDescriptor.Value"/>.
/// </summary>
FullMatch,
/// <summary>
/// HTML attribute value case sensitively starts with <see cref="RequiredAttributeDescriptor.Value"/>.
/// </summary>
PrefixMatch,
/// <summary>
/// HTML attribute value case sensitively ends with <see cref="RequiredAttributeDescriptor.Value"/>.
/// </summary>
SuffixMatch,
}
}
}

View File

@ -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<char> InvalidNonWhitespaceAttributeNameCharacters { get; } = new HashSet<char>(
new[] { '@', '!', '<', '/', '?', '[', '>', ']', '=', '"', '\'', '*' });
private string _name;
private RequiredAttributeDescriptor.NameComparisonMode _nameComparison;
private string _value;
private RequiredAttributeDescriptor.ValueComparisonMode _valueComparison;
private HashSet<RazorDiagnostic> _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<RazorDiagnostic>(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<RazorDiagnostic> 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<RazorDiagnostic>();
}
}
private class DefaultTagHelperRequiredAttributeDescriptor : RequiredAttributeDescriptor
{
public DefaultTagHelperRequiredAttributeDescriptor(
string name,
NameComparisonMode nameComparison,
string value,
ValueComparisonMode valueComparison,
IEnumerable<RazorDiagnostic> diagnostics)
{
Name = name;
NameComparison = nameComparison;
Value = value;
ValueComparison = valueComparison;
Diagnostics = new List<RazorDiagnostic>(diagnostics);
}
}
}
}

View File

@ -159,4 +159,43 @@
<data name="RazorTemplateEngine_ItemCouldNotBeFound" xml:space="preserve">
<value>The item '{0}' could not be found.</value>
</data>
<data name="InvalidBoundAttributeName" xml:space="preserve">
<value>Invalid tag helper bound property '{0}.{1}'. Tag helpers cannot bind to HTML attributes with name '{2}' because the name contains a '{3}' character.</value>
</data>
<data name="InvalidBoundAttributeNameStartsWith" xml:space="preserve">
<value>Invalid tag helper bound property '{0}.{1}'. Tag helpers cannot bind to HTML attributes with name '{2}' because the name starts with '{3}'.</value>
</data>
<data name="InvalidBoundAttributeNullOrWhitespace" xml:space="preserve">
<value>Invalid tag helper bound property '{0}.{1}'. Tag helpers cannot bind to HTML attributes with a null or empty name.</value>
</data>
<data name="InvalidBoundAttributePrefix" xml:space="preserve">
<value>Invalid tag helper bound property '{0}.{1}'. Tag helpers cannot bind to HTML attributes with prefix '{2}' because the prefix contains a '{3}' character.</value>
</data>
<data name="InvalidBoundAttributePrefixStartsWith" xml:space="preserve">
<value>Invalid tag helper bound property '{0}.{1}'. Tag helpers cannot bind to HTML attributes with prefix '{2}' because the prefix starts with '{3}'.</value>
</data>
<data name="InvalidRestrictedChild" xml:space="preserve">
<value>Invalid restricted child '{0}' for tag helper '{1}'. Tag helpers cannot restrict child elements that contain a '{2}' character.</value>
</data>
<data name="InvalidRestrictedChildNullOrWhitespace" xml:space="preserve">
<value>Invalid restricted child for tag helper '{0}'. Name cannot be null or whitespace.</value>
</data>
<data name="InvalidTargetedAttributeName" xml:space="preserve">
<value>Tag helpers cannot target attribute name '{0}' because it contains a '{1}' character.</value>
</data>
<data name="InvalidTargetedAttributeNameNullOrWhitespace" xml:space="preserve">
<value>Targeted attribute name cannot be null or whitespace.</value>
</data>
<data name="InvalidTargetedParentTagName" xml:space="preserve">
<value>Tag helpers cannot target parent tag name '{0}' because it contains a '{1}' character.</value>
</data>
<data name="InvalidTargetedParentTagNameNullOrWhitespace" xml:space="preserve">
<value>Targeted parent tag name cannot be null or whitespace.</value>
</data>
<data name="InvalidTargetedTagName" xml:space="preserve">
<value>Tag helpers cannot target tag name '{0}' because it contains a '{1}' character.</value>
</data>
<data name="InvalidTargetedTagNameNullOrWhitespace" xml:space="preserve">
<value>Targeted tag name cannot be null or whitespace.</value>
</data>
</root>

View File

@ -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
{
/// <summary>
/// A metadata class describing a tag helper attribute.
/// </summary>
public class TagHelperAttributeDescriptor
{
private string _typeName;
private string _name;
private string _propertyName;
/// <summary>
/// Instantiates a new instance of the <see cref="TagHelperAttributeDescriptor"/> class.
/// </summary>
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;
}
/// <summary>
/// Gets an indication whether this <see cref="TagHelperAttributeDescriptor"/> is used for dictionary indexer
/// assignments.
/// </summary>
/// <value>
/// If <c>true</c> this <see cref="TagHelperAttributeDescriptor"/> should be associated with all HTML
/// attributes that have names starting with <see cref="Name"/>. Otherwise this
/// <see cref="TagHelperAttributeDescriptor"/> is used for property assignment and is only associated with an
/// HTML attribute that has the exact <see cref="Name"/>.
/// </value>
/// <remarks>
/// HTML attribute names are matched case-insensitively, regardless of <see cref="IsIndexer"/>.
/// </remarks>
public bool IsIndexer { get; set; }
/// <summary>
/// Gets or sets an indication whether this property is an <see cref="Enum"/>.
/// </summary>
public bool IsEnum { get; set; }
/// <summary>
/// Gets or sets an indication whether this property is of type <see cref="string"/> or, if
/// <see cref="IsIndexer"/> is <c>true</c>, whether the indexer's value is of type <see cref="string"/>.
/// </summary>
/// <value>
/// If <c>true</c> the <see cref="TypeName"/> is for <see cref="string"/>. This causes the Razor parser
/// to allow empty values for HTML attributes matching this <see cref="TagHelperAttributeDescriptor"/>. If
/// <c>false</c> empty values for such matching attributes lead to errors.
/// </value>
public bool IsStringProperty { get; set; }
/// <summary>
/// The HTML attribute name or, if <see cref="IsIndexer"/> is <c>true</c>, the prefix for matching attribute
/// names.
/// </summary>
public string Name
{
get
{
return _name;
}
set
{
if (value == null)
{
throw new ArgumentNullException(nameof(value));
}
_name = value;
}
}
/// <summary>
/// The name of the CLR property that corresponds to the HTML attribute.
/// </summary>
public string PropertyName
{
get
{
return _propertyName;
}
set
{
if (value == null)
{
throw new ArgumentNullException(nameof(value));
}
_propertyName = value;
}
}
/// <summary>
/// The full name of the named (see <see name="PropertyName"/>) property's <see cref="Type"/> or, if
/// <see cref="IsIndexer"/> is <c>true</c>, the full name of the indexer's value <see cref="Type"/>.
/// </summary>
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);
}
}
/// <summary>
/// The <see cref="TagHelperAttributeDesignTimeDescriptor"/> that contains design time information about
/// this attribute.
/// </summary>
public TagHelperAttributeDesignTimeDescriptor DesignTimeDescriptor { get; set; }
/// <summary>
/// Determines whether HTML attribute <paramref name="name"/> matches this
/// <see cref="TagHelperAttributeDescriptor"/>.
/// </summary>
/// <param name="name">Name of the HTML attribute to check.</param>
/// <returns>
/// <c>true</c> if this <see cref="TagHelperAttributeDescriptor"/> matches <paramref name="name"/>.
/// <c>false</c> otherwise.
/// </returns>
public bool IsNameMatch(string name)
{
if (IsIndexer)
{
return name.StartsWith(Name, StringComparison.OrdinalIgnoreCase);
}
else
{
return string.Equals(name, Name, StringComparison.OrdinalIgnoreCase);
}
}
}
}

View File

@ -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
{
/// <summary>
/// A metadata class containing information about tag helper use.
/// </summary>
public class TagHelperAttributeDesignTimeDescriptor
{
/// <summary>
/// A summary of how to use a tag helper.
/// </summary>
public string Summary { get; set; }
/// <summary>
/// Remarks about how to use a tag helper.
/// </summary>
public string Remarks { get; set; }
}
}

View File

@ -42,7 +42,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution
visitor.VisitBlock(import.Root);
}
}
visitor.VisitBlock(syntaxTree.Root);
var errorList = new List<RazorDiagnostic>();
@ -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<TagHelperDirectiveDescriptor> 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<TagHelperDescriptor> ProcessDirectives(
IReadOnlyList<TagHelperDirectiveDescriptor> directives,
IReadOnlyList<TagHelperDescriptor> tagHelpers,
@ -85,9 +117,6 @@ namespace Microsoft.AspNetCore.Razor.Evolution
{
var matches = new HashSet<TagHelperDescriptor>(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<TagHelperDescriptor> tagHelpers)
@ -254,21 +270,6 @@ namespace Microsoft.AspNetCore.Razor.Evolution
return true;
}
private static IReadOnlyList<TagHelperDescriptor> PrefixDescriptors(
string prefix,
IEnumerable<TagHelperDescriptor> 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)

View File

@ -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
{
/// <summary>
/// A metadata class describing a tag helper.
/// </summary>
public class TagHelperDescriptor
public abstract class TagHelperDescriptor
{
private string _prefix = string.Empty;
private string _tagName;
private string _typeName;
private string _assemblyName;
private IDictionary<string, string> _propertyBag;
private IEnumerable<TagHelperAttributeDescriptor> _attributes =
Enumerable.Empty<TagHelperAttributeDescriptor>();
private IEnumerable<TagHelperRequiredAttributeDescriptor> _requiredAttributes =
Enumerable.Empty<TagHelperRequiredAttributeDescriptor>();
private IEnumerable<RazorDiagnostic> _allDiagnostics;
/// <summary>
/// Creates a new <see cref="TagHelperDescriptor"/>.
/// </summary>
public TagHelperDescriptor()
protected TagHelperDescriptor(string kind)
{
Kind = kind;
}
/// <summary>
/// Creates a shallow copy of the given <see cref="TagHelperDescriptor"/>.
/// </summary>
/// <param name="descriptor">The <see cref="TagHelperDescriptor"/> to copy.</param>
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; }
/// <summary>
/// Text used as a required prefix when matching HTML start and end tags in the Razor source to available
/// tag helpers.
/// </summary>
public string Prefix
public IEnumerable<TagMatchingRule> TagMatchingRules { get; protected set; }
public string AssemblyName { get; protected set; }
public IEnumerable<BoundAttributeDescriptor> BoundAttributes { get; protected set; }
public IEnumerable<string> AllowedChildTags { get; protected set; }
public string Documentation { get; protected set; }
public string DisplayName { get; protected set; }
public string TagOutputHint { get; protected set; }
public IReadOnlyList<RazorDiagnostic> Diagnostics { get; protected set; }
public IReadOnlyDictionary<string, string> 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;
}
}
/// <summary>
/// The tag name that the tag helper should target.
/// </summary>
public string TagName
public virtual IEnumerable<RazorDiagnostic> 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;
}
}
/// <summary>
/// The full tag name that is required for the tag helper to target an HTML element.
/// </summary>
/// <remarks>This is equivalent to <see cref="Prefix"/> and <see cref="TagName"/> concatenated.</remarks>
public string FullTagName
{
get
{
return Prefix + TagName;
}
}
/// <summary>
/// The full name of the tag helper class.
/// </summary>
public string TypeName
{
get
{
return _typeName;
}
set
{
if (value == null)
{
throw new ArgumentNullException(nameof(value));
}
_typeName = value;
}
}
/// <summary>
/// The name of the assembly containing the tag helper class.
/// </summary>
public string AssemblyName
{
get
{
return _assemblyName;
}
set
{
if (value == null)
{
throw new ArgumentNullException(nameof(value));
}
_assemblyName = value;
}
}
/// <summary>
/// The list of attributes the tag helper expects.
/// </summary>
public IEnumerable<TagHelperAttributeDescriptor> Attributes
{
get
{
return _attributes;
}
set
{
if (value == null)
{
throw new ArgumentNullException(nameof(value));
}
_attributes = value;
}
}
/// <summary>
/// The list of required attribute names the tag helper expects to target an element.
/// </summary>
/// <remarks>
/// <c>*</c> at the end of an attribute name acts as a prefix match.
/// </remarks>
public IEnumerable<TagHelperRequiredAttributeDescriptor> RequiredAttributes
{
get
{
return _requiredAttributes;
}
set
{
if (value == null)
{
throw new ArgumentNullException(nameof(value));
}
_requiredAttributes = value;
}
}
/// <summary>
/// Get the names of elements allowed as children.
/// </summary>
/// <remarks><c>null</c> indicates all children are allowed.</remarks>
public IEnumerable<string> AllowedChildren { get; set; }
/// <summary>
/// Get the name of the HTML element required as the immediate parent.
/// </summary>
/// <remarks><c>null</c> indicates no restriction on parent tag.</remarks>
public string RequiredParent { get; set; }
/// <summary>
/// The expected tag structure.
/// </summary>
/// <remarks>
/// If <see cref="TagStructure.Unspecified"/> and no other tag helpers applying to the same element specify
/// their <see cref="TagStructure"/> the <see cref="TagStructure.NormalOrSelfClosing"/> behavior is used:
/// <para>
/// <code>
/// &lt;my-tag-helper&gt;&lt;/my-tag-helper&gt;
/// &lt;!-- OR --&gt;
/// &lt;my-tag-helper /&gt;
/// </code>
/// Otherwise, if another tag helper applying to the same element does specify their behavior, that behavior
/// is used.
/// </para>
/// <para>
/// If <see cref="TagStructure.WithoutEndTag"/> HTML elements can be written in the following formats:
/// <code>
/// &lt;my-tag-helper&gt;
/// &lt;!-- OR --&gt;
/// &lt;my-tag-helper /&gt;
/// </code>
/// </para>
/// </remarks>
public TagStructure TagStructure { get; set; }
/// <summary>
/// The <see cref="TagHelperDesignTimeDescriptor"/> that contains design time information about this
/// tag helper.
/// </summary>
public TagHelperDesignTimeDescriptor DesignTimeDescriptor { get; set; }
/// <summary>
/// A dictionary containing additional information about the <see cref="TagHelperDescriptor"/>.
/// </summary>
public IDictionary<string, string> PropertyBag
{
get
{
if (_propertyBag == null)
{
_propertyBag = new Dictionary<string, string>();
}
return _propertyBag;
}
return _allDiagnostics;
}
}
}

View File

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

View File

@ -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
{
/// <summary>
/// A metadata class containing design time information about a tag helper.
/// </summary>
public class TagHelperDesignTimeDescriptor
{
/// <summary>
/// A summary of how to use a tag helper.
/// </summary>
public string Summary { get; set; }
/// <summary>
/// Remarks about how to use a tag helper.
/// </summary>
public string Remarks { get; set; }
/// <summary>
/// The HTML element a tag helper may output.
/// </summary>
/// <remarks>
/// In IDEs supporting IntelliSense, may override the HTML information provided at design time.
/// </remarks>
public string OutputElementHint { get; set; }
}
}

View File

@ -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
{
/// <summary>
/// A metadata class describing a required tag helper attribute.
/// </summary>
public class TagHelperRequiredAttributeDescriptor
{
/// <summary>
/// The HTML attribute name.
/// </summary>
public string Name { get; set; }
/// <summary>
/// The comparison method to use for <see cref="Name"/> when determining if an HTML attribute name matches.
/// </summary>
public TagHelperRequiredAttributeNameComparison NameComparison { get; set; }
/// <summary>
/// The HTML attribute value.
/// </summary>
public string Value { get; set; }
/// <summary>
/// The comparison method to use for <see cref="Value"/> when determining if an HTML attribute value matches.
/// </summary>
public TagHelperRequiredAttributeValueComparison ValueComparison { get; set; }
/// <summary>
/// Determines if the current <see cref="TagHelperRequiredAttributeDescriptor"/> matches the given
/// <paramref name="attributeName"/> and <paramref name="attributeValue"/>.
/// </summary>
/// <param name="attributeName">An HTML attribute name.</param>
/// <param name="attributeValue">An HTML attribute value.</param>
/// <returns><c>true</c> if the current <see cref="TagHelperRequiredAttributeDescriptor"/> matches
/// <paramref name="attributeName"/> and <paramref name="attributeValue"/>; <c>false</c> otherwise.</returns>
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;
}
}
}
}

View File

@ -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
{
/// <summary>
/// Acceptable <see cref="TagHelperRequiredAttributeDescriptor.Name"/> comparison modes.
/// </summary>
public enum TagHelperRequiredAttributeNameComparison
{
/// <summary>
/// HTML attribute name case insensitively matches <see cref="TagHelperRequiredAttributeDescriptor.Name"/>.
/// </summary>
FullMatch,
/// <summary>
/// HTML attribute name case insensitively starts with <see cref="TagHelperRequiredAttributeDescriptor.Name"/>.
/// </summary>
PrefixMatch,
}
}

View File

@ -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
{
/// <summary>
/// Acceptable <see cref="TagHelperRequiredAttributeDescriptor.Value"/> comparison modes.
/// </summary>
public enum TagHelperRequiredAttributeValueComparison
{
/// <summary>
/// HTML attribute value always matches <see cref="TagHelperRequiredAttributeDescriptor.Value"/>.
/// </summary>
None,
/// <summary>
/// HTML attribute value case sensitively matches <see cref="TagHelperRequiredAttributeDescriptor.Value"/>.
/// </summary>
FullMatch,
/// <summary>
/// HTML attribute value case sensitively starts with <see cref="TagHelperRequiredAttributeDescriptor.Value"/>.
/// </summary>
PrefixMatch,
/// <summary>
/// HTML attribute value case sensitively ends with <see cref="TagHelperRequiredAttributeDescriptor.Value"/>.
/// </summary>
SuffixMatch,
}
}

View File

@ -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<RazorDiagnostic> _allDiagnostics;
public string TagName { get; protected set; }
public IEnumerable<RequiredAttributeDescriptor> Attributes { get; protected set; }
public string ParentTag { get; protected set; }
public TagStructure TagStructure { get; protected set; }
public IReadOnlyList<RazorDiagnostic> 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<RazorDiagnostic> GetAllDiagnostics()
{
if (_allDiagnostics == null)
{
var attributeDiagnostics = Attributes.SelectMany(attribute => attribute.Diagnostics);
var combinedDiagnostics = Diagnostics.Concat(attributeDiagnostics);
_allDiagnostics = combinedDiagnostics.ToArray();
}
return _allDiagnostics;
}
}
}

View File

@ -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<char> InvalidNonWhitespaceTagNameCharacters { get; } = new HashSet<char>(
new[] { '@', '!', '<', '/', '?', '[', '>', ']', '=', '"', '\'', '*' });
private string _tagName;
private string _parentTag;
private TagStructure _tagStructure;
private HashSet<RequiredAttributeDescriptor> _requiredAttributeDescriptors;
private HashSet<RazorDiagnostic> _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<RequiredAttributeDescriptorBuilder> 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<RazorDiagnostic>(validationDiagnostics);
if (_diagnostics != null)
{
diagnostics.UnionWith(_diagnostics);
}
var rule = new DefaultTagMatchingRule(
_tagName,
_parentTag,
_tagStructure,
_requiredAttributeDescriptors ?? Enumerable.Empty<RequiredAttributeDescriptor>(),
diagnostics);
return rule;
}
public void Reset()
{
_tagName = null;
_parentTag = null;
_tagStructure = default(TagStructure);
_requiredAttributeDescriptors?.Clear();
_diagnostics?.Clear();
}
private IEnumerable<RazorDiagnostic> 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<RequiredAttributeDescriptor>(RequiredAttributeDescriptorComparer.Default);
}
}
private void EnsureDiagnostics()
{
if (_diagnostics == null)
{
_diagnostics = new HashSet<RazorDiagnostic>();
}
}
private class DefaultTagMatchingRule : TagMatchingRule
{
public DefaultTagMatchingRule(
string tagName,
string parentTag,
TagStructure tagStructure,
IEnumerable<RequiredAttributeDescriptor> requiredAttributeDescriptors,
IEnumerable<RazorDiagnostic> diagnostics)
{
TagName = tagName;
ParentTag = parentTag;
TagStructure = tagStructure;
Attributes = new List<RequiredAttributeDescriptor>(requiredAttributeDescriptors);
Diagnostics = new List<RazorDiagnostic>(diagnostics);
}
}
}
}

View File

@ -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<TagMatchingRule>
{
/// <summary>
/// A default instance of the <see cref="TagMatchingRuleComparer"/>.
/// </summary>
public static readonly TagMatchingRuleComparer Default = new TagMatchingRuleComparer();
/// <summary>
/// A default instance of the <see cref="TagMatchingRuleComparer"/> that does case-sensitive comparison.
/// </summary>
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;
}
}
}

View File

@ -37,11 +37,16 @@ namespace Microsoft.AspNetCore.Razor.Compilation.TagHelpers
/// </remarks>
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) &&

View File

@ -91,7 +91,7 @@ namespace Microsoft.AspNetCore.Razor.Parser.TagHelpers
public TagMode TagMode { get; }
/// <summary>
/// <see cref="TagHelperDescriptor"/>s for the HTML element.
/// <see cref="TagHelperDescriptor"/> bindings for the HTML element.
/// </summary>
public IEnumerable<TagHelperDescriptor> Descriptors { get; }

View File

@ -21,9 +21,10 @@ namespace Microsoft.CodeAnalysis.Razor
public override TagHelperResolutionResult GetTagHelpers(Compilation compilation, IEnumerable<string> assemblyNameFilters)
{
var descriptors = new List<TagHelperDescriptor>();
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<RazorDiagnostic>();
@ -37,7 +38,7 @@ namespace Microsoft.CodeAnalysis.Razor
return resolutionResult;
}
private void VisitTagHelpers(Compilation compilation, IEnumerable<string> assemblyNameFilters, List<TagHelperDescriptor> results, ErrorSink errors)
private void VisitTagHelpers(Compilation compilation, IEnumerable<string> assemblyNameFilters, List<TagHelperDescriptor> results)
{
var types = new List<INamedTypeSymbol>();
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);
}
}
}
}

View File

@ -10,373 +10,117 @@ namespace Microsoft.CodeAnalysis.Razor
private static readonly ResourceManager _resourceManager
= new ResourceManager("Microsoft.CodeAnalysis.Razor.Resources", typeof(Resources).GetTypeInfo().Assembly);
/// <summary>
/// {0} cannot be null or an empty string.
/// </summary>
internal static string Argument_Cannot_Be_Null_Or_Empty
{
get { return GetString("Argument_Cannot_Be_Null_Or_Empty"); }
}
/// <summary>
/// {0} cannot be null or an empty string.
/// </summary>
internal static string FormatArgument_Cannot_Be_Null_Or_Empty(object p0)
{
return string.Format(CultureInfo.CurrentCulture, GetString("Argument_Cannot_Be_Null_Or_Empty"), p0);
}
/// <summary>
/// Tag helpers cannot target {0} name '{1}' because it contains a '{2}' character.
/// </summary>
internal static string HtmlTargetElementAttribute_InvalidName
{
get { return GetString("HtmlTargetElementAttribute_InvalidName"); }
}
/// <summary>
/// Tag helpers cannot target {0} name '{1}' because it contains a '{2}' character.
/// </summary>
internal static string FormatHtmlTargetElementAttribute_InvalidName(object p0, object p1, object p2)
{
return string.Format(CultureInfo.CurrentCulture, GetString("HtmlTargetElementAttribute_InvalidName"), p0, p1, p2);
}
/// <summary>
/// {0} name cannot be null or whitespace.
/// </summary>
internal static string HtmlTargetElementAttribute_NameCannotBeNullOrWhitespace
{
get { return GetString("HtmlTargetElementAttribute_NameCannotBeNullOrWhitespace"); }
}
/// <summary>
/// {0} name cannot be null or whitespace.
/// </summary>
internal static string FormatHtmlTargetElementAttribute_NameCannotBeNullOrWhitespace(object p0)
{
return string.Format(CultureInfo.CurrentCulture, GetString("HtmlTargetElementAttribute_NameCannotBeNullOrWhitespace"), p0);
}
/// <summary>
/// Attribute
/// </summary>
internal static string TagHelperDescriptorFactory_Attribute
{
get { return GetString("TagHelperDescriptorFactory_Attribute"); }
}
/// <summary>
/// Attribute
/// </summary>
internal static string FormatTagHelperDescriptorFactory_Attribute()
{
return GetString("TagHelperDescriptorFactory_Attribute");
}
/// <summary>
/// Could not find matching ']' for required attribute '{0}'.
/// </summary>
internal static string TagHelperDescriptorFactory_CouldNotFindMatchingEndBrace
{
get { return GetString("TagHelperDescriptorFactory_CouldNotFindMatchingEndBrace"); }
get => GetString("TagHelperDescriptorFactory_CouldNotFindMatchingEndBrace");
}
/// <summary>
/// Could not find matching ']' for required attribute '{0}'.
/// </summary>
internal static string FormatTagHelperDescriptorFactory_CouldNotFindMatchingEndBrace(object p0)
{
return string.Format(CultureInfo.CurrentCulture, GetString("TagHelperDescriptorFactory_CouldNotFindMatchingEndBrace"), p0);
}
/// <summary>
/// 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}'.
/// </summary>
internal static string TagHelperDescriptorFactory_InvalidAttributeNameAttribute
{
get { return GetString("TagHelperDescriptorFactory_InvalidAttributeNameAttribute"); }
}
/// <summary>
/// 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}'.
/// </summary>
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);
}
/// <summary>
/// Invalid tag helper bound property '{0}.{1}'. '{2}.{3}' must be null or empty if property has no public setter.
/// </summary>
internal static string TagHelperDescriptorFactory_InvalidAttributeNameNotNullOrEmpty
{
get { return GetString("TagHelperDescriptorFactory_InvalidAttributeNameNotNullOrEmpty"); }
}
/// <summary>
/// Invalid tag helper bound property '{0}.{1}'. '{2}.{3}' must be null or empty if property has no public setter.
/// </summary>
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);
}
/// <summary>
/// Invalid tag helper bound property '{0}.{1}'. Tag helpers cannot bind to HTML attributes with a null or empty name.
/// </summary>
internal static string TagHelperDescriptorFactory_InvalidAttributeNameNullOrEmpty
{
get { return GetString("TagHelperDescriptorFactory_InvalidAttributeNameNullOrEmpty"); }
}
/// <summary>
/// Invalid tag helper bound property '{0}.{1}'. Tag helpers cannot bind to HTML attributes with a null or empty name.
/// </summary>
internal static string FormatTagHelperDescriptorFactory_InvalidAttributeNameNullOrEmpty(object p0, object p1)
{
return string.Format(CultureInfo.CurrentCulture, GetString("TagHelperDescriptorFactory_InvalidAttributeNameNullOrEmpty"), p0, p1);
}
/// <summary>
/// Invalid tag helper bound property '{0}.{1}'. Tag helpers cannot bind to HTML attributes with {2} '{3}' because {2} contains a '{4}' character.
/// </summary>
internal static string TagHelperDescriptorFactory_InvalidAttributeNameOrPrefixCharacter
{
get { return GetString("TagHelperDescriptorFactory_InvalidAttributeNameOrPrefixCharacter"); }
}
/// <summary>
/// Invalid tag helper bound property '{0}.{1}'. Tag helpers cannot bind to HTML attributes with {2} '{3}' because {2} contains a '{4}' character.
/// </summary>
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);
}
/// <summary>
/// Invalid tag helper bound property '{0}.{1}'. Tag helpers cannot bind to HTML attributes with {2} '{3}' because {2} starts with '{4}'.
/// </summary>
internal static string TagHelperDescriptorFactory_InvalidAttributeNameOrPrefixStart
{
get { return GetString("TagHelperDescriptorFactory_InvalidAttributeNameOrPrefixStart"); }
}
/// <summary>
/// Invalid tag helper bound property '{0}.{1}'. Tag helpers cannot bind to HTML attributes with {2} '{3}' because {2} starts with '{4}'.
/// </summary>
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);
}
/// <summary>
/// Invalid tag helper bound property '{0}.{1}'. Tag helpers cannot bind to HTML attributes with a whitespace {2}.
/// </summary>
internal static string TagHelperDescriptorFactory_InvalidAttributeNameOrPrefixWhitespace
{
get { return GetString("TagHelperDescriptorFactory_InvalidAttributeNameOrPrefixWhitespace"); }
}
/// <summary>
/// Invalid tag helper bound property '{0}.{1}'. Tag helpers cannot bind to HTML attributes with a whitespace {2}.
/// </summary>
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);
/// <summary>
/// Invalid tag helper bound property '{0}.{1}'. '{2}.{3}' must be null unless property type implements '{4}'.
/// </summary>
internal static string TagHelperDescriptorFactory_InvalidAttributePrefixNotNull
{
get { return GetString("TagHelperDescriptorFactory_InvalidAttributePrefixNotNull"); }
get => GetString("TagHelperDescriptorFactory_InvalidAttributePrefixNotNull");
}
/// <summary>
/// Invalid tag helper bound property '{0}.{1}'. '{2}.{3}' must be null unless property type implements '{4}'.
/// </summary>
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);
/// <summary>
/// Invalid tag helper bound property '{0}.{1}'. '{2}.{3}' must be null or empty if property has no public setter.
/// </summary>
internal static string TagHelperDescriptorFactory_InvalidAttributeNameNotNullOrEmpty
{
return string.Format(CultureInfo.CurrentCulture, GetString("TagHelperDescriptorFactory_InvalidAttributePrefixNotNull"), p0, p1, p2, p3, p4);
get => GetString("TagHelperDescriptorFactory_InvalidAttributeNameNotNullOrEmpty");
}
/// <summary>
/// Invalid tag helper bound property '{0}.{1}'. '{2}.{3}' must be null or empty if property has no public setter.
/// </summary>
internal static string FormatTagHelperDescriptorFactory_InvalidAttributeNameNotNullOrEmpty(object p0, object p1, object p2, object p3)
=> string.Format(CultureInfo.CurrentCulture, GetString("TagHelperDescriptorFactory_InvalidAttributeNameNotNullOrEmpty"), p0, p1, p2, p3);
/// <summary>
/// Invalid tag helper bound property '{0}.{1}'. '{2}.{3}' must not be null if property has no public setter and its type implements '{4}'.
/// </summary>
internal static string TagHelperDescriptorFactory_InvalidAttributePrefixNull
{
get { return GetString("TagHelperDescriptorFactory_InvalidAttributePrefixNull"); }
get => GetString("TagHelperDescriptorFactory_InvalidAttributePrefixNull");
}
/// <summary>
/// Invalid tag helper bound property '{0}.{1}'. '{2}.{3}' must not be null if property has no public setter and its type implements '{4}'.
/// </summary>
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);
/// <summary>
/// Invalid required attribute character '{0}' in required attribute '{1}'. Separate required attributes with commas.
/// </summary>
internal static string TagHelperDescriptorFactory_InvalidRequiredAttributeCharacter
{
get { return GetString("TagHelperDescriptorFactory_InvalidRequiredAttributeCharacter"); }
get => GetString("TagHelperDescriptorFactory_InvalidRequiredAttributeCharacter");
}
/// <summary>
/// Invalid required attribute character '{0}' in required attribute '{1}'. Separate required attributes with commas.
/// </summary>
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);
/// <summary>
/// Required attribute '{0}' has mismatched quotes '{1}' around value.
/// </summary>
internal static string TagHelperDescriptorFactory_InvalidRequiredAttributeMismatchedQuotes
{
get { return GetString("TagHelperDescriptorFactory_InvalidRequiredAttributeMismatchedQuotes"); }
get => GetString("TagHelperDescriptorFactory_InvalidRequiredAttributeMismatchedQuotes");
}
/// <summary>
/// Required attribute '{0}' has mismatched quotes '{1}' around value.
/// </summary>
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);
/// <summary>
/// Invalid character '{0}' in required attribute '{1}'. Expected supported CSS operator or ']'.
/// </summary>
internal static string TagHelperDescriptorFactory_InvalidRequiredAttributeOperator
{
get { return GetString("TagHelperDescriptorFactory_InvalidRequiredAttributeOperator"); }
get => GetString("TagHelperDescriptorFactory_InvalidRequiredAttributeOperator");
}
/// <summary>
/// Invalid character '{0}' in required attribute '{1}'. Expected supported CSS operator or ']'.
/// </summary>
internal static string FormatTagHelperDescriptorFactory_InvalidRequiredAttributeOperator(object p0, object p1)
{
return string.Format(CultureInfo.CurrentCulture, GetString("TagHelperDescriptorFactory_InvalidRequiredAttributeOperator"), p0, p1);
}
/// <summary>
/// Invalid '{0}' tag name '{1}' for tag helper '{2}'. Tag helpers cannot restrict child elements that contain a '{3}' character.
/// </summary>
internal static string TagHelperDescriptorFactory_InvalidRestrictChildrenAttributeName
{
get { return GetString("TagHelperDescriptorFactory_InvalidRestrictChildrenAttributeName"); }
}
/// <summary>
/// Invalid '{0}' tag name '{1}' for tag helper '{2}'. Tag helpers cannot restrict child elements that contain a '{3}' character.
/// </summary>
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);
}
/// <summary>
/// Invalid '{0}' tag name for tag helper '{1}'. Name cannot be null or whitespace.
/// </summary>
internal static string TagHelperDescriptorFactory_InvalidRestrictChildrenAttributeNameNullWhitespace
{
get { return GetString("TagHelperDescriptorFactory_InvalidRestrictChildrenAttributeNameNullWhitespace"); }
}
/// <summary>
/// Invalid '{0}' tag name for tag helper '{1}'. Name cannot be null or whitespace.
/// </summary>
internal static string FormatTagHelperDescriptorFactory_InvalidRestrictChildrenAttributeNameNullWhitespace(object p0, object p1)
{
return string.Format(CultureInfo.CurrentCulture, GetString("TagHelperDescriptorFactory_InvalidRestrictChildrenAttributeNameNullWhitespace"), p0, p1);
}
/// <summary>
/// name
/// </summary>
internal static string TagHelperDescriptorFactory_Name
{
get { return GetString("TagHelperDescriptorFactory_Name"); }
}
/// <summary>
/// name
/// </summary>
internal static string FormatTagHelperDescriptorFactory_Name()
{
return GetString("TagHelperDescriptorFactory_Name");
}
/// <summary>
/// Parent Tag
/// </summary>
internal static string TagHelperDescriptorFactory_ParentTag
{
get { return GetString("TagHelperDescriptorFactory_ParentTag"); }
}
/// <summary>
/// Parent Tag
/// </summary>
internal static string FormatTagHelperDescriptorFactory_ParentTag()
{
return GetString("TagHelperDescriptorFactory_ParentTag");
}
=> string.Format(CultureInfo.CurrentCulture, GetString("TagHelperDescriptorFactory_InvalidRequiredAttributeOperator"), p0, p1);
/// <summary>
/// Required attribute '{0}' has a partial CSS operator. '{1}' must be followed by an equals.
/// </summary>
internal static string TagHelperDescriptorFactory_PartialRequiredAttributeOperator
{
get { return GetString("TagHelperDescriptorFactory_PartialRequiredAttributeOperator"); }
get => GetString("TagHelperDescriptorFactory_PartialRequiredAttributeOperator");
}
/// <summary>
/// Required attribute '{0}' has a partial CSS operator. '{1}' must be followed by an equals.
/// </summary>
internal static string FormatTagHelperDescriptorFactory_PartialRequiredAttributeOperator(object p0, object p1)
{
return string.Format(CultureInfo.CurrentCulture, GetString("TagHelperDescriptorFactory_PartialRequiredAttributeOperator"), p0, p1);
}
/// <summary>
/// prefix
/// </summary>
internal static string TagHelperDescriptorFactory_Prefix
{
get { return GetString("TagHelperDescriptorFactory_Prefix"); }
}
/// <summary>
/// prefix
/// </summary>
internal static string FormatTagHelperDescriptorFactory_Prefix()
{
return GetString("TagHelperDescriptorFactory_Prefix");
}
/// <summary>
/// Tag
/// </summary>
internal static string TagHelperDescriptorFactory_Tag
{
get { return GetString("TagHelperDescriptorFactory_Tag"); }
}
/// <summary>
/// Tag
/// </summary>
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)
{

View File

@ -15,80 +15,70 @@ namespace Microsoft.CodeAnalysis.Razor
/// </summary>
internal static string ViewComponent_AmbiguousMethods
{
get { return GetString("ViewComponent_AmbiguousMethods"); }
get => GetString("ViewComponent_AmbiguousMethods");
}
/// <summary>
/// View component '{0}' must have exactly one public method named '{1}' or '{2}'.
/// </summary>
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);
/// <summary>
/// Method '{0}' of view component '{1}' should be declared to return {2}&amp;lt;T&amp;gt;.
/// </summary>
internal static string ViewComponent_AsyncMethod_ShouldReturnTask
{
get { return GetString("ViewComponent_AsyncMethod_ShouldReturnTask"); }
get => GetString("ViewComponent_AsyncMethod_ShouldReturnTask");
}
/// <summary>
/// Method '{0}' of view component '{1}' should be declared to return {2}&amp;lt;T&amp;gt;.
/// </summary>
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);
/// <summary>
/// Could not find an '{0}' or '{1}' method for the view component '{2}'.
/// </summary>
internal static string ViewComponent_CannotFindMethod
{
get { return GetString("ViewComponent_CannotFindMethod"); }
get => GetString("ViewComponent_CannotFindMethod");
}
/// <summary>
/// Could not find an '{0}' or '{1}' method for the view component '{2}'.
/// </summary>
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);
/// <summary>
/// Method '{0}' of view component '{1}' cannot return a {2}.
/// </summary>
internal static string ViewComponent_SyncMethod_CannotReturnTask
{
get { return GetString("ViewComponent_SyncMethod_CannotReturnTask"); }
get => GetString("ViewComponent_SyncMethod_CannotReturnTask");
}
/// <summary>
/// Method '{0}' of view component '{1}' cannot return a {2}.
/// </summary>
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);
/// <summary>
/// Method '{0}' of view component '{1}' should be declared to return a value.
/// </summary>
internal static string ViewComponent_SyncMethod_ShouldReturnValue
{
get { return GetString("ViewComponent_SyncMethod_ShouldReturnValue"); }
get => GetString("ViewComponent_SyncMethod_ShouldReturnValue");
}
/// <summary>
/// Method '{0}' of view component '{1}' should be declared to return a value.
/// </summary>
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)
{

View File

@ -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<string, TValue>");
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<string, TValue>");
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
}
}

View File

@ -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<char, RequiredAttributeDescriptor.ValueComparisonMode> CssValueComparisons =
new Dictionary<char, RequiredAttributeDescriptor.ValueComparisonMode>
{
{ '=', 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<RequiredAttributeDescriptor>();
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++;
}
}
}
}
}

View File

@ -117,42 +117,15 @@
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="Argument_Cannot_Be_Null_Or_Empty" xml:space="preserve">
<value>{0} cannot be null or an empty string.</value>
</data>
<data name="HtmlTargetElementAttribute_InvalidName" xml:space="preserve">
<value>Tag helpers cannot target {0} name '{1}' because it contains a '{2}' character.</value>
</data>
<data name="HtmlTargetElementAttribute_NameCannotBeNullOrWhitespace" xml:space="preserve">
<value>{0} name cannot be null or whitespace.</value>
</data>
<data name="TagHelperDescriptorFactory_Attribute" xml:space="preserve">
<value>Attribute</value>
</data>
<data name="TagHelperDescriptorFactory_CouldNotFindMatchingEndBrace" xml:space="preserve">
<value>Could not find matching ']' for required attribute '{0}'.</value>
</data>
<data name="TagHelperDescriptorFactory_InvalidAttributeNameAttribute" xml:space="preserve">
<value>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}'.</value>
<data name="TagHelperDescriptorFactory_InvalidAttributePrefixNotNull" xml:space="preserve">
<value>Invalid tag helper bound property '{0}.{1}'. '{2}.{3}' must be null unless property type implements '{4}'.</value>
</data>
<data name="TagHelperDescriptorFactory_InvalidAttributeNameNotNullOrEmpty" xml:space="preserve">
<value>Invalid tag helper bound property '{0}.{1}'. '{2}.{3}' must be null or empty if property has no public setter.</value>
</data>
<data name="TagHelperDescriptorFactory_InvalidAttributeNameNullOrEmpty" xml:space="preserve">
<value>Invalid tag helper bound property '{0}.{1}'. Tag helpers cannot bind to HTML attributes with a null or empty name.</value>
</data>
<data name="TagHelperDescriptorFactory_InvalidAttributeNameOrPrefixCharacter" xml:space="preserve">
<value>Invalid tag helper bound property '{0}.{1}'. Tag helpers cannot bind to HTML attributes with {2} '{3}' because {2} contains a '{4}' character.</value>
</data>
<data name="TagHelperDescriptorFactory_InvalidAttributeNameOrPrefixStart" xml:space="preserve">
<value>Invalid tag helper bound property '{0}.{1}'. Tag helpers cannot bind to HTML attributes with {2} '{3}' because {2} starts with '{4}'.</value>
</data>
<data name="TagHelperDescriptorFactory_InvalidAttributeNameOrPrefixWhitespace" xml:space="preserve">
<value>Invalid tag helper bound property '{0}.{1}'. Tag helpers cannot bind to HTML attributes with a whitespace {2}.</value>
</data>
<data name="TagHelperDescriptorFactory_InvalidAttributePrefixNotNull" xml:space="preserve">
<value>Invalid tag helper bound property '{0}.{1}'. '{2}.{3}' must be null unless property type implements '{4}'.</value>
</data>
<data name="TagHelperDescriptorFactory_InvalidAttributePrefixNull" xml:space="preserve">
<value>Invalid tag helper bound property '{0}.{1}'. '{2}.{3}' must not be null if property has no public setter and its type implements '{4}'.</value>
</data>
@ -165,25 +138,7 @@
<data name="TagHelperDescriptorFactory_InvalidRequiredAttributeOperator" xml:space="preserve">
<value>Invalid character '{0}' in required attribute '{1}'. Expected supported CSS operator or ']'.</value>
</data>
<data name="TagHelperDescriptorFactory_InvalidRestrictChildrenAttributeName" xml:space="preserve">
<value>Invalid '{0}' tag name '{1}' for tag helper '{2}'. Tag helpers cannot restrict child elements that contain a '{3}' character.</value>
</data>
<data name="TagHelperDescriptorFactory_InvalidRestrictChildrenAttributeNameNullWhitespace" xml:space="preserve">
<value>Invalid '{0}' tag name for tag helper '{1}'. Name cannot be null or whitespace.</value>
</data>
<data name="TagHelperDescriptorFactory_Name" xml:space="preserve">
<value>name</value>
</data>
<data name="TagHelperDescriptorFactory_ParentTag" xml:space="preserve">
<value>Parent Tag</value>
</data>
<data name="TagHelperDescriptorFactory_PartialRequiredAttributeOperator" xml:space="preserve">
<value>Required attribute '{0}' has a partial CSS operator. '{1}' must be followed by an equals.</value>
</data>
<data name="TagHelperDescriptorFactory_Prefix" xml:space="preserve">
<value>prefix</value>
</data>
<data name="TagHelperDescriptorFactory_Tag" xml:space="preserve">
<value>Tag</value>
</data>
</root>

View File

@ -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)
{

View File

@ -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<IParameterSymbol> methodParameters, TagMatchingRuleBuilder builder)
{
var methodParameters = GetInvokeMethodParameters(type);
var attributeDescriptors = new List<TagHelperAttributeDescriptor>();
var indexerDescriptors = new List<TagHelperAttributeDescriptor>();
var requiredAttributeDescriptors = new List<TagHelperRequiredAttributeDescriptor>();
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<IParameterSymbol> 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<IParameterSymbol> GetInvokeMethodParameters(INamedTypeSymbol componentType)

View File

@ -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
{
/// <summary>
/// Extracts summary and remarks XML documentation from XML member documentation.
/// </summary>
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);
}
/// <summary>
/// Retrieves the <c>&lt;summary&gt;</c> documentation.
/// </summary>
/// <returns><c>&lt;summary&gt;</c> documentation.</returns>
public string GetSummary()
{
var summaryElement = _element.Element("summary");
if (summaryElement != null)
{
var summaryValue = GetElementValue(summaryElement);
return summaryValue;
}
return null;
}
/// <summary>
/// Retrieves the <c>&lt;remarks&gt;</c> documentation.
/// </summary>
/// <returns><c>&lt;remarks&gt;</c> documentation.</returns>
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();
}
}
}

View File

@ -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<TagHelperDescriptor> tagHelpers)
private IReadOnlyList<TagHelperDescriptor> GetDocumentedTagHelpers(Compilation compilation, IReadOnlyList<TagHelperDescriptor> tagHelpers)
{
var documentedTagHelpers = new List<TagHelperDescriptor>();
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()

View File

@ -37,6 +37,7 @@
<PackageReference Include="Microsoft.VisualStudio.Shell.Interop.11.0" Version="11.0.61030" />
<PackageReference Include="Microsoft.VisualStudio.Shell.Interop.12.0" Version="12.0.30110" />
<PackageReference Include="Microsoft.VisualStudio.Text.Data" Version="15.0.26201" />
<PackageReference Include="Newtonsoft.Json" Version="$(JsonNetVersion)" />
<ProjectReference Include="..\..\src\Microsoft.CodeAnalysis.Razor.Workspaces\Microsoft.CodeAnalysis.Razor.Workspaces.csproj" />
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Razor.Evolution\Microsoft.AspNetCore.Razor.Evolution.csproj" />
</ItemGroup>

View File

@ -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);
/// <summary>
/// Deserialization of {0} type '{1}' is not supported.
/// </summary>
internal static string RazorDiagnosticJsonConverter_UnsupportedRazorDiagnosticType
{
get => GetString("RazorDiagnosticJsonConverter_UnsupportedRazorDiagnosticType");
}
/// <summary>
/// Deserialization of {0} type '{1}' is not supported.
/// </summary>
internal static string FormatRazorDiagnosticJsonConverter_UnsupportedRazorDiagnosticType(object p0, object p1)
=> string.Format(CultureInfo.CurrentCulture, GetString("RazorDiagnosticJsonConverter_UnsupportedRazorDiagnosticType"), p0, p1);
/// <summary>
/// An unexpected exception occurred when invoking '{0}.{1}' on the Razor language service.
/// </summary>
internal static string UnexpectedException
{
get { return GetString("UnexpectedException"); }
get => GetString("UnexpectedException");
}
/// <summary>
/// An unexpected exception occurred when invoking '{0}.{1}' on the Razor language service.
/// </summary>
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)
{

View File

@ -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<JObject>();
var absoluteIndex = span[nameof(SourceSpan.AbsoluteIndex)].Value<int>();
var lineIndex = span[nameof(SourceSpan.LineIndex)].Value<int>();
var characterIndex = span[nameof(SourceSpan.CharacterIndex)].Value<int>();
var length = span[nameof(SourceSpan.Length)].Value<int>();
var filePath = span[nameof(SourceSpan.FilePath)].Value<string>();
var message = diagnostic[RazorDiagnosticMessageKey].Value<string>();
var typeName = diagnostic[RazorDiagnosticTypeNameKey].Value<string>();
if (string.Equals(typeName, typeof(DefaultRazorDiagnostic).FullName, StringComparison.Ordinal))
{
var id = diagnostic[nameof(RazorDiagnostic.Id)].Value<string>();
var severity = diagnostic[nameof(RazorDiagnostic.Severity)].Value<int>();
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<T>(JsonWriter writer, string key, T value)
{
writer.WritePropertyName(key);
writer.WriteValue(value);
}
}
}

View File

@ -117,6 +117,9 @@
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="RazorDiagnosticJsonConverter_UnsupportedRazorDiagnosticType" xml:space="preserve">
<value>Deserialization of {0} type '{1}' is not supported.</value>
</data>
<data name="UnexpectedException" xml:space="preserve">
<value>An unexpected exception occurred when invoking '{0}.{1}' on the Razor language service.</value>
</data>

View File

@ -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<string>();
var typeName = descriptor[nameof(TagHelperDescriptor.Name)].Value<string>();
var assemblyName = descriptor[nameof(TagHelperDescriptor.AssemblyName)].Value<string>();
var tagMatchingRules = descriptor[nameof(TagHelperDescriptor.TagMatchingRules)].Value<JArray>();
var boundAttributes = descriptor[nameof(TagHelperDescriptor.BoundAttributes)].Value<JArray>();
var childTags = descriptor[nameof(TagHelperDescriptor.AllowedChildTags)].Value<JArray>();
var documentation = descriptor[nameof(TagHelperDescriptor.Documentation)].Value<string>();
var tagOutputHint = descriptor[nameof(TagHelperDescriptor.TagOutputHint)].Value<string>();
var diagnostics = descriptor[nameof(TagHelperDescriptor.Diagnostics)].Value<JArray>();
var metadata = descriptor[nameof(TagHelperDescriptor.Metadata)].Value<JObject>();
var builder = ITagHelperDescriptorBuilder.Create(typeName, assemblyName);
builder
.Documentation(documentation)
.TagOutputHint(tagOutputHint);
foreach (var tagMatchingRule in tagMatchingRules)
{
var rule = tagMatchingRule.Value<JObject>();
builder.TagMatchingRule(b => ReadTagMatchingRule(b, rule, serializer));
}
foreach (var boundAttribute in boundAttributes)
{
var attribute = boundAttribute.Value<JObject>();
builder.BindAttribute(b => ReadBoundAttribute(b, attribute, serializer));
}
foreach (var childTag in childTags)
{
var tagValue = childTag.Value<string>();
builder.AllowChildTag(tagValue);
}
foreach (var diagnostic in diagnostics)
{
var diagnosticReader = diagnostic.CreateReader();
var diagnosticObject = serializer.Deserialize<RazorDiagnostic>(diagnosticReader);
builder.AddDiagnostic(diagnosticObject);
}
var metadataReader = metadata.CreateReader();
var metadataValue = serializer.Deserialize<Dictionary<string, string>>(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<string>();
var attributes = rule[nameof(TagMatchingRule.Attributes)].Value<JArray>();
var parentTag = rule[nameof(TagMatchingRule.ParentTag)].Value<string>();
var tagStructure = rule[nameof(TagMatchingRule.TagStructure)].Value<int>();
var diagnostics = rule[nameof(TagMatchingRule.Diagnostics)].Value<JArray>();
builder
.RequireTagName(tagName)
.RequireParentTag(parentTag)
.RequireTagStructure((TagStructure)tagStructure);
foreach (var attribute in attributes)
{
var attibuteValue = attribute.Value<JObject>();
builder.RequireAttribute(b => ReadRequiredAttribute(b, attibuteValue, serializer));
}
foreach (var diagnostic in diagnostics)
{
var diagnosticReader = diagnostic.CreateReader();
var diagnosticObject = serializer.Deserialize<RazorDiagnostic>(diagnosticReader);
builder.AddDiagnostic(diagnosticObject);
}
}
private void ReadRequiredAttribute(RequiredAttributeDescriptorBuilder builder, JObject attribute, JsonSerializer serializer)
{
var name = attribute[nameof(RequiredAttributeDescriptor.Name)].Value<string>();
var nameComparison = attribute[nameof(RequiredAttributeDescriptor.NameComparison)].Value<int>();
var value = attribute[nameof(RequiredAttributeDescriptor.Value)].Value<string>();
var valueComparison = attribute[nameof(RequiredAttributeDescriptor.ValueComparison)].Value<int>();
var diagnostics = attribute[nameof(RequiredAttributeDescriptor.Diagnostics)].Value<JArray>();
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<RazorDiagnostic>(diagnosticReader);
builder.AddDiagnostic(diagnosticObject);
}
}
private void ReadBoundAttribute(ITagHelperBoundAttributeDescriptorBuilder builder, JObject attribute, JsonSerializer serializer)
{
var descriptorKind = attribute[nameof(BoundAttributeDescriptor.Kind)].Value<string>();
if (descriptorKind != ITagHelperBoundAttributeDescriptorBuilder.DescriptorKind)
{
throw new NotSupportedException();
}
var name = attribute[nameof(BoundAttributeDescriptor.Name)].Value<string>();
var typeName = attribute[nameof(BoundAttributeDescriptor.TypeName)].Value<string>();
var isEnum = attribute[nameof(BoundAttributeDescriptor.IsEnum)].Value<bool>();
var indexerNamePrefix = attribute[nameof(BoundAttributeDescriptor.IndexerNamePrefix)].Value<string>();
var indexerTypeName = attribute[nameof(BoundAttributeDescriptor.IndexerTypeName)].Value<string>();
var documentation = attribute[nameof(BoundAttributeDescriptor.Documentation)].Value<string>();
var diagnostics = attribute[nameof(BoundAttributeDescriptor.Diagnostics)].Value<JArray>();
var metadata = attribute[nameof(BoundAttributeDescriptor.Metadata)].Value<JObject>();
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<RazorDiagnostic>(diagnosticReader);
builder.AddDiagnostic(diagnosticObject);
}
var metadataReader = metadata.CreateReader();
var metadataValue = serializer.Deserialize<Dictionary<string, string>>(metadataReader);
foreach (var item in metadataValue)
{
builder.AddMetadata(item.Key, item.Value);
}
var propertyName = metadataValue[ITagHelperBoundAttributeDescriptorBuilder.PropertyNameKey];
builder.PropertyName(propertyName);
}
}
}

View File

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

View File

@ -174,12 +174,10 @@ namespace Microsoft.AspNetCore.Razor.Evolution
<span val=""@Hello World""></span>");
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
<cool:span val=""@Hello World""></cool:span>");
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<TagHelperIRNode>(c1);
Children(
@ -324,24 +318,23 @@ namespace Microsoft.AspNetCore.Razor.Evolution
// Arrange
var codeDocument = TestRazorCodeDocument.Create(@"@addTagHelper *, TestAssembly
<input bound='foo' />");
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<ITagHelperBoundAttributeDescriptorBuilder>[]
{
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<Action<ITagHelperBoundAttributeDescriptorBuilder>> 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;
}
}
}

View File

@ -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<TagHelperDescriptor> descriptors)
private void RunRuntimeTagHelpersTest(IEnumerable<TagHelperDescriptor> 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<TagHelperDescriptor> descriptors)
private void RunDesignTimeTagHelpersTest(IEnumerable<TagHelperDescriptor> 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);

View File

@ -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<ITagHelperBoundAttributeDescriptorBuilder>[]
{
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<Action<ITagHelperBoundAttributeDescriptorBuilder>> 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;
}
}
}

View File

@ -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<ITagHelperBoundAttributeDescriptorBuilder>[]
{
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<ITagHelperBoundAttributeDescriptorBuilder>[]
{
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<Action<ITagHelperBoundAttributeDescriptorBuilder>> 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;
}
}
}

View File

@ -9,50 +9,35 @@ namespace Microsoft.AspNetCore.Razor.Evolution.IntegrationTests
{
public class TestTagHelperDescriptors
{
internal static IEnumerable<TagHelperDescriptor> DefaultPAndInputTagHelperDescriptors { get; }
= BuildPAndInputTagHelperDescriptors(prefix: string.Empty);
internal static IEnumerable<TagHelperDescriptor> PrefixedPAndInputTagHelperDescriptors { get; }
= BuildPAndInputTagHelperDescriptors(prefix: "THS");
internal static IEnumerable<TagHelperDescriptor> 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<ITagHelperBoundAttributeDescriptorBuilder>[]
{
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<TagMatchingRuleBuilder>[]
{
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<TagMatchingRuleBuilder>[]
{
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<ITagHelperBoundAttributeDescriptorBuilder>[]
{
new TagHelperAttributeDescriptor("type", inputTypePropertyInfo),
builder => BuildBoundAttributeDescriptorFromPropertyInfo(builder, "type", inputTypePropertyInfo),
},
RequiredAttributes = new[]
ruleBuilders: new Action<TagMatchingRuleBuilder>[]
{
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<ITagHelperBoundAttributeDescriptorBuilder>[]
{
new TagHelperAttributeDescriptor("type", inputTypePropertyInfo),
builder => BuildBoundAttributeDescriptorFromPropertyInfo(builder, "type", inputTypePropertyInfo),
},
RequiredAttributes = new[]
ruleBuilders: new Action<TagMatchingRuleBuilder>[]
{
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<TagMatchingRuleBuilder>[]
{
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<TagMatchingRuleBuilder>[]
{
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<ITagHelperBoundAttributeDescriptorBuilder>[]
{
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<ITagHelperBoundAttributeDescriptorBuilder>[]
{
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<ITagHelperBoundAttributeDescriptorBuilder>[]
{
new TagHelperAttributeDescriptor
{
Name = "[item]",
PropertyName = "ListItems",
TypeName = typeof(List<string>).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<string>).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<TagMatchingRuleBuilder>[]
{
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<ITagHelperBoundAttributeDescriptorBuilder>[]
{
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<TagMatchingRuleBuilder>[]
{
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<ITagHelperBoundAttributeDescriptorBuilder>[]
{
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<TagMatchingRuleBuilder>[]
{
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<ITagHelperBoundAttributeDescriptorBuilder>[]
{
new TagHelperAttributeDescriptor
{
Name = "bound",
PropertyName = "Bound",
TypeName = typeof(string).FullName,
IsStringProperty = true
}
}
}
};
}
}
internal static IEnumerable<TagHelperDescriptor> 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<TagMatchingRuleBuilder>[]
{
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<ITagHelperBoundAttributeDescriptorBuilder>[]
{
new TagHelperAttributeDescriptor("type", inputTypePropertyInfo),
new TagHelperAttributeDescriptor("checked", inputCheckedPropertyInfo)
builder => BuildBoundAttributeDescriptorFromPropertyInfo(builder, "type", inputTypePropertyInfo),
},
RequiredAttributes = new[]
ruleBuilders: new Action<TagMatchingRuleBuilder>[]
{
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<ITagHelperBoundAttributeDescriptorBuilder>[]
{
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<TagMatchingRuleBuilder>[]
{
builder => builder
.RequireAttribute(attribute => attribute.Name("type"))
.RequireAttribute(attribute => attribute.Name("checked")),
}),
CreateTagHelperDescriptor(
tagName: "*",
typeName: "TestNamespace.CatchAllTagHelper",
assemblyName: "TestAssembly",
ruleBuilders: new Action<TagMatchingRuleBuilder>[]
{
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<ITagHelperBoundAttributeDescriptorBuilder>[]
{
new TagHelperAttributeDescriptor
{
Name = "int-prefix-grabber",
PropertyName = "IntProperty",
TypeName = typeof(int).FullName
},
new TagHelperAttributeDescriptor
{
Name = "int-dictionary",
PropertyName = "IntDictionaryProperty",
TypeName = typeof(IDictionary<string, int>).FullName
},
new TagHelperAttributeDescriptor
{
Name = "string-dictionary",
PropertyName = "StringDictionaryProperty",
TypeName = "Namespace.DictionaryWithoutParameterlessConstructor<string, string>"
},
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<string, int>).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<string, string>")
.AsDictionary("string-prefix-", typeof(string).FullName),
}),
CreateTagHelperDescriptor(
tagName: "input",
typeName: "TestNamespace.InputTagHelper2",
assemblyName: "TestAssembly",
attributes: new Action<ITagHelperBoundAttributeDescriptorBuilder>[]
{
new TagHelperAttributeDescriptor
{
Name = "int-dictionary",
PropertyName = "IntDictionaryProperty",
TypeName = typeof(int).FullName
},
new TagHelperAttributeDescriptor
{
Name = "string-dictionary",
PropertyName = "StringDictionaryProperty",
TypeName = "Namespace.DictionaryWithoutParameterlessConstructor<string, string>"
},
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<string, string>")
.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<ITagHelperBoundAttributeDescriptorBuilder>[]
{
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<TagHelperDescriptor> BuildPAndInputTagHelperDescriptors(string prefix)
internal static IEnumerable<TagHelperDescriptor> 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<ITagHelperBoundAttributeDescriptorBuilder>[]
{
builder => BuildBoundAttributeDescriptorFromPropertyInfo(builder, "age", pAgePropertyInfo),
},
ruleBuilders: new Action<TagMatchingRuleBuilder>[]
{
builder => builder.RequireTagStructure(TagStructure.NormalOrSelfClosing)
}),
CreateTagHelperDescriptor(
tagName: "input",
typeName: "TestNamespace.InputTagHelper",
assemblyName: "TestAssembly",
attributes: new Action<ITagHelperBoundAttributeDescriptorBuilder>[]
{
builder => BuildBoundAttributeDescriptorFromPropertyInfo(builder, "type", inputTypePropertyInfo),
},
ruleBuilders: new Action<TagMatchingRuleBuilder>[]
{
builder => builder.RequireTagStructure(TagStructure.WithoutEndTag)
}),
CreateTagHelperDescriptor(
tagName: "input",
typeName: "TestNamespace.InputTagHelper2",
assemblyName: "TestAssembly",
attributes: new Action<ITagHelperBoundAttributeDescriptorBuilder>[]
{
builder => BuildBoundAttributeDescriptorFromPropertyInfo(builder, "type", inputTypePropertyInfo),
builder => BuildBoundAttributeDescriptorFromPropertyInfo(builder, "checked", checkedPropertyInfo),
}),
};
}
}
private static TagHelperDescriptor CreateTagHelperDescriptor(
string tagName,
string typeName,
string assemblyName,
IEnumerable<Action<ITagHelperBoundAttributeDescriptorBuilder>> attributes = null,
IEnumerable<Action<TagMatchingRuleBuilder>> 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

View File

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

View File

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

View File

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

View File

@ -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<TagHelperAttributeDescriptor>
{
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;
}
}
}

View File

@ -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<TagHelperAttributeDesignTimeDescriptor>
{
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;
}
}
}

View File

@ -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<string>).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<string>).Namespace + "List<System.String>"))
.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<string, int>).FullName),
string.Format(errorFormat, "int-dictionary", "input", typeof(IDictionary<string, int>).Namespace + ".IDictionary<System.String, System.Int32>"),
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<string, string>).FullName),
string.Format(errorFormat, "string-dictionary", "input", typeof(IDictionary<string, string>).Namespace + ".IDictionary<System.String, System.String>"),
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<string, int>).FullName
},
new TagHelperAttributeDescriptor
{
Name = "string-dictionary",
PropertyName = "DictionaryOfStringProperty",
TypeName = typeof(IDictionary<string, string>).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<string, int>).Namespace + ".IDictionary<System.String, System.Int32>")
.AsDictionary("int-prefix-", typeof(int).FullName))
.BindAttribute(attribute =>
attribute
.Name("string-dictionary")
.PropertyName("DictionaryOfStringProperty")
.TypeName(typeof(IDictionary<string, string>).Namespace + ".IDictionary<System.String, System.String>")
.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);

View File

@ -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<TagHelperDescriptor>)availableDescriptors);
var provider = new TagHelperDescriptorProvider(null, (IEnumerable<TagHelperDescriptor>)availableDescriptors);
// Act
var resolvedDescriptors = provider.GetDescriptors(
var bindingResult = provider.GetTagHelperBinding(
tagName,
attributes: Enumerable.Empty<KeyValuePair<string, string>>(),
parentTagName: parentTagName);
// Assert
Assert.Equal((IEnumerable<TagHelperDescriptor>)expectedDescriptors, resolvedDescriptors, CaseSensitiveTagHelperDescriptorComparer.Default);
Assert.Equal((IEnumerable<TagHelperDescriptor>)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<TagHelperDescriptor>()
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<TagHelperDescriptor>()
null
},
{
"input",
new[] { kvp("prefix-") },
defaultWildcardDescriptors,
Enumerable.Empty<TagHelperDescriptor>()
null
},
{
"input",
new[] { kvp("nodashprefix") },
defaultWildcardDescriptors,
Enumerable.Empty<TagHelperDescriptor>()
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<KeyValuePair<string, string>> providedAttributes,
object availableDescriptors,
object expectedDescriptors)
{
// Arrange
var provider = new TagHelperDescriptorProvider((IEnumerable<TagHelperDescriptor>)availableDescriptors);
var provider = new TagHelperDescriptorProvider(null, (IEnumerable<TagHelperDescriptor>)availableDescriptors);
// Act
var resolvedDescriptors = provider.GetDescriptors(tagName, providedAttributes, parentTagName: "p").ToArray();
var bindingResult = provider.GetTagHelperBinding(tagName, providedAttributes, parentTagName: "p");
// Assert
Assert.Equal((IEnumerable<TagHelperDescriptor>)expectedDescriptors, resolvedDescriptors, CaseSensitiveTagHelperDescriptorComparer.Default);
Assert.Equal((IEnumerable<TagHelperDescriptor>)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<KeyValuePair<string, string>>(),
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<TagHelperRequiredAttributeDescriptor>()
{
new TagHelperRequiredAttributeDescriptor()
{
Name = "a",
NameComparison = TagHelperRequiredAttributeNameComparison.FullMatch
}
},
},
new TagHelperDescriptor
{
AssemblyName = "TestAssembly",
TagName = "form",
TypeName = "TestFormTagHelper",
RequiredAttributes = new List<TagHelperRequiredAttributeDescriptor>()
{
new TagHelperRequiredAttributeDescriptor()
{
Name = "b",
NameComparison = TagHelperRequiredAttributeNameComparison.FullMatch
}
},
},
};
var provider = new TagHelperDescriptorProvider(descriptors);
// Act
var resolvedDescriptors = provider.GetDescriptors(
tagName: "form",
attributes: new List<KeyValuePair<string, string>>()
{
new KeyValuePair<string, string>("a", "hi" ),
new KeyValuePair<string, string>("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<KeyValuePair<string, string>>(),
parentTagName: "p");
var retrievedDescriptorsSpan = provider.GetDescriptors(
tagName: "th2:span",
attributes: Enumerable.Empty<KeyValuePair<string, string>>(),
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<KeyValuePair<string, string>>(),
parentTagName: "p");
var retrievedDescriptorsSpan = provider.GetDescriptors(
var bindingResultSpan = provider.GetTagHelperBinding(
tagName: "th:span",
attributes: Enumerable.Empty<KeyValuePair<string, string>>(),
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<KeyValuePair<string, string>>(),
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<KeyValuePair<string, string>>(),
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<KeyValuePair<string, string>>(),
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<KeyValuePair<string, string>>(),
parentTagName: "p");
var spanDescriptors = provider.GetDescriptors(
var spanBinding = provider.GetTagHelperBinding(
tagName: "span",
attributes: Enumerable.Empty<KeyValuePair<string, string>>(),
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<KeyValuePair<string, string>>(),
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<KeyValuePair<string, string>>(),
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);
}
}
}

View File

@ -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<TagHelperDesignTimeDescriptor>
{
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;
}
}
}

View File

@ -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<RazorError> expectedErrors)
IEnumerable<RazorError> 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)));

View File

@ -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<TagMatchingRuleBuilder>[]
{
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<TagHelperRequiredAttributeDescriptor>()
{
new TagHelperRequiredAttributeDescriptor()
{
Name = "a",
NameComparison = TagHelperRequiredAttributeNameComparison.FullMatch
}
},
},
new TagHelperDescriptor
{
AssemblyName = "TestAssembly",
TagName = "form",
TypeName = "TestFormTagHelper",
RequiredAttributes = new List<TagHelperRequiredAttributeDescriptor>()
{
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<TagHelperBlock>(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<TagMatchingRuleBuilder>[]
{
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<TagHelperRequiredAttributeDescriptor>()
{
new TagHelperRequiredAttributeDescriptor()
{
Name = "a",
NameComparison = TagHelperRequiredAttributeNameComparison.FullMatch
}
},
},
new TagHelperDescriptor
{
AssemblyName = "TestAssembly",
TagName = "form",
TypeName = "TestFormTagHelper",
RequiredAttributes = new List<TagHelperRequiredAttributeDescriptor>()
{
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<TagHelperBlock>(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<ITagHelperFeature>(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<ITagHelperFeature>(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<TagHelperDescriptor>, // tagHelpers
IEnumerable<TagHelperDirectiveDescriptor>, // directiveDescriptors
IEnumerable<TagHelperDescriptor>> // expectedDescriptors
// directiveDescriptors, expected prefix
return new TheoryData<IEnumerable<TagHelperDirectiveDescriptor>, 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<TagHelperDescriptor>)expectedDescriptors;
var document = RazorCodeDocument.Create(new DefaultRazorSourceDocument("Test content", encoding: Encoding.UTF8, fileName: "TestFile"));
// Act
var results = pass.ProcessDirectives(
((IEnumerable<TagHelperDirectiveDescriptor>)directiveDescriptors).ToArray(),
((IEnumerable<TagHelperDescriptor>)tagHelpers).ToArray(),
errorSink);
var prefix = pass.ProcessTagHelperPrefix(((IEnumerable<TagHelperDirectiveDescriptor>)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<string>
{
$"{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<Action<ITagHelperBoundAttributeDescriptorBuilder>> attributes = null,
IEnumerable<Action<TagMatchingRuleBuilder>> 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<TagHelperDescriptor> Resolve(IList<RazorDiagnostic> errors)
{
errors.Add(_error);
return new[] { new TagHelperDescriptor() { AssemblyName = "TestAssembly" } };
return new[] { CreateTagHelperDescriptor(
tagName: _tagName,
typeName: null,
assemblyName: "TestAssembly") };
}
}
}

View File

@ -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<TagHelperAttributeDescriptor>
{
new TagHelperAttributeDescriptor
{
Name = "test-attribute",
PropertyName = "TestAttribute",
TypeName = "string"
}
},
RequiredAttributes = new List<TagHelperRequiredAttributeDescriptor>
{
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<TagHelperDescriptor>(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<TagHelperDescriptor>(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<TagHelperDescriptor>(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<TagHelperDescriptor>(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"]);
}
}
}

View File

@ -9,144 +9,127 @@ namespace Microsoft.AspNetCore.Razor.Evolution
get
{
// requiredAttributeDescriptor, attributeName, attributeValue, expectedResult
return new TheoryData<TagHelperRequiredAttributeDescriptor, string, string, bool>
return new TheoryData<RequiredAttributeDescriptor, string, string, bool>
{
{
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);

View File

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

View File

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

View File

@ -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<TagHelperAttributeDescriptor>
{
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;
}
}
}

View File

@ -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<TagHelperAttributeDesignTimeDescriptor>
{
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;
}
}
}

View File

@ -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<TagHelperDesignTimeDescriptor>
{
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;
}
}
}

View File

@ -424,7 +424,7 @@ namespace Microsoft.CodeAnalysis.Razor.Workspaces.Test
[HtmlTargetElement("a")]
[HtmlTargetElement("p")]
[OutputElementHint("div")]
public class MulitpleDescriptorTagHelperWithOutputElementHint : TagHelper
public class MultipleDescriptorTagHelperWithOutputElementHint : TagHelper
{
}

View File

@ -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

View File

@ -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

View File

@ -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<TagHelperAttributeDescriptor>
{
new TagHelperAttributeDescriptor
{
Name = "foo",
PropertyName = "foo",
TypeName = typeof(string).FullName
},
new TagHelperAttributeDescriptor
{
Name = "bar",
PropertyName = "bar",
TypeName = typeof(string).FullName
}
},
RequiredAttributes = new List<TagHelperRequiredAttributeDescriptor>
{
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<TagHelperAttributeDescriptor>
{
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<TagHelperRequiredAttributeDescriptor>
{
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<TagHelperAttributeDescriptor>
{
new TagHelperAttributeDescriptor
{
Name = "foo",
PropertyName = "Foo",
TypeName = "System.Collections.Generic.List<System.String>"
},
new TagHelperAttributeDescriptor
{
Name = "bar",
PropertyName = "Bar",
TypeName = "System.Collections.Generic.Dictionary<System.String, System.Int32>"
},
new TagHelperAttributeDescriptor
{
Name = "bar-",
PropertyName = "Bar",
TypeName = typeof(int).FullName,
IsIndexer = true
}
},
RequiredAttributes = new List<TagHelperRequiredAttributeDescriptor>
{
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<System.String>"))
.BindAttribute(attribute =>
attribute
.Name("bar")
.PropertyName("Bar")
.TypeName("System.Collections.Generic.Dictionary<System.String, System.Int32>")
.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);
}
}

View File

@ -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<ITagHelperBoundAttributeDescriptorBuilder>[]
{
builder => builder
.Name("test-attribute")
.PropertyName("TestAttribute")
.TypeName("string"),
},
ruleBuilders: new Action<TagMatchingRuleBuilder>[]
{
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<TagHelperDescriptor>(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<ITagHelperBoundAttributeDescriptorBuilder>[]
{
builder => builder
.Name("test-attribute")
.PropertyName("TestAttribute")
.TypeName("string"),
},
ruleBuilders: new Action<TagMatchingRuleBuilder>[]
{
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<TagHelperDescriptor>(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<ITagHelperBoundAttributeDescriptorBuilder>[]
{
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<TagMatchingRuleBuilder>[]
{
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<TagHelperDescriptor>(serializedDescriptor, TagHelperDescriptorJsonConverter.Instance, RazorDiagnosticJsonConverter.Instance);
// Assert
Assert.Equal(expectedDescriptor, descriptor, TagHelperDescriptorComparer.Default);
}
private static TagHelperDescriptor CreateTagHelperDescriptor(
string tagName,
string typeName,
string assemblyName,
IEnumerable<Action<ITagHelperBoundAttributeDescriptorBuilder>> attributes = null,
IEnumerable<Action<TagMatchingRuleBuilder>> ruleBuilders = null,
Action<ITagHelperDescriptorBuilder> 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;
}
}
}

View File

@ -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