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)
{
UpdateCompletions(attributeDescriptor.Name, attributeDescriptor);
if (!string.IsNullOrEmpty(attributeDescriptor.IndexerNamePrefix))
{
UpdateCompletions(attributeDescriptor.IndexerNamePrefix + "...", attributeDescriptor);
}
}
}
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)
{
@ -100,11 +114,11 @@ namespace Microsoft.VisualStudio.Editor.Razor
{
if (htmlNameToBoundAttribute.TryGetValue(requiredAttribute.Name, out var attributeDescriptor))
{
UpdateCompletions(requiredAttribute.Name, attributeDescriptor);
UpdateCompletions(requiredAttribute.DisplayName, attributeDescriptor);
}
else
{
UpdateCompletions(requiredAttribute.Name, possibleDescriptor: null);
UpdateCompletions(requiredAttribute.DisplayName, possibleDescriptor: null);
}
}
}

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

@ -1,6 +1,7 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Razor.Language;
@ -11,6 +12,96 @@ namespace Microsoft.VisualStudio.Editor.Razor
{
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]
public void GetAttributeCompletions_DoesNotReturnCompletionsForAlreadySuppliedAttributes()
{