Repository: microsoft/kiota (csharp)

Issue:
Emit [GeneratedCode] for C# code generation
### Is your feature request related to a problem? Please describe the problem.

When generating C# client code with Kiota, the classes produced are not annotated with the [`[GeneratedCode]` attribute](https://learn.microsoft.com/dotnet/api/system.codedom.compiler.generatedcodeattribute).

While `// <auto-generated/>` is added to the source files, which is useful for tools like style analyzers, this is not visible to static analysis tools that work with compiled binaries.

For example, it is common to exclude generated code from tools such as code coverage tools, as often the coverage of such code is not considered interesting when determining test suite coverage. Many tools such as [coverlet](https://github.com/coverlet-coverage/coverlet) support [excluding code marked with specific attributes](https://github.com/coverlet-coverage/coverlet/blob/master/Documentation/MSBuildIntegration.md#attributes), such as `[ExcludeFromCodeCoverage]` or `[GeneratedCode]`, as these are easy to blanket-apply, rather than requiring types be excluded by specific names/namespaces.

See here for a similar feature request for source-generated code in the .NET runtime: https://github.com/dotnet/runtime/issues/89007

As well as the specific motivating factor of easier code coverage tooling exclusions, such annotations would also be useful for other static analysis that may wish to specifically find code that has been generated by Kiota as opposed to user-written.


### Client library/SDK language

Csharp

### Describe the solution you'd like

All C# types generated by Kiota are decorated with `[GeneratedCode("{tool}", "{version}")]`.

For example:

```diff
+ [System.CodeDom.Compiler.GeneratedCode("kiota", "1.15.0")]
  public class ApiClient : BaseRequestBuilder
  {
  }
```


### Additional context

_No response_

Pull request:
- Emit `[GeneratedCode]` attribute for C# types.
- Ignore MD024 for duplicate headings in CHANGELOG.
- Fix malformed link syntax in CHANGELOG.

Resolves #4907.

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 81e0dbe606..2136947087 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,8 @@ All notable changes to this project will be documented in this file.
 The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
 and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
 
+<!-- markdownlint-disable-file MD024 -->
+
 ## [Unreleased]
 
 ### Added
@@ -22,6 +24,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 - References to C# types generated by kiota are prefixed with `global::` to avoid name collisions. [#4796](https://github.com/microsoft/kiota/issues/4796)
 - Ensures HashSet properties in `KiotaLock` maintain IgnoreCase comparer across runs [#4916](https://github.com/microsoft/kiota/issues/4916)
 - Dropped `client base url set to` message when generating plugins. [#4905](https://github.com/microsoft/kiota/issues/4905)
+- Emit `[GeneratedCode]` attribute for C# types. [#4907](https://github.com/microsoft/kiota/issues/4907)
 
 ## [1.15.0] - 2024-06-06
 
@@ -880,7 +883,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 - Split parsable interface and additional property/data interface in Ruby. [#1654](https://github.com/microsoft/kiota/issues/1654)
 - Changed format of datetimes in Go to be converted to ISO 8601 by default when place in path parameters(Go)
 - Defined the Access Token Provider Interface for Ruby authentication. [#1638](https://github.com/microsoft/kiota/issues/1638)
-- Reduce code verbosity on Go Getters and Setters. [G0#26][https://github.com/microsoftgraph/msgraph-sdk-go-core/issues/26]
+- Reduce code verbosity on Go Getters and Setters. [G0#26](https://github.com/microsoftgraph/msgraph-sdk-go-core/issues/26)
 
 ## [0.3.0] - 2022-07-08
 
diff --git a/src/Kiota.Builder/Writers/CSharp/CodeClassDeclarationWriter.cs b/src/Kiota.Builder/Writers/CSharp/CodeClassDeclarationWriter.cs
index 040381a0fb..e02a5a5f52 100644
--- a/src/Kiota.Builder/Writers/CSharp/CodeClassDeclarationWriter.cs
+++ b/src/Kiota.Builder/Writers/CSharp/CodeClassDeclarationWriter.cs
@@ -7,6 +7,7 @@ namespace Kiota.Builder.Writers.CSharp;
 public class CodeClassDeclarationWriter : BaseElementWriter<ClassDeclaration, CSharpConventionService>
 {
     public static string AutoGenerationHeader => "// <auto-generated/>";
+    public static string GeneratedCodeAttribute { get; } = $"[global::System.CodeDom.Compiler.GeneratedCode(\"Kiota\", \"{Kiota.Generated.KiotaVersion.Current()}\")]";
     public CodeClassDeclarationWriter(CSharpConventionService conventionService) : base(conventionService) { }
     public override void WriteCodeElement(ClassDeclaration codeElement, LanguageWriter writer)
     {
@@ -37,6 +38,7 @@ public override void WriteCodeElement(ClassDeclaration codeElement, LanguageWrit
         var derivation = derivedTypes.Length != 0 ? ": " + derivedTypes.Aggregate(static (x, y) => $"{x}, {y}") : string.Empty;
         bool hasDescription = conventions.WriteLongDescription(parentClass, writer);
         conventions.WriteDeprecationAttribute(parentClass, writer);
+        writer.WriteLine(GeneratedCodeAttribute);
         if (!hasDescription) writer.WriteLine("#pragma warning disable CS1591");
         writer.WriteLine($"public partial class {codeElement.Name.ToFirstCharacterUpperCase()} {derivation}");
         if (!hasDescription) writer.WriteLine("#pragma warning restore CS1591");
diff --git a/src/Kiota.Builder/Writers/CSharp/CodeEnumWriter.cs b/src/Kiota.Builder/Writers/CSharp/CodeEnumWriter.cs
index 33a82562ad..467299f8a3 100644
--- a/src/Kiota.Builder/Writers/CSharp/CodeEnumWriter.cs
+++ b/src/Kiota.Builder/Writers/CSharp/CodeEnumWriter.cs
@@ -9,6 +9,7 @@ namespace Kiota.Builder.Writers.CSharp;
 public class CodeEnumWriter : BaseElementWriter<CodeEnum, CSharpConventionService>
 {
     public static string AutoGenerationHeader => "// <auto-generated/>";
+    public static string GeneratedCodeAttribute { get; } = $"[global::System.CodeDom.Compiler.GeneratedCode(\"Kiota\", \"{Kiota.Generated.KiotaVersion.Current()}\")]";
     public CodeEnumWriter(CSharpConventionService conventionService) : base(conventionService) { }
     public override void WriteCodeElement(CodeEnum codeElement, LanguageWriter writer)
     {
@@ -30,6 +31,7 @@ public override void WriteCodeElement(CodeEnum codeElement, LanguageWriter write
             writer.WriteLine($"namespace {codeNamespace.Name}");
             writer.StartBlock();
         }
+        writer.WriteLine(GeneratedCodeAttribute);
         bool hasDescription = conventions.WriteShortDescription(codeElement, writer);
         if (codeElement.Flags)
             writer.WriteLine("[Flags]");


Test patch:
diff --git a/tests/Kiota.Builder.Tests/Writers/CSharp/CodeClassDeclarationWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/CSharp/CodeClassDeclarationWriterTests.cs
index d9100b9886..dcb3867f5d 100644
--- a/tests/Kiota.Builder.Tests/Writers/CSharp/CodeClassDeclarationWriterTests.cs
+++ b/tests/Kiota.Builder.Tests/Writers/CSharp/CodeClassDeclarationWriterTests.cs
@@ -107,4 +107,13 @@ public void WritesImports()
         Assert.Contains("Project.Graph", result);
         Assert.Contains("System.Util", result);
     }
+
+    [Fact]
+    public void WritesGeneratedCodeAttribute()
+    {
+        codeElementWriter.WriteCodeElement(parentClass.StartBlock, writer);
+        var result = tw.ToString();
+        var pattern = @"\s+\[global::System\.CodeDom\.Compiler\.GeneratedCode\(""Kiota"", ""[0-9]+\.[0-9]+\.[0-9]+\""\)\]";
+        Assert.Matches(pattern, result);
+    }
 }
diff --git a/tests/Kiota.Builder.Tests/Writers/CSharp/CodeEnumWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/CSharp/CodeEnumWriterTests.cs
index e5e6590f4f..3cba9639ce 100644
--- a/tests/Kiota.Builder.Tests/Writers/CSharp/CodeEnumWriterTests.cs
+++ b/tests/Kiota.Builder.Tests/Writers/CSharp/CodeEnumWriterTests.cs
@@ -110,6 +110,7 @@ public void WritesFlagsEnum()
         Assert.Contains("= 1", result);
         Assert.Contains("= 2", result);
     }
+
     [Fact]
     public void WritesEnumOptionDescription()
     {
@@ -120,6 +121,7 @@ public void WritesEnumOptionDescription()
         Assert.Contains($"<summary>{Option.Documentation.DescriptionTemplate}</summary>", result);
         AssertExtensions.CurlyBracesAreClosed(result, 1);
     }
+
     [Fact]
     public void DoesntWriteAnythingOnNoOption()
     {
@@ -127,4 +129,14 @@ public void DoesntWriteAnythingOnNoOption()
         var result = tw.ToString();
         Assert.Empty(result);
     }
+
+    [Fact]
+    public void WritesGeneratedCodeAttribute()
+    {
+        currentEnum.AddOption(new CodeEnumOption { Name = "option2" });
+        writer.Write(currentEnum);
+        var result = tw.ToString();
+        var pattern = @"\s+\[global::System\.CodeDom\.Compiler\.GeneratedCode\(""Kiota"", ""[0-9]+\.[0-9]+\.[0-9]+\""\)\]";
+        Assert.Matches(pattern, result);
+    }
 }