diff --git a/analyzers/its/expected/Net5/Net5--net5.0-S2436.json b/analyzers/its/expected/Net5/Net5--net5.0-S2436.json
index 1da2b5c681b..4425c85b4cb 100644
--- a/analyzers/its/expected/Net5/Net5--net5.0-S2436.json
+++ b/analyzers/its/expected/Net5/Net5--net5.0-S2436.json
@@ -2,7 +2,7 @@
 "issues":  [
 {
 "id":  "S2436",
-"message":  "Reduce the number of generic parameters in the '.LocalBar' method to no more than the 3 authorized.",
+"message":  "Reduce the number of generic parameters in the 'LocalBar' method to no more than the 3 authorized.",
 "location":  {
 "uri":  "sources\Net5\Net5\Main.cs",
 "region":  {
diff --git a/analyzers/src/SonarAnalyzer.CSharp/Extensions/SyntaxNodeExtensions.cs b/analyzers/src/SonarAnalyzer.CSharp/Extensions/SyntaxNodeExtensions.cs
index 1111611bdf9..69903ed35a0 100644
--- a/analyzers/src/SonarAnalyzer.CSharp/Extensions/SyntaxNodeExtensions.cs
+++ b/analyzers/src/SonarAnalyzer.CSharp/Extensions/SyntaxNodeExtensions.cs
@@ -91,6 +91,7 @@ public static string GetDeclarationTypeName(this SyntaxNode node) =>
                 SyntaxKind.StructDeclaration => "struct",
                 SyntaxKind.InterfaceDeclaration => "interface",
                 SyntaxKindEx.RecordClassDeclaration => "record",
+                SyntaxKindEx.RecordStructDeclaration => "record struct",
                 _ => GetUnknownType(node.Kind())
             };
 
diff --git a/analyzers/src/SonarAnalyzer.CSharp/Rules/TooManyGenericParameters.cs b/analyzers/src/SonarAnalyzer.CSharp/Rules/TooManyGenericParameters.cs
index 410a1814de9..d58785edc25 100644
--- a/analyzers/src/SonarAnalyzer.CSharp/Rules/TooManyGenericParameters.cs
+++ b/analyzers/src/SonarAnalyzer.CSharp/Rules/TooManyGenericParameters.cs
@@ -19,6 +19,7 @@
  */
 
 using System.Collections.Immutable;
+using System.Linq;
 using Microsoft.CodeAnalysis;
 using Microsoft.CodeAnalysis.CSharp;
 using Microsoft.CodeAnalysis.CSharp.Syntax;
@@ -56,20 +57,24 @@ protected override void Initialize(ParameterLoadingAnalysisContext context)
                 {
                     var typeDeclaration = (TypeDeclarationSyntax)c.Node;
 
-                    if (c.ContainingSymbol.Kind != SymbolKind.NamedType
+                    if (c.IsRedundantPositionalRecordContext()
                         || typeDeclaration.TypeParameterList == null
                         || typeDeclaration.TypeParameterList.Parameters.Count <= MaxNumberOfGenericParametersInClass)
                     {
                         return;
                     }
 
-                    c.ReportIssue(Diagnostic.Create(Rule, typeDeclaration.Identifier.GetLocation(),
-                        typeDeclaration.Identifier.ValueText, typeDeclaration.GetDeclarationTypeName(), MaxNumberOfGenericParametersInClass));
+                    c.ReportIssue(Diagnostic.Create(Rule,
+                                                    typeDeclaration.Identifier.GetLocation(),
+                                                    typeDeclaration.Identifier.ValueText,
+                                                    typeDeclaration.GetDeclarationTypeName(),
+                                                    MaxNumberOfGenericParametersInClass));
                 },
                 SyntaxKind.ClassDeclaration,
                 SyntaxKind.StructDeclaration,
                 SyntaxKind.InterfaceDeclaration,
-                SyntaxKindEx.RecordClassDeclaration);
+                SyntaxKindEx.RecordClassDeclaration,
+                SyntaxKindEx.RecordStructDeclaration);
 
             context.RegisterSyntaxNodeActionInNonGenerated(
                 c =>
@@ -81,43 +86,17 @@ protected override void Initialize(ParameterLoadingAnalysisContext context)
                         return;
                     }
 
-                    c.ReportIssue(Diagnostic.Create(
-                        Rule,
-                        methodDeclaration.Identifier.GetLocation(),
-                        $"{GetEnclosingTypeName(c.Node)}.{methodDeclaration.Identifier.ValueText}",
-                        "method",
-                        MaxNumberOfGenericParametersInMethod));
+                    c.ReportIssue(Diagnostic.Create(Rule,
+                                                    methodDeclaration.Identifier.GetLocation(),
+                                                    new[] { EnclosingTypeName(c.Node), methodDeclaration.Identifier.ValueText }.JoinNonEmpty("."),
+                                                    "method",
+                                                    MaxNumberOfGenericParametersInMethod));
                 },
                 SyntaxKind.MethodDeclaration,
                 SyntaxKindEx.LocalFunctionStatement);
         }
 
-        private static string GetEnclosingTypeName(SyntaxNode node)
-        {
-            var parent = node.Parent;
-
-            while (parent != null)
-            {
-                switch (parent.Kind())
-                {
-                    case SyntaxKind.ClassDeclaration:
-                        return ((ClassDeclarationSyntax)parent).Identifier.ValueText;
-
-                    case SyntaxKind.StructDeclaration:
-                        return ((StructDeclarationSyntax)parent).Identifier.ValueText;
-
-                    case SyntaxKind.InterfaceDeclaration:
-                        return ((InterfaceDeclarationSyntax)parent).Identifier.ValueText;
-
-                    case SyntaxKindEx.RecordClassDeclaration:
-                        return ((RecordDeclarationSyntaxWrapper)parent).Identifier.ValueText;
-
-                    default:
-                        parent = parent.Parent;
-                        break;
-                }
-            }
-            return null;
-        }
+        private static string EnclosingTypeName(SyntaxNode node) =>
+            node.Ancestors().OfType<BaseTypeDeclarationSyntax>().FirstOrDefault()?.Identifier.ValueText;
     }
 }
diff --git a/analyzers/src/SonarAnalyzer.Common/Helpers/EnumerableExtensions.cs b/analyzers/src/SonarAnalyzer.Common/Helpers/EnumerableExtensions.cs
index 4871f26b763..29fb92fe5ab 100644
--- a/analyzers/src/SonarAnalyzer.Common/Helpers/EnumerableExtensions.cs
+++ b/analyzers/src/SonarAnalyzer.Common/Helpers/EnumerableExtensions.cs
@@ -118,29 +118,36 @@ public static int IndexOf<T>(this IEnumerable<T> enumerable, Func<T, bool> predi
                 ?.index ?? -1;
 
         /// <summary>
-        /// This is string.Join() as extension. Concatenates members of collection using specified separator between each member. Selector is used to extract string value from T for concatenation.
+        /// This is <see cref="string.Join"/> as extension. It concatenates the members of the collection using the specified <paramref name="separator"/> between each member.
+        /// <paramref name="selector"/> is used to convert <typeparamref name="T"/> to <see cref="string"/> for concatenation.
         /// </summary>
         public static string JoinStr<T>(this IEnumerable<T> enumerable, string separator, Func<T, string> selector) =>
             string.Join(separator, enumerable.Select(x => selector(x)));
 
         /// <summary>
-        /// This is string.Join() as extension. Concatenates members of collection using specified separator between each member. Selector is used to extract integer value from T for concatenation.
+        /// This is <see cref="string.Join"/> as extension. It concatenates the members of the collection using the specified <paramref name="separator"/> between each member.
+        /// <paramref name="selector"/> is used to convert <typeparamref name="T"/> to <see cref="int"/> for concatenation.
         /// </summary>
         public static string JoinStr<T>(this IEnumerable<T> enumerable, string separator, Func<T, int> selector) =>
             string.Join(separator, enumerable.Select(x => selector(x)));
 
         /// <summary>
-        /// This is string.Join() as extension. Concatenates members of string collection using specified separator between each member.
+        /// This is <see cref="string.Join"/> as extension. It concatenates the members of the collection using the specified <paramref name="separator"/> between each member.
         /// </summary>
         public static string JoinStr(this IEnumerable<string> enumerable, string separator) =>
             JoinStr(enumerable, separator, x => x);
 
         /// <summary>
-        /// This is string.Join() as extension. Concatenates members of int collection using specified separator between each member.
+        /// This is <see cref="string.Join"/> as extension. It concatenates the members of the collection using the specified <paramref name="separator"/> between each member.
         /// </summary>
         public static string JoinStr(this IEnumerable<int> enumerable, string separator) =>
             JoinStr(enumerable, separator, x => x);
 
-
+        /// <summary>
+        /// Concatenates the members of a <see cref="string"/> collection using the specified <paramref name="separator"/> between each member.
+        /// Any whitespace or null member of the collection will be ignored.
+        /// </summary>
+        public static string JoinNonEmpty(this IEnumerable<string> enumerable, string separator) =>
+            string.Join(separator, enumerable.Where(x => !string.IsNullOrWhiteSpace(x)));
     }
 }
diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/Helpers/EnumerableExtensionsTest.cs b/analyzers/tests/SonarAnalyzer.UnitTest/Helpers/EnumerableExtensionsTest.cs
index 3d0414f3a29..4f58a100ccc 100644
--- a/analyzers/tests/SonarAnalyzer.UnitTest/Helpers/EnumerableExtensionsTest.cs
+++ b/analyzers/tests/SonarAnalyzer.UnitTest/Helpers/EnumerableExtensionsTest.cs
@@ -18,6 +18,8 @@
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 
+#pragma warning disable SA1122 // Use string.Empty for empty strings
+
 using System;
 using System.Collections.Generic;
 using FluentAssertions;
@@ -131,6 +133,23 @@ public void JoinStr_Int()
             new[] { 1, 22, 333 }.JoinStr(null).Should().Be("122333");
         }
 
+        [TestMethod]
+        public void JoinNonEmpty()
+        {
+            Array.Empty<string>().JoinNonEmpty(", ").Should().Be("");
+            new[] { "a" }.JoinNonEmpty(", ").Should().Be("a");
+            new[] { "a", "bb", "ccc" }.JoinNonEmpty(", ").Should().Be("a, bb, ccc");
+            new[] { "a", "bb", "ccc" }.JoinNonEmpty(null).Should().Be("abbccc");
+            new[] { "a", "bb", "ccc" }.JoinNonEmpty("").Should().Be("abbccc");
+            new[] { null, "a", "b" }.JoinNonEmpty(".").Should().Be("a.b");
+            new[] { "a", null, "b" }.JoinNonEmpty(".").Should().Be("a.b");
+            new[] { "a", "b", null }.JoinNonEmpty(".").Should().Be("a.b");
+            new string[] { null, null, null }.JoinNonEmpty(".").Should().Be("");
+            new string[] { "", "", "" }.JoinNonEmpty(".").Should().Be("");
+            new string[] { "", "\t", " " }.JoinNonEmpty(".").Should().Be("");
+            new string[] { "a", "\t", "b" }.JoinNonEmpty(".").Should().Be("a.b");
+        }
+
         [TestMethod]
         public void WhereNotNull_Class()
         {
@@ -138,7 +157,7 @@ public void WhereNotNull_Class()
             Array.Empty<object>().WhereNotNull().Should().BeEmpty();
             new object[] { null, null, null }.WhereNotNull().Should().BeEmpty();
             new object[] { 1, "a", instance }.WhereNotNull().Should().BeEquivalentTo(new object[] { 1, "a", instance });
-            new object[] { 1, "a", null }.WhereNotNull().Should().BeEquivalentTo(new object[] { 1, "a"});
+            new object[] { 1, "a", null }.WhereNotNull().Should().BeEquivalentTo(new object[] { 1, "a" });
         }
 
         [TestMethod]
@@ -162,3 +181,5 @@ public StructType(int count)
         }
     }
 }
+
+#pragma warning restore SA1122 // Use string.Empty for empty strings
diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/Rules/TooManyGenericParametersTest.cs b/analyzers/tests/SonarAnalyzer.UnitTest/Rules/TooManyGenericParametersTest.cs
index b94382a6ee7..309e04c5560 100644
--- a/analyzers/tests/SonarAnalyzer.UnitTest/Rules/TooManyGenericParametersTest.cs
+++ b/analyzers/tests/SonarAnalyzer.UnitTest/Rules/TooManyGenericParametersTest.cs
@@ -27,23 +27,27 @@ namespace SonarAnalyzer.UnitTest.Rules
     [TestClass]
     public class TooManyGenericParametersTest
     {
+        private readonly VerifierBuilder builder = new VerifierBuilder<TooManyGenericParameters>();
+
         [TestMethod]
         public void TooManyGenericParameters_DefaultValues() =>
-            OldVerifier.VerifyAnalyzer(@"TestCases\TooManyGenericParameters_DefaultValues.cs", new TooManyGenericParameters());
+            builder.AddPaths("TooManyGenericParameters.DefaultValues.cs").Verify();
 
         [TestMethod]
         public void TooManyGenericParameters_CustomValues() =>
-            OldVerifier.VerifyAnalyzer(@"TestCases\TooManyGenericParameters_CustomValues.cs",
-                new TooManyGenericParameters { MaxNumberOfGenericParametersInClass = 4, MaxNumberOfGenericParametersInMethod = 4 });
+            new VerifierBuilder()
+            .AddAnalyzer(() => new TooManyGenericParameters { MaxNumberOfGenericParametersInClass = 4, MaxNumberOfGenericParametersInMethod = 4 })
+            .AddPaths("TooManyGenericParameters.CustomValues.cs")
+            .Verify();
 
 #if NET
         [TestMethod]
         public void TooManyGenericParameters_CSharp9() =>
-            OldVerifier.VerifyAnalyzerFromCSharp9Console(@"TestCases\TooManyGenericParameters.CSharp9.cs", new TooManyGenericParameters());
+            builder.AddPaths("TooManyGenericParameters.CSharp9.cs").WithTopLevelStatements().Verify();
 
         [TestMethod]
         public void TooManyGenericParameters_CSharp10() =>
-            OldVerifier.VerifyAnalyzerFromCSharp10Console(@"TestCases\TooManyGenericParameters.CSharp10.cs", new TooManyGenericParameters());
+            builder.AddPaths("TooManyGenericParameters.CSharp10.cs").WithOptions(ParseOptionsHelper.FromCSharp10).Verify();
 #endif
     }
 }
diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/TooManyGenericParameters.CSharp10.cs b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/TooManyGenericParameters.CSharp10.cs
index 4a734e7df97..ef4f4016e3d 100644
--- a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/TooManyGenericParameters.CSharp10.cs
+++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/TooManyGenericParameters.CSharp10.cs
@@ -1,22 +1,19 @@
-void LocalFoo<T1, T2, T3>() { }
-void LocalBar<T1, T2, T3, T4>() { } // Noncompliant {{Reduce the number of generic parameters in the '.LocalBar' method to no more than the 3 authorized.}}
-
-record struct RecordStruct
+record struct RecordStruct
 {
     public void Foo<T1, T2, T3>() { }
-    public void Foo<T1, T2, T3, T4>() { } // Noncompliant  {{Reduce the number of generic parameters in the '.Foo' method to no more than the 3 authorized.}}
-                                          // Although an issue is raised, the above message is not correct. There should be 'RecordStruct.Foo' instead of '.Foo'.
+    public void Foo<T1, T2, T3, T4>() { } // Noncompliant  {{Reduce the number of generic parameters in the 'RecordStruct.Foo' method to no more than the 3 authorized.}}
+    //          ^^^
 }
 
 record struct PositionalRecordStruct(int SomeProperty)
 {
     public void Foo<T1, T2, T3>() { }
-    public void Foo<T1, T2, T3, T4>() { } // Noncompliant  {{Reduce the number of generic parameters in the '.Foo' method to no more than the 3 authorized.}}
-                                          // Although an issue is raised, the above message is not correct. There should be 'PositionalRecordStruct.Foo' instead of '.Foo'.
+    public void Foo<T1, T2, T3, T4>() { } // Noncompliant  {{Reduce the number of generic parameters in the 'PositionalRecordStruct.Foo' method to no more than the 3 authorized.}}
 }
 
 record struct RecordStruct<T1, T2> { }
-record struct RecordStruct<T1, T2, T3> { } // FN
+record struct RecordStruct<T1, T2, T3> { } // Noncompliant  {{Reduce the number of generic parameters in the 'RecordStruct' record struct to no more than the 2 authorized.}}
+//            ^^^^^^^^^^^^
 
 record struct PositionalRecordStruct<T1, T2>(int SomeProperty) { }
-record struct PositionalRecordStruct<T1, T2, T3>(int SomeProperty) { } // FN
+record struct PositionalRecordStruct<T1, T2, T3>(int SomeProperty) { } // Noncompliant  {{Reduce the number of generic parameters in the 'PositionalRecordStruct' record struct to no more than the 2 authorized.}}
diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/TooManyGenericParameters.CSharp9.cs b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/TooManyGenericParameters.CSharp9.cs
index 99f43c197bd..b398d4d16a9 100644
--- a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/TooManyGenericParameters.CSharp9.cs
+++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/TooManyGenericParameters.CSharp9.cs
@@ -1,5 +1,5 @@
 void LocalFoo<T1, T2, T3>() { }
-void LocalBar<T1, T2, T3, T4>() { } // Noncompliant {{Reduce the number of generic parameters in the '.LocalBar' method to no more than the 3 authorized.}}
+void LocalBar<T1, T2, T3, T4>() { } // Noncompliant {{Reduce the number of generic parameters in the 'LocalBar' method to no more than the 3 authorized.}}
 
 record Record
 {
diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/TooManyGenericParameters_CustomValues.cs b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/TooManyGenericParameters.CustomValues.cs
similarity index 100%
rename from analyzers/tests/SonarAnalyzer.UnitTest/TestCases/TooManyGenericParameters_CustomValues.cs
rename to analyzers/tests/SonarAnalyzer.UnitTest/TestCases/TooManyGenericParameters.CustomValues.cs
diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/TooManyGenericParameters_DefaultValues.cs b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/TooManyGenericParameters.DefaultValues.cs
similarity index 100%
rename from analyzers/tests/SonarAnalyzer.UnitTest/TestCases/TooManyGenericParameters_DefaultValues.cs
rename to analyzers/tests/SonarAnalyzer.UnitTest/TestCases/TooManyGenericParameters.DefaultValues.cs