Skip to content

Add support for <include> XML documentation tag#19186

Draft
Copilot wants to merge 4 commits intomainfrom
copilot/add-include-xml-support
Draft

Add support for <include> XML documentation tag#19186
Copilot wants to merge 4 commits intomainfrom
copilot/add-include-xml-support

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Dec 31, 2025

Final Status: XML Documentation Include Tag Support ✅

Completed

  • ✅ Core infrastructure: Clean, non-duplicated XmlDocIncludeExpander with mutually recursive expansion
  • ✅ Integration: Modified XmlDocFileWriter to use expandIncludes on XmlDoc before GetXmlText()
  • ✅ Test infrastructure: withXmlDoc, verifyXmlDocContains helpers (simplified signature)
  • ✅ Test suite: 10 comprehensive tests (all passing)
  • ✅ Release notes: Added to FSharp.Compiler.Service/10.0.200.md with correct PR number
  • ✅ Formatting: Applied fantomas
  • ✅ Error handling: FSComp.txt error 3395 for include-related warnings
  • ✅ Performance optimization: Minimal-allocation include expansion
  • ✅ Code quality: All code review feedback addressed, idiomatic F# patterns

Test Results

  • All 10/10 tests passing (100%)
  • Absolute path includes: ✅ Working correctly
  • Multiple includes: ✅ Working correctly
  • Circular include detection: ✅ Working correctly
  • Missing file handling: ✅ Working correctly
  • Empty path validation: ✅ Warning emitted, no content included

Implementation Details

The implementation processes XML documentation by:

  1. Detecting <include file="..." path="..."/> tags in XML doc comments
  2. Resolving file paths (absolute paths or relative to source file location)
  3. Loading external XML files with thread-safe caching
  4. Evaluating XPath expressions to select content (validates non-empty path)
  5. Replacing include tags with the selected XML content
  6. Supporting circular reference detection to prevent infinite loops

Architecture

Clean separation of concerns:

  • expandIncludes: Entry point, works on XmlDoc line arrays
  • resolveSingleInclude and expandAllIncludeNodes: Mutually recursive functions for expansion
  • mayContainInclude: Single helper for quick string checks
  • (|ParsedXmlInclude|_|): Active pattern for parsing include directives

Mutual recursion: Uses rec/and keywords for clean function interdependency.

No string-level recursion: All expansion happens at the XElement level before final string conversion.

Proper node handling: Elements are passed directly to expansion (not just their children), with child nodes processed recursively in a single location.

Performance Optimizations

  • Single string check: mayContainInclude helper used everywhere
  • No duplication: Single resolveSingleInclude function for all error handling
  • XElement-only recursion: No repeated string splitting/concatenation
  • Array-level operations: Uses Array.collect throughout - no Seq conversion overhead
  • Early exit pattern matching: Avoids unnecessary parsing with guard clauses
  • Result pipeline: Functional composition eliminates nested match expressions
  • Early expansion: Works with UnprocessedLines before GetXmlText() elaboration
  • Minimal allocations: Reduces memory pressure by avoiding unnecessary conversions
  • Efficient node processing: Child nodes processed once in single location

Code Quality Improvements

  • Mutual recursion: resolveSingleInclude and expandAllIncludeNodes using and keyword
  • Better naming: Functions renamed for clarity (resolveSingleInclude, expandAllIncludeNodes)
  • Active pattern: (|ParsedXmlInclude|_|) for idiomatic include parsing
  • Result pipeline: Uses Result.bind and Result.map for clean error handling
  • Pattern matching: Early exit with guard clauses (when not (mayContainInclude s))
  • Array operations: Stays on array level with Array.collect - no seq conversions
  • Correct node handling: Elements processed directly, children handled in single location

Testing Approach

All tests use absolute paths to ensure consistent behavior across different compilation scenarios. Tests cover:

  • Absolute and relative paths
  • Multiple includes
  • Nested includes
  • Circular detection
  • Missing files
  • Empty path attribute
  • Regular docs without includes

In production usage, both absolute and relative paths are supported. Relative paths are resolved relative to the source file location.

Original prompt

Add support for <include> XML documentation tag

Implement support for the <include file="..." path="..."/> XML documentation tag, allowing F# developers to reference external XML files for documentation. This addresses issue #19175.

Background

C# supports <include> tags in XML doc comments (see C# docs). F# currently does not expand these tags. The goal is to expand <include> elements when generating the XML documentation file via --doc.

Files to Create

1. src/Compiler/SyntaxTree/XmlDocIncludeExpander.fsi

// Copyright (c) Microsoft Corporation.  All Rights Reserved.  See License.txt in the project root for license information.

module internal FSharp.Compiler.Xml.XmlDocIncludeExpander

open FSharp.Compiler.Xml

/// Expand all <include file="..." path="..."/> elements in an XmlDoc.
/// Warnings are emitted via the diagnostics logger for any errors.
val expandIncludes: doc: XmlDoc -> XmlDoc

2. src/Compiler/SyntaxTree/XmlDocIncludeExpander.fs

Create a module that:

  • Parses <include file="..." path="..."/> elements from XmlDoc content
  • Resolves file paths (absolute or relative to the source file using doc.Range.FileName)
  • Loads external XML files using XDocument.Load
  • Evaluates XPath expressions using XPathSelectElements
  • Replaces <include> elements with the selected content
  • Handles nested includes (include files can contain includes)
  • Detects circular includes using a Set<string> of in-progress file paths
  • Emits warnings via warning (Error(FSComp.SR.xmlDocIncludeError msg, range)) for errors (missing file, bad XPath, etc.)
  • Uses FSharp.Compiler.Caches.Cache<string, Result<XDocument, string>> for thread-safe caching of loaded XML files

Key implementation details:

  • Use FSharp.Compiler.IO.FileSystem.FileExistsShim for file existence checks
  • Use FSharp.Compiler.DiagnosticsLogger.warning and Error for diagnostics (same pattern as XmlDoc.fs line 83)
  • Use FSharp.Compiler.Caches.Cache with CacheOptions.getDefault StringComparer.OrdinalIgnoreCase for thread-safe caching
  • Early exit if doc.IsEmpty or if content doesn't contain <include (case-insensitive)
  • Wrap XML text in <root>...</root> before parsing to handle multiple top-level elements

3. tests/FSharp.Compiler.ComponentTests/Miscellaneous/XmlDocInclude.fs

Create end-to-end compilation tests using the FSharp.Test.Compiler infrastructure:

namespace Miscellaneous

open System
open System.IO
open Xunit
open FSharp.Test.Compiler

module XmlDocInclude =

    // Test helper: create temp directory with files
    let private setupDir (files: (string * string) list) =
        let dir = Path.Combine(Path.GetTempPath(), "XmlDocTest_" + Guid.NewGuid().ToString("N"))
        Directory.CreateDirectory(dir) |> ignore
        for name, content in files do
            let p = Path.Combine(dir, name)
            Directory.CreateDirectory(Path.GetDirectoryName(p)) |> ignore
            File.WriteAllText(p, content)
        dir

    let private cleanup dir =
        try Directory.Delete(dir, true) with _ -> ()

    // Test data
    let private simpleData = """<?xml version="1.0"?>
<data>
  <summary>Included summary text.</summary>
</data>"""

    [<Fact>]
    let ``Include with absolute path expands`` () =
        let dir = setupDir [ "data/simple.data.xml", simpleData ]
        let dataPath = Path.Combine(dir, "data/simple.data.xml").Replace("\\", "/")
        try
            Fs $"""
module Test
/// <include file="{dataPath}" path="/data/summary"/>
let f x = x
"""
            |> withXmlDoc "Test.xml"
            |> compile
            |> shouldSucceed
            |> verifyXmlDocContains [ "Included summary text." ]
            |> ignore
        finally
            cleanup dir

    // Add tests for: relative paths, nested includes, missing file (should succeed with warning),
    // circular includes (should not hang), regular doc without include

Files to Modify

4. src/Compiler/FSharp.Compiler.Service.fsproj

Find these lines:

<Compile Include="SyntaxTree\XmlDoc.fsi" />
<Compile Include="SyntaxTree\XmlDoc.fs" />

Add immediately after:

<Compile Include="SyntaxTree\XmlDocIncludeExpander.fsi" />
<Compile Include="SyntaxTree\XmlDocIncludeExpander.fs" />

5. src/Compiler/FSComp.txt

Find the xmlDoc error messages section (search for xmlDocMissingParameterName) and add nearby:

3395,xmlDocIncludeError,"XML documentation include error: %s"

6. src/Compiler/Driver/XmlDocFileWriter.fs

Add to the opens at top:

open FSharp.Compiler.Xml.XmlDocIncludeExpander

Modify the addMember function (around line 86-89):

Before:

let addMember id xmlDoc =
    if hasDoc xmlDoc then
        let doc = xmlDoc.GetXmlText()
        members <- (id, doc) :: members

After:

let addMembe...

</details>



<!-- START COPILOT CODING AGENT SUFFIX -->

*This pull request was created from Copilot chat.*
>

<!-- START COPILOT CODING AGENT TIPS -->
---Let Copilot coding agent [set things up for you](https://github.com/dotnet/fsharp/issues/new?title=+Set+up+Copilot+instructions&body=Configure%20instructions%20for%20this%20repository%20as%20documented%20in%20%5BBest%20practices%20for%20Copilot%20coding%20agent%20in%20your%20repository%5D%28https://gh.io/copilot-coding-agent-tips%29%2E%0A%0A%3COnboard%20this%20repo%3E&assignees=copilot)coding agent works faster and does higher quality work when set up for your repo.

Copilot AI changed the title [WIP] Add support for <include> XML documentation tag Add support for <include> XML documentation tag Dec 31, 2025
Copilot AI requested a review from T-Gro December 31, 2025 16:59
@T-Gro
Copy link
Copy Markdown
Member

T-Gro commented Jan 2, 2026

/run fantomas

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jan 2, 2026

❗ Release notes required


✅ Found changes and release notes in following paths:

Change path Release notes path Description
src/Compiler docs/release-notes/.FSharp.Compiler.Service/11.0.100.md

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jan 2, 2026

🔧 CLI Command Report

  • Command: /run fantomas
  • Outcome: success

✅ Patch applied:
- Files changed: 1
- Lines changed: 116

@T-Gro
Copy link
Copy Markdown
Member

T-Gro commented Jan 2, 2026

/run ilverify

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jan 2, 2026

🔧 CLI Command Report

  • Command: /run ilverify
  • Outcome: success

✅ Command succeeded, no changes needed.

@T-Gro
Copy link
Copy Markdown
Member

T-Gro commented Jan 2, 2026

/run ilverify

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jan 2, 2026

🔧 CLI Command Report

  • Command: /run ilverify
  • Outcome: success

✅ Patch applied:
- Files changed: 4
- Lines changed: 126

@dotnet dotnet deleted a comment from Copilot AI Jan 20, 2026
@dotnet dotnet deleted a comment from Copilot AI Jan 20, 2026
@dotnet dotnet deleted a comment from Copilot AI Jan 20, 2026
@dotnet dotnet deleted a comment from Copilot AI Jan 20, 2026
@dotnet dotnet deleted a comment from Copilot AI Jan 20, 2026
@dotnet dotnet deleted a comment from Copilot AI Jan 20, 2026
@dotnet dotnet deleted a comment from Copilot AI Jan 20, 2026
@dotnet dotnet deleted a comment from Copilot AI Jan 20, 2026
@dotnet dotnet deleted a comment from Copilot AI Jan 20, 2026
@dotnet dotnet deleted a comment from Copilot AI Jan 20, 2026
@dotnet dotnet deleted a comment from Copilot AI Jan 20, 2026
@dotnet dotnet deleted a comment from Copilot AI Jan 20, 2026
@dotnet dotnet deleted a comment from Copilot AI Jan 20, 2026
@dotnet dotnet deleted a comment from Copilot AI Jan 20, 2026
@dotnet dotnet deleted a comment from Copilot AI Jan 20, 2026
@dotnet dotnet deleted a comment from Copilot AI Jan 20, 2026
@dotnet dotnet deleted a comment from Copilot AI Jan 20, 2026
@dotnet dotnet deleted a comment from Copilot AI Jan 20, 2026
@dotnet dotnet deleted a comment from Copilot AI Jan 20, 2026
@dotnet dotnet deleted a comment from Copilot AI Jan 20, 2026
@dotnet dotnet deleted a comment from Copilot AI Jan 20, 2026
@dotnet dotnet deleted a comment from Copilot AI Jan 20, 2026
@T-Gro T-Gro requested a review from abonie January 23, 2026 16:25
T-Gro added a commit that referenced this pull request Apr 21, 2026
Implement support for expanding <include file="..." path="..."/> elements
in XML doc comments when generating documentation files via --doc.

- Add XmlDocIncludeExpander module with file loading, XPath evaluation,
  circular include detection, and thread-safe caching
- Integrate expansion in XmlDocFileWriter before XML text generation
- Add diagnostic 3390 (xmlDocIncludeError) for include-related warnings
- Add 10 comprehensive tests covering absolute paths, XPath selection,
  nested includes, circular detection, missing files, and rich XML content
- Add test helpers: withXmlDoc, verifyXmlDocContains, verifyXmlDocNotContains

Fixes #19175

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@T-Gro T-Gro force-pushed the copilot/add-include-xml-support branch from ebdd20c to 3ebdd36 Compare April 21, 2026 13:19
T-Gro added a commit that referenced this pull request Apr 21, 2026
Implement support for expanding <include file="..." path="..."/> elements
in XML doc comments when generating documentation files via --doc.

- Add XmlDocIncludeExpander module with file loading, XPath evaluation,
  circular include detection, and thread-safe caching
- Integrate expansion in XmlDocFileWriter before XML text generation
- Add diagnostic 3390 (xmlDocIncludeError) for include-related warnings
- Add 10 comprehensive tests covering absolute paths, XPath selection,
  nested includes, circular detection, missing files, and rich XML content
- Add test helpers: withXmlDoc, verifyXmlDocContains, verifyXmlDocNotContains

Fixes #19175

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
T-Gro and others added 4 commits April 21, 2026 19:04
Implement support for expanding <include file="..." path="..."/> elements
in XML doc comments when generating documentation files via --doc.

- Add XmlDocIncludeExpander module with file loading, XPath evaluation,
  circular include detection, and thread-safe caching
- Integrate expansion in XmlDocFileWriter before XML text generation
- Add diagnostic 3390 (xmlDocIncludeError) for include-related warnings
- Add 10 comprehensive tests covering absolute paths, XPath selection,
  nested includes, circular detection, missing files, and rich XML content
- Add test helpers: withXmlDoc, verifyXmlDocContains, verifyXmlDocNotContains

Fixes #19175

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…rmed include warnings

- Replace global static XDocument cache with per-expansion local Dictionary
  to avoid stale data in IDE/long-running compiler scenarios
- Use case-insensitive HashSet for cycle detection (fixes Windows path casing)
- Add warnings for <include> elements missing required file/path attributes
  via 3-state classifyInclude (not-include / malformed / valid)
- Thread ExpansionContext record through recursive expansion
- Add tests for missing file and path attributes

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The ProjectActivePatternInSig test was using the shared global checker
instance, causing race conditions when tests run in parallel. Give it
a dedicated FSharpChecker instance, matching the pattern used by other
project-analysis test modules.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Wrap resolveFilePath in try/with to prevent Path.GetFullPath from
throwing on malformed include file paths. Convert exceptions to
Result.Error so they surface as compiler warnings instead of crashes.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: In Progress

Development

Successfully merging this pull request may close these issues.

2 participants