diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/General/NamingConventionsAnalyzerTests.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/General/NamingConventionsAnalyzerTests.cs new file mode 100644 index 0000000..6ca1b73 --- /dev/null +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/General/NamingConventionsAnalyzerTests.cs @@ -0,0 +1,1143 @@ +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using RoslynTester.DiagnosticResults; +using RoslynTester.Helpers.CSharp; +using VSDiagnostics.Diagnostics.General.NamingConventions; + +namespace VSDiagnostics.Test.Tests.General +{ + [TestClass] + public class NamingConventionsAnalyzerTests : CSharpCodeFixVerifier + { + protected override DiagnosticAnalyzer DiagnosticAnalyzer => new NamingConventionsAnalyzer(); + protected override CodeFixProvider CodeFixProvider => new NamingConventionsCodeFix(); + + [TestMethod] + public void NamingConventionsAnalyzer_WithPrivateField_AndCapitalStart_InvokesWarning() + { + var original = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + private int X; + } +}"; + + var result = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + private int _x; + } +}"; + + var expectedDiagnostic = new DiagnosticResult + { + Id = NamingConventionsAnalyzer.DiagnosticId, + Message = string.Format(NamingConventionsAnalyzer.Message, "field", "X", "_x"), + Severity = NamingConventionsAnalyzer.Severity, + Locations = + new[] + { + new DiagnosticResultLocation("Test0.cs", 9, 21) + } + }; + + VerifyDiagnostic(original, expectedDiagnostic); + VerifyFix(original, result); + } + + [TestMethod] + public void NamingConventionsAnalyzer_WithPrivateField_AndCapitalStartWithUnderscore_InvokesWarning() + { + var original = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + private int _X; + } +}"; + + var result = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + private int _x; + } +}"; + + var expectedDiagnostic = new DiagnosticResult + { + Id = NamingConventionsAnalyzer.DiagnosticId, + Message = string.Format(NamingConventionsAnalyzer.Message, "field", "_X", "_x"), + Severity = NamingConventionsAnalyzer.Severity, + Locations = + new[] + { + new DiagnosticResultLocation("Test0.cs", 9, 21) + } + }; + + VerifyDiagnostic(original, expectedDiagnostic); + VerifyFix(original, result); + } + + [TestMethod] + public void NamingConventionsAnalyzer_WithPublicField_InvokesWarning() + { + var original = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + public int x; + } +}"; + + var result = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + public int X; + } +}"; + + var expectedDiagnostic = new DiagnosticResult + { + Id = NamingConventionsAnalyzer.DiagnosticId, + Message = string.Format(NamingConventionsAnalyzer.Message, "field", "x", "X"), + Severity = NamingConventionsAnalyzer.Severity, + Locations = + new[] + { + new DiagnosticResultLocation("Test0.cs", 9, 20) + } + }; + + VerifyDiagnostic(original, expectedDiagnostic); + VerifyFix(original, result); + } + + [TestMethod] + public void NamingConventionsAnalyzer_WithProtectedField_InvokesWarning() + { + var original = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + protected int x; + } +}"; + + var result = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + protected int X; + } +}"; + + var expectedDiagnostic = new DiagnosticResult + { + Id = NamingConventionsAnalyzer.DiagnosticId, + Message = string.Format(NamingConventionsAnalyzer.Message, "field", "x", "X"), + Severity = NamingConventionsAnalyzer.Severity, + Locations = + new[] + { + new DiagnosticResultLocation("Test0.cs", 9, 23) + } + }; + + VerifyDiagnostic(original, expectedDiagnostic); + VerifyFix(original, result); + } + + [TestMethod] + public void NamingConventionsAnalyzer_WithInternalField_InvokesWarning() + { + var original = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + internal int x; + } +}"; + + var result = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + internal int X; + } +}"; + + var expectedDiagnostic = new DiagnosticResult + { + Id = NamingConventionsAnalyzer.DiagnosticId, + Message = string.Format(NamingConventionsAnalyzer.Message, "field", "x", "X"), + Severity = NamingConventionsAnalyzer.Severity, + Locations = + new[] + { + new DiagnosticResultLocation("Test0.cs", 9, 22) + } + }; + + VerifyDiagnostic(original, expectedDiagnostic); + VerifyFix(original, result); + } + + [TestMethod] + public void NamingConventionsAnalyzer_WithProtectedInternalField_InvokesWarning() + { + var original = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + protected internal int x; + } +}"; + + var result = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + protected internal int X; + } +}"; + + var expectedDiagnostic = new DiagnosticResult + { + Id = NamingConventionsAnalyzer.DiagnosticId, + Message = string.Format(NamingConventionsAnalyzer.Message, "field", "x", "X"), + Severity = NamingConventionsAnalyzer.Severity, + Locations = + new[] + { + new DiagnosticResultLocation("Test0.cs", 9, 32) + } + }; + + VerifyDiagnostic(original, expectedDiagnostic); + VerifyFix(original, result); + } + + [TestMethod] + public void NamingConventionsAnalyzer_WithProperty_InvokesWarning() + { + var original = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + public int x { get; set; } + } +}"; + + var result = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + public int X { get; set; } + } +}"; + + var expectedDiagnostic = new DiagnosticResult + { + Id = NamingConventionsAnalyzer.DiagnosticId, + Message = string.Format(NamingConventionsAnalyzer.Message, "property", "x", "X"), + Severity = NamingConventionsAnalyzer.Severity, + Locations = + new[] + { + new DiagnosticResultLocation("Test0.cs", 9, 20) + } + }; + + VerifyDiagnostic(original, expectedDiagnostic); + VerifyFix(original, result); + } + + [TestMethod] + public void NamingConventionsAnalyzer_WithMethod_InvokesWarning() + { + var original = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + void method() + { + } + } +}"; + + var result = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + } + } +}"; + + var expectedDiagnostic = new DiagnosticResult + { + Id = NamingConventionsAnalyzer.DiagnosticId, + Message = string.Format(NamingConventionsAnalyzer.Message, "method", "method", "Method"), + Severity = NamingConventionsAnalyzer.Severity, + Locations = + new[] + { + new DiagnosticResultLocation("Test0.cs", 9, 14) + } + }; + + VerifyDiagnostic(original, expectedDiagnostic); + VerifyFix(original, result); + } + + [TestMethod] + public void NamingConventionsAnalyzer_WithClass_InvokesWarning() + { + var original = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class myClass + { + } +}"; + + var result = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + } +}"; + + var expectedDiagnostic = new DiagnosticResult + { + Id = NamingConventionsAnalyzer.DiagnosticId, + Message = string.Format(NamingConventionsAnalyzer.Message, "class", "myClass", "MyClass"), + Severity = NamingConventionsAnalyzer.Severity, + Locations = + new[] + { + new DiagnosticResultLocation("Test0.cs", 7, 11) + } + }; + + VerifyDiagnostic(original, expectedDiagnostic); + VerifyFix(original, result); + } + + [TestMethod] + public void NamingConventionsAnalyzer_WithInterface_WithoutPrefix_InvokesWarning() + { + var original = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + interface something + { + } +}"; + + var result = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + interface ISomething + { + } +}"; + + var expectedDiagnostic = new DiagnosticResult + { + Id = NamingConventionsAnalyzer.DiagnosticId, + Message = string.Format(NamingConventionsAnalyzer.Message, "interface", "something", "ISomething"), + Severity = NamingConventionsAnalyzer.Severity, + Locations = + new[] + { + new DiagnosticResultLocation("Test0.cs", 7, 15) + } + }; + + VerifyDiagnostic(original, expectedDiagnostic); + VerifyFix(original, result); + } + + [TestMethod] + public void NamingConventionsAnalyzer_WithInterface_WithPrefix_AndLowerSecondLetter_InvokesWarning() + { + var original = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + interface Isomething + { + } +}"; + + var result = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + interface ISomething + { + } +}"; + + var expectedDiagnostic = new DiagnosticResult + { + Id = NamingConventionsAnalyzer.DiagnosticId, + Message = string.Format(NamingConventionsAnalyzer.Message, "interface", "Isomething", "ISomething"), + Severity = NamingConventionsAnalyzer.Severity, + Locations = + new[] + { + new DiagnosticResultLocation("Test0.cs", 7, 15) + } + }; + + VerifyDiagnostic(original, expectedDiagnostic); + VerifyFix(original, result); + } + + [TestMethod] + public void NamingConventionsAnalyzer_WithInterface_WithlowerPrefix_AndCapitalSecondLetter_InvokesWarning() + { + var original = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + interface iSomething + { + } +}"; + + var result = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + interface ISomething + { + } +}"; + + var expectedDiagnostic = new DiagnosticResult + { + Id = NamingConventionsAnalyzer.DiagnosticId, + Message = string.Format(NamingConventionsAnalyzer.Message, "interface", "iSomething", "ISomething"), + Severity = NamingConventionsAnalyzer.Severity, + Locations = + new[] + { + new DiagnosticResultLocation("Test0.cs", 7, 15) + } + }; + + VerifyDiagnostic(original, expectedDiagnostic); + VerifyFix(original, result); + } + + [TestMethod] + public void NamingConventionsAnalyzer_WithLocalVariable_InvokesWarning() + { + var original = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + var MyVar = 5; + } + } +}"; + + var result = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + var myVar = 5; + } + } +}"; + + var expectedDiagnostic = new DiagnosticResult + { + Id = NamingConventionsAnalyzer.DiagnosticId, + Message = string.Format(NamingConventionsAnalyzer.Message, "local", "MyVar", "myVar"), + Severity = NamingConventionsAnalyzer.Severity, + Locations = + new[] + { + new DiagnosticResultLocation("Test0.cs", 11, 17) + } + }; + + VerifyDiagnostic(original, expectedDiagnostic); + VerifyFix(original, result); + } + + [TestMethod] + public void NamingConventionsAnalyzer_WithParameter_InvokesWarning() + { + var original = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + void Method(string Param) + { + } + } +}"; + + var result = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + void Method(string param) + { + } + } +}"; + + var expectedDiagnostic = new DiagnosticResult + { + Id = NamingConventionsAnalyzer.DiagnosticId, + Message = string.Format(NamingConventionsAnalyzer.Message, "parameter", "Param", "param"), + Severity = NamingConventionsAnalyzer.Severity, + Locations = + new[] + { + new DiagnosticResultLocation("Test0.cs", 9, 28) + } + }; + + VerifyDiagnostic(original, expectedDiagnostic); + VerifyFix(original, result); + } + + [TestMethod] + public void NamingConventionsAnalyzer_WithMultipleFields_InvokesWarning() + { + var original = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + private int X, Y; + } +}"; + + var result = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + private int _x, _y; + } +}"; + + var expectedDiagnostic = new DiagnosticResult + { + Id = NamingConventionsAnalyzer.DiagnosticId, + Message = string.Format(NamingConventionsAnalyzer.Message, "field", "X", "_x"), + Severity = NamingConventionsAnalyzer.Severity, + Locations = + new[] + { + new DiagnosticResultLocation("Test0.cs", 9, 21) + } + }; + + var expectedDiagnostic2 = new DiagnosticResult + { + Id = NamingConventionsAnalyzer.DiagnosticId, + Message = string.Format(NamingConventionsAnalyzer.Message, "field", "Y", "_y"), + Severity = NamingConventionsAnalyzer.Severity, + Locations = + new[] + { + new DiagnosticResultLocation("Test0.cs", 9, 24) + } + }; + + VerifyDiagnostic(original, expectedDiagnostic, expectedDiagnostic2); + VerifyFix(original, result); + } + + [TestMethod] + public void NamingConventionsAnalyzer_WithPrivateField_FollowingConventions_DoesNotInvokeWarning() + { + var original = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + private int _x; + } +}"; + + VerifyDiagnostic(original); + } + + [TestMethod] + public void NamingConventionsAnalyzer_WithProtectedField_FollowingConventions_DoesNotInvokeWarning() + { + var original = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + protected int X; + } +}"; + + VerifyDiagnostic(original); + } + + [TestMethod] + public void NamingConventionsAnalyzer_WithInternalField_FollowingConventions_DoesNotInvokeWarning() + { + var original = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + internal int X; + } +}"; + + VerifyDiagnostic(original); + } + + [TestMethod] + public void NamingConventionsAnalyzer_WithPublicField_FollowingConventions_DoesNotInvokeWarning() + { + var original = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + public int X; + } +}"; + + VerifyDiagnostic(original); + } + + [TestMethod] + public void NamingConventionsAnalyzer_WithInternalProtectedField_FollowingConventions_DoesNotInvokeWarning() + { + var original = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + internal protected int X; + } +}"; + + VerifyDiagnostic(original); + } + + [TestMethod] + public void NamingConventionsAnalyzer_WithProperty_FollowingConventions_DoesNotInvokeWarning() + { + var original = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + private int X { get; set; } + } +}"; + + VerifyDiagnostic(original); + } + + [TestMethod] + public void NamingConventionsAnalyzer_WithMethod_FollowingConventions_DoesNotInvokeWarning() + { + var original = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + } + } +}"; + + VerifyDiagnostic(original); + } + + [TestMethod] + public void NamingConventionsAnalyzer_WithClass_FollowingConventions_DoesNotInvokeWarning() + { + var original = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + } +}"; + + VerifyDiagnostic(original); + } + + [TestMethod] + public void NamingConventionsAnalyzer_WithInterface_FollowingConventions_DoesNotInvokeWarning() + { + var original = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + interface ISomething + { + } +}"; + + VerifyDiagnostic(original); + } + + [TestMethod] + public void NamingConventionsAnalyzer_WithLocalVariable_FollowingConventions_DoesNotInvokeWarning() + { + var original = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + string myVar = string.Empty; + } + } +}"; + + VerifyDiagnostic(original); + } + + [TestMethod] + public void NamingConventionsAnalyzer_WithParameter_FollowingConventions_DoesNotInvokeWarning() + { + var original = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + void Method(string param) + { + } + } +}"; + + VerifyDiagnostic(original); + } + + [TestMethod] + public void NamingConventionsAnalyzer_WithVerbatimIdentifier_DoesNotInvokeWarning() + { + var original = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + public int @class; + } +}"; + VerifyDiagnostic(original); + } + + [TestMethod] + public void NamingConventionsAnalyzer_WithEscapedIdentifier_DoesNotInvokeWarning() + { + var original = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + public int \u0061ss; + } +}"; + VerifyDiagnostic(original); + } + + [TestMethod] + public void NamingConventionsAnalyzer_WithMultipleDifferentTypes_InvokesWarning() + { + var original = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + internal int x; + + void method() + { + } + } +}"; + + var result = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + internal int X; + + void Method() + { + } + } +}"; + + var expectedDiagnostic = new DiagnosticResult + { + Id = NamingConventionsAnalyzer.DiagnosticId, + Message = string.Format(NamingConventionsAnalyzer.Message, "field", "x", "X"), + Severity = NamingConventionsAnalyzer.Severity, + Locations = + new[] + { + new DiagnosticResultLocation("Test0.cs", 9, 22) + } + }; + + var expectedDiagnostic2 = new DiagnosticResult + { + Id = NamingConventionsAnalyzer.DiagnosticId, + Message = string.Format(NamingConventionsAnalyzer.Message, "method", "method", "Method"), + Severity = NamingConventionsAnalyzer.Severity, + Locations = + new[] + { + new DiagnosticResultLocation("Test0.cs", 11, 14) + } + }; + + VerifyDiagnostic(original, expectedDiagnostic, expectedDiagnostic2); + VerifyFix(original, result); + } + + [TestMethod] + public void NamingConventionsAnalyzer_WithMultipleSimilarTypes_InvokesWarning() + { + var original = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + int X, Y; + } + } +}"; + + var result = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + void Method() + { + int x, y; + } + } +}"; + + var expectedDiagnostic = new DiagnosticResult + { + Id = NamingConventionsAnalyzer.DiagnosticId, + Message = string.Format(NamingConventionsAnalyzer.Message, "local", "X", "x"), + Severity = NamingConventionsAnalyzer.Severity, + Locations = + new[] + { + new DiagnosticResultLocation("Test0.cs", 11, 17) + } + }; + + var expectedDiagnostic2 = new DiagnosticResult + { + Id = NamingConventionsAnalyzer.DiagnosticId, + Message = string.Format(NamingConventionsAnalyzer.Message, "local", "Y", "y"), + Severity = NamingConventionsAnalyzer.Severity, + Locations = + new[] + { + new DiagnosticResultLocation("Test0.cs", 11, 20) + } + }; + + VerifyDiagnostic(original, expectedDiagnostic, expectedDiagnostic2); + VerifyFix(original, result); + } + + [TestMethod] + public void NamingConventionsAnalyzer_WithExclusivelySpecialCharacters_DoesNotInvokeWarning() + { + var original = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + protected int ___; + } +}"; + VerifyDiagnostic(original); + } + + [TestMethod] + public void NamingConventionsAnalyzer_WithOneLetterPrivateVariable_InvokesWarning() + { + var original = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + private int x; + } +}"; + + var result = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + private int _x; + } +}"; + + var expectedDiagnostic = new DiagnosticResult + { + Id = NamingConventionsAnalyzer.DiagnosticId, + Message = string.Format(NamingConventionsAnalyzer.Message, "field", "x", "_x"), + Severity = NamingConventionsAnalyzer.Severity, + Locations = + new[] + { + new DiagnosticResultLocation("Test0.cs", 9, 21) + } + }; + + VerifyDiagnostic(original, expectedDiagnostic); + VerifyFix(original, result); + } + + [TestMethod] + public void NamingConventionsAnalyzer_WithPrivateField_WithoutAccessModifier_InvokesWarning() + { + var original = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + int X; + } +}"; + + var result = @" +using System; +using System.Text; + +namespace ConsoleApplication1 +{ + class MyClass + { + int _x; + } +}"; + + var expectedDiagnostic = new DiagnosticResult + { + Id = NamingConventionsAnalyzer.DiagnosticId, + Message = string.Format(NamingConventionsAnalyzer.Message, "field", "X", "_x"), + Severity = NamingConventionsAnalyzer.Severity, + Locations = + new[] + { + new DiagnosticResultLocation("Test0.cs", 9, 13) + } + }; + + VerifyDiagnostic(original, expectedDiagnostic); + VerifyFix(original, result); + } + } +} \ No newline at end of file diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/VSDiagnostics.Test.csproj b/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/VSDiagnostics.Test.csproj index b275b95..c3f8b2e 100644 --- a/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/VSDiagnostics.Test.csproj +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/VSDiagnostics.Test.csproj @@ -121,6 +121,7 @@ + diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/packages.config b/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/packages.config index 1cb831c..bee7325 100644 --- a/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/packages.config +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/packages.config @@ -1,10 +1,16 @@  + - - - - - + + + + + diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/NamingConventions/NamingConventionsAnalyzer.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/NamingConventions/NamingConventionsAnalyzer.cs new file mode 100644 index 0000000..d7ab9d1 --- /dev/null +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/NamingConventions/NamingConventionsAnalyzer.cs @@ -0,0 +1,119 @@ +using System.Collections.Immutable; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; +using VSDiagnostics.Utilities; + +namespace VSDiagnostics.Diagnostics.General.NamingConventions +{ + [DiagnosticAnalyzer(LanguageNames.CSharp)] + public class NamingConventionsAnalyzer : DiagnosticAnalyzer + { + public const string DiagnosticId = nameof(NamingConventionsAnalyzer); + internal const string Title = "A member does not follow naming conventions."; + internal const string Message = "The {0} {1} does not follow naming conventions. Should be {2}."; + internal const string Category = "General"; + internal const DiagnosticSeverity Severity = DiagnosticSeverity.Warning; + internal static DiagnosticDescriptor Rule = new DiagnosticDescriptor(DiagnosticId, Title, Message, Category, Severity, true); + public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); + + public override void Initialize(AnalysisContext context) + { + context.RegisterSyntaxNodeAction(AnalyzeSymbol, + SyntaxKind.FieldDeclaration, + SyntaxKind.PropertyDeclaration, + SyntaxKind.MethodDeclaration, + SyntaxKind.ClassDeclaration, + SyntaxKind.InterfaceDeclaration, + SyntaxKind.LocalDeclarationStatement, + SyntaxKind.Parameter); + } + + private void AnalyzeSymbol(SyntaxNodeAnalysisContext context) + { + var nodeAsField = context.Node as FieldDeclarationSyntax; + if (nodeAsField != null) + { + if (nodeAsField.Declaration == null) + { + return; + } + + foreach (var variable in nodeAsField.Declaration.Variables) + { + if (nodeAsField.Modifiers.Any(x => new[] { "internal", "protected", "public" }.Contains(x.Text))) + { + CheckNaming(variable.Identifier, "field", NamingConvention.UpperCamelCase, context); + } + else if (nodeAsField.Modifiers.Any(x => x.Text == "private") || nodeAsField.Modifiers.Count == 0 /* no access modifier defaults to private */) + { + CheckNaming(variable.Identifier, "field", NamingConvention.UnderscoreLowerCamelCase, context); + } + else + { + return; // Code is in an incomplete state + } + } + + return; + } + + var nodeAsProperty = context.Node as PropertyDeclarationSyntax; + if (nodeAsProperty != null) + { + CheckNaming(nodeAsProperty.Identifier, "property", NamingConvention.UpperCamelCase, context); + } + + var nodeAsMethod = context.Node as MethodDeclarationSyntax; + if (nodeAsMethod != null) + { + CheckNaming(nodeAsMethod.Identifier, "method", NamingConvention.UpperCamelCase, context); + } + + var nodeAsClass = context.Node as ClassDeclarationSyntax; + if (nodeAsClass != null) + { + CheckNaming(nodeAsClass.Identifier, "class", NamingConvention.UpperCamelCase, context); + } + + var nodeAsInterface = context.Node as InterfaceDeclarationSyntax; + if (nodeAsInterface != null) + { + CheckNaming(nodeAsInterface.Identifier, "interface", NamingConvention.InterfacePrefixUpperCamelCase, context); + } + + var nodeAsLocal = context.Node as LocalDeclarationStatementSyntax; + if (nodeAsLocal != null) + { + if (nodeAsLocal.Declaration == null) + { + return; + } + + foreach (var variable in nodeAsLocal.Declaration.Variables) + { + CheckNaming(variable.Identifier, "local", NamingConvention.LowerCamelCase, context); + } + + return; + } + + var nodeAsParameter = context.Node as ParameterSyntax; + if (nodeAsParameter != null) + { + CheckNaming(nodeAsParameter.Identifier, "parameter", NamingConvention.LowerCamelCase, context); + } + } + + private void CheckNaming(SyntaxToken currentIdentifier, string memberType, NamingConvention convention, SyntaxNodeAnalysisContext context) + { + var conventionedIdentifier = currentIdentifier.WithConvention(convention); + if (conventionedIdentifier.Text != currentIdentifier.Text) + { + context.ReportDiagnostic(Diagnostic.Create(Rule, currentIdentifier.GetLocation(), memberType, currentIdentifier.Text, conventionedIdentifier.Text)); + } + } + } +} \ No newline at end of file diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/NamingConventions/NamingConventionsCodeFix.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/NamingConventions/NamingConventionsCodeFix.cs new file mode 100644 index 0000000..b3a842a --- /dev/null +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/NamingConventions/NamingConventionsCodeFix.cs @@ -0,0 +1,76 @@ +using System.Collections.Immutable; +using System.Composition; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using VSDiagnostics.Utilities; + +namespace VSDiagnostics.Diagnostics.General.NamingConventions +{ + [ExportCodeFixProvider("NamingConventions", LanguageNames.CSharp), Shared] + public class NamingConventionsCodeFix : CodeFixProvider + { + public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(NamingConventionsAnalyzer.DiagnosticId); + public override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; + + public override async Task RegisterCodeFixesAsync(CodeFixContext context) + { + var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); + var diagnostic = context.Diagnostics.First(); + var diagnosticSpan = diagnostic.Location.SourceSpan; + + var identifier = root.FindToken(diagnosticSpan.Start); + context.RegisterCodeFix(CodeAction.Create("Rename", x => RenameAsync(context.Document, root, identifier)), diagnostic); + } + + private Task RenameAsync(Document document, SyntaxNode root, SyntaxToken identifier) + { + var identifierParent = identifier.Parent; + var newIdentifier = default(SyntaxToken); + + do + { + var parentAsField = identifierParent as FieldDeclarationSyntax; + if (parentAsField != null) + { + if (parentAsField.Modifiers.Any(x => new[] { "internal", "protected", "public" }.Contains(x.Text))) + { + newIdentifier = identifier.WithConvention(NamingConvention.UpperCamelCase); + } + else + { + newIdentifier = identifier.WithConvention(NamingConvention.UnderscoreLowerCamelCase); + } + break; + } + + if (identifierParent is PropertyDeclarationSyntax || identifierParent is MethodDeclarationSyntax || identifierParent is ClassDeclarationSyntax) + { + newIdentifier = identifier.WithConvention(NamingConvention.UpperCamelCase); + break; + } + + if (identifierParent is LocalDeclarationStatementSyntax || identifierParent is ParameterSyntax) + { + newIdentifier = identifier.WithConvention(NamingConvention.LowerCamelCase); + break; + } + + if (identifierParent is InterfaceDeclarationSyntax) + { + newIdentifier = identifier.WithConvention(NamingConvention.InterfacePrefixUpperCamelCase); + break; + } + + identifierParent = identifierParent.Parent; + } while (identifierParent != null); + + var newParent = identifierParent.ReplaceToken(identifier, newIdentifier); + var newRoot = root.ReplaceNode(identifierParent, newParent); + return Task.FromResult(document.WithSyntaxRoot(newRoot).Project.Solution); + } + } +} \ No newline at end of file diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Utilities/Extensions.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Utilities/Extensions.cs index 9b07016..c4d3ded 100644 --- a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Utilities/Extensions.cs +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Utilities/Extensions.cs @@ -1,5 +1,7 @@ using System; +using System.Linq; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; namespace VSDiagnostics.Utilities { @@ -24,5 +26,152 @@ public static bool InheritsFrom(this ISymbol typeSymbol, Type type) return false; } + + public static SyntaxToken WithConvention(this SyntaxToken identifier, NamingConvention namingConvention) + { + // int @class = 5; + if (identifier.IsVerbatimIdentifier()) + { + return identifier; + } + + // int cl\u0061ss = 5; + if (identifier.Text.Contains("\\")) + { + return identifier; + } + + var originalValue = identifier.ValueText; + string newValue; + + switch (namingConvention) + { + case NamingConvention.LowerCamelCase: + newValue = GetLowerCamelCaseIdentifier(originalValue); + break; + case NamingConvention.UpperCamelCase: + newValue = GetUpperCamelCaseIdentifier(originalValue); + break; + case NamingConvention.UnderscoreLowerCamelCase: + newValue = GetUnderscoreLowerCamelCaseIdentifier(originalValue); + break; + case NamingConvention.InterfacePrefixUpperCamelCase: + newValue = GetInterfacePrefixUpperCamelCaseIdentifier(originalValue); + break; + default: + throw new ArgumentException(nameof(namingConvention)); + } + + return SyntaxFactory.Identifier(identifier.LeadingTrivia, newValue, identifier.TrailingTrivia); + } + + // lowerCamelCase + private static string GetLowerCamelCaseIdentifier(string identifier) + { + if (ContainsSpecialCharacters(identifier)) + { + return identifier; + } + + var normalizedString = GetNormalizedString(identifier); + + if (normalizedString.Length >= 1) + { + return char.ToLower(normalizedString[0]) + normalizedString.Substring(1); + } + return identifier; + } + + // UpperCamelCase + private static string GetUpperCamelCaseIdentifier(string identifier) + { + if (ContainsSpecialCharacters(identifier)) + { + return identifier; + } + + var normalizedString = GetNormalizedString(identifier); + + if (normalizedString.Length == 0) + { + return identifier; + } + return char.ToUpper(normalizedString[0]) + normalizedString.Substring(1); + } + + // _lowerCamelCase + private static string GetUnderscoreLowerCamelCaseIdentifier(string identifier) + { + if (ContainsSpecialCharacters(identifier, '_')) + { + return identifier; + } + + var normalizedString = GetNormalizedString(identifier); + if (normalizedString.Length == 0) + { + return identifier; + } + + // Var + if (char.IsUpper(normalizedString[0])) + { + return "_" + char.ToLower(normalizedString[0]) + normalizedString.Substring(1); + } + + // var + if (char.IsLower(normalizedString[0])) + { + return "_" + normalizedString; + } + + return normalizedString; + } + + // IInterface + private static string GetInterfacePrefixUpperCamelCaseIdentifier(string identifier) + { + if (ContainsSpecialCharacters(identifier)) + { + return identifier; + } + + var normalizedString = GetNormalizedString(identifier); + + if (normalizedString.Length <= 1) + { + return identifier; + } + + // iSomething + if (normalizedString[0] == 'i' && char.IsUpper(normalizedString[1])) + { + return "I" + normalizedString.Substring(1); + } + + // isomething + if (char.IsLower(normalizedString[0]) && char.IsLower(normalizedString[1])) + { + return "I" + char.ToUpper(normalizedString[0]) + normalizedString.Substring(1); + } + + // Isomething + if (normalizedString[0] == 'I' && char.IsLower(normalizedString[1])) + { + return "I" + char.ToUpper(normalizedString[1]) + normalizedString.Substring(2); + } + + return normalizedString; + } + + private static string GetNormalizedString(string input) + { + return new string(input.ToCharArray().Where(x => char.IsLetter(x) || char.IsNumber(x)).ToArray()); + } + + private static bool ContainsSpecialCharacters(string input, params char[] allowedCharacters) + { + return !input.ToCharArray().All(x => char.IsLetter(x) || char.IsNumber(x) || allowedCharacters.Contains(x)); + } } } \ No newline at end of file diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics/Utilities/NamingConvention.cs b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Utilities/NamingConvention.cs new file mode 100644 index 0000000..2cce77c --- /dev/null +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics/Utilities/NamingConvention.cs @@ -0,0 +1,10 @@ +namespace VSDiagnostics.Utilities +{ + public enum NamingConvention + { + UpperCamelCase, + LowerCamelCase, + UnderscoreLowerCamelCase, + InterfacePrefixUpperCamelCase + } +} \ No newline at end of file diff --git a/VSDiagnostics/VSDiagnostics/VSDiagnostics/VSDiagnostics.csproj b/VSDiagnostics/VSDiagnostics/VSDiagnostics/VSDiagnostics.csproj index ea83c7c..fb1ecd5 100644 --- a/VSDiagnostics/VSDiagnostics/VSDiagnostics/VSDiagnostics.csproj +++ b/VSDiagnostics/VSDiagnostics/VSDiagnostics/VSDiagnostics.csproj @@ -48,6 +48,8 @@ + + @@ -64,6 +66,7 @@ +