From 5765877a3693bac37b85bc6c17be807c0be42c4d Mon Sep 17 00:00:00 2001 From: Adrian Haberecht Date: Mon, 26 Jan 2026 17:59:23 +0100 Subject: [PATCH 1/6] Read group documentation from document tags. --- .../RefitMultipleInterfaceByTagGenerator.cs | 2 +- .../RefitMultipleInterfaceGenerator.cs | 2 +- .../XmlDocumentationGenerator.cs | 12 +++++- .../SwaggerPetstoreMultipleInterfaces.g.cs | 42 +++++++++---------- ...waggerPetstoreMultipleInterfacesByTag.g.cs | 6 +-- .../XmlDocumentationGeneratorTests.cs | 17 +++++++- 6 files changed, 49 insertions(+), 32 deletions(-) diff --git a/src/Refitter.Core/RefitMultipleInterfaceByTagGenerator.cs b/src/Refitter.Core/RefitMultipleInterfaceByTagGenerator.cs index d379a16c3..b8a7aba5b 100644 --- a/src/Refitter.Core/RefitMultipleInterfaceByTagGenerator.cs +++ b/src/Refitter.Core/RefitMultipleInterfaceByTagGenerator.cs @@ -49,7 +49,7 @@ public override IEnumerable GenerateCode() if (!interfacesByGroup.TryGetValue(kv.Key, out var sb)) { interfacesByGroup[kv.Key] = sb = new StringBuilder(); - this.docGenerator.AppendInterfaceDocumentation(operation, sb); + this.docGenerator.AppendInterfaceDocumentation(document, operation, sb); interfaceName = GetInterfaceName(kv.Key); sb.AppendLine($$""" diff --git a/src/Refitter.Core/RefitMultipleInterfaceGenerator.cs b/src/Refitter.Core/RefitMultipleInterfaceGenerator.cs index 6f744fcf2..e420a34f8 100644 --- a/src/Refitter.Core/RefitMultipleInterfaceGenerator.cs +++ b/src/Refitter.Core/RefitMultipleInterfaceGenerator.cs @@ -36,7 +36,7 @@ public override IEnumerable GenerateCode() : "Execute"; var code = new StringBuilder(); - this.docGenerator.AppendInterfaceDocumentation(operation, code); + this.docGenerator.AppendInterfaceDocumentation(document, operation, code); var interfaceName = GetInterfaceName(kv, verb, operation); code.AppendLine($$""" diff --git a/src/Refitter.Core/XmlDocumentationGenerator.cs b/src/Refitter.Core/XmlDocumentationGenerator.cs index c492ab7c9..d94a5268d 100644 --- a/src/Refitter.Core/XmlDocumentationGenerator.cs +++ b/src/Refitter.Core/XmlDocumentationGenerator.cs @@ -32,16 +32,24 @@ internal XmlDocumentationGenerator(RefitGeneratorSettings settings) /// Appends XML docs for the given interface definition to the given code builder. /// This uses the OpenAPI operation info to generate the summary and remarks. /// + /// The parent document of the group. /// The OpenAPI definition of the interface. /// The builder to append the documentation to. - public void AppendInterfaceDocumentation(OpenApiOperation group, StringBuilder code) + public void AppendInterfaceDocumentation(OpenApiDocument document, OpenApiOperation group, StringBuilder code) { if (!_settings.GenerateXmlDocCodeComments) { return; } - this.AppendXmlCommentBlock("summary", group?.Summary ?? "No summary available", code, indent: Separator); + var controllerTag = document.Tags.FirstOrDefault(tag => group?.Tags.Contains(tag.Name) ?? false); + var content = controllerTag?.Description; + if (string.IsNullOrEmpty(content)) + { + content = group?.Summary; + } + + this.AppendXmlCommentBlock("summary", content ?? "No summary available", code, indent: Separator); } /// diff --git a/src/Refitter.SourceGenerator.Tests/AdditionalFiles/Generated/SwaggerPetstoreMultipleInterfaces.g.cs b/src/Refitter.SourceGenerator.Tests/AdditionalFiles/Generated/SwaggerPetstoreMultipleInterfaces.g.cs index f9aba24c0..ebe810bd1 100644 --- a/src/Refitter.SourceGenerator.Tests/AdditionalFiles/Generated/SwaggerPetstoreMultipleInterfaces.g.cs +++ b/src/Refitter.SourceGenerator.Tests/AdditionalFiles/Generated/SwaggerPetstoreMultipleInterfaces.g.cs @@ -15,7 +15,7 @@ namespace Refitter.Tests.AdditionalFiles.ByEndpoint { - /// Update an existing pet + /// Everything about your Pets [System.CodeDom.Compiler.GeneratedCode("Refitter", "1.0.0.0")] public partial interface IUpdatePetEndpoint { @@ -50,7 +50,7 @@ public partial interface IUpdatePetEndpoint Task Execute([Body] Pet body, CancellationToken cancellationToken = default); } - /// Add a new pet to the store + /// Everything about your Pets [System.CodeDom.Compiler.GeneratedCode("Refitter", "1.0.0.0")] public partial interface IAddPetEndpoint { @@ -77,7 +77,7 @@ public partial interface IAddPetEndpoint Task Execute([Body] Pet body, CancellationToken cancellationToken = default); } - /// Finds Pets by status + /// Everything about your Pets [System.CodeDom.Compiler.GeneratedCode("Refitter", "1.0.0.0")] public partial interface IFindPetsByStatusEndpoint { @@ -103,7 +103,7 @@ public partial interface IFindPetsByStatusEndpoint Task> Execute([Query] Status? status, CancellationToken cancellationToken = default); } - /// Finds Pets by tags + /// Everything about your Pets [System.CodeDom.Compiler.GeneratedCode("Refitter", "1.0.0.0")] public partial interface IFindPetsByTagsEndpoint { @@ -129,7 +129,7 @@ public partial interface IFindPetsByTagsEndpoint Task> Execute([Query(CollectionFormat.Multi)] IEnumerable tags, CancellationToken cancellationToken = default); } - /// Find pet by ID + /// Everything about your Pets [System.CodeDom.Compiler.GeneratedCode("Refitter", "1.0.0.0")] public partial interface IGetPetByIdEndpoint { @@ -159,7 +159,7 @@ public partial interface IGetPetByIdEndpoint Task Execute(long petId, CancellationToken cancellationToken = default); } - /// Updates a pet in the store with form data + /// Everything about your Pets [System.CodeDom.Compiler.GeneratedCode("Refitter", "1.0.0.0")] public partial interface IUpdatePetWithFormEndpoint { @@ -186,12 +186,11 @@ public partial interface IUpdatePetWithFormEndpoint Task Execute(long petId, [Query] string name, [Query] string status, CancellationToken cancellationToken = default); } - /// Deletes a pet + /// Everything about your Pets [System.CodeDom.Compiler.GeneratedCode("Refitter", "1.0.0.0")] public partial interface IDeletePetEndpoint { /// Deletes a pet - /// api_key parameter /// Pet id to delete /// The cancellation token to cancel the request. /// A that completes when the request is finished. @@ -212,14 +211,13 @@ public partial interface IDeletePetEndpoint Task Execute(long petId, [Header("api_key")] string api_key, CancellationToken cancellationToken = default); } - /// uploads an image + /// Everything about your Pets [System.CodeDom.Compiler.GeneratedCode("Refitter", "1.0.0.0")] public partial interface IUploadFileEndpoint { /// uploads an image /// ID of pet to update /// Additional Metadata - /// body parameter /// The cancellation token to cancel the request. /// /// A representing the instance containing the result: @@ -239,7 +237,7 @@ public partial interface IUploadFileEndpoint Task Execute(long petId, [Query] string additionalMetadata, StreamPart body, CancellationToken cancellationToken = default); } - /// Returns pet inventories by status + /// Operations about user [System.CodeDom.Compiler.GeneratedCode("Refitter", "1.0.0.0")] public partial interface IGetInventoryEndpoint { @@ -252,13 +250,12 @@ public partial interface IGetInventoryEndpoint Task> Execute(CancellationToken cancellationToken = default); } - /// Place an order for a pet + /// Operations about user [System.CodeDom.Compiler.GeneratedCode("Refitter", "1.0.0.0")] public partial interface IPlaceOrderEndpoint { /// Place an order for a pet /// Place a new order in the store - /// body parameter /// The cancellation token to cancel the request. /// successful operation /// @@ -279,7 +276,7 @@ public partial interface IPlaceOrderEndpoint Task Execute([Body] Order body, CancellationToken cancellationToken = default); } - /// Find purchase order by ID + /// Operations about user [System.CodeDom.Compiler.GeneratedCode("Refitter", "1.0.0.0")] public partial interface IGetOrderByIdEndpoint { @@ -309,7 +306,7 @@ public partial interface IGetOrderByIdEndpoint Task Execute(long orderId, CancellationToken cancellationToken = default); } - /// Delete purchase order by ID + /// Operations about user [System.CodeDom.Compiler.GeneratedCode("Refitter", "1.0.0.0")] public partial interface IDeleteOrderEndpoint { @@ -339,7 +336,7 @@ public partial interface IDeleteOrderEndpoint Task Execute(long orderId, CancellationToken cancellationToken = default); } - /// Create user + /// Access to Petstore orders [System.CodeDom.Compiler.GeneratedCode("Refitter", "1.0.0.0")] public partial interface ICreateUserEndpoint { @@ -354,13 +351,12 @@ public partial interface ICreateUserEndpoint Task Execute([Body] User body, CancellationToken cancellationToken = default); } - /// Creates list of users with given input array + /// Access to Petstore orders [System.CodeDom.Compiler.GeneratedCode("Refitter", "1.0.0.0")] public partial interface ICreateUsersWithListInputEndpoint { /// Creates list of users with given input array /// Creates list of users with given input array - /// body parameter /// The cancellation token to cancel the request. /// Successful operation /// Thrown when the request returns a non-success status code. @@ -369,7 +365,7 @@ public partial interface ICreateUsersWithListInputEndpoint Task Execute([Body] IEnumerable body, CancellationToken cancellationToken = default); } - /// Logs user into the system + /// Access to Petstore orders [System.CodeDom.Compiler.GeneratedCode("Refitter", "1.0.0.0")] public partial interface ILoginUserEndpoint { @@ -395,7 +391,7 @@ public partial interface ILoginUserEndpoint Task Execute([Query] string username, [Query] string password, CancellationToken cancellationToken = default); } - /// Logs out current logged in user session + /// Access to Petstore orders [System.CodeDom.Compiler.GeneratedCode("Refitter", "1.0.0.0")] public partial interface ILogoutUserEndpoint { @@ -407,7 +403,7 @@ public partial interface ILogoutUserEndpoint Task Execute(CancellationToken cancellationToken = default); } - /// Get user by user name + /// Access to Petstore orders [System.CodeDom.Compiler.GeneratedCode("Refitter", "1.0.0.0")] public partial interface IGetUserByNameEndpoint { @@ -436,7 +432,7 @@ public partial interface IGetUserByNameEndpoint Task Execute(string username, CancellationToken cancellationToken = default); } - /// Update user + /// Access to Petstore orders [System.CodeDom.Compiler.GeneratedCode("Refitter", "1.0.0.0")] public partial interface IUpdateUserEndpoint { @@ -452,7 +448,7 @@ public partial interface IUpdateUserEndpoint Task Execute(string username, [Body] User body, CancellationToken cancellationToken = default); } - /// Delete user + /// Access to Petstore orders [System.CodeDom.Compiler.GeneratedCode("Refitter", "1.0.0.0")] public partial interface IDeleteUserEndpoint { diff --git a/src/Refitter.SourceGenerator.Tests/AdditionalFiles/Generated/SwaggerPetstoreMultipleInterfacesByTag.g.cs b/src/Refitter.SourceGenerator.Tests/AdditionalFiles/Generated/SwaggerPetstoreMultipleInterfacesByTag.g.cs index f6ab4e9ef..c950f32c2 100644 --- a/src/Refitter.SourceGenerator.Tests/AdditionalFiles/Generated/SwaggerPetstoreMultipleInterfacesByTag.g.cs +++ b/src/Refitter.SourceGenerator.Tests/AdditionalFiles/Generated/SwaggerPetstoreMultipleInterfacesByTag.g.cs @@ -14,7 +14,7 @@ namespace Refitter.Tests.AdditionalFiles.ByTag { - /// Update an existing pet + /// Everything about your Pets [System.CodeDom.Compiler.GeneratedCode("Refitter", "1.0.0.0")] public partial interface IPetApi { @@ -195,7 +195,7 @@ public partial interface IPetApi Task UploadFile(long petId, [Query] string additionalMetadata, StreamPart body); } - /// Returns pet inventories by status + /// Operations about user [System.CodeDom.Compiler.GeneratedCode("Refitter", "1.0.0.0")] public partial interface IStoreApi { @@ -276,7 +276,7 @@ public partial interface IStoreApi Task DeleteOrder(long orderId); } - /// Create user + /// Access to Petstore orders [System.CodeDom.Compiler.GeneratedCode("Refitter", "1.0.0.0")] public partial interface IUserApi { diff --git a/src/Refitter.Tests/XmlDocumentationGeneratorTests.cs b/src/Refitter.Tests/XmlDocumentationGeneratorTests.cs index eaf8f6568..071ae2c2d 100644 --- a/src/Refitter.Tests/XmlDocumentationGeneratorTests.cs +++ b/src/Refitter.Tests/XmlDocumentationGeneratorTests.cs @@ -22,7 +22,7 @@ public void Can_Generate_Interface_Doc_Without_Linebreaks() { var docs = new StringBuilder(); var interfaceDefinition = new OpenApiOperation { Summary = "Test", }; - this._generator.AppendInterfaceDocumentation(interfaceDefinition, docs); + this._generator.AppendInterfaceDocumentation(new OpenApiDocument(), interfaceDefinition, docs); docs.ToString().Trim().Should().Be("/// Test"); } @@ -31,12 +31,25 @@ public void Can_Generate_Interface_Doc_With_Linebreaks() { var docs = new StringBuilder(); var interfaceDefinition = new OpenApiOperation { Summary = "Test\n", }; - this._generator.AppendInterfaceDocumentation(interfaceDefinition, docs); + this._generator.AppendInterfaceDocumentation(new OpenApiDocument(), interfaceDefinition, docs); docs.ToString().Trim().Should().NotBe("/// Test"); docs.ToString().Trim().Should().Contain("") .And.Contain("Test"); } + [Test] + public void Can_Generate_Interface_Doc_From_Controller_Tag() + { + var docs = new StringBuilder(); + var interfaceDefinition = new OpenApiOperation { Summary = "Test", Tags = ["TestController"] }; + var controllerTag = new OpenApiTag { Name = "TestController", Description = "TestControllerDescription" }; + var document = new OpenApiDocument { Tags = [controllerTag] }; + + this._generator.AppendInterfaceDocumentation(document, interfaceDefinition, docs); + + docs.ToString().Trim().Should().Be("/// TestControllerDescription"); + } + [Test] public void Can_Generate_Method_Summary() { From bedec1767f92d0d21db91740d54a6d81a3ec187a Mon Sep 17 00:00:00 2001 From: Adrian Haberecht Date: Thu, 29 Jan 2026 14:33:25 +0100 Subject: [PATCH 2/6] Remove unnecessary null checks. --- src/Refitter.Core/XmlDocumentationGenerator.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Refitter.Core/XmlDocumentationGenerator.cs b/src/Refitter.Core/XmlDocumentationGenerator.cs index d94a5268d..a4dcc1404 100644 --- a/src/Refitter.Core/XmlDocumentationGenerator.cs +++ b/src/Refitter.Core/XmlDocumentationGenerator.cs @@ -42,11 +42,11 @@ public void AppendInterfaceDocumentation(OpenApiDocument document, OpenApiOperat return; } - var controllerTag = document.Tags.FirstOrDefault(tag => group?.Tags.Contains(tag.Name) ?? false); + var controllerTag = document.Tags.FirstOrDefault(tag => group.Tags.Contains(tag.Name)); var content = controllerTag?.Description; if (string.IsNullOrEmpty(content)) { - content = group?.Summary; + content = group.Summary; } this.AppendXmlCommentBlock("summary", content ?? "No summary available", code, indent: Separator); From 28b07d00324615f42c11cda46cc423019c67ea6f Mon Sep 17 00:00:00 2001 From: Adrian Haberecht Date: Thu, 29 Jan 2026 14:47:13 +0100 Subject: [PATCH 3/6] Escape XML tags in controller summary. --- src/Refitter.Core/XmlDocumentationGenerator.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Refitter.Core/XmlDocumentationGenerator.cs b/src/Refitter.Core/XmlDocumentationGenerator.cs index a4dcc1404..83f1fc3b7 100644 --- a/src/Refitter.Core/XmlDocumentationGenerator.cs +++ b/src/Refitter.Core/XmlDocumentationGenerator.cs @@ -49,7 +49,8 @@ public void AppendInterfaceDocumentation(OpenApiDocument document, OpenApiOperat content = group.Summary; } - this.AppendXmlCommentBlock("summary", content ?? "No summary available", code, indent: Separator); + content ??= "No summary available"; + this.AppendXmlCommentBlock("summary", EscapeSymbols(content), code, indent: Separator); } /// From b8c632978a2a718e45b26d196594090a51d69564 Mon Sep 17 00:00:00 2001 From: Adrian Haberecht Date: Fri, 30 Jan 2026 15:39:49 +0100 Subject: [PATCH 4/6] Separate documentation strategies based on interface generation settings. --- src/Refitter.Core/RefitInterfaceGenerator.cs | 2 +- .../RefitMultipleInterfaceByTagGenerator.cs | 2 +- .../RefitMultipleInterfaceGenerator.cs | 2 +- .../XmlDocumentationGenerator.cs | 54 ++++++++++++------- .../SwaggerPetstoreMultipleInterfaces.g.cs | 38 ++++++------- .../UseJsonInheritanceConverter.g.cs | 1 - .../UsePolymorphicSerialization.g.cs | 1 - .../XmlDocumentationGeneratorTests.cs | 7 ++- 8 files changed, 59 insertions(+), 48 deletions(-) diff --git a/src/Refitter.Core/RefitInterfaceGenerator.cs b/src/Refitter.Core/RefitInterfaceGenerator.cs index dc5623080..68cb74d0e 100644 --- a/src/Refitter.Core/RefitInterfaceGenerator.cs +++ b/src/Refitter.Core/RefitInterfaceGenerator.cs @@ -363,7 +363,7 @@ private string GenerateInterfaceDeclaration(out string interfaceName) var modifier = settings.TypeAccessibility.ToString().ToLowerInvariant(); var code = new StringBuilder(); - docGenerator.AppendInterfaceDocumentation(document, code); + docGenerator.AppendSingleInterfaceDocumentation(document, code); code.Append($""" {Separator}{GetGeneratedCodeAttribute()} {Separator}{modifier} partial interface {interfaceName}{inheritance} diff --git a/src/Refitter.Core/RefitMultipleInterfaceByTagGenerator.cs b/src/Refitter.Core/RefitMultipleInterfaceByTagGenerator.cs index b8a7aba5b..442f374ab 100644 --- a/src/Refitter.Core/RefitMultipleInterfaceByTagGenerator.cs +++ b/src/Refitter.Core/RefitMultipleInterfaceByTagGenerator.cs @@ -49,7 +49,7 @@ public override IEnumerable GenerateCode() if (!interfacesByGroup.TryGetValue(kv.Key, out var sb)) { interfacesByGroup[kv.Key] = sb = new StringBuilder(); - this.docGenerator.AppendInterfaceDocumentation(document, operation, sb); + this.docGenerator.AppendInterfaceDocumentationByTag(document, kv.Key, sb); interfaceName = GetInterfaceName(kv.Key); sb.AppendLine($$""" diff --git a/src/Refitter.Core/RefitMultipleInterfaceGenerator.cs b/src/Refitter.Core/RefitMultipleInterfaceGenerator.cs index e420a34f8..224356a30 100644 --- a/src/Refitter.Core/RefitMultipleInterfaceGenerator.cs +++ b/src/Refitter.Core/RefitMultipleInterfaceGenerator.cs @@ -36,7 +36,7 @@ public override IEnumerable GenerateCode() : "Execute"; var code = new StringBuilder(); - this.docGenerator.AppendInterfaceDocumentation(document, operation, code); + this.docGenerator.AppendInterfaceDocumentationByEndpoint(operation, code); var interfaceName = GetInterfaceName(kv, verb, operation); code.AppendLine($$""" diff --git a/src/Refitter.Core/XmlDocumentationGenerator.cs b/src/Refitter.Core/XmlDocumentationGenerator.cs index 83f1fc3b7..d60bdc95c 100644 --- a/src/Refitter.Core/XmlDocumentationGenerator.cs +++ b/src/Refitter.Core/XmlDocumentationGenerator.cs @@ -29,48 +29,62 @@ internal XmlDocumentationGenerator(RefitGeneratorSettings settings) } /// - /// Appends XML docs for the given interface definition to the given code builder. - /// This uses the OpenAPI operation info to generate the summary and remarks. + /// Generates an interface description from the tags of the given OpenAPI document and appends it to the builder. /// - /// The parent document of the group. - /// The OpenAPI definition of the interface. + /// The parent document of the controller. + /// The controller tag that the endpoints were grouped by. /// The builder to append the documentation to. - public void AppendInterfaceDocumentation(OpenApiDocument document, OpenApiOperation group, StringBuilder code) + public void AppendInterfaceDocumentationByTag(OpenApiDocument document, string tag, StringBuilder code) { if (!_settings.GenerateXmlDocCodeComments) { return; } - var controllerTag = document.Tags.FirstOrDefault(tag => group.Tags.Contains(tag.Name)); - var content = controllerTag?.Description; - if (string.IsNullOrEmpty(content)) + var controllerTag = document.Tags.FirstOrDefault(t => t.Name.Equals(tag, StringComparison.OrdinalIgnoreCase)); + var controllerDescription = controllerTag?.Description; + if (!string.IsNullOrEmpty(controllerDescription)) { - content = group.Summary; + this.AppendXmlCommentBlock("summary", EscapeSymbols(controllerDescription), code, indent: Separator); + } + } + + /// + /// Generates an interface description from the summary of the given endpoint and appends it to the builder. + /// + /// The OpenAPI definition of the endpoint. + /// The builder to append the documentation to. + public void AppendInterfaceDocumentationByEndpoint(OpenApiOperation endpoint, StringBuilder code) + { + if (!_settings.GenerateXmlDocCodeComments) + { + return; } - content ??= "No summary available"; - this.AppendXmlCommentBlock("summary", EscapeSymbols(content), code, indent: Separator); + var summary = endpoint.Summary; + if (!string.IsNullOrEmpty(summary)) + { + this.AppendXmlCommentBlock("summary", EscapeSymbols(summary), code, indent: Separator); + } } /// - /// Appends XML docs for the given interface definition to the given code builder. - /// This uses the OpenAPI document's info description as the summary. + /// Generates an interface description from the title of the given document and appends it to the builder. /// - /// The OpenAPI definition of the interface. + /// The OpenAPI definition of the document. /// The builder to append the documentation to. - public void AppendInterfaceDocumentation(OpenApiDocument document, StringBuilder code) + public void AppendSingleInterfaceDocumentation(OpenApiDocument document, StringBuilder code) { if (!_settings.GenerateXmlDocCodeComments) { return; } - this.AppendXmlCommentBlock( - "summary", - document.Info?.Title ?? "Refit interface - no description available", - code, - indent: Separator); + var title = document.Info?.Title; + if (!string.IsNullOrEmpty(title)) + { + this.AppendXmlCommentBlock("summary", EscapeSymbols(title), code, indent: Separator); + } } /// diff --git a/src/Refitter.SourceGenerator.Tests/AdditionalFiles/Generated/SwaggerPetstoreMultipleInterfaces.g.cs b/src/Refitter.SourceGenerator.Tests/AdditionalFiles/Generated/SwaggerPetstoreMultipleInterfaces.g.cs index ebe810bd1..cae33f5ad 100644 --- a/src/Refitter.SourceGenerator.Tests/AdditionalFiles/Generated/SwaggerPetstoreMultipleInterfaces.g.cs +++ b/src/Refitter.SourceGenerator.Tests/AdditionalFiles/Generated/SwaggerPetstoreMultipleInterfaces.g.cs @@ -15,7 +15,7 @@ namespace Refitter.Tests.AdditionalFiles.ByEndpoint { - /// Everything about your Pets + /// Update an existing pet [System.CodeDom.Compiler.GeneratedCode("Refitter", "1.0.0.0")] public partial interface IUpdatePetEndpoint { @@ -50,7 +50,7 @@ public partial interface IUpdatePetEndpoint Task Execute([Body] Pet body, CancellationToken cancellationToken = default); } - /// Everything about your Pets + /// Add a new pet to the store [System.CodeDom.Compiler.GeneratedCode("Refitter", "1.0.0.0")] public partial interface IAddPetEndpoint { @@ -77,7 +77,7 @@ public partial interface IAddPetEndpoint Task Execute([Body] Pet body, CancellationToken cancellationToken = default); } - /// Everything about your Pets + /// Finds Pets by status [System.CodeDom.Compiler.GeneratedCode("Refitter", "1.0.0.0")] public partial interface IFindPetsByStatusEndpoint { @@ -103,7 +103,7 @@ public partial interface IFindPetsByStatusEndpoint Task> Execute([Query] Status? status, CancellationToken cancellationToken = default); } - /// Everything about your Pets + /// Finds Pets by tags [System.CodeDom.Compiler.GeneratedCode("Refitter", "1.0.0.0")] public partial interface IFindPetsByTagsEndpoint { @@ -129,7 +129,7 @@ public partial interface IFindPetsByTagsEndpoint Task> Execute([Query(CollectionFormat.Multi)] IEnumerable tags, CancellationToken cancellationToken = default); } - /// Everything about your Pets + /// Find pet by ID [System.CodeDom.Compiler.GeneratedCode("Refitter", "1.0.0.0")] public partial interface IGetPetByIdEndpoint { @@ -159,7 +159,7 @@ public partial interface IGetPetByIdEndpoint Task Execute(long petId, CancellationToken cancellationToken = default); } - /// Everything about your Pets + /// Updates a pet in the store with form data [System.CodeDom.Compiler.GeneratedCode("Refitter", "1.0.0.0")] public partial interface IUpdatePetWithFormEndpoint { @@ -186,7 +186,7 @@ public partial interface IUpdatePetWithFormEndpoint Task Execute(long petId, [Query] string name, [Query] string status, CancellationToken cancellationToken = default); } - /// Everything about your Pets + /// Deletes a pet [System.CodeDom.Compiler.GeneratedCode("Refitter", "1.0.0.0")] public partial interface IDeletePetEndpoint { @@ -211,7 +211,7 @@ public partial interface IDeletePetEndpoint Task Execute(long petId, [Header("api_key")] string api_key, CancellationToken cancellationToken = default); } - /// Everything about your Pets + /// uploads an image [System.CodeDom.Compiler.GeneratedCode("Refitter", "1.0.0.0")] public partial interface IUploadFileEndpoint { @@ -237,7 +237,7 @@ public partial interface IUploadFileEndpoint Task Execute(long petId, [Query] string additionalMetadata, StreamPart body, CancellationToken cancellationToken = default); } - /// Operations about user + /// Returns pet inventories by status [System.CodeDom.Compiler.GeneratedCode("Refitter", "1.0.0.0")] public partial interface IGetInventoryEndpoint { @@ -250,7 +250,7 @@ public partial interface IGetInventoryEndpoint Task> Execute(CancellationToken cancellationToken = default); } - /// Operations about user + /// Place an order for a pet [System.CodeDom.Compiler.GeneratedCode("Refitter", "1.0.0.0")] public partial interface IPlaceOrderEndpoint { @@ -276,7 +276,7 @@ public partial interface IPlaceOrderEndpoint Task Execute([Body] Order body, CancellationToken cancellationToken = default); } - /// Operations about user + /// Find purchase order by ID [System.CodeDom.Compiler.GeneratedCode("Refitter", "1.0.0.0")] public partial interface IGetOrderByIdEndpoint { @@ -306,7 +306,7 @@ public partial interface IGetOrderByIdEndpoint Task Execute(long orderId, CancellationToken cancellationToken = default); } - /// Operations about user + /// Delete purchase order by ID [System.CodeDom.Compiler.GeneratedCode("Refitter", "1.0.0.0")] public partial interface IDeleteOrderEndpoint { @@ -336,7 +336,7 @@ public partial interface IDeleteOrderEndpoint Task Execute(long orderId, CancellationToken cancellationToken = default); } - /// Access to Petstore orders + /// Create user [System.CodeDom.Compiler.GeneratedCode("Refitter", "1.0.0.0")] public partial interface ICreateUserEndpoint { @@ -351,7 +351,7 @@ public partial interface ICreateUserEndpoint Task Execute([Body] User body, CancellationToken cancellationToken = default); } - /// Access to Petstore orders + /// Creates list of users with given input array [System.CodeDom.Compiler.GeneratedCode("Refitter", "1.0.0.0")] public partial interface ICreateUsersWithListInputEndpoint { @@ -365,7 +365,7 @@ public partial interface ICreateUsersWithListInputEndpoint Task Execute([Body] IEnumerable body, CancellationToken cancellationToken = default); } - /// Access to Petstore orders + /// Logs user into the system [System.CodeDom.Compiler.GeneratedCode("Refitter", "1.0.0.0")] public partial interface ILoginUserEndpoint { @@ -391,7 +391,7 @@ public partial interface ILoginUserEndpoint Task Execute([Query] string username, [Query] string password, CancellationToken cancellationToken = default); } - /// Access to Petstore orders + /// Logs out current logged in user session [System.CodeDom.Compiler.GeneratedCode("Refitter", "1.0.0.0")] public partial interface ILogoutUserEndpoint { @@ -403,7 +403,7 @@ public partial interface ILogoutUserEndpoint Task Execute(CancellationToken cancellationToken = default); } - /// Access to Petstore orders + /// Get user by user name [System.CodeDom.Compiler.GeneratedCode("Refitter", "1.0.0.0")] public partial interface IGetUserByNameEndpoint { @@ -432,7 +432,7 @@ public partial interface IGetUserByNameEndpoint Task Execute(string username, CancellationToken cancellationToken = default); } - /// Access to Petstore orders + /// Update user [System.CodeDom.Compiler.GeneratedCode("Refitter", "1.0.0.0")] public partial interface IUpdateUserEndpoint { @@ -448,7 +448,7 @@ public partial interface IUpdateUserEndpoint Task Execute(string username, [Body] User body, CancellationToken cancellationToken = default); } - /// Access to Petstore orders + /// Delete user [System.CodeDom.Compiler.GeneratedCode("Refitter", "1.0.0.0")] public partial interface IDeleteUserEndpoint { diff --git a/src/Refitter.SourceGenerator.Tests/AdditionalFiles/Generated/UseJsonInheritanceConverter.g.cs b/src/Refitter.SourceGenerator.Tests/AdditionalFiles/Generated/UseJsonInheritanceConverter.g.cs index 3e9b0f840..81e0efe25 100644 --- a/src/Refitter.SourceGenerator.Tests/AdditionalFiles/Generated/UseJsonInheritanceConverter.g.cs +++ b/src/Refitter.SourceGenerator.Tests/AdditionalFiles/Generated/UseJsonInheritanceConverter.g.cs @@ -12,7 +12,6 @@ namespace Refitter.Tests.UseJsonInheritanceConverter { - /// Refit interface - no description available [System.CodeDom.Compiler.GeneratedCode("Refitter", "1.0.0.0")] public partial interface IApiClient { diff --git a/src/Refitter.SourceGenerator.Tests/AdditionalFiles/Generated/UsePolymorphicSerialization.g.cs b/src/Refitter.SourceGenerator.Tests/AdditionalFiles/Generated/UsePolymorphicSerialization.g.cs index b20a2322a..9bb27f74b 100644 --- a/src/Refitter.SourceGenerator.Tests/AdditionalFiles/Generated/UsePolymorphicSerialization.g.cs +++ b/src/Refitter.SourceGenerator.Tests/AdditionalFiles/Generated/UsePolymorphicSerialization.g.cs @@ -12,7 +12,6 @@ namespace Refitter.Tests.UsePolymorphicSerialization { - /// Refit interface - no description available [System.CodeDom.Compiler.GeneratedCode("Refitter", "1.0.0.0")] public partial interface IApiClient { diff --git a/src/Refitter.Tests/XmlDocumentationGeneratorTests.cs b/src/Refitter.Tests/XmlDocumentationGeneratorTests.cs index 071ae2c2d..2c3550321 100644 --- a/src/Refitter.Tests/XmlDocumentationGeneratorTests.cs +++ b/src/Refitter.Tests/XmlDocumentationGeneratorTests.cs @@ -22,7 +22,7 @@ public void Can_Generate_Interface_Doc_Without_Linebreaks() { var docs = new StringBuilder(); var interfaceDefinition = new OpenApiOperation { Summary = "Test", }; - this._generator.AppendInterfaceDocumentation(new OpenApiDocument(), interfaceDefinition, docs); + this._generator.AppendInterfaceDocumentationByEndpoint(interfaceDefinition, docs); docs.ToString().Trim().Should().Be("/// Test"); } @@ -31,7 +31,7 @@ public void Can_Generate_Interface_Doc_With_Linebreaks() { var docs = new StringBuilder(); var interfaceDefinition = new OpenApiOperation { Summary = "Test\n", }; - this._generator.AppendInterfaceDocumentation(new OpenApiDocument(), interfaceDefinition, docs); + this._generator.AppendInterfaceDocumentationByEndpoint(interfaceDefinition, docs); docs.ToString().Trim().Should().NotBe("/// Test"); docs.ToString().Trim().Should().Contain("") .And.Contain("Test"); @@ -41,11 +41,10 @@ public void Can_Generate_Interface_Doc_With_Linebreaks() public void Can_Generate_Interface_Doc_From_Controller_Tag() { var docs = new StringBuilder(); - var interfaceDefinition = new OpenApiOperation { Summary = "Test", Tags = ["TestController"] }; var controllerTag = new OpenApiTag { Name = "TestController", Description = "TestControllerDescription" }; var document = new OpenApiDocument { Tags = [controllerTag] }; - this._generator.AppendInterfaceDocumentation(document, interfaceDefinition, docs); + this._generator.AppendInterfaceDocumentationByTag(document, "TestController", docs); docs.ToString().Trim().Should().Be("/// TestControllerDescription"); } From dd0f73b484591d2f8d229f4b7b4ffe25e2b7ccfe Mon Sep 17 00:00:00 2001 From: Adrian Haberecht Date: Fri, 30 Jan 2026 15:44:32 +0100 Subject: [PATCH 5/6] Introduce constant for summary tag. Make SonarQube happy. --- src/Refitter.Core/XmlDocumentationGenerator.cs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/Refitter.Core/XmlDocumentationGenerator.cs b/src/Refitter.Core/XmlDocumentationGenerator.cs index d60bdc95c..6fa876126 100644 --- a/src/Refitter.Core/XmlDocumentationGenerator.cs +++ b/src/Refitter.Core/XmlDocumentationGenerator.cs @@ -19,6 +19,11 @@ public class XmlDocumentationGenerator /// private const string Separator = " "; + /// + /// The name of the XML documentation tag used for summaries. + /// + private const string SummaryTag = "summary"; + /// /// Instantiates a new instance of the class. /// @@ -45,7 +50,7 @@ public void AppendInterfaceDocumentationByTag(OpenApiDocument document, string t var controllerDescription = controllerTag?.Description; if (!string.IsNullOrEmpty(controllerDescription)) { - this.AppendXmlCommentBlock("summary", EscapeSymbols(controllerDescription), code, indent: Separator); + this.AppendXmlCommentBlock(SummaryTag, EscapeSymbols(controllerDescription), code, indent: Separator); } } @@ -64,7 +69,7 @@ public void AppendInterfaceDocumentationByEndpoint(OpenApiOperation endpoint, St var summary = endpoint.Summary; if (!string.IsNullOrEmpty(summary)) { - this.AppendXmlCommentBlock("summary", EscapeSymbols(summary), code, indent: Separator); + this.AppendXmlCommentBlock(SummaryTag, EscapeSymbols(summary), code, indent: Separator); } } @@ -83,7 +88,7 @@ public void AppendSingleInterfaceDocumentation(OpenApiDocument document, StringB var title = document.Info?.Title; if (!string.IsNullOrEmpty(title)) { - this.AppendXmlCommentBlock("summary", EscapeSymbols(title), code, indent: Separator); + this.AppendXmlCommentBlock(SummaryTag, EscapeSymbols(title), code, indent: Separator); } } @@ -108,7 +113,7 @@ public void AppendMethodDocumentation( return; if (!string.IsNullOrWhiteSpace(method.Summary)) - this.AppendXmlCommentBlock("summary", EscapeSymbols(method.Summary), code); + this.AppendXmlCommentBlock(SummaryTag, EscapeSymbols(method.Summary), code); if (!string.IsNullOrWhiteSpace(method.Description)) this.AppendXmlCommentBlock("remarks", EscapeSymbols(method.Description), code); From 5c2e1844877111b6d01d17189c51b54ff49936d1 Mon Sep 17 00:00:00 2001 From: Adrian Haberecht Date: Fri, 30 Jan 2026 16:02:42 +0100 Subject: [PATCH 6/6] Refactor controller tag sanitization. Apply the same sanitization in documentation generator for comparison. --- src/Refitter.Core/IdentifierUtils.cs | 10 ++++++++++ .../RefitMultipleInterfaceByTagGenerator.cs | 3 +-- src/Refitter.Core/XmlDocumentationGenerator.cs | 2 +- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/Refitter.Core/IdentifierUtils.cs b/src/Refitter.Core/IdentifierUtils.cs index 223bb9fde..a774f8db8 100644 --- a/src/Refitter.Core/IdentifierUtils.cs +++ b/src/Refitter.Core/IdentifierUtils.cs @@ -53,4 +53,14 @@ public static string Sanitize(this string value) return string.Join(string.Empty, value.Split(IllegalSymbols, StringSplitOptions.RemoveEmptyEntries)) .Trim(dash); } + + /// + /// Sanitizes and formats controller tags for identifier usage. + /// + /// The tag to sanitize. + /// A sanitized, title-cased identifier string. + public static string SanitizeControllerTag(this string tag) + { + return tag.Sanitize().CapitalizeFirstCharacter(); + } } diff --git a/src/Refitter.Core/RefitMultipleInterfaceByTagGenerator.cs b/src/Refitter.Core/RefitMultipleInterfaceByTagGenerator.cs index 442f374ab..b3b2afae9 100644 --- a/src/Refitter.Core/RefitMultipleInterfaceByTagGenerator.cs +++ b/src/Refitter.Core/RefitMultipleInterfaceByTagGenerator.cs @@ -131,8 +131,7 @@ private string GetGroupName(OpenApiOperation operation, string ungroupedTitle) { if (operation.Tags.FirstOrDefault() is string group && !string.IsNullOrWhiteSpace(group)) { - return IdentifierUtils.Sanitize(group) - .CapitalizeFirstCharacter(); + return group.SanitizeControllerTag(); } return ungroupedTitle; diff --git a/src/Refitter.Core/XmlDocumentationGenerator.cs b/src/Refitter.Core/XmlDocumentationGenerator.cs index 6fa876126..9f798000a 100644 --- a/src/Refitter.Core/XmlDocumentationGenerator.cs +++ b/src/Refitter.Core/XmlDocumentationGenerator.cs @@ -46,7 +46,7 @@ public void AppendInterfaceDocumentationByTag(OpenApiDocument document, string t return; } - var controllerTag = document.Tags.FirstOrDefault(t => t.Name.Equals(tag, StringComparison.OrdinalIgnoreCase)); + var controllerTag = document.Tags.FirstOrDefault(t => t.Name.SanitizeControllerTag() == tag); var controllerDescription = controllerTag?.Description; if (!string.IsNullOrEmpty(controllerDescription)) {