From cfe672b21d422b8254b3d5539df50c66c1e8078c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 30 Jul 2025 15:07:05 +0000 Subject: [PATCH 1/4] Initial plan From d30ea01983b59e6c40a063abcf93a6ae4f3cddf5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 30 Jul 2025 15:33:44 +0000 Subject: [PATCH 2/4] Add automated XML documentation validation test for FSharp.Core .fsi files Co-authored-by: T-Gro <46543583+T-Gro@users.noreply.github.com> --- .../FSharp.Core.UnitTests.fsproj | 1 + .../FSharp.Core/XmlDocumentationValidation.fs | 110 ++++++++++++++++++ 2 files changed, 111 insertions(+) create mode 100644 tests/FSharp.Core.UnitTests/FSharp.Core/XmlDocumentationValidation.fs diff --git a/tests/FSharp.Core.UnitTests/FSharp.Core.UnitTests.fsproj b/tests/FSharp.Core.UnitTests/FSharp.Core.UnitTests.fsproj index b51337c3530..16e45542174 100644 --- a/tests/FSharp.Core.UnitTests/FSharp.Core.UnitTests.fsproj +++ b/tests/FSharp.Core.UnitTests/FSharp.Core.UnitTests.fsproj @@ -88,6 +88,7 @@ + diff --git a/tests/FSharp.Core.UnitTests/FSharp.Core/XmlDocumentationValidation.fs b/tests/FSharp.Core.UnitTests/FSharp.Core/XmlDocumentationValidation.fs new file mode 100644 index 00000000000..3010ef7e95f --- /dev/null +++ b/tests/FSharp.Core.UnitTests/FSharp.Core/XmlDocumentationValidation.fs @@ -0,0 +1,110 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information. + +module FSharp.Core.UnitTests.XmlDocumentationValidation + +open System +open System.IO +open System.Text.RegularExpressions +open System.Xml +open Xunit + +/// Extracts XML documentation blocks from F# signature files +let extractXmlDocBlocks (content: string) = + // Regex to match XML documentation comments (/// followed by XML content) + let xmlDocPattern = @"^\s*///\s*(.*)$" + let regex = Regex(xmlDocPattern, RegexOptions.Multiline) + + let lines = content.Split([|'\n'; '\r'|], StringSplitOptions.RemoveEmptyEntries) + let mutable xmlBlocks = [] + let mutable currentBlock = [] + let mutable lineNumber = 0 + + for line in lines do + lineNumber <- lineNumber + 1 + let trimmedLine = line.Trim() + if trimmedLine.StartsWith("///") then + let xmlContent = trimmedLine.Substring(3).Trim() + currentBlock <- (xmlContent, lineNumber) :: currentBlock + else + if not (List.isEmpty currentBlock) then + xmlBlocks <- List.rev currentBlock :: xmlBlocks + currentBlock <- [] + + // Don't forget the last block if file ends with XML comments + if not (List.isEmpty currentBlock) then + xmlBlocks <- List.rev currentBlock :: xmlBlocks + + List.rev xmlBlocks + +/// Validates that XML content is well-formed +let validateXmlBlock (xmlLines: (string * int) list) = + if List.isEmpty xmlLines then + Ok () + else + let xmlContent = xmlLines |> List.map fst |> String.concat "\n" + let firstLineNumber = xmlLines |> List.head |> snd + + // Skip empty or whitespace-only blocks + if String.IsNullOrWhiteSpace(xmlContent) then + Ok () + else + try + // Wrap content in a root element to make it valid XML document + let wrappedXml = sprintf "%s" xmlContent + let doc = XmlDocument() + doc.LoadXml(wrappedXml) + Ok () + with + | :? XmlException as ex -> + Error (sprintf "Line %d: Invalid XML - %s" firstLineNumber ex.Message) + | ex -> + Error (sprintf "Line %d: XML parsing error - %s" firstLineNumber ex.Message) + +/// Gets all .fsi files in FSharp.Core directory +let getFSharpCoreFsiFiles () = + let coreDir = Path.Combine(__SOURCE_DIRECTORY__, "..", "..", "..", "src", "FSharp.Core") + let fullPath = Path.GetFullPath(coreDir) + if Directory.Exists(fullPath) then + Directory.GetFiles(fullPath, "*.fsi", SearchOption.AllDirectories) + |> Array.toList + else + [] + +[] +let ``XML documentation in FSharp.Core fsi files should be well-formed`` () = + let fsiFiles = getFSharpCoreFsiFiles() + + Assert.False(List.isEmpty fsiFiles, "No .fsi files found in FSharp.Core directory") + + let mutable errors = [] + let mutable totalBlocks = 0 + + for fsiFile in fsiFiles do + let relativePath = Path.GetFileName(fsiFile) + try + let content = File.ReadAllText(fsiFile) + let xmlBlocks = extractXmlDocBlocks content + + for xmlBlock in xmlBlocks do + totalBlocks <- totalBlocks + 1 + match validateXmlBlock xmlBlock with + | Ok () -> () + | Error errorMsg -> + let error = sprintf "%s: %s" relativePath errorMsg + errors <- error :: errors + with + | ex -> + let error = sprintf "%s: Failed to read file - %s" relativePath ex.Message + errors <- error :: errors + + // Report statistics + let validBlocks = totalBlocks - List.length errors + let message = sprintf "Validated %d XML documentation blocks in %d .fsi files. %d valid, %d invalid." + totalBlocks (List.length fsiFiles) validBlocks (List.length errors) + + if not (List.isEmpty errors) then + let errorDetails = errors |> List.rev |> String.concat "\n" + Assert.Fail(sprintf "%s\n\nErrors:\n%s" message errorDetails) + else + // This will show in test output for successful runs + Assert.True(true, message) \ No newline at end of file From c8f0f09c69642cf0d6cbe95670238325ee78bf09 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 31 Jul 2025 09:11:20 +0000 Subject: [PATCH 3/4] Add intentional XML documentation error to demonstrate test failure Co-authored-by: T-Gro <46543583+T-Gro@users.noreply.github.com> --- src/FSharp.Core/option.fsi | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/FSharp.Core/option.fsi b/src/FSharp.Core/option.fsi index db381aec8ad..37385b92164 100644 --- a/src/FSharp.Core/option.fsi +++ b/src/FSharp.Core/option.fsi @@ -64,7 +64,12 @@ module Option = /// The input option. /// /// The option if the option is Some, else the result of evaluating . - /// is not evaluated unless is None. + /// is not evaluated unless is None. + /// + /// Additional text here + /// + /// More text + /// /// /// /// From 84e20ecdb279f2b001d9d807f414d9be11c01cb4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 31 Jul 2025 10:28:32 +0000 Subject: [PATCH 4/4] Revert intentional XML documentation error in option.fsi Co-authored-by: T-Gro <46543583+T-Gro@users.noreply.github.com> --- src/FSharp.Core/option.fsi | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/FSharp.Core/option.fsi b/src/FSharp.Core/option.fsi index 37385b92164..db381aec8ad 100644 --- a/src/FSharp.Core/option.fsi +++ b/src/FSharp.Core/option.fsi @@ -64,12 +64,7 @@ module Option = /// The input option. /// /// The option if the option is Some, else the result of evaluating . - /// is not evaluated unless is None. - /// - /// Additional text here - /// - /// More text - /// + /// is not evaluated unless is None. /// /// ///