diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/Helpers/TaskHelper.cs b/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/Helpers/TaskHelper.cs index 26f9a851a..6bd3b87dd 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/Helpers/TaskHelper.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/Helpers/TaskHelper.cs @@ -13,6 +13,8 @@ namespace StyleCop.Analyzers.Helpers internal static class TaskHelper { + public static readonly Task CompletedTask = Task.FromResult(0); + public static bool IsTaskReturningMethod(SemanticModel semanticModel, MethodDeclarationSyntax methodDeclarationSyntax, CancellationToken cancellationToken) { return IsTaskType(semanticModel, methodDeclarationSyntax.ReturnType, cancellationToken); diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/NamingRules/SA1317CodeFixProvider.cs b/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/NamingRules/SA1317CodeFixProvider.cs new file mode 100644 index 000000000..5b5780437 --- /dev/null +++ b/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/NamingRules/SA1317CodeFixProvider.cs @@ -0,0 +1,43 @@ +// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +#nullable disable + +namespace StyleCop.Analyzers.NamingRules +{ + using System.Collections.Immutable; + using System.Composition; + using System.Threading.Tasks; + using Microsoft.CodeAnalysis; + using Microsoft.CodeAnalysis.CodeActions; + using Microsoft.CodeAnalysis.CodeFixes; + using Microsoft.CodeAnalysis.CSharp; + using StyleCop.Analyzers.Helpers; + + /// + /// Implements a code fix for . + /// + /// + /// To fix a violation of this rule, replace non-Latin letters with appropriate Latin letters. + /// + [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(SA1317CodeFixProvider))] + [Shared] + internal class SA1317CodeFixProvider : CodeFixProvider + { + /// + public override ImmutableArray FixableDiagnosticIds { get; } = + ImmutableArray.Create(SA1317IdentifierShouldBeNamedOnlyWithLatinLetters.DiagnosticId); + + /// + public override FixAllProvider GetFixAllProvider() + { + return null; + } + + /// + public override async Task RegisterCodeFixesAsync(CodeFixContext context) + { + await TaskHelper.CompletedTask.ConfigureAwait(false); + } + } +} diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp10/NamingRules/SA1317CSharp10UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp10/NamingRules/SA1317CSharp10UnitTests.cs new file mode 100644 index 000000000..ed6960bcd --- /dev/null +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp10/NamingRules/SA1317CSharp10UnitTests.cs @@ -0,0 +1,13 @@ +// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +#nullable disable + +namespace StyleCop.Analyzers.Test.CSharp10.NamingRules +{ + using StyleCop.Analyzers.Test.CSharp9.NamingRules; + + public partial class SA1317CSharp10UnitTests : SA1317CSharp9UnitTests + { + } +} diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp11/NamingRules/SA1317CSharp11UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp11/NamingRules/SA1317CSharp11UnitTests.cs new file mode 100644 index 000000000..b4671e508 --- /dev/null +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp11/NamingRules/SA1317CSharp11UnitTests.cs @@ -0,0 +1,13 @@ +// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +#nullable disable + +namespace StyleCop.Analyzers.Test.CSharp11.NamingRules +{ + using StyleCop.Analyzers.Test.CSharp10.NamingRules; + + public partial class SA1317CSharp11UnitTests : SA1317CSharp10UnitTests + { + } +} diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp12/NamingRules/SA1317CSharp12UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp12/NamingRules/SA1317CSharp12UnitTests.cs new file mode 100644 index 000000000..9e8f09f80 --- /dev/null +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp12/NamingRules/SA1317CSharp12UnitTests.cs @@ -0,0 +1,13 @@ +// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +#nullable disable + +namespace StyleCop.Analyzers.Test.CSharp12.NamingRules +{ + using StyleCop.Analyzers.Test.CSharp11.NamingRules; + + public partial class SA1317CSharp12UnitTests : SA1317CSharp11UnitTests + { + } +} diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp13/NamingRules/SA1317CSharp13UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp13/NamingRules/SA1317CSharp13UnitTests.cs new file mode 100644 index 000000000..c352df66a --- /dev/null +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp13/NamingRules/SA1317CSharp13UnitTests.cs @@ -0,0 +1,13 @@ +// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +#nullable disable + +namespace StyleCop.Analyzers.Test.CSharp13.NamingRules +{ + using StyleCop.Analyzers.Test.CSharp12.NamingRules; + + public partial class SA1317CSharp13UnitTests : SA1317CSharp12UnitTests + { + } +} diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp7/NamingRules/SA1317CSharp7UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp7/NamingRules/SA1317CSharp7UnitTests.cs new file mode 100644 index 000000000..ad94a4894 --- /dev/null +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp7/NamingRules/SA1317CSharp7UnitTests.cs @@ -0,0 +1,13 @@ +// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +#nullable disable + +namespace StyleCop.Analyzers.Test.CSharp7.NamingRules +{ + using StyleCop.Analyzers.Test.NamingRules; + + public partial class SA1317CSharp7UnitTests : SA1317UnitTests + { + } +} diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/NamingRules/SA1317CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/NamingRules/SA1317CSharp8UnitTests.cs new file mode 100644 index 000000000..a8ac93634 --- /dev/null +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/NamingRules/SA1317CSharp8UnitTests.cs @@ -0,0 +1,13 @@ +// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +#nullable disable + +namespace StyleCop.Analyzers.Test.CSharp8.NamingRules +{ + using StyleCop.Analyzers.Test.CSharp7.NamingRules; + + public partial class SA1317CSharp8UnitTests : SA1317CSharp7UnitTests + { + } +} diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp9/NamingRules/SA1317CSharp9UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp9/NamingRules/SA1317CSharp9UnitTests.cs new file mode 100644 index 000000000..1ac8e9fc7 --- /dev/null +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp9/NamingRules/SA1317CSharp9UnitTests.cs @@ -0,0 +1,46 @@ +// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +#nullable disable + +namespace StyleCop.Analyzers.Test.CSharp9.NamingRules +{ + using System; + using System.Threading; + using System.Threading.Tasks; + using Microsoft.CodeAnalysis.Testing; + using StyleCop.Analyzers.Test.CSharp8.NamingRules; + using Xunit; + using static StyleCop.Analyzers.Test.Verifiers.StyleCopCodeFixVerifier< + StyleCop.Analyzers.NamingRules.SA1317IdentifierShouldBeNamedOnlyWithLatinLetters, + StyleCop.Analyzers.NamingRules.SA1317CodeFixProvider>; + + public partial class SA1317CSharp9UnitTests : SA1317CSharp8UnitTests + { + [Fact] + public async Task TestRecordNameDoesNotContainNonLatinLettersAsync() + { + var testCode = @"public record RecordName {}"; + + await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); + } + + [Fact] + public async Task TestRecordNameContainsNonLatinLettersAsync() + { + var testCode = @"public record RеcоrdNаmе {}"; + + Console.WriteLine("Test"); + + var expected = new DiagnosticResult[] + { + Diagnostic().WithArguments("RеcоrdNаmе", 1).WithLocation(1, 15), + Diagnostic().WithArguments("RеcоrdNаmе", 3).WithLocation(1, 15), + Diagnostic().WithArguments("RеcоrdNаmе", 7).WithLocation(1, 15), + Diagnostic().WithArguments("RеcоrdNаmе", 9).WithLocation(1, 15), + }; + + await VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false); + } + } +} diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test/NamingRules/SA1317UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test/NamingRules/SA1317UnitTests.cs new file mode 100644 index 000000000..992408925 --- /dev/null +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test/NamingRules/SA1317UnitTests.cs @@ -0,0 +1,495 @@ +// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +#nullable disable + +namespace StyleCop.Analyzers.Test.NamingRules +{ + using System.Threading; + using System.Threading.Tasks; + using Microsoft.CodeAnalysis.Testing; + using Xunit; + using static StyleCop.Analyzers.Test.Verifiers.StyleCopCodeFixVerifier< + StyleCop.Analyzers.NamingRules.SA1317IdentifierShouldBeNamedOnlyWithLatinLetters, + StyleCop.Analyzers.NamingRules.SA1317CodeFixProvider>; + + public class SA1317UnitTests + { + [Fact] + public async Task TestClassNameDoesNotContainNonLatinLettersAsync() + { + var testCode = @"public class ClassName {}"; + + await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); + } + + [Fact] + public async Task TestClassNameContainsNonLatinLettersAsync() + { + var testCode = @"public class СlаssNаmе {}"; + + var expected = new DiagnosticResult[] + { + Diagnostic().WithArguments("СlаssNаmе", 0).WithLocation(1, 14), + Diagnostic().WithArguments("СlаssNаmе", 2).WithLocation(1, 14), + Diagnostic().WithArguments("СlаssNаmе", 6).WithLocation(1, 14), + Diagnostic().WithArguments("СlаssNаmе", 8).WithLocation(1, 14), + }; + + await VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false); + } + + [Fact] + public async Task TestMethodNameDoesNotContainNonLatinLettersAsync() + { + var testCode = @"public class ClassName +{ + public void MethodName() {} +}"; + + await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); + } + + [Fact] + public async Task TestMethodNameContainsNonLatinLettersAsync() + { + var testCode = @"public class ClassName +{ + public void MеthоdNаmе() {} +}"; + + var expected = new DiagnosticResult[] + { + Diagnostic().WithArguments("MеthоdNаmе", 1).WithLocation(3, 17), + Diagnostic().WithArguments("MеthоdNаmе", 4).WithLocation(3, 17), + Diagnostic().WithArguments("MеthоdNаmе", 7).WithLocation(3, 17), + Diagnostic().WithArguments("MеthоdNаmе", 9).WithLocation(3, 17), + }; + + await VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false); + } + + [Fact] + public async Task TestConstructorNameDoesNotContainNonLatinLettersAsync() + { + var testCode = @"public class ClassName +{ + public ClassName() {} +}"; + + await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); + } + + [Fact] + public async Task TestConstructorNameContainsNonLatinLettersAsync() + { + var testCode = @"public class СlаssNаmе +{ + public СlаssNаmе() {} +}"; + + var expected = new DiagnosticResult[] + { + Diagnostic().WithArguments("СlаssNаmе", 0).WithLocation(1, 14), + Diagnostic().WithArguments("СlаssNаmе", 2).WithLocation(1, 14), + Diagnostic().WithArguments("СlаssNаmе", 6).WithLocation(1, 14), + Diagnostic().WithArguments("СlаssNаmе", 8).WithLocation(1, 14), + Diagnostic().WithArguments("СlаssNаmе", 0).WithLocation(3, 12), + Diagnostic().WithArguments("СlаssNаmе", 2).WithLocation(3, 12), + Diagnostic().WithArguments("СlаssNаmе", 6).WithLocation(3, 12), + Diagnostic().WithArguments("СlаssNаmе", 8).WithLocation(3, 12), + }; + + await VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false); + } + + [Fact] + public async Task TestDestructorNameDoesNotContainNonLatinLettersAsync() + { + var testCode = @"public class ClassName +{ + ~ClassName() {} +}"; + + await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); + } + + [Fact] + public async Task TestDestructorNameContainsInvalidCharactersAsync() + { + var testCode = @"public class СlаssNаmе +{ + ~СlаssNаmе() {} +}"; + + var expected = new DiagnosticResult[] + { + Diagnostic().WithArguments("СlаssNаmе", 0).WithLocation(1, 14), + Diagnostic().WithArguments("СlаssNаmе", 2).WithLocation(1, 14), + Diagnostic().WithArguments("СlаssNаmе", 6).WithLocation(1, 14), + Diagnostic().WithArguments("СlаssNаmе", 8).WithLocation(1, 14), + Diagnostic().WithArguments("СlаssNаmе", 0).WithLocation(3, 6), + Diagnostic().WithArguments("СlаssNаmе", 2).WithLocation(3, 6), + Diagnostic().WithArguments("СlаssNаmе", 6).WithLocation(3, 6), + Diagnostic().WithArguments("СlаssNаmе", 8).WithLocation(3, 6), + }; + + await VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false); + } + + [Fact] + public async Task TestEnumNameDoesNotContainNonLatinLettersAsync() + { + var testCode = @"enum EnumName {}"; + + await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); + } + + [Fact] + public async Task TestEnumNameContainsNonLatinLettersAsync() + { + var testCode = @"enum ЕnumNаmе {}"; + + var expected = new DiagnosticResult[] + { + Diagnostic().WithArguments("ЕnumNаmе", 0).WithLocation(1, 6), + Diagnostic().WithArguments("ЕnumNаmе", 5).WithLocation(1, 6), + Diagnostic().WithArguments("ЕnumNаmе", 7).WithLocation(1, 6), + }; + + await VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false); + } + + [Fact] + public async Task TestEnumValueNameDoesNotContainNonLatinLettersAsync() + { + var testCode = @"enum EnumName +{ + EnumValueName +}"; + + await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); + } + + [Fact] + public async Task TestEnumValueNameContainsNonLatinLettersAsync() + { + var testCode = @"enum EnumName +{ + ЕnumVаluеNаmе +}"; + + var expected = new DiagnosticResult[] + { + Diagnostic().WithArguments("ЕnumVаluеNаmе", 0).WithLocation(3, 5), + Diagnostic().WithArguments("ЕnumVаluеNаmе", 5).WithLocation(3, 5), + Diagnostic().WithArguments("ЕnumVаluеNаmе", 8).WithLocation(3, 5), + Diagnostic().WithArguments("ЕnumVаluеNаmе", 10).WithLocation(3, 5), + Diagnostic().WithArguments("ЕnumVаluеNаmе", 12).WithLocation(3, 5), + }; + + await VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false); + } + + [Fact] + public async Task TestStructNameDoesNotContainNonLatinLettersAsync() + { + var testCode = @"public struct StructName {}"; + + await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); + } + + [Fact] + public async Task TestStructNameContainsNonLatinLettersAsync() + { + var testCode = @"public struct StruсtNаmе {}"; + + var expected = new DiagnosticResult[] + { + Diagnostic().WithArguments("StruсtNаmе", 4).WithLocation(1, 15), + Diagnostic().WithArguments("StruсtNаmе", 7).WithLocation(1, 15), + Diagnostic().WithArguments("StruсtNаmе", 9).WithLocation(1, 15), + }; + + await VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false); + } + + [Fact] + public async Task TestInterfaceNameDoesNotContainNonLatinLettersAsync() + { + var testCode = @"interface InterfaceName {}"; + + await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); + } + + [Fact] + public async Task TestInterfaceNameContainsNonLatinLettersAsync() + { + var testCode = @"interface IntеrfасеNаmе {}"; + + var expected = new DiagnosticResult[] + { + Diagnostic().WithArguments("IntеrfасеNаmе", 3).WithLocation(1, 11), + Diagnostic().WithArguments("IntеrfасеNаmе", 6).WithLocation(1, 11), + Diagnostic().WithArguments("IntеrfасеNаmе", 7).WithLocation(1, 11), + Diagnostic().WithArguments("IntеrfасеNаmе", 8).WithLocation(1, 11), + Diagnostic().WithArguments("IntеrfасеNаmе", 10).WithLocation(1, 11), + Diagnostic().WithArguments("IntеrfасеNаmе", 12).WithLocation(1, 11), + }; + + await VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false); + } + + [Fact] + public async Task TestPropertyNameDoesNotContainNonLatinLettersAsync() + { + var testCode = @"public class ClassName +{ + public int PropertyName { get; set; } +}"; + + await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); + } + + [Fact] + public async Task TestPropertyNameContainsNonLatinLettersAsync() + { + var testCode = @"public class ClassName +{ + public int РrореrtуNаmе { get; set; } +}"; + + var expected = new DiagnosticResult[] + { + Diagnostic().WithArguments("РrореrtуNаmе", 0).WithLocation(3, 16), + Diagnostic().WithArguments("РrореrtуNаmе", 2).WithLocation(3, 16), + Diagnostic().WithArguments("РrореrtуNаmе", 3).WithLocation(3, 16), + Diagnostic().WithArguments("РrореrtуNаmе", 4).WithLocation(3, 16), + Diagnostic().WithArguments("РrореrtуNаmе", 7).WithLocation(3, 16), + Diagnostic().WithArguments("РrореrtуNаmе", 9).WithLocation(3, 16), + Diagnostic().WithArguments("РrореrtуNаmе", 11).WithLocation(3, 16), + }; + + await VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false); + } + + [Fact] + public async Task TestFieldNameDoesNotContainNonLatinLettersAsync() + { + var testCode = @"public class ClassName +{ + public int FirstFieldName, SecondFieldName; +}"; + + await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); + } + + [Fact] + public async Task TestFieldNameContainsNonLatinLettersAsync() + { + var testCode = @"public class ClassName +{ + public int FirstFiеldNаmе, SесоndFiеldNаmе; +}"; + + var expected = new DiagnosticResult[] + { + Diagnostic().WithArguments("FirstFiеldNаmе", 7).WithLocation(3, 16), + Diagnostic().WithArguments("FirstFiеldNаmе", 11).WithLocation(3, 16), + Diagnostic().WithArguments("FirstFiеldNаmе", 13).WithLocation(3, 16), + Diagnostic().WithArguments("SесоndFiеldNаmе", 1).WithLocation(3, 32), + Diagnostic().WithArguments("SесоndFiеldNаmе", 2).WithLocation(3, 32), + Diagnostic().WithArguments("SесоndFiеldNаmе", 3).WithLocation(3, 32), + Diagnostic().WithArguments("SесоndFiеldNаmе", 8).WithLocation(3, 32), + Diagnostic().WithArguments("SесоndFiеldNаmе", 12).WithLocation(3, 32), + Diagnostic().WithArguments("SесоndFiеldNаmе", 14).WithLocation(3, 32), + }; + + await VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false); + } + + [Fact] + public async Task TestEventNameDoesNotContainNonLatinLettersAsync() + { + var testCode = @"public class ClassName +{ + public delegate void DelegateName(); + public event DelegateName EventName; +}"; + + await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); + } + + [Fact] + public async Task TestEventNameContainsNonLatinLettersAsync() + { + var testCode = @"public class ClassName +{ + public delegate void DelegateName(); + public event DelegateName ЕvеntNаmе; +}"; + + var expected = new DiagnosticResult[] + { + Diagnostic().WithArguments("ЕvеntNаmе", 0).WithLocation(4, 31), + Diagnostic().WithArguments("ЕvеntNаmе", 2).WithLocation(4, 31), + Diagnostic().WithArguments("ЕvеntNаmе", 6).WithLocation(4, 31), + Diagnostic().WithArguments("ЕvеntNаmе", 8).WithLocation(4, 31), + }; + + await VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false); + } + + [Fact] + public async Task TestDelegateNameDoesNotContainNonLatinLettersAsync() + { + var testCode = @"public class ClassName +{ + public delegate void DelegateName(); +}"; + + await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); + } + + [Fact] + public async Task TestDelegateNameContainsNonLatinLettersAsync() + { + var testCode = @"public class ClassName +{ + public delegate void DеlеgаtеNаmе(); +}"; + + var expected = new DiagnosticResult[] + { + Diagnostic().WithArguments("DеlеgаtеNаmе", 1).WithLocation(3, 26), + Diagnostic().WithArguments("DеlеgаtеNаmе", 3).WithLocation(3, 26), + Diagnostic().WithArguments("DеlеgаtеNаmе", 5).WithLocation(3, 26), + Diagnostic().WithArguments("DеlеgаtеNаmе", 7).WithLocation(3, 26), + Diagnostic().WithArguments("DеlеgаtеNаmе", 9).WithLocation(3, 26), + Diagnostic().WithArguments("DеlеgаtеNаmе", 11).WithLocation(3, 26), + }; + + await VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false); + } + + [Fact] + public async Task TestVariableNameDoesNotContainNonLatinLettersAsync() + { + var testCode = @"public class ClassName +{ + public void MethodName() + { + int variableName1, variableName2 = 0; + + for (var variableName3 = 0; variableName3 < 10; ++variableName3) {} + } +}"; + + await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); + } + + [Fact] + public async Task TestVariableNameContainsNonLatinLettersAsync() + { + var testCode = @"public class ClassName +{ + public void MethodName() + { + int vаriаblеNаmе1, vаriаblеNаmе2 = 0; + + for (var vаriаblеNаmе3 = 0; vаriаblеNаmе3 < 10; ++vаriаblеNаmе3) {} + } +}"; + + var expected = new DiagnosticResult[] + { + Diagnostic().WithArguments("vаriаblеNаmе1", 1).WithLocation(5, 13), + Diagnostic().WithArguments("vаriаblеNаmе1", 4).WithLocation(5, 13), + Diagnostic().WithArguments("vаriаblеNаmе1", 7).WithLocation(5, 13), + Diagnostic().WithArguments("vаriаblеNаmе1", 9).WithLocation(5, 13), + Diagnostic().WithArguments("vаriаblеNаmе1", 11).WithLocation(5, 13), + Diagnostic().WithArguments("vаriаblеNаmе2", 1).WithLocation(5, 28), + Diagnostic().WithArguments("vаriаblеNаmе2", 4).WithLocation(5, 28), + Diagnostic().WithArguments("vаriаblеNаmе2", 7).WithLocation(5, 28), + Diagnostic().WithArguments("vаriаblеNаmе2", 9).WithLocation(5, 28), + Diagnostic().WithArguments("vаriаblеNаmе2", 11).WithLocation(5, 28), + Diagnostic().WithArguments("vаriаblеNаmе3", 1).WithLocation(7, 18), + Diagnostic().WithArguments("vаriаblеNаmе3", 4).WithLocation(7, 18), + Diagnostic().WithArguments("vаriаblеNаmе3", 7).WithLocation(7, 18), + Diagnostic().WithArguments("vаriаblеNаmе3", 9).WithLocation(7, 18), + Diagnostic().WithArguments("vаriаblеNаmе3", 11).WithLocation(7, 18), + }; + + await VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false); + } + + [Fact] + public async Task TestConstantNameDoesNotContainNonLatinLettersAsync() + { + var testCode = @"public class ClassName +{ + public void MethodName() + { + const int constName1 = 0; + } +}"; + + await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); + } + + [Fact] + public async Task TestConstantNameContainsNonLatinLettersAsync() + { + var testCode = @"public class ClassName +{ + public void MethodName() + { + const int соnstNаmе1 = 0; + } +}"; + + var expected = new DiagnosticResult[] + { + Diagnostic().WithArguments("соnstNаmе1", 0).WithLocation(5, 19), + Diagnostic().WithArguments("соnstNаmе1", 1).WithLocation(5, 19), + Diagnostic().WithArguments("соnstNаmе1", 6).WithLocation(5, 19), + Diagnostic().WithArguments("соnstNаmе1", 8).WithLocation(5, 19), + }; + + await VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false); + } + + [Fact] + public async Task TestParameterNameDoesNotContainNonLatinLettersAsync() + { + var testCode = @"public class ClassName +{ + public void MethodName(int parameterName) {} +}"; + + await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); + } + + [Fact] + public async Task TestParameterNameContainsNonLatinLettersAsync() + { + var testCode = @"public class ClassName +{ + public void MethodName(int раrаmеtеrNаmе) {} +}"; + + var expected = new DiagnosticResult[] + { + Diagnostic().WithArguments("раrаmеtеrNаmе", 0).WithLocation(3, 32), + Diagnostic().WithArguments("раrаmеtеrNаmе", 1).WithLocation(3, 32), + Diagnostic().WithArguments("раrаmеtеrNаmе", 3).WithLocation(3, 32), + Diagnostic().WithArguments("раrаmеtеrNаmе", 5).WithLocation(3, 32), + Diagnostic().WithArguments("раrаmеtеrNаmе", 7).WithLocation(3, 32), + Diagnostic().WithArguments("раrаmеtеrNаmе", 10).WithLocation(3, 32), + Diagnostic().WithArguments("раrаmеtеrNаmе", 12).WithLocation(3, 32), + }; + + await VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false); + } + } +} diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/AnalyzerReleases.Unshipped.md b/StyleCop.Analyzers/StyleCop.Analyzers/AnalyzerReleases.Unshipped.md index 83e85e73d..146f3e8ca 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers/AnalyzerReleases.Unshipped.md +++ b/StyleCop.Analyzers/StyleCop.Analyzers/AnalyzerReleases.Unshipped.md @@ -8,6 +8,7 @@ Rule ID | Category | Severity | Notes SA1141 | StyleCop.CSharp.ReadabilityRules | Warning | SA1141UseTupleSyntax, [Documentation](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1141.md) SA1142 | StyleCop.CSharp.ReadabilityRules | Warning | SA1142ReferToTupleElementsByName, [Documentation](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1142.md) SA1316 | StyleCop.CSharp.NamingRules | Warning | SA1316TupleElementNamesShouldUseCorrectCasing, [Documentation](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1316.md) +SA1317 | StyleCop.CSharp.NamingRules | Warning | SA1317IdentifierShouldBeNamedOnlyWithLatinLetters, [Documentation](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1317.md) SA1414 | StyleCop.CSharp.ReadabilityRules | Warning | SA1414TupleTypesInSignaturesShouldHaveElementNames, [Documentation](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1414.md) SA1518 | StyleCop.CSharp.LayoutRules | Warning | SA1518UseLineEndingsCorrectlyAtEndOfFile, [Documentation](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1518.md) diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/Lightup/.generated/Microsoft.CodeAnalysis.ResxSourceGenerator.CSharp/Microsoft.CodeAnalysis.ResxSourceGenerator.CSharp.CSharpResxGenerator/NamingResources.Designer.cs b/StyleCop.Analyzers/StyleCop.Analyzers/Lightup/.generated/Microsoft.CodeAnalysis.ResxSourceGenerator.CSharp/Microsoft.CodeAnalysis.ResxSourceGenerator.CSharp.CSharpResxGenerator/NamingResources.Designer.cs index 6393d2d1b..4acdba9ec 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers/Lightup/.generated/Microsoft.CodeAnalysis.ResxSourceGenerator.CSharp/Microsoft.CodeAnalysis.ResxSourceGenerator.CSharp.CSharpResxGenerator/NamingResources.Designer.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers/Lightup/.generated/Microsoft.CodeAnalysis.ResxSourceGenerator.CSharp/Microsoft.CodeAnalysis.ResxSourceGenerator.CSharp.CSharpResxGenerator/NamingResources.Designer.cs @@ -118,6 +118,12 @@ internal static partial class NamingResources public static string @SA1316MessageFormat => GetResourceString("SA1316MessageFormat")!; /// Tuple element names should use correct casing public static string @SA1316Title => GetResourceString("SA1316Title")!; + /// An identifier should be named only with Latin letters. + public static string @SA1317Description => GetResourceString("SA1317Description")!; + /// Identifier '{0}' should be named only with Latin letters. Position {1}. + public static string @SA1317MessageFormat => GetResourceString("SA1317MessageFormat")!; + /// Identifier should be named only with Latin letters + public static string @SA1317Title => GetResourceString("SA1317Title")!; /// A field name in C# does not begin with an underscore. public static string @SX1309Description => GetResourceString("SX1309Description")!; /// Field '{0}' should begin with an underscore diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/NamingRules/NamingResources.resx b/StyleCop.Analyzers/StyleCop.Analyzers/NamingRules/NamingResources.resx index 80a01e537..ed0e8c9d0 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers/NamingRules/NamingResources.resx +++ b/StyleCop.Analyzers/StyleCop.Analyzers/NamingRules/NamingResources.resx @@ -273,6 +273,15 @@ Tuple element names should use correct casing + + An identifier should be named only with Latin letters. + + + Identifier '{0}' should be named only with Latin letters. Position {1}. + + + Identifier should be named only with Latin letters + A field name in C# does not begin with an underscore. diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/NamingRules/SA1317IdentifierShouldBeNamedOnlyWithLatinLetters.cs b/StyleCop.Analyzers/StyleCop.Analyzers/NamingRules/SA1317IdentifierShouldBeNamedOnlyWithLatinLetters.cs new file mode 100644 index 000000000..13b3bcba7 --- /dev/null +++ b/StyleCop.Analyzers/StyleCop.Analyzers/NamingRules/SA1317IdentifierShouldBeNamedOnlyWithLatinLetters.cs @@ -0,0 +1,185 @@ +// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +#nullable disable + +namespace StyleCop.Analyzers.NamingRules +{ + using System; + using System.Collections.Immutable; + using Microsoft.CodeAnalysis; + using Microsoft.CodeAnalysis.CSharp; + using Microsoft.CodeAnalysis.CSharp.Syntax; + using Microsoft.CodeAnalysis.Diagnostics; + using StyleCop.Analyzers.Lightup; + using StyleCop.Analyzers.Settings.ObjectModel; + + /// + /// An identifier name contains non-Latin letters. + /// + /// + /// A violation of this rule occurs when an identifier name contains non-Latin letters. + /// This is very common mistake to use Cyrillic letter instead of the Latin letter in some countries that use Cyrillic alphabet + /// (Bulgaria, Russia, Serbia, Macedonia, Ukraine, etc.). + /// For example, these letters ('a' ('а'), 'o' ('о'), 'e' ('е'), 'k' ('к') and few others) look the same in both Latin and Cyrillic alphabets. + /// + [DiagnosticAnalyzer(LanguageNames.CSharp)] + internal class SA1317IdentifierShouldBeNamedOnlyWithLatinLetters : DiagnosticAnalyzer + { + /// + /// The ID for diagnostics produced by the analyzer. + /// + public const string DiagnosticId = "SA1317"; + private const string HelpLink = "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1317.md"; + private static readonly LocalizableString Title = new LocalizableResourceString(nameof(NamingResources.SA1317Title), NamingResources.ResourceManager, typeof(NamingResources)); + private static readonly LocalizableString MessageFormat = new LocalizableResourceString(nameof(NamingResources.SA1317MessageFormat), NamingResources.ResourceManager, typeof(NamingResources)); + private static readonly LocalizableString Description = new LocalizableResourceString(nameof(NamingResources.SA1317Description), NamingResources.ResourceManager, typeof(NamingResources)); + + private static readonly DiagnosticDescriptor Descriptor = + new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, AnalyzerCategory.NamingRules, DiagnosticSeverity.Warning, AnalyzerConstants.EnabledByDefault, Description, HelpLink); + + private static readonly Action HandleMemberDeclarationIdentifierNameAction = HandleMemberDeclarationIdentifierName; + private static readonly Action HandleVariableDeclarationIdentifierNameAction = HandleVariableDeclarationIdentifierName; + private static readonly Action HandleParameterIdentifierNameAction = HandleParameterIdentifierName; + + private static readonly ImmutableArray MemberDeclarationKinds = + ImmutableArray.Create( + SyntaxKind.EventFieldDeclaration, + SyntaxKind.EventDeclaration, + SyntaxKind.FieldDeclaration, + SyntaxKind.ConstructorDeclaration, + SyntaxKind.DestructorDeclaration, + SyntaxKind.MethodDeclaration, + SyntaxKind.NamespaceDeclaration, + SyntaxKind.PropertyDeclaration, + SyntaxKind.EnumDeclaration, + SyntaxKind.EnumMemberDeclaration, + SyntaxKind.StructDeclaration, + SyntaxKind.InterfaceDeclaration, + SyntaxKind.DelegateDeclaration, + SyntaxKind.ClassDeclaration, + SyntaxKindEx.RecordDeclaration); + + /// + public override ImmutableArray SupportedDiagnostics { get; } = + ImmutableArray.Create(Descriptor); + + /// + public override void Initialize(AnalysisContext context) + { + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + context.EnableConcurrentExecution(); + + context.RegisterCompilationStartAction(context => + { + context.RegisterSyntaxNodeAction(HandleMemberDeclarationIdentifierNameAction, MemberDeclarationKinds); + context.RegisterSyntaxNodeAction(HandleVariableDeclarationIdentifierNameAction, SyntaxKind.VariableDeclaration); + context.RegisterSyntaxNodeAction(HandleParameterIdentifierNameAction, SyntaxKind.Parameter); + }); + } + + private static void HandleMemberDeclarationIdentifierName(SyntaxNodeAnalysisContext context, StyleCopSettings settings) + { + var syntax = (MemberDeclarationSyntax)context.Node; + + if (syntax is MethodDeclarationSyntax methodDeclaration) + { + CheckIdentifierName(context, methodDeclaration.Identifier); + return; + } + + if (syntax is ConstructorDeclarationSyntax constructorDeclaration) + { + CheckIdentifierName(context, constructorDeclaration.Identifier); + return; + } + + if (syntax is DestructorDeclarationSyntax destructorDeclaration) + { + CheckIdentifierName(context, destructorDeclaration.Identifier); + return; + } + + if (syntax is BaseTypeDeclarationSyntax typeDeclaration) + { + CheckIdentifierName(context, typeDeclaration.Identifier); + return; + } + + if (syntax is PropertyDeclarationSyntax propertyDeclaration) + { + CheckIdentifierName(context, propertyDeclaration.Identifier); + return; + } + + if (syntax is DelegateDeclarationSyntax delegateDeclaration) + { + CheckIdentifierName(context, delegateDeclaration.Identifier); + return; + } + + if (syntax is EnumMemberDeclarationSyntax enumMemberDeclaration) + { + CheckIdentifierName(context, enumMemberDeclaration.Identifier); + return; + } + + if (syntax is BaseFieldDeclarationSyntax baseFieldDeclarationSyntax) + { + var variableDeclarationSyntax = baseFieldDeclarationSyntax.Declaration; + if (variableDeclarationSyntax == null || variableDeclarationSyntax.IsMissing) + { + return; + } + + foreach (var declarator in variableDeclarationSyntax.Variables) + { + if (declarator == null || declarator.IsMissing) + { + continue; + } + + CheckIdentifierName(context, declarator.Identifier); + } + } + } + + private static void HandleVariableDeclarationIdentifierName(SyntaxNodeAnalysisContext context, StyleCopSettings settings) + { + var syntax = (VariableDeclarationSyntax)context.Node; + if (syntax.Parent.IsKind(SyntaxKind.FieldDeclaration) || syntax.Parent.IsKind(SyntaxKind.EventFieldDeclaration)) + { + // This diagnostic is only for local variables. + return; + } + + foreach (var variableDeclarator in syntax.Variables) + { + if (variableDeclarator is not null) + { + CheckIdentifierName(context, variableDeclarator.Identifier); + } + } + } + + private static void HandleParameterIdentifierName(SyntaxNodeAnalysisContext context, StyleCopSettings settings) + { + var syntax = (ParameterSyntax)context.Node; + + CheckIdentifierName(context, syntax.Identifier); + } + + private static void CheckIdentifierName(SyntaxNodeAnalysisContext context, SyntaxToken identifier) + { + var name = identifier.Text; + + for (var i = 0; i < name.Length; i++) + { + if (char.IsLetter(name[i]) && name[i] is not ((>= 'a' and <= 'z') or (>= 'A' and <= 'Z'))) + { + context.ReportDiagnostic(Diagnostic.Create(Descriptor, identifier.GetLocation(), name, i)); + } + } + } + } +} diff --git a/documentation/NamingRules.md b/documentation/NamingRules.md index ddfb5d8e4..3811a68dc 100644 --- a/documentation/NamingRules.md +++ b/documentation/NamingRules.md @@ -19,3 +19,4 @@ Identifier | Name | Description [SA1313](SA1313.md) | ParameterNamesMustBeginWithLowerCaseLetter | The name of a parameter in C# does not begin with a lower-case letter. [SA1314](SA1314.md) | TypeParameterNamesMustBeginWithT | The name of a C# type parameter does not begin with the capital letter T. [SA1316](SA1316.md) | TupleElementNamesShouldUseCorrectCasing | Element names within a tuple type should have the correct casing. +[SA1317](SA1317.md) | IdentifierShouldBeNamedOnlyWithLatinLetters | An identifier should be named only with Latin letters. diff --git a/documentation/SA1317.md b/documentation/SA1317.md new file mode 100644 index 000000000..7eac36e50 --- /dev/null +++ b/documentation/SA1317.md @@ -0,0 +1,55 @@ +## SA1317 + + + + + + + + + + + + + + +
TypeNameSA1317IdentifierShouldBeNamedOnlyWithLatinLetters
CheckIdSA1317
CategoryNaming Rules
+ +## Cause + +An identifier should be named only with Latin letters. + +## Rule description + +An identifier should be named only with Latin letters. + +For example, the following code would produce a violation of this rule because identifier 'ЕxаmplеClаss' contains non-Latin letters **Е**x**а**mpl**е**Cl**а**ss: + +```csharp +public class ЕxаmplеClаss +{ +} +``` + +The following code would not produce any violations: + +```csharp +public class ExampleClass +{ +} +``` + +## How to fix violations + +To fix a violation of this rule, replace non-Latin letters with appropriate Latin letters. + +## How to suppress violations + +```csharp +[SuppressMessage("StyleCop.CSharp.NamingRules", "SA1317:IdentifierShouldBeNamedOnlyWithLatinLetters", Justification = "Reviewed.")] +``` + +```csharp +#pragma warning disable SA1317 // Identifier should be named only with Latin letters +#pragma warning restore SA1317 // Identifier should be named only with Latin letters +```