Skip to content

Commit 84e1af7

Browse files
authored
Merge pull request #25622 from Neme12/attributeCompletion
Completion for attributes: searching for attributes recursively
2 parents 250804a + 46b4fb9 commit 84e1af7

File tree

6 files changed

+270
-42
lines changed

6 files changed

+270
-42
lines changed

src/EditorFeatures/CSharpTest/Completion/CompletionProviders/SymbolCompletionProviderTests.cs

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2901,6 +2901,100 @@ class Program { }";
29012901
await VerifyItemIsAbsentAsync(markup, "@namespace", sourceCodeKind: SourceCodeKind.Regular);
29022902
}
29032903

2904+
[WorkItem(25589, "https://github.com/dotnet/roslyn/issues/25589")]
2905+
[Fact, Trait(Traits.Feature, Traits.Features.Completion)]
2906+
public async Task AttributeSearch_NamespaceWithNestedAttribute1()
2907+
{
2908+
var markup = @"
2909+
namespace Namespace1
2910+
{
2911+
namespace Namespace2 { class NonAttribute { } }
2912+
namespace Namespace3.Namespace4 { class CustomAttribute : System.Attribute { } }
2913+
}
2914+
2915+
[$$]";
2916+
await VerifyItemExistsAsync(markup, "Namespace1");
2917+
}
2918+
2919+
[Fact, Trait(Traits.Feature, Traits.Features.Completion)]
2920+
public async Task AttributeSearch_NamespaceWithNestedAttribute2()
2921+
{
2922+
var markup = @"
2923+
namespace Namespace1
2924+
{
2925+
namespace Namespace2 { class NonAttribute { } }
2926+
namespace Namespace3.Namespace4 { class CustomAttribute : System.Attribute { } }
2927+
}
2928+
2929+
[Namespace1.$$]";
2930+
await VerifyItemIsAbsentAsync(markup, "Namespace2");
2931+
await VerifyItemExistsAsync(markup, "Namespace3");
2932+
}
2933+
2934+
[Fact, Trait(Traits.Feature, Traits.Features.Completion)]
2935+
public async Task AttributeSearch_NamespaceWithNestedAttribute3()
2936+
{
2937+
var markup = @"
2938+
namespace Namespace1
2939+
{
2940+
namespace Namespace2 { class NonAttribute { } }
2941+
namespace Namespace3.Namespace4 { class CustomAttribute : System.Attribute { } }
2942+
}
2943+
2944+
[Namespace1.Namespace3.$$]";
2945+
await VerifyItemExistsAsync(markup, "Namespace4");
2946+
}
2947+
2948+
[Fact, Trait(Traits.Feature, Traits.Features.Completion)]
2949+
public async Task AttributeSearch_NamespaceWithNestedAttribute4()
2950+
{
2951+
var markup = @"
2952+
namespace Namespace1
2953+
{
2954+
namespace Namespace2 { class NonAttribute { } }
2955+
namespace Namespace3.Namespace4 { class CustomAttribute : System.Attribute { } }
2956+
}
2957+
2958+
[Namespace1.Namespace3.Namespace4.$$]";
2959+
await VerifyItemExistsAsync(markup, "Custom");
2960+
}
2961+
2962+
[Fact, Trait(Traits.Feature, Traits.Features.Completion)]
2963+
public async Task AttributeSearch_NamespaceWithNestedAttribute_NamespaceAlias()
2964+
{
2965+
var markup = @"
2966+
using Namespace1Alias = Namespace1;
2967+
using Namespace2Alias = Namespace1.Namespace2;
2968+
using Namespace3Alias = Namespace1.Namespace3;
2969+
using Namespace4Alias = Namespace1.Namespace3.Namespace4;
2970+
2971+
namespace Namespace1
2972+
{
2973+
namespace Namespace2 { class NonAttribute { } }
2974+
namespace Namespace3.Namespace4 { class CustomAttribute : System.Attribute { } }
2975+
}
2976+
2977+
[$$]";
2978+
await VerifyItemExistsAsync(markup, "Namespace1Alias");
2979+
await VerifyItemIsAbsentAsync(markup, "Namespace2Alias");
2980+
await VerifyItemExistsAsync(markup, "Namespace3Alias");
2981+
await VerifyItemExistsAsync(markup, "Namespace4Alias");
2982+
}
2983+
2984+
[Fact, Trait(Traits.Feature, Traits.Features.Completion)]
2985+
public async Task AttributeSearch_NamespaceWithoutNestedAttribute()
2986+
{
2987+
var markup = @"
2988+
namespace Namespace1
2989+
{
2990+
namespace Namespace2 { class NonAttribute { } }
2991+
namespace Namespace3.Namespace4 { class NonAttribute : System.NonAttribute { } }
2992+
}
2993+
2994+
[$$]";
2995+
await VerifyItemIsAbsentAsync(markup, "Namespace1");
2996+
}
2997+
29042998
[WorkItem(542230, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/542230")]
29052999
[Fact, Trait(Traits.Feature, Traits.Features.Completion)]
29063000
public async Task RangeVariableInQuerySelect()

src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/SymbolCompletionProviderTests.vb

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1854,6 +1854,142 @@ End Class
18541854
Await VerifyItemIsAbsentAsync(markup, "C2")
18551855
End Function
18561856

1857+
<WorkItem(25589, "https://github.com/dotnet/roslyn/issues/25589")>
1858+
<Fact, Trait(Traits.Feature, Traits.Features.Completion)>
1859+
Public Async Function AttributeSearch_NamespaceWithNestedAttribute1() As Task
1860+
Dim markup = <Text><![CDATA[
1861+
Namespace Namespace1
1862+
Namespace Namespace2
1863+
Class NonAttribute
1864+
End Class
1865+
End Namespace
1866+
Namespace Namespace3.Namespace4
1867+
Class CustomAttribute
1868+
Inherits System.Attribute
1869+
End Class
1870+
End Namespace
1871+
End Namespace
1872+
1873+
<$$>
1874+
]]></Text>.Value
1875+
1876+
Await VerifyItemExistsAsync(markup, "Namespace1")
1877+
End Function
1878+
1879+
<Fact, Trait(Traits.Feature, Traits.Features.Completion)>
1880+
Public Async Function AttributeSearch_NamespaceWithNestedAttribute2() As Task
1881+
Dim markup = <Text><![CDATA[
1882+
Namespace Namespace1
1883+
Namespace Namespace2
1884+
Class NonAttribute
1885+
End Class
1886+
End Namespace
1887+
Namespace Namespace3.Namespace4
1888+
Class CustomAttribute
1889+
Inherits System.Attribute
1890+
End Class
1891+
End Namespace
1892+
End Namespace
1893+
1894+
<Namespace1.$$>
1895+
]]></Text>.Value
1896+
1897+
Await VerifyItemIsAbsentAsync(markup, "Namespace2")
1898+
Await VerifyItemExistsAsync(markup, "Namespace3")
1899+
End Function
1900+
1901+
<Fact, Trait(Traits.Feature, Traits.Features.Completion)>
1902+
Public Async Function AttributeSearch_NamespaceWithNestedAttribute3() As Task
1903+
Dim markup = <Text><![CDATA[
1904+
Namespace Namespace1
1905+
Namespace Namespace2
1906+
Class NonAttribute
1907+
End Class
1908+
End Namespace
1909+
Namespace Namespace3.Namespace4
1910+
Class CustomAttribute
1911+
Inherits System.Attribute
1912+
End Class
1913+
End Namespace
1914+
End Namespace
1915+
1916+
<Namespace1.Namespace3.$$>
1917+
]]></Text>.Value
1918+
1919+
Await VerifyItemExistsAsync(markup, "Namespace4")
1920+
End Function
1921+
1922+
<Fact, Trait(Traits.Feature, Traits.Features.Completion)>
1923+
Public Async Function AttributeSearch_NamespaceWithNestedAttribute4() As Task
1924+
Dim markup = <Text><![CDATA[
1925+
Namespace Namespace1
1926+
Namespace Namespace2
1927+
Class NonAttribute
1928+
End Class
1929+
End Namespace
1930+
Namespace Namespace3.Namespace4
1931+
Class CustomAttribute
1932+
Inherits System.Attribute
1933+
End Class
1934+
End Namespace
1935+
End Namespace
1936+
1937+
<Namespace1.Namespace3.Namespace4.$$>
1938+
]]></Text>.Value
1939+
1940+
Await VerifyItemExistsAsync(markup, "Custom")
1941+
End Function
1942+
1943+
<Fact, Trait(Traits.Feature, Traits.Features.Completion)>
1944+
Public Async Function AttributeSearch_NamespaceWithNestedAttribute_NamespaceAlias() As Task
1945+
Dim markup = <Text><![CDATA[
1946+
Imports Namespace1Alias = Namespace1
1947+
Imports Namespace2Alias = Namespace1.Namespace2
1948+
Imports Namespace3Alias = Namespace1.Namespace3
1949+
Imports Namespace4Alias = Namespace1.Namespace3.Namespace4
1950+
1951+
Namespace Namespace1
1952+
Namespace Namespace2
1953+
Class NonAttribute
1954+
End Class
1955+
End Namespace
1956+
Namespace Namespace3.Namespace4
1957+
Class CustomAttribute
1958+
Inherits System.Attribute
1959+
End Class
1960+
End Namespace
1961+
End Namespace
1962+
1963+
<$$>
1964+
]]></Text>.Value
1965+
1966+
Await VerifyItemExistsAsync(markup, "Namespace1Alias")
1967+
Await VerifyItemIsAbsentAsync(markup, "Namespace2Alias")
1968+
Await VerifyItemExistsAsync(markup, "Namespace3Alias")
1969+
Await VerifyItemExistsAsync(markup, "Namespace4Alias")
1970+
End Function
1971+
1972+
<Fact, Trait(Traits.Feature, Traits.Features.Completion)>
1973+
Public Async Function AttributeSearch_NamespaceWithoutNestedAttribute() As Task
1974+
Dim markup = <Text><![CDATA[
1975+
Namespace Namespace1
1976+
Namespace Namespace2
1977+
Class NonAttribute
1978+
End Class
1979+
End Namespace
1980+
Namespace Namespace3.Namespace4
1981+
Class NonAttribute
1982+
Inherits System.NonAttribute
1983+
End Class
1984+
End Namespace
1985+
End Namespace
1986+
1987+
<$$>
1988+
]]></Text>.Value
1989+
1990+
Await VerifyItemIsAbsentAsync(markup, "Namespace1")
1991+
End Function
1992+
18571993
<WorkItem(542737, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/542737")>
18581994
<Fact, Trait(Traits.Feature, Traits.Features.Completion)>
18591995
Public Async Function TestQueryVariableAfterSelectClause() As Task

src/Workspaces/Core/Portable/Recommendations/AbstractRecommendationService.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -151,8 +151,10 @@ internal bool ShouldIncludeSymbol(ISymbol symbol)
151151

152152
if (_context.IsAttributeNameContext)
153153
{
154-
var enclosingSymbol = _context.SemanticModel.GetEnclosingNamedType(_context.LeftToken.SpanStart, _cancellationToken);
155-
return symbol.IsOrContainsAccessibleAttribute(enclosingSymbol, _context.SemanticModel.Compilation.Assembly);
154+
return symbol.IsOrContainsAccessibleAttribute(
155+
_context.SemanticModel.GetEnclosingNamedType(_context.LeftToken.SpanStart, _cancellationToken),
156+
_context.SemanticModel.Compilation.Assembly,
157+
_cancellationToken);
156158
}
157159

158160
if (_context.IsEnumTypeMemberAccessContext)

src/Workspaces/Core/Portable/Shared/Extensions/INamespaceOrTypeSymbolExtensions.cs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System.Collections.Generic;
55
using System.Linq;
66
using System.Runtime.CompilerServices;
7+
using System.Threading;
78
using Roslyn.Utilities;
89

910
namespace Microsoft.CodeAnalysis.Shared.Extensions
@@ -82,5 +83,33 @@ private static void GetNameParts(INamespaceOrTypeSymbol namespaceOrTypeSymbol, L
8283
GetNameParts(namespaceOrTypeSymbol.ContainingNamespace, result);
8384
result.Add(namespaceOrTypeSymbol.Name);
8485
}
86+
87+
/// <summary>
88+
/// Lazily returns all nested types contained (recursively) within this namespace or type.
89+
/// In case of a type, it is included itself as the first result.
90+
/// </summary>
91+
public static IEnumerable<INamedTypeSymbol> GetAllTypes(
92+
this INamespaceOrTypeSymbol namespaceOrTypeSymbol,
93+
CancellationToken cancellationToken)
94+
{
95+
var stack = new Stack<INamespaceOrTypeSymbol>();
96+
stack.Push(namespaceOrTypeSymbol);
97+
98+
while (stack.Count > 0)
99+
{
100+
cancellationToken.ThrowIfCancellationRequested();
101+
var current = stack.Pop();
102+
if (current is INamespaceSymbol currentNs)
103+
{
104+
stack.Push(currentNs.GetMembers());
105+
}
106+
else
107+
{
108+
var namedType = (INamedTypeSymbol)current;
109+
stack.Push(namedType.GetTypeMembers());
110+
yield return namedType;
111+
}
112+
}
113+
}
85114
}
86115
}

src/Workspaces/Core/Portable/Shared/Extensions/INamespaceSymbolExtensions.cs

Lines changed: 1 addition & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -54,30 +54,6 @@ public static int CompareTo(this INamespaceSymbol n1, INamespaceSymbol n2)
5454
return names1.Count - names2.Count;
5555
}
5656

57-
public static IEnumerable<INamedTypeSymbol> GetAllTypes(
58-
this INamespaceSymbol namespaceSymbol,
59-
CancellationToken cancellationToken)
60-
{
61-
var stack = new Stack<INamespaceOrTypeSymbol>();
62-
stack.Push(namespaceSymbol);
63-
64-
while (stack.Count > 0)
65-
{
66-
cancellationToken.ThrowIfCancellationRequested();
67-
var current = stack.Pop();
68-
if (current is INamespaceSymbol currentNs)
69-
{
70-
stack.Push(currentNs.GetMembers());
71-
}
72-
else
73-
{
74-
var namedType = (INamedTypeSymbol)current;
75-
stack.Push(namedType.GetTypeMembers());
76-
yield return namedType;
77-
}
78-
}
79-
}
80-
8157
public static IEnumerable<INamespaceOrTypeSymbol> GetAllNamespacesAndTypes(
8258
this INamespaceSymbol namespaceSymbol,
8359
CancellationToken cancellationToken)
@@ -107,7 +83,7 @@ public static IEnumerable<INamedTypeSymbol> GetAllTypes(
10783
this IEnumerable<INamespaceSymbol> namespaceSymbols,
10884
CancellationToken cancellationToken)
10985
{
110-
return namespaceSymbols.SelectMany(n => GetAllTypes(n, cancellationToken));
86+
return namespaceSymbols.SelectMany(n => n.GetAllTypes(cancellationToken));
11187
}
11288

11389
/// <summary>

src/Workspaces/Core/Portable/Shared/Extensions/ISymbolExtensions.cs

Lines changed: 6 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -530,28 +530,19 @@ public static bool IsNamespace(this ISymbol symbol)
530530
return symbol?.Kind == SymbolKind.Namespace;
531531
}
532532

533-
public static bool IsOrContainsAccessibleAttribute(this ISymbol symbol, ISymbol withinType, IAssemblySymbol withinAssembly)
533+
public static bool IsOrContainsAccessibleAttribute(
534+
this ISymbol symbol, ISymbol withinType, IAssemblySymbol withinAssembly, CancellationToken cancellationToken)
534535
{
535-
if (symbol is IAliasSymbol alias)
536-
{
537-
symbol = alias.Target;
538-
}
539-
540-
var namespaceOrType = symbol as INamespaceOrTypeSymbol;
536+
var namespaceOrType = symbol is IAliasSymbol alias ? alias.Target : symbol as INamespaceOrTypeSymbol;
541537
if (namespaceOrType == null)
542538
{
543539
return false;
544540
}
545541

546-
if (namespaceOrType.IsAttribute() && namespaceOrType.IsAccessibleWithin(withinType ?? withinAssembly))
547-
{
548-
return true;
549-
}
550-
551-
// PERF: Avoid allocating a lambda capture as this method is recursive
552-
foreach (var namedType in namespaceOrType.GetTypeMembers())
542+
// PERF: Avoid allocating a lambda capture
543+
foreach (var type in namespaceOrType.GetAllTypes(cancellationToken))
553544
{
554-
if (namedType.IsOrContainsAccessibleAttribute(withinType, withinAssembly))
545+
if (type.IsAttribute() && type.IsAccessibleWithin(withinType ?? withinAssembly))
555546
{
556547
return true;
557548
}

0 commit comments

Comments
 (0)