Update TagHelper completion to understand dictionary attributes.

- Since dictionaries fill `TagHelperDescriptor`s with two ways to bind to a `TagHelper` (prefix or full match) we need to take into account their indexer name prefix when providing attribute completions.
- Added tests to ensure that bound and unbound scenarios work as expected.

#7759
This commit is contained in:
N. Taylor Mullen 2019-02-20 12:59:58 -08:00
Родитель 4c8e63e610
Коммит e3d89c262e
2 изменённых файлов: 108 добавлений и 3 удалений

Просмотреть файл

@ -88,11 +88,25 @@ namespace Microsoft.VisualStudio.Editor.Razor
foreach (var attributeDescriptor in descriptor.BoundAttributes) foreach (var attributeDescriptor in descriptor.BoundAttributes)
{ {
UpdateCompletions(attributeDescriptor.Name, attributeDescriptor); UpdateCompletions(attributeDescriptor.Name, attributeDescriptor);
if (!string.IsNullOrEmpty(attributeDescriptor.IndexerNamePrefix))
{
UpdateCompletions(attributeDescriptor.IndexerNamePrefix + "...", attributeDescriptor);
}
} }
} }
else else
{ {
var htmlNameToBoundAttribute = descriptor.BoundAttributes.ToDictionary(attribute => attribute.Name, StringComparer.OrdinalIgnoreCase); var htmlNameToBoundAttribute = new Dictionary<string, BoundAttributeDescriptor>(StringComparer.OrdinalIgnoreCase);
foreach (var attributeDescriptor in descriptor.BoundAttributes)
{
htmlNameToBoundAttribute[attributeDescriptor.Name] = attributeDescriptor;
if (!string.IsNullOrEmpty(attributeDescriptor.IndexerNamePrefix))
{
htmlNameToBoundAttribute[attributeDescriptor.IndexerNamePrefix] = attributeDescriptor;
}
}
foreach (var rule in descriptor.TagMatchingRules) foreach (var rule in descriptor.TagMatchingRules)
{ {
@ -100,11 +114,11 @@ namespace Microsoft.VisualStudio.Editor.Razor
{ {
if (htmlNameToBoundAttribute.TryGetValue(requiredAttribute.Name, out var attributeDescriptor)) if (htmlNameToBoundAttribute.TryGetValue(requiredAttribute.Name, out var attributeDescriptor))
{ {
UpdateCompletions(requiredAttribute.Name, attributeDescriptor); UpdateCompletions(requiredAttribute.DisplayName, attributeDescriptor);
} }
else else
{ {
UpdateCompletions(requiredAttribute.Name, possibleDescriptor: null); UpdateCompletions(requiredAttribute.DisplayName, possibleDescriptor: null);
} }
} }
} }

Просмотреть файл

@ -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;
using System.Linq; using System.Linq;
using Microsoft.AspNetCore.Razor.Language; using Microsoft.AspNetCore.Razor.Language;
@ -11,6 +12,96 @@ namespace Microsoft.VisualStudio.Editor.Razor
{ {
public class DefaultTagHelperCompletionServiceTest public class DefaultTagHelperCompletionServiceTest
{ {
[Fact]
public void GetAttributeCompletions_BoundDictionaryAttribute_ReturnsPrefixIndexerAndFullSetter()
{
// Arrange
var documentDescriptors = new[]
{
TagHelperDescriptorBuilder.Create("FormTagHelper", "TestAssembly")
.TagMatchingRuleDescriptor(rule => rule
.RequireTagName("form"))
.BoundAttributeDescriptor(attribute => attribute
.Name("asp-all-route-data")
.TypeName("System.Collections.Generic.IDictionary<System.String, System.String>")
.PropertyName("RouteValues").AsDictionary("asp-route-", typeof(string).FullName))
.Build(),
};
var expectedCompletions = AttributeCompletionResult.Create(new Dictionary<string, HashSet<BoundAttributeDescriptor>>()
{
["asp-all-route-data"] = new HashSet<BoundAttributeDescriptor>()
{
documentDescriptors[0].BoundAttributes.Last(),
},
["asp-route-..."] = new HashSet<BoundAttributeDescriptor>()
{
documentDescriptors[0].BoundAttributes.Last(),
}
});
var completionContext = BuildAttributeCompletionContext(
documentDescriptors,
Array.Empty<string>(),
attributes: new Dictionary<string, string>(),
currentTagName: "form");
var service = CreateTagHelperCompletionFactsService();
// Act
var completions = service.GetAttributeCompletions(completionContext);
// Assert
AssertCompletionsAreEquivalent(expectedCompletions, completions);
}
[Fact]
public void GetAttributeCompletions_RequiredBoundDictionaryAttribute_ReturnsPrefixIndexerAndFullSetter()
{
// Arrange
var documentDescriptors = new[]
{
TagHelperDescriptorBuilder.Create("FormTagHelper", "TestAssembly")
.TagMatchingRuleDescriptor(rule => rule
.RequireTagName("form")
.RequireAttributeDescriptor(builder =>
{
builder.Name = "asp-route-";
builder.NameComparisonMode = RequiredAttributeDescriptor.NameComparisonMode.PrefixMatch;
}))
.TagMatchingRuleDescriptor(rule => rule
.RequireTagName("form")
.RequireAttributeDescriptor(builder => builder.Name = "asp-all-route-data"))
.BoundAttributeDescriptor(attribute => attribute
.Name("asp-all-route-data")
.TypeName("System.Collections.Generic.IDictionary<System.String, System.String>")
.PropertyName("RouteValues").AsDictionary("asp-route-", typeof(string).FullName))
.Build(),
};
var expectedCompletions = AttributeCompletionResult.Create(new Dictionary<string, HashSet<BoundAttributeDescriptor>>()
{
["asp-all-route-data"] = new HashSet<BoundAttributeDescriptor>()
{
documentDescriptors[0].BoundAttributes.Last(),
},
["asp-route-..."] = new HashSet<BoundAttributeDescriptor>()
{
documentDescriptors[0].BoundAttributes.Last(),
}
});
var completionContext = BuildAttributeCompletionContext(
documentDescriptors,
Array.Empty<string>(),
attributes: new Dictionary<string, string>(),
currentTagName: "form");
var service = CreateTagHelperCompletionFactsService();
// Act
var completions = service.GetAttributeCompletions(completionContext);
// Assert
AssertCompletionsAreEquivalent(expectedCompletions, completions);
}
[Fact] [Fact]
public void GetAttributeCompletions_DoesNotReturnCompletionsForAlreadySuppliedAttributes() public void GetAttributeCompletions_DoesNotReturnCompletionsForAlreadySuppliedAttributes()
{ {