diff --git a/Microsoft.FluentUI-v5.sln b/Microsoft.FluentUI-v5.sln
index cbf9a6c60d..e419f5024a 100644
--- a/Microsoft.FluentUI-v5.sln
+++ b/Microsoft.FluentUI-v5.sln
@@ -43,6 +43,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{02EA
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FluentUI.Samples.WasmStandalone", "examples\Samples\FluentUI.Samples.WasmStandalone\FluentUI.Samples.WasmStandalone.csproj", "{EB38AC75-966B-41BB-A1C5-0CAFE17FE3D5}"
EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{91F72CE5-24E4-432C-A410-360A3C9C8591}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FluentUI.Demo.DocApiGen.IntegrationTests", "examples\Tools\FluentUI.Demo.DocApiGen.IntegrationTests\FluentUI.Demo.DocApiGen.IntegrationTests.csproj", "{E3369070-0374-60BB-075B-38A8D80AEA78}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FluentUI.Demo.DocApiGen.Tests", "examples\Tools\FluentUI.Demo.DocApiGen.Tests\FluentUI.Demo.DocApiGen.Tests.csproj", "{8A85FC00-B688-35AF-962C-2E07C4E02ED1}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -98,6 +104,14 @@ Global
{EB38AC75-966B-41BB-A1C5-0CAFE17FE3D5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EB38AC75-966B-41BB-A1C5-0CAFE17FE3D5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EB38AC75-966B-41BB-A1C5-0CAFE17FE3D5}.Release|Any CPU.Build.0 = Release|Any CPU
+ {E3369070-0374-60BB-075B-38A8D80AEA78}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {E3369070-0374-60BB-075B-38A8D80AEA78}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {E3369070-0374-60BB-075B-38A8D80AEA78}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {E3369070-0374-60BB-075B-38A8D80AEA78}.Release|Any CPU.Build.0 = Release|Any CPU
+ {8A85FC00-B688-35AF-962C-2E07C4E02ED1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {8A85FC00-B688-35AF-962C-2E07C4E02ED1}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {8A85FC00-B688-35AF-962C-2E07C4E02ED1}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {8A85FC00-B688-35AF-962C-2E07C4E02ED1}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -112,13 +126,16 @@ Global
{0252FA12-8398-4A68-8C80-8DFBDF3021FD} = {2A11833A-E392-484A-99DF-A870C0692302}
{958BF092-4CF2-470C-B058-9244496B234F} = {B98A7516-E9B2-4301-B6A3-33656BF4F4D9}
{B98A7516-E9B2-4301-B6A3-33656BF4F4D9} = {F273876F-7528-42B3-BFE8-7CFF8ED1E2A2}
- {2462C5CC-81BC-47AF-85B8-5FADD9E47ADF} = {B98A7516-E9B2-4301-B6A3-33656BF4F4D9}
+ {2462C5CC-81BC-47AF-85B8-5FADD9E47ADF} = {91F72CE5-24E4-432C-A410-360A3C9C8591}
{32466925-47C6-420F-B869-5F922162C3A7} = {B98A7516-E9B2-4301-B6A3-33656BF4F4D9}
{E67B08B6-AEE4-4281-8700-1C87A5A3C11E} = {B98A7516-E9B2-4301-B6A3-33656BF4F4D9}
{F380FA22-53D8-4381-B89B-4047AF544D53} = {A7EC98D2-21E3-4967-8C5A-D62E640305EB}
{D52F6265-A983-46E0-8831-67FA80D95FBE} = {B98A7516-E9B2-4301-B6A3-33656BF4F4D9}
{02EA681E-C7D8-13C7-8484-4AC65E1B71E8} = {F273876F-7528-42B3-BFE8-7CFF8ED1E2A2}
{EB38AC75-966B-41BB-A1C5-0CAFE17FE3D5} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
+ {91F72CE5-24E4-432C-A410-360A3C9C8591} = {B98A7516-E9B2-4301-B6A3-33656BF4F4D9}
+ {E3369070-0374-60BB-075B-38A8D80AEA78} = {91F72CE5-24E4-432C-A410-360A3C9C8591}
+ {8A85FC00-B688-35AF-962C-2E07C4E02ED1} = {91F72CE5-24E4-432C-A410-360A3C9C8591}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {44D95FF7-AEBE-41FB-9D40-CF1E09ADC6BC}
diff --git a/Microsoft.FluentUI-v5.slnx b/Microsoft.FluentUI-v5.slnx
index 5cab1a9f97..bf9c49300d 100644
--- a/Microsoft.FluentUI-v5.slnx
+++ b/Microsoft.FluentUI-v5.slnx
@@ -7,11 +7,14 @@
-
+
+
+
+
diff --git a/examples/Tools/FluentUI.Demo.DocApiGen.IntegrationTests/FluentUI.Demo.DocApiGen.IntegrationTests.csproj b/examples/Tools/FluentUI.Demo.DocApiGen.IntegrationTests/FluentUI.Demo.DocApiGen.IntegrationTests.csproj
new file mode 100644
index 0000000000..4820151457
--- /dev/null
+++ b/examples/Tools/FluentUI.Demo.DocApiGen.IntegrationTests/FluentUI.Demo.DocApiGen.IntegrationTests.csproj
@@ -0,0 +1,45 @@
+
+
+
+ net9.0
+ Exe
+ enable
+ enable
+ latest
+ false
+ false
+
+ $(NoWarn);IDE0005;CA1510
+
+
+
+
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+
+
+
+
+
+
+
+ PreserveNewest
+
+
+
+
diff --git a/examples/Tools/FluentUI.Demo.DocApiGen.IntegrationTests/FluentUIComponentsIntegrationTests.cs b/examples/Tools/FluentUI.Demo.DocApiGen.IntegrationTests/FluentUIComponentsIntegrationTests.cs
new file mode 100644
index 0000000000..a857ae0054
--- /dev/null
+++ b/examples/Tools/FluentUI.Demo.DocApiGen.IntegrationTests/FluentUIComponentsIntegrationTests.cs
@@ -0,0 +1,446 @@
+// ------------------------------------------------------------------------
+// This file is licensed to you under the MIT License.
+// ------------------------------------------------------------------------
+
+using FluentUI.Demo.DocApiGen;
+using FluentUI.Demo.DocApiGen.Abstractions;
+using FluentUI.Demo.DocApiGen.Formatters;
+using FluentUI.Demo.DocApiGen.Generators;
+using System.Reflection;
+using System.Text.Json;
+using Xunit;
+
+namespace FluentUI.Demo.DocApiGen.IntegrationTests;
+
+///
+/// Integration tests using the real Microsoft.FluentUI.AspNetCore.Components.xml file.
+/// These tests validate documentation generation for actual FluentUI components.
+///
+public class FluentUIComponentsIntegrationTests : IDisposable
+{
+ private readonly FileInfo _xmlDocumentation;
+ private readonly string _tempOutputDirectory;
+ private readonly string _xmlPath;
+ private readonly Assembly _fluentUIAssembly;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public FluentUIComponentsIntegrationTests()
+ {
+ _tempOutputDirectory = Path.Combine(Path.GetTempPath(), $"DocApiGen_FluentUI_Tests_{Guid.NewGuid()}");
+ Directory.CreateDirectory(_tempOutputDirectory);
+
+ // Get project root directory
+ var projectRoot = GetProjectRootDirectory();
+ _xmlPath = Path.Combine(projectRoot, "examples", "Tools", "FluentUI.Demo.DocApiGen", "Microsoft.FluentUI.AspNetCore.Components.xml");
+
+ if (!File.Exists(_xmlPath))
+ {
+ throw new FileNotFoundException($"XML documentation file not found at: {_xmlPath}");
+ }
+
+ _xmlDocumentation = new FileInfo(_xmlPath);
+
+ // Load the FluentUI assembly dynamically
+ var fluentUIAssemblyPath = Path.Combine(projectRoot, "src", "Core", "bin", "Debug", "net9.0", "Microsoft.FluentUI.AspNetCore.Components.dll");
+
+ if (!File.Exists(fluentUIAssemblyPath))
+ {
+ // Try alternative path (Release build)
+ fluentUIAssemblyPath = Path.Combine(projectRoot, "src", "Core", "bin", "Release", "net9.0", "Microsoft.FluentUI.AspNetCore.Components.dll");
+
+ if (!File.Exists(fluentUIAssemblyPath))
+ {
+ throw new FileNotFoundException($"FluentUI assembly not found. Please build the Core project first. Looked for: {fluentUIAssemblyPath}");
+ }
+ }
+
+ _fluentUIAssembly = Assembly.LoadFrom(fluentUIAssemblyPath);
+ }
+
+ ///
+ /// Gets the project root directory by walking up from the current directory.
+ ///
+ private static string GetProjectRootDirectory()
+ {
+ var directory = new DirectoryInfo(Directory.GetCurrentDirectory());
+
+ // Look for solution file
+ while (directory != null)
+ {
+ var solutionFiles = directory.GetFiles("*.sln");
+ if (solutionFiles.Length > 0)
+ {
+ return directory.FullName;
+ }
+
+ directory = directory.Parent;
+ }
+
+ throw new InvalidOperationException($"Could not find project root directory. Current directory: {Directory.GetCurrentDirectory()}");
+ }
+
+ ///
+ /// Cleanup temporary files and directories.
+ ///
+ public void Dispose()
+ {
+ if (Directory.Exists(_tempOutputDirectory))
+ {
+ try
+ {
+ Directory.Delete(_tempOutputDirectory, recursive: true);
+ }
+ catch
+ {
+ // Ignore cleanup errors
+ }
+ }
+ }
+
+ #region Summary Mode Tests (New Architecture)
+
+ [Fact]
+ public void SummaryGenerator_ShouldGenerateJsonSuccessfully()
+ {
+ // Arrange
+ var generator = DocumentationGeneratorFactory.CreateSummaryGenerator(_fluentUIAssembly, _xmlDocumentation);
+ var formatter = OutputFormatterFactory.CreateJsonFormatter(useCompactFormat: true);
+
+ // Act
+ var json = generator.Generate(formatter);
+
+ // Assert
+ Assert.NotNull(json);
+ Assert.NotEmpty(json);
+ Assert.Contains("__Generated__", json);
+ Assert.Contains("AssemblyVersion", json);
+ }
+
+ [Fact]
+ public void SummaryGenerator_ShouldGenerateCSharpSuccessfully()
+ {
+ // Arrange
+ var generator = DocumentationGeneratorFactory.CreateSummaryGenerator(_fluentUIAssembly, _xmlDocumentation);
+ var formatter = OutputFormatterFactory.CreateCSharpFormatter();
+
+ // Act
+ var code = generator.Generate(formatter);
+
+ // Assert
+ Assert.NotNull(code);
+ Assert.NotEmpty(code);
+ Assert.Contains("public static class CodeComments", code);
+ Assert.Contains("Mode: Summary", code);
+ Assert.Contains("SummaryData", code);
+ }
+
+ [Fact]
+ public void SummaryGenerator_JsonOutput_ShouldContainFluentUIComponents()
+ {
+ // Arrange
+ var generator = DocumentationGeneratorFactory.CreateSummaryGenerator(_fluentUIAssembly, _xmlDocumentation);
+ var formatter = OutputFormatterFactory.CreateJsonFormatter(useCompactFormat: true);
+
+ // Act
+ var json = generator.Generate(formatter);
+
+ // Assert
+ // Should contain common FluentUI component names
+ Assert.Contains("FluentButton", json);
+ }
+
+ [Fact]
+ public void SummaryGenerator_CSharpOutput_ShouldContainFluentUIComponents()
+ {
+ // Arrange
+ var generator = DocumentationGeneratorFactory.CreateSummaryGenerator(_fluentUIAssembly, _xmlDocumentation);
+ var formatter = OutputFormatterFactory.CreateCSharpFormatter();
+
+ // Act
+ var code = generator.Generate(formatter);
+
+ // Assert
+ // Should contain common FluentUI component names
+ Assert.Contains("FluentButton", code);
+ }
+
+ [Fact]
+ public void SummaryGenerator_JsonOutput_ShouldBeValidJson()
+ {
+ // Arrange
+ var generator = DocumentationGeneratorFactory.CreateSummaryGenerator(_fluentUIAssembly, _xmlDocumentation);
+ var formatter = OutputFormatterFactory.CreateJsonFormatter(useCompactFormat: true);
+
+ // Act
+ var json = generator.Generate(formatter);
+
+ // Assert - Verify it's valid JSON
+ var exception = Record.Exception(() => JsonDocument.Parse(json));
+ Assert.Null(exception);
+ }
+
+ [Fact]
+ public void SummaryGenerator_SaveToFile_JsonShouldSucceed()
+ {
+ // Arrange
+ var generator = DocumentationGeneratorFactory.CreateSummaryGenerator(_fluentUIAssembly, _xmlDocumentation);
+ var formatter = OutputFormatterFactory.CreateJsonFormatter(useCompactFormat: true);
+ var outputPath = Path.Combine(_tempOutputDirectory, "fluentui_summary.json");
+
+ // Act
+ generator.SaveToFile(outputPath, formatter);
+
+ // Assert
+ Assert.True(File.Exists(outputPath));
+
+ var content = File.ReadAllText(outputPath);
+ Assert.NotEmpty(content);
+ Assert.Contains("__Generated__", content);
+
+ // Verify valid JSON
+ var exception = Record.Exception(() => JsonDocument.Parse(content));
+ Assert.Null(exception);
+ }
+
+ [Fact]
+ public void SummaryGenerator_SaveToFile_CSharpShouldSucceed()
+ {
+ // Arrange
+ var generator = DocumentationGeneratorFactory.CreateSummaryGenerator(_fluentUIAssembly, _xmlDocumentation);
+ var formatter = OutputFormatterFactory.CreateCSharpFormatter();
+ var outputPath = Path.Combine(_tempOutputDirectory, "fluentui_summary.cs");
+
+ // Act
+ generator.SaveToFile(outputPath, formatter);
+
+ // Assert
+ Assert.True(File.Exists(outputPath));
+
+ var content = File.ReadAllText(outputPath);
+ Assert.NotEmpty(content);
+ Assert.Contains("public static class CodeComments", content);
+ }
+
+ [Fact]
+ public void SummaryGenerator_LargeScale_ShouldCompleteWithoutErrors()
+ {
+ // Arrange
+ var generator = DocumentationGeneratorFactory.CreateSummaryGenerator(_fluentUIAssembly, _xmlDocumentation);
+ var jsonFormatter = OutputFormatterFactory.CreateJsonFormatter(useCompactFormat: true);
+ var csharpFormatter = OutputFormatterFactory.CreateCSharpFormatter();
+
+ // Act & Assert - Should complete without throwing
+ var exception = Record.Exception(() =>
+ {
+ var json = generator.Generate(jsonFormatter);
+ Assert.NotNull(json);
+ Assert.NotEmpty(json);
+
+ var code = generator.Generate(csharpFormatter);
+ Assert.NotNull(code);
+ Assert.NotEmpty(code);
+ });
+
+ Assert.Null(exception);
+ }
+
+ [Fact]
+ public void SummaryGenerator_OutputSize_ShouldBeReasonable()
+ {
+ // Arrange
+ var generator = DocumentationGeneratorFactory.CreateSummaryGenerator(_fluentUIAssembly, _xmlDocumentation);
+ var jsonFormatter = OutputFormatterFactory.CreateJsonFormatter(useCompactFormat: true);
+ var csharpFormatter = OutputFormatterFactory.CreateCSharpFormatter();
+
+ // Act
+ var json = generator.Generate(jsonFormatter);
+ var code = generator.Generate(csharpFormatter);
+
+ // Assert - Output should be substantial but not excessive
+ Assert.True(json.Length > 1000, "JSON output should be substantial");
+ Assert.True(json.Length < 50_000_000, "JSON output should not be excessive");
+
+ Assert.True(code.Length > 1000, "C# output should be substantial");
+ Assert.True(code.Length < 50_000_000, "C# output should not be excessive");
+ }
+
+ [Fact]
+ public void SummaryGenerator_JsonMetadata_ShouldBePresent()
+ {
+ // Arrange
+ var generator = DocumentationGeneratorFactory.CreateSummaryGenerator(_fluentUIAssembly, _xmlDocumentation);
+ var formatter = OutputFormatterFactory.CreateJsonFormatter(useCompactFormat: true);
+
+ // Act
+ var json = generator.Generate(formatter);
+ using var doc = JsonDocument.Parse(json);
+
+ // Assert
+ var root = doc.RootElement;
+ Assert.True(root.TryGetProperty("__Generated__", out var generated));
+
+ Assert.True(generated.TryGetProperty("AssemblyVersion", out _));
+ Assert.True(generated.TryGetProperty("DateUtc", out _));
+ }
+
+ #endregion
+
+ #region Summary Mode Tests - Compact Format (Standard)
+
+ [Fact]
+ public void SummaryGenerator_CompactFormat_ShouldGenerateCorrectStructure()
+ {
+ // Arrange
+ var generator = DocumentationGeneratorFactory.CreateSummaryGenerator(_fluentUIAssembly, _xmlDocumentation);
+ var formatter = OutputFormatterFactory.CreateJsonFormatter(useCompactFormat: true);
+
+ // Act
+ var json = generator.Generate(formatter);
+
+ // Assert
+ Assert.NotNull(json);
+ Assert.NotEmpty(json);
+ Assert.Contains("__Generated__", json);
+ Assert.Contains("AssemblyVersion", json);
+ Assert.Contains("DateUtc", json);
+ Assert.Contains("FluentButton", json);
+ }
+
+ [Fact]
+ public void SummaryGenerator_CompactFormat_ShouldBeValidJson()
+ {
+ // Arrange
+ var generator = DocumentationGeneratorFactory.CreateSummaryGenerator(_fluentUIAssembly, _xmlDocumentation);
+ var formatter = OutputFormatterFactory.CreateJsonFormatter(useCompactFormat: true);
+
+ // Act
+ var json = generator.Generate(formatter);
+
+ // Assert - Verify it's valid JSON
+ var exception = Record.Exception(() => JsonDocument.Parse(json));
+ Assert.Null(exception);
+ }
+
+ [Fact]
+ public void SummaryGenerator_CompactFormat_SaveToFile_ShouldSucceed()
+ {
+ // Arrange
+ var generator = DocumentationGeneratorFactory.CreateSummaryGenerator(_fluentUIAssembly, _xmlDocumentation);
+ var formatter = OutputFormatterFactory.CreateJsonFormatter(useCompactFormat: true);
+ var outputPath = Path.Combine(_tempOutputDirectory, "fluentui_compact.json");
+
+ // Act
+ generator.SaveToFile(outputPath, formatter);
+
+ // Assert
+ Assert.True(File.Exists(outputPath));
+
+ var content = File.ReadAllText(outputPath);
+ Assert.NotEmpty(content);
+ Assert.Contains("__Generated__", content);
+
+ // Verify valid JSON
+ var exception = Record.Exception(() => JsonDocument.Parse(content));
+ Assert.Null(exception);
+ }
+
+ #endregion
+
+ #region Summary Mode Tests - Structured Format (Extended)
+
+ [Fact]
+ public void SummaryGenerator_StructuredFormat_ShouldContainMetadata()
+ {
+ // Arrange
+ var generator = DocumentationGeneratorFactory.CreateSummaryGenerator(_fluentUIAssembly, _xmlDocumentation);
+ var formatter = OutputFormatterFactory.CreateJsonFormatter(useCompactFormat: false);
+
+ // Act
+ var json = generator.Generate(formatter);
+ using var doc = JsonDocument.Parse(json);
+
+ // Assert
+ var root = doc.RootElement;
+ Assert.True(root.TryGetProperty("metadata", out var metadata));
+ Assert.True(metadata.TryGetProperty("assemblyVersion", out _));
+ Assert.True(metadata.TryGetProperty("dateUtc", out _));
+ Assert.True(metadata.TryGetProperty("mode", out var mode));
+ Assert.Equal("Summary", mode.GetString());
+ }
+
+ #endregion
+
+ #region All Mode Tests
+
+ [Fact]
+ public void AllGenerator_ShouldGenerateJsonSuccessfully()
+ {
+ // Arrange
+ var generator = DocumentationGeneratorFactory.CreateAllGenerator(_fluentUIAssembly, _xmlDocumentation);
+ var formatter = OutputFormatterFactory.CreateJsonFormatter(useCompactFormat: false);
+
+ // Act
+ var json = generator.Generate(formatter);
+
+ // Assert
+ Assert.NotNull(json);
+ Assert.NotEmpty(json);
+ Assert.Contains("metadata", json);
+ Assert.Contains("components", json);
+ }
+
+ [Fact]
+ public void AllGenerator_ShouldNotSupportCSharpFormat()
+ {
+ // Arrange
+ var generator = DocumentationGeneratorFactory.CreateAllGenerator(_fluentUIAssembly, _xmlDocumentation);
+ var formatter = OutputFormatterFactory.CreateCSharpFormatter();
+
+ // Act & Assert
+ var exception = Assert.Throws(() => generator.Generate(formatter));
+ Assert.Contains("only supports JSON format", exception.Message);
+ }
+
+ [Fact]
+ public void AllGenerator_JsonOutput_ShouldContainComponents()
+ {
+ // Arrange
+ var generator = DocumentationGeneratorFactory.CreateAllGenerator(_fluentUIAssembly, _xmlDocumentation);
+ var formatter = OutputFormatterFactory.CreateJsonFormatter(useCompactFormat: false);
+
+ // Act
+ var json = generator.Generate(formatter);
+ using var doc = JsonDocument.Parse(json);
+
+ // Assert
+ var root = doc.RootElement;
+ Assert.True(root.TryGetProperty("components", out var components));
+ Assert.True(components.GetArrayLength() > 0);
+ }
+
+ [Fact]
+ public void AllGenerator_SaveToFile_ShouldSucceed()
+ {
+ // Arrange
+ var generator = DocumentationGeneratorFactory.CreateAllGenerator(_fluentUIAssembly, _xmlDocumentation);
+ var formatter = OutputFormatterFactory.CreateJsonFormatter(useCompactFormat: false);
+ var outputPath = Path.Combine(_tempOutputDirectory, "fluentui_all.json");
+
+ // Act
+ generator.SaveToFile(outputPath, formatter);
+
+ // Assert
+ Assert.True(File.Exists(outputPath));
+
+ var content = File.ReadAllText(outputPath);
+ Assert.NotEmpty(content);
+
+ // Verify valid JSON
+ var exception = Record.Exception(() => JsonDocument.Parse(content));
+ Assert.Null(exception);
+ }
+
+ #endregion
+}
diff --git a/examples/Tools/FluentUI.Demo.DocApiGen.IntegrationTests/GlobalUsings.cs b/examples/Tools/FluentUI.Demo.DocApiGen.IntegrationTests/GlobalUsings.cs
new file mode 100644
index 0000000000..4ab42991e3
--- /dev/null
+++ b/examples/Tools/FluentUI.Demo.DocApiGen.IntegrationTests/GlobalUsings.cs
@@ -0,0 +1,7 @@
+// ------------------------------------------------------------------------
+// This file is licensed to you under the MIT License.
+// ------------------------------------------------------------------------
+
+global using Xunit;
+global using System;
+global using System.IO;
diff --git a/examples/Tools/FluentUI.Demo.DocApiGen.Tests/FluentUI.Demo.DocApiGen.Tests.csproj b/examples/Tools/FluentUI.Demo.DocApiGen.Tests/FluentUI.Demo.DocApiGen.Tests.csproj
new file mode 100644
index 0000000000..232fe53585
--- /dev/null
+++ b/examples/Tools/FluentUI.Demo.DocApiGen.Tests/FluentUI.Demo.DocApiGen.Tests.csproj
@@ -0,0 +1,35 @@
+
+
+
+ net9.0
+ Exe
+ enable
+ enable
+ latest
+ true
+ $(NoWarn);CS1591
+ false
+
+
+
+
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+
+
+
diff --git a/examples/Tools/FluentUI.Demo.DocApiGen.Tests/Models/AllMode/ComponentInfoTests.cs b/examples/Tools/FluentUI.Demo.DocApiGen.Tests/Models/AllMode/ComponentInfoTests.cs
new file mode 100644
index 0000000000..8e18664022
--- /dev/null
+++ b/examples/Tools/FluentUI.Demo.DocApiGen.Tests/Models/AllMode/ComponentInfoTests.cs
@@ -0,0 +1,247 @@
+// ------------------------------------------------------------------------
+// This file is licensed to you under the MIT License.
+// ------------------------------------------------------------------------
+
+using Xunit;
+using FluentUI.Demo.DocApiGen.Models.AllMode;
+
+namespace FluentUI.Demo.DocApiGen.Tests.Models.AllMode;
+
+///
+/// Unit tests for .
+///
+public class ComponentInfoTests
+{
+ [Fact]
+ public void Constructor_ShouldInitializeWithDefaults()
+ {
+ // Arrange & Act
+ var component = new ComponentInfo();
+
+ // Assert
+ Assert.Equal(string.Empty, component.Name);
+ Assert.Equal(string.Empty, component.FullName);
+ Assert.Null(component.Summary);
+ Assert.Null(component.Category);
+ Assert.False(component.IsGeneric);
+ Assert.Null(component.BaseClass);
+ Assert.Null(component.Properties);
+ Assert.Null(component.Events);
+ Assert.Null(component.Methods);
+ }
+
+ [Fact]
+ public void Name_ShouldBeSettable()
+ {
+ // Arrange
+ var component = new ComponentInfo
+ {
+ // Act
+ Name = "FluentButton"
+ };
+
+ // Assert
+ Assert.Equal("FluentButton", component.Name);
+ }
+
+ [Fact]
+ public void FullName_ShouldBeSettable()
+ {
+ // Arrange
+ var component = new ComponentInfo
+ {
+ // Act
+ FullName = "Microsoft.FluentUI.AspNetCore.Components.FluentButton"
+ };
+
+ // Assert
+ Assert.Equal("Microsoft.FluentUI.AspNetCore.Components.FluentButton", component.FullName);
+ }
+
+ [Fact]
+ public void Summary_ShouldBeSettable()
+ {
+ // Arrange
+ var component = new ComponentInfo
+ {
+ // Act
+ Summary = "A button component"
+ };
+
+ // Assert
+ Assert.Equal("A button component", component.Summary);
+ }
+
+ [Fact]
+ public void Category_ShouldBeSettable()
+ {
+ // Arrange
+ var component = new ComponentInfo
+ {
+ // Act
+ Category = "Forms"
+ };
+
+ // Assert
+ Assert.Equal("Forms", component.Category);
+ }
+
+ [Fact]
+ public void IsGeneric_ShouldBeSettable()
+ {
+ // Arrange
+ var component = new ComponentInfo
+ {
+ // Act
+ IsGeneric = true
+ };
+
+ // Assert
+ Assert.True(component.IsGeneric);
+ }
+
+ [Fact]
+ public void BaseClass_ShouldBeSettableToNull()
+ {
+ // Arrange
+ var component = new ComponentInfo { BaseClass = "SomeBase" };
+
+ // Act
+ component.BaseClass = null;
+
+ // Assert
+ Assert.Null(component.BaseClass);
+ }
+
+ [Fact]
+ public void BaseClass_ShouldBeSettableToValue()
+ {
+ // Arrange
+ var component = new ComponentInfo
+ {
+ // Act
+ BaseClass = "FluentComponentBase"
+ };
+
+ // Assert
+ Assert.Equal("FluentComponentBase", component.BaseClass);
+ }
+
+ [Fact]
+ public void Properties_ShouldBeSettable()
+ {
+ // Arrange
+ var component = new ComponentInfo();
+ var properties = new List
+ {
+ new() { Name = "Appearance", Type = "Appearance?" },
+ new() { Name = "Disabled", Type = "bool" }
+ };
+
+ // Act
+ component.Properties = properties;
+
+ // Assert
+ Assert.Same(properties, component.Properties);
+ Assert.Equal(2, component.Properties.Count);
+ }
+
+ [Fact]
+ public void Events_ShouldBeSettable()
+ {
+ // Arrange
+ var component = new ComponentInfo();
+ var events = new List
+ {
+ new() { Name = "OnClick", Type = "EventCallback" }
+ };
+
+ // Act
+ component.Events = events;
+
+ // Assert
+ Assert.Same(events, component.Events);
+ Assert.Single(component.Events);
+ }
+
+ [Fact]
+ public void Methods_ShouldBeSettable()
+ {
+ // Arrange
+ var component = new ComponentInfo();
+ var methods = new List
+ {
+ new() { Name = "Focus", ReturnType = "Task" }
+ };
+
+ // Act
+ component.Methods = methods;
+
+ // Assert
+ Assert.Same(methods, component.Methods);
+ Assert.Single(component.Methods);
+ }
+
+ [Fact]
+ public void CompleteObject_ShouldBeConstructedProperly()
+ {
+ // Arrange & Act
+ var component = new ComponentInfo
+ {
+ Name = "FluentButton",
+ FullName = "Microsoft.FluentUI.AspNetCore.Components.FluentButton",
+ Summary = "A button component",
+ Category = "Forms",
+ IsGeneric = false,
+ BaseClass = "FluentComponentBase",
+ Properties =
+ [
+ new PropertyInfo { Name = "Appearance", Type = "Appearance?" }
+ ],
+ Events =
+ [
+ new EventInfo { Name = "OnClick", Type = "EventCallback" }
+ ],
+ Methods =
+ [
+ new MethodInfo { Name = "Focus", ReturnType = "Task" }
+ ]
+ };
+
+ // Assert
+ Assert.Equal("FluentButton", component.Name);
+ Assert.Equal("Microsoft.FluentUI.AspNetCore.Components.FluentButton", component.FullName);
+ Assert.Equal("A button component", component.Summary);
+ Assert.Equal("Forms", component.Category);
+ Assert.False(component.IsGeneric);
+ Assert.Equal("FluentComponentBase", component.BaseClass);
+ Assert.Single(component.Properties);
+ Assert.Single(component.Events);
+ Assert.Single(component.Methods);
+ }
+
+ [Fact]
+ public void Collections_CanBeModifiedAfterConstruction()
+ {
+ // Arrange
+ var component = new ComponentInfo
+ {
+ Properties = [],
+ Events = [],
+ Methods = []
+ };
+
+ // Act
+ component.Properties.Add(new PropertyInfo { Name = "Prop1" });
+ component.Events.Add(new EventInfo { Name = "Event1" });
+ component.Methods.Add(new MethodInfo { Name = "Method1" });
+
+ // Assert
+ Assert.Single(component.Properties);
+ Assert.Single(component.Events);
+ Assert.Single(component.Methods);
+ Assert.Equal("Prop1", component.Properties[0].Name);
+ Assert.Equal("Event1", component.Events[0].Name);
+ Assert.Equal("Method1", component.Methods[0].Name);
+ }
+}
diff --git a/examples/Tools/FluentUI.Demo.DocApiGen.Tests/Models/AllMode/DocumentationMetadataTests.cs b/examples/Tools/FluentUI.Demo.DocApiGen.Tests/Models/AllMode/DocumentationMetadataTests.cs
new file mode 100644
index 0000000000..f3608b6b31
--- /dev/null
+++ b/examples/Tools/FluentUI.Demo.DocApiGen.Tests/Models/AllMode/DocumentationMetadataTests.cs
@@ -0,0 +1,99 @@
+// ------------------------------------------------------------------------
+// This file is licensed to you under the MIT License.
+// ------------------------------------------------------------------------
+
+using Xunit;
+using FluentUI.Demo.DocApiGen.Models.AllMode;
+
+namespace FluentUI.Demo.DocApiGen.Tests.Models.AllMode;
+
+///
+/// Unit tests for .
+///
+public class DocumentationMetadataTests
+{
+ [Fact]
+ public void Constructor_ShouldInitializeWithDefaults()
+ {
+ // Arrange & Act
+ var metadata = new DocumentationMetadata();
+
+ // Assert
+ Assert.Equal(string.Empty, metadata.AssemblyVersion);
+ Assert.Equal(string.Empty, metadata.GeneratedDateUtc);
+ Assert.Equal(0, metadata.ComponentCount);
+ Assert.Equal(0, metadata.EnumCount);
+ }
+
+ [Fact]
+ public void AssemblyVersion_ShouldBeSettable()
+ {
+ // Arrange
+ var metadata = new DocumentationMetadata();
+
+ // Act
+ metadata.AssemblyVersion = "1.2.3";
+
+ // Assert
+ Assert.Equal("1.2.3", metadata.AssemblyVersion);
+ }
+
+ [Fact]
+ public void GeneratedDateUtc_ShouldBeSettable()
+ {
+ // Arrange
+ var metadata = new DocumentationMetadata();
+ var date = "2024-12-01T10:30:00Z";
+
+ // Act
+ metadata.GeneratedDateUtc = date;
+
+ // Assert
+ Assert.Equal(date, metadata.GeneratedDateUtc);
+ }
+
+ [Fact]
+ public void ComponentCount_ShouldBeSettable()
+ {
+ // Arrange
+ var metadata = new DocumentationMetadata();
+
+ // Act
+ metadata.ComponentCount = 42;
+
+ // Assert
+ Assert.Equal(42, metadata.ComponentCount);
+ }
+
+ [Fact]
+ public void EnumCount_ShouldBeSettable()
+ {
+ // Arrange
+ var metadata = new DocumentationMetadata();
+
+ // Act
+ metadata.EnumCount = 15;
+
+ // Assert
+ Assert.Equal(15, metadata.EnumCount);
+ }
+
+ [Fact]
+ public void AllProperties_CanBeSetTogether()
+ {
+ // Arrange & Act
+ var metadata = new DocumentationMetadata
+ {
+ AssemblyVersion = "2.0.0",
+ GeneratedDateUtc = "2024-12-01T12:00:00Z",
+ ComponentCount = 100,
+ EnumCount = 20
+ };
+
+ // Assert
+ Assert.Equal("2.0.0", metadata.AssemblyVersion);
+ Assert.Equal("2024-12-01T12:00:00Z", metadata.GeneratedDateUtc);
+ Assert.Equal(100, metadata.ComponentCount);
+ Assert.Equal(20, metadata.EnumCount);
+ }
+}
diff --git a/examples/Tools/FluentUI.Demo.DocApiGen.Tests/Models/AllMode/DocumentationRootTests.cs b/examples/Tools/FluentUI.Demo.DocApiGen.Tests/Models/AllMode/DocumentationRootTests.cs
new file mode 100644
index 0000000000..bfbb1d1140
--- /dev/null
+++ b/examples/Tools/FluentUI.Demo.DocApiGen.Tests/Models/AllMode/DocumentationRootTests.cs
@@ -0,0 +1,226 @@
+// ------------------------------------------------------------------------
+// This file is licensed to you under the MIT License.
+// ------------------------------------------------------------------------
+
+using Xunit;
+using FluentUI.Demo.DocApiGen.Models.AllMode;
+
+namespace FluentUI.Demo.DocApiGen.Tests.Models.AllMode;
+
+///
+/// Unit tests for .
+///
+public class DocumentationRootTests
+{
+ [Fact]
+ public void Constructor_ShouldInitializeWithDefaults()
+ {
+ // Arrange & Act
+ var root = new DocumentationRoot();
+
+ // Assert
+ Assert.NotNull(root.Metadata);
+ Assert.NotNull(root.Components);
+ Assert.NotNull(root.Enums);
+ Assert.Empty(root.Components);
+ Assert.Empty(root.Enums);
+ }
+
+ [Fact]
+ public void Metadata_ShouldBeSettable()
+ {
+ // Arrange
+ var root = new DocumentationRoot();
+ var metadata = new DocumentationMetadata
+ {
+ AssemblyVersion = "1.0.0",
+ GeneratedDateUtc = "2024-01-01T00:00:00Z",
+ ComponentCount = 10,
+ EnumCount = 5
+ };
+
+ // Act
+ root.Metadata = metadata;
+
+ // Assert
+ Assert.Same(metadata, root.Metadata);
+ Assert.Equal("1.0.0", root.Metadata.AssemblyVersion);
+ Assert.Equal("2024-01-01T00:00:00Z", root.Metadata.GeneratedDateUtc);
+ Assert.Equal(10, root.Metadata.ComponentCount);
+ Assert.Equal(5, root.Metadata.EnumCount);
+ }
+
+ [Fact]
+ public void Components_ShouldBeSettable()
+ {
+ // Arrange
+ var root = new DocumentationRoot();
+ var components = new List
+ {
+ new() { Name = "FluentButton", FullName = "Microsoft.FluentUI.AspNetCore.Components.FluentButton" },
+ new() { Name = "FluentCard", FullName = "Microsoft.FluentUI.AspNetCore.Components.FluentCard" }
+ };
+
+ // Act
+ root.Components = components;
+
+ // Assert
+ Assert.Same(components, root.Components);
+ Assert.Equal(2, root.Components.Count);
+ Assert.Equal("FluentButton", root.Components[0].Name);
+ Assert.Equal("FluentCard", root.Components[1].Name);
+ }
+
+ [Fact]
+ public void Enums_ShouldBeSettable()
+ {
+ // Arrange
+ var root = new DocumentationRoot();
+ var enums = new List
+ {
+ new() { Name = "Appearance", FullName = "Microsoft.FluentUI.AspNetCore.Components.Appearance" },
+ new() { Name = "Color", FullName = "Microsoft.FluentUI.AspNetCore.Components.Color" }
+ };
+
+ // Act
+ root.Enums = enums;
+
+ // Assert
+ Assert.Same(enums, root.Enums);
+ Assert.Equal(2, root.Enums.Count);
+ Assert.Equal("Appearance", root.Enums[0].Name);
+ Assert.Equal("Color", root.Enums[1].Name);
+ }
+
+ [Fact]
+ public void CompleteObject_ShouldBeConstructedProperly()
+ {
+ // Arrange & Act
+ var root = new DocumentationRoot
+ {
+ Metadata = new DocumentationMetadata
+ {
+ AssemblyVersion = "2.0.0",
+ GeneratedDateUtc = "2024-12-01T10:30:00Z",
+ ComponentCount = 2,
+ EnumCount = 1
+ },
+ Components =
+ [
+ new ComponentInfo
+ {
+ Name = "FluentButton",
+ FullName = "Microsoft.FluentUI.AspNetCore.Components.FluentButton",
+ Summary = "A button component",
+ Category = "Forms",
+ IsGeneric = false,
+ BaseClass = "FluentComponentBase",
+ Properties =
+ [
+ new PropertyInfo
+ {
+ Name = "Appearance",
+ Type = "Appearance?",
+ Description = "The appearance of the button",
+ IsParameter = true
+ }
+ ],
+ Events =
+ [
+ new EventInfo
+ {
+ Name = "OnClick",
+ Type = "EventCallback",
+ Description = "Triggered when button is clicked"
+ }
+ ],
+ Methods =
+ [
+ new MethodInfo
+ {
+ Name = "Focus",
+ ReturnType = "Task",
+ Description = "Sets focus on the button"
+ }
+ ]
+ }
+ ],
+ Enums =
+ [
+ new EnumInfo
+ {
+ Name = "Appearance",
+ FullName = "Microsoft.FluentUI.AspNetCore.Components.Appearance",
+ Description = "Defines button appearance styles",
+ Values =
+ [
+ new EnumValueInfo
+ {
+ Name = "Neutral",
+ Value = 0,
+ Description = "Neutral appearance"
+ },
+ new EnumValueInfo
+ {
+ Name = "Accent",
+ Value = 1,
+ Description = "Accent appearance"
+ }
+ ]
+ }
+ ]
+ };
+
+ // Assert
+ Assert.NotNull(root.Metadata);
+ Assert.Equal("2.0.0", root.Metadata.AssemblyVersion);
+ Assert.Equal(2, root.Metadata.ComponentCount);
+ Assert.Equal(1, root.Metadata.EnumCount);
+
+ Assert.Single(root.Components);
+ Assert.Equal("FluentButton", root.Components[0].Name);
+ Assert.Equal("Forms", root.Components[0].Category);
+ Assert.NotNull(root.Components[0].Properties);
+ Assert.Single(root.Components[0].Properties!);
+ Assert.NotNull(root.Components[0].Events);
+ Assert.Single(root.Components[0].Events!);
+ Assert.NotNull(root.Components[0].Methods);
+ Assert.Single(root.Components[0].Methods!);
+
+ Assert.Single(root.Enums);
+ Assert.Equal("Appearance", root.Enums[0].Name);
+ Assert.Equal(2, root.Enums[0].Values.Count);
+ }
+
+ [Fact]
+ public void Components_CanBeModifiedAfterConstruction()
+ {
+ // Arrange
+ var root = new DocumentationRoot();
+
+ // Act
+ root.Components.Add(new ComponentInfo { Name = "Component1" });
+ root.Components.Add(new ComponentInfo { Name = "Component2" });
+
+ // Assert
+ Assert.Equal(2, root.Components.Count);
+ Assert.Equal("Component1", root.Components[0].Name);
+ Assert.Equal("Component2", root.Components[1].Name);
+ }
+
+ [Fact]
+ public void Enums_CanBeModifiedAfterConstruction()
+ {
+ // Arrange
+ var root = new DocumentationRoot();
+
+ // Act
+ root.Enums.Add(new EnumInfo { Name = "Enum1" });
+ root.Enums.Add(new EnumInfo { Name = "Enum2" });
+
+ // Assert
+ Assert.Equal(2, root.Enums.Count);
+ Assert.Equal("Enum1", root.Enums[0].Name);
+ Assert.Equal("Enum2", root.Enums[1].Name);
+ }
+}
diff --git a/examples/Tools/FluentUI.Demo.DocApiGen.Tests/Models/AllMode/EnumInfoTests.cs b/examples/Tools/FluentUI.Demo.DocApiGen.Tests/Models/AllMode/EnumInfoTests.cs
new file mode 100644
index 0000000000..99726b5f0b
--- /dev/null
+++ b/examples/Tools/FluentUI.Demo.DocApiGen.Tests/Models/AllMode/EnumInfoTests.cs
@@ -0,0 +1,145 @@
+// ------------------------------------------------------------------------
+// This file is licensed to you under the MIT License.
+// ------------------------------------------------------------------------
+
+using Xunit;
+using FluentUI.Demo.DocApiGen.Models.AllMode;
+
+namespace FluentUI.Demo.DocApiGen.Tests.Models.AllMode;
+
+///
+/// Unit tests for .
+///
+public class EnumInfoTests
+{
+ [Fact]
+ public void Constructor_ShouldInitializeWithDefaults()
+ {
+ // Arrange & Act
+ var enumInfo = new EnumInfo();
+
+ // Assert
+ Assert.Equal(string.Empty, enumInfo.Name);
+ Assert.Equal(string.Empty, enumInfo.FullName);
+ Assert.Null(enumInfo.Description);
+ Assert.NotNull(enumInfo.Values);
+ Assert.Empty(enumInfo.Values);
+ }
+
+ [Fact]
+ public void Name_ShouldBeSettable()
+ {
+ // Arrange
+ var enumInfo = new EnumInfo();
+
+ // Act
+ enumInfo.Name = "Appearance";
+
+ // Assert
+ Assert.Equal("Appearance", enumInfo.Name);
+ }
+
+ [Fact]
+ public void FullName_ShouldBeSettable()
+ {
+ // Arrange
+ var enumInfo = new EnumInfo();
+
+ // Act
+ enumInfo.FullName = "Microsoft.FluentUI.AspNetCore.Components.Appearance";
+
+ // Assert
+ Assert.Equal("Microsoft.FluentUI.AspNetCore.Components.Appearance", enumInfo.FullName);
+ }
+
+ [Fact]
+ public void Description_ShouldBeSettable()
+ {
+ // Arrange
+ var enumInfo = new EnumInfo();
+
+ // Act
+ enumInfo.Description = "Defines button appearance styles";
+
+ // Assert
+ Assert.Equal("Defines button appearance styles", enumInfo.Description);
+ }
+
+ [Fact]
+ public void Values_ShouldBeSettable()
+ {
+ // Arrange
+ var enumInfo = new EnumInfo();
+ var values = new List
+ {
+ new() { Name = "Neutral", Value = 0 },
+ new() { Name = "Accent", Value = 1 }
+ };
+
+ // Act
+ enumInfo.Values = values;
+
+ // Assert
+ Assert.Same(values, enumInfo.Values);
+ Assert.Equal(2, enumInfo.Values.Count);
+ }
+
+ [Fact]
+ public void CompleteObject_ShouldBeConstructedProperly()
+ {
+ // Arrange & Act
+ var enumInfo = new EnumInfo
+ {
+ Name = "Appearance",
+ FullName = "Microsoft.FluentUI.AspNetCore.Components.Appearance",
+ Description = "Defines button appearance styles",
+ Values =
+ [
+ new EnumValueInfo
+ {
+ Name = "Neutral",
+ Value = 0,
+ Description = "Neutral appearance"
+ },
+ new EnumValueInfo
+ {
+ Name = "Accent",
+ Value = 1,
+ Description = "Accent appearance"
+ },
+ new EnumValueInfo
+ {
+ Name = "Lightweight",
+ Value = 2,
+ Description = "Lightweight appearance"
+ }
+ ]
+ };
+
+ // Assert
+ Assert.Equal("Appearance", enumInfo.Name);
+ Assert.Equal("Microsoft.FluentUI.AspNetCore.Components.Appearance", enumInfo.FullName);
+ Assert.Equal("Defines button appearance styles", enumInfo.Description);
+ Assert.Equal(3, enumInfo.Values.Count);
+ Assert.Equal("Neutral", enumInfo.Values[0].Name);
+ Assert.Equal(0, enumInfo.Values[0].Value);
+ Assert.Equal("Accent", enumInfo.Values[1].Name);
+ Assert.Equal(1, enumInfo.Values[1].Value);
+ }
+
+ [Fact]
+ public void Values_CanBeModifiedAfterConstruction()
+ {
+ // Arrange
+ var enumInfo = new EnumInfo();
+
+ // Act
+ enumInfo.Values.Add(new EnumValueInfo { Name = "Value1", Value = 0 });
+ enumInfo.Values.Add(new EnumValueInfo { Name = "Value2", Value = 1 });
+
+ // Assert
+ Assert.Equal(2, enumInfo.Values.Count);
+ Assert.Equal("Value1", enumInfo.Values[0].Name);
+ Assert.Equal("Value2", enumInfo.Values[1].Name);
+ }
+}
diff --git a/examples/Tools/FluentUI.Demo.DocApiGen.Tests/Models/SummaryMode/ApiClassPerformanceTests.cs b/examples/Tools/FluentUI.Demo.DocApiGen.Tests/Models/SummaryMode/ApiClassPerformanceTests.cs
new file mode 100644
index 0000000000..d6ba59d87c
--- /dev/null
+++ b/examples/Tools/FluentUI.Demo.DocApiGen.Tests/Models/SummaryMode/ApiClassPerformanceTests.cs
@@ -0,0 +1,225 @@
+// ------------------------------------------------------------------------
+// This file is licensed to you under the MIT License.
+// ------------------------------------------------------------------------
+
+using Xunit;
+using FluentUI.Demo.DocApiGen.Models.SummaryMode;
+
+namespace FluentUI.Demo.DocApiGen.Tests.Models.SummaryMode;
+
+///
+/// Performance tests for optimizations.
+///
+public class ApiClassPerformanceTests
+{
+ ///
+ /// Test that abstract types don't cause exceptions during processing.
+ ///
+ [Fact]
+ public void ApiClass_ShouldHandleAbstractTypes_WithoutExceptions()
+ {
+ // Arrange
+ var assembly = typeof(TestAbstractClass).Assembly;
+ var docReader = CreateMockDocReader();
+ var options = new ApiClassOptions(assembly, docReader);
+
+ // Act
+ var exception = Record.Exception(() =>
+ {
+ var apiClass = new ApiClass(typeof(TestAbstractClass), options);
+ var dictionary = apiClass.ToDictionary();
+ });
+
+ // Assert
+ Assert.Null(exception); // Should not throw
+ }
+
+ ///
+ /// Test that interface types don't cause exceptions during processing.
+ ///
+ [Fact]
+ public void ApiClass_ShouldHandleInterfaces_WithoutExceptions()
+ {
+ // Arrange
+ var assembly = typeof(ITestInterface).Assembly;
+ var docReader = CreateMockDocReader();
+ var options = new ApiClassOptions(assembly, docReader);
+
+ // Act
+ var exception = Record.Exception(() =>
+ {
+ var apiClass = new ApiClass(typeof(ITestInterface), options);
+ var dictionary = apiClass.ToDictionary();
+ });
+
+ // Assert
+ Assert.Null(exception); // Should not throw
+ }
+
+ ///
+ /// Test that types with complex constructors don't cause exceptions.
+ ///
+ [Fact]
+ public void ApiClass_ShouldHandleComplexConstructors_WithoutExceptions()
+ {
+ // Arrange
+ var assembly = typeof(TestClassWithComplexConstructor).Assembly;
+ var docReader = CreateMockDocReader();
+ var options = new ApiClassOptions(assembly, docReader);
+
+ // Act
+ var exception = Record.Exception(() =>
+ {
+ var apiClass = new ApiClass(typeof(TestClassWithComplexConstructor), options);
+ var dictionary = apiClass.ToDictionary();
+ });
+
+ // Assert
+ Assert.Null(exception); // Should not throw
+ }
+
+ ///
+ /// Test that concrete types with parameterless constructors work correctly.
+ ///
+ [Fact]
+ public void ApiClass_ShouldHandleConcreteTypes_Successfully()
+ {
+ // Arrange
+ var assembly = typeof(TestConcreteClass).Assembly;
+ var docReader = CreateMockDocReader();
+ var options = new ApiClassOptions(assembly, docReader);
+
+ // Act
+ var apiClass = new ApiClass(typeof(TestConcreteClass), options);
+ var dictionary = apiClass.ToDictionary();
+
+ // Assert
+ Assert.NotNull(dictionary);
+ // Should have at least the public properties
+ Assert.Contains("TestProperty", dictionary.Keys);
+ }
+
+ ///
+ /// Test that processing multiple types doesn't accumulate exceptions.
+ ///
+ [Fact]
+ public void ApiClass_ShouldProcessMultipleTypes_Efficiently()
+ {
+ // Arrange
+ var assembly = typeof(TestAbstractClass).Assembly;
+ var docReader = CreateMockDocReader();
+ var options = new ApiClassOptions(assembly, docReader);
+
+ var types = new[]
+ {
+ typeof(TestAbstractClass),
+ typeof(ITestInterface),
+ typeof(TestConcreteClass),
+ typeof(TestClassWithComplexConstructor)
+ };
+
+ // Act
+ var startTime = DateTime.UtcNow;
+ var processedCount = 0;
+
+ foreach (var type in types)
+ {
+ var exception = Record.Exception(() =>
+ {
+ var apiClass = new ApiClass(type, options);
+ var dictionary = apiClass.ToDictionary();
+ processedCount++;
+ });
+
+ Assert.Null(exception); // Should not throw
+ }
+
+ var elapsed = DateTime.UtcNow - startTime;
+
+ // Assert
+ Assert.Equal(types.Length, processedCount);
+ Assert.True(elapsed.TotalSeconds < 5, $"Processing took {elapsed.TotalSeconds} seconds, expected < 5 seconds");
+ }
+
+ ///
+ /// Creates a mock DocXmlReader for testing.
+ ///
+ private static LoxSmoke.DocXml.DocXmlReader CreateMockDocReader()
+ {
+ // Create a minimal XML documentation file for testing
+ var xmlContent = @"
+
+
+ FluentUI.Demo.DocApiGen.Tests
+
+
+
+";
+
+ var tempFile = Path.GetTempFileName();
+ File.WriteAllText(tempFile, xmlContent);
+
+ return new LoxSmoke.DocXml.DocXmlReader(tempFile);
+ }
+
+ #region Test Types
+
+ ///
+ /// Test abstract class for validation.
+ ///
+ public abstract class TestAbstractClass
+ {
+ ///
+ /// Test property.
+ ///
+ public string? TestProperty { get; set; }
+ }
+
+ ///
+ /// Test interface for validation.
+ ///
+ public interface ITestInterface
+ {
+ ///
+ /// Test property.
+ ///
+ string? TestProperty { get; set; }
+ }
+
+ ///
+ /// Test class with complex constructor.
+ ///
+ public class TestClassWithComplexConstructor
+ {
+ ///
+ /// Test property.
+ ///
+ public string? TestProperty { get; set; }
+
+ ///
+ /// Constructor with required dependencies.
+ ///
+ public TestClassWithComplexConstructor(string requiredParam, int anotherParam)
+ {
+ TestProperty = requiredParam;
+ }
+ }
+
+ ///
+ /// Test concrete class with parameterless constructor.
+ ///
+ public class TestConcreteClass
+ {
+ ///
+ /// Test property.
+ ///
+ public string? TestProperty { get; set; }
+
+ ///
+ /// Test integer property.
+ ///
+ public int TestIntProperty { get; set; }
+ }
+
+ #endregion
+}
diff --git a/examples/Tools/FluentUI.Demo.DocApiGen/Abstractions/IDocumentationGenerator.cs b/examples/Tools/FluentUI.Demo.DocApiGen/Abstractions/IDocumentationGenerator.cs
new file mode 100644
index 0000000000..734b307394
--- /dev/null
+++ b/examples/Tools/FluentUI.Demo.DocApiGen/Abstractions/IDocumentationGenerator.cs
@@ -0,0 +1,46 @@
+// ------------------------------------------------------------------------
+// This file is licensed to you under the MIT License.
+// ------------------------------------------------------------------------
+
+namespace FluentUI.Demo.DocApiGen.Abstractions;
+
+///
+/// Defines the contract for documentation generation.
+///
+public interface IDocumentationGenerator
+{
+ ///
+ /// Gets the generation mode (Summary or All).
+ ///
+ GenerationMode Mode { get; }
+
+ ///
+ /// Generates documentation and returns it in the specified format.
+ ///
+ /// The output formatter to use.
+ /// The formatted documentation string.
+ string Generate(IOutputFormatter formatter);
+
+ ///
+ /// Saves the generated documentation to a file.
+ ///
+ /// The output file path.
+ /// The output formatter to use.
+ void SaveToFile(string filePath, IOutputFormatter formatter);
+}
+
+///
+/// Defines the generation mode.
+///
+public enum GenerationMode
+{
+ ///
+ /// Generate summary documentation with only [Parameter] properties.
+ ///
+ Summary,
+
+ ///
+ /// Generate complete documentation including all properties, methods, and events.
+ ///
+ All
+}
diff --git a/examples/Tools/FluentUI.Demo.DocApiGen/Abstractions/IOutputFormatter.cs b/examples/Tools/FluentUI.Demo.DocApiGen/Abstractions/IOutputFormatter.cs
new file mode 100644
index 0000000000..5fcf19b3cb
--- /dev/null
+++ b/examples/Tools/FluentUI.Demo.DocApiGen/Abstractions/IOutputFormatter.cs
@@ -0,0 +1,23 @@
+// ------------------------------------------------------------------------
+// This file is licensed to you under the MIT License.
+// ------------------------------------------------------------------------
+
+namespace FluentUI.Demo.DocApiGen.Abstractions;
+
+///
+/// Defines the contract for output formatting.
+///
+public interface IOutputFormatter
+{
+ ///
+ /// Gets the format name (e.g., "json", "csharp").
+ ///
+ string FormatName { get; }
+
+ ///
+ /// Formats the documentation data into a string.
+ ///
+ /// The documentation data to format.
+ /// The formatted string.
+ string Format(object data);
+}
diff --git a/examples/Tools/FluentUI.Demo.DocApiGen/ApiClassGenerator.cs b/examples/Tools/FluentUI.Demo.DocApiGen/ApiClassGenerator.cs
deleted file mode 100644
index e0f24b9632..0000000000
--- a/examples/Tools/FluentUI.Demo.DocApiGen/ApiClassGenerator.cs
+++ /dev/null
@@ -1,242 +0,0 @@
-// ------------------------------------------------------------------------
-// This file is licensed to you under the MIT License.
-// ------------------------------------------------------------------------
-
-using FluentUI.Demo.DocApiGen.Extensions;
-using FluentUI.Demo.DocApiGen.Models;
-using System.Globalization;
-using System.Reflection;
-using System.Text;
-
-namespace FluentUI.Demo.DocApiGen;
-
-///
-/// Engine to generate the documentation classes.
-///
-public class ApiClassGenerator
-{
- ///
- /// Initializes a new instance of the class.
- ///
- ///
- ///
- public ApiClassGenerator(Assembly assembly, FileInfo xmlDocumentation)
- {
- Assembly = assembly;
- DocXmlReader = new LoxSmoke.DocXml.DocXmlReader(xmlDocumentation.FullName);
- }
-
- ///
- /// Gets the assembly to generate the documentation.
- ///
- public Assembly Assembly { get; }
-
- ///
- /// Gets the summary reader.
- ///
- public LoxSmoke.DocXml.DocXmlReader DocXmlReader { get; }
-
- ///
- /// Gets the for the specified component.
- ///
- ///
- ///
- public ApiClass FromTypeName(Type type)
- {
- var options = new ApiClassOptions(Assembly, DocXmlReader)
- {
- PropertyParameterOnly = false,
- };
-
- return new ApiClass(type, options);
- }
-
- ///
- /// Generates the C# code for the documentation.
- ///
- ///
- [System.Diagnostics.CodeAnalysis.SuppressMessage("Globalization", "CA1305:Specify IFormatProvider", Justification = "Not necessary")]
- public string GenerateCSharp()
- {
- var code = new StringBuilder();
- var assemblyInfo = GetAssemblyInfo(Assembly);
-
- code.AppendLine("// ------------------------------------------------------------------------");
- code.AppendLine("// This file is licensed to you under the MIT License. ");
- code.AppendLine("// ------------------------------------------------------------------------");
- code.AppendLine();
- code.AppendLine("//------------------------------------------------------------------------------");
- code.AppendLine("// ");
- code.AppendLine("// This code was generated by a tool.");
- code.AppendLine("//");
- code.AppendLine("// Changes to this file may cause incorrect behavior and will be lost if");
- code.AppendLine("// the code is regenerated.");
- code.AppendLine("//");
- code.AppendLine("// Version: " + assemblyInfo.Version + " - " + assemblyInfo.Date);
- code.AppendLine("// ");
- code.AppendLine("//------------------------------------------------------------------------------");
- code.AppendLine();
- code.AppendLine("using System.Reflection;");
- code.AppendLine();
- code.AppendLine("/// ");
- code.AppendLine("public class CodeComments");
- code.AppendLine("{");
-
- code.AppendLine(" /// ");
- code.AppendLine(" public static readonly IDictionary> SummaryData = new Dictionary>");
- code.AppendLine(" {");
-
- foreach (var type in Assembly.GetTypes().Where(i => i.IsValidType()))
- {
- var apiClass = FromTypeName(type);
- var apiClassMembers = apiClass.ToDictionary();
-
- if (apiClassMembers.Any())
- {
- code.AppendLine($" {{ \"{apiClass.Name}\", new Dictionary");
- code.AppendLine($" {{");
- code.AppendLine($" {{ \"__summary__\", \"{FormatDescription(apiClass.Summary)}\" }},");
-
- foreach (var member in apiClass.ToDictionary())
- {
- code.AppendLine($" {{ \"{member.Key}\", \"{FormatDescription(member.Value)}\" }},");
- }
-
- code.AppendLine($" }}");
- code.AppendLine($" }},");
- }
- }
-
- code.AppendLine(" };");
- code.AppendLine();
- code.AppendLine(" /// ");
- code.AppendLine(" public static string GetSignature(MemberInfo member)");
- code.AppendLine(" {");
- code.AppendLine(" return member.MemberType == MemberTypes.Method");
- code.AppendLine(" ? $\"{member.Name}({string.Join(\", \", ((MethodInfo)member).GetParameters().Select(p => p.ParameterType.Name))})\"");
- code.AppendLine(" : member.Name;");
- code.AppendLine(" }");
- code.AppendLine();
- code.AppendLine(" /// ");
- code.AppendLine(" public static string GetSummary(MemberInfo member)");
- code.AppendLine(" {");
- code.AppendLine(" var name = member.Name;");
- code.AppendLine(" var signature = GetSignature(member);");
- code.AppendLine();
- code.AppendLine(" return SummaryData.TryGetValue(name, out var comments) && comments.TryGetValue(signature, out var comment)");
- code.AppendLine(" ? comment");
- code.AppendLine(" : string.Empty;");
- code.AppendLine(" }");
- code.AppendLine("}");
- code.AppendLine();
-
- return code.ToString();
- }
-
- ///
- /// Generates the JSON for the documentation.
- ///
- ///
- [System.Diagnostics.CodeAnalysis.SuppressMessage("Globalization", "CA1305:Specify IFormatProvider", Justification = "Not necessary")]
- public string GenerateJson()
- {
- var code = new StringBuilder();
- var assemblyInfo = GetAssemblyInfo(Assembly);
-
- code.AppendLine("{");
- code.AppendLine($" \"__Generated__\": {{");
- code.AppendLine($" \"AssemblyVersion\": \"{assemblyInfo.Version}\",");
- code.AppendLine($" \"DateUtc\": \"{assemblyInfo.Date}\"");
- code.AppendLine($" }},");
-
- foreach (var type in Assembly.GetTypes().Where(i => i.IsValidType()))
- {
- var apiClass = FromTypeName(type);
- var apiClassMembers = apiClass.ToDictionary();
-
- if (apiClassMembers.Any())
- {
- code.AppendLine($" \"{apiClass.Name}\": {{");
- code.AppendLine($" \"__summary__\": \"{FormatDescription(apiClass.Summary)}\",");
-
- foreach (var member in apiClass.ToDictionary())
- {
- code.AppendLine($" \"{member.Key}\": \"{FormatDescription(member.Value)}\",");
- }
-
- RemoveLastComma(code); // Remove the last comma
- code.AppendLine($" }},");
- }
- }
-
- RemoveLastComma(code); // Remove the last comma
- code.AppendLine("}");
- code.AppendLine();
-
- return code.ToString();
- }
-
- ///
- /// Saves the documentation to a file.
- ///
- ///
- ///
- public void SaveToFile(string fileName, string format)
- {
- if (File.Exists(fileName))
- {
- File.Delete(fileName);
- }
-
- if (format == "json")
- {
- File.WriteAllText(fileName, GenerateJson());
- }
- else
- {
- File.WriteAllText(fileName, GenerateCSharp());
- }
- }
-
- ///
- private static string FormatDescription(string description)
- {
- return description.Replace("\r\n", " ").Replace("\n", " ").Replace("\"", "\\\"");
- }
-
- ///
- private static void RemoveLastComma(StringBuilder sb)
- {
- if (sb == null || sb.Length == 0)
- {
- return;
- }
-
- var lastIndex = sb.ToString().LastIndexOf(',');
- sb.Remove(lastIndex, sb.Length - lastIndex);
- sb.AppendLine();
- }
-
- internal static (string Version, string Date) GetAssemblyInfo(Assembly assembly)
- {
- // Assembly version
- string strVersion = default!;
- var versionAttribute = assembly.GetCustomAttribute();
- if (versionAttribute != null)
- {
- var version = versionAttribute.InformationalVersion;
- var plusIndex = version.IndexOf('+');
- if (plusIndex >= 0 && plusIndex + 9 < version.Length)
- {
- strVersion = version[..(plusIndex + 9)];
- }
- else
- {
- strVersion = version;
- }
- }
-
- // Date
- return (strVersion, DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm", CultureInfo.InvariantCulture));
- }
-}
diff --git a/examples/Tools/FluentUI.Demo.DocApiGen/Extensions/ReflectionExtensions.cs b/examples/Tools/FluentUI.Demo.DocApiGen/Extensions/ReflectionExtensions.cs
index 37347e7749..bcb94dc561 100644
--- a/examples/Tools/FluentUI.Demo.DocApiGen/Extensions/ReflectionExtensions.cs
+++ b/examples/Tools/FluentUI.Demo.DocApiGen/Extensions/ReflectionExtensions.cs
@@ -43,7 +43,10 @@ public static bool IsValidType(this Type type)
type.BaseType != typeof(Regex) &&
type.BaseType?.Name != "Icon" &&
type.IsAbstract == false &&
- type.Name.EndsWith("_g") == false;
+ !type.Name.EndsWith("_g") &&
+ !type.Name.Contains('+') && // Exclude nested types
+ !type.IsInterface &&
+ type.IsPublic;
}
///
diff --git a/examples/Tools/FluentUI.Demo.DocApiGen/Formatters/CSharpOutputFormatter.cs b/examples/Tools/FluentUI.Demo.DocApiGen/Formatters/CSharpOutputFormatter.cs
new file mode 100644
index 0000000000..6114b7c7d8
--- /dev/null
+++ b/examples/Tools/FluentUI.Demo.DocApiGen/Formatters/CSharpOutputFormatter.cs
@@ -0,0 +1,132 @@
+// ------------------------------------------------------------------------
+// This file is licensed to you under the MIT License.
+// ------------------------------------------------------------------------
+
+using System.Text;
+using FluentUI.Demo.DocApiGen.Abstractions;
+using FluentUI.Demo.DocApiGen.Models.SummaryMode;
+
+namespace FluentUI.Demo.DocApiGen.Formatters;
+
+///
+/// Formats documentation output as C# code.
+/// Only supports Summary mode data.
+///
+public class CSharpOutputFormatter : IOutputFormatter
+{
+ ///
+ public string FormatName => "csharp";
+
+ ///
+ public string Format(object data)
+ {
+ ArgumentNullException.ThrowIfNull(data);
+
+ if (data is not SummaryDocumentationData summaryData)
+ {
+ throw new InvalidOperationException(
+ $"CSharpOutputFormatter only supports SummaryDocumentationData. Received: {data.GetType().Name}");
+ }
+
+ return GenerateCSharpCode(summaryData);
+ }
+
+ private static string GenerateCSharpCode(SummaryDocumentationData data)
+ {
+ var sb = new StringBuilder();
+
+ // License header
+ sb.AppendLine("// ------------------------------------------------------------------------");
+ sb.AppendLine("// This file is licensed to you under the MIT License.");
+ sb.AppendLine("// ------------------------------------------------------------------------");
+ sb.AppendLine();
+
+ // Auto-generated comment
+ sb.AppendLine("// ");
+#pragma warning disable CA1305 // Specify IFormatProvider
+ sb.AppendLine($"// This code was generated by a tool on {data.Metadata.DateUtc}.");
+ sb.AppendLine($"// Assembly: {data.Metadata.AssemblyVersion}");
+ sb.AppendLine($"// Mode: {data.Metadata.Mode}");
+#pragma warning restore CA1305 // Specify IFormatProvider
+ sb.AppendLine("//");
+ sb.AppendLine("// Changes to this file may cause incorrect behavior and will be lost if");
+ sb.AppendLine("// the code is regenerated.");
+ sb.AppendLine("// ");
+ sb.AppendLine();
+
+ // Using statements
+ sb.AppendLine("using System.Collections.Generic;");
+ sb.AppendLine("using System.Reflection;");
+ sb.AppendLine();
+
+ // Namespace and class
+ sb.AppendLine("namespace FluentUI.Demo.Generated;");
+ sb.AppendLine();
+ sb.AppendLine("/// ");
+ sb.AppendLine("/// Provides access to API documentation comments.");
+ sb.AppendLine("/// ");
+ sb.AppendLine("public static class CodeComments");
+ sb.AppendLine("{");
+
+ // Dictionary
+ sb.AppendLine(" /// ");
+ sb.AppendLine(" /// Dictionary containing all API documentation.");
+ sb.AppendLine(" /// ");
+ sb.AppendLine(" public static readonly IDictionary SummaryData = new Dictionary");
+ sb.AppendLine(" {");
+
+ // Add entries
+ foreach (var entry in data.Components)
+ {
+ var key = EscapeString(entry.Key);
+ var summary = EscapeString(entry.Value.Summary);
+ var signature = EscapeString(entry.Value.Signature);
+
+#pragma warning disable CA1305 // Specify IFormatProvider
+ sb.AppendLine($" {{ \"{key}\", (\"{summary}\", \"{signature}\") }},");
+#pragma warning restore CA1305 // Specify IFormatProvider
+ }
+
+ sb.AppendLine(" };");
+ sb.AppendLine();
+
+ // Helper methods
+ sb.AppendLine(" /// ");
+ sb.AppendLine(" /// Gets the signature for a member.");
+ sb.AppendLine(" /// ");
+ sb.AppendLine(" public static string GetSignature(MemberInfo member)");
+ sb.AppendLine(" {");
+ sb.AppendLine(" var key = $\"{member.DeclaringType?.FullName}.{member.Name}\";");
+ sb.AppendLine(" return SummaryData.TryGetValue(key, out var data) ? data.Signature : string.Empty;");
+ sb.AppendLine(" }");
+ sb.AppendLine();
+
+ sb.AppendLine(" /// ");
+ sb.AppendLine(" /// Gets the summary for a member.");
+ sb.AppendLine(" /// ");
+ sb.AppendLine(" public static string GetSummary(MemberInfo member)");
+ sb.AppendLine(" {");
+ sb.AppendLine(" var key = $\"{member.DeclaringType?.FullName}.{member.Name}\";");
+ sb.AppendLine(" return SummaryData.TryGetValue(key, out var data) ? data.Summary : string.Empty;");
+ sb.AppendLine(" }");
+
+ sb.AppendLine("}");
+
+ return sb.ToString();
+ }
+
+ private static string EscapeString(string value)
+ {
+ if (string.IsNullOrEmpty(value))
+ {
+ return string.Empty;
+ }
+
+ return value
+ .Replace("\\", "\\\\")
+ .Replace("\"", "\\\"")
+ .Replace("\n", "\\n")
+ .Replace("\r", "\\r")
+ .Replace("\t", "\\t");
+ }
+}
diff --git a/examples/Tools/FluentUI.Demo.DocApiGen/Formatters/JsonOutputFormatter.cs b/examples/Tools/FluentUI.Demo.DocApiGen/Formatters/JsonOutputFormatter.cs
new file mode 100644
index 0000000000..3136c62b2c
--- /dev/null
+++ b/examples/Tools/FluentUI.Demo.DocApiGen/Formatters/JsonOutputFormatter.cs
@@ -0,0 +1,155 @@
+// ------------------------------------------------------------------------
+// This file is licensed to you under the MIT License.
+// ------------------------------------------------------------------------
+
+using System.Globalization;
+using System.Text;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using FluentUI.Demo.DocApiGen.Abstractions;
+using FluentUI.Demo.DocApiGen.Models.SummaryMode;
+
+namespace FluentUI.Demo.DocApiGen.Formatters;
+
+///
+/// Formats documentation output as JSON.
+///
+public class JsonOutputFormatter : IOutputFormatter
+{
+ private readonly bool _indented;
+ private readonly bool _useCompactFormat;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Whether to indent the JSON output.
+ /// Whether to use the compact format for Summary mode (default true).
+ public JsonOutputFormatter(bool indented = true, bool useCompactFormat = true)
+ {
+ _indented = indented;
+ _useCompactFormat = useCompactFormat;
+ }
+
+ ///
+ public string FormatName => "json";
+
+ ///
+ public string Format(object data)
+ {
+ ArgumentNullException.ThrowIfNull(data);
+
+ // Si c'est un SummaryDocumentationData et qu'on veut le format compact (Summary mode standard)
+ if (_useCompactFormat && data is SummaryDocumentationData summaryData)
+ {
+ return FormatSummary(summaryData);
+ }
+
+ // Format structuré (pour All mode ou mode étendu)
+ var options = new JsonSerializerOptions
+ {
+ WriteIndented = _indented,
+ PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
+ DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
+ Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping
+ };
+
+ return JsonSerializer.Serialize(data, options);
+ }
+
+ ///
+ /// Formats the documentation data in the Summary mode compact format.
+ /// This format groups members by component type with simple keys.
+ ///
+ private static string FormatSummary(SummaryDocumentationData data)
+ {
+ var sb = new StringBuilder();
+ sb.AppendLine("{");
+
+ // Ajouter les métadonnées
+ sb.AppendLine(" \"__Generated__\": {");
+ sb.AppendLine(CultureInfo.InvariantCulture, $" \"AssemblyVersion\": \"{EscapeJson(data.Metadata.AssemblyVersion)}\",");
+ sb.AppendLine(CultureInfo.InvariantCulture, $" \"DateUtc\": \"{EscapeJson(data.Metadata.DateUtc)}\"");
+ sb.Append(" }");
+
+ // Grouper les composants par type
+ var componentsByType = new Dictionary>();
+
+ foreach (var kvp in data.Components)
+ {
+ // Format de la clé: "Namespace.TypeName.__summary__" ou "Namespace.TypeName.MemberName"
+ var fullKey = kvp.Key;
+ var lastDotIndex = fullKey.LastIndexOf('.');
+
+ if (lastDotIndex == -1)
+ {
+ continue;
+ }
+
+ var beforeLastDot = fullKey[..lastDotIndex];
+ var memberName = fullKey[(lastDotIndex + 1)..];
+
+ // Extraire le nom du type (dernière partie avant le membre)
+ var typeNameStartIndex = beforeLastDot.LastIndexOf('.') + 1;
+ var typeName = beforeLastDot[typeNameStartIndex..];
+
+ if (!componentsByType.TryGetValue(typeName, out var members))
+ {
+ members = [];
+ componentsByType[typeName] = members;
+ }
+
+ members[memberName] = kvp.Value.Summary ?? string.Empty;
+ }
+
+ // Écrire chaque type dans l'ordre
+ foreach (var typeEntry in componentsByType.OrderBy(x => x.Key))
+ {
+ sb.AppendLine(",");
+ sb.AppendLine(CultureInfo.InvariantCulture, $" \"{EscapeJson(typeEntry.Key)}\": {{");
+
+ var membersList = typeEntry.Value.OrderBy(x => x.Key).ToList();
+ for (var i = 0; i < membersList.Count; i++)
+ {
+ var member = membersList[i];
+ var isLast = i == membersList.Count - 1;
+
+ var escapedValue = EscapeJson(member.Value);
+ sb.Append(CultureInfo.InvariantCulture, $" \"{EscapeJson(member.Key)}\": \"{escapedValue}\"");
+
+ if (!isLast)
+ {
+ sb.AppendLine(",");
+ }
+ else
+ {
+ sb.AppendLine();
+ }
+ }
+
+ sb.Append(" }");
+ }
+
+ sb.AppendLine();
+ sb.AppendLine("}");
+
+ return sb.ToString();
+ }
+
+ ///
+ /// Escapes special characters for JSON strings.
+ ///
+ private static string EscapeJson(string value)
+ {
+ if (string.IsNullOrEmpty(value))
+ {
+ return string.Empty;
+ }
+
+ return value
+ .Replace("\\", "\\\\")
+ .Replace("\"", "\\\"")
+ .Replace("\r\n", " ")
+ .Replace("\n", " ")
+ .Replace("\r", " ");
+ }
+}
diff --git a/examples/Tools/FluentUI.Demo.DocApiGen/Formatters/OutputFormatterFactory.cs b/examples/Tools/FluentUI.Demo.DocApiGen/Formatters/OutputFormatterFactory.cs
new file mode 100644
index 0000000000..c537fd6da8
--- /dev/null
+++ b/examples/Tools/FluentUI.Demo.DocApiGen/Formatters/OutputFormatterFactory.cs
@@ -0,0 +1,57 @@
+// ------------------------------------------------------------------------
+// This file is licensed to you under the MIT License.
+// ------------------------------------------------------------------------
+
+using FluentUI.Demo.DocApiGen.Abstractions;
+
+namespace FluentUI.Demo.DocApiGen.Formatters;
+
+///
+/// Factory for creating output formatters.
+///
+public static class OutputFormatterFactory
+{
+ ///
+ /// Creates an output formatter for the specified format name.
+ ///
+ /// The format name ("json" or "csharp").
+ /// Whether to indent the output (JSON only).
+ /// Whether to use compact format for Summary mode (default true).
+ /// An output formatter instance.
+ /// Thrown when formatName is null or empty.
+ /// Thrown when the format is not supported.
+ public static IOutputFormatter Create(string formatName, bool indented = true, bool useCompactFormat = true)
+ {
+ if (string.IsNullOrWhiteSpace(formatName))
+ {
+ throw new ArgumentException("Format name cannot be null or empty.", nameof(formatName));
+ }
+
+ return formatName.ToLowerInvariant() switch
+ {
+ "json" => new JsonOutputFormatter(indented, useCompactFormat),
+ "csharp" or "cs" => new CSharpOutputFormatter(),
+ _ => throw new NotSupportedException($"Output format '{formatName}' is not supported. Supported formats: json, csharp.")
+ };
+ }
+
+ ///
+ /// Creates a JSON output formatter.
+ ///
+ /// Whether to indent the JSON output.
+ /// Whether to use compact format for Summary mode (default true).
+ /// A JSON output formatter.
+ public static IOutputFormatter CreateJsonFormatter(bool indented = true, bool useCompactFormat = true)
+ {
+ return new JsonOutputFormatter(indented, useCompactFormat);
+ }
+
+ ///
+ /// Creates a C# output formatter.
+ ///
+ /// A C# output formatter.
+ public static IOutputFormatter CreateCSharpFormatter()
+ {
+ return new CSharpOutputFormatter();
+ }
+}
diff --git a/examples/Tools/FluentUI.Demo.DocApiGen/Generators/AllDocumentationGenerator.cs b/examples/Tools/FluentUI.Demo.DocApiGen/Generators/AllDocumentationGenerator.cs
new file mode 100644
index 0000000000..b9bace2286
--- /dev/null
+++ b/examples/Tools/FluentUI.Demo.DocApiGen/Generators/AllDocumentationGenerator.cs
@@ -0,0 +1,337 @@
+// ------------------------------------------------------------------------
+// This file is licensed to you under the MIT License.
+// ------------------------------------------------------------------------
+
+using System.Globalization;
+using System.Reflection;
+using FluentUI.Demo.DocApiGen.Abstractions;
+using FluentUI.Demo.DocApiGen.Extensions;
+using FluentUI.Demo.DocApiGen.Models.AllMode;
+using FluentUI.Demo.DocApiGen.Models.SummaryMode;
+
+namespace FluentUI.Demo.DocApiGen.Generators;
+
+///
+/// Generates All mode documentation (complete: all properties, methods, events).
+/// Supports JSON output format only.
+///
+public sealed class AllDocumentationGenerator : DocumentationGeneratorBase
+{
+ private readonly LoxSmoke.DocXml.DocXmlReader _docXmlReader;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The assembly to generate documentation for.
+ /// The XML documentation file.
+ public AllDocumentationGenerator(Assembly assembly, FileInfo xmlDocumentation)
+ : base(assembly, xmlDocumentation)
+ {
+ _docXmlReader = new LoxSmoke.DocXml.DocXmlReader(xmlDocumentation.FullName);
+ }
+
+ ///
+ public override GenerationMode Mode => GenerationMode.All;
+
+ ///
+ public override string Generate(IOutputFormatter formatter)
+ {
+ ArgumentNullException.ThrowIfNull(formatter);
+
+ if (formatter.FormatName != "json")
+ {
+ throw new NotSupportedException(
+ $"AllDocumentationGenerator only supports JSON format. Requested format: {formatter.FormatName}");
+ }
+
+ var data = BuildDocumentationData();
+ return formatter.Format(data);
+ }
+
+ ///
+ /// Builds the complete documentation data structure.
+ ///
+ private DocumentationRoot BuildDocumentationData()
+ {
+ var (version, date) = GetAssemblyInfo();
+ var components = new List();
+ var enums = new List();
+
+ var validTypes = Assembly.GetTypes().Where(IsValidComponentType).ToList();
+ var enumTypes = Assembly.GetTypes().Where(t => t.IsEnum && t.IsPublic).ToList();
+
+ Console.WriteLine($"Processing {validTypes.Count} components and {enumTypes.Count} enums...");
+
+ // Generate components
+ var componentCount = 0;
+ foreach (var type in validTypes)
+ {
+ componentCount++;
+ if (componentCount % 10 == 0)
+ {
+ Console.Write($"\rProcessed {componentCount}/{validTypes.Count} components...");
+ }
+
+ var componentInfo = GenerateComponentInfo(type);
+ if (componentInfo != null)
+ {
+ components.Add(componentInfo);
+ }
+ }
+
+ Console.WriteLine();
+
+ // Generate enums
+ var enumCount = 0;
+ foreach (var type in enumTypes)
+ {
+ enumCount++;
+ enums.Add(GenerateEnumInfo(type));
+ }
+
+ Console.WriteLine($"✓ Processed {validTypes.Count} components and {enumTypes.Count} enums.");
+
+ return new DocumentationRoot
+ {
+ Metadata = new DocumentationMetadata
+ {
+ AssemblyVersion = version,
+ GeneratedDateUtc = date,
+ ComponentCount = components.Count,
+ EnumCount = enums.Count
+ },
+ Components = components,
+ Enums = enums
+ };
+ }
+
+ ///
+ /// Generates component information for a specific type.
+ ///
+ private ComponentInfo? GenerateComponentInfo(Type type)
+ {
+ try
+ {
+ var options = new ApiClassOptions(Assembly, _docXmlReader)
+ {
+ Mode = GenerationMode.All
+ };
+
+ var apiClass = new ApiClass(type, options);
+
+ var component = new ComponentInfo
+ {
+ Name = apiClass.Name,
+ FullName = type.FullName ?? type.Name,
+ Summary = !string.IsNullOrWhiteSpace(apiClass.Summary) ? apiClass.Summary : null,
+ Category = DetermineCategory(type),
+ IsGeneric = type.IsGenericType,
+ BaseClass = type.BaseType?.Name
+ };
+
+ // Extract properties - only if we have any
+ var properties = new List();
+ foreach (var property in apiClass.Properties)
+ {
+ var isInherited = property.MemberInfo.DeclaringType != type;
+ var description = !string.IsNullOrWhiteSpace(property.Description) ? property.Description : null;
+
+ properties.Add(new Models.AllMode.PropertyInfo
+ {
+ Name = property.Name,
+ Type = property.Type,
+ Description = description,
+ IsParameter = property.IsParameter,
+ IsInherited = isInherited,
+ DefaultValue = property.Default,
+ EnumValues = property.EnumValues != null && property.EnumValues.Length > 0 ? property.EnumValues : null
+ });
+ }
+
+ component.Properties = properties.Count > 0 ? properties : null;
+
+ // Extract events - only if we have any
+ var events = new List();
+ foreach (var evt in apiClass.Events)
+ {
+ var isInherited = evt.MemberInfo.DeclaringType != type;
+ var description = !string.IsNullOrWhiteSpace(evt.Description) ? evt.Description : null;
+
+ events.Add(new Models.AllMode.EventInfo
+ {
+ Name = evt.Name,
+ Type = evt.Type,
+ Description = description,
+ IsInherited = isInherited
+ });
+ }
+
+ component.Events = events.Count > 0 ? events : null;
+
+ // Extract methods - only if we have any
+ var methods = new List();
+ foreach (var method in apiClass.Methods)
+ {
+ var isInherited = method.MemberInfo.DeclaringType != type;
+ var description = !string.IsNullOrWhiteSpace(method.Description) ? method.Description : null;
+
+ methods.Add(new Models.AllMode.MethodInfo
+ {
+ Name = method.Name,
+ ReturnType = method.Type,
+ Description = description,
+ Parameters = method.Parameters != null && method.Parameters.Length > 0 ? method.Parameters : null,
+ IsInherited = isInherited
+ });
+ }
+
+ component.Methods = methods.Count > 0 ? methods : null;
+
+ return component;
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine();
+ Console.WriteLine($"[WARNING] Error processing component {type.FullName}: {ex.Message}");
+ return null;
+ }
+ }
+
+ ///
+ /// Generates enum information for a specific type.
+ ///
+ private EnumInfo GenerateEnumInfo(Type type)
+ {
+ var values = new List();
+ var names = Enum.GetNames(type);
+ var enumValues = Enum.GetValues(type);
+
+ for (var i = 0; i < names.Length; i++)
+ {
+ var name = names[i];
+ var value = Convert.ToInt32(enumValues.GetValue(i), CultureInfo.InvariantCulture);
+ var field = type.GetField(name);
+ var description = field != null ? _docXmlReader.GetMemberSummary(field) : string.Empty;
+
+ values.Add(new EnumValueInfo
+ {
+ Name = name,
+ Value = value,
+ Description = !string.IsNullOrWhiteSpace(description) ? description : null
+ });
+ }
+
+ var enumDescription = _docXmlReader.GetComponentSummary(type);
+
+ return new EnumInfo
+ {
+ Name = type.Name,
+ FullName = type.FullName ?? type.Name,
+ Description = !string.IsNullOrWhiteSpace(enumDescription) ? enumDescription : null,
+ Values = values
+ };
+ }
+
+ ///
+ /// Checks if a type is a valid component type.
+ ///
+ private static bool IsValidComponentType(Type type)
+ {
+ return type != null &&
+ type.IsPublic &&
+ !type.IsAbstract &&
+ !type.IsInterface &&
+ type.IsClass &&
+ !Constants.EXCLUDE_TYPES.Contains(type.Name) &&
+ !type.Name.Contains('<') &&
+ !type.Name.Contains('>') &&
+ !type.Name.EndsWith("_g", StringComparison.Ordinal) &&
+ type.IsValidType();
+ }
+
+ ///
+ /// Determines the category of a component based on its name.
+ ///
+ private static string DetermineCategory(Type type)
+ {
+ var name = type.Name;
+
+ if (name.Contains("Button", StringComparison.OrdinalIgnoreCase))
+ {
+ return "Button";
+ }
+
+ if (name.Contains("Input", StringComparison.OrdinalIgnoreCase) ||
+ name.Contains("TextField", StringComparison.OrdinalIgnoreCase))
+ {
+ return "Input";
+ }
+
+ if (name.Contains("Dialog", StringComparison.OrdinalIgnoreCase))
+ {
+ return "Dialog";
+ }
+
+ if (name.Contains("Menu", StringComparison.OrdinalIgnoreCase))
+ {
+ return "Menu";
+ }
+
+ if (name.Contains("Nav", StringComparison.OrdinalIgnoreCase))
+ {
+ return "Navigation";
+ }
+
+ if (name.Contains("Grid", StringComparison.OrdinalIgnoreCase) ||
+ name.Contains("Table", StringComparison.OrdinalIgnoreCase))
+ {
+ return "DataGrid";
+ }
+
+ if (name.Contains("Card", StringComparison.OrdinalIgnoreCase))
+ {
+ return "Card";
+ }
+
+ if (name.Contains("Icon", StringComparison.OrdinalIgnoreCase))
+ {
+ return "Icon";
+ }
+
+ if (name.Contains("Layout", StringComparison.OrdinalIgnoreCase) ||
+ name.Contains("Stack", StringComparison.OrdinalIgnoreCase))
+ {
+ return "Layout";
+ }
+
+ return "Components";
+ }
+
+ ///
+ /// Gets assembly version and current date.
+ ///
+ private (string Version, string Date) GetAssemblyInfo()
+ {
+ var version = "Unknown";
+
+ var versionAttribute = Assembly.GetCustomAttribute();
+ if (versionAttribute != null)
+ {
+ var versionString = versionAttribute.InformationalVersion;
+ var plusIndex = versionString.IndexOf('+');
+
+ if (plusIndex >= 0 && plusIndex + 9 < versionString.Length)
+ {
+ version = versionString[..(plusIndex + 9)];
+ }
+ else
+ {
+ version = versionString;
+ }
+ }
+
+ var date = DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm", CultureInfo.InvariantCulture);
+
+ return (version, date);
+ }
+}
diff --git a/examples/Tools/FluentUI.Demo.DocApiGen/Generators/DocumentationGeneratorBase.cs b/examples/Tools/FluentUI.Demo.DocApiGen/Generators/DocumentationGeneratorBase.cs
new file mode 100644
index 0000000000..3fa2a1a780
--- /dev/null
+++ b/examples/Tools/FluentUI.Demo.DocApiGen/Generators/DocumentationGeneratorBase.cs
@@ -0,0 +1,74 @@
+// ------------------------------------------------------------------------
+// This file is licensed to you under the MIT License.
+// ------------------------------------------------------------------------
+
+using System.Reflection;
+using FluentUI.Demo.DocApiGen.Abstractions;
+
+namespace FluentUI.Demo.DocApiGen.Generators;
+
+///
+/// Base class for documentation generators implementing common functionality.
+///
+public abstract class DocumentationGeneratorBase : IDocumentationGenerator
+{
+ ///
+ /// Represents the assembly associated with the current context or operation.
+ ///
+ protected readonly Assembly Assembly;
+
+ ///
+ /// Represents the XML documentation file associated with the assembly.
+ ///
+ protected readonly FileInfo XmlDocumentation;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The assembly to generate documentation for.
+ /// The XML documentation file.
+ protected DocumentationGeneratorBase(Assembly assembly, FileInfo xmlDocumentation)
+ {
+ Assembly = assembly ?? throw new ArgumentNullException(nameof(assembly));
+ XmlDocumentation = xmlDocumentation ?? throw new ArgumentNullException(nameof(xmlDocumentation));
+
+ if (!xmlDocumentation.Exists)
+ {
+ throw new FileNotFoundException($"XML documentation file not found: {xmlDocumentation.FullName}");
+ }
+ }
+
+ ///
+ public abstract GenerationMode Mode { get; }
+
+ ///
+ public abstract string Generate(IOutputFormatter formatter);
+
+ ///
+ public virtual void SaveToFile(string filePath, IOutputFormatter formatter)
+ {
+ if (string.IsNullOrWhiteSpace(filePath))
+ {
+ throw new ArgumentException("File path cannot be null or empty.", nameof(filePath));
+ }
+
+ ArgumentNullException.ThrowIfNull(formatter);
+
+ var output = Generate(formatter);
+
+ // Delete existing file if it exists
+ if (File.Exists(filePath))
+ {
+ File.Delete(filePath);
+ }
+
+ // Ensure directory exists
+ var directory = Path.GetDirectoryName(filePath);
+ if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory))
+ {
+ Directory.CreateDirectory(directory);
+ }
+
+ File.WriteAllText(filePath, output);
+ }
+}
diff --git a/examples/Tools/FluentUI.Demo.DocApiGen/Generators/DocumentationGeneratorFactory.cs b/examples/Tools/FluentUI.Demo.DocApiGen/Generators/DocumentationGeneratorFactory.cs
new file mode 100644
index 0000000000..22db0b3b6c
--- /dev/null
+++ b/examples/Tools/FluentUI.Demo.DocApiGen/Generators/DocumentationGeneratorFactory.cs
@@ -0,0 +1,66 @@
+// ------------------------------------------------------------------------
+// This file is licensed to you under the MIT License.
+// ------------------------------------------------------------------------
+
+using System.Reflection;
+using FluentUI.Demo.DocApiGen.Abstractions;
+
+namespace FluentUI.Demo.DocApiGen.Generators;
+
+///
+/// Factory for creating documentation generators.
+///
+public static class DocumentationGeneratorFactory
+{
+ ///
+ /// Creates a documentation generator for the specified mode.
+ ///
+ /// The generation mode.
+ /// The assembly to generate documentation for.
+ /// The XML documentation file.
+ /// A documentation generator instance.
+ /// Thrown when assembly or xmlDocumentation is null.
+ /// Thrown when the mode is not supported.
+ public static IDocumentationGenerator Create(
+ GenerationMode mode,
+ Assembly assembly,
+ FileInfo xmlDocumentation)
+ {
+ ArgumentNullException.ThrowIfNull(assembly);
+
+ ArgumentNullException.ThrowIfNull(xmlDocumentation);
+
+ return mode switch
+ {
+ GenerationMode.Summary => new SummaryDocumentationGenerator(assembly, xmlDocumentation),
+ GenerationMode.All => new AllDocumentationGenerator(assembly, xmlDocumentation),
+ _ => throw new NotSupportedException($"Generation mode '{mode}' is not supported.")
+ };
+ }
+
+ ///
+ /// Creates a Summary mode documentation generator.
+ ///
+ /// The assembly to generate documentation for.
+ /// The XML documentation file.
+ /// A Summary mode documentation generator.
+ public static IDocumentationGenerator CreateSummaryGenerator(
+ Assembly assembly,
+ FileInfo xmlDocumentation)
+ {
+ return Create(GenerationMode.Summary, assembly, xmlDocumentation);
+ }
+
+ ///
+ /// Creates an All mode documentation generator.
+ ///
+ /// The assembly to generate documentation for.
+ /// The XML documentation file.
+ /// An All mode documentation generator.
+ public static IDocumentationGenerator CreateAllGenerator(
+ Assembly assembly,
+ FileInfo xmlDocumentation)
+ {
+ return Create(GenerationMode.All, assembly, xmlDocumentation);
+ }
+}
diff --git a/examples/Tools/FluentUI.Demo.DocApiGen/Generators/SummaryDocumentationGenerator.cs b/examples/Tools/FluentUI.Demo.DocApiGen/Generators/SummaryDocumentationGenerator.cs
new file mode 100644
index 0000000000..b01c112d3c
--- /dev/null
+++ b/examples/Tools/FluentUI.Demo.DocApiGen/Generators/SummaryDocumentationGenerator.cs
@@ -0,0 +1,145 @@
+// ------------------------------------------------------------------------
+// This file is licensed to you under the MIT License.
+// ------------------------------------------------------------------------
+
+using System.Globalization;
+using System.Reflection;
+using FluentUI.Demo.DocApiGen.Abstractions;
+using FluentUI.Demo.DocApiGen.Extensions;
+using FluentUI.Demo.DocApiGen.Models.SummaryMode;
+
+namespace FluentUI.Demo.DocApiGen.Generators;
+
+///
+/// Generates Summary mode documentation (only [Parameter] properties).
+/// Supports JSON and C# output formats.
+///
+public sealed class SummaryDocumentationGenerator : DocumentationGeneratorBase
+{
+ private readonly LoxSmoke.DocXml.DocXmlReader _docXmlReader;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The assembly to generate documentation for.
+ /// The XML documentation file.
+ public SummaryDocumentationGenerator(Assembly assembly, FileInfo xmlDocumentation)
+ : base(assembly, xmlDocumentation)
+ {
+ _docXmlReader = new LoxSmoke.DocXml.DocXmlReader(xmlDocumentation.FullName);
+ }
+
+ ///
+ public override GenerationMode Mode => GenerationMode.Summary;
+
+ ///
+ public override string Generate(IOutputFormatter formatter)
+ {
+ ArgumentNullException.ThrowIfNull(formatter);
+
+ var data = BuildDocumentationData();
+ return formatter.Format(data);
+ }
+
+ ///
+ /// Builds the documentation data structure.
+ ///
+ private SummaryDocumentationData BuildDocumentationData()
+ {
+ var (version, date) = GetAssemblyInfo();
+ var components = new Dictionary();
+
+ var options = new ApiClassOptions(Assembly, _docXmlReader)
+ {
+ Mode = GenerationMode.Summary
+ };
+
+ var validTypes = Assembly.GetTypes().Where(t => t.IsValidType()).ToList();
+ Console.WriteLine($"Processing {validTypes.Count} valid types...");
+
+ var processedCount = 0;
+ foreach (var type in validTypes)
+ {
+ processedCount++;
+ if (processedCount % 10 == 0)
+ {
+ Console.Write($"\rProcessed {processedCount}/{validTypes.Count} types...");
+ }
+
+ try
+ {
+ var apiClass = new ApiClass(type, options);
+ var members = apiClass.ToDictionary();
+
+ if (members.Any())
+ {
+ // Add class summary
+ var classKey = $"{type.FullName}.__summary__";
+ components[classKey] = new ComponentEntry
+ {
+ Summary = apiClass.Summary,
+ Signature = type.Name
+ };
+
+ // Add members
+ foreach (var member in members)
+ {
+ var memberKey = $"{type.FullName}.{member.Key}";
+ components[memberKey] = new ComponentEntry
+ {
+ Summary = member.Value,
+ Signature = member.Key
+ };
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine();
+ Console.WriteLine($"[WARNING] Error processing type {type.FullName}: {ex.Message}");
+ }
+ }
+
+ Console.WriteLine();
+ Console.WriteLine($"✓ Processed {validTypes.Count} types, generated {components.Count} documentation entries.");
+
+ return new SummaryDocumentationData
+ {
+ Metadata = new SummaryMetadata
+ {
+ AssemblyVersion = version,
+ DateUtc = date,
+ Mode = Mode.ToString()
+ },
+ Components = components
+ };
+ }
+
+ ///
+ /// Gets assembly version and current date.
+ ///
+ private (string Version, string Date) GetAssemblyInfo()
+ {
+ var version = "Unknown";
+
+ var versionAttribute = Assembly.GetCustomAttribute();
+ if (versionAttribute != null)
+ {
+ var versionString = versionAttribute.InformationalVersion;
+ var plusIndex = versionString.IndexOf('+');
+
+ if (plusIndex >= 0 && plusIndex + 9 < versionString.Length)
+ {
+ version = versionString[..(plusIndex + 9)];
+ }
+ else
+ {
+ version = versionString;
+ }
+ }
+
+ var date = DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm", CultureInfo.InvariantCulture);
+
+ return (version, date);
+ }
+}
diff --git a/examples/Tools/FluentUI.Demo.DocApiGen/Models/AllMode/ComponentInfo.cs b/examples/Tools/FluentUI.Demo.DocApiGen/Models/AllMode/ComponentInfo.cs
new file mode 100644
index 0000000000..a27c17d8ff
--- /dev/null
+++ b/examples/Tools/FluentUI.Demo.DocApiGen/Models/AllMode/ComponentInfo.cs
@@ -0,0 +1,69 @@
+// ------------------------------------------------------------------------
+// This file is licensed to you under the MIT License.
+// ------------------------------------------------------------------------
+
+using System.Text.Json.Serialization;
+
+namespace FluentUI.Demo.DocApiGen.Models.AllMode;
+
+///
+/// Represents a Fluent UI component with its full documentation.
+///
+public class ComponentInfo
+{
+ ///
+ /// Gets or sets the name of the component.
+ ///
+ public string Name { get; set; } = string.Empty;
+
+ ///
+ /// Gets or sets the full type name of the component.
+ ///
+ public string FullName { get; set; } = string.Empty;
+
+ ///
+ /// Gets or sets a brief description of the component.
+ ///
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
+ public string? Summary { get; set; }
+
+ ///
+ /// Gets or sets the category of the component.
+ ///
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
+ public string? Category { get; set; }
+
+ ///
+ /// Gets or sets whether the component is a generic type.
+ /// Only serialized when true to reduce JSON size.
+ ///
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
+ public bool IsGeneric { get; set; }
+
+ ///
+ /// Gets or sets the base class name.
+ ///
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ public string? BaseClass { get; set; }
+
+ ///
+ /// Gets or sets the list of properties.
+ /// Only serialized when not empty to reduce JSON size.
+ ///
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
+ public List? Properties { get; set; }
+
+ ///
+ /// Gets or sets the list of events.
+ /// Only serialized when not empty to reduce JSON size.
+ ///
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
+ public List? Events { get; set; }
+
+ ///
+ /// Gets or sets the list of methods.
+ /// Only serialized when not empty to reduce JSON size.
+ ///
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
+ public List? Methods { get; set; }
+}
diff --git a/examples/Tools/FluentUI.Demo.DocApiGen/Models/AllMode/DocumentationMetadata.cs b/examples/Tools/FluentUI.Demo.DocApiGen/Models/AllMode/DocumentationMetadata.cs
new file mode 100644
index 0000000000..11d6735003
--- /dev/null
+++ b/examples/Tools/FluentUI.Demo.DocApiGen/Models/AllMode/DocumentationMetadata.cs
@@ -0,0 +1,31 @@
+// ------------------------------------------------------------------------
+// This file is licensed to you under the MIT License.
+// ------------------------------------------------------------------------
+
+namespace FluentUI.Demo.DocApiGen.Models.AllMode;
+
+///
+/// Metadata about the generated documentation.
+///
+public class DocumentationMetadata
+{
+ ///
+ /// Gets or sets the assembly version.
+ ///
+ public string AssemblyVersion { get; set; } = string.Empty;
+
+ ///
+ /// Gets or sets the generation date in UTC.
+ ///
+ public string GeneratedDateUtc { get; set; } = string.Empty;
+
+ ///
+ /// Gets or sets the total component count.
+ ///
+ public int ComponentCount { get; set; }
+
+ ///
+ /// Gets or sets the total enum count.
+ ///
+ public int EnumCount { get; set; }
+}
diff --git a/examples/Tools/FluentUI.Demo.DocApiGen/Models/AllMode/DocumentationRoot.cs b/examples/Tools/FluentUI.Demo.DocApiGen/Models/AllMode/DocumentationRoot.cs
new file mode 100644
index 0000000000..9930c23170
--- /dev/null
+++ b/examples/Tools/FluentUI.Demo.DocApiGen/Models/AllMode/DocumentationRoot.cs
@@ -0,0 +1,27 @@
+// ------------------------------------------------------------------------
+// This file is licensed to you under the MIT License.
+// ------------------------------------------------------------------------
+
+namespace FluentUI.Demo.DocApiGen.Models.AllMode;
+
+///
+/// Root model for the MCP documentation JSON file.
+/// Contains all components and enums documentation.
+///
+public class DocumentationRoot
+{
+ ///
+ /// Gets or sets metadata about the generated documentation.
+ ///
+ public DocumentationMetadata Metadata { get; set; } = new();
+
+ ///
+ /// Gets or sets the list of all components.
+ ///
+ public List Components { get; set; } = [];
+
+ ///
+ /// Gets or sets the list of all enums.
+ ///
+ public List Enums { get; set; } = [];
+}
diff --git a/examples/Tools/FluentUI.Demo.DocApiGen/Models/AllMode/EnumInfo.cs b/examples/Tools/FluentUI.Demo.DocApiGen/Models/AllMode/EnumInfo.cs
new file mode 100644
index 0000000000..a44868ca7b
--- /dev/null
+++ b/examples/Tools/FluentUI.Demo.DocApiGen/Models/AllMode/EnumInfo.cs
@@ -0,0 +1,34 @@
+// ------------------------------------------------------------------------
+// This file is licensed to you under the MIT License.
+// ------------------------------------------------------------------------
+
+using System.Text.Json.Serialization;
+
+namespace FluentUI.Demo.DocApiGen.Models.AllMode;
+
+///
+/// Represents an enum type.
+///
+public class EnumInfo
+{
+ ///
+ /// Gets or sets the name of the enum.
+ ///
+ public string Name { get; set; } = string.Empty;
+
+ ///
+ /// Gets or sets the full type name of the enum.
+ ///
+ public string FullName { get; set; } = string.Empty;
+
+ ///
+ /// Gets or sets the description of the enum.
+ ///
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
+ public string? Description { get; set; }
+
+ ///
+ /// Gets or sets the enum values.
+ ///
+ public List Values { get; set; } = [];
+}
diff --git a/examples/Tools/FluentUI.Demo.DocApiGen/Models/AllMode/EnumValueInfo.cs b/examples/Tools/FluentUI.Demo.DocApiGen/Models/AllMode/EnumValueInfo.cs
new file mode 100644
index 0000000000..d6b98e7166
--- /dev/null
+++ b/examples/Tools/FluentUI.Demo.DocApiGen/Models/AllMode/EnumValueInfo.cs
@@ -0,0 +1,29 @@
+// ------------------------------------------------------------------------
+// This file is licensed to you under the MIT License.
+// ------------------------------------------------------------------------
+
+using System.Text.Json.Serialization;
+
+namespace FluentUI.Demo.DocApiGen.Models.AllMode;
+
+///
+/// Represents an enum value.
+///
+public class EnumValueInfo
+{
+ ///
+ /// Gets or sets the name of the enum value.
+ ///
+ public string Name { get; set; } = string.Empty;
+
+ ///
+ /// Gets or sets the numeric value.
+ ///
+ public int Value { get; set; }
+
+ ///
+ /// Gets or sets the description of the enum value.
+ ///
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
+ public string? Description { get; set; }
+}
diff --git a/examples/Tools/FluentUI.Demo.DocApiGen/Models/AllMode/EventInfo.cs b/examples/Tools/FluentUI.Demo.DocApiGen/Models/AllMode/EventInfo.cs
new file mode 100644
index 0000000000..d6861bcf88
--- /dev/null
+++ b/examples/Tools/FluentUI.Demo.DocApiGen/Models/AllMode/EventInfo.cs
@@ -0,0 +1,36 @@
+// ------------------------------------------------------------------------
+// This file is licensed to you under the MIT License.
+// ------------------------------------------------------------------------
+
+using System.Text.Json.Serialization;
+
+namespace FluentUI.Demo.DocApiGen.Models.AllMode;
+
+///
+/// Represents an event of a component.
+///
+public class EventInfo
+{
+ ///
+ /// Gets or sets the name of the event.
+ ///
+ public string Name { get; set; } = string.Empty;
+
+ ///
+ /// Gets or sets the type of the event callback.
+ ///
+ public string Type { get; set; } = string.Empty;
+
+ ///
+ /// Gets or sets the description of the event.
+ ///
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
+ public string? Description { get; set; }
+
+ ///
+ /// Gets or sets whether this event is inherited.
+ /// Only serialized when true to reduce JSON size.
+ ///
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
+ public bool IsInherited { get; set; }
+}
diff --git a/examples/Tools/FluentUI.Demo.DocApiGen/Models/AllMode/MethodInfo.cs b/examples/Tools/FluentUI.Demo.DocApiGen/Models/AllMode/MethodInfo.cs
new file mode 100644
index 0000000000..f7b91c948d
--- /dev/null
+++ b/examples/Tools/FluentUI.Demo.DocApiGen/Models/AllMode/MethodInfo.cs
@@ -0,0 +1,43 @@
+// ------------------------------------------------------------------------
+// This file is licensed to you under the MIT License.
+// ------------------------------------------------------------------------
+
+using System.Text.Json.Serialization;
+
+namespace FluentUI.Demo.DocApiGen.Models.AllMode;
+
+///
+/// Represents a method of a component.
+///
+public class MethodInfo
+{
+ ///
+ /// Gets or sets the name of the method.
+ ///
+ public string Name { get; set; } = string.Empty;
+
+ ///
+ /// Gets or sets the return type of the method.
+ ///
+ public string ReturnType { get; set; } = string.Empty;
+
+ ///
+ /// Gets or sets the description of the method.
+ ///
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
+ public string? Description { get; set; }
+
+ ///
+ /// Gets or sets the parameters of the method.
+ /// Only serialized when not empty to reduce JSON size.
+ ///
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
+ public string[]? Parameters { get; set; }
+
+ ///
+ /// Gets or sets whether this method is inherited.
+ /// Only serialized when true to reduce JSON size.
+ ///
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
+ public bool IsInherited { get; set; }
+}
diff --git a/examples/Tools/FluentUI.Demo.DocApiGen/Models/AllMode/PropertyInfo.cs b/examples/Tools/FluentUI.Demo.DocApiGen/Models/AllMode/PropertyInfo.cs
new file mode 100644
index 0000000000..93ac884006
--- /dev/null
+++ b/examples/Tools/FluentUI.Demo.DocApiGen/Models/AllMode/PropertyInfo.cs
@@ -0,0 +1,54 @@
+// ------------------------------------------------------------------------
+// This file is licensed to you under the MIT License.
+// ------------------------------------------------------------------------
+
+using System.Text.Json.Serialization;
+
+namespace FluentUI.Demo.DocApiGen.Models.AllMode;
+
+///
+/// Represents a property of a component.
+///
+public class PropertyInfo
+{
+ ///
+ /// Gets or sets the name of the property.
+ ///
+ public string Name { get; set; } = string.Empty;
+
+ ///
+ /// Gets or sets the type of the property.
+ ///
+ public string Type { get; set; } = string.Empty;
+
+ ///
+ /// Gets or sets the description of the property.
+ ///
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
+ public string? Description { get; set; }
+
+ ///
+ /// Gets or sets whether this property is a [Parameter].
+ ///
+ public bool IsParameter { get; set; }
+
+ ///
+ /// Gets or sets whether this property is inherited.
+ /// Only serialized when true to reduce JSON size.
+ ///
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
+ public bool IsInherited { get; set; }
+
+ ///
+ /// Gets or sets the default value of the property.
+ ///
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ public string? DefaultValue { get; set; }
+
+ ///
+ /// Gets or sets the enum values if this property is an enum type.
+ /// Only serialized when not empty to reduce JSON size.
+ ///
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
+ public string[]? EnumValues { get; set; }
+}
diff --git a/examples/Tools/FluentUI.Demo.DocApiGen/Models/ApiClassOptions.cs b/examples/Tools/FluentUI.Demo.DocApiGen/Models/ApiClassOptions.cs
index 25195753a2..e69de29bb2 100644
--- a/examples/Tools/FluentUI.Demo.DocApiGen/Models/ApiClassOptions.cs
+++ b/examples/Tools/FluentUI.Demo.DocApiGen/Models/ApiClassOptions.cs
@@ -1,39 +0,0 @@
-// ------------------------------------------------------------------------
-// This file is licensed to you under the MIT License.
-// ------------------------------------------------------------------------
-
-using System.Reflection;
-
-namespace FluentUI.Demo.DocApiGen.Models;
-
-///
-/// Represents the options for the class generation.
-///
-public class ApiClassOptions
-{
- ///
- /// Initializes a new instance of the class.
- ///
- ///
- ///
- public ApiClassOptions(Assembly assembly, LoxSmoke.DocXml.DocXmlReader docReader)
- {
- Assembly = assembly;
- DocXmlReader = docReader;
- }
-
- ///
- /// Gets the assembly to generate the documentation.
- ///
- public Assembly Assembly { get; }
-
- ///
- /// Gets the summary reader.
- ///
- public LoxSmoke.DocXml.DocXmlReader DocXmlReader { get; }
-
- ///
- /// Gets or sets whether to include all properties (false) or only those with [Parameter] attribute (true).
- ///
- public bool PropertyParameterOnly { get; set; } = true;
-}
diff --git a/examples/Tools/FluentUI.Demo.DocApiGen/Models/ApiClass.cs b/examples/Tools/FluentUI.Demo.DocApiGen/Models/SummaryMode/ApiClass.cs
similarity index 80%
rename from examples/Tools/FluentUI.Demo.DocApiGen/Models/ApiClass.cs
rename to examples/Tools/FluentUI.Demo.DocApiGen/Models/SummaryMode/ApiClass.cs
index 54240c530d..d64087d26b 100644
--- a/examples/Tools/FluentUI.Demo.DocApiGen/Models/ApiClass.cs
+++ b/examples/Tools/FluentUI.Demo.DocApiGen/Models/SummaryMode/ApiClass.cs
@@ -8,7 +8,7 @@
using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;
-namespace FluentUI.Demo.DocApiGen.Models;
+namespace FluentUI.Demo.DocApiGen.Models.SummaryMode;
///
/// Represents a class with properties, methods, and events.
@@ -64,7 +64,22 @@ public Type[] InstanceTypes
///
/// Gets the list of properties for the specified component.
///
- public IEnumerable Properties => GetMembers(MemberTypes.Property).Where(i => _options.PropertyParameterOnly == false ? true : i.IsParameter);
+ public IEnumerable Properties
+ {
+ get
+ {
+ var properties = GetMembers(MemberTypes.Property);
+
+ // For enums, include all values regardless of PropertyParameterOnly setting
+ if (_component.IsEnum)
+ {
+ return properties;
+ }
+
+ // For classes, apply PropertyParameterOnly filter
+ return properties.Where(i => _options.PropertyParameterOnly == false || i.IsParameter);
+ }
+ }
///
/// Gets the list of Events for the specified component.
@@ -103,6 +118,7 @@ private IEnumerable GetMembers(MemberTypes type)
List? members = [];
object? obj = null;
var created = false;
+ var canCreateInstance = CanCreateInstance(_component);
var ctorArguments = HasCtorWithArguments(_component, ["LibraryConfiguration"])
? new object?[] { null }
@@ -111,16 +127,21 @@ private IEnumerable GetMembers(MemberTypes type)
// Create an instance of the component to get the default values
object? GetObjectValue(string propertyName)
{
- try
+ // Skip instance creation if we know it will fail
+ if (!canCreateInstance || _component.IsAbstract || _component.IsInterface)
{
+ return null;
+ }
+ try
+ {
if (!created)
{
if (_component.IsGenericType)
{
if (InstanceTypes is null)
{
- throw new InvalidCastException("InstanceTypes must be specified when Component is a generic type");
+ return null;
}
// Supply the type to create the generic instance with (needs to be an array)
@@ -135,10 +156,11 @@ private IEnumerable GetMembers(MemberTypes type)
}
return obj?.GetType().GetProperty(propertyName)?.GetValue(obj);
-
}
catch (Exception)
{
+ // Mark as unable to create to avoid future attempts
+ canCreateInstance = false;
return null;
}
}
@@ -188,9 +210,9 @@ private IEnumerable GetMembers(MemberTypes type)
// Parameters/properties
if (!isEvent)
{
- // Icon? icon = null;
+ // Only try to get default value if we can create an instance and the property type is simple
var defaultValue = "";
- if (propertyInfo.PropertyType.IsValueType || propertyInfo.PropertyType == typeof(string))
+ if (canCreateInstance && (propertyInfo.PropertyType.IsValueType || propertyInfo.PropertyType == typeof(string)))
{
defaultValue = GetObjectValue(propertyInfo.Name)?.ToString();
}
@@ -250,10 +272,10 @@ private IEnumerable GetMembers(MemberTypes type)
}
}
}
- catch (Exception)
+ catch (Exception ex)
{
- Console.WriteLine($"[ApiDocumentation] ERROR: Cannot found {_component.FullName} -> {memberInfo.Name}");
- throw;
+ Console.WriteLine($"[ApiDocumentation] ERROR: Cannot process {_component.FullName} -> {memberInfo.Name}: {ex.Message}");
+ // Don't rethrow, continue with next member
}
}
@@ -263,6 +285,45 @@ private IEnumerable GetMembers(MemberTypes type)
return _allMembers.Where(i => i.MemberType == type);
}
+ ///
+ /// Checks if a type can be instantiated.
+ ///
+ private static bool CanCreateInstance(Type type)
+ {
+ if (type.IsAbstract || type.IsInterface || type.IsGenericTypeDefinition)
+ {
+ return false;
+ }
+
+ // Check if type has a parameterless constructor or a constructor with nullable LibraryConfiguration
+ var constructors = type.GetConstructors(BindingFlags.Public | BindingFlags.Instance);
+ if (constructors.Length == 0)
+ {
+ return false;
+ }
+
+ foreach (var ctor in constructors)
+ {
+ var parameters = ctor.GetParameters();
+
+ // Parameterless constructor
+ if (parameters.Length == 0)
+ {
+ return true;
+ }
+
+ // Constructor with single nullable LibraryConfiguration parameter
+ if (parameters.Length == 1 &&
+ parameters[0].ParameterType.Name == "LibraryConfiguration" &&
+ parameters[0].IsOptional == false)
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
///
private string GetSummary(Type component, MemberInfo? member)
{
diff --git a/examples/Tools/FluentUI.Demo.DocApiGen/Models/SummaryMode/ApiClassOptions.cs b/examples/Tools/FluentUI.Demo.DocApiGen/Models/SummaryMode/ApiClassOptions.cs
new file mode 100644
index 0000000000..287a7c7d41
--- /dev/null
+++ b/examples/Tools/FluentUI.Demo.DocApiGen/Models/SummaryMode/ApiClassOptions.cs
@@ -0,0 +1,51 @@
+// ------------------------------------------------------------------------
+// This file is licensed to you under the MIT License.
+// ------------------------------------------------------------------------
+
+using System.Reflection;
+using FluentUI.Demo.DocApiGen.Abstractions;
+
+namespace FluentUI.Demo.DocApiGen.Models.SummaryMode;
+
+///
+/// Represents the options for the class generation.
+///
+public class ApiClassOptions
+{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ ///
+ public ApiClassOptions(Assembly assembly, LoxSmoke.DocXml.DocXmlReader docReader)
+ {
+ Assembly = assembly;
+ DocXmlReader = docReader;
+ }
+
+ ///
+ /// Gets the assembly to generate the documentation.
+ ///
+ public Assembly Assembly { get; }
+
+ ///
+ /// Gets the summary reader.
+ ///
+ public LoxSmoke.DocXml.DocXmlReader DocXmlReader { get; }
+
+ ///
+ /// Gets or sets whether to include all properties (false) or only those with [Parameter] attribute (true).
+ ///
+ public bool PropertyParameterOnly { get; set; } = true;
+
+ ///
+ /// Gets or sets the generation mode (Summary or All).
+ /// Summary mode includes only [Parameter] properties (PropertyParameterOnly = true).
+ /// All mode includes all properties, methods, and events (PropertyParameterOnly = false).
+ ///
+ public GenerationMode Mode
+ {
+ get => PropertyParameterOnly ? GenerationMode.Summary : GenerationMode.All;
+ set => PropertyParameterOnly = value == GenerationMode.Summary;
+ }
+}
diff --git a/examples/Tools/FluentUI.Demo.DocApiGen/Models/ApiMember.cs b/examples/Tools/FluentUI.Demo.DocApiGen/Models/SummaryMode/ApiMember.cs
similarity index 97%
rename from examples/Tools/FluentUI.Demo.DocApiGen/Models/ApiMember.cs
rename to examples/Tools/FluentUI.Demo.DocApiGen/Models/SummaryMode/ApiMember.cs
index cb10ffd36f..64ed355512 100644
--- a/examples/Tools/FluentUI.Demo.DocApiGen/Models/ApiMember.cs
+++ b/examples/Tools/FluentUI.Demo.DocApiGen/Models/SummaryMode/ApiMember.cs
@@ -5,7 +5,7 @@
using System.Reflection;
using FluentUI.Demo.DocApiGen.Extensions;
-namespace FluentUI.Demo.DocApiGen.Models;
+namespace FluentUI.Demo.DocApiGen.Models.SummaryMode;
///
/// Represents a member of a class (Property, Method, Event).
diff --git a/examples/Tools/FluentUI.Demo.DocApiGen/Models/SummaryMode/SummaryDocumentationData.cs b/examples/Tools/FluentUI.Demo.DocApiGen/Models/SummaryMode/SummaryDocumentationData.cs
new file mode 100644
index 0000000000..8c22933606
--- /dev/null
+++ b/examples/Tools/FluentUI.Demo.DocApiGen/Models/SummaryMode/SummaryDocumentationData.cs
@@ -0,0 +1,60 @@
+// ------------------------------------------------------------------------
+// This file is licensed to you under the MIT License.
+// ------------------------------------------------------------------------
+
+namespace FluentUI.Demo.DocApiGen.Models.SummaryMode;
+
+///
+/// Root object for Summary mode documentation.
+///
+public class SummaryDocumentationData
+{
+ ///
+ /// Gets or sets the metadata.
+ ///
+ public required SummaryMetadata Metadata { get; set; }
+
+ ///
+ /// Gets or sets the components dictionary.
+ /// Key: Full member name (Type.Member)
+ /// Value: Summary and Signature
+ ///
+ public required Dictionary Components { get; set; }
+}
+
+///
+/// Represents a component entry in Summary mode.
+///
+public class ComponentEntry
+{
+ ///
+ /// Gets or sets the summary text.
+ ///
+ public string Summary { get; set; } = string.Empty;
+
+ ///
+ /// Gets or sets the signature.
+ ///
+ public string Signature { get; set; } = string.Empty;
+}
+
+///
+/// Metadata for Summary mode generated documentation.
+///
+public class SummaryMetadata
+{
+ ///
+ /// Gets or sets the assembly version.
+ ///
+ public string AssemblyVersion { get; set; } = string.Empty;
+
+ ///
+ /// Gets or sets the generation date (UTC).
+ ///
+ public string DateUtc { get; set; } = string.Empty;
+
+ ///
+ /// Gets or sets the generation mode.
+ ///
+ public string Mode { get; set; } = string.Empty;
+}
diff --git a/examples/Tools/FluentUI.Demo.DocApiGen/Program.cs b/examples/Tools/FluentUI.Demo.DocApiGen/Program.cs
index 830aa9967b..57f6466f97 100644
--- a/examples/Tools/FluentUI.Demo.DocApiGen/Program.cs
+++ b/examples/Tools/FluentUI.Demo.DocApiGen/Program.cs
@@ -2,24 +2,31 @@
// This file is licensed to you under the MIT License.
// ------------------------------------------------------------------------
+using FluentUI.Demo.DocApiGen.Abstractions;
+using FluentUI.Demo.DocApiGen.Formatters;
+using FluentUI.Demo.DocApiGen.Generators;
using Microsoft.Extensions.Configuration;
using System.Reflection;
namespace FluentUI.Demo.DocApiGen;
-///
+///
+/// Main entry point for the DocApiGen tool.
+///
public class Program
{
- private static readonly System.Diagnostics.Stopwatch _watcher = new ();
+ private static readonly System.Diagnostics.Stopwatch _watcher = new();
- ///
+ ///
+ /// Main method.
+ ///
public static void Main(string[] args)
{
_watcher.Start();
Console.WriteLine($"-------------------------------------------------------------------");
Console.WriteLine($" DocApiGen v{Assembly.GetEntryAssembly()?.GetName().Version} ");
- Console.WriteLine($" A simple command line tool to generate the documentation classes. ");
+ Console.WriteLine($" A tool to generate API documentation from assemblies. ");
Console.WriteLine($"-------------------------------------------------------------------");
Console.WriteLine();
@@ -29,41 +36,125 @@ public static void Main(string[] args)
var dllFile = config["dll"];
var outputFile = config["output"];
var format = config["format"] ?? "json";
+ var modeArg = config["mode"] ?? "summary";
// Help
if (string.IsNullOrEmpty(xmlFile) || string.IsNullOrEmpty(dllFile))
{
- Console.WriteLine("Usage: DocApiGen --xml " +
- " --dll " +
- " --output " +
- " --format ");
+ ShowHelp();
return;
}
- // Assembly and documentation file
- var assembly = Assembly.LoadFrom(dllFile);
- var docXml = new FileInfo(xmlFile);
- var apiGenerator = new ApiClassGenerator(assembly, docXml);
-
- Console.WriteLine("Generating documentation...");
- if (!string.IsNullOrEmpty(outputFile))
- {
- apiGenerator.SaveToFile(outputFile, format);
- Console.WriteLine($"Documentation saved to {outputFile}");
- }
- else
+ try
{
+ // Parse generation mode
+ var mode = ParseMode(modeArg);
+
+ // Validate format compatibility
+ ValidateFormatCompatibility(mode, format);
+
+ // Load assembly and XML documentation
+ var assembly = Assembly.LoadFrom(dllFile);
+ var docXml = new FileInfo(xmlFile);
+
+ Console.WriteLine("Generating documentation...");
+ Console.WriteLine($" Assembly: {assembly.GetName().Name}");
+ Console.WriteLine($" Mode: {mode}");
+ Console.WriteLine($" Format: {format}");
Console.WriteLine();
- if (format == "json")
+
+ // Create generator and formatter
+ var generator = DocumentationGeneratorFactory.Create(mode, assembly, docXml);
+ var formatter = OutputFormatterFactory.Create(format);
+
+ // Generate and output
+ if (!string.IsNullOrEmpty(outputFile))
{
- Console.WriteLine(apiGenerator.GenerateJson());
+ generator.SaveToFile(outputFile, formatter);
+ Console.WriteLine($"✓ Documentation saved to: {outputFile}");
}
else
{
- Console.WriteLine(apiGenerator.GenerateCSharp());
+ var output = generator.Generate(formatter);
+ Console.WriteLine(output);
+ }
+
+ _watcher.Stop();
+ Console.WriteLine();
+ Console.WriteLine($"✓ Completed in {_watcher.ElapsedMilliseconds} ms");
+ }
+ catch (Exception ex)
+ {
+ if (OperatingSystem.IsWindows() || OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
+ {
+ Console.ForegroundColor = ConsoleColor.Red;
}
+
+ Console.WriteLine($"✗ Error: {ex.Message}");
+
+ if (OperatingSystem.IsWindows() || OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
+ {
+ Console.ResetColor();
+ }
+
+ Environment.Exit(1);
}
+ }
- Console.WriteLine($"Completed in {_watcher.ElapsedMilliseconds} ms");
+ private static void ShowHelp()
+ {
+ Console.WriteLine("Usage:");
+ Console.WriteLine(" DocApiGen --xml --dll [options]");
+ Console.WriteLine();
+ Console.WriteLine("Required Arguments:");
+ Console.WriteLine(" --xml Path to the XML documentation file");
+ Console.WriteLine(" --dll Path to the assembly DLL file");
+ Console.WriteLine();
+ Console.WriteLine("Optional Arguments:");
+ Console.WriteLine(" --output Path to the output file (default: stdout)");
+ Console.WriteLine(" --format Output format (default: json)");
+ Console.WriteLine(" --mode Generation mode (default: summary)");
+ Console.WriteLine();
+ Console.WriteLine("Formats:");
+ Console.WriteLine(" json - Generate JSON documentation");
+ Console.WriteLine(" csharp - Generate C# code with documentation dictionary");
+ Console.WriteLine();
+ Console.WriteLine("Modes:");
+ Console.WriteLine(" summary - Generate documentation with only [Parameter] properties");
+ Console.WriteLine(" Supports: json, csharp");
+ Console.WriteLine(" all - Generate complete documentation (properties, methods, events)");
+ Console.WriteLine(" Supports: json only");
+ Console.WriteLine();
+ Console.WriteLine("Examples:");
+ Console.WriteLine(" # Generate Summary mode JSON");
+ Console.WriteLine(" DocApiGen --xml MyApp.xml --dll MyApp.dll --output api-summary.json");
+ Console.WriteLine();
+ Console.WriteLine(" # Generate Summary mode C#");
+ Console.WriteLine(" DocApiGen --xml MyApp.xml --dll MyApp.dll --output CodeComments.cs --format csharp");
+ Console.WriteLine();
+ Console.WriteLine(" # Generate All mode JSON");
+ Console.WriteLine(" DocApiGen --xml MyApp.xml --dll MyApp.dll --output api-all.json --mode all");
+ }
+
+ private static GenerationMode ParseMode(string modeArg)
+ {
+ return modeArg.ToLowerInvariant() switch
+ {
+ "summary" => GenerationMode.Summary,
+ "all" => GenerationMode.All,
+ _ => throw new ArgumentException($"Invalid mode '{modeArg}'. Valid modes are: summary, all")
+ };
+ }
+
+ private static void ValidateFormatCompatibility(GenerationMode mode, string format)
+ {
+ var formatLower = format.ToLowerInvariant();
+
+ // All mode only supports JSON
+ if (mode == GenerationMode.All && formatLower != "json")
+ {
+ throw new NotSupportedException(
+ $"Mode 'all' only supports JSON format. Requested format: {format}");
+ }
}
}
diff --git a/examples/Tools/FluentUI.Demo.DocApiGen/Properties/launchSettings.json b/examples/Tools/FluentUI.Demo.DocApiGen/Properties/launchSettings.json
index 1619966f4a..717f3739e2 100644
--- a/examples/Tools/FluentUI.Demo.DocApiGen/Properties/launchSettings.json
+++ b/examples/Tools/FluentUI.Demo.DocApiGen/Properties/launchSettings.json
@@ -3,6 +3,10 @@
"FluentUI.Demo.DocApiGen": {
"commandName": "Project",
"commandLineArgs": "--xml \"$(MSBuildProjectDirectory)/Microsoft.FluentUI.AspNetCore.Components.xml\" --dll \"$(SolutionDir)src/Core/bin/$(Configuration)/net9.0/Microsoft.FluentUI.AspNetCore.Components.dll\" --output \"$(SolutionDir)examples/Demo/FluentUI.Demo.Client/wwwroot/api-comments.json\" --format json"
+ },
+ "FluentUI.Demo.DocApiGen all": {
+ "commandName": "Project",
+ "commandLineArgs": "--xml \"$(MSBuildProjectDirectory)/Microsoft.FluentUI.AspNetCore.Components.xml\" --dll \"$(SolutionDir)src/Core/bin/$(Configuration)/net9.0/Microsoft.FluentUI.AspNetCore.Components.dll\" --output \"$(SolutionDir)examples/Demo/FluentUI.Demo.Client/wwwroot/api-comments-all.json\" --format json --mode all"
}
}
}
diff --git a/examples/Tools/FluentUI.Demo.DocApiGen/ReadMe.md b/examples/Tools/FluentUI.Demo.DocApiGen/ReadMe.md
index 51c86150b8..fd1477bebb 100644
--- a/examples/Tools/FluentUI.Demo.DocApiGen/ReadMe.md
+++ b/examples/Tools/FluentUI.Demo.DocApiGen/ReadMe.md
@@ -5,16 +5,55 @@ you can generate it using this project `FluentUI.Demo.DocApiGen`.
Simply run the project `FluentUI.Demo.DocApiGen` to generate the file.
-From Visual Studio
+## From Visual Studio
1. Right-click on the project `FluentUI.Demo.DocApiGen`.
1. Select the menu `Debug` > `Start without debugging`.
1. Re-run the project `FluentUI.Demo` to see the changes.
-From command line
+## From command line
-1. Open a command line in the folder `FluentUI.Demo.DocApiGen`.
-1. Run the command
- ```
- dotnet run --xml "./Microsoft.FluentUI.AspNetCore.Components.xml" --dll "../../../src/Core/bin/Debug/net9.0/Microsoft.FluentUI.AspNetCore.Components.dll" --output "../../../examples/Demo/FluentUI.Demo.Client/wwwroot/api-comments.json" --format json
- ```
+The tool uses the **compact format by default** for Summary mode, which is optimized for documentation display.
+
+### Generate Summary mode (default, compact format)
+
+```bash
+dotnet run --xml "./Microsoft.FluentUI.AspNetCore.Components.xml" --dll "../../../src/Core/bin/Debug/net9.0/Microsoft.FluentUI.AspNetCore.Components.dll" --output "../../../examples/Demo/FluentUI.Demo.Client/wwwroot/api-comments.json" --format json
+```
+
+This will generate a JSON file with the compact structure optimized for Summary mode:
+```json
+{
+ "__Generated__": {
+ "AssemblyVersion": "5.0.0-alpha.1+07c679a2",
+ "DateUtc": "2025-12-13 21:33"
+ },
+ "FluentButton": {
+ "__summary__": "The FluentButton component...",
+ "Appearance": "Gets or sets the visual appearance...",
+ ...
+ }
+}
+```
+
+### Generate All mode (complete documentation)
+
+```bash
+dotnet run --xml "./Microsoft.FluentUI.AspNetCore.Components.xml" --dll "../../../src/Core/bin/Debug/net9.0/Microsoft.FluentUI.AspNetCore.Components.dll" --output "../../../examples/Demo/FluentUI.Demo.Client/wwwroot/api-comments-all.json" --format json --mode all
+```
+
+## Documentation Formats
+
+The tool supports different JSON formats depending on the generation mode:
+
+1. **Summary Mode - Compact Format** (default): Optimized for documentation display
+ - Uses `__Generated__` for metadata
+ - Simple component names as keys
+ - Direct member properties
+ - Best for quick lookups and UI display
+
+2. **All Mode - Structured Format**: Complete documentation with all members
+ - Uses `metadata` and `components` sections
+ - CamelCase property names
+ - Full type names with namespaces
+ - Includes properties, methods, events, and enums