-
Notifications
You must be signed in to change notification settings - Fork 4.2k
Filter IntelliSense attribute suggestions by AttributeTargets #81157
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 2 commits
3a09a5d
c780868
a95e595
51cfa91
bd67526
8fc6527
0a0d8ce
78d6908
13d6ec2
286d305
01e2862
df40d59
f463828
dc37782
3424a47
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -447,6 +447,124 @@ await VerifyExpectedItemsAsync(code, [ | |
| ]); | ||
| } | ||
|
|
||
| [Fact] | ||
| public async Task AttributeTargetFiltering_AssemblyAttribute() | ||
| { | ||
| var code = """ | ||
| [assembly: $$] | ||
|
|
||
| namespace TestNamespace | ||
| { | ||
| class Program | ||
| { | ||
| static void Main(string[] args) | ||
| { | ||
| } | ||
| } | ||
| } | ||
|
|
||
| [System.AttributeUsage(System.AttributeTargets.Assembly)] | ||
| public class AssemblyOnlyAttribute : System.Attribute | ||
| { | ||
| } | ||
|
|
||
| [System.AttributeUsage(System.AttributeTargets.Class)] | ||
| public class ClassOnlyAttribute : System.Attribute | ||
| { | ||
| } | ||
| """; | ||
|
|
||
| await VerifyExpectedItemsAsync(code, [ | ||
| ItemExpectation.Exists("AssemblyOnly"), | ||
| ItemExpectation.Absent("ClassOnly") | ||
| ]); | ||
| } | ||
|
|
||
| [Fact] | ||
| public async Task AttributeTargetFiltering_ClassAttribute() | ||
| { | ||
| var code = """ | ||
| [$$] | ||
| class TestClass | ||
| { | ||
| } | ||
|
|
||
| [System.AttributeUsage(System.AttributeTargets.Assembly)] | ||
| public class AssemblyOnlyAttribute : System.Attribute | ||
| { | ||
| } | ||
|
|
||
| [System.AttributeUsage(System.AttributeTargets.Class)] | ||
| public class ClassOnlyAttribute : System.Attribute | ||
| { | ||
| } | ||
| """; | ||
|
|
||
| await VerifyExpectedItemsAsync(code, [ | ||
| ItemExpectation.Absent("AssemblyOnly"), | ||
| ItemExpectation.Exists("ClassOnly") | ||
| ]); | ||
| } | ||
|
|
||
| [Fact] | ||
| public async Task AttributeTargetFiltering_MethodAttribute() | ||
| { | ||
| var code = """ | ||
| class TestClass | ||
| { | ||
| [$$] | ||
| void TestMethod() | ||
| { | ||
| } | ||
| } | ||
|
|
||
| [System.AttributeUsage(System.AttributeTargets.Method)] | ||
| public class MethodOnlyAttribute : System.Attribute | ||
| { | ||
| } | ||
|
|
||
| [System.AttributeUsage(System.AttributeTargets.Property)] | ||
| public class PropertyOnlyAttribute : System.Attribute | ||
| { | ||
| } | ||
| """; | ||
|
|
||
| await VerifyExpectedItemsAsync(code, [ | ||
| ItemExpectation.Exists("MethodOnly"), | ||
| ItemExpectation.Absent("PropertyOnly") | ||
| ]); | ||
| } | ||
|
|
||
| [Fact] | ||
| public async Task AttributeTargetFiltering_ExplicitTargetSpecifier() | ||
| { | ||
| var code = """ | ||
| class TestClass | ||
| { | ||
| [return: $$] | ||
| int TestMethod() | ||
| { | ||
| return 0; | ||
| } | ||
| } | ||
|
|
||
| [System.AttributeUsage(System.AttributeTargets.ReturnValue)] | ||
| public class ReturnValueOnlyAttribute : System.Attribute | ||
| { | ||
| } | ||
|
|
||
| [System.AttributeUsage(System.AttributeTargets.Method)] | ||
| public class MethodOnlyAttribute : System.Attribute | ||
| { | ||
| } | ||
| """; | ||
|
|
||
| await VerifyExpectedItemsAsync(code, [ | ||
| ItemExpectation.Exists("ReturnValueOnly"), | ||
| ItemExpectation.Absent("MethodOnly") | ||
| ]); | ||
| } | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @copilot have tests for all the possible nodes that you compute different AttributeTargets for. Also, have tests for all the possible target identifiers.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added comprehensive tests for all syntax node types (struct, interface, enum, delegate, record class, record struct, constructor, property, field, event, parameter, type parameter, indexer) and all target specifiers (assembly, module, type, method, field, property, event, param, return, typevar). Total of 22 new tests added. (commit bd67526) |
||
|
|
||
| [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/7213")] | ||
| public Task NamespaceName_EmptyNameSpan_TopLevel() | ||
| => VerifyItemExistsAsync(@"namespace $$ { }", "System", sourceCodeKind: SourceCodeKind.Regular); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -536,6 +536,10 @@ public static bool IsStaticType([NotNullWhen(true)] this ISymbol? symbol) | |
|
|
||
| public static bool IsOrContainsAccessibleAttribute( | ||
| [NotNullWhen(true)] this ISymbol? symbol, ISymbol withinType, IAssemblySymbol withinAssembly, CancellationToken cancellationToken) | ||
| => IsOrContainsAccessibleAttribute(symbol, withinType, withinAssembly, AttributeTargets.All, cancellationToken); | ||
|
|
||
| public static bool IsOrContainsAccessibleAttribute( | ||
| [NotNullWhen(true)] this ISymbol? symbol, ISymbol withinType, IAssemblySymbol withinAssembly, AttributeTargets validTargets, CancellationToken cancellationToken) | ||
| { | ||
| var namespaceOrType = symbol is IAliasSymbol alias ? alias.Target : symbol as INamespaceOrTypeSymbol; | ||
| if (namespaceOrType == null) | ||
|
|
@@ -548,13 +552,49 @@ public static bool IsOrContainsAccessibleAttribute( | |
| { | ||
| if (type.IsAttribute() && type.IsAccessibleWithin(withinType ?? withinAssembly)) | ||
| { | ||
| // Check if the attribute is valid for the specified targets | ||
| if (validTargets != AttributeTargets.All && !IsAttributeValidForTargets(type, validTargets)) | ||
| { | ||
| continue; | ||
| } | ||
|
|
||
| return true; | ||
| } | ||
| } | ||
|
|
||
| return false; | ||
| } | ||
|
|
||
| private static bool IsAttributeValidForTargets(INamedTypeSymbol attributeType, AttributeTargets validTargets) | ||
| { | ||
| // Get the AttributeUsageAttribute applied to this attribute type | ||
| var attributeUsageAttribute = attributeType.GetAttributes() | ||
| .FirstOrDefault(attr => attr.AttributeClass?.Name == "AttributeUsageAttribute" && | ||
| attr.AttributeClass.ContainingNamespace?.Name == "System" && | ||
| attr.AttributeClass.ContainingNamespace.ContainingNamespace?.IsGlobalNamespace == true); | ||
|
||
|
|
||
| if (attributeUsageAttribute == null) | ||
| { | ||
| // If no AttributeUsage is specified, the default is AttributeTargets.All | ||
| return true; | ||
| } | ||
|
|
||
| // The first constructor argument is the AttributeTargets value | ||
| if (attributeUsageAttribute.ConstructorArguments.Length > 0) | ||
| { | ||
| var constructorArg = attributeUsageAttribute.ConstructorArguments[0]; | ||
| if (constructorArg.Value is int targetsValue) | ||
|
||
| { | ||
| var attributeTargets = (AttributeTargets)targetsValue; | ||
| // Check if there's any overlap between the attribute's targets and the valid targets | ||
| return (attributeTargets & validTargets) != 0; | ||
| } | ||
| } | ||
|
|
||
| // Default to allowing the attribute if we can't determine the targets | ||
| return true; | ||
| } | ||
|
|
||
| public static IEnumerable<IPropertySymbol> GetValidAnonymousTypeProperties(this ISymbol symbol) | ||
| { | ||
| Contract.ThrowIfFalse(symbol.IsNormalAnonymousType()); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,6 +2,7 @@ | |
| // The .NET Foundation licenses this file to you under the MIT license. | ||
| // See the LICENSE file in the project root for more information. | ||
|
|
||
| using System; | ||
| using System.Collections.Generic; | ||
| using System.Diagnostics.CodeAnalysis; | ||
| using System.Threading; | ||
|
|
@@ -177,6 +178,8 @@ | |
| this.PrecedingModifiers = precedingModifiers; | ||
| } | ||
|
|
||
| public override AttributeTargets ValidAttributeTargets => ComputeValidAttributeTargets(); | ||
|
||
|
|
||
| public static CSharpSyntaxContext CreateContext(Document document, SemanticModel semanticModel, int position, CancellationToken cancellationToken) | ||
| => CreateContextWorker(document, semanticModel, position, cancellationToken); | ||
|
|
||
|
|
@@ -512,4 +515,75 @@ | |
|
|
||
| return false; | ||
| } | ||
|
|
||
| private AttributeTargets ComputeValidAttributeTargets() | ||
| { | ||
| // If we're not in an attribute context, return All to allow all attributes | ||
| if (!IsAttributeNameContext) | ||
| return AttributeTargets.All; | ||
|
|
||
| // Find the attribute list that contains the current position | ||
| var token = TargetToken; | ||
| var attributeList = token.Parent?.FirstAncestorOrSelf<AttributeListSyntax>(); | ||
| if (attributeList == null) | ||
| return AttributeTargets.All; | ||
|
|
||
| // Check if there's an explicit target specifier (e.g., "assembly:", "return:", etc.) | ||
| if (attributeList.Target != null) | ||
| { | ||
| var targetIdentifier = attributeList.Target.Identifier.ValueText; | ||
| return targetIdentifier switch | ||
| { | ||
| "assembly" or "module" => AttributeTargets.Assembly | AttributeTargets.Module, | ||
| "type" => AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum | AttributeTargets.Interface | AttributeTargets.Delegate, | ||
| "method" => AttributeTargets.Method, | ||
| "field" => AttributeTargets.Field, | ||
| "property" => AttributeTargets.Property, | ||
| "event" => AttributeTargets.Event, | ||
| "param" => AttributeTargets.Parameter, | ||
| "return" => AttributeTargets.ReturnValue, | ||
| "typevar" => AttributeTargets.GenericParameter, | ||
| _ => AttributeTargets.All | ||
|
||
| }; | ||
| } | ||
|
|
||
| // No explicit target, determine from context | ||
| // Walk up to find what the attribute is attached to | ||
| var parentNode = attributeList.Parent; | ||
| if (parentNode == null) | ||
| return AttributeTargets.All; | ||
|
|
||
| return parentNode switch | ||
| { | ||
| // Type declarations | ||
| ClassDeclarationSyntax => AttributeTargets.Class, | ||
| StructDeclarationSyntax => AttributeTargets.Struct, | ||
| InterfaceDeclarationSyntax => AttributeTargets.Interface, | ||
| EnumDeclarationSyntax => AttributeTargets.Enum, | ||
| DelegateDeclarationSyntax => AttributeTargets.Delegate, | ||
| RecordDeclarationSyntax record => record.ClassOrStructKeyword.IsKind(SyntaxKind.StructKeyword) | ||
|
Check failure on line 564 in src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/ContextQuery/CSharpSyntaxContext.cs
|
||
| ? AttributeTargets.Struct | ||
|
Check failure on line 565 in src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/ContextQuery/CSharpSyntaxContext.cs
|
||
CyrusNajmabadi marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| : AttributeTargets.Class, | ||
|
|
||
| // Member declarations | ||
| MethodDeclarationSyntax => AttributeTargets.Method, | ||
| ConstructorDeclarationSyntax => AttributeTargets.Constructor, | ||
| PropertyDeclarationSyntax => AttributeTargets.Property, | ||
| EventDeclarationSyntax => AttributeTargets.Event, | ||
| EventFieldDeclarationSyntax => AttributeTargets.Event, | ||
| FieldDeclarationSyntax => AttributeTargets.Field, | ||
| IndexerDeclarationSyntax => AttributeTargets.Property, | ||
|
|
||
| // Parameters | ||
| ParameterSyntax => AttributeTargets.Parameter, | ||
|
|
||
| // Type parameters | ||
| TypeParameterSyntax => AttributeTargets.GenericParameter, | ||
|
|
||
| // Assembly/module level | ||
| CompilationUnitSyntax => AttributeTargets.Assembly | AttributeTargets.Module, | ||
|
|
||
| _ => AttributeTargets.All | ||
| }; | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@copilot use
[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/7640")]on all tests you addThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added
[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/7640")]to all 22 attribute filtering tests. (commit bd67526)