Repository: microsoft/kiota (csharp)

Issue:
go files names should not end with _windows.go
We've had to patch the beta weekly releases for the longest time [example](https://github.com/microsoftgraph/msgraph-beta-sdk-go/pull/395/commits/a65077cbc83c4273608b6bfe34abaf6f3c9ef6fb)
Now those changes are trickling down to v1.
Both set of manual changes are making our weekly releases costlier than they need to (somebody need to check why the CI failed, cherry-pick the patch etc...)

It turns out that some suffixes are [special cased for code variants on the platform](https://stackoverflow.com/questions/25161774/what-are-conventions-for-filenames-in-go) and since our build agents are mostly linux, the files ending with _windows.go are being excluded.

We should update the go Path segmenter to escape anything ending with:
- _windows.go
- _test.go
- _unix.go
- _others.go
- _amd64.go

(there may be more, finding some official documentation on the topic would be helpful)

Pull request description:
fixes #4407
Escape all special suffixes in go file names

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c717546586..d29ee7d31b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 ### Added
 
 - Suppress CS1591 when generating CSharp code and documentation is not available
+- Added file name suffix escaping in Go to avoid generating files with reserved suffixes. [#4407](https://github.com/microsoft/kiota/issues/4407)
 
 ### Changed
 
diff --git a/src/Kiota.Builder/Extensions/StringExtensions.cs b/src/Kiota.Builder/Extensions/StringExtensions.cs
index 46a9258f6c..44738cec93 100644
--- a/src/Kiota.Builder/Extensions/StringExtensions.cs
+++ b/src/Kiota.Builder/Extensions/StringExtensions.cs
@@ -57,6 +57,15 @@ public static string ShortenFileName(this string name, int length = 251) =>
         (!string.IsNullOrEmpty(name) && name.Length > length) ? HashString(name).ToLowerInvariant() : name;
 #pragma warning restore CA1308
 
+    public static string EscapeSuffix(this string? name, HashSet<string> specialFileNameSuffixes, char separator = '_')
+    {
+        ArgumentNullException.ThrowIfNull(specialFileNameSuffixes);
+        if (string.IsNullOrEmpty(name)) return string.Empty;
+
+        var last = name.Split(separator)[^1];
+        return specialFileNameSuffixes.Contains(last) ? $"{name}_escaped" : name;
+    }
+
     public static string ToSnakeCase(this string? name, char separator = '_')
     {
         if (string.IsNullOrEmpty(name)) return string.Empty;
diff --git a/src/Kiota.Builder/PathSegmenters/GoPathSegmenter.cs b/src/Kiota.Builder/PathSegmenters/GoPathSegmenter.cs
index 4a1a29e8e0..90306fab87 100644
--- a/src/Kiota.Builder/PathSegmenters/GoPathSegmenter.cs
+++ b/src/Kiota.Builder/PathSegmenters/GoPathSegmenter.cs
@@ -1,4 +1,5 @@
-﻿using System.Collections.Generic;
+﻿using System;
+using System.Collections.Generic;
 using System.Linq;
 
 using Kiota.Builder.CodeDOM;
@@ -7,6 +8,67 @@
 namespace Kiota.Builder.PathSegmenters;
 public class GoPathSegmenter : CommonPathSegmenter
 {
+    private static readonly HashSet<string> specialFileNameSuffixes = new(StringComparer.OrdinalIgnoreCase) {
+        "test"       ,
+
+        "aix"       ,
+        "android"   ,
+        "darwin"    ,
+        "dragonfly" ,
+        "freebsd"   ,
+        "hurd"      ,
+        "illumos"   ,
+        "ios"       ,
+        "js"        ,
+        "linux"     ,
+        "nacl"      ,
+        "netbsd"    ,
+        "openbsd"   ,
+        "plan9"     ,
+        "solaris"   ,
+        "wasip1"    ,
+        "windows"   ,
+        "zos"       ,
+
+        "aix"       ,
+        "android"   ,
+        "darwin"    ,
+        "dragonfly" ,
+        "freebsd"   ,
+        "hurd"      ,
+        "illumos"   ,
+        "ios"       ,
+        "linux"     ,
+        "netbsd"    ,
+        "openbsd"   ,
+        "solaris"   ,
+
+        "386"         ,
+        "amd64"       ,
+        "amd64p32"    ,
+        "arm"         ,
+        "armbe"       ,
+        "arm64"       ,
+        "arm64be"     ,
+        "loong64"     ,
+        "mips"        ,
+        "mipsle"      ,
+        "mips64"      ,
+        "mips64le"    ,
+        "mips64p32"   ,
+        "mips64p32le" ,
+        "ppc"         ,
+        "ppc64"       ,
+        "ppc64le"     ,
+        "riscv"       ,
+        "riscv64"     ,
+        "s390"        ,
+        "s390x"       ,
+        "sparc"       ,
+        "sparc64"     ,
+        "wasm"        ,
+    };
+
     public GoPathSegmenter(string rootPath, string clientNamespaceName) : base(rootPath, clientNamespaceName) { }
     public override string FileSuffix => ".go";
     public override IEnumerable<string> GetAdditionalSegment(CodeElement currentElement, string fileName)
@@ -17,12 +79,13 @@ public override IEnumerable<string> GetAdditionalSegment(CodeElement currentElem
             _ => Enumerable.Empty<string>(),
         };
     }
+
     public override string NormalizeFileName(CodeElement currentElement)
     {
         return currentElement switch
         {
             CodeNamespace => "go",
-            _ => GetLastFileNameSegment(currentElement).ToSnakeCase().ShortenFileName(),
+            _ => GetLastFileNameSegment(currentElement).ToSnakeCase().EscapeSuffix(specialFileNameSuffixes).ShortenFileName(),
         };
     }
     public override string NormalizeNamespaceSegment(string segmentName) => segmentName?.ToLowerInvariant() ?? string.Empty;


Test patch:
diff --git a/tests/Kiota.Builder.Tests/PathSegmenters/GoPathSegmenterTests.cs b/tests/Kiota.Builder.Tests/PathSegmenters/GoPathSegmenterTests.cs
new file mode 100644
index 0000000000..7251d54cf6
--- /dev/null
+++ b/tests/Kiota.Builder.Tests/PathSegmenters/GoPathSegmenterTests.cs
@@ -0,0 +1,42 @@
+﻿using Kiota.Builder.CodeDOM;
+using Kiota.Builder.PathSegmenters;
+using Xunit;
+
+namespace Kiota.Builder.Tests.PathSegmenters;
+
+public class GoPathSegmenterTests
+{
+    private readonly GoPathSegmenter segmenter;
+    public GoPathSegmenterTests()
+    {
+        segmenter = new GoPathSegmenter("D:\\source\\repos\\kiota-sample", "client");
+    }
+
+    [Fact]
+    public void GoPathSegmenterGeneratesCorrectFileName()
+    {
+        var fileName = segmenter.NormalizeFileName(new CodeClass
+        {
+            Name = "testClass"
+        });
+        Assert.Equal("test_class", fileName); // file name should be snake case
+    }
+
+    [Fact]
+    public void GoPathSegmenterGeneratesEscapedSpecialClassName()
+    {
+        var fileName = segmenter.NormalizeFileName(new CodeClass
+        {
+            Name = "adminWindows"
+        });
+        Assert.Equal("admin_windows_escaped", fileName); // file name should be snake case and escaped
+    }
+
+    [Fact]
+    public void GoPathSegmenterGeneratesNamespaceFolderName()
+    {
+        var namespaceName = "Microsoft.Graph";
+        var normalizedNamespace = segmenter.NormalizeNamespaceSegment(namespaceName);
+        Assert.Equal("microsoft.graph", normalizedNamespace);// the file name should be lower case
+    }
+}