Summary
SymbolExtensions.HasAttribute was introduced alongside EPC42 (DataContractSerializableMemberAnalyzer):
public static bool HasAttribute(this ISymbol symbol, INamedTypeSymbol? attributeType)
Several analyzers predate it and open-code the same attribute lookup. This issue tracks migrating them so there is a single way to ask "is this symbol marked with attribute X?".
Purely a cleanup — no behavior change intended, and there are a few traps that make a blind find/replace wrong (see below).
Straightforward call sites
These are plain boolean checks against a resolved attribute type and map directly onto HasAttribute:
EventSourceAnalysis/EventSourceAnalyzer.cs:278 — attributes.Any(a => a.AttributeClass?.IsClrType(compilation, typeof(NonEventAttribute)) == true)
EventSourceAnalysis/EventSourceAnalyzer.cs:334 — same NonEventAttribute check, negated
Note both currently resolve the attribute type through CompilationExtensions.IsClrType (which does a GetTypeByFullName per call), whereas HasAttribute takes an already-resolved INamedTypeSymbol. Resolving the well-known type once in a RegisterCompilationStartAction and passing it down is the better shape and avoids the repeated lookup.
Call sites that need a companion helper first
HasAttribute returns bool, but these need the AttributeData itself, so they cannot migrate until we add something like TryGetAttribute / GetAttributeOrDefault returning AttributeData?:
ExcludeFromCodeCoverageOnPartialClassAnalyzer.cs:32-44 — needs attribute.ApplicationSyntaxReference to compute the diagnostic location.
EventSourceAnalysis/EventSourceAnalyzer.cs:284-285 — needs the EventAttribute instance to read its arguments.
StructSizeCalculator.cs:68-75 — needs structLayoutAttribute.NamedArguments to read Size.
Proposal:
public static AttributeData? TryGetAttribute(this ISymbol symbol, INamedTypeSymbol? attributeType);
and implement HasAttribute on top of it, or keep them independent to preserve the allocation-free foreach in HasAttribute (it runs per-symbol).
Call sites that must NOT be migrated as-is
These match attributes by name rather than by symbol identity, which is deliberate — the attribute type is not referenced by the analyzer and may come from anywhere:
CoreAnalyzers/MustUseResultAnalyzer.cs:91-95 — matches "MustUseResultAttribute" and JetBrains' "MustUseReturnValueAttribute" by name, in addition to the symbol-based check for our own MustUseResultAttribute. Swapping in HasAttribute would silently drop JetBrains annotation support.
AsyncAnalyzers/ConfigureAwaitConfiguration.cs:12,17 — matches assembly-level attributes with Name.StartsWith("DoNotUseConfigureAwait") / "UseConfigureAwaitFalse"; users declare these attributes in their own assemblies, so there is no symbol to compare against.
StructSizeCalculator.cs:69 — matches nameof(StructLayoutAttribute) by name.
If we want these covered too, it needs a separate name-based overload, e.g. HasAttributeWithName(this ISymbol symbol, string name), and it should be introduced consciously rather than as part of a mechanical sweep.
Suggested scope
- Add
TryGetAttribute (returning AttributeData?) next to HasAttribute in SymbolExtensions.
- Migrate the two
EventSourceAnalyzer boolean checks and the three AttributeData consumers listed above, hoisting the well-known type resolution to compilation start where it is easy.
- Leave the name-based matchers alone, or introduce an explicit name-based overload in a follow-up.
- No new tests needed — the existing analyzer tests must stay green (currently 418).
Summary
SymbolExtensions.HasAttributewas introduced alongside EPC42 (DataContractSerializableMemberAnalyzer):Several analyzers predate it and open-code the same attribute lookup. This issue tracks migrating them so there is a single way to ask "is this symbol marked with attribute X?".
Purely a cleanup — no behavior change intended, and there are a few traps that make a blind find/replace wrong (see below).
Straightforward call sites
These are plain boolean checks against a resolved attribute type and map directly onto
HasAttribute:EventSourceAnalysis/EventSourceAnalyzer.cs:278—attributes.Any(a => a.AttributeClass?.IsClrType(compilation, typeof(NonEventAttribute)) == true)EventSourceAnalysis/EventSourceAnalyzer.cs:334— sameNonEventAttributecheck, negatedNote both currently resolve the attribute type through
CompilationExtensions.IsClrType(which does aGetTypeByFullNameper call), whereasHasAttributetakes an already-resolvedINamedTypeSymbol. Resolving the well-known type once in aRegisterCompilationStartActionand passing it down is the better shape and avoids the repeated lookup.Call sites that need a companion helper first
HasAttributereturnsbool, but these need theAttributeDataitself, so they cannot migrate until we add something likeTryGetAttribute/GetAttributeOrDefaultreturningAttributeData?:ExcludeFromCodeCoverageOnPartialClassAnalyzer.cs:32-44— needsattribute.ApplicationSyntaxReferenceto compute the diagnostic location.EventSourceAnalysis/EventSourceAnalyzer.cs:284-285— needs theEventAttributeinstance to read its arguments.StructSizeCalculator.cs:68-75— needsstructLayoutAttribute.NamedArgumentsto readSize.Proposal:
and implement
HasAttributeon top of it, or keep them independent to preserve the allocation-freeforeachinHasAttribute(it runs per-symbol).Call sites that must NOT be migrated as-is
These match attributes by name rather than by symbol identity, which is deliberate — the attribute type is not referenced by the analyzer and may come from anywhere:
CoreAnalyzers/MustUseResultAnalyzer.cs:91-95— matches"MustUseResultAttribute"and JetBrains'"MustUseReturnValueAttribute"by name, in addition to the symbol-based check for our ownMustUseResultAttribute. Swapping inHasAttributewould silently drop JetBrains annotation support.AsyncAnalyzers/ConfigureAwaitConfiguration.cs:12,17— matches assembly-level attributes withName.StartsWith("DoNotUseConfigureAwait")/"UseConfigureAwaitFalse"; users declare these attributes in their own assemblies, so there is no symbol to compare against.StructSizeCalculator.cs:69— matchesnameof(StructLayoutAttribute)by name.If we want these covered too, it needs a separate name-based overload, e.g.
HasAttributeWithName(this ISymbol symbol, string name), and it should be introduced consciously rather than as part of a mechanical sweep.Suggested scope
TryGetAttribute(returningAttributeData?) next toHasAttributeinSymbolExtensions.EventSourceAnalyzerboolean checks and the threeAttributeDataconsumers listed above, hoisting the well-known type resolution to compilation start where it is easy.