[Experiment] Platform specific docs analyzer#128411
Open
ericstj wants to merge 6 commits into
Open
Conversation
…onventions Introduces a Roslyn analyzer (PLATDOC001-003) that enforces documentation placement conventions for platform-specific libraries with UseCompilerGeneratedDocXmlFile=true: - PLATDOC001: Public types must have a source file named TypeName.cs - PLATDOC002: Partial source files must follow TypeName.Something.cs convention - PLATDOC003: Public members in non-primary partial files must not have XML documentation comments (docs should be in TypeName.cs) The analyzer only activates when the TargetFramework has a platform suffix (e.g. -windows, -linux) and UseCompilerGeneratedDocXmlFile is true. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Only check types declared across multiple files (partial types split into separate files). Single-file types don't have doc placement issues. - Exclude nested type declarations from PLATDOC003 since they define their own documentation scope. - Suppress PLATDOC002 on System.Private.Xml Async partial files that use the established TypeNameAsync.cs naming convention. - Suppress PLATDOC003 on XmlResolver.FileSystemResolver.cs and XmlResolver.ThrowingResolver.cs which contain non-platform-specific public properties organized by convention. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add a section to adding-api-guidelines.md explaining why documentation must be placed on the primary source file (TypeName.cs) for platform- specific libraries, and how the PlatformDocAnalyzer enforces this. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ilds When a library targets both a platform-agnostic TFM (e.g. net11.0) and platform-specific TFMs (e.g. net11.0-windows), the build now passes the canonical TFM's compiler-generated doc XML to the PlatformDocAnalyzer. The analyzer compares each public API's documentation against the canonical and reports PLATDOC004 when they differ. This directly validates the goal (consistent docs across platforms) rather than relying solely on file-naming heuristics. The canonical doc XML is located at its expected artifact path rather than via ProjectReference, avoiding circular dependencies with PNSE builds and the overhead of GetTargetFrameworks calls. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Contributor
|
Tagging subscribers to this area: @dotnet/area-system-xml |
Contributor
There was a problem hiding this comment.
Pull request overview
This PR introduces a new Roslyn analyzer (eng/analyzers/PlatformDocAnalyzer) and MSBuild wiring intended to prevent XML doc comments from drifting or going missing across platform-specific TFMs when compiler-generated doc XML is used.
Changes:
- Add
PlatformDocAnalyzerwith diagnostics PLATDOC001–PLATDOC004 to enforce partial-file doc placement conventions and (optionally) compare docs against a canonical doc XML. - Integrate canonical doc XML discovery for platform-specific TFMs via
eng/intellisense.targetsand wire the analyzer into source project builds viaeng/generators.targets. - Update documentation guidance and add suppressions in a handful of
System.Private.Xmlfiles.
Show a summary per file
| File | Description |
|---|---|
| src/libraries/System.Private.Xml/src/System/Xml/XmlResolver.ThrowingResolver.cs | Adds PLATDOC003 suppression. |
| src/libraries/System.Private.Xml/src/System/Xml/XmlResolver.FileSystemResolver.cs | Adds PLATDOC003 suppression. |
| src/libraries/System.Private.Xml/src/System/Xml/Resolvers/XmlPreloadedResolverAsync.cs | Adds PLATDOC002 suppression. |
| src/libraries/System.Private.Xml/src/System/Xml/Core/XmlWriterAsync.cs | Adds PLATDOC002 suppression. |
| src/libraries/System.Private.Xml/src/System/Xml/Core/XmlReaderAsync.cs | Adds PLATDOC002 suppression. |
| eng/intellisense.targets | Adds target to pass canonical doc XML as an AdditionalFiles input when available. |
| eng/generators.targets | Adds analyzer project reference and imports analyzer props for source projects. |
| eng/analyzers/PlatformDocAnalyzer/PlatformDocAnalyzer.props | Declares compiler-visible MSBuild properties/metadata for analyzer context. |
| eng/analyzers/PlatformDocAnalyzer/PlatformDocAnalyzer.csproj | Defines the analyzer project and Roslyn dependency. |
| eng/analyzers/PlatformDocAnalyzer/PlatformDocAnalyzer.cs | Implements diagnostics and canonical doc comparison logic. |
| eng/analyzers/PlatformDocAnalyzer.Tests/PlatformDocAnalyzerTests.cs | Adds analyzer unit tests covering PLATDOC001–004 behavior. |
| eng/analyzers/PlatformDocAnalyzer.Tests/PlatformDocAnalyzer.Tests.csproj | Defines the analyzer test project. |
| eng/analyzers/Directory.Build.props | Marks eng/analyzers/* projects as generator/analyzer-style projects. |
| docs/coding-guidelines/adding-api-guidelines.md | Documents the new conventions and diagnostics for platform-specific docs. |
Copilot's findings
- Files reviewed: 14/14 changed files
- Comments generated: 10
ericstj
commented
May 20, 2026
Replace the separate GetPNSEDocTargetFramework, AddProjectReferenceTo- PNSEDocSource, and ResolveCanonicalDocSource targets with a single unified flow: GetCanonicalDocSourceTargetFramework -> AddCanonicalDoc- SourceReference -> ConsumeCanonicalDocSource. Both PNSE and platform-specific builds now resolve the same canonical doc source via ProjectReference, guaranteeing it builds first. The canonical doc source has IsCandidateCompilerGeneratedDocFile=true, meaning it never adds a doc-source reference of its own -- so no cycles. For PNSE builds the canonical doc is used as DocFileOverride; for platform-specific builds it is passed to PlatformDocAnalyzer as an AdditionalFile for PLATDOC004 comparison. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Narrow #pragma suppressions to minimal scope with restore in all 5 System.Private.Xml files. - Fix net11.0-linux TFM examples to use valid platform TFMs (windows, ios) in docs and code comments. - Remove unreachable TypeDeclarationSyntax and DelegateDeclarationSyntax switch arms from GetMemberName/GetMemberIdentifierLocation since CheckMembersForDocs skips nested type declarations. - Add eng/analyzers/README.md noting test run instructions and that analyzer tests follow the existing convention of running locally. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Comment on lines
+141
to
+148
| '$(CanonicalDocSourceTargetFramework)' != '' and | ||
| '$(CanonicalDocSourceTargetFramework)' != '$(TargetFramework)' and | ||
| ('$(BuildTargetFramework)' == '' or '$(TargetFrameworkIdentifier)' == '.NETCoreApp')"> | ||
|
|
||
| <Error Condition="'$(IsPlatformNotSupportedAssembly)' == 'true' and '$(CanonicalDocSourceTargetFramework)' == ''" | ||
| Text="Could not find a compatible TargetFramework to use as the documentation source for this PNSE build. Set 'CanonicalDocSourceTargetFramework' to the desired target framework." /> | ||
|
|
||
| <ItemGroup> |
Comment on lines
+154
to
+167
| // Check the type itself and all its public members. | ||
| CheckSymbolDoc(context, namedType, canonicalDocs); | ||
|
|
||
| foreach (ISymbol member in namedType.GetMembers()) | ||
| { | ||
| if (member.DeclaredAccessibility != Accessibility.Public) | ||
| continue; | ||
|
|
||
| // Skip accessors; they're covered by the property/event. | ||
| if (member is IMethodSymbol { AssociatedSymbol: not null }) | ||
| continue; | ||
|
|
||
| CheckSymbolDoc(context, member, canonicalDocs); | ||
| } |
Comment on lines
+126
to
+137
| private static Dictionary<string, string> ParseDocXml(string xml) | ||
| { | ||
| var docs = new Dictionary<string, string>(StringComparer.Ordinal); | ||
|
|
||
| // Use regex to extract member elements to avoid XML parser normalization | ||
| // that could cause false mismatches with GetDocumentationCommentXml() output. | ||
| foreach (Match match in Regex.Matches(xml, @"<member\s+name=""([^""]+)"">(.*?)</member>", RegexOptions.Singleline)) | ||
| { | ||
| string name = match.Groups[1].Value; | ||
| string innerXml = match.Groups[2].Value; | ||
| docs[name] = NormalizeDocXml(innerXml); | ||
| } |
Comment on lines
+219
to
+222
| private static string NormalizeDocXml(string xml) | ||
| { | ||
| // Normalize whitespace: collapse runs of whitespace into single spaces, trim. | ||
| return Regex.Replace(xml, @"\s+", " ").Trim(); |
| <ItemGroup> | ||
| <PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="$(MicrosoftCodeAnalysisVersion)" /> | ||
| <PackageReference Include="Microsoft.CodeAnalysis.CSharp.Analyzer.Testing" Version="$(CompilerPlatformTestingVersion)" /> | ||
| <PackageReference Include="Microsoft.CodeAnalysis.CSharp.CodeFix.Testing" Version="$(CompilerPlatformTestingVersion)" /> |
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.
Add PlatformDocAnalyzer to enforce consistent documentation across platform-specific builds
When a library uses
UseCompilerGeneratedDocXmlFile=true(the default) and targets platform-specific TFMs likenet11.0-windows, only one platform's compiler-generated doc XML is shipped to customers in the IntelliSense package. If XML doc comments are placed on platform-specific partial source files, they may be missing or inconsistent on other platforms.This PR adds a Roslyn analyzer (
eng/analyzers/PlatformDocAnalyzer) that enforces documentation placement conventions and validates doc consistency. It only activates for platform-specific TFMs withUseCompilerGeneratedDocXmlFile=true.Diagnostics
TypeName.cs.TypeName.Something.csnaming convention.TypeName.cs.PLATDOC001–003 are heuristic rules that guide source organization for partial types split across multiple files. PLATDOC004 is the authoritative check: when a project also targets a platform-agnostic TFM (e.g.
net11.0alongsidenet11.0-windows), the build locates the canonical TFM's compiler-generated doc XML and passes it to the analyzer as anAdditionalFile. The analyzer then compares each public API's documentation against the canonical version and reports mismatches.Build integration
eng/intellisense.targets— For platform-specific TFMs, derives the canonical TFM by stripping the platform suffix (e.g.net11.0-windows→net11.0) and passes its doc XML as anAdditionalFileif available. Uses the artifact path directly rather than aProjectReferenceto avoid circular dependencies with PNSE builds.eng/generators.targets— Wires the analyzer into allIsSourceProjectC# builds.eng/analyzers/PlatformDocAnalyzer/PlatformDocAnalyzer.props— DeclaresCompilerVisiblePropertyandCompilerVisibleItemMetadatafor the analyzer to read MSBuild context.Suppressions
Added
#pragma warning disablein 5 System.Private.Xml files that use established non-platform-specific patterns (TypeNameAsync.csfor async partials,XmlResolver.FileSystemResolver.cs/ThrowingResolver.csfor nested-type-adjacent properties).