Directive attributes part 1: Support parameters in bound attributes (dotnet/aspnetcore-tooling#597)

* Directive attributes part 1: Support parameters in bound attributes

* update

* Fix test

* feedback

* Updated design

* Do case sensitive comparison

* more

* Bug fix
\n\nCommit migrated from 9bbf240948
This commit is contained in:
Ajay Bhargav Baaskaran 2019-05-21 13:07:23 -07:00 committed by GitHub
parent 13397dd4d6
commit 5bad5de7ee
60 changed files with 1089 additions and 262 deletions

View File

@ -47,6 +47,8 @@ namespace Microsoft.AspNetCore.Razor.Language
public IReadOnlyDictionary<string, string> Metadata { get; protected set; } public IReadOnlyDictionary<string, string> Metadata { get; protected set; }
public virtual IReadOnlyList<BoundAttributeParameterDescriptor> BoundAttributeParameters { get; protected set; }
public bool HasErrors public bool HasErrors
{ {
get get

View File

@ -1,6 +1,7 @@
// Copyright (c) .NET Foundation. All rights reserved. // 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. // 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.Generic;
namespace Microsoft.AspNetCore.Razor.Language namespace Microsoft.AspNetCore.Razor.Language
@ -26,5 +27,11 @@ namespace Microsoft.AspNetCore.Razor.Language
public abstract IDictionary<string, string> Metadata { get; } public abstract IDictionary<string, string> Metadata { get; }
public abstract RazorDiagnosticCollection Diagnostics { get; } public abstract RazorDiagnosticCollection Diagnostics { get; }
public virtual IReadOnlyList<BoundAttributeParameterDescriptorBuilder> BoundAttributeParameters { get; }
public virtual void BindAttributeParameter(Action<BoundAttributeParameterDescriptorBuilder> configure)
{
}
} }
} }

View File

@ -29,9 +29,9 @@ namespace Microsoft.AspNetCore.Razor.Language
throw new ArgumentNullException(nameof(builder)); throw new ArgumentNullException(nameof(builder));
} }
if (builder.Metadata.ContainsKey(TagHelperMetadata.Common.PropertyName)) if (builder.Metadata.TryGetValue(TagHelperMetadata.Common.PropertyName, out var value))
{ {
return builder.Metadata[TagHelperMetadata.Common.PropertyName]; return value;
} }
return null; return null;
@ -51,5 +51,35 @@ namespace Microsoft.AspNetCore.Razor.Language
builder.IndexerAttributeNamePrefix = attributeNamePrefix; builder.IndexerAttributeNamePrefix = attributeNamePrefix;
builder.IndexerValueTypeName = valueTypeName; builder.IndexerValueTypeName = valueTypeName;
} }
public static void SetPropertyName(this BoundAttributeParameterDescriptorBuilder builder, string propertyName)
{
if (builder == null)
{
throw new ArgumentNullException(nameof(builder));
}
if (propertyName == null)
{
throw new ArgumentNullException(nameof(propertyName));
}
builder.Metadata[TagHelperMetadata.Common.PropertyName] = propertyName;
}
public static string GetPropertyName(this BoundAttributeParameterDescriptorBuilder builder)
{
if (builder == null)
{
throw new ArgumentNullException(nameof(builder));
}
if (builder.Metadata.TryGetValue(TagHelperMetadata.Common.PropertyName, out var value))
{
return value;
}
return null;
}
} }
} }

View File

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
using System.Linq;
namespace Microsoft.AspNetCore.Razor.Language namespace Microsoft.AspNetCore.Razor.Language
{ {
@ -49,5 +50,26 @@ namespace Microsoft.AspNetCore.Razor.Language
var isIndexerNameMatch = TagHelperMatchingConventions.SatisfiesBoundAttributeIndexer(name, attribute); var isIndexerNameMatch = TagHelperMatchingConventions.SatisfiesBoundAttributeIndexer(name, attribute);
return isIndexerNameMatch && attribute.IsIndexerBooleanProperty; return isIndexerNameMatch && attribute.IsIndexerBooleanProperty;
} }
public static bool IsDefaultKind(this BoundAttributeParameterDescriptor parameter)
{
if (parameter == null)
{
throw new ArgumentNullException(nameof(parameter));
}
return string.Equals(parameter.Kind, TagHelperConventions.DefaultKind, StringComparison.Ordinal);
}
public static string GetPropertyName(this BoundAttributeParameterDescriptor parameter)
{
if (parameter == null)
{
throw new ArgumentNullException(nameof(parameter));
}
parameter.Metadata.TryGetValue(TagHelperMetadata.Common.PropertyName, out var propertyName);
return propertyName;
}
} }
} }

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.Collections.Generic;
using System.Linq;
namespace Microsoft.AspNetCore.Razor.Language
{
public abstract class BoundAttributeParameterDescriptor : IEquatable<BoundAttributeParameterDescriptor>
{
protected BoundAttributeParameterDescriptor(string kind)
{
Kind = kind;
}
public string Kind { get; }
public bool IsEnum { get; protected set; }
public bool IsStringProperty { get; protected set; }
public bool IsBooleanProperty { get; protected set; }
public string Name { get; protected set; }
public string TypeName { 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 HasErrors
{
get
{
var errors = Diagnostics.Any(diagnostic => diagnostic.Severity == RazorDiagnosticSeverity.Error);
return errors;
}
}
public override string ToString()
{
return DisplayName ?? base.ToString();
}
public bool Equals(BoundAttributeParameterDescriptor other)
{
return BoundAttributeParameterDescriptorComparer.Default.Equals(this, other);
}
public override bool Equals(object obj)
{
return Equals(obj as BoundAttributeParameterDescriptor);
}
public override int GetHashCode()
{
return BoundAttributeParameterDescriptorComparer.Default.GetHashCode(this);
}
}
}

View File

@ -0,0 +1,24 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
namespace Microsoft.AspNetCore.Razor.Language
{
public abstract class BoundAttributeParameterDescriptorBuilder
{
public abstract string Name { get; set; }
public abstract string TypeName { get; set; }
public abstract bool IsEnum { get; set; }
public abstract string Documentation { get; set; }
public abstract string DisplayName { get; set; }
public abstract IDictionary<string, string> Metadata { get; }
public abstract RazorDiagnosticCollection Diagnostics { get; }
}
}

View File

@ -0,0 +1,79 @@
// 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.Language
{
internal class BoundAttributeParameterDescriptorComparer : IEqualityComparer<BoundAttributeParameterDescriptor>
{
/// <summary>
/// A default instance of the <see cref="BoundAttributeParameterDescriptorComparer"/>.
/// </summary>
public static readonly BoundAttributeParameterDescriptorComparer Default = new BoundAttributeParameterDescriptorComparer();
/// <summary>
/// A default instance of the <see cref="BoundAttributeParameterDescriptorComparer"/> that does case-sensitive comparison.
/// </summary>
internal static readonly BoundAttributeParameterDescriptorComparer CaseSensitive =
new BoundAttributeParameterDescriptorComparer(caseSensitive: true);
private readonly StringComparer _stringComparer;
private readonly StringComparison _stringComparison;
private BoundAttributeParameterDescriptorComparer(bool caseSensitive = false)
{
if (caseSensitive)
{
_stringComparer = StringComparer.Ordinal;
_stringComparison = StringComparison.Ordinal;
}
else
{
_stringComparer = StringComparer.OrdinalIgnoreCase;
_stringComparison = StringComparison.OrdinalIgnoreCase;
}
}
public virtual bool Equals(BoundAttributeParameterDescriptor descriptorX, BoundAttributeParameterDescriptor descriptorY)
{
if (object.ReferenceEquals(descriptorX, descriptorY))
{
return true;
}
if (descriptorX == null ^ descriptorY == null)
{
return false;
}
return
string.Equals(descriptorX.Kind, descriptorY.Kind, StringComparison.Ordinal) &&
descriptorX.IsEnum == descriptorY.IsEnum &&
string.Equals(descriptorX.Name, descriptorY.Name, _stringComparison) &&
string.Equals(descriptorX.TypeName, descriptorY.TypeName, StringComparison.Ordinal) &&
string.Equals(descriptorX.Documentation, descriptorY.Documentation, StringComparison.Ordinal) &&
string.Equals(descriptorX.DisplayName, descriptorY.DisplayName, StringComparison.Ordinal) &&
Enumerable.SequenceEqual(
descriptorX.Metadata.OrderBy(propertyX => propertyX.Key, StringComparer.Ordinal),
descriptorY.Metadata.OrderBy(propertyY => propertyY.Key, StringComparer.Ordinal));
}
public virtual int GetHashCode(BoundAttributeParameterDescriptor descriptor)
{
if (descriptor == null)
{
throw new ArgumentNullException(nameof(descriptor));
}
var hash = HashCodeCombiner.Start();
hash.Add(descriptor.Kind);
hash.Add(descriptor.Name, _stringComparer);
return hash.CombinedHash;
}
}
}

View File

@ -123,14 +123,20 @@
<data name="BindTagHelper_Element_Documentation" xml:space="preserve"> <data name="BindTagHelper_Element_Documentation" xml:space="preserve">
<value>Binds the provided expression to the '{0}' attribute and a change event delegate to the '{1}' attribute.</value> <value>Binds the provided expression to the '{0}' attribute and a change event delegate to the '{1}' attribute.</value>
</data> </data>
<data name="BindTagHelper_Element_Event_Documentation" xml:space="preserve">
<value>Specifies the event handler name to attach for change notifications for the value provided by the '{0}' attribute.</value>
</data>
<data name="BindTagHelper_Element_Format_Documentation" xml:space="preserve"> <data name="BindTagHelper_Element_Format_Documentation" xml:space="preserve">
<value>Specifies a format to convert the value specified by the '{0}' attribute. The format string can currently only be used with expressions of type &lt;code&gt;DateTime&lt;/code&gt;.</value> <value>Specifies a format to convert the value specified by the '{0}' attribute. The format string can currently only be used with expressions of type &lt;code&gt;DateTime&lt;/code&gt;.</value>
</data> </data>
<data name="BindTagHelper_Fallback_Documentation" xml:space="preserve"> <data name="BindTagHelper_Fallback_Documentation" xml:space="preserve">
<value>Binds the provided expression to an attribute and a change event, based on the naming of the bind attribute. For example: &lt;code&gt;bind-value-onchange="..."&lt;/code&gt; will assign the current value of the expression to the 'value' attribute, and assign a delegate that attempts to set the value to the 'onchange' attribute.</value> <value>Binds the provided expression to an attribute and a change event, based on the naming of the bind attribute. For example: &lt;code&gt;bind-value="..."&lt;/code&gt; and &lt;code&gt;bind-value:event="onchange"&lt;/code&gt; will assign the current value of the expression to the 'value' attribute, and assign a delegate that attempts to set the value to the 'onchange' attribute.</value>
</data>
<data name="BindTagHelper_Fallback_Event_Documentation" xml:space="preserve">
<value>Specifies the event handler name to attach for change notifications for the value provided by the '{0}' attribute.</value>
</data> </data>
<data name="BindTagHelper_Fallback_Format_Documentation" xml:space="preserve"> <data name="BindTagHelper_Fallback_Format_Documentation" xml:space="preserve">
<value>Specifies a format to convert the value specified by the corresponding bind attribute. For example: &lt;code&gt;format-value="..."&lt;/code&gt; will apply a format string to the value specified in &lt;code&gt;bind-value-...&lt;/code&gt;. The format string can currently only be used with expressions of type &lt;code&gt;DateTime&lt;/code&gt;.</value> <value>Specifies a format to convert the value specified by the corresponding bind attribute. For example: &lt;code&gt;bind-value:format="..."&lt;/code&gt; will apply a format string to the value specified in &lt;code&gt;bind-value="..."&lt;/code&gt;. The format string can currently only be used with expressions of type &lt;code&gt;DateTime&lt;/code&gt;.</value>
</data> </data>
<data name="ChildContentParameterName_Documentation" xml:space="preserve"> <data name="ChildContentParameterName_Documentation" xml:space="preserve">
<value>Specifies the parameter name for the '{0}' child content expression.</value> <value>Specifies the parameter name for the '{0}' child content expression.</value>

View File

@ -30,18 +30,25 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
// For each bind *usage* we need to rewrite the tag helper node to map to basic constructs. // For each bind *usage* we need to rewrite the tag helper node to map to basic constructs.
var references = documentNode.FindDescendantReferences<TagHelperPropertyIntermediateNode>(); var references = documentNode.FindDescendantReferences<TagHelperPropertyIntermediateNode>();
var parameterReferences = documentNode.FindDescendantReferences<TagHelperAttributeParameterIntermediateNode>();
var parents = new HashSet<IntermediateNode>(); var parents = new HashSet<IntermediateNode>();
for (var i = 0; i < references.Count; i++) for (var i = 0; i < references.Count; i++)
{ {
parents.Add(references[i].Parent); parents.Add(references[i].Parent);
} }
for (var i = 0; i < parameterReferences.Count; i++)
{
parents.Add(parameterReferences[i].Parent);
}
foreach (var parent in parents) foreach (var parent in parents)
{ {
ProcessDuplicates(parent); ProcessDuplicates(parent);
} }
// First, collect all the non-parameterized bind or bind-* attributes.
var bindEntries = new Dictionary<string, BindEntry>();
for (var i = 0; i < references.Count; i++) for (var i = 0; i < references.Count; i++)
{ {
var reference = references[i]; var reference = references[i];
@ -55,14 +62,60 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
if (node.TagHelper.IsBindTagHelper() && node.AttributeName.StartsWith("bind")) if (node.TagHelper.IsBindTagHelper() && node.AttributeName.StartsWith("bind"))
{ {
// Workaround for https://github.com/aspnet/Blazor/issues/703 bindEntries[node.AttributeName] = new BindEntry(reference);
var rewritten = RewriteUsage(reference.Parent, node); }
reference.Remove(); }
for (var j = 0; j < rewritten.Length; j++) // Now collect all the parameterized attributes and store them along with their corresponding bind or bind-* attributes.
for (var i = 0; i < parameterReferences.Count; i++)
{
var parameterReference = parameterReferences[i];
var node = (TagHelperAttributeParameterIntermediateNode)parameterReference.Node;
if (!parameterReference.Parent.Children.Contains(node))
{
// This node was removed as a duplicate, skip it.
continue;
}
if (node.TagHelper.IsBindTagHelper() && node.AttributeName.StartsWith("bind"))
{
if (!bindEntries.TryGetValue(node.AttributeNameWithoutParameter, out var entry))
{ {
reference.Parent.Children.Add(rewritten[j]); // There is no corresponding bind node. Add a diagnostic and move on.
parameterReference.Parent.Diagnostics.Add(ComponentDiagnosticFactory.CreateBindAttributeParameter_MissingBind(
node.Source,
node.AttributeName));
} }
else if (node.BoundAttributeParameter.Name == "event")
{
entry.BindEventNode = node;
}
else if (node.BoundAttributeParameter.Name == "format")
{
entry.BindFormatNode = node;
}
else
{
// Unsupported bind attribute parameter. This can only happen if bound attribute descriptor
// is configured to expect a parameter other than 'event' and 'format'.
}
// We've extracted what we need from the parameterized bind node. Remove it.
parameterReference.Remove();
}
}
// We now have all the info we need to rewrite the tag helper.
foreach (var entry in bindEntries)
{
var reference = entry.Value.BindNodeReference;
var rewritten = RewriteUsage(reference.Parent, entry.Value);
reference.Remove();
for (var j = 0; j < rewritten.Length; j++)
{
reference.Parent.Children.Add(rewritten[j]);
} }
} }
} }
@ -78,18 +131,42 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
{ {
// For each usage of the general 'fallback' bind tag helper, it could duplicate // For each usage of the general 'fallback' bind tag helper, it could duplicate
// the usage of a more specific one. Look for duplicates and remove the fallback. // the usage of a more specific one. Look for duplicates and remove the fallback.
var attribute = node.Children[i] as TagHelperPropertyIntermediateNode; TagHelperDescriptor tagHelper = null;
string attributeName = null;
var attribute = node.Children[i];
if (attribute is TagHelperPropertyIntermediateNode propertyAttribute)
{
attributeName = propertyAttribute.AttributeName;
tagHelper = propertyAttribute.TagHelper;
}
else if (attribute is TagHelperAttributeParameterIntermediateNode parameterAttribute)
{
attributeName = parameterAttribute.AttributeName;
tagHelper = parameterAttribute.TagHelper;
}
if (attribute != null && if (attribute != null &&
attribute.TagHelper != null && tagHelper != null &&
attribute.TagHelper.IsFallbackBindTagHelper()) tagHelper.IsFallbackBindTagHelper())
{ {
for (var j = 0; j < node.Children.Count; j++) for (var j = 0; j < node.Children.Count; j++)
{ {
var duplicate = node.Children[j] as TagHelperPropertyIntermediateNode; TagHelperDescriptor duplicateTagHelper = null;
string duplicateAttributeName = null;
var duplicate = node.Children[j];
if (duplicate is TagHelperPropertyIntermediateNode duplicatePropertyAttribute)
{
duplicateAttributeName = duplicatePropertyAttribute.AttributeName;
duplicateTagHelper = duplicatePropertyAttribute.TagHelper;
}
else if (duplicate is TagHelperAttributeParameterIntermediateNode duplicateParameterAttribute)
{
duplicateAttributeName = duplicateParameterAttribute.AttributeName;
duplicateTagHelper = duplicateParameterAttribute.TagHelper;
}
if (duplicate != null && if (duplicate != null &&
duplicate.TagHelper != null && duplicateTagHelper != null &&
duplicate.TagHelper.IsBindTagHelper() && duplicateTagHelper.IsBindTagHelper() &&
duplicate.AttributeName == attribute.AttributeName && duplicateAttributeName == attributeName &&
!object.ReferenceEquals(attribute, duplicate)) !object.ReferenceEquals(attribute, duplicate))
{ {
// Found a duplicate - remove the 'fallback' in favor of the // Found a duplicate - remove the 'fallback' in favor of the
@ -104,16 +181,28 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
// This is a workaround for a limitation where you can't write a tag helper that binds only // This is a workaround for a limitation where you can't write a tag helper that binds only
// when a specific attribute is **not** present. // when a specific attribute is **not** present.
if (attribute != null && if (attribute != null &&
attribute.TagHelper != null && tagHelper != null &&
attribute.TagHelper.IsInputElementFallbackBindTagHelper()) tagHelper.IsInputElementFallbackBindTagHelper())
{ {
for (var j = 0; j < node.Children.Count; j++) for (var j = 0; j < node.Children.Count; j++)
{ {
var duplicate = node.Children[j] as TagHelperPropertyIntermediateNode; TagHelperDescriptor duplicateTagHelper = null;
string duplicateAttributeName = null;
var duplicate = node.Children[j];
if (duplicate is TagHelperPropertyIntermediateNode duplicatePropertyAttribute)
{
duplicateAttributeName = duplicatePropertyAttribute.AttributeName;
duplicateTagHelper = duplicatePropertyAttribute.TagHelper;
}
else if (duplicate is TagHelperAttributeParameterIntermediateNode duplicateParameterAttribute)
{
duplicateAttributeName = duplicateParameterAttribute.AttributeName;
duplicateTagHelper = duplicateParameterAttribute.TagHelper;
}
if (duplicate != null && if (duplicate != null &&
duplicate.TagHelper != null && duplicateTagHelper != null &&
duplicate.TagHelper.IsInputElementBindTagHelper() && duplicateTagHelper.IsInputElementBindTagHelper() &&
duplicate.AttributeName == attribute.AttributeName && duplicateAttributeName == attributeName &&
!object.ReferenceEquals(attribute, duplicate)) !object.ReferenceEquals(attribute, duplicate))
{ {
// Found a duplicate - remove the 'fallback' input tag helper in favor of the // Found a duplicate - remove the 'fallback' input tag helper in favor of the
@ -144,7 +233,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
} }
} }
private IntermediateNode[] RewriteUsage(IntermediateNode parent, TagHelperPropertyIntermediateNode node) private IntermediateNode[] RewriteUsage(IntermediateNode parent, BindEntry bindEntry)
{ {
// Bind works similarly to a macro, it always expands to code that the user could have written. // Bind works similarly to a macro, it always expands to code that the user could have written.
// //
@ -168,10 +257,10 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
// We also assume that the element will be treated as a component for now because // We also assume that the element will be treated as a component for now because
// multiple passes handle 'special' tag helpers. We have another pass that translates // multiple passes handle 'special' tag helpers. We have another pass that translates
// a tag helper node back into 'regular' element when it doesn't have an associated component // a tag helper node back into 'regular' element when it doesn't have an associated component
var node = bindEntry.BindNode;
if (!TryComputeAttributeNames( if (!TryComputeAttributeNames(
parent, parent,
node, bindEntry,
node.AttributeName,
out var valueAttributeName, out var valueAttributeName,
out var changeAttributeName, out var changeAttributeName,
out var expressionAttributeName, out var expressionAttributeName,
@ -198,16 +287,20 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
// Look for a matching format node. If we find one then we need to pass the format into the // Look for a matching format node. If we find one then we need to pass the format into the
// two nodes we generate. // two nodes we generate.
IntermediateToken format = null; IntermediateToken format = null;
if (bindEntry.BindFormatNode != null)
{
format = GetAttributeContent(bindEntry.BindFormatNode);
}
if (TryGetFormatNode( if (TryGetFormatNode(
parent, parent,
node, node,
valueAttributeName, valueAttributeName,
out var formatNode)) out var formatNode))
{ {
// Don't write the format out as its own attribute, just capture it as a string // If there is a format- attribute present, add a warning to say that it's unsupported.
// or expression.
parent.Children.Remove(formatNode); parent.Children.Remove(formatNode);
format = GetAttributeContent(formatNode); parent.Diagnostics.Add(ComponentDiagnosticFactory.CreateBindAttribute_FormatNode_Unsupported(formatNode.Source));
} }
var valueExpressionTokens = new List<IntermediateToken>(); var valueExpressionTokens = new List<IntermediateToken>();
@ -336,10 +429,11 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
} }
private bool TryParseBindAttribute( private bool TryParseBindAttribute(
string attributeName, BindEntry bindEntry,
out string valueAttributeName, out string valueAttributeName,
out string changeAttributeName) out string changeAttributeName)
{ {
var attributeName = bindEntry.BindNode.AttributeName;
valueAttributeName = null; valueAttributeName = null;
changeAttributeName = null; changeAttributeName = null;
@ -348,6 +442,11 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
return false; return false;
} }
if (bindEntry.BindEventNode != null)
{
changeAttributeName = GetAttributeContent(bindEntry.BindEventNode)?.Content?.Trim('"');
}
if (attributeName == "bind") if (attributeName == "bind")
{ {
return true; return true;
@ -369,8 +468,9 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
return true; return true;
case 3: case 3:
changeAttributeName = segments[2];
valueAttributeName = segments[1]; valueAttributeName = segments[1];
changeAttributeName = segments[2];
bindEntry.BindNode.Diagnostics.Add(ComponentDiagnosticFactory.CreateBindAttribute_UnsupportedFormat(bindEntry.BindNode.Source));
return true; return true;
default: default:
@ -381,8 +481,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
// Attempts to compute the attribute names that should be used for an instance of 'bind'. // Attempts to compute the attribute names that should be used for an instance of 'bind'.
private bool TryComputeAttributeNames( private bool TryComputeAttributeNames(
IntermediateNode parent, IntermediateNode parent,
TagHelperPropertyIntermediateNode node, BindEntry bindEntry,
string attributeName,
out string valueAttributeName, out string valueAttributeName,
out string changeAttributeName, out string changeAttributeName,
out string expressionAttributeName, out string expressionAttributeName,
@ -397,7 +496,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
// Even though some of our 'bind' tag helpers specify the attribute names, they // Even though some of our 'bind' tag helpers specify the attribute names, they
// should still satisfy one of the valid syntaxes. // should still satisfy one of the valid syntaxes.
if (!TryParseBindAttribute(attributeName, out valueAttributeName, out changeAttributeName)) if (!TryParseBindAttribute(bindEntry, out valueAttributeName, out changeAttributeName))
{ {
return false; return false;
} }
@ -408,6 +507,8 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
// generated to match a specific tag and has metadata that identify the attributes. // generated to match a specific tag and has metadata that identify the attributes.
// //
// We expect 1 bind tag helper per-node. // We expect 1 bind tag helper per-node.
var node = bindEntry.BindNode;
var attributeName = node.AttributeName;
valueAttributeName = node.TagHelper.GetValueAttributeName() ?? valueAttributeName; valueAttributeName = node.TagHelper.GetValueAttributeName() ?? valueAttributeName;
changeAttributeName = node.TagHelper.GetChangeAttributeName() ?? changeAttributeName; changeAttributeName = node.TagHelper.GetChangeAttributeName() ?? changeAttributeName;
expressionAttributeName = node.TagHelper.GetExpressionAttributeName() ?? expressionAttributeName; expressionAttributeName = node.TagHelper.GetExpressionAttributeName() ?? expressionAttributeName;
@ -630,7 +731,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
}); });
} }
private static IntermediateToken GetAttributeContent(TagHelperPropertyIntermediateNode node) private static IntermediateToken GetAttributeContent(IntermediateNode node)
{ {
var template = node.FindDescendantNodes<TemplateIntermediateNode>().FirstOrDefault(); var template = node.FindDescendantNodes<TemplateIntermediateNode>().FirstOrDefault();
if (template != null) if (template != null)
@ -674,5 +775,22 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
}; };
} }
} }
private class BindEntry
{
public BindEntry(IntermediateNodeReference bindNodeReference)
{
BindNodeReference = bindNodeReference;
BindNode = (TagHelperPropertyIntermediateNode)bindNodeReference.Node;
}
public IntermediateNodeReference BindNodeReference { get; }
public TagHelperPropertyIntermediateNode BindNode { get; }
public TagHelperAttributeParameterIntermediateNode BindEventNode { get; set; }
public TagHelperAttributeParameterIntermediateNode BindFormatNode { get; set; }
}
} }
} }

View File

@ -166,7 +166,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
new RazorDiagnosticDescriptor( new RazorDiagnosticDescriptor(
$"{DiagnosticPrefix}9991", $"{DiagnosticPrefix}9991",
() => "The attribute names could not be inferred from bind attribute '{0}'. Bind attributes should be of the form" + () => "The attribute names could not be inferred from bind attribute '{0}'. Bind attributes should be of the form" +
"'bind', 'bind-value' or 'bind-value-change'", "'bind' or 'bind-value' along with their corresponding optional parameters like 'bind-value:event', 'bind:format' etc.",
RazorDiagnosticSeverity.Error); RazorDiagnosticSeverity.Error);
public static RazorDiagnostic CreateBindAttribute_InvalidSyntax(SourceSpan? source, string attribute) public static RazorDiagnostic CreateBindAttribute_InvalidSyntax(SourceSpan? source, string attribute)
@ -332,5 +332,48 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
{ {
return RazorDiagnostic.Create(UnsupportedComponentImportContent, source ?? SourceSpan.Undefined); return RazorDiagnostic.Create(UnsupportedComponentImportContent, source ?? SourceSpan.Undefined);
} }
public static readonly RazorDiagnosticDescriptor BindAttributeParameter_MissingBind =
new RazorDiagnosticDescriptor(
$"{DiagnosticPrefix}10004",
() => "Could not find the non-parameterized bind attribute that corresponds to the attribute '{0}'.",
RazorDiagnosticSeverity.Error);
public static RazorDiagnostic CreateBindAttributeParameter_MissingBind(SourceSpan? source, string attribute)
{
var diagnostic = RazorDiagnostic.Create(
BindAttributeParameter_MissingBind,
source ?? SourceSpan.Undefined,
attribute);
return diagnostic;
}
public static readonly RazorDiagnosticDescriptor BindAttribute_UnsupportedFormat =
new RazorDiagnosticDescriptor(
$"{DiagnosticPrefix}10005",
() => "Specifying event handlers in bind attributes are no longer supported. Specify it using the bind:event=... attribute instead.",
RazorDiagnosticSeverity.Warning);
public static RazorDiagnostic CreateBindAttribute_UnsupportedFormat(SourceSpan? source)
{
var diagnostic = RazorDiagnostic.Create(
BindAttribute_UnsupportedFormat,
source ?? SourceSpan.Undefined);
return diagnostic;
}
public static readonly RazorDiagnosticDescriptor BindAttribute_FormatNode_Unsupported =
new RazorDiagnosticDescriptor(
$"{DiagnosticPrefix}10005",
() => "Specifying format using 'format-...' attributes are no longer supported. Specify it using the 'bind-...:format=...' attribute instead.",
RazorDiagnosticSeverity.Warning);
public static RazorDiagnostic CreateBindAttribute_FormatNode_Unsupported(SourceSpan? source)
{
var diagnostic = RazorDiagnostic.Create(
BindAttribute_FormatNode_Unsupported,
source ?? SourceSpan.Undefined);
return diagnostic;
}
} }
} }

View File

@ -17,6 +17,7 @@ namespace Microsoft.AspNetCore.Razor.Language
string indexerTypeName, string indexerTypeName,
string documentation, string documentation,
string displayName, string displayName,
BoundAttributeParameterDescriptor[] parameterDescriptors,
Dictionary<string, string> metadata, Dictionary<string, string> metadata,
RazorDiagnostic[] diagnostics) RazorDiagnostic[] diagnostics)
: base(kind) : base(kind)
@ -29,6 +30,7 @@ namespace Microsoft.AspNetCore.Razor.Language
IndexerTypeName = indexerTypeName; IndexerTypeName = indexerTypeName;
Documentation = documentation; Documentation = documentation;
DisplayName = displayName; DisplayName = displayName;
BoundAttributeParameters = parameterDescriptors;
Metadata = metadata; Metadata = metadata;
Diagnostics = diagnostics; Diagnostics = diagnostics;

View File

@ -31,6 +31,7 @@ namespace Microsoft.AspNetCore.Razor.Language
private readonly DefaultTagHelperDescriptorBuilder _parent; private readonly DefaultTagHelperDescriptorBuilder _parent;
private readonly string _kind; private readonly string _kind;
private readonly Dictionary<string, string> _metadata; private readonly Dictionary<string, string> _metadata;
private List<DefaultBoundAttributeParameterDescriptorBuilder> _attributeParameterBuilders;
private RazorDiagnosticCollection _diagnostics; private RazorDiagnosticCollection _diagnostics;
@ -73,6 +74,20 @@ namespace Microsoft.AspNetCore.Razor.Language
} }
} }
public override void BindAttributeParameter(Action<BoundAttributeParameterDescriptorBuilder> configure)
{
if (configure == null)
{
throw new ArgumentNullException(nameof(configure));
}
EnsureAttributeParameterBuilders();
var builder = new DefaultBoundAttributeParameterDescriptorBuilder(this, _kind);
configure(builder);
_attributeParameterBuilders.Add(builder);
}
public BoundAttributeDescriptor Build() public BoundAttributeDescriptor Build()
{ {
var validationDiagnostics = Validate(); var validationDiagnostics = Validate();
@ -82,6 +97,19 @@ namespace Microsoft.AspNetCore.Razor.Language
diagnostics.UnionWith(_diagnostics); diagnostics.UnionWith(_diagnostics);
} }
var parameters = Array.Empty<BoundAttributeParameterDescriptor>();
if (_attributeParameterBuilders != null)
{
// Attribute parameters are case-sensitive.
var parameterset = new HashSet<BoundAttributeParameterDescriptor>(BoundAttributeParameterDescriptorComparer.CaseSensitive);
for (var i = 0; i < _attributeParameterBuilders.Count; i++)
{
parameterset.Add(_attributeParameterBuilders[i].Build());
}
parameters = parameterset.ToArray();
}
var descriptor = new DefaultBoundAttributeDescriptor( var descriptor = new DefaultBoundAttributeDescriptor(
_kind, _kind,
Name, Name,
@ -92,6 +120,7 @@ namespace Microsoft.AspNetCore.Razor.Language
IndexerValueTypeName, IndexerValueTypeName,
Documentation, Documentation,
GetDisplayName(), GetDisplayName(),
parameters,
new Dictionary<string, string>(Metadata), new Dictionary<string, string>(Metadata),
diagnostics.ToArray()); diagnostics.ToArray());
@ -205,5 +234,13 @@ namespace Microsoft.AspNetCore.Razor.Language
} }
} }
} }
private void EnsureAttributeParameterBuilders()
{
if (_attributeParameterBuilders == null)
{
_attributeParameterBuilders = new List<DefaultBoundAttributeParameterDescriptorBuilder>();
}
}
} }
} }

View File

@ -0,0 +1,34 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
namespace Microsoft.AspNetCore.Razor.Language
{
internal class DefaultBoundAttributeParameterDescriptor : BoundAttributeParameterDescriptor
{
public DefaultBoundAttributeParameterDescriptor(
string kind,
string name,
string typeName,
bool isEnum,
string documentation,
string displayName,
Dictionary<string, string> metadata,
RazorDiagnostic[] diagnostics)
: base(kind)
{
Name = name;
TypeName = typeName;
IsEnum = isEnum;
Documentation = documentation;
DisplayName = displayName;
Metadata = metadata;
Diagnostics = diagnostics;
IsStringProperty = typeName == typeof(string).FullName || typeName == "string";
IsBooleanProperty = typeName == typeof(bool).FullName || typeName == "bool";
}
}
}

View File

@ -0,0 +1,106 @@
// 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.Language
{
internal class DefaultBoundAttributeParameterDescriptorBuilder : BoundAttributeParameterDescriptorBuilder
{
private readonly DefaultBoundAttributeDescriptorBuilder _parent;
private readonly string _kind;
private readonly Dictionary<string, string> _metadata;
private RazorDiagnosticCollection _diagnostics;
public DefaultBoundAttributeParameterDescriptorBuilder(DefaultBoundAttributeDescriptorBuilder parent, string kind)
{
_parent = parent;
_kind = kind;
_metadata = new Dictionary<string, string>();
}
public override string Name { get; set; }
public override string TypeName { get; set; }
public override bool IsEnum { get; set; }
public override string Documentation { get; set; }
public override string DisplayName { get; set; }
public override IDictionary<string, string> Metadata => _metadata;
public override RazorDiagnosticCollection Diagnostics
{
get
{
if (_diagnostics == null)
{
_diagnostics = new RazorDiagnosticCollection();
}
return _diagnostics;
}
}
public BoundAttributeParameterDescriptor Build()
{
var validationDiagnostics = Validate();
var diagnostics = new HashSet<RazorDiagnostic>(validationDiagnostics);
if (_diagnostics != null)
{
diagnostics.UnionWith(_diagnostics);
}
var descriptor = new DefaultBoundAttributeParameterDescriptor(
_kind,
Name,
TypeName,
IsEnum,
Documentation,
GetDisplayName(),
new Dictionary<string, string>(Metadata),
diagnostics.ToArray());
return descriptor;
}
private string GetDisplayName()
{
if (DisplayName != null)
{
return DisplayName;
}
return $":{Name}";
}
private IEnumerable<RazorDiagnostic> Validate()
{
if (string.IsNullOrWhiteSpace(Name))
{
var diagnostic = RazorDiagnosticFactory.CreateTagHelper_InvalidBoundAttributeParameterNullOrWhitespace(_parent.Name);
yield return diagnostic;
}
else
{
foreach (var character in Name)
{
if (char.IsWhiteSpace(character) || HtmlConventions.InvalidNonWhitespaceHtmlCharacters.Contains(character))
{
var diagnostic = RazorDiagnosticFactory.CreateTagHelper_InvalidBoundAttributeParameterName(
_parent.Name,
Name,
character);
yield return diagnostic;
}
}
}
}
}
}

View File

@ -1716,6 +1716,7 @@ namespace Microsoft.AspNetCore.Razor.Language
var descriptors = element.TagHelperInfo.BindingResult.Descriptors; var descriptors = element.TagHelperInfo.BindingResult.Descriptors;
var attributeName = node.Name.GetContent(); var attributeName = node.Name.GetContent();
var attributeValueNode = node.Value; var attributeValueNode = node.Value;
var associatedDescriptors = descriptors.Where(descriptor => var associatedDescriptors = descriptors.Where(descriptor =>
descriptor.BoundAttributes.Any(attributeDescriptor => TagHelperMatchingConventions.CanSatisfyBoundAttribute(attributeName, attributeDescriptor))); descriptor.BoundAttributes.Any(attributeDescriptor => TagHelperMatchingConventions.CanSatisfyBoundAttribute(attributeName, attributeDescriptor)));
@ -1723,24 +1724,47 @@ namespace Microsoft.AspNetCore.Razor.Language
{ {
foreach (var associatedDescriptor in associatedDescriptors) foreach (var associatedDescriptor in associatedDescriptors)
{ {
var associatedAttributeDescriptor = associatedDescriptor.BoundAttributes.First(a => if (TagHelperMatchingConventions.TryGetFirstBoundAttributeMatch(
attributeName,
associatedDescriptor,
out var associatedAttributeDescriptor,
out var indexerMatch,
out var parameterMatch,
out var associatedAttributeParameterDescriptor))
{ {
return TagHelperMatchingConventions.CanSatisfyBoundAttribute(attributeName, a); IntermediateNode attributeNode;
}); if (parameterMatch &&
TagHelperMatchingConventions.TryGetBoundAttributeParameter(attributeName, out var attributeNameWithoutParameter, out var _))
{
attributeNode = new TagHelperAttributeParameterIntermediateNode()
{
AttributeName = attributeName,
AttributeNameWithoutParameter = attributeNameWithoutParameter,
BoundAttributeParameter = associatedAttributeParameterDescriptor,
BoundAttribute = associatedAttributeDescriptor,
TagHelper = associatedDescriptor,
IsIndexerNameMatch = indexerMatch,
AttributeStructure = node.TagHelperAttributeInfo.AttributeStructure,
Source = BuildSourceSpanFromNode(attributeValueNode),
};
}
else
{
attributeNode = new TagHelperPropertyIntermediateNode()
{
AttributeName = attributeName,
BoundAttribute = associatedAttributeDescriptor,
TagHelper = associatedDescriptor,
AttributeStructure = node.TagHelperAttributeInfo.AttributeStructure,
Source = BuildSourceSpanFromNode(attributeValueNode),
IsIndexerNameMatch = indexerMatch,
};
}
var setTagHelperProperty = new TagHelperPropertyIntermediateNode() _builder.Push(attributeNode);
{ VisitAttributeValue(attributeValueNode);
AttributeName = attributeName, _builder.Pop();
BoundAttribute = associatedAttributeDescriptor, }
TagHelper = associatedDescriptor,
AttributeStructure = node.TagHelperAttributeInfo.AttributeStructure,
Source = BuildSourceSpanFromNode(attributeValueNode),
IsIndexerNameMatch = TagHelperMatchingConventions.SatisfiesBoundAttributeIndexer(attributeName, associatedAttributeDescriptor),
};
_builder.Push(setTagHelperProperty);
VisitAttributeValue(attributeValueNode);
_builder.Pop();
} }
} }
else else

View File

@ -129,6 +129,11 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate
VisitDefault(node); VisitDefault(node);
} }
public virtual void VisitTagHelperAttributeParameter(TagHelperAttributeParameterIntermediateNode node)
{
VisitDefault(node);
}
public virtual void VisitComponent(ComponentIntermediateNode node) public virtual void VisitComponent(ComponentIntermediateNode node)
{ {
VisitDefault(node); VisitDefault(node);

View File

@ -0,0 +1,47 @@
// 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;
namespace Microsoft.AspNetCore.Razor.Language.Intermediate
{
public sealed class TagHelperAttributeParameterIntermediateNode : IntermediateNode
{
public override IntermediateNodeCollection Children { get; } = new IntermediateNodeCollection();
public string AttributeName { get; set; }
public string AttributeNameWithoutParameter { get; set; }
public AttributeStructure AttributeStructure { get; set; }
public BoundAttributeParameterDescriptor BoundAttributeParameter { get; set; }
public BoundAttributeDescriptor BoundAttribute { get; set; }
public TagHelperDescriptor TagHelper { get; set; }
public bool IsIndexerNameMatch { get; set; }
public override void Accept(IntermediateNodeVisitor visitor)
{
if (visitor == null)
{
throw new ArgumentNullException(nameof(visitor));
}
visitor.VisitTagHelperAttributeParameter(this);
}
public override void FormatNode(IntermediateNodeFormatter formatter)
{
formatter.WriteContent(AttributeName);
formatter.WriteProperty(nameof(AttributeName), AttributeName);
formatter.WriteProperty(nameof(AttributeStructure), AttributeStructure.ToString());
formatter.WriteProperty(nameof(BoundAttribute), BoundAttribute?.DisplayName);
formatter.WriteProperty(nameof(BoundAttributeParameter), BoundAttributeParameter?.DisplayName);
formatter.WriteProperty(nameof(TagHelper), TagHelper?.DisplayName);
}
}
}

View File

@ -273,17 +273,22 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
// Determines the full name of the Type of the property corresponding to an attribute with the given name. // Determines the full name of the Type of the property corresponding to an attribute with the given name.
private static string GetPropertyType(string name, IEnumerable<TagHelperDescriptor> descriptors) private static string GetPropertyType(string name, IEnumerable<TagHelperDescriptor> descriptors)
{ {
var firstBoundAttribute = FindFirstBoundAttribute(name, descriptors); foreach (var descriptor in descriptors)
var isBoundToIndexer = TagHelperMatchingConventions.SatisfiesBoundAttributeIndexer(name, firstBoundAttribute); {
if (TagHelperMatchingConventions.TryGetFirstBoundAttributeMatch(name, descriptor, out var firstBoundAttribute, out var indexerMatch, out var _, out var _))
{
if (indexerMatch)
{
return firstBoundAttribute.IndexerTypeName;
}
else
{
return firstBoundAttribute.TypeName;
}
}
}
if (isBoundToIndexer) return null;
{
return firstBoundAttribute?.IndexerTypeName;
}
else
{
return firstBoundAttribute?.TypeName;
}
} }
// Create a TryParseResult for given name, filling in binding details. // Create a TryParseResult for given name, filling in binding details.
@ -292,13 +297,39 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
IEnumerable<TagHelperDescriptor> descriptors, IEnumerable<TagHelperDescriptor> descriptors,
HashSet<string> processedBoundAttributeNames) HashSet<string> processedBoundAttributeNames)
{ {
var firstBoundAttribute = FindFirstBoundAttribute(name, descriptors); var isBoundAttribute = false;
var isBoundAttribute = firstBoundAttribute != null; var isBoundNonStringAttribute = false;
var isBoundNonStringAttribute = isBoundAttribute && !firstBoundAttribute.ExpectsStringValue(name); var isBoundBooleanAttribute = false;
var isBoundBooleanAttribute = isBoundAttribute && firstBoundAttribute.ExpectsBooleanValue(name); var isMissingDictionaryKey = false;
var isMissingDictionaryKey = isBoundAttribute &&
firstBoundAttribute.IndexerNamePrefix != null && foreach (var descriptor in descriptors)
name.Length == firstBoundAttribute.IndexerNamePrefix.Length; {
if (TagHelperMatchingConventions.TryGetFirstBoundAttributeMatch(
name,
descriptor,
out var firstBoundAttribute,
out var indexerMatch,
out var parameterMatch,
out var boundAttributeParameter))
{
isBoundAttribute = true;
if (parameterMatch)
{
isBoundNonStringAttribute = !boundAttributeParameter.IsStringProperty;
isBoundBooleanAttribute = boundAttributeParameter.IsBooleanProperty;
isMissingDictionaryKey = false;
}
else
{
isBoundNonStringAttribute = !firstBoundAttribute.ExpectsStringValue(name);
isBoundBooleanAttribute = firstBoundAttribute.ExpectsBooleanValue(name);
isMissingDictionaryKey = firstBoundAttribute.IndexerNamePrefix != null &&
name.Length == firstBoundAttribute.IndexerNamePrefix.Length;
}
break;
}
}
var isDuplicateAttribute = false; var isDuplicateAttribute = false;
if (isBoundAttribute && !processedBoundAttributeNames.Add(name)) if (isBoundAttribute && !processedBoundAttributeNames.Add(name))
@ -318,18 +349,6 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
}; };
} }
// Finds first TagHelperAttributeDescriptor matching given name.
private static BoundAttributeDescriptor FindFirstBoundAttribute(
string name,
IEnumerable<TagHelperDescriptor> descriptors)
{
var firstBoundAttribute = descriptors
.SelectMany(descriptor => descriptor.BoundAttributes)
.FirstOrDefault(attributeDescriptor => TagHelperMatchingConventions.CanSatisfyBoundAttribute(name, attributeDescriptor));
return firstBoundAttribute;
}
private static string GetAttributeValueContent(RazorSyntaxNode attributeBlock) private static string GetAttributeValueContent(RazorSyntaxNode attributeBlock)
{ {
if (attributeBlock is MarkupTagHelperAttributeSyntax tagHelperAttribute) if (attributeBlock is MarkupTagHelperAttributeSyntax tagHelperAttribute)

View File

@ -779,6 +779,41 @@ namespace Microsoft.AspNetCore.Razor.Language
return diagnostic; return diagnostic;
} }
internal static readonly RazorDiagnosticDescriptor TagHelper_InvalidBoundAttributeParameterNullOrWhitespace =
new RazorDiagnosticDescriptor(
$"{DiagnosticPrefix}3013",
() => Resources.TagHelper_InvalidBoundAttributeParameterNullOrWhitespace,
RazorDiagnosticSeverity.Error);
public static RazorDiagnostic CreateTagHelper_InvalidBoundAttributeParameterNullOrWhitespace(string attributeName)
{
var diagnostic = RazorDiagnostic.Create(
TagHelper_InvalidBoundAttributeParameterNullOrWhitespace,
new SourceSpan(SourceLocation.Undefined, contentLength: 0),
attributeName);
return diagnostic;
}
internal static readonly RazorDiagnosticDescriptor TagHelper_InvalidBoundAttributeParameterName =
new RazorDiagnosticDescriptor(
$"{DiagnosticPrefix}3014",
() => Resources.TagHelper_InvalidBoundAttributeParameterName,
RazorDiagnosticSeverity.Error);
public static RazorDiagnostic CreateTagHelper_InvalidBoundAttributeParameterName(
string attributeName,
string invalidName,
char invalidCharacter)
{
var diagnostic = RazorDiagnostic.Create(
TagHelper_InvalidBoundAttributeParameterName,
new SourceSpan(SourceLocation.Undefined, contentLength: 0),
attributeName,
invalidName,
invalidCharacter);
return diagnostic;
}
#endregion #endregion
#region Rewriter Errors #region Rewriter Errors

View File

@ -6,6 +6,7 @@ namespace Microsoft.AspNetCore.Razor.Language
public enum RazorDiagnosticSeverity public enum RazorDiagnosticSeverity
{ {
// Purposely using the same value as Roslyn here. // Purposely using the same value as Roslyn here.
Warning = 2,
Error = 3, Error = 3,
} }
} }

View File

@ -541,4 +541,10 @@
<data name="NamespaceDirective_NamespaceToken_Name" xml:space="preserve"> <data name="NamespaceDirective_NamespaceToken_Name" xml:space="preserve">
<value>Namespace</value> <value>Namespace</value>
</data> </data>
<data name="TagHelper_InvalidBoundAttributeParameterName" xml:space="preserve">
<value>Invalid tag helper bound attribute parameter '{1}' on bound attribute '{0}'. Tag helpers cannot bind to HTML attribute parameters with name '{1}' because the name contains a '{3}' character.</value>
</data>
<data name="TagHelper_InvalidBoundAttributeParameterNullOrWhitespace" xml:space="preserve">
<value>Invalid tag helper bound attribute parameter '{0}'. Tag helpers cannot bind to HTML attribute parameters with a null or empty name.</value>
</data>
</root> </root>

View File

@ -128,7 +128,9 @@ namespace Microsoft.AspNetCore.Razor.Language
public static bool CanSatisfyBoundAttribute(string name, BoundAttributeDescriptor descriptor) public static bool CanSatisfyBoundAttribute(string name, BoundAttributeDescriptor descriptor)
{ {
return SatisfiesBoundAttributeName(name, descriptor) || SatisfiesBoundAttributeIndexer(name, descriptor); return SatisfiesBoundAttributeName(name, descriptor) ||
SatisfiesBoundAttributeIndexer(name, descriptor) ||
descriptor.BoundAttributeParameters.Any(p => SatisfiesBoundAttributeWithParameter(name, descriptor, p));
} }
public static bool SatisfiesBoundAttributeIndexer(string name, BoundAttributeDescriptor descriptor) public static bool SatisfiesBoundAttributeIndexer(string name, BoundAttributeDescriptor descriptor)
@ -138,6 +140,84 @@ namespace Microsoft.AspNetCore.Razor.Language
name.StartsWith(descriptor.IndexerNamePrefix, StringComparison.OrdinalIgnoreCase); name.StartsWith(descriptor.IndexerNamePrefix, StringComparison.OrdinalIgnoreCase);
} }
public static bool SatisfiesBoundAttributeWithParameter(string name, BoundAttributeDescriptor parent, BoundAttributeParameterDescriptor descriptor)
{
if (TryGetBoundAttributeParameter(name, out var attributeName, out var parameterName))
{
var satisfiesBoundAttributeName = SatisfiesBoundAttributeName(attributeName, parent);
var satisfiesBoundAttributeIndexer = SatisfiesBoundAttributeIndexer(attributeName, parent);
var matchesParameter = string.Equals(descriptor.Name, parameterName, StringComparison.Ordinal);
return (satisfiesBoundAttributeName || satisfiesBoundAttributeIndexer) && matchesParameter;
}
return false;
}
public static bool TryGetBoundAttributeParameter(string fullAttributeName, out string boundAttributeName, out string parameterName)
{
boundAttributeName = null;
parameterName = null;
if (!string.IsNullOrEmpty(fullAttributeName) && fullAttributeName.IndexOf(':') != -1)
{
var segments = fullAttributeName.Split(new[] { ':' }, 2);
boundAttributeName = segments[0];
parameterName = segments[1];
return true;
}
return false;
}
public static bool TryGetFirstBoundAttributeMatch(
string name,
TagHelperDescriptor descriptor,
out BoundAttributeDescriptor boundAttribute,
out bool indexerMatch,
out bool parameterMatch,
out BoundAttributeParameterDescriptor boundAttributeParameter)
{
indexerMatch = false;
parameterMatch = false;
boundAttribute = null;
boundAttributeParameter = null;
if (string.IsNullOrEmpty(name) || descriptor == null)
{
return false;
}
// First, check if we have a bound attribute descriptor that matches the parameter if it exists.
foreach (var attribute in descriptor.BoundAttributes)
{
boundAttributeParameter = attribute.BoundAttributeParameters.FirstOrDefault(
p => SatisfiesBoundAttributeWithParameter(name, attribute, p));
if (boundAttributeParameter != null)
{
boundAttribute = attribute;
indexerMatch = SatisfiesBoundAttributeIndexer(name, attribute);
parameterMatch = true;
return true;
}
}
// If we reach here, either the attribute name doesn't contain a parameter portion or
// the specified parameter isn't supported by any of the BoundAttributeDescriptors.
foreach (var attribute in descriptor.BoundAttributes)
{
if (CanSatisfyBoundAttribute(name, attribute))
{
boundAttribute = attribute;
indexerMatch = SatisfiesBoundAttributeIndexer(name, attribute);
return true;
}
}
// No matches found.
return false;
}
private static bool SatisfiesBoundAttributeName(string name, BoundAttributeDescriptor descriptor) private static bool SatisfiesBoundAttributeName(string name, BoundAttributeDescriptor descriptor)
{ {
return string.Equals(descriptor.Name, name, StringComparison.OrdinalIgnoreCase); return string.Equals(descriptor.Name, name, StringComparison.OrdinalIgnoreCase);

View File

@ -644,7 +644,7 @@ namespace Test
}")); }"));
// Act // Act
var generated = CompileToCSharp(@" var generated = CompileToCSharp(@"
<MyComponent bind-Value-OnChanged=""ParentValue"" /> <MyComponent bind-Value=""ParentValue"" bind-Value:event=""OnChanged"" />
@code { @code {
public int ParentValue { get; set; } = 42; public int ParentValue { get; set; } = 42;
}"); }");
@ -674,7 +674,7 @@ namespace Test
}")); }"));
var generated = CompileToCSharp(@" var generated = CompileToCSharp(@"
<MyComponent bind-Value-OnChanged=""ParentValue"" /> <MyComponent bind-Value=""ParentValue"" bind-Value:event=""OnChanged"" />
@code { @code {
public int ParentValue { get; set; } = 42; public int ParentValue { get; set; } = 42;
}"); }");
@ -982,7 +982,7 @@ namespace Test
// Act // Act
var generated = CompileToCSharp(@" var generated = CompileToCSharp(@"
<input type=""text"" bind=""@CurrentDate"" format-value=""MM/dd/yyyy""/> <input type=""text"" bind=""@CurrentDate"" bind:format=""MM/dd/yyyy""/>
@code { @code {
public DateTime CurrentDate { get; set; } = new DateTime(2018, 1, 1); public DateTime CurrentDate { get; set; } = new DateTime(2018, 1, 1);
}"); }");
@ -1000,7 +1000,7 @@ namespace Test
// Act // Act
var generated = CompileToCSharp(@" var generated = CompileToCSharp(@"
<input type=""text"" bind=""@CurrentDate"" format-value=""@Format""/> <input type=""text"" bind=""@CurrentDate"" bind:format=""@Format""/>
@code { @code {
public DateTime CurrentDate { get; set; } = new DateTime(2018, 1, 1); public DateTime CurrentDate { get; set; } = new DateTime(2018, 1, 1);
@ -1056,7 +1056,7 @@ namespace Test
// Act // Act
var generated = CompileToCSharp(@" var generated = CompileToCSharp(@"
<input type=""text"" bind-value-onchange=""@ParentValue"" /> <input type=""text"" bind-value=""@ParentValue"" bind-value:event=""onchange"" />
@code { @code {
public int ParentValue { get; set; } = 42; public int ParentValue { get; set; } = 42;
}"); }");
@ -1074,7 +1074,7 @@ namespace Test
// Act // Act
var generated = CompileToCSharp(@" var generated = CompileToCSharp(@"
<input type=""text"" bind-value-onchange=""@CurrentDate"" format-value=""MM/dd"" /> <input type=""text"" bind-value=""@CurrentDate"" bind-value:event=""onchange"" bind-value:format=""MM/dd"" />
@code { @code {
public DateTime CurrentDate { get; set; } = new DateTime(2018, 1, 1); public DateTime CurrentDate { get; set; } = new DateTime(2018, 1, 1);
}"); }");

View File

@ -23,7 +23,7 @@ namespace Test
__o = Microsoft.AspNetCore.Components.RuntimeHelpers.TypeCheck<System.Int32>(Microsoft.AspNetCore.Components.BindMethods.GetValue( __o = Microsoft.AspNetCore.Components.RuntimeHelpers.TypeCheck<System.Int32>(Microsoft.AspNetCore.Components.BindMethods.GetValue(
#nullable restore #nullable restore
#line 1 "x:\dir\subdir\Test\TestComponent.cshtml" #line 1 "x:\dir\subdir\Test\TestComponent.cshtml"
ParentValue ParentValue
#line default #line default
#line hidden #line hidden

View File

@ -14,16 +14,16 @@ Document -
CSharpCode - CSharpCode -
IntermediateToken - - CSharp - #pragma warning restore 0414 IntermediateToken - - CSharp - #pragma warning restore 0414
MethodDeclaration - - protected override - void - BuildRenderTree MethodDeclaration - - protected override - void - BuildRenderTree
Component - (0:0,0 [50] x:\dir\subdir\Test\TestComponent.cshtml) - MyComponent Component - (0:0,0 [69] x:\dir\subdir\Test\TestComponent.cshtml) - MyComponent
ComponentAttribute - (35:0,35 [11] x:\dir\subdir\Test\TestComponent.cshtml) - Value - AttributeStructure.DoubleQuotes ComponentAttribute - (25:0,25 [11] x:\dir\subdir\Test\TestComponent.cshtml) - Value - AttributeStructure.DoubleQuotes
CSharpExpression - CSharpExpression -
IntermediateToken - - CSharp - Microsoft.AspNetCore.Components.BindMethods.GetValue( IntermediateToken - - CSharp - Microsoft.AspNetCore.Components.BindMethods.GetValue(
IntermediateToken - (35:0,35 [11] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - ParentValue IntermediateToken - (25:0,25 [11] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - ParentValue
IntermediateToken - - CSharp - ) IntermediateToken - - CSharp - )
ComponentAttribute - (35:0,35 [11] x:\dir\subdir\Test\TestComponent.cshtml) - OnChanged - AttributeStructure.DoubleQuotes ComponentAttribute - (25:0,25 [11] x:\dir\subdir\Test\TestComponent.cshtml) - OnChanged - AttributeStructure.DoubleQuotes
CSharpExpression - CSharpExpression -
IntermediateToken - - CSharp - __value => ParentValue = __value IntermediateToken - - CSharp - __value => ParentValue = __value
HtmlContent - (50:0,50 [2] x:\dir\subdir\Test\TestComponent.cshtml) HtmlContent - (69:0,69 [2] x:\dir\subdir\Test\TestComponent.cshtml)
IntermediateToken - (50:0,50 [2] x:\dir\subdir\Test\TestComponent.cshtml) - Html - \n IntermediateToken - (69:0,69 [2] x:\dir\subdir\Test\TestComponent.cshtml) - Html - \n
CSharpCode - (59:1,7 [50] x:\dir\subdir\Test\TestComponent.cshtml) CSharpCode - (78:1,7 [50] x:\dir\subdir\Test\TestComponent.cshtml)
IntermediateToken - (59:1,7 [50] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - \n public int ParentValue { get; set; } = 42;\n IntermediateToken - (78:1,7 [50] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - \n public int ParentValue { get; set; } = 42;\n

View File

@ -1,13 +1,13 @@
Source Location: (35:0,35 [11] x:\dir\subdir\Test\TestComponent.cshtml) Source Location: (25:0,25 [11] x:\dir\subdir\Test\TestComponent.cshtml)
|ParentValue| |ParentValue|
Generated Location: (1022:25,35 [11] ) Generated Location: (1012:25,25 [11] )
|ParentValue| |ParentValue|
Source Location: (59:1,7 [50] x:\dir\subdir\Test\TestComponent.cshtml) Source Location: (78:1,7 [50] x:\dir\subdir\Test\TestComponent.cshtml)
| |
public int ParentValue { get; set; } = 42; public int ParentValue { get; set; } = 42;
| |
Generated Location: (1627:47,7 [50] ) Generated Location: (1617:47,7 [50] )
| |
public int ParentValue { get; set; } = 42; public int ParentValue { get; set; } = 42;
| |

View File

@ -23,7 +23,7 @@ namespace Test
__o = Microsoft.AspNetCore.Components.BindMethods.GetValue( __o = Microsoft.AspNetCore.Components.BindMethods.GetValue(
#nullable restore #nullable restore
#line 1 "x:\dir\subdir\Test\TestComponent.cshtml" #line 1 "x:\dir\subdir\Test\TestComponent.cshtml"
ParentValue ParentValue
#line default #line default
#line hidden #line hidden

View File

@ -14,16 +14,16 @@ Document -
CSharpCode - CSharpCode -
IntermediateToken - - CSharp - #pragma warning restore 0414 IntermediateToken - - CSharp - #pragma warning restore 0414
MethodDeclaration - - protected override - void - BuildRenderTree MethodDeclaration - - protected override - void - BuildRenderTree
Component - (0:0,0 [50] x:\dir\subdir\Test\TestComponent.cshtml) - MyComponent Component - (0:0,0 [69] x:\dir\subdir\Test\TestComponent.cshtml) - MyComponent
ComponentAttribute - (35:0,35 [11] x:\dir\subdir\Test\TestComponent.cshtml) - Value - AttributeStructure.DoubleQuotes ComponentAttribute - (25:0,25 [11] x:\dir\subdir\Test\TestComponent.cshtml) - Value - AttributeStructure.DoubleQuotes
CSharpExpression - CSharpExpression -
IntermediateToken - - CSharp - Microsoft.AspNetCore.Components.BindMethods.GetValue( IntermediateToken - - CSharp - Microsoft.AspNetCore.Components.BindMethods.GetValue(
IntermediateToken - (35:0,35 [11] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - ParentValue IntermediateToken - (25:0,25 [11] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - ParentValue
IntermediateToken - - CSharp - ) IntermediateToken - - CSharp - )
ComponentAttribute - (35:0,35 [11] x:\dir\subdir\Test\TestComponent.cshtml) - OnChanged - AttributeStructure.DoubleQuotes ComponentAttribute - (25:0,25 [11] x:\dir\subdir\Test\TestComponent.cshtml) - OnChanged - AttributeStructure.DoubleQuotes
CSharpExpression - CSharpExpression -
IntermediateToken - - CSharp - Microsoft.AspNetCore.Components.EventCallback.Factory.CreateBinder(this, __value => ParentValue = __value, ParentValue) IntermediateToken - - CSharp - Microsoft.AspNetCore.Components.EventCallback.Factory.CreateBinder(this, __value => ParentValue = __value, ParentValue)
HtmlContent - (50:0,50 [2] x:\dir\subdir\Test\TestComponent.cshtml) HtmlContent - (69:0,69 [2] x:\dir\subdir\Test\TestComponent.cshtml)
IntermediateToken - (50:0,50 [2] x:\dir\subdir\Test\TestComponent.cshtml) - Html - \n IntermediateToken - (69:0,69 [2] x:\dir\subdir\Test\TestComponent.cshtml) - Html - \n
CSharpCode - (59:1,7 [50] x:\dir\subdir\Test\TestComponent.cshtml) CSharpCode - (78:1,7 [50] x:\dir\subdir\Test\TestComponent.cshtml)
IntermediateToken - (59:1,7 [50] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - \n public int ParentValue { get; set; } = 42;\n IntermediateToken - (78:1,7 [50] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - \n public int ParentValue { get; set; } = 42;\n

View File

@ -1,13 +1,13 @@
Source Location: (35:0,35 [11] x:\dir\subdir\Test\TestComponent.cshtml) Source Location: (25:0,25 [11] x:\dir\subdir\Test\TestComponent.cshtml)
|ParentValue| |ParentValue|
Generated Location: (951:25,35 [11] ) Generated Location: (941:25,25 [11] )
|ParentValue| |ParentValue|
Source Location: (59:1,7 [50] x:\dir\subdir\Test\TestComponent.cshtml) Source Location: (78:1,7 [50] x:\dir\subdir\Test\TestComponent.cshtml)
| |
public int ParentValue { get; set; } = 42; public int ParentValue { get; set; } = 42;
| |
Generated Location: (1595:46,7 [50] ) Generated Location: (1585:46,7 [50] )
| |
public int ParentValue { get; set; } = 42; public int ParentValue { get; set; } = 42;
| |

View File

@ -23,7 +23,7 @@ namespace Test
__o = Microsoft.AspNetCore.Components.BindMethods.GetValue( __o = Microsoft.AspNetCore.Components.BindMethods.GetValue(
#nullable restore #nullable restore
#line 1 "x:\dir\subdir\Test\TestComponent.cshtml" #line 1 "x:\dir\subdir\Test\TestComponent.cshtml"
CurrentDate CurrentDate
#line default #line default
#line hidden #line hidden

View File

@ -14,21 +14,21 @@ Document -
CSharpCode - CSharpCode -
IntermediateToken - - CSharp - #pragma warning restore 0414 IntermediateToken - - CSharp - #pragma warning restore 0414
MethodDeclaration - - protected override - void - BuildRenderTree MethodDeclaration - - protected override - void - BuildRenderTree
MarkupElement - (0:0,0 [77] x:\dir\subdir\Test\TestComponent.cshtml) - input MarkupElement - (0:0,0 [101] x:\dir\subdir\Test\TestComponent.cshtml) - input
HtmlAttribute - - type=" - " HtmlAttribute - - type=" - "
HtmlAttributeValue - (13:0,13 [4] x:\dir\subdir\Test\TestComponent.cshtml) - HtmlAttributeValue - (13:0,13 [4] x:\dir\subdir\Test\TestComponent.cshtml) -
IntermediateToken - (13:0,13 [4] x:\dir\subdir\Test\TestComponent.cshtml) - Html - text IntermediateToken - (13:0,13 [4] x:\dir\subdir\Test\TestComponent.cshtml) - Html - text
HtmlAttribute - (40:0,40 [12] x:\dir\subdir\Test\TestComponent.cshtml) - value=" - " HtmlAttribute - (31:0,31 [12] x:\dir\subdir\Test\TestComponent.cshtml) - value=" - "
CSharpExpressionAttributeValue - - CSharpExpressionAttributeValue - -
IntermediateToken - - CSharp - Microsoft.AspNetCore.Components.BindMethods.GetValue( IntermediateToken - - CSharp - Microsoft.AspNetCore.Components.BindMethods.GetValue(
IntermediateToken - (41:0,41 [11] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - CurrentDate IntermediateToken - (32:0,32 [11] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - CurrentDate
IntermediateToken - - CSharp - , IntermediateToken - - CSharp - ,
IntermediateToken - - CSharp - "MM/dd" IntermediateToken - - CSharp - "MM/dd"
IntermediateToken - - CSharp - ) IntermediateToken - - CSharp - )
HtmlAttribute - (40:0,40 [12] x:\dir\subdir\Test\TestComponent.cshtml) - onchange=" - " HtmlAttribute - (31:0,31 [12] x:\dir\subdir\Test\TestComponent.cshtml) - onchange=" - "
CSharpExpressionAttributeValue - - CSharpExpressionAttributeValue - -
IntermediateToken - - CSharp - Microsoft.AspNetCore.Components.EventCallback.Factory.CreateBinder(this, __value => CurrentDate = __value, CurrentDate, "MM/dd") IntermediateToken - - CSharp - Microsoft.AspNetCore.Components.EventCallback.Factory.CreateBinder(this, __value => CurrentDate = __value, CurrentDate, "MM/dd")
HtmlContent - (77:0,77 [2] x:\dir\subdir\Test\TestComponent.cshtml) HtmlContent - (101:0,101 [2] x:\dir\subdir\Test\TestComponent.cshtml)
IntermediateToken - (77:0,77 [2] x:\dir\subdir\Test\TestComponent.cshtml) - Html - \n IntermediateToken - (101:0,101 [2] x:\dir\subdir\Test\TestComponent.cshtml) - Html - \n
CSharpCode - (86:1,7 [77] x:\dir\subdir\Test\TestComponent.cshtml) CSharpCode - (110:1,7 [77] x:\dir\subdir\Test\TestComponent.cshtml)
IntermediateToken - (86:1,7 [77] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - \n public DateTime CurrentDate { get; set; } = new DateTime(2018, 1, 1);\n IntermediateToken - (110:1,7 [77] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - \n public DateTime CurrentDate { get; set; } = new DateTime(2018, 1, 1);\n

View File

@ -1,13 +1,13 @@
Source Location: (41:0,41 [11] x:\dir\subdir\Test\TestComponent.cshtml) Source Location: (32:0,32 [11] x:\dir\subdir\Test\TestComponent.cshtml)
|CurrentDate| |CurrentDate|
Generated Location: (957:25,41 [11] ) Generated Location: (948:25,32 [11] )
|CurrentDate| |CurrentDate|
Source Location: (86:1,7 [77] x:\dir\subdir\Test\TestComponent.cshtml) Source Location: (110:1,7 [77] x:\dir\subdir\Test\TestComponent.cshtml)
| |
public DateTime CurrentDate { get; set; } = new DateTime(2018, 1, 1); public DateTime CurrentDate { get; set; } = new DateTime(2018, 1, 1);
| |
Generated Location: (1320:36,7 [77] ) Generated Location: (1311:36,7 [77] )
| |
public DateTime CurrentDate { get; set; } = new DateTime(2018, 1, 1); public DateTime CurrentDate { get; set; } = new DateTime(2018, 1, 1);
| |

View File

@ -23,7 +23,7 @@ namespace Test
__o = Microsoft.AspNetCore.Components.BindMethods.GetValue( __o = Microsoft.AspNetCore.Components.BindMethods.GetValue(
#nullable restore #nullable restore
#line 1 "x:\dir\subdir\Test\TestComponent.cshtml" #line 1 "x:\dir\subdir\Test\TestComponent.cshtml"
ParentValue ParentValue
#line default #line default
#line hidden #line hidden

View File

@ -14,19 +14,19 @@ Document -
CSharpCode - CSharpCode -
IntermediateToken - - CSharp - #pragma warning restore 0414 IntermediateToken - - CSharp - #pragma warning restore 0414
MethodDeclaration - - protected override - void - BuildRenderTree MethodDeclaration - - protected override - void - BuildRenderTree
MarkupElement - (0:0,0 [56] x:\dir\subdir\Test\TestComponent.cshtml) - input MarkupElement - (0:0,0 [75] x:\dir\subdir\Test\TestComponent.cshtml) - input
HtmlAttribute - - type=" - " HtmlAttribute - - type=" - "
HtmlAttributeValue - (13:0,13 [4] x:\dir\subdir\Test\TestComponent.cshtml) - HtmlAttributeValue - (13:0,13 [4] x:\dir\subdir\Test\TestComponent.cshtml) -
IntermediateToken - (13:0,13 [4] x:\dir\subdir\Test\TestComponent.cshtml) - Html - text IntermediateToken - (13:0,13 [4] x:\dir\subdir\Test\TestComponent.cshtml) - Html - text
HtmlAttribute - (40:0,40 [12] x:\dir\subdir\Test\TestComponent.cshtml) - value=" - " HtmlAttribute - (31:0,31 [12] x:\dir\subdir\Test\TestComponent.cshtml) - value=" - "
CSharpExpressionAttributeValue - - CSharpExpressionAttributeValue - -
IntermediateToken - - CSharp - Microsoft.AspNetCore.Components.BindMethods.GetValue( IntermediateToken - - CSharp - Microsoft.AspNetCore.Components.BindMethods.GetValue(
IntermediateToken - (41:0,41 [11] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - ParentValue IntermediateToken - (32:0,32 [11] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - ParentValue
IntermediateToken - - CSharp - ) IntermediateToken - - CSharp - )
HtmlAttribute - (40:0,40 [12] x:\dir\subdir\Test\TestComponent.cshtml) - onchange=" - " HtmlAttribute - (31:0,31 [12] x:\dir\subdir\Test\TestComponent.cshtml) - onchange=" - "
CSharpExpressionAttributeValue - - CSharpExpressionAttributeValue - -
IntermediateToken - - CSharp - Microsoft.AspNetCore.Components.EventCallback.Factory.CreateBinder(this, __value => ParentValue = __value, ParentValue) IntermediateToken - - CSharp - Microsoft.AspNetCore.Components.EventCallback.Factory.CreateBinder(this, __value => ParentValue = __value, ParentValue)
HtmlContent - (56:0,56 [2] x:\dir\subdir\Test\TestComponent.cshtml) HtmlContent - (75:0,75 [2] x:\dir\subdir\Test\TestComponent.cshtml)
IntermediateToken - (56:0,56 [2] x:\dir\subdir\Test\TestComponent.cshtml) - Html - \n IntermediateToken - (75:0,75 [2] x:\dir\subdir\Test\TestComponent.cshtml) - Html - \n
CSharpCode - (65:1,7 [50] x:\dir\subdir\Test\TestComponent.cshtml) CSharpCode - (84:1,7 [50] x:\dir\subdir\Test\TestComponent.cshtml)
IntermediateToken - (65:1,7 [50] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - \n public int ParentValue { get; set; } = 42;\n IntermediateToken - (84:1,7 [50] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - \n public int ParentValue { get; set; } = 42;\n

View File

@ -1,13 +1,13 @@
Source Location: (41:0,41 [11] x:\dir\subdir\Test\TestComponent.cshtml) Source Location: (32:0,32 [11] x:\dir\subdir\Test\TestComponent.cshtml)
|ParentValue| |ParentValue|
Generated Location: (957:25,41 [11] ) Generated Location: (948:25,32 [11] )
|ParentValue| |ParentValue|
Source Location: (65:1,7 [50] x:\dir\subdir\Test\TestComponent.cshtml) Source Location: (84:1,7 [50] x:\dir\subdir\Test\TestComponent.cshtml)
| |
public int ParentValue { get; set; } = 42; public int ParentValue { get; set; } = 42;
| |
Generated Location: (1302:36,7 [50] ) Generated Location: (1293:36,7 [50] )
| |
public int ParentValue { get; set; } = 42; public int ParentValue { get; set; } = 42;
| |

View File

@ -31,7 +31,7 @@ namespace Test
, ,
#nullable restore #nullable restore
#line 1 "x:\dir\subdir\Test\TestComponent.cshtml" #line 1 "x:\dir\subdir\Test\TestComponent.cshtml"
Format Format
#line default #line default
#line hidden #line hidden

View File

@ -14,7 +14,7 @@ Document -
CSharpCode - CSharpCode -
IntermediateToken - - CSharp - #pragma warning restore 0414 IntermediateToken - - CSharp - #pragma warning restore 0414
MethodDeclaration - - protected override - void - BuildRenderTree MethodDeclaration - - protected override - void - BuildRenderTree
MarkupElement - (0:0,0 [63] x:\dir\subdir\Test\TestComponent.cshtml) - input MarkupElement - (0:0,0 [62] x:\dir\subdir\Test\TestComponent.cshtml) - input
HtmlAttribute - - type=" - " HtmlAttribute - - type=" - "
HtmlAttributeValue - (13:0,13 [4] x:\dir\subdir\Test\TestComponent.cshtml) - HtmlAttributeValue - (13:0,13 [4] x:\dir\subdir\Test\TestComponent.cshtml) -
IntermediateToken - (13:0,13 [4] x:\dir\subdir\Test\TestComponent.cshtml) - Html - text IntermediateToken - (13:0,13 [4] x:\dir\subdir\Test\TestComponent.cshtml) - Html - text
@ -23,12 +23,12 @@ Document -
IntermediateToken - - CSharp - Microsoft.AspNetCore.Components.BindMethods.GetValue( IntermediateToken - - CSharp - Microsoft.AspNetCore.Components.BindMethods.GetValue(
IntermediateToken - (26:0,26 [11] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - CurrentDate IntermediateToken - (26:0,26 [11] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - CurrentDate
IntermediateToken - - CSharp - , IntermediateToken - - CSharp - ,
IntermediateToken - (54:0,54 [6] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - Format IntermediateToken - (53:0,53 [6] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - Format
IntermediateToken - - CSharp - ) IntermediateToken - - CSharp - )
HtmlAttribute - (25:0,25 [12] x:\dir\subdir\Test\TestComponent.cshtml) - onchange=" - " HtmlAttribute - (25:0,25 [12] x:\dir\subdir\Test\TestComponent.cshtml) - onchange=" - "
CSharpExpressionAttributeValue - - CSharpExpressionAttributeValue - -
IntermediateToken - - CSharp - Microsoft.AspNetCore.Components.EventCallback.Factory.CreateBinder(this, __value => CurrentDate = __value, CurrentDate, Format) IntermediateToken - - CSharp - Microsoft.AspNetCore.Components.EventCallback.Factory.CreateBinder(this, __value => CurrentDate = __value, CurrentDate, Format)
HtmlContent - (63:0,63 [2] x:\dir\subdir\Test\TestComponent.cshtml) HtmlContent - (62:0,62 [2] x:\dir\subdir\Test\TestComponent.cshtml)
IntermediateToken - (63:0,63 [2] x:\dir\subdir\Test\TestComponent.cshtml) - Html - \n IntermediateToken - (62:0,62 [2] x:\dir\subdir\Test\TestComponent.cshtml) - Html - \n
CSharpCode - (72:1,7 [135] x:\dir\subdir\Test\TestComponent.cshtml) CSharpCode - (71:1,7 [135] x:\dir\subdir\Test\TestComponent.cshtml)
IntermediateToken - (72:1,7 [135] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - \n public DateTime CurrentDate { get; set; } = new DateTime(2018, 1, 1);\n\n public string Format { get; set; } = "MM/dd/yyyy";\n IntermediateToken - (71:1,7 [135] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - \n public DateTime CurrentDate { get; set; } = new DateTime(2018, 1, 1);\n\n public string Format { get; set; } = "MM/dd/yyyy";\n

View File

@ -3,18 +3,18 @@ Source Location: (26:0,26 [11] x:\dir\subdir\Test\TestComponent.cshtml)
Generated Location: (942:25,26 [11] ) Generated Location: (942:25,26 [11] )
|CurrentDate| |CurrentDate|
Source Location: (54:0,54 [6] x:\dir\subdir\Test\TestComponent.cshtml) Source Location: (53:0,53 [6] x:\dir\subdir\Test\TestComponent.cshtml)
|Format| |Format|
Generated Location: (1145:33,54 [6] ) Generated Location: (1144:33,53 [6] )
|Format| |Format|
Source Location: (72:1,7 [135] x:\dir\subdir\Test\TestComponent.cshtml) Source Location: (71:1,7 [135] x:\dir\subdir\Test\TestComponent.cshtml)
| |
public DateTime CurrentDate { get; set; } = new DateTime(2018, 1, 1); public DateTime CurrentDate { get; set; } = new DateTime(2018, 1, 1);
public string Format { get; set; } = "MM/dd/yyyy"; public string Format { get; set; } = "MM/dd/yyyy";
| |
Generated Location: (1493:44,7 [135] ) Generated Location: (1492:44,7 [135] )
| |
public DateTime CurrentDate { get; set; } = new DateTime(2018, 1, 1); public DateTime CurrentDate { get; set; } = new DateTime(2018, 1, 1);

View File

@ -14,7 +14,7 @@ Document -
CSharpCode - CSharpCode -
IntermediateToken - - CSharp - #pragma warning restore 0414 IntermediateToken - - CSharp - #pragma warning restore 0414
MethodDeclaration - - protected override - void - BuildRenderTree MethodDeclaration - - protected override - void - BuildRenderTree
MarkupElement - (0:0,0 [66] x:\dir\subdir\Test\TestComponent.cshtml) - input MarkupElement - (0:0,0 [65] x:\dir\subdir\Test\TestComponent.cshtml) - input
HtmlAttribute - - type=" - " HtmlAttribute - - type=" - "
HtmlAttributeValue - (13:0,13 [4] x:\dir\subdir\Test\TestComponent.cshtml) - HtmlAttributeValue - (13:0,13 [4] x:\dir\subdir\Test\TestComponent.cshtml) -
IntermediateToken - (13:0,13 [4] x:\dir\subdir\Test\TestComponent.cshtml) - Html - text IntermediateToken - (13:0,13 [4] x:\dir\subdir\Test\TestComponent.cshtml) - Html - text
@ -28,7 +28,7 @@ Document -
HtmlAttribute - (25:0,25 [12] x:\dir\subdir\Test\TestComponent.cshtml) - onchange=" - " HtmlAttribute - (25:0,25 [12] x:\dir\subdir\Test\TestComponent.cshtml) - onchange=" - "
CSharpExpressionAttributeValue - - CSharpExpressionAttributeValue - -
IntermediateToken - - CSharp - Microsoft.AspNetCore.Components.EventCallback.Factory.CreateBinder(this, __value => CurrentDate = __value, CurrentDate, "MM/dd/yyyy") IntermediateToken - - CSharp - Microsoft.AspNetCore.Components.EventCallback.Factory.CreateBinder(this, __value => CurrentDate = __value, CurrentDate, "MM/dd/yyyy")
HtmlContent - (66:0,66 [2] x:\dir\subdir\Test\TestComponent.cshtml) HtmlContent - (65:0,65 [2] x:\dir\subdir\Test\TestComponent.cshtml)
IntermediateToken - (66:0,66 [2] x:\dir\subdir\Test\TestComponent.cshtml) - Html - \n IntermediateToken - (65:0,65 [2] x:\dir\subdir\Test\TestComponent.cshtml) - Html - \n
CSharpCode - (75:1,7 [77] x:\dir\subdir\Test\TestComponent.cshtml) CSharpCode - (74:1,7 [77] x:\dir\subdir\Test\TestComponent.cshtml)
IntermediateToken - (75:1,7 [77] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - \n public DateTime CurrentDate { get; set; } = new DateTime(2018, 1, 1);\n IntermediateToken - (74:1,7 [77] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - \n public DateTime CurrentDate { get; set; } = new DateTime(2018, 1, 1);\n

View File

@ -3,7 +3,7 @@ Source Location: (26:0,26 [11] x:\dir\subdir\Test\TestComponent.cshtml)
Generated Location: (942:25,26 [11] ) Generated Location: (942:25,26 [11] )
|CurrentDate| |CurrentDate|
Source Location: (75:1,7 [77] x:\dir\subdir\Test\TestComponent.cshtml) Source Location: (74:1,7 [77] x:\dir\subdir\Test\TestComponent.cshtml)
| |
public DateTime CurrentDate { get; set; } = new DateTime(2018, 1, 1); public DateTime CurrentDate { get; set; } = new DateTime(2018, 1, 1);
| |

View File

@ -17,7 +17,7 @@ namespace Test
builder.AddAttribute(1, "Value", Microsoft.AspNetCore.Components.RuntimeHelpers.TypeCheck<System.Int32>(Microsoft.AspNetCore.Components.BindMethods.GetValue( builder.AddAttribute(1, "Value", Microsoft.AspNetCore.Components.RuntimeHelpers.TypeCheck<System.Int32>(Microsoft.AspNetCore.Components.BindMethods.GetValue(
#nullable restore #nullable restore
#line 1 "x:\dir\subdir\Test\TestComponent.cshtml" #line 1 "x:\dir\subdir\Test\TestComponent.cshtml"
ParentValue ParentValue
#line default #line default
#line hidden #line hidden

View File

@ -7,14 +7,14 @@ Document -
UsingDirective - (104:5,1 [39] ) - Microsoft.AspNetCore.Components UsingDirective - (104:5,1 [39] ) - Microsoft.AspNetCore.Components
ClassDeclaration - - public - TestComponent - Microsoft.AspNetCore.Components.ComponentBase - ClassDeclaration - - public - TestComponent - Microsoft.AspNetCore.Components.ComponentBase -
MethodDeclaration - - protected override - void - BuildRenderTree MethodDeclaration - - protected override - void - BuildRenderTree
Component - (0:0,0 [50] x:\dir\subdir\Test\TestComponent.cshtml) - MyComponent Component - (0:0,0 [69] x:\dir\subdir\Test\TestComponent.cshtml) - MyComponent
ComponentAttribute - (35:0,35 [11] x:\dir\subdir\Test\TestComponent.cshtml) - Value - AttributeStructure.DoubleQuotes ComponentAttribute - (25:0,25 [11] x:\dir\subdir\Test\TestComponent.cshtml) - Value - AttributeStructure.DoubleQuotes
CSharpExpression - CSharpExpression -
IntermediateToken - - CSharp - Microsoft.AspNetCore.Components.BindMethods.GetValue( IntermediateToken - - CSharp - Microsoft.AspNetCore.Components.BindMethods.GetValue(
IntermediateToken - (35:0,35 [11] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - ParentValue IntermediateToken - (25:0,25 [11] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - ParentValue
IntermediateToken - - CSharp - ) IntermediateToken - - CSharp - )
ComponentAttribute - (35:0,35 [11] x:\dir\subdir\Test\TestComponent.cshtml) - OnChanged - AttributeStructure.DoubleQuotes ComponentAttribute - (25:0,25 [11] x:\dir\subdir\Test\TestComponent.cshtml) - OnChanged - AttributeStructure.DoubleQuotes
CSharpExpression - CSharpExpression -
IntermediateToken - - CSharp - __value => ParentValue = __value IntermediateToken - - CSharp - __value => ParentValue = __value
CSharpCode - (59:1,7 [50] x:\dir\subdir\Test\TestComponent.cshtml) CSharpCode - (78:1,7 [50] x:\dir\subdir\Test\TestComponent.cshtml)
IntermediateToken - (59:1,7 [50] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - \n public int ParentValue { get; set; } = 42;\n IntermediateToken - (78:1,7 [50] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - \n public int ParentValue { get; set; } = 42;\n

View File

@ -1,8 +1,8 @@
Source Location: (59:1,7 [50] x:\dir\subdir\Test\TestComponent.cshtml) Source Location: (78:1,7 [50] x:\dir\subdir\Test\TestComponent.cshtml)
| |
public int ParentValue { get; set; } = 42; public int ParentValue { get; set; } = 42;
| |
Generated Location: (1202:31,7 [50] ) Generated Location: (1192:31,7 [50] )
| |
public int ParentValue { get; set; } = 42; public int ParentValue { get; set; } = 42;
| |

View File

@ -17,7 +17,7 @@ namespace Test
builder.AddAttribute(1, "Value", Microsoft.AspNetCore.Components.BindMethods.GetValue( builder.AddAttribute(1, "Value", Microsoft.AspNetCore.Components.BindMethods.GetValue(
#nullable restore #nullable restore
#line 1 "x:\dir\subdir\Test\TestComponent.cshtml" #line 1 "x:\dir\subdir\Test\TestComponent.cshtml"
ParentValue ParentValue
#line default #line default
#line hidden #line hidden

View File

@ -7,14 +7,14 @@ Document -
UsingDirective - (104:5,1 [39] ) - Microsoft.AspNetCore.Components UsingDirective - (104:5,1 [39] ) - Microsoft.AspNetCore.Components
ClassDeclaration - - public - TestComponent - Microsoft.AspNetCore.Components.ComponentBase - ClassDeclaration - - public - TestComponent - Microsoft.AspNetCore.Components.ComponentBase -
MethodDeclaration - - protected override - void - BuildRenderTree MethodDeclaration - - protected override - void - BuildRenderTree
Component - (0:0,0 [50] x:\dir\subdir\Test\TestComponent.cshtml) - MyComponent Component - (0:0,0 [69] x:\dir\subdir\Test\TestComponent.cshtml) - MyComponent
ComponentAttribute - (35:0,35 [11] x:\dir\subdir\Test\TestComponent.cshtml) - Value - AttributeStructure.DoubleQuotes ComponentAttribute - (25:0,25 [11] x:\dir\subdir\Test\TestComponent.cshtml) - Value - AttributeStructure.DoubleQuotes
CSharpExpression - CSharpExpression -
IntermediateToken - - CSharp - Microsoft.AspNetCore.Components.BindMethods.GetValue( IntermediateToken - - CSharp - Microsoft.AspNetCore.Components.BindMethods.GetValue(
IntermediateToken - (35:0,35 [11] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - ParentValue IntermediateToken - (25:0,25 [11] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - ParentValue
IntermediateToken - - CSharp - ) IntermediateToken - - CSharp - )
ComponentAttribute - (35:0,35 [11] x:\dir\subdir\Test\TestComponent.cshtml) - OnChanged - AttributeStructure.DoubleQuotes ComponentAttribute - (25:0,25 [11] x:\dir\subdir\Test\TestComponent.cshtml) - OnChanged - AttributeStructure.DoubleQuotes
CSharpExpression - CSharpExpression -
IntermediateToken - - CSharp - Microsoft.AspNetCore.Components.EventCallback.Factory.CreateBinder(this, __value => ParentValue = __value, ParentValue) IntermediateToken - - CSharp - Microsoft.AspNetCore.Components.EventCallback.Factory.CreateBinder(this, __value => ParentValue = __value, ParentValue)
CSharpCode - (59:1,7 [50] x:\dir\subdir\Test\TestComponent.cshtml) CSharpCode - (78:1,7 [50] x:\dir\subdir\Test\TestComponent.cshtml)
IntermediateToken - (59:1,7 [50] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - \n public int ParentValue { get; set; } = 42;\n IntermediateToken - (78:1,7 [50] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - \n public int ParentValue { get; set; } = 42;\n

View File

@ -1,8 +1,8 @@
Source Location: (59:1,7 [50] x:\dir\subdir\Test\TestComponent.cshtml) Source Location: (78:1,7 [50] x:\dir\subdir\Test\TestComponent.cshtml)
| |
public int ParentValue { get; set; } = 42; public int ParentValue { get; set; } = 42;
| |
Generated Location: (1184:31,7 [50] ) Generated Location: (1174:31,7 [50] )
| |
public int ParentValue { get; set; } = 42; public int ParentValue { get; set; } = 42;
| |

View File

@ -18,7 +18,7 @@ namespace Test
builder.AddAttribute(2, "value", Microsoft.AspNetCore.Components.BindMethods.GetValue( builder.AddAttribute(2, "value", Microsoft.AspNetCore.Components.BindMethods.GetValue(
#nullable restore #nullable restore
#line 1 "x:\dir\subdir\Test\TestComponent.cshtml" #line 1 "x:\dir\subdir\Test\TestComponent.cshtml"
CurrentDate CurrentDate
#line default #line default
#line hidden #line hidden

View File

@ -7,19 +7,19 @@ Document -
UsingDirective - (104:5,1 [39] ) - Microsoft.AspNetCore.Components UsingDirective - (104:5,1 [39] ) - Microsoft.AspNetCore.Components
ClassDeclaration - - public - TestComponent - Microsoft.AspNetCore.Components.ComponentBase - ClassDeclaration - - public - TestComponent - Microsoft.AspNetCore.Components.ComponentBase -
MethodDeclaration - - protected override - void - BuildRenderTree MethodDeclaration - - protected override - void - BuildRenderTree
MarkupElement - (0:0,0 [77] x:\dir\subdir\Test\TestComponent.cshtml) - input MarkupElement - (0:0,0 [101] x:\dir\subdir\Test\TestComponent.cshtml) - input
HtmlAttribute - - type=" - " HtmlAttribute - - type=" - "
HtmlAttributeValue - (13:0,13 [4] x:\dir\subdir\Test\TestComponent.cshtml) - HtmlAttributeValue - (13:0,13 [4] x:\dir\subdir\Test\TestComponent.cshtml) -
IntermediateToken - (13:0,13 [4] x:\dir\subdir\Test\TestComponent.cshtml) - Html - text IntermediateToken - (13:0,13 [4] x:\dir\subdir\Test\TestComponent.cshtml) - Html - text
HtmlAttribute - (40:0,40 [12] x:\dir\subdir\Test\TestComponent.cshtml) - value=" - " HtmlAttribute - (31:0,31 [12] x:\dir\subdir\Test\TestComponent.cshtml) - value=" - "
CSharpExpressionAttributeValue - - CSharpExpressionAttributeValue - -
IntermediateToken - - CSharp - Microsoft.AspNetCore.Components.BindMethods.GetValue( IntermediateToken - - CSharp - Microsoft.AspNetCore.Components.BindMethods.GetValue(
IntermediateToken - (41:0,41 [11] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - CurrentDate IntermediateToken - (32:0,32 [11] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - CurrentDate
IntermediateToken - - CSharp - , IntermediateToken - - CSharp - ,
IntermediateToken - - CSharp - "MM/dd" IntermediateToken - - CSharp - "MM/dd"
IntermediateToken - - CSharp - ) IntermediateToken - - CSharp - )
HtmlAttribute - (40:0,40 [12] x:\dir\subdir\Test\TestComponent.cshtml) - onchange=" - " HtmlAttribute - (31:0,31 [12] x:\dir\subdir\Test\TestComponent.cshtml) - onchange=" - "
CSharpExpressionAttributeValue - - CSharpExpressionAttributeValue - -
IntermediateToken - - CSharp - Microsoft.AspNetCore.Components.EventCallback.Factory.CreateBinder(this, __value => CurrentDate = __value, CurrentDate, "MM/dd") IntermediateToken - - CSharp - Microsoft.AspNetCore.Components.EventCallback.Factory.CreateBinder(this, __value => CurrentDate = __value, CurrentDate, "MM/dd")
CSharpCode - (86:1,7 [77] x:\dir\subdir\Test\TestComponent.cshtml) CSharpCode - (110:1,7 [77] x:\dir\subdir\Test\TestComponent.cshtml)
IntermediateToken - (86:1,7 [77] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - \n public DateTime CurrentDate { get; set; } = new DateTime(2018, 1, 1);\n IntermediateToken - (110:1,7 [77] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - \n public DateTime CurrentDate { get; set; } = new DateTime(2018, 1, 1);\n

View File

@ -1,8 +1,8 @@
Source Location: (86:1,7 [77] x:\dir\subdir\Test\TestComponent.cshtml) Source Location: (110:1,7 [77] x:\dir\subdir\Test\TestComponent.cshtml)
| |
public DateTime CurrentDate { get; set; } = new DateTime(2018, 1, 1); public DateTime CurrentDate { get; set; } = new DateTime(2018, 1, 1);
| |
Generated Location: (1248:32,7 [77] ) Generated Location: (1239:32,7 [77] )
| |
public DateTime CurrentDate { get; set; } = new DateTime(2018, 1, 1); public DateTime CurrentDate { get; set; } = new DateTime(2018, 1, 1);
| |

View File

@ -18,7 +18,7 @@ namespace Test
builder.AddAttribute(2, "value", Microsoft.AspNetCore.Components.BindMethods.GetValue( builder.AddAttribute(2, "value", Microsoft.AspNetCore.Components.BindMethods.GetValue(
#nullable restore #nullable restore
#line 1 "x:\dir\subdir\Test\TestComponent.cshtml" #line 1 "x:\dir\subdir\Test\TestComponent.cshtml"
ParentValue ParentValue
#line default #line default
#line hidden #line hidden

View File

@ -7,17 +7,17 @@ Document -
UsingDirective - (104:5,1 [39] ) - Microsoft.AspNetCore.Components UsingDirective - (104:5,1 [39] ) - Microsoft.AspNetCore.Components
ClassDeclaration - - public - TestComponent - Microsoft.AspNetCore.Components.ComponentBase - ClassDeclaration - - public - TestComponent - Microsoft.AspNetCore.Components.ComponentBase -
MethodDeclaration - - protected override - void - BuildRenderTree MethodDeclaration - - protected override - void - BuildRenderTree
MarkupElement - (0:0,0 [56] x:\dir\subdir\Test\TestComponent.cshtml) - input MarkupElement - (0:0,0 [75] x:\dir\subdir\Test\TestComponent.cshtml) - input
HtmlAttribute - - type=" - " HtmlAttribute - - type=" - "
HtmlAttributeValue - (13:0,13 [4] x:\dir\subdir\Test\TestComponent.cshtml) - HtmlAttributeValue - (13:0,13 [4] x:\dir\subdir\Test\TestComponent.cshtml) -
IntermediateToken - (13:0,13 [4] x:\dir\subdir\Test\TestComponent.cshtml) - Html - text IntermediateToken - (13:0,13 [4] x:\dir\subdir\Test\TestComponent.cshtml) - Html - text
HtmlAttribute - (40:0,40 [12] x:\dir\subdir\Test\TestComponent.cshtml) - value=" - " HtmlAttribute - (31:0,31 [12] x:\dir\subdir\Test\TestComponent.cshtml) - value=" - "
CSharpExpressionAttributeValue - - CSharpExpressionAttributeValue - -
IntermediateToken - - CSharp - Microsoft.AspNetCore.Components.BindMethods.GetValue( IntermediateToken - - CSharp - Microsoft.AspNetCore.Components.BindMethods.GetValue(
IntermediateToken - (41:0,41 [11] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - ParentValue IntermediateToken - (32:0,32 [11] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - ParentValue
IntermediateToken - - CSharp - ) IntermediateToken - - CSharp - )
HtmlAttribute - (40:0,40 [12] x:\dir\subdir\Test\TestComponent.cshtml) - onchange=" - " HtmlAttribute - (31:0,31 [12] x:\dir\subdir\Test\TestComponent.cshtml) - onchange=" - "
CSharpExpressionAttributeValue - - CSharpExpressionAttributeValue - -
IntermediateToken - - CSharp - Microsoft.AspNetCore.Components.EventCallback.Factory.CreateBinder(this, __value => ParentValue = __value, ParentValue) IntermediateToken - - CSharp - Microsoft.AspNetCore.Components.EventCallback.Factory.CreateBinder(this, __value => ParentValue = __value, ParentValue)
CSharpCode - (65:1,7 [50] x:\dir\subdir\Test\TestComponent.cshtml) CSharpCode - (84:1,7 [50] x:\dir\subdir\Test\TestComponent.cshtml)
IntermediateToken - (65:1,7 [50] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - \n public int ParentValue { get; set; } = 42;\n IntermediateToken - (84:1,7 [50] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - \n public int ParentValue { get; set; } = 42;\n

View File

@ -1,8 +1,8 @@
Source Location: (65:1,7 [50] x:\dir\subdir\Test\TestComponent.cshtml) Source Location: (84:1,7 [50] x:\dir\subdir\Test\TestComponent.cshtml)
| |
public int ParentValue { get; set; } = 42; public int ParentValue { get; set; } = 42;
| |
Generated Location: (1230:32,7 [50] ) Generated Location: (1221:32,7 [50] )
| |
public int ParentValue { get; set; } = 42; public int ParentValue { get; set; } = 42;
| |

View File

@ -26,7 +26,7 @@ namespace Test
, ,
#nullable restore #nullable restore
#line 1 "x:\dir\subdir\Test\TestComponent.cshtml" #line 1 "x:\dir\subdir\Test\TestComponent.cshtml"
Format Format
#line default #line default
#line hidden #line hidden

View File

@ -7,7 +7,7 @@ Document -
UsingDirective - (104:5,1 [39] ) - Microsoft.AspNetCore.Components UsingDirective - (104:5,1 [39] ) - Microsoft.AspNetCore.Components
ClassDeclaration - - public - TestComponent - Microsoft.AspNetCore.Components.ComponentBase - ClassDeclaration - - public - TestComponent - Microsoft.AspNetCore.Components.ComponentBase -
MethodDeclaration - - protected override - void - BuildRenderTree MethodDeclaration - - protected override - void - BuildRenderTree
MarkupElement - (0:0,0 [63] x:\dir\subdir\Test\TestComponent.cshtml) - input MarkupElement - (0:0,0 [62] x:\dir\subdir\Test\TestComponent.cshtml) - input
HtmlAttribute - - type=" - " HtmlAttribute - - type=" - "
HtmlAttributeValue - (13:0,13 [4] x:\dir\subdir\Test\TestComponent.cshtml) - HtmlAttributeValue - (13:0,13 [4] x:\dir\subdir\Test\TestComponent.cshtml) -
IntermediateToken - (13:0,13 [4] x:\dir\subdir\Test\TestComponent.cshtml) - Html - text IntermediateToken - (13:0,13 [4] x:\dir\subdir\Test\TestComponent.cshtml) - Html - text
@ -16,10 +16,10 @@ Document -
IntermediateToken - - CSharp - Microsoft.AspNetCore.Components.BindMethods.GetValue( IntermediateToken - - CSharp - Microsoft.AspNetCore.Components.BindMethods.GetValue(
IntermediateToken - (26:0,26 [11] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - CurrentDate IntermediateToken - (26:0,26 [11] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - CurrentDate
IntermediateToken - - CSharp - , IntermediateToken - - CSharp - ,
IntermediateToken - (54:0,54 [6] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - Format IntermediateToken - (53:0,53 [6] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - Format
IntermediateToken - - CSharp - ) IntermediateToken - - CSharp - )
HtmlAttribute - (25:0,25 [12] x:\dir\subdir\Test\TestComponent.cshtml) - onchange=" - " HtmlAttribute - (25:0,25 [12] x:\dir\subdir\Test\TestComponent.cshtml) - onchange=" - "
CSharpExpressionAttributeValue - - CSharpExpressionAttributeValue - -
IntermediateToken - - CSharp - Microsoft.AspNetCore.Components.EventCallback.Factory.CreateBinder(this, __value => CurrentDate = __value, CurrentDate, Format) IntermediateToken - - CSharp - Microsoft.AspNetCore.Components.EventCallback.Factory.CreateBinder(this, __value => CurrentDate = __value, CurrentDate, Format)
CSharpCode - (72:1,7 [135] x:\dir\subdir\Test\TestComponent.cshtml) CSharpCode - (71:1,7 [135] x:\dir\subdir\Test\TestComponent.cshtml)
IntermediateToken - (72:1,7 [135] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - \n public DateTime CurrentDate { get; set; } = new DateTime(2018, 1, 1);\n\n public string Format { get; set; } = "MM/dd/yyyy";\n IntermediateToken - (71:1,7 [135] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - \n public DateTime CurrentDate { get; set; } = new DateTime(2018, 1, 1);\n\n public string Format { get; set; } = "MM/dd/yyyy";\n

View File

@ -1,10 +1,10 @@
Source Location: (72:1,7 [135] x:\dir\subdir\Test\TestComponent.cshtml) Source Location: (71:1,7 [135] x:\dir\subdir\Test\TestComponent.cshtml)
| |
public DateTime CurrentDate { get; set; } = new DateTime(2018, 1, 1); public DateTime CurrentDate { get; set; } = new DateTime(2018, 1, 1);
public string Format { get; set; } = "MM/dd/yyyy"; public string Format { get; set; } = "MM/dd/yyyy";
| |
Generated Location: (1421:40,7 [135] ) Generated Location: (1420:40,7 [135] )
| |
public DateTime CurrentDate { get; set; } = new DateTime(2018, 1, 1); public DateTime CurrentDate { get; set; } = new DateTime(2018, 1, 1);

View File

@ -7,7 +7,7 @@ Document -
UsingDirective - (104:5,1 [39] ) - Microsoft.AspNetCore.Components UsingDirective - (104:5,1 [39] ) - Microsoft.AspNetCore.Components
ClassDeclaration - - public - TestComponent - Microsoft.AspNetCore.Components.ComponentBase - ClassDeclaration - - public - TestComponent - Microsoft.AspNetCore.Components.ComponentBase -
MethodDeclaration - - protected override - void - BuildRenderTree MethodDeclaration - - protected override - void - BuildRenderTree
MarkupElement - (0:0,0 [66] x:\dir\subdir\Test\TestComponent.cshtml) - input MarkupElement - (0:0,0 [65] x:\dir\subdir\Test\TestComponent.cshtml) - input
HtmlAttribute - - type=" - " HtmlAttribute - - type=" - "
HtmlAttributeValue - (13:0,13 [4] x:\dir\subdir\Test\TestComponent.cshtml) - HtmlAttributeValue - (13:0,13 [4] x:\dir\subdir\Test\TestComponent.cshtml) -
IntermediateToken - (13:0,13 [4] x:\dir\subdir\Test\TestComponent.cshtml) - Html - text IntermediateToken - (13:0,13 [4] x:\dir\subdir\Test\TestComponent.cshtml) - Html - text
@ -21,5 +21,5 @@ Document -
HtmlAttribute - (25:0,25 [12] x:\dir\subdir\Test\TestComponent.cshtml) - onchange=" - " HtmlAttribute - (25:0,25 [12] x:\dir\subdir\Test\TestComponent.cshtml) - onchange=" - "
CSharpExpressionAttributeValue - - CSharpExpressionAttributeValue - -
IntermediateToken - - CSharp - Microsoft.AspNetCore.Components.EventCallback.Factory.CreateBinder(this, __value => CurrentDate = __value, CurrentDate, "MM/dd/yyyy") IntermediateToken - - CSharp - Microsoft.AspNetCore.Components.EventCallback.Factory.CreateBinder(this, __value => CurrentDate = __value, CurrentDate, "MM/dd/yyyy")
CSharpCode - (75:1,7 [77] x:\dir\subdir\Test\TestComponent.cshtml) CSharpCode - (74:1,7 [77] x:\dir\subdir\Test\TestComponent.cshtml)
IntermediateToken - (75:1,7 [77] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - \n public DateTime CurrentDate { get; set; } = new DateTime(2018, 1, 1);\n IntermediateToken - (74:1,7 [77] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - \n public DateTime CurrentDate { get; set; } = new DateTime(2018, 1, 1);\n

View File

@ -1,4 +1,4 @@
Source Location: (75:1,7 [77] x:\dir\subdir\Test\TestComponent.cshtml) Source Location: (74:1,7 [77] x:\dir\subdir\Test\TestComponent.cshtml)
| |
public DateTime CurrentDate { get; set; } = new DateTime(2018, 1, 1); public DateTime CurrentDate { get; set; } = new DateTime(2018, 1, 1);
| |

View File

@ -46,7 +46,7 @@ namespace Microsoft.CodeAnalysis.Razor
// //
// We handle a few different cases here: // We handle a few different cases here:
// //
// 1. When given an attribute like **anywhere**'bind-value-onchange="@FirstName"' we will // 1. When given an attribute like **anywhere**'bind-value="@FirstName"' and 'bind-value:event="onchange"' we will
// generate the 'value' attribute and 'onchange' attribute. // generate the 'value' attribute and 'onchange' attribute.
// //
// We don't do any transformation or inference for this case, because the developer has // We don't do any transformation or inference for this case, because the developer has
@ -143,15 +143,35 @@ namespace Microsoft.CodeAnalysis.Razor
{ {
attribute.Documentation = ComponentResources.BindTagHelper_Fallback_Documentation; attribute.Documentation = ComponentResources.BindTagHelper_Fallback_Documentation;
attribute.Name = "bind-..."; var attributeName = "bind-...";
attribute.Name = attributeName;
attribute.AsDictionary("bind-", typeof(object).FullName); attribute.AsDictionary("bind-", typeof(object).FullName);
// WTE has a bug 15.7p1 where a Tag Helper without a display-name that looks like // WTE has a bug 15.7p1 where a Tag Helper without a display-name that looks like
// a C# property will crash trying to create the toolips. // a C# property will crash trying to create the toolips.
attribute.SetPropertyName("Bind"); attribute.SetPropertyName("Bind");
attribute.TypeName = "System.Collections.Generic.Dictionary<string, object>"; attribute.TypeName = "System.Collections.Generic.Dictionary<string, object>";
attribute.BindAttributeParameter(parameter =>
{
parameter.Name = "format";
parameter.TypeName = typeof(string).FullName;
parameter.Documentation = ComponentResources.BindTagHelper_Fallback_Format_Documentation;
parameter.SetPropertyName("Format");
});
attribute.BindAttributeParameter(parameter =>
{
parameter.Name = "event";
parameter.TypeName = typeof(string).FullName;
parameter.Documentation = string.Format(ComponentResources.BindTagHelper_Fallback_Event_Documentation, attributeName);
parameter.SetPropertyName("Event");
});
}); });
// This is no longer supported. This is just here so we can add a diagnostic later on when this matches.
builder.BindAttribute(attribute => builder.BindAttribute(attribute =>
{ {
attribute.Documentation = ComponentResources.BindTagHelper_Fallback_Format_Documentation; attribute.Documentation = ComponentResources.BindTagHelper_Fallback_Format_Documentation;
@ -237,6 +257,7 @@ namespace Microsoft.CodeAnalysis.Razor
return results; return results;
} }
private List<TagHelperDescriptor> CreateElementBindTagHelpers(List<ElementBindData> data) private List<TagHelperDescriptor> CreateElementBindTagHelpers(List<ElementBindData> data)
{ {
var results = new List<TagHelperDescriptor>(); var results = new List<TagHelperDescriptor>();
@ -251,6 +272,8 @@ namespace Microsoft.CodeAnalysis.Razor
var formatName = entry.Suffix == null ? "Format_" + entry.ValueAttribute : "Format_" + entry.Suffix; var formatName = entry.Suffix == null ? "Format_" + entry.ValueAttribute : "Format_" + entry.Suffix;
var formatAttributeName = entry.Suffix == null ? "format-" + entry.ValueAttribute : "format-" + entry.Suffix; var formatAttributeName = entry.Suffix == null ? "format-" + entry.ValueAttribute : "format-" + entry.Suffix;
var eventName = entry.Suffix == null ? "Event_" + entry.ValueAttribute : "Event_" + entry.Suffix;
var builder = TagHelperDescriptorBuilder.Create(ComponentMetadata.Bind.TagHelperKind, name, ComponentsApi.AssemblyName); var builder = TagHelperDescriptorBuilder.Create(ComponentMetadata.Bind.TagHelperKind, name, ComponentsApi.AssemblyName);
builder.Documentation = string.Format( builder.Documentation = string.Format(
ComponentResources.BindTagHelper_Element_Documentation, ComponentResources.BindTagHelper_Element_Documentation,
@ -315,14 +338,32 @@ namespace Microsoft.CodeAnalysis.Razor
// WTE has a bug 15.7p1 where a Tag Helper without a display-name that looks like // WTE has a bug 15.7p1 where a Tag Helper without a display-name that looks like
// a C# property will crash trying to create the toolips. // a C# property will crash trying to create the toolips.
a.SetPropertyName(name); a.SetPropertyName(name);
a.BindAttributeParameter(parameter =>
{
parameter.Name = "format";
parameter.TypeName = typeof(string).FullName;
parameter.Documentation = string.Format(ComponentResources.BindTagHelper_Element_Format_Documentation, attributeName);
parameter.SetPropertyName(formatName);
});
a.BindAttributeParameter(parameter =>
{
parameter.Name = "event";
parameter.TypeName = typeof(string).FullName;
parameter.Documentation = string.Format(ComponentResources.BindTagHelper_Element_Event_Documentation, attributeName);
parameter.SetPropertyName(eventName);
});
}); });
// This is no longer supported. This is just here so we can add a diagnostic later on when this matches.
builder.BindAttribute(attribute => builder.BindAttribute(attribute =>
{ {
attribute.Documentation = string.Format(ComponentResources.BindTagHelper_Element_Format_Documentation, attributeName);
attribute.Name = formatAttributeName; attribute.Name = formatAttributeName;
attribute.TypeName = "System.String"; attribute.TypeName = "System.String";
attribute.Documentation = string.Format(ComponentResources.BindTagHelper_Element_Format_Documentation, attributeName);
// WTE has a bug 15.7p1 where a Tag Helper without a display-name that looks like // WTE has a bug 15.7p1 where a Tag Helper without a display-name that looks like
// a C# property will crash trying to create the toolips. // a C# property will crash trying to create the toolips.
@ -376,7 +417,6 @@ namespace Microsoft.CodeAnalysis.Razor
if (tagHelper.BoundAttributes[j].Name == valueAttributeName) if (tagHelper.BoundAttributes[j].Name == valueAttributeName)
{ {
valueAttribute = tagHelper.BoundAttributes[j]; valueAttribute = tagHelper.BoundAttributes[j];
} }
if (tagHelper.BoundAttributes[j].Name == expressionAttributeName) if (tagHelper.BoundAttributes[j].Name == expressionAttributeName)

View File

@ -395,33 +395,28 @@ namespace Test
Assert.False(attribute.IsBooleanProperty); Assert.False(attribute.IsBooleanProperty);
Assert.False(attribute.IsEnum); Assert.False(attribute.IsEnum);
attribute = Assert.Single(bind.BoundAttributes, a => a.Name.StartsWith("format")); var parameter = Assert.Single(attribute.BoundAttributeParameters, a => a.Name.Equals("format"));
// Invariants // Invariants
Assert.Empty(attribute.Diagnostics); Assert.Empty(parameter.Diagnostics);
Assert.False(attribute.HasErrors); Assert.False(parameter.HasErrors);
Assert.Equal(ComponentMetadata.Bind.TagHelperKind, attribute.Kind); Assert.Equal(ComponentMetadata.Bind.TagHelperKind, parameter.Kind);
Assert.False(attribute.IsDefaultKind()); Assert.False(parameter.IsDefaultKind());
Assert.False(attribute.HasIndexer);
Assert.Null(attribute.IndexerNamePrefix);
Assert.Null(attribute.IndexerTypeName);
Assert.False(attribute.IsIndexerBooleanProperty);
Assert.False(attribute.IsIndexerStringProperty);
Assert.Equal( Assert.Equal(
"Specifies a format to convert the value specified by the 'bind' attribute. " + "Specifies a format to convert the value specified by the 'bind' attribute. " +
"The format string can currently only be used with expressions of type <code>DateTime</code>.", "The format string can currently only be used with expressions of type <code>DateTime</code>.",
attribute.Documentation); parameter.Documentation);
Assert.Equal("format-myprop", attribute.Name); Assert.Equal("format", parameter.Name);
Assert.Equal("Format_myprop", attribute.GetPropertyName()); Assert.Equal("Format_myprop", parameter.GetPropertyName());
Assert.Equal("string Test.BindAttributes.Format_myprop", attribute.DisplayName); Assert.Equal(":format", parameter.DisplayName);
// Defined from the property type // Defined from the property type
Assert.Equal("System.String", attribute.TypeName); Assert.Equal("System.String", parameter.TypeName);
Assert.True(attribute.IsStringProperty); Assert.True(parameter.IsStringProperty);
Assert.False(attribute.IsBooleanProperty); Assert.False(parameter.IsBooleanProperty);
Assert.False(attribute.IsEnum); Assert.False(parameter.IsEnum);
} }
[Fact] [Fact]
@ -529,10 +524,10 @@ namespace Test
Assert.Equal("Bind", attribute.GetPropertyName()); Assert.Equal("Bind", attribute.GetPropertyName());
Assert.Equal("object Test.BindAttributes.Bind", attribute.DisplayName); Assert.Equal("object Test.BindAttributes.Bind", attribute.DisplayName);
attribute = Assert.Single(bind.BoundAttributes, a => a.Name.StartsWith("format")); var parameter = Assert.Single(attribute.BoundAttributeParameters, a => a.Name.Equals("format"));
Assert.Equal("format-myprop", attribute.Name); Assert.Equal("format", parameter.Name);
Assert.Equal("Format_myprop", attribute.GetPropertyName()); Assert.Equal("Format_myprop", parameter.GetPropertyName());
Assert.Equal("string Test.BindAttributes.Format_myprop", attribute.DisplayName); Assert.Equal(":format", parameter.DisplayName);
} }
[Fact] [Fact]
@ -597,10 +592,10 @@ namespace Test
Assert.Equal("Bind", attribute.GetPropertyName()); Assert.Equal("Bind", attribute.GetPropertyName());
Assert.Equal("object Test.BindAttributes.Bind", attribute.DisplayName); Assert.Equal("object Test.BindAttributes.Bind", attribute.DisplayName);
attribute = Assert.Single(bind.BoundAttributes, a => a.Name.StartsWith("format")); var parameter = Assert.Single(attribute.BoundAttributeParameters, a => a.Name.Equals("format"));
Assert.Equal("format-myprop", attribute.Name); Assert.Equal("format", parameter.Name);
Assert.Equal("Format_myprop", attribute.GetPropertyName()); Assert.Equal("Format_myprop", parameter.GetPropertyName());
Assert.Equal("string Test.BindAttributes.Format_myprop", attribute.DisplayName); Assert.Equal(":format", parameter.DisplayName);
} }
[Fact] [Fact]
@ -665,10 +660,10 @@ namespace Test
Assert.Equal("Bind_somevalue", attribute.GetPropertyName()); Assert.Equal("Bind_somevalue", attribute.GetPropertyName());
Assert.Equal("object Test.BindAttributes.Bind_somevalue", attribute.DisplayName); Assert.Equal("object Test.BindAttributes.Bind_somevalue", attribute.DisplayName);
attribute = Assert.Single(bind.BoundAttributes, a => a.Name.StartsWith("format")); var parameter = Assert.Single(attribute.BoundAttributeParameters, a => a.Name.Equals("format"));
Assert.Equal("format-somevalue", attribute.Name); Assert.Equal("format", parameter.Name);
Assert.Equal("Format_somevalue", attribute.GetPropertyName()); Assert.Equal("Format_somevalue", parameter.GetPropertyName());
Assert.Equal("string Test.BindAttributes.Format_somevalue", attribute.DisplayName); Assert.Equal(":format", parameter.DisplayName);
} }
[Fact] [Fact]
@ -710,7 +705,7 @@ namespace Test
Assert.Equal( Assert.Equal(
"Binds the provided expression to an attribute and a change event, based on the naming of " + "Binds the provided expression to an attribute and a change event, based on the naming of " +
"the bind attribute. For example: <code>bind-value-onchange=\"...\"</code> will assign the " + "the bind attribute. For example: <code>bind-value=\"...\"</code> and <code>bind-value:event=\"onchange\"</code> will assign the " +
"current value of the expression to the 'value' attribute, and assign a delegate that attempts " + "current value of the expression to the 'value' attribute, and assign a delegate that attempts " +
"to set the value to the 'onchange' attribute.", "to set the value to the 'onchange' attribute.",
bind.Documentation); bind.Documentation);
@ -753,7 +748,7 @@ namespace Test
Assert.Equal( Assert.Equal(
"Binds the provided expression to an attribute and a change event, based on the naming of " + "Binds the provided expression to an attribute and a change event, based on the naming of " +
"the bind attribute. For example: <code>bind-value-onchange=\"...\"</code> will assign the " + "the bind attribute. For example: <code>bind-value=\"...\"</code> and <code>bind-value:event=\"onchange\"</code> will assign the " +
"current value of the expression to the 'value' attribute, and assign a delegate that attempts " + "current value of the expression to the 'value' attribute, and assign a delegate that attempts " +
"to set the value to the 'onchange' attribute.", "to set the value to the 'onchange' attribute.",
attribute.Documentation); attribute.Documentation);
@ -770,37 +765,30 @@ namespace Test
Assert.False(attribute.IsBooleanProperty); Assert.False(attribute.IsBooleanProperty);
Assert.False(attribute.IsEnum); Assert.False(attribute.IsEnum);
attribute = Assert.Single(bind.BoundAttributes, a => a.Name.StartsWith("format")); var parameter = Assert.Single(attribute.BoundAttributeParameters, a => a.Name.Equals("format"));
// Invariants // Invariants
Assert.Empty(attribute.Diagnostics); Assert.Empty(parameter.Diagnostics);
Assert.False(attribute.HasErrors); Assert.False(parameter.HasErrors);
Assert.Equal(ComponentMetadata.Bind.TagHelperKind, attribute.Kind); Assert.Equal(ComponentMetadata.Bind.TagHelperKind, parameter.Kind);
Assert.False(attribute.IsDefaultKind()); Assert.False(parameter.IsDefaultKind());
Assert.True(attribute.HasIndexer);
Assert.Equal("format-", attribute.IndexerNamePrefix);
Assert.Equal("System.String", attribute.IndexerTypeName);
Assert.False(attribute.IsIndexerBooleanProperty);
Assert.True(attribute.IsIndexerStringProperty);
Assert.Equal( Assert.Equal(
"Specifies a format to convert the value specified by the corresponding bind attribute. " + "Specifies a format to convert the value specified by the corresponding bind attribute. " +
"For example: <code>format-value=\"...\"</code> will apply a format string to the value " + "For example: <code>bind-value:format=\"...\"</code> will apply a format string to the value " +
"specified in <code>bind-value-...</code>. The format string can currently only be used with " + "specified in <code>bind-value=\"...\"</code>. The format string can currently only be used with " +
"expressions of type <code>DateTime</code>.", "expressions of type <code>DateTime</code>.",
attribute.Documentation); parameter.Documentation);
Assert.Equal("format-...", attribute.Name); Assert.Equal("format", parameter.Name);
Assert.Equal("Format", attribute.GetPropertyName()); Assert.Equal("Format", parameter.GetPropertyName());
Assert.Equal( Assert.Equal(":format", parameter.DisplayName);
"System.Collections.Generic.Dictionary<string, string> Microsoft.AspNetCore.Components.Bind.Format",
attribute.DisplayName);
// Defined from the property type // Defined from the property type
Assert.Equal("System.Collections.Generic.Dictionary<string, string>", attribute.TypeName); Assert.Equal("System.String", parameter.TypeName);
Assert.False(attribute.IsStringProperty); Assert.True(parameter.IsStringProperty);
Assert.False(attribute.IsBooleanProperty); Assert.False(parameter.IsBooleanProperty);
Assert.False(attribute.IsEnum); Assert.False(parameter.IsEnum);
} }

View File

@ -128,6 +128,11 @@ namespace Microsoft.AspNetCore.Razor.Language.IntegrationTests
WriteContentNode(node, node.AttributeName, string.Format("HtmlAttributeValueStyle.{0}", node.AttributeStructure)); WriteContentNode(node, node.AttributeName, string.Format("HtmlAttributeValueStyle.{0}", node.AttributeStructure));
} }
public override void VisitTagHelperAttributeParameter(TagHelperAttributeParameterIntermediateNode node)
{
WriteContentNode(node, node.AttributeName, string.Format("HtmlAttributeValueStyle.{0}", node.AttributeStructure));
}
public override void VisitComponent(ComponentIntermediateNode node) public override void VisitComponent(ComponentIntermediateNode node)
{ {
WriteContentNode(node, node.TagName); WriteContentNode(node, node.TagName);