Skip to content

Commit

Permalink
Merge pull request #80 from Vannevelj/issue25
Browse files Browse the repository at this point in the history
Implemented #25
  • Loading branch information
Vannevelj committed Jun 3, 2015
2 parents 95032f4 + 1b4a796 commit 3d07f46
Show file tree
Hide file tree
Showing 8 changed files with 1,512 additions and 5 deletions.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@
<Compile Include="Tests\General\CompareBooleanToTrueLiteralAnalyzerTests.cs" />
<Compile Include="Tests\General\ConditionalOperatorReturnsDefaultOptionsAnalyzerTests.cs" />
<Compile Include="Tests\General\IfStatementWithoutBracesAnalyzerTests.cs" />
<Compile Include="Tests\General\NamingConventionsAnalyzerTests.cs" />
<Compile Include="Tests\General\NullableToShorthandAnalyzerTests.cs" />
<Compile Include="Tests\General\OnPropertyChangedWithoutNameOfOperatorAnalyzerTests.cs" />
<Compile Include="Tests\General\SimplifyExpressionBodiedMemberAnalyzerTests.cs" />
Expand Down
16 changes: 11 additions & 5 deletions VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/packages.config
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>

<packages>
<package id="Microsoft.CodeAnalysis.Analyzers" version="1.0.0-rc3-20150529-06" targetFramework="net45" userInstalled="true" />
<package id="Microsoft.CodeAnalysis.Common" version="1.0.0-rc3-20150529-06" targetFramework="net45" userInstalled="true" />
<package id="Microsoft.CodeAnalysis.CSharp" version="1.0.0-rc3-20150529-06" targetFramework="net45" userInstalled="true" />
<package id="Microsoft.CodeAnalysis.CSharp.Workspaces" version="1.0.0-rc3-20150529-06" targetFramework="net45" userInstalled="true" />
<package id="Microsoft.CodeAnalysis.Workspaces.Common" version="1.0.0-rc3-20150529-06" targetFramework="net45" userInstalled="true" />
<package id="Microsoft.CodeAnalysis.Analyzers" version="1.0.0-rc3-20150529-06" targetFramework="net45"
userInstalled="true" />
<package id="Microsoft.CodeAnalysis.Common" version="1.0.0-rc3-20150529-06" targetFramework="net45"
userInstalled="true" />
<package id="Microsoft.CodeAnalysis.CSharp" version="1.0.0-rc3-20150529-06" targetFramework="net45"
userInstalled="true" />
<package id="Microsoft.CodeAnalysis.CSharp.Workspaces" version="1.0.0-rc3-20150529-06" targetFramework="net45"
userInstalled="true" />
<package id="Microsoft.CodeAnalysis.Workspaces.Common" version="1.0.0-rc3-20150529-06" targetFramework="net45"
userInstalled="true" />
<package id="Microsoft.Composition" version="1.0.30" targetFramework="net45" userInstalled="true" />
<package id="NUnit" version="3.0.0-beta-2" targetFramework="net45" userInstalled="true" />
<package id="RoslynTester" version="1.1.0-rc2" targetFramework="net45" userInstalled="true" />
Expand Down
Original file line number Diff line number Diff line change
@@ -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<DiagnosticDescriptor> 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));
}
}
}
}
Original file line number Diff line number Diff line change
@@ -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<string> 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<Solution> 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);
}
}
}
149 changes: 149 additions & 0 deletions VSDiagnostics/VSDiagnostics/VSDiagnostics/Utilities/Extensions.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using System;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;

namespace VSDiagnostics.Utilities
{
Expand All @@ -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));
}
}
}
Loading

0 comments on commit 3d07f46

Please sign in to comment.