Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Dec 31, 2025

Implements <include file="..." path="..."/> XML documentation tag support, matching C# functionality. F# developers can now reference external XML files in doc comments.

Changes

Core Implementation

  • XmlDocIncludeExpander module: Parses and expands <include> tags recursively with XPath evaluation
  • Thread-safe file caching using FSharp.Compiler.Caches.Cache with case-insensitive path comparison
  • Circular include detection prevents infinite loops
  • File path resolution: absolute or relative to source file location
  • Diagnostic warnings for missing files, invalid XPath, circular references

Integration

  • XmlDocFileWriter.fs: Calls expandIncludes before writing XML output
  • FSComp.txt: Added error 3395 (xmlDocIncludeError) for include-related warnings

Test Infrastructure

  • Helper functions: withXmlDoc, verifyXmlDocContains, verifyXmlDocNotContains
  • 9 test cases covering absolute/relative paths, nested includes, error conditions

Example Usage

// External file: docs/common.xml
<?xml version="1.0"?>
<docs>
  <summary>This is a reusable summary.</summary>
  <param name="x">The input parameter</param>
</docs>

// F# source file
module MyModule

/// <include file="docs/common.xml" path="/docs/summary"/>
/// <include file="docs/common.xml" path="/docs/param[@name='x']"/>
let myFunction x = x + 1

The generated XML documentation will contain the expanded content from the external file.

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 and others added 3 commits December 31, 2025 16:21
Co-authored-by: T-Gro <46543583+T-Gro@users.noreply.github.com>
Co-authored-by: T-Gro <46543583+T-Gro@users.noreply.github.com>
Co-authored-by: T-Gro <46543583+T-Gro@users.noreply.github.com>
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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: New

Development

Successfully merging this pull request may close these issues.

2 participants