Add an analyzer for [Obsolete] on authored APIs without [Deprecated] - #2506
Merged
Sergio0694 merged 3 commits intoAug 1, 2026
Conversation
…ted] '[Obsolete]' is a .NET concept with no Windows Runtime counterpart, so the WinMD generator copies it verbatim into the '.winmd' rather than translating it, where no other language projection can see it. Deprecating an API of an authored component requires '[Windows.Foundation.Metadata.Deprecated]', which is the only deprecation Windows Runtime metadata can carry. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: ed5549ac-fb33-4d08-a8f3-d6cf3b8bc8e1
Reports publicly exposed APIs of a Windows Runtime component that carry '[Obsolete]' but no
'[Windows.Foundation.Metadata.Deprecated]'. Having both applied is the supported way to deprecate
an API for .NET and Windows Runtime consumers alike, so that combination is not reported.
The check covers types and their members, not just types: the WinMD generator supports
'[Deprecated]' on methods, properties and events too, so '[Obsolete]' on a member is exactly as
silently ineffective as it is on a type.
Only what actually reaches the '.winmd' is considered, so that every report has an action the
developer can take:
- Types are only reported when public and top level, as Windows Runtime has no nested types.
- Members are only reported when public and declared by such a type. They are also restricted to
classes and interfaces: a Windows Runtime struct is a plain field aggregate, so the generator
drops every member of one other than its public instance fields.
- Accessors are skipped, as they are only exported as part of their property or event (which is
reported instead). The generator moves a '[Deprecated]' from the property or event down onto
the accessor row, and never reads one written on a C# accessor.
- Constructors are skipped, because '[Deprecated]' does not include 'AttributeTargets.Constructor'
in its usage and so cannot be applied to one at all. Reporting them would produce a warning
whose only resolutions are suppressing it or dropping the '[Obsolete]'.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: ed5549ac-fb33-4d08-a8f3-d6cf3b8bc8e1
Covers the cases the analyzer must stay quiet for (no attributes, only '[Deprecated]', both attributes, non component projects, non public types and members, nested types, struct members, constructors and accessors) and the ones it must report (every public type kind, and public methods, properties and events of both classes and interfaces). The accessor case is the one most likely to regress into a vacuous test, so it puts '[Obsolete]' on the accessor itself rather than on the property: an attribute written on a property is never surfaced on its accessor symbols, so a test that relies on that would pass with the guard removed. Verified that this one does fail without it. Also links the diagnostic from the '[Obsolete]' note in the attribute projections doc, which is where the behavior it guards is documented. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: ed5549ac-fb33-4d08-a8f3-d6cf3b8bc8e1
|
Azure Pipelines: There may be pipelines that require an authorized user to comment /azp run to run. |
Member
Author
|
Superseded by #2508. This PR was never actually merged: while #2503 was being fixed, its branch (the base of this PR) briefly contained this PR's commits, and GitHub marks a PR as merged as soon as its head commits become reachable from its base. Those commits were removed from #2503 again two minutes later, so nothing from here landed in \staging/3.0, #2503 or #2457. A merged PR cannot be reopened, so the work continues in #2508 with byte-identical content. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Add
CSWINRT2021, an analyzer that reports publicly exposed APIs of an authored Windows Runtime component carrying[Obsolete]without[Windows.Foundation.Metadata.Deprecated].Motivation
The WinMD generator does not translate
[Obsolete]into[Deprecated]: it copies it verbatim, so the.winmdends up with aSystem.ObsoleteAttributereference that no other language projection (C++/WinRT,windows-rs, ...) understands.[Obsolete]on its own therefore deprecates an API for nobody but the C# code inside the component itself.That is not obvious.
[Obsolete]is the way to deprecate an API in C#, it compiles without complaint, and the component builds and works — the deprecation just silently never reaches any consumer. #2503 documented the behavior, but a doc only helps the developer who already suspects there is something to look up.Changes
src/Authoring/WinRT.SourceGenerator2/Diagnostics/DiagnosticDescriptors.cs,AnalyzerReleases.Shipped.md: theCSWINRT2021descriptor (Warning), registered for release tracking.src/Authoring/WinRT.SourceGenerator2/Diagnostics/Analyzers/ObsoleteWithoutDeprecatedAnalyzer.cs: the analyzer. Gated onCsWinRTComponent, and silent when both attributes are applied, which is the supported way to deprecate an API for .NET and Windows Runtime consumers alike.docs/attribute-projections.md: links the diagnostic from the[Obsolete]note added in Project the Windows Runtime [Experimental] attribute as the .NET one #2503.Scope
The check covers types and their members, not just types: the WinMD generator supports
[Deprecated]on methods, properties and events too, so[Obsolete]on a member is exactly as silently ineffective as it is on a type.Only what actually reaches the
.winmdis reported, so that every report has an action the developer can take:Types are only reported when public and top level, as Windows Runtime has no nested types.
Members are only reported when public and declared by such a type, and only for classes and interfaces: a Windows Runtime struct is a plain field aggregate, so the generator drops every member of one other than its public instance fields.
Accessors are skipped, as they are only exported as part of their property or event (which is reported instead). The generator moves a
[Deprecated]from the property or event down onto the accessor row, and never reads one written on a C# accessor.Constructors are skipped, because
[Deprecated]does not includeAttributeTargets.Constructorin its usage and so cannot be applied to one at all. Reporting them would produce a warning whose only resolutions are suppressing it or dropping the[Obsolete].Testing
src/Tests/SourceGenerator2Test/Test_ObsoleteWithoutDeprecatedAnalyzer.cscovers the cases the analyzer must stay quiet for (no attributes, only[Deprecated], both attributes, non-component projects, non-public types and members, nested types, struct members, constructors, accessors) and the ones it must report (every public type kind, and public methods, properties and events of both classes and interfaces).The accessor test deliberately puts
[Obsolete]on the accessor rather than on the property: an attribute written on a property is never surfaced on its accessor symbols, so a test relying on that would pass with the guard removed. Verified that this one does fail without it.Full suite: 148/148 passing.