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)
{
var boundRules = bindingResult.GetBoundRules(descriptor);
var boundRules = bindingResult.Mappings[descriptor];
var nonDefaultRule = boundRules.FirstOrDefault(rule => rule.TagStructure != TagStructure.Unspecified);
if (nonDefaultRule?.TagStructure == TagStructure.WithoutEndTag)

View File

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

View File

@ -1,14 +1,13 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
namespace Microsoft.AspNetCore.Razor.Language
{
public sealed class TagHelperBinding
{
private IReadOnlyDictionary<TagHelperDescriptor, IReadOnlyList<TagMatchingRuleDescriptor>> _mappings;
internal TagHelperBinding(
string tagName,
IReadOnlyList<KeyValuePair<string, string>> attributes,
@ -19,12 +18,42 @@ namespace Microsoft.AspNetCore.Razor.Language
TagName = tagName;
Attributes = attributes;
ParentTagName = parentTagName;
Mappings = mappings;
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; }
@ -32,11 +61,18 @@ namespace Microsoft.AspNetCore.Razor.Language
public IReadOnlyList<KeyValuePair<string, string>> Attributes { get; }
public IReadOnlyDictionary<TagHelperDescriptor, IReadOnlyList<TagMatchingRuleDescriptor>> Mappings { get; }
public string TagHelperPrefix { get; }
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 TypeName = "Common.TypeName";
public static readonly string ClassifyAttributesOnly = "Common.ClassifyAttributesOnly";
}
public static class Runtime

View File

@ -121,6 +121,7 @@ namespace Microsoft.CodeAnalysis.Razor
builder.Documentation = ComponentResources.BindTagHelper_Fallback_Documentation;
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[BlazorMetadata.Bind.FallbackKey] = bool.TrueString;
@ -257,6 +258,7 @@ namespace Microsoft.CodeAnalysis.Razor
entry.ChangeAttribute);
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[BlazorMetadata.Bind.ValueAttribute] = entry.ValueAttribute;
builder.Metadata[BlazorMetadata.Bind.ChangeAttribute] = entry.ChangeAttribute;

View File

@ -35,7 +35,6 @@ namespace Microsoft.CodeAnalysis.Razor
return;
}
var eventHandlerData = GetEventHandlerData(compilation);
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.EventHandler.EventArgsType, entry.EventArgsType.ToDisplayString());
builder.Metadata.Add(TagHelperMetadata.Common.ClassifyAttributesOnly, bool.TrueString);
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

View File

@ -21,6 +21,20 @@ namespace Microsoft.CodeAnalysis.Razor
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());
}
@ -30,6 +44,7 @@ namespace Microsoft.CodeAnalysis.Razor
builder.Documentation = ComponentResources.RefTagHelper_Documentation;
builder.Metadata.Add(BlazorMetadata.SpecialKindKey, BlazorMetadata.Ref.TagHelperKind);
builder.Metadata.Add(TagHelperMetadata.Common.ClassifyAttributesOnly, bool.TrueString);
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

View File

@ -207,6 +207,7 @@ namespace Test
Assert.Empty(bind.Diagnostics);
Assert.False(bind.HasErrors);
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.False(bind.IsDefaultKind());
Assert.False(bind.KindUsesDefaultTagHelperRuntime());
@ -571,6 +572,7 @@ namespace Test
Assert.Empty(bind.Diagnostics);
Assert.False(bind.HasErrors);
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.False(bind.IsDefaultKind());
Assert.False(bind.KindUsesDefaultTagHelperRuntime());

View File

@ -4,10 +4,10 @@
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.CodeAnalysis.Razor;
using Microsoft.AspNetCore.Razor.Language.Components;
using Xunit;
namespace Microsoft.AspNetCore.Razor.Language.Components
namespace Microsoft.CodeAnalysis.Razor
{
public class EventHandlerTagHelperDescriptorProviderTest : BaseTagHelperDescriptorProviderTest
{
@ -52,6 +52,7 @@ namespace Test
Assert.Empty(item.Diagnostics);
Assert.False(item.HasErrors);
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.False(item.IsDefaultKind());
Assert.False(item.KindUsesDefaultTagHelperRuntime());

View File

@ -15,6 +15,8 @@ namespace Microsoft.CodeAnalysis.Razor
{
// Arrange
var context = TagHelperDescriptorProviderContext.Create();
context.SetCompilation(BaseCompilation);
var provider = new RefTagHelperDescriptorProvider();
// Act
@ -29,6 +31,7 @@ namespace Microsoft.CodeAnalysis.Razor
Assert.Empty(item.Diagnostics);
Assert.False(item.HasErrors);
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.False(item.IsDefaultKind());
Assert.False(item.KindUsesDefaultTagHelperRuntime());