Skip to content

Add XML documentation validation to OneBranch builds - #4730

Open
paulmedynski wants to merge 15 commits into
mainfrom
dev/paul/xml-docs-validation
Open

paulmedynski wants to merge 15 commits into
mainfrom
dev/paul/xml-docs-validation

Conversation

@paulmedynski

@paulmedynski paulmedynski commented Sep 22, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Adds a preflight for the XML documentation cross-references that feed dotnet/sqlclient-api-docs, so malformed documentation IDs fail our own build instead of surfacing as xref-not-found warnings on an API Docs pull request. Motivated by the warnings reported against dotnet/sqlclient-api-docs#99.

Four commits: the first adds the validation and deliberately leaves its findings in place, the rest fix them and refine how findings are classified.

Verified by non-official run 177645, queued with failOnValidationError: true so the gates had to pass rather than merely report. All seven stages succeeded and all eleven validation steps came back green with zero warnings:

Validate XML documentation sources      Microsoft.Data.SqlClient            0 warnings
Validate XML documentation sources      Microsoft.SqlServer.Server          0 warnings
Validate generated XML documentation    (six packages)                      0 warnings
Validate packaged XML documentation     whole drop                          0 warnings
Validate localized resources                                                0 warnings
Validate NuGet packages                                                     0 warnings

The packaged-documentation gate examined 29,510 cross-references across 15 files.

What is validated

eng/pipelines/onebranch/scripts/validate-xml-docs.ps1 reports:

Category Severity Detects
invalid-docid error C# aliases in signatures, empty () on parameterless members, embedded whitespace, array T: UIDs
unknown-namespace-root error Misspelled namespaces, e.g. Microssoft.Data.SqlClient
unresolved-cref error Cross-references the compiler could not bind, emitted as !:
malformed-xml error Files that are not well-formed
stale-allowlist-entry error Allowlisted cross-references that no longer appear
missing-documentation error A project generates documentation but produced none, or a package ships an assembly without it
missing-public-uid error A public member references something that resolves nowhere
lib-documentation-trimmed error A package's lib/ XML has no remarks or examples
ref-documentation-untrimmed error A package's ref/ XML still has them
lib-ref-documentation-identical error lib/ and ref/ are byte-identical
unexpected-documentation warning Documentation found for a project that does not generate it
mismatched-docid-prefix warning Real member, wrong kind prefix (M: on a property, etc.)
missing-local-uid info As missing-public-uid, but from a non-public member, or where the public surface is unknown
documentation-not-expected info Nothing was validated, with the reason stated
unprefixed-cref info No T:/M:/… prefix; legal, the compiler binds it

No network access is required: these defects are decidable from documentation-ID syntax and the local member index, so the ~338 MB Learn xref map is not needed. It remains available via -ExternalXrefMapPath for local investigation.

Severity follows the member holding the reference

An unresolved reference from a public member becomes a visible xref-not-found on a published page. The same reference from an internal member reaches no reader, because internal members are never published.

A reference assembly contains the public API and nothing else, so the documentation in a package's ref/ folder is exactly that surface — no reflection needed, just a set lookup. Where no reference documentation is available the surface is unknown and nothing is escalated, which is the case for the per-assembly gates.

This separated 19 findings from 60 on the net8.0 package, and in the whole-drop gate reduced 187 undifferentiated warnings to 8 errors and 175 informational. The 8 were all one defect (below), now fixed; run 177645 reports zero.

Expectations come from the project, not the pipeline

GenerateDocumentationFile in the csproj is the single declaration; nothing is restated in YAML, so the two cannot drift:

  • generates documentation — it must exist; absence is an error
  • does not — absence is reported as information naming the reason, presence as a warning

Snippet validation follows the same principle: only the snippets a project's sources actually <include> are validated. Project files are parsed as XML, not searched as text — a comment naming GenerateDocumentationFile would otherwise read as setting it.

Where it runs

  1. Snippet sources, before the build — defects reported in seconds against the file and line a developer edits.
  2. Generated documentation, after the build — additionally sees !: cross-references and resolves against the members actually emitted.
  3. Assembled packages, during package validation — the only point showing what a consumer receives.

All three run on Windows agents: the two jobs that host them, build-buildproj-job.yml and validate-packages-job.yml, both declare pool: type: windows. The pipeline's Linux jobs, version computation and symbol publishing, run no validation. Windows is therefore the only platform these gates cover, and net462 exists only there.

Validation gating

One failOnValidationError parameter governs the localization, XML documentation and package validation steps. When false, findings become warnings and the build continues; malformed inputs and a validator that fails to run still fail the step. sqlclient-official.yml sets it true; sqlclient-non-official.yml exposes it at queue time. No intermediate template declares a default.

Steps reporting warnings or errors emit ##vso[task.complete result=SucceededWithIssues;], because task.logissue alone leaves the task result untouched and a step carrying 307 warnings still rendered green. Informational findings are written as plain output and do not mark the step, so a run is flagged only when something needs attention. Run 177953 confirms this: 175 informational findings in the packaged gate produced zero warnings and a green step.

breakOnSdlError is renamed failOnSdlError to match.

Fixes

Documentation IDs — every cross-reference the gates reported: 5 array T: UIDs, 3 GetSchema DocIDs (empty parentheses, C# aliases), 1 embedded whitespace, and 29 wrong-kind prefixes across 16 snippet files.

Two defects only the public/internal split could surface. SqlDataReader.xml referenced T:Microsoft.Data.SqlClient — a namespace written with a type prefix. SqlMetaData.xml referenced T:Microsoft.Data.SqlClient.Server.SqlUserDefinedTypeAttribute, a namespace that contains no such type; the attribute is Microsoft.SqlServer.Server.SqlUserDefinedTypeAttribute, which eight other snippets already referenced correctly. Both sit on public API members, so both would have produced unresolved references on published pages.

lib/<tfm> documentation regression. lib/ is the source the API docs pipeline consumes and needs the full text; ref/ is trimmed because remarks and examples render badly in Visual Studio tooltips. lib/net8.0 and lib/net9.0 had been taking the trimmed reference copy since 07a928049 (#4033), so published documentation lost every remark and example for those frameworks — 1,226 documented members with 0 remarks, where the implementation has 3,235 members with 635 remarks and 175 examples. Both now take the implementation artifact.

netstandard2.0 has no implementation build, so the reference project now keeps an untrimmed copy beside the trimmed one and lib/netstandard2.0 uses that. Every lib target carries full text with no exception list.

TrimDocsForIntelliSense ordering. It ran after the output copy, so a first build published untrimmed reference documentation (1,523,234 bytes, 507 remarks) and only a second build published the trimmed file (736,069 bytes, 0 remarks). Whether ref/ was trimmed depended on how many times the project had been built. Now runs between CoreCompile and CopyFilesToOutputDirectory.

Snippet edits did not trigger rebuild. <include> files are read by the compiler but were not MSBuild inputs, so editing a snippet left the generated documentation stale — making a fix appear to be ignored by the validation. Declared via CustomAdditionalCompileInputs. Verified with a control: without it an edit is silently dropped; with it the edit is picked up, and an unchanged rebuild still skips CoreCompile.

Microsoft.SqlServer.Server now generates documentation. Its public types already carried complete comments — 63 documented members, 0 build warnings — but they were compiled away, so consumers got no IntelliSense. Set unconditionally: net46 is type-forwards only and gets an essentially empty file, but a TargetFramework condition would leave the project without a single unambiguous answer about whether it produces documentation. Run 177953 confirms the package now ships its documentation.

Also records in the nuspec why net462 packs the real implementation into both lib/ and runtimes/, which is otherwise easy to mistake for an accident.

Rules added during review

Code review drove a second round of grammar and resolution rules, each reproduced against the
validator before being fixed and covered by a test confirmed to fail without it:

Rule What passed before
Recursive alias detection T:...List{string} — the outer name is an ordinary type, so the alias inside was never examined
Conversion operator return types M:...op_Implicit(System.Int32)~string — scanning stopped at the parameter list
Balanced generic delimiters T:...List{System.String — read from the first brace to the last, so a missing one silently changed the argument list
Trailing signature text M:...Bar(System.Int32)garbage and a bare trailing ~
Namespace resolution N:Microsoft.Data.SqlClinet — namespaces were exempt from local resolution, so a misspelling passed every gate
Wrong-kind prefix on a namespace N: naming a type is now a prefix mismatch
Overload versus prefix mismatch An overload that was never emitted was reported as a prefix mismatch, advising a prefix be replaced with itself
Reversed parameter list M:...Bad)( threw, ending the run without writing the JSON report that report-only mode exists to produce
Multidimensional arrays T:System.Int32[,] and the [0:,0:] documentation-ID spelling
Angle-bracket generics T:...List<System.String>

Two build-correctness fixes came from the same review. The documentation compile inputs were
declared in src/Directory.Build.props, which every project under src/ inherits, so editing any
snippet invalidated CoreCompile for all 31 projects including the 23 test projects that consume
none. They now live in src/Directory.Build.targets behind a property the five consuming projects
set, verified by evaluating the item in every project: 5 of 31 carry it, and a snippet edit still
rebuilds those five. Separately, TrimDocs.ps1 was not among the trimming target's Inputs, so
editing the script left the trimmed output considered up to date until someone built clean.

Conflicts with #4673

#4673 removes the RID-specific DLLs and changes lib/net462's XML from the implementation to the trimmed reference copy. Combined with the mappings it leaves untouched, all four lib/<tfm> folders would carry trimmed documentation — the regression this PR fixes, extended to the one framework that still escaped it. The two PRs otherwise agree: #4673 puts real implementation DLLs into lib/, this PR puts the matching full XML there. A plain merge conflicts on lib/net462 and must be resolved toward lib = full.

Testing

214 tests pass across eng/pipelines/onebranch/scripts/tests/, including the pre-existing suites.

Run 177953
on 439575d0e is the end-to-end evidence, and it ran with failOnValidationError at its true
default, so any error-severity finding would have failed the build:

Job Step Crefs examined
Microsoft.Data.SqlClient sources / generated 4,495 across 84 files / 13,758 across 3
Microsoft.SqlServer.Server sources / generated 180 across 10 files / 180 across 2
Extensions.Abstractions, Extensions.Azure, Internal.Logging, AKV generated 98 across 5 files
Validate Packages packaged 29,510 across 15 files, 6 packages

All 11 validation steps succeeded — 48,221 cross-references in total. Across the run's 678 timeline
records: 628 succeeded, 50 skipped, none failed and none succeededWithIssues. That last count
is the informative one, since SucceededWithIssues is exactly what a step emits when the validator
finds something; its absence means nothing was found rather than findings being tolerated.

This is also the first run to exercise the reference-project restructure on Windows across all four
target frameworks, net462 included, which no local run can reach.

Four defects were found only by running the pipeline, each now covered by a test verified to fail when the defect is reintroduced:

Run Defect
177246 -Switch:Value binds as a string under the PowerShell task's -Command dot-sourcing
177258 The generated-documentation gate threw for a package emitting no XML
177268 Findings were recorded but steps rendered green
177610 Informational findings marked steps as needing attention

Also verified against real artifacts rather than only synthetic ones: both projects built, packages assembled reproducing both the broken and corrected nuspec mappings, and a package with its XML deliberately dropped.

  • Tests added or updated
  • Public API changes documented — n/a, no public API change
  • Verified against customer repro — n/a
  • Ensure no breaking changes introduced

Copilot AI balanced review requested due to automatic review settings September 22, 2026 14:36
@github-project-automation github-project-automation Bot moved this to To triage in SqlClient Board Sep 22, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot review overview

🟡 Changes recommended

The gates can fail on missing SqlServer XML output and deliberately unresolved baseline findings.

Get a fresh assessment by requesting another Copilot review.

Review effort: Balanced
Findings: 2 High severity · 1 Medium severity

Open (3)
What changed in this PR

Adds offline XML documentation validation to OneBranch builds and centralizes report-only gating.

Changes:

  • Validates source, generated, and packaged XML documentation.
  • Adds shared failOnValidationError behavior.
  • Adds Pester coverage and documentation.
File Description
eng/​pipelines/​onebranch/​steps/​validate-xml-docs-step.yml Defines the XML validation step.
eng/​pipelines/​onebranch/​steps/​validate-packages-step.yml Supports report-only package validation.
eng/​pipelines/​onebranch/​steps/​validate-localization-step.yml Supports report-only localization validation.
eng/​pipelines/​onebranch/​stages/​build-stages.yml Propagates validation policy.
eng/​pipelines/​onebranch/​sqlclient-official.yml Enforces validation in official builds.
eng/​pipelines/​onebranch/​sqlclient-non-official.yml Exposes validation gating at queue time.
eng/​pipelines/​onebranch/​scripts/​validate-xml-docs.ps1 Implements XML documentation checks.
eng/​pipelines/​onebranch/​scripts/​validate-packages.ps1 Implements report-only package behavior.
eng/​pipelines/​onebranch/​scripts/​validate-localization.ps1 Downgrades findings when requested.
eng/​pipelines/​onebranch/​scripts/​tests/​validate-xml-docs.Tests.ps1 Tests XML validation rules.
eng/​pipelines/​onebranch/​scripts/​tests/​validate-packages.Tests.ps1 Tests package report-only behavior.
eng/​pipelines/​onebranch/​scripts/​tests/​validate-localization.Tests.ps1 Tests localization report-only behavior.
eng/​pipelines/​onebranch/​scripts/​tests/​README.md Documents validation tests.
eng/​pipelines/​onebranch/​jobs/​validate-packages-job.yml Adds packaged-documentation validation.
eng/​pipelines/​onebranch/​jobs/​build-buildproj-job.yml Adds source and generated-document gates.
.github/​instructions/​onebranch-pipeline-design.instructions.md Documents validation architecture.
.github/​instructions/​documentation.instructions.md Documents cref and trimming rules.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread eng/pipelines/onebranch/jobs/build-buildproj-job.yml
Comment thread eng/pipelines/onebranch/sqlclient-official.yml
Comment thread eng/pipelines/onebranch/scripts/validate-xml-docs.ps1
Copilot AI review requested due to automatic review settings September 22, 2026 15:19

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot review overview

🔵 Needs a closer look

The current rollout guarantees pipeline failures and leaves documentation-ID forms unvalidated.

Review effort: Balanced
Findings: 2 High severity · 1 Medium severity

Open (3)
Previously missed (1)

In code that hasn't changed since last review

Medium severity Recognize multidimensional array suffixes in documentation IDs

eng/​pipelines/​onebranch/​scripts/​validate-xml-docs.ps1:375

This only recognizes the [] array suffix. Multidimensional array forms such as [,] or the documentation-ID form [0:,0:] also represent constructed types but do not match, so an invalid T: array cref passes. Match any bracketed array suffix here.

Copilot AI review requested due to automatic review settings September 22, 2026 15:28
@paulmedynski paulmedynski added this to the 8.0.0-preview1 milestone Sep 22, 2026
@paulmedynski paulmedynski moved this from To triage to In progress in SqlClient Board Sep 22, 2026
@paulmedynski paulmedynski added Area\Engineering Use this for issues that are targeted for changes in the 'eng' folder or build systems. Hotfix 7.1.1 PRs targeting main that should be backported to release/7.1 for 7.1.1. labels Sep 22, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot review overview

🟡 Changes recommended

Known findings block official builds, and generated-doc and report-only paths contain functional failures.

Get a fresh assessment by requesting another Copilot review.

Review effort: Balanced
Findings: 3 High severity · 1 Medium severity

Open (4)

Comment thread eng/pipelines/onebranch/steps/validate-packages-step.yml
Copilot AI review requested due to automatic review settings September 22, 2026 16:38

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot review overview

🔵 Needs a closer look

Known findings make official builds fail, and nested generic aliases currently escape validation.

Review effort: Balanced
Findings: 3 High severity · 1 Medium severity

Open (4)

Copilot AI review requested due to automatic review settings September 22, 2026 18:28

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot review overview

🟡 Changes recommended

The official pipeline is guaranteed to fail on known defects, and the validator has confirmed parsing and stale-extraction gaps.

Get a fresh assessment by requesting another Copilot review.

Review effort: Balanced
Findings: 3 High severity · 1 Medium severity

Open (4)
Resolved since last review (1)

Comment thread eng/pipelines/onebranch/scripts/validate-xml-docs.ps1
Copilot AI review requested due to automatic review settings September 22, 2026 18:48

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot review overview

🔵 Needs a closer look

The official gate will fail on acknowledged existing findings, and the validator has stale-extraction and nested-generic validation defects.

Review effort: Balanced
Findings: 3 High severity · 1 Medium severity

Open (4)

Adds a preflight for the XML documentation cross-references that feed
dotnet/sqlclient-api-docs, so malformed documentation IDs fail our own build
rather than surfacing as xref-not-found warnings on an API Docs pull request.

This commit exposes the problems; it does not fix them. The findings it
reports are left in place so they can be reviewed and fixed separately.

Validation
----------

eng/pipelines/onebranch/scripts/validate-xml-docs.ps1 reports, as errors:
documentation-ID syntax defects (C# aliases in signatures, empty parentheses
on parameterless members, embedded whitespace, array T: UIDs), unknown
namespace roots, cross-references the compiler could not bind, malformed
files, stale allowlist entries, documentation that should exist but does not,
and lib/ versus ref/ documentation trimming defects in a package. Resolution
findings that a target-framework-conditional or cross-assembly member can
legitimately trigger are warnings.

No network access is required: these defects are decidable from
documentation-ID syntax and the local member index, so the published Learn
xref map is not needed. It stays available via -ExternalXrefMapPath.

It runs at three points: snippet sources before the build, generated
documentation after it, and the assembled packages during package validation.
Only the last shows what a consumer actually receives.

Expectations come from the project
----------------------------------

GenerateDocumentationFile in the csproj is the single declaration, so nothing
is restated in the pipeline and the two cannot drift. A project that generates
documentation must produce it; one that does not is reported as information
naming the reason, and documentation appearing there is reported as a warning.
Snippet validation follows the same principle: only the snippets a project's
sources reference are validated. Project files are parsed as XML rather than
searched as text, because a comment naming the property would otherwise read
as setting it.

Gating
------

One failOnValidationError parameter governs the localization, XML
documentation, and package validation steps. When false, findings are reported
as warnings and the build continues; malformed inputs and a validator that
fails to run still fail the step. The official pipeline sets it true and the
non-official pipeline exposes it at queue time, with no intermediate template
declaring a default. Steps reporting findings mark themselves
SucceededWithIssues, because task.logissue alone leaves the task result
untouched and a step carrying warnings would otherwise render as a clean
success.

breakOnSdlError is renamed failOnSdlError to match.

Microsoft.SqlServer.Server
--------------------------

Enables documentation generation. Its public types already carry complete
documentation comments, producing 63 documented members with no warnings, but
they were compiled away so consumers got no IntelliSense. Set unconditionally:
net46 is type-forwards only and gets an essentially empty file, but a
TargetFramework condition would leave the project without a single
unambiguous answer about whether it produces documentation.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@paulmedynski
paulmedynski force-pushed the dev/paul/xml-docs-validation branch from 069e7a9 to 752bc4a Compare September 22, 2026 19:04
Copilot AI review requested due to automatic review settings September 23, 2026 13:42
Companion to the commit that added the validation. That one deliberately left
its findings in place; this one fixes them.

Documentation IDs
-----------------

Corrects every cross-reference the snippet and generated-documentation gates
reported: array T: UIDs that name no documentation page, a parameterless
method written with empty parentheses, C# aliases where a documentation ID
must name CLR types, whitespace inside a documentation ID, and 29
cross-references carrying the wrong kind prefix.

Packaging
---------

lib/<tfm> is the source the API docs pipeline consumes and must carry the full
text; ref/<tfm> is trimmed because remarks and examples render badly in Visual
Studio tooltips. lib/net8.0 and lib/net9.0 had been taking the trimmed
reference copy since 07a9280, so the published documentation lost every
remark and example for those frameworks. Both now take the implementation
artifact.

netstandard2.0 has no implementation build to take full documentation from, so
the reference project now keeps an untrimmed copy of its documentation beside
the trimmed one, and lib/netstandard2.0 uses that. Every lib target therefore
carries the full text without needing an exception.

Also records in the nuspec why net462 packs the real implementation into both
lib/ and runtimes/, which is otherwise easy to mistake for an accident.

Build correctness
-----------------

TrimDocsForIntelliSense ran after the output copy, so a first build published
untrimmed reference documentation and only a second build published the
trimmed file. It now runs between CoreCompile and CopyFilesToOutputDirectory,
so one build is enough.

Documentation comments take their text from doc/snippets through <include>,
which the compiler reads but MSBuild did not treat as an input. Editing a
snippet left the generated documentation stale until the project was rebuilt
from scratch, which made the validation above appear to ignore a fix. The
snippets are now declared as compile inputs.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings September 24, 2026 13:23
A cref naming a namespace was exempt from local resolution, because the compiler emits
no <member> entry for a namespace and every N: cref would otherwise have been reported
as missing.  The exemption also excused a misspelling: N:Microsoft.Data.SqlClinet
carries an allowed namespace root, and no pipeline invocation supplies an external xref
map, so it passed every gate and still became an unresolved reference on Learn.

Derive the set of namespaces from the members that were emitted, taking every leading
portion of each identifier, and resolve N: crefs against it.  A leading portion may also
name a type containing a nested one, which is harmless here: the member index is
consulted first, so a namespace prefix on a type is reported as the prefix mismatch it
is rather than being admitted as a namespace.

The unresolved message is chosen by prefix, as saying that no matching member was
emitted explains nothing about a cref that never named a member.

Verified against the emitted documentation for Microsoft.Data.SqlClient: the three real
N: crefs still resolve and the finding count is unchanged at 70.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@paulmedynski

Copy link
Copy Markdown
Contributor Author

Addressed the Validate local namespace cref resolution finding in cf79cfd. Reproduced it first — with N:Microsoft.Data.SqlClinet and T:Microsoft.Data.SqlClinet.SqlConnection in the same file, only the type was reported. The namespace passed every gate, exactly as described.

Namespaces are now resolved against a set derived from the members the build did emit, taking every leading portion of each identifier.

One design note worth calling out. A leading portion can also name a type containing a nested one, so the derived set is deliberately permissive — but that cannot weaken anything, because the member index is consulted before the namespace set. So N:Microsoft.Data.SqlClient.SqlConnection is reported as mismatched-docid-prefix (was emitted as 'T:') rather than being admitted as a namespace. Ordering the checks this way meant I did not have to distinguish a namespace from a containing type in a DocID, which is not reliably possible. That wrong-prefix case is a second gap this closes.

I also split the unresolved message by prefix — "no matching documented member was emitted" explains nothing about a cref that never named a member.

Verification, since the false-positive risk here is real (doc/snippets contains three N:Microsoft.Data.SqlClient crefs that must keep resolving):

  • Ran against the emitted Microsoft.Data.SqlClient.xml for net9.0: 4,579 crefs, missing-local-uid=70 both before and after — byte-identical outcome, zero N: findings.
  • doc/snippets still clean at 4,687 crefs across 99 files.
  • 200 tests pass, up from 197. The three new cases cover a namespace that resolves, a misspelling that does not, and the N:-on-a-type prefix mismatch; I confirmed the two failure cases fail against the previous logic rather than passing vacuously.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot review overview

🔵 Needs a closer look

The validator can misclassify missing overloads as prefix mismatches and issue impossible remediation guidance.

Review effort: Balanced
Findings: None

Previously missed (2)

In code that hasn't changed since last review

Medium severity Avoid misclassifying missing overloads as prefix mismatches

eng/​pipelines/​onebranch/​scripts/​validate-xml-docs.ps1:511

A nonexistent overload can be misclassified as a prefix mismatch when a parameterless overload exists. For example, if M:Widget.Run is emitted and a cref names M:Widget.Run(System.String), the namePart fallback selects Widget.Run and reports that M: should be changed to M:. Only use the fallback when the emitted prefixes do not already contain the cref's current prefix; otherwise this should remain a missing-UID finding.

Low severity Identify Learn API pages as the consumer of trimmed lib XML

eng/​pipelines/​onebranch/​scripts/​validate-xml-docs.ps1:36

The collapsed mapping did not remove remarks from IntelliSense: IntelliSense intentionally consumes the trimmed ref/ XML. It removed narrative content from the Learn API pages, which consume lib/. Naming the affected consumer correctly avoids contradicting the package-layout contract documented below.

This issue also appears on line 126 of the same file.

Copilot AI review requested due to automatic review settings September 24, 2026 13:49
…t consumer

Looking an identifier up without its signature is what lets a cref written as M: for a
property be reported as a prefix mismatch rather than a bare lookup failure.  The same
fallback also matched a cref naming an overload that does not exist, because it shares
its identifier, and therefore its prefix, with the overload that does: a cref for
Run(System.String) against an emitted Run produced "uses prefix 'M:', but ... was
emitted as 'M:'", advising a replacement of the prefix with itself and hiding that the
build emitted no such member.  Take the fallback only when the emitted prefixes differ
from the one written.

The header and the category table also named IntelliSense as what the 7.1.0 collapse
harmed.  IntelliSense reads the trimmed ref/ copy by design and was unaffected; lib/
feeds the pipeline that builds the Learn pages, and those are what lost every remark and
example.  Name that consumer, matching the finding messages and the package layout
contract in documentation.instructions.md.  The missing-documentation message named
IntelliSense too, though it covers an assembly shipping no XML in either folder, so it
now speaks of documentation text generally.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@paulmedynski

Copy link
Copy Markdown
Contributor Author

Both items addressed in 52a2ca5.

Avoid misclassifying missing overloads as prefix mismatches — reproduced exactly as described. An emitted M:Widget.Run with a cref naming M:Widget.Run(System.String) produced:

uses prefix M:, but Microsoft.Data.SqlClient.Widget.Run was emitted as M:

Self-contradictory, and it hid the real problem — the build emitted no such overload. The signature-less lookup is what lets a cref written as M: for a property be named as a prefix mismatch, but it also matches an overload that does not exist, since that shares its identifier and therefore its prefix with the overload that does. The fallback is now taken only when the emitted prefixes differ from the one written. The missing overload is reported as missing-local-uid, while M: on a property and P: on a method are still reported as mismatches.

Identify Learn API pages as the consumer of trimmed lib XML — correct, and this is a mapping I have gotten backwards before, so thank you for catching the residue. IntelliSense reads the trimmed ref/ copy by design and was unaffected by the 7.1.0 collapse; lib/ feeds the pipeline that builds the Learn pages, and those are what lost every remark and example. Fixed in both the header and the category table.

I swept the rest of the file rather than only the two lines cited. That turned up a third: the missing-documentation message also said "no IntelliSense text", but it fires for an assembly shipping no XML in either folder, so it now speaks of documentation text generally. The two remaining IntelliSense mentions are correct — both describe ref/ being trimmed. The lib-documentation-trimmed finding message already named the API docs pipeline correctly, so the header and table were the only places contradicting the contract in documentation.instructions.md.

Verification:

  • 202 tests pass, up from 200. I confirmed the new overload case fails against the previous logic rather than passing vacuously.
  • Real corpus unaffected: the emitted Microsoft.Data.SqlClient.xml for net9.0 reports 4,579 crefs and missing-local-uid=70 both before and after, so no existing cref was travelling the misclassification path.
  • doc/snippets still clean at 4,687 crefs across 99 files.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot review overview

🔵 Needs a closer look

The validator still accepts malformed generic DocIDs with unbalanced braces.

Review effort: Balanced
Findings: None

Previously missed (1)

In code that hasn't changed since last review

Medium severity Reject unbalanced generic delimiters in cref DocIDs

eng/​pipelines/​onebranch/​scripts/​validate-xml-docs.ps1:390

The generic-syntax check only rejects angle brackets; it still accepts unbalanced braces. For example, T:System.Collections.Generic.List{System.String exits successfully with zero findings, although this malformed DocID cannot resolve. Please validate balanced and correctly nested generic delimiters and add a regression case before accepting the cref.

Copilot AI review requested due to automatic review settings September 24, 2026 14:01
The generic-syntax rule rejected angle brackets but said nothing about braces that do
not pair up, so T:System.Collections.Generic.List{System.String passed with no findings
despite naming nothing.  Such a cref is read from the first brace to the last, so a
missing delimiter quietly produces a different argument list than the text shows, or
none at all, and the identifier reaches Learn unresolvable.

Count the delimiters before the cref is taken apart, since the extraction that follows
assumes they pair up.  A closing brace with nothing open ends the scan, as nothing later
can balance it.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@paulmedynski

Copy link
Copy Markdown
Contributor Author

Addressed in f737219. Reproduced first — T:System.Collections.Generic.List{System.String exited with zero findings, as did the misnested T:System.Collections.Generic.List}System.String{.

The failure is quiet rather than loud, which is what makes it worth gating: the generic argument list is read from the first brace to the last, so a missing delimiter yields a different argument set than the text shows, or none at all, and the identifier reaches Learn unresolvable. It is also why the existing recursive alias check could not catch it — with no closing brace there is no inner text to recurse into.

The delimiters are now counted before the cref is taken apart, since the extraction that follows assumes they pair up. A closing brace with nothing open ends the scan, as nothing later can balance it, which is what catches the misnested form rather than only the truncated one.

I covered the signature position as well as the type position, since the same body is scanned for both: M:...Use(System.Collections.Generic.List{System.String) was equally accepted and is now rejected.

Verification:

  • 208 tests pass, up from 202 — three rejected forms and three accepted controls, the controls covering a simple generic, a nested generic (Dictionary{System.String,List{System.Int32}}), and a generic inside a signature. I confirmed all three rejection cases fail against the previous logic while the controls passed both before and after, so the rule adds detection without narrowing what it accepts.
  • Real corpus unaffected: emitted Microsoft.Data.SqlClient.xml for net9.0 still reports 4,579 crefs with missing-local-uid=70, and doc/snippets is still clean at 4,687 crefs across 99 files.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot review overview

🔵 Needs a closer look

The validator still permits malformed nested type namespaces and conversion-return aliases.

Review effort: Balanced
Findings: None

Previously missed (1)

In code that hasn't changed since last review

Medium severity Validate aliases in conversion operator return suffixes

eng/​pipelines/​onebranch/​scripts/​validate-xml-docs.ps1:465

Alias validation stops at the parameter list and never examines a conversion operator's return suffix. As a result, a DocID such as M:System.Foo.op_Implicit(System.Int32)~string passes even though the documented rule requires CLR type names throughout the signature; because System is external and no xref map is used in CI, later gates do not catch it either. Parse the text after ~ and run the same recursive alias/type validation over it, with a regression test beside the existing conversion-operator control.

This issue also appears on line 503 of the same file.

Alias scanning covered the parameter list, and the name taken for the later checks
ends at the '(', so the return type of a conversion operator was read by neither:
M:System.Foo.op_Implicit(System.Int32)~string passed with no findings even though a
documentation ID names CLR types throughout.  Scan the return type with the parameters,
and describe the finding as the signature rather than the parameter list, since the
alias may now be in either.

Nothing else may follow a parameter list, so text there that is not a return marker, or
a marker naming no type, is reported rather than dropped.  Both forms reached none of
the surrounding checks, which read the name before the '(' and the arguments within it.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings September 24, 2026 14:27
@paulmedynski

Copy link
Copy Markdown
Contributor Author

Review body feedback

Copilot review 2026-09-24T14:07:57Z — Validate aliases in conversion operator return suffixes (eng/pipelines/onebranch/scripts/validate-xml-docs.ps1:465, also line 503) — Fixed in 439575d.

Reproduced first: M:System.Foo.op_Implicit(System.Int32)~string and ~System.Collections.Generic.List{int} both passed with zero findings. The two cited lines share one cause — alias scanning ends at the parameter list, and the name used by the later checks ends at the (, so the text after ~ was read by neither.

The return type is now scanned alongside the parameters, and the message says "in its signature" rather than "in its parameter list", since the alias may be in either. A signature carrying aliases in both is reported once, listing both.

While fixing it I found an adjacent hole that was not reported: nothing after a parameter list was examined at all, so M:System.Foo.Bar(System.Int32)garbage and a bare trailing ~ also passed. Only a conversion operator's return marker may follow a parameter list, so both are now rejected.

Verification: 214 tests pass, up from 208. Five of the six new cases fail against the previous logic rather than passing vacuously; the sixth is an accept-control. The real corpora are unaffected — doc/snippets is clean at 4,687 crefs across 99 files, and the emitted Microsoft.Data.SqlClient.xml for net9.0 still reports 4,579 crefs with missing-local-uid=70, including its three real op_Implicit members.

Other sources inspected

  • Review threads: 10 total, all resolved, 0 unresolved.
  • Copilot suppressed findings: none. This Copilot version reports through "Open" / "Previously missed" sections rather than a suppressed-comments block.
  • Discussion comments: 8 inspected — 1 coverage report set aside as operational, 7 are my own earlier replies. No feedback requiring action.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot review overview

🔵 Needs a closer look

It substantially changes build ordering, package contents, and release-gating behavior across multiple products.

Review effort: Balanced
Findings: None

@paulmedynski
paulmedynski marked this pull request as ready for review September 24, 2026 17:00
@paulmedynski
paulmedynski requested a review from a team September 24, 2026 17:00
@paulmedynski
paulmedynski enabled auto-merge (squash) September 24, 2026 17:01
@paulmedynski paulmedynski removed the Hotfix 7.1.1 PRs targeting main that should be backported to release/7.1 for 7.1.1. label Sep 24, 2026

This branch has not been deployed

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

Labels

Area\Engineering Use this for issues that are targeted for changes in the 'eng' folder or build systems. Hotfix 6.1.8 Hotfix 7.0.4

Projects

Status: In review

Development

Successfully merging this pull request may close these issues.

4 participants