Skip to content

[Experiment] Platform specific docs analyzer#128411

Open
ericstj wants to merge 6 commits into
dotnet:mainfrom
ericstj:platform-specific-docs-analyzer
Open

[Experiment] Platform specific docs analyzer#128411
ericstj wants to merge 6 commits into
dotnet:mainfrom
ericstj:platform-specific-docs-analyzer

Conversation

@ericstj
Copy link
Copy Markdown
Member

@ericstj ericstj commented May 20, 2026

Add PlatformDocAnalyzer to enforce consistent documentation across platform-specific builds

When a library uses UseCompilerGeneratedDocXmlFile=true (the default) and targets platform-specific TFMs like net11.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 with UseCompilerGeneratedDocXmlFile=true.

Diagnostics

ID Severity Description
PLATDOC001 Warning Public type has no source file named TypeName.cs.
PLATDOC002 Warning Partial source file doesn't follow the TypeName.Something.cs naming convention.
PLATDOC003 Warning Public member in a non-primary partial file has XML doc comments that should be moved to TypeName.cs.
PLATDOC004 Warning Documentation for a public API differs from the canonical (platform-agnostic) build.

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.0 alongside net11.0-windows), the build locates the canonical TFM's compiler-generated doc XML and passes it to the analyzer as an AdditionalFile. 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-windowsnet11.0) and passes its doc XML as an AdditionalFile if available. Uses the artifact path directly rather than a ProjectReference to avoid circular dependencies with PNSE builds.
  • eng/generators.targets — Wires the analyzer into all IsSourceProject C# builds.
  • eng/analyzers/PlatformDocAnalyzer/PlatformDocAnalyzer.props — Declares CompilerVisibleProperty and CompilerVisibleItemMetadata for the analyzer to read MSBuild context.

Suppressions

Added #pragma warning disable in 5 System.Private.Xml files that use established non-platform-specific patterns (TypeNameAsync.cs for async partials, XmlResolver.FileSystemResolver.cs / ThrowingResolver.cs for nested-type-adjacent properties).

ericstj and others added 4 commits May 19, 2026 11:44
…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>
Copilot AI review requested due to automatic review settings May 20, 2026 17:11
@dotnet-policy-service
Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @dotnet/area-system-xml
See info in area-owners.md if you want to be subscribed.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 PlatformDocAnalyzer with 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.targets and wire the analyzer into source project builds via eng/generators.targets.
  • Update documentation guidance and add suppressions in a handful of System.Private.Xml files.
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

Comment thread src/libraries/System.Private.Xml/src/System/Xml/XmlResolver.ThrowingResolver.cs Outdated
Comment thread src/libraries/System.Private.Xml/src/System/Xml/XmlResolver.FileSystemResolver.cs Outdated
Comment thread src/libraries/System.Private.Xml/src/System/Xml/Core/XmlWriterAsync.cs Outdated
Comment thread src/libraries/System.Private.Xml/src/System/Xml/Core/XmlReaderAsync.cs Outdated
Comment thread docs/coding-guidelines/adding-api-guidelines.md
Comment thread eng/analyzers/PlatformDocAnalyzer/PlatformDocAnalyzer.cs
Comment thread eng/analyzers/PlatformDocAnalyzer/PlatformDocAnalyzer.cs
Comment thread eng/analyzers/PlatformDocAnalyzer/PlatformDocAnalyzer.cs
Comment thread eng/intellisense.targets Outdated
ericstj and others added 2 commits May 20, 2026 13:22
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>
Copilot AI review requested due to automatic review settings May 20, 2026 21:15
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot's findings

  • Files reviewed: 15/15 changed files
  • Comments generated: 5

Comment thread eng/intellisense.targets
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)" />
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants