Skip to content

Migrate open-coded attribute lookups to SymbolExtensions.HasAttribute #332

Description

@SergeyTeplyakov

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:278attributes.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

  1. Add TryGetAttribute (returning AttributeData?) next to HasAttribute in SymbolExtensions.
  2. 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.
  3. Leave the name-based matchers alone, or introduce an explicit name-based overload in a follow-up.
  4. No new tests needed — the existing analyzer tests must stay green (currently 418).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions