Add attribute only bindings and fix dotnet/aspnetcore-tooling#6373

Adds a new API for WTE to call given a TagHelperBinding to determine if
the binding should colorize/classify only the attributes of the HTML
element in source code. This is driven by a new metadata item that the
Components 'directive attributes' all set. There's no way for a user to
access this feature via tag helpers currently, but it could be added
easily in the future.

Also fixing dotnet/aspnetcore-tooling#6376 while I'm in there. 👍
\n\nCommit migrated from df449beea9
This commit is contained in:
Ryan Nowak 2019-01-14 08:24:20 -08:00
parent 148bc99cce
commit 40633dde21
10 changed files with 72 additions and 11 deletions

View File

@ -40,7 +40,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
foreach (var descriptor in bindingResult.Descriptors) foreach (var descriptor in bindingResult.Descriptors)
{ {
var boundRules = bindingResult.GetBoundRules(descriptor); var boundRules = bindingResult.Mappings[descriptor];
var nonDefaultRule = boundRules.FirstOrDefault(rule => rule.TagStructure != TagStructure.Unspecified); var nonDefaultRule = boundRules.FirstOrDefault(rule => rule.TagStructure != TagStructure.Unspecified);
if (nonDefaultRule?.TagStructure == TagStructure.WithoutEndTag) if (nonDefaultRule?.TagStructure == TagStructure.WithoutEndTag)

View File

@ -336,7 +336,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
foreach (var descriptor in tagHelperBinding.Descriptors) foreach (var descriptor in tagHelperBinding.Descriptors)
{ {
var boundRules = tagHelperBinding.GetBoundRules(descriptor); var boundRules = tagHelperBinding.Mappings[descriptor];
var invalidRule = boundRules.FirstOrDefault(rule => rule.TagStructure == TagStructure.WithoutEndTag); var invalidRule = boundRules.FirstOrDefault(rule => rule.TagStructure == TagStructure.WithoutEndTag);
if (invalidRule != null) if (invalidRule != null)
@ -456,7 +456,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
TagStructure? baseStructure = null; TagStructure? baseStructure = null;
foreach (var descriptor in bindingResult.Descriptors) foreach (var descriptor in bindingResult.Descriptors)
{ {
var boundRules = bindingResult.GetBoundRules(descriptor); var boundRules = bindingResult.Mappings[descriptor];
foreach (var rule in boundRules) foreach (var rule in boundRules)
{ {
if (rule.TagStructure != TagStructure.Unspecified) if (rule.TagStructure != TagStructure.Unspecified)

View File

@ -1,14 +1,13 @@
// 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
{ {
public sealed class TagHelperBinding public sealed class TagHelperBinding
{ {
private IReadOnlyDictionary<TagHelperDescriptor, IReadOnlyList<TagMatchingRuleDescriptor>> _mappings;
internal TagHelperBinding( internal TagHelperBinding(
string tagName, string tagName,
IReadOnlyList<KeyValuePair<string, string>> attributes, IReadOnlyList<KeyValuePair<string, string>> attributes,
@ -19,12 +18,42 @@ namespace Microsoft.AspNetCore.Razor.Language
TagName = tagName; TagName = tagName;
Attributes = attributes; Attributes = attributes;
ParentTagName = parentTagName; ParentTagName = parentTagName;
Mappings = mappings;
TagHelperPrefix = tagHelperPrefix; TagHelperPrefix = tagHelperPrefix;
_mappings = mappings;
} }
public IEnumerable<TagHelperDescriptor> Descriptors => _mappings.Keys; public IEnumerable<TagHelperDescriptor> Descriptors => Mappings.Keys;
/// <summary>
/// Gets a value that indicates whether the the binding matched on attributes only.
/// </summary>
/// <returns><c>false</c> if the entire element should be classified as a tag helper.</returns>
/// <remarks>
/// If this returns <c>true</c>, use <c>TagHelperFactsService.GetBoundTagHelperAttributes</c> to find the
/// set of attributes that should be considered part of the match.
/// </remarks>
public bool IsAttributeMatch
{
get
{
foreach (var descriptor in Mappings.Keys)
{
if (!descriptor.Metadata.TryGetValue(TagHelperMetadata.Common.ClassifyAttributesOnly, out var value) ||
!string.Equals(value, bool.TrueString, StringComparison.OrdinalIgnoreCase))
{
return false;
}
}
// All the matching tag helpers want to be classified with **attributes only**.
//
// Ex: (components)
//
// <button onclick="..." />
return true;
}
}
public string TagName { get; } public string TagName { get; }
@ -32,11 +61,18 @@ namespace Microsoft.AspNetCore.Razor.Language
public IReadOnlyList<KeyValuePair<string, string>> Attributes { get; } public IReadOnlyList<KeyValuePair<string, string>> Attributes { get; }
public IReadOnlyDictionary<TagHelperDescriptor, IReadOnlyList<TagMatchingRuleDescriptor>> Mappings { get; }
public string TagHelperPrefix { get; } public string TagHelperPrefix { get; }
public IReadOnlyList<TagMatchingRuleDescriptor> GetBoundRules(TagHelperDescriptor descriptor) public IReadOnlyList<TagMatchingRuleDescriptor> GetBoundRules(TagHelperDescriptor descriptor)
{ {
return _mappings[descriptor]; if (descriptor == null)
{
throw new ArgumentNullException(nameof(descriptor));
}
return Mappings[descriptor];
} }
} }
} }

View File

@ -10,6 +10,8 @@ namespace Microsoft.AspNetCore.Razor.Language
public static readonly string PropertyName = "Common.PropertyName"; public static readonly string PropertyName = "Common.PropertyName";
public static readonly string TypeName = "Common.TypeName"; public static readonly string TypeName = "Common.TypeName";
public static readonly string ClassifyAttributesOnly = "Common.ClassifyAttributesOnly";
} }
public static class Runtime public static class Runtime

View File

@ -121,6 +121,7 @@ namespace Microsoft.CodeAnalysis.Razor
builder.Documentation = ComponentResources.BindTagHelper_Fallback_Documentation; builder.Documentation = ComponentResources.BindTagHelper_Fallback_Documentation;
builder.Metadata.Add(BlazorMetadata.SpecialKindKey, BlazorMetadata.Bind.TagHelperKind); builder.Metadata.Add(BlazorMetadata.SpecialKindKey, BlazorMetadata.Bind.TagHelperKind);
builder.Metadata.Add(TagHelperMetadata.Common.ClassifyAttributesOnly, bool.TrueString);
builder.Metadata[TagHelperMetadata.Runtime.Name] = BlazorMetadata.Bind.RuntimeName; builder.Metadata[TagHelperMetadata.Runtime.Name] = BlazorMetadata.Bind.RuntimeName;
builder.Metadata[BlazorMetadata.Bind.FallbackKey] = bool.TrueString; builder.Metadata[BlazorMetadata.Bind.FallbackKey] = bool.TrueString;
@ -257,6 +258,7 @@ namespace Microsoft.CodeAnalysis.Razor
entry.ChangeAttribute); entry.ChangeAttribute);
builder.Metadata.Add(BlazorMetadata.SpecialKindKey, BlazorMetadata.Bind.TagHelperKind); builder.Metadata.Add(BlazorMetadata.SpecialKindKey, BlazorMetadata.Bind.TagHelperKind);
builder.Metadata.Add(TagHelperMetadata.Common.ClassifyAttributesOnly, bool.TrueString);
builder.Metadata[TagHelperMetadata.Runtime.Name] = BlazorMetadata.Bind.RuntimeName; builder.Metadata[TagHelperMetadata.Runtime.Name] = BlazorMetadata.Bind.RuntimeName;
builder.Metadata[BlazorMetadata.Bind.ValueAttribute] = entry.ValueAttribute; builder.Metadata[BlazorMetadata.Bind.ValueAttribute] = entry.ValueAttribute;
builder.Metadata[BlazorMetadata.Bind.ChangeAttribute] = entry.ChangeAttribute; builder.Metadata[BlazorMetadata.Bind.ChangeAttribute] = entry.ChangeAttribute;

View File

@ -35,7 +35,6 @@ namespace Microsoft.CodeAnalysis.Razor
return; return;
} }
var eventHandlerData = GetEventHandlerData(compilation); var eventHandlerData = GetEventHandlerData(compilation);
foreach (var tagHelper in CreateEventHandlerTagHelpers(eventHandlerData)) foreach (var tagHelper in CreateEventHandlerTagHelpers(eventHandlerData))
@ -112,6 +111,7 @@ namespace Microsoft.CodeAnalysis.Razor
builder.Metadata.Add(BlazorMetadata.SpecialKindKey, BlazorMetadata.EventHandler.TagHelperKind); builder.Metadata.Add(BlazorMetadata.SpecialKindKey, BlazorMetadata.EventHandler.TagHelperKind);
builder.Metadata.Add(BlazorMetadata.EventHandler.EventArgsType, entry.EventArgsType.ToDisplayString()); builder.Metadata.Add(BlazorMetadata.EventHandler.EventArgsType, entry.EventArgsType.ToDisplayString());
builder.Metadata.Add(TagHelperMetadata.Common.ClassifyAttributesOnly, bool.TrueString);
builder.Metadata[TagHelperMetadata.Runtime.Name] = BlazorMetadata.EventHandler.RuntimeName; builder.Metadata[TagHelperMetadata.Runtime.Name] = BlazorMetadata.EventHandler.RuntimeName;
// WTE has a bug in 15.7p1 where a Tag Helper without a display-name that looks like // WTE has a bug in 15.7p1 where a Tag Helper without a display-name that looks like

View File

@ -21,6 +21,20 @@ namespace Microsoft.CodeAnalysis.Razor
throw new ArgumentNullException(nameof(context)); throw new ArgumentNullException(nameof(context));
} }
var compilation = context.GetCompilation();
if (compilation == null)
{
return;
}
var elementRef = compilation.GetTypeByMetadataName(ComponentsApi.ElementRef.FullTypeName);
if (elementRef == null)
{
// If we can't find ElementRef, then just bail. We won't be able to compile the
// generated code anyway.
return;
}
context.Results.Add(CreateRefTagHelper()); context.Results.Add(CreateRefTagHelper());
} }
@ -30,6 +44,7 @@ namespace Microsoft.CodeAnalysis.Razor
builder.Documentation = ComponentResources.RefTagHelper_Documentation; builder.Documentation = ComponentResources.RefTagHelper_Documentation;
builder.Metadata.Add(BlazorMetadata.SpecialKindKey, BlazorMetadata.Ref.TagHelperKind); builder.Metadata.Add(BlazorMetadata.SpecialKindKey, BlazorMetadata.Ref.TagHelperKind);
builder.Metadata.Add(TagHelperMetadata.Common.ClassifyAttributesOnly, bool.TrueString);
builder.Metadata[TagHelperMetadata.Runtime.Name] = BlazorMetadata.Ref.RuntimeName; builder.Metadata[TagHelperMetadata.Runtime.Name] = BlazorMetadata.Ref.RuntimeName;
// WTE has a bug in 15.7p1 where a Tag Helper without a display-name that looks like // WTE has a bug in 15.7p1 where a Tag Helper without a display-name that looks like

View File

@ -207,6 +207,7 @@ namespace Test
Assert.Empty(bind.Diagnostics); Assert.Empty(bind.Diagnostics);
Assert.False(bind.HasErrors); Assert.False(bind.HasErrors);
Assert.Equal(BlazorMetadata.Bind.TagHelperKind, bind.Kind); Assert.Equal(BlazorMetadata.Bind.TagHelperKind, bind.Kind);
Assert.Equal(bool.TrueString, bind.Metadata[TagHelperMetadata.Common.ClassifyAttributesOnly]);
Assert.Equal(BlazorMetadata.Bind.RuntimeName, bind.Metadata[TagHelperMetadata.Runtime.Name]); Assert.Equal(BlazorMetadata.Bind.RuntimeName, bind.Metadata[TagHelperMetadata.Runtime.Name]);
Assert.False(bind.IsDefaultKind()); Assert.False(bind.IsDefaultKind());
Assert.False(bind.KindUsesDefaultTagHelperRuntime()); Assert.False(bind.KindUsesDefaultTagHelperRuntime());
@ -571,6 +572,7 @@ namespace Test
Assert.Empty(bind.Diagnostics); Assert.Empty(bind.Diagnostics);
Assert.False(bind.HasErrors); Assert.False(bind.HasErrors);
Assert.Equal(BlazorMetadata.Bind.TagHelperKind, bind.Kind); Assert.Equal(BlazorMetadata.Bind.TagHelperKind, bind.Kind);
Assert.Equal(bool.TrueString, bind.Metadata[TagHelperMetadata.Common.ClassifyAttributesOnly]);
Assert.Equal(BlazorMetadata.Bind.RuntimeName, bind.Metadata[TagHelperMetadata.Runtime.Name]); Assert.Equal(BlazorMetadata.Bind.RuntimeName, bind.Metadata[TagHelperMetadata.Runtime.Name]);
Assert.False(bind.IsDefaultKind()); Assert.False(bind.IsDefaultKind());
Assert.False(bind.KindUsesDefaultTagHelperRuntime()); Assert.False(bind.KindUsesDefaultTagHelperRuntime());

View File

@ -4,10 +4,10 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Microsoft.AspNetCore.Razor.Language; using Microsoft.AspNetCore.Razor.Language;
using Microsoft.CodeAnalysis.Razor; using Microsoft.AspNetCore.Razor.Language.Components;
using Xunit; using Xunit;
namespace Microsoft.AspNetCore.Razor.Language.Components namespace Microsoft.CodeAnalysis.Razor
{ {
public class EventHandlerTagHelperDescriptorProviderTest : BaseTagHelperDescriptorProviderTest public class EventHandlerTagHelperDescriptorProviderTest : BaseTagHelperDescriptorProviderTest
{ {
@ -52,6 +52,7 @@ namespace Test
Assert.Empty(item.Diagnostics); Assert.Empty(item.Diagnostics);
Assert.False(item.HasErrors); Assert.False(item.HasErrors);
Assert.Equal(BlazorMetadata.EventHandler.TagHelperKind, item.Kind); Assert.Equal(BlazorMetadata.EventHandler.TagHelperKind, item.Kind);
Assert.Equal(bool.TrueString, item.Metadata[TagHelperMetadata.Common.ClassifyAttributesOnly]);
Assert.Equal(BlazorMetadata.EventHandler.RuntimeName, item.Metadata[TagHelperMetadata.Runtime.Name]); Assert.Equal(BlazorMetadata.EventHandler.RuntimeName, item.Metadata[TagHelperMetadata.Runtime.Name]);
Assert.False(item.IsDefaultKind()); Assert.False(item.IsDefaultKind());
Assert.False(item.KindUsesDefaultTagHelperRuntime()); Assert.False(item.KindUsesDefaultTagHelperRuntime());

View File

@ -15,6 +15,8 @@ namespace Microsoft.CodeAnalysis.Razor
{ {
// Arrange // Arrange
var context = TagHelperDescriptorProviderContext.Create(); var context = TagHelperDescriptorProviderContext.Create();
context.SetCompilation(BaseCompilation);
var provider = new RefTagHelperDescriptorProvider(); var provider = new RefTagHelperDescriptorProvider();
// Act // Act
@ -29,6 +31,7 @@ namespace Microsoft.CodeAnalysis.Razor
Assert.Empty(item.Diagnostics); Assert.Empty(item.Diagnostics);
Assert.False(item.HasErrors); Assert.False(item.HasErrors);
Assert.Equal(BlazorMetadata.Ref.TagHelperKind, item.Kind); Assert.Equal(BlazorMetadata.Ref.TagHelperKind, item.Kind);
Assert.Equal(bool.TrueString, item.Metadata[TagHelperMetadata.Common.ClassifyAttributesOnly]);
Assert.Equal(BlazorMetadata.Ref.RuntimeName, item.Metadata[TagHelperMetadata.Runtime.Name]); Assert.Equal(BlazorMetadata.Ref.RuntimeName, item.Metadata[TagHelperMetadata.Runtime.Name]);
Assert.False(item.IsDefaultKind()); Assert.False(item.IsDefaultKind());
Assert.False(item.KindUsesDefaultTagHelperRuntime()); Assert.False(item.KindUsesDefaultTagHelperRuntime());