Skip to content

Comment-based help examples now support optional titles - #27387

Open
Marius Storhaug (MariusStorhaug) wants to merge 41 commits into
PowerShell:masterfrom
MariusStorhaug:feature/example-titles-in-comment-help
Open

Marius Storhaug (MariusStorhaug) wants to merge 41 commits into
PowerShell:masterfrom
MariusStorhaug:feature/example-titles-in-comment-help

Conversation

@MariusStorhaug

@MariusStorhaug Marius Storhaug (MariusStorhaug) commented May 1, 2026 •

Copy link
Copy Markdown

Comment-based help examples now support optional titles using .EXAMPLE <Title> syntax, matching the inline pattern already used by .PARAMETER <Name>. Titles appear in Get-Help output, round-trip through serialization, and are exposed through a new public API so external tools like PlatyPS can consume them. Examples without titles continue to work exactly as before.

New: Titled examples in comment-based help

Authors can now place a title on the same line as .EXAMPLE:

function Get-Report {
<#
    .SYNOPSIS
    Generates a report.

    .EXAMPLE Generating a summary report
    Get-Report -Type Summary

    Returns a summary of current data.

    .EXAMPLE
    Get-Report -Type Full

    Returns all data. (Untitled — still works as before.)
#>
    param([string]$Type)
}

Get-Help Get-Report -Examples renders the title alongside the auto-generated number:

-------------------------- EXAMPLE 1: Generating a summary report --------------------------

PS > Get-Report -Type Summary

Returns a summary of current data.


-------------------------- EXAMPLE 2 --------------------------

PS > Get-Report -Type Full

Returns all data.

Line-comment syntax (# .EXAMPLE Title) is also supported.

New: CommentHelpInfo.ExampleTitles public property

External tools (PlatyPS, doc generators, formatters) can now read titles via a new property on CommentHelpInfo:

public ReadOnlyCollection<string> ExampleTitles { get; internal set; }

ExampleTitles runs in parallel with the existing Examples collection — same length, same index mapping. An empty string at an index means that example has no title. The existing Examples property type is unchanged, so the public API remains binary- and source-compatible.

var help = scriptBlockAst.GetHelpContent();
for (int i = 0; i < help.Examples.Count; i++)
{
    string title = help.ExampleTitles[i];
    string body  = help.Examples[i];
    Console.WriteLine(string.IsNullOrEmpty(title) ? $"EXAMPLE {i+1}" : $"EXAMPLE {i+1}: {title}");
    Console.WriteLine(body);
}

Backward compatibility

This change is fully backward compatible for functions and modules that run on the version that includes this fix:

  • .EXAMPLE with no trailing text continues to behave exactly as before.
  • All existing Get-Help output is identical — the only observable difference is that .EXAMPLE <Title> now works instead of breaking.
  • CommentHelpInfo.ExampleTitles is purely additive; Examples is unchanged.

Behavior on older PowerShell versions: Functions that use .EXAMPLE <Title> continue to load and run normally on older PowerShell. Get-Help on older versions silently discards the entire help block when it encounters .EXAMPLE <Title> — no error is thrown, but no authored help content is shown. Authors who adopt the new syntax should document that their help requires the version that includes this fix.

Implementation plan progress — from #23966:

  • Parser changes
  • Data model (ExampleTitles parallel collection — non-breaking)
  • XML generation
  • Round-trip serialization (GetCommentBlock + ProxyCommand.GetHelpComments + ExtractExampleTitle)
  • Tests (41 passing)
  • PowerShell-Docs updates — separate PR
  • PlatyPS update — PlatyPS#627
  • vscode-powershell / EditorSyntax grammar updates — separate PRs
Technical Details

Semver classification: Minor. The ExampleTitles public API addition and the new observable Get-Help behavior both require a minor version bump. The default: return false parser fix is patch-eligible on its own, but the additive API determines the classification.

Files changed:

  • src/System.Management.Automation/help/HelpCommentsParser.cs — Added case "EXAMPLE" to the Groups[3].Success == true switch branch in AnalyzeCommentBlock. Captures match.Groups[3].Value.Trim() as the title, then calls GetSection() for the body. The existing untitled branch appends string.Empty to _exampleTitles to keep indices aligned. XML generation conditionally appends : <title> to the heading string when the title is non-empty.
  • src/System.Management.Automation/engine/parser/ast.cs — Added ReadOnlyCollection<string> ExampleTitles { get; internal set; } on CommentHelpInfo. Updated GetCommentBlock() to emit .EXAMPLE <title> when the title is non-empty and .EXAMPLE on its own line otherwise.
  • src/System.Management.Automation/engine/ProxyCommand.cs — Updated GetHelpComments to emit .EXAMPLE <title> when the MAML title contains a user-supplied portion. Added ExtractExampleTitle — a culture-agnostic helper that recovers the user title by anchoring on surrounding dashes and the : separator rather than the literal word EXAMPLE, so it works under any UI culture and handles both comment-based decorated headings and compiled-cmdlet MAML titles (which use "Example N: Title" without surrounding dashes). Also fixed a pre-existing bug where functions with exactly one example had their example silently dropped because Get-Help returns a single example as a bare PSObject rather than PSObject[] — the fix normalizes both cases before the loop.
  • test/powershell/Language/Scripting/ScriptHelp.ExampleTitles.Tests.ps1 — New Pester file (28 tests) covering: titled examples, mixed titled/untitled, GetCommentBlock() round-trip, line-comment syntax, edge-case titles (with colons, dashes, titles ending in a dash), ExampleTitles.Count == Examples.Count parity, and backward-compat regression guard.
  • test/powershell/engine/Api/ProxyCommand.Tests.ps1 — Extended with 5 new tests: round-trip fidelity for titled examples, single-example functions (regression for the PSObject vs PSObject[] fix), colon-in-title, dash-in-title, and titled/untitled mixed.

Rendering path: All Get-Help output modes (-Examples, -Detailed, -Full) render titles through the same MamlExampleControl → .AddPropertyExpressionBinding("Title") path, which reads the maml:title XML node verbatim. The WPF help window (HelpParagraphBuilder.AddExamples) also reads the title node directly — no changes were needed in either renderer.

Related issues

…into feature/example-titles-in-comment-help
… tests

- Revert CommentHelpInfo.Examples to ReadOnlyCollection<string>; add parallel ExampleTitles property per issue spec (non-breaking)
- Update HelpCommentsParser to maintain parallel _exampleTitles list
- Make ProxyCommand.ExtractExampleTitle culture-agnostic (anchor on dashes/colon, not the literal English word EXAMPLE)
- Fix \n vs backtick-n in ProxyCommand round-trip tests
- Add tests: line-comment titled examples, ExampleTitles count parity, title ending with dash, untitled-output regression
@MariusStorhaug Marius Storhaug (MariusStorhaug) changed the title 🚀 [Feature]: Comment-based help examples now support optional titles Comment-based help examples now support optional titles May 1, 2026
@MariusStorhaug

Copy link
Copy Markdown
Author

If this is interesting, ill continue with the other repos to align those with the added feature.

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.

Pull request overview

Adds support for optional titles on comment-based help examples using the .EXAMPLE <Title> syntax, and preserves those titles through XML generation and proxy help comment round-tripping.

Changes:

  • Extend comment-based help parsing and XML generation to capture/render optional .EXAMPLE titles.
  • Add a new CommentHelpInfo.ExampleTitles parallel collection and update GetCommentBlock() to round-trip titled examples.
  • Update ProxyCommand.GetHelpComments() to emit .EXAMPLE <title> when a user title can be recovered, and add Pester coverage for the new behavior.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
src/System.Management.Automation/help/HelpCommentsParser.cs Captures .EXAMPLE <title> in the parser and appends titles to the generated example heading.
src/System.Management.Automation/engine/parser/ast.cs Adds CommentHelpInfo.ExampleTitles and updates GetCommentBlock() to emit .EXAMPLE <title> when present.
src/System.Management.Automation/engine/ProxyCommand.cs Extracts user titles from decorated MAML headings and emits .EXAMPLE <title> during proxy help comment generation.
test/powershell/Language/Scripting/ScriptHelp.ExampleTitles.Tests.ps1 New Pester coverage for titled .EXAMPLE parsing, round-trip via GetCommentBlock(), and backward compatibility.
test/powershell/engine/Api/ProxyCommand.Tests.ps1 Adds tests asserting ProxyCommand.GetHelpComments() preserves/emits titled examples and handles edge cases.

Comment thread test/powershell/Language/Scripting/ScriptHelp.ExampleTitles.Tests.ps1 Outdated
Comment thread src/System.Management.Automation/engine/ProxyCommand.cs Outdated

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.

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.

Comment thread test/powershell/engine/Api/ProxyCommand.Tests.ps1 Outdated
Comment thread src/System.Management.Automation/engine/ProxyCommand.cs Outdated
…hen ProxyCommand title round-trip tests

- Updated the XML doc on ExtractExampleTitle to say ':' (matching the
  IndexOf(':') implementation) instead of the misleading ': '.
- Changed the existing round-trip assertion from Should -Not -BeNullOrEmpty
  (trivially true for all examples) to exact title equality comparison.
- Added a dedicated test 'ProxyCommand.GetHelpComments preserves custom
  example titles' that defines a function with titled and untitled examples,
  round-trips through GetHelpComments, and asserts the custom title text
  survives.

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.

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated no new comments.

Use the fixed generated heading border and first colon-space delimiter without culture or example-number matching. Preserve unframed titles and add cross-culture and title-boundary regression coverage.
@MariusStorhaug

Copy link
Copy Markdown
Author

Tested commit 74aba26f1 with Microsoft.PowerShell.PlatyPS 1.0.3. Basic titled examples already export successfully to Markdown, YAML, and MAML.

This exposed two existing title-preservation bugs in platyPS, addressed in PowerShell/platyPS#864. With that fix, all 10 sample example headings survive export unchanged.

The platyPS fix follows the same localization-independent principle: remove decorative borders without interpreting the example label. Unlike proxy comment generation, platyPS retains the complete numbered heading, so it does not split at : .

@MariusStorhaug

Copy link
Copy Markdown
Author

The companion documentation PR is ready for review: MicrosoftDocs/PowerShell-Docs#13272

It documents optional .EXAMPLE <Title> syntax, titled and untitled examples, Get-Help output, and compatibility with older PowerShell versions. The documentation targets 7.7 provisionally; merging it depends on this PR landing and confirmation of the first supporting release.

Comment thread src/System.Management.Automation/engine/ProxyCommand.cs Outdated
Marius Storhaug and others added 3 commits September 8, 2026 13:58
Remove anchored dash-and-space borders without depending on a fixed width. Preserve hyphens in custom titles and leave malformed borders unchanged.

Add regression coverage for border widths, required separator spaces, and title punctuation.

Co-authored-by: Copilot App <[email protected]>
Capture the custom title and outer borders in one non-backtracking regex.
Preserve existing untitled, localized, malformed-heading, and punctuation
behavior.

Add exact round-trip coverage for authored dash borders and internal
hyphens, including line-comment help.

Co-authored-by: Copilot App <[email protected]>
@MariusStorhaug

Marius Storhaug (MariusStorhaug) commented Sep 8, 2026 •

Copy link
Copy Markdown
Author

Simplified the title extraction as suggested, in 24fef03.

ExtractExampleTitle now recovers the title in a single anchored match instead of a border match followed by a separate IndexOf(": ") scan:

Match titleMatch = Regex.Match(title, @"\A-+ .*?(?:: (?<title>.*?))? -+\z", RegexOptions.NonBacktracking);

The outer dash runs and the first : delimiter are handled in one step. RegexOptions.NonBacktracking is used because the equivalent backtracking pattern degrades badly on a malformed heading that has many : delimiters and no closing border; there is a regression case for that input.

Only the generated decoration is removed, so authored punctuation survives intact:

maml:title Emitted directive
-------- EXAMPLE 1: Authentication - As a User -------- .EXAMPLE Authentication - As a User
------ Example: --- Test - This is a title --- ------ .EXAMPLE --- Test - This is a title ---
--- EXAMPLE 1: Step 1: Initialize --- .EXAMPLE Step 1: Initialize
--- EXAMPLE 1 --- .EXAMPLE

Added round-trip coverage for internal hyphens and authored dash borders through both ProxyCommand.GetHelpComments and CommentHelpInfo.GetCommentBlock, plus cases for an empty title after the delimiter and the malformed-heading input above. Verified a real localized heading as well: under de-DE, BEISPIEL 1: --- Test - This is a title --- still yields .EXAMPLE --- Test - This is a title ---.

Both focused files pass locally via Start-PSPester: 239 passed, 0 failed.

Also merged current master (5bbe60c). The two LocProject.Tests.ps1 failures in the previous CI run are unrelated to this PR and reproduce on a clean master checkout with no changes applied:

[-] Validate LocItems in LocProject.json
      Expected: '.pipelines\store\PDPs\PDP\en-US\'
      But was:  '.pipelines\store\PDPs\PDP\'
[-] Validate total resource count
      Expected 150, but got 151.

The .pipelines\store\PDPs\PDP\en-US\PDP.xml entry added in #27966 sets OutputPath to the parent of the en-US folder, which the first assertion rejects, and it is counted in LocItems even though the count is compared against the number of .resx files under src. Tracked separately in #27987.

Comment thread src/System.Management.Automation/engine/ProxyCommand.cs Outdated
Marius Storhaug and others added 4 commits September 9, 2026 08:00
Assert title and body alignment for titled-first, untitled-first, and
three-example interleavings through both GetCommentBlock and proxy comment
generation.

Exercise titles containing hyphens and dash borders next to untitled
examples, where a misaligned index would otherwise go unnoticed.

Co-authored-by: Copilot App <[email protected]>
Add fully titled and fully untitled three-example cases so the layout
permutations span both uniform ends alongside the interleaved ones.

Co-authored-by: Copilot App <[email protected]>
Trim the dash border before the first space and after the last space using
index scans over spans, then split at the first ": " delimiter. Behavior is
unchanged, so authored hyphens and dash borders are still preserved.

Removes the regular expression and its NonBacktracking dependency, which was
only needed to bound backtracking on malformed headings.

Co-authored-by: Copilot App <[email protected]>
Use Trim('-', ' ') to remove the generated dash border, then split at the
first ": " delimiter. A proxy-generated title therefore cannot begin or end
with a dash, and a heading without the delimiter carries no title.

The comment parser is unchanged, so authored titles still round-trip verbatim
through CommentHelpInfo.GetCommentBlock.

Co-authored-by: Copilot App <[email protected]>
Comment thread src/System.Management.Automation/engine/ProxyCommand.cs Outdated
Comment thread src/System.Management.Automation/engine/ProxyCommand.cs
Comment thread src/System.Management.Automation/engine/ProxyCommand.cs Outdated
@microsoft-github-policy-service

Copy link
Copy Markdown
Contributor

This pull request has been automatically marked as Review Needed because it has been there has not been any activity for 7 days.
Maintainer, please provide feedback and/or mark it as Waiting on Author

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

Labels

CL-General Indicates that a PR should be marked as a general cmdlet change in the Change Log PowerShell-Docs needed The PR was reviewed and a PowerShell Docs update is needed Review - Needed The PR is being reviewed WG-Interactive-HelpSystem help infrastructure and formatting of help WG-Reviewed A Working Group has reviewed this and made a recommendation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Supporting Titles in Comment-Based Help Examples for script-based functions

6 participants