Skip to content

Commit

Permalink
Close issue #20 - Find and remove public empty ctor's with no comments.
Browse files Browse the repository at this point in the history
  • Loading branch information
Hosch250 committed Aug 5, 2015
1 parent 1900e88 commit 8fccdcb
Show file tree
Hide file tree
Showing 5 changed files with 242 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using RoslynTester.Helpers.CSharp;
using VSDiagnostics.Diagnostics.General.SingleEmptyConstructor;

namespace VSDiagnostics.Test.Tests.General
{
[TestClass]
public class SingleEmptyConstructorAnalyzerTests : CSharpCodeFixVerifier
{
protected override DiagnosticAnalyzer DiagnosticAnalyzer => new SingleEmptyConstructorAnalyzer();

protected override CodeFixProvider CodeFixProvider => new SingleEmptyConstructorCodeFix();

[TestMethod]
public void SingleEmptyConstructorAnalyzer_WithEmptyConstructor_InvokesWarning ()
{
var original = @"
namespace ConsoleApplication1
{
public class MyClass
{
public MyClass()
{
}
}
}";

var result = @"
namespace ConsoleApplication1
{
public class MyClass
{
}
}";

VerifyDiagnostic (original, SingleEmptyConstructorAnalyzer.Rule.MessageFormat.ToString());
VerifyFix(original, result);
}

[TestMethod]
public void SingleEmptyConstructorAnalyzer_WithCommentInConstructor_DoesNotInvokeWarning ()
{
var original = @"
namespace ConsoleApplication1
{
public class MyClass
{
public MyClass()
{
// ctor has comment
}
}
}";

VerifyDiagnostic(original);
}

[TestMethod]
public void SingleEmptyConstructorAnalyzer_WithConstructorParameters_DoesNotInvokeWarning ()
{
var original = @"
namespace ConsoleApplication1
{
public class MyClass
{
public MyClass(int foo)
{
}
}
}";

VerifyDiagnostic(original);
}

[TestMethod]
public void SingleEmptyConstructorAnalyzer_WithConstructorBody_DoesNotInvokeWarning ()
{
var original = @"
namespace ConsoleApplication1
{
public class MyClass
{
public int Foo { get; }
public MyClass()
{
Foo = 0;
}
}
}";

VerifyDiagnostic(original);
}

[TestMethod]
public void SingleEmptyConstructorAnalyzer_WithImplicitPrivateConstructor_DoesNotInvokeWarning ()
{
var original = @"
namespace ConsoleApplication1
{
public class MyClass
{
public int Foo { get; }
MyClass()
{
Foo = 0;
}
}
}";

VerifyDiagnostic(original);
}

[TestMethod]
public void SingleEmptyConstructorAnalyzer_WithExplicitInternalConstructor_DoesNotInvokeWarning ()
{
var original = @"
namespace ConsoleApplication1
{
public class MyClass
{
public int Foo { get; }
internal MyClass()
{
Foo = 0;
}
}
}";

VerifyDiagnostic(original);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@
<Compile Include="Tests\General\NullableToShorthandAnalyzerTests.cs" />
<Compile Include="Tests\General\OnPropertyChangedWithoutNameOfOperatorAnalyzerTests.cs" />
<Compile Include="Tests\General\SimplifyExpressionBodiedMemberAnalyzerTests.cs" />
<Compile Include="Tests\General\SingleEmptyConstructorAnalyzerTests.cs" />
<Compile Include="Tests\General\TryCastUsingAsNotNullInsteadOfIsAsAnalyzerTests.cs" />
<Compile Include="Tests\General\TypeToVarAnalyzerTests.cs" />
<Compile Include="Tests\Strings\ReplaceEmptyStringWithStringDotEmptyTests.cs" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
using System.Collections.Immutable;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;

namespace VSDiagnostics.Diagnostics.General.SingleEmptyConstructor
{
[DiagnosticAnalyzer(LanguageNames.CSharp)]
class SingleEmptyConstructorAnalyzer : DiagnosticAnalyzer
{
private const string Category = "General";
private const string DiagnosticId = nameof(SingleEmptyConstructorAnalyzer);
private const string Message = "Use default constructor.";
private const DiagnosticSeverity Severity = DiagnosticSeverity.Warning;
private const string Title = "Your constructor is the same as a default constructor and can be removed.";

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.ConstructorDeclaration);
}

private void AnalyzeSymbol (SyntaxNodeAnalysisContext context)
{
var ctorExpression = context.Node as ConstructorDeclarationSyntax;
if (ctorExpression == null)
{
return;
}

// ctor must be public
if (ctorExpression.Modifiers.ToImmutableList().All(m => m.Text != "public"))
{
return;
}

// ctor must not have parameters
if (ctorExpression.ParameterList.Parameters.Any())
{
return;
}

// ctor must have no body statements
if (ctorExpression.Body.Statements.Any())
{
return;
}

// ctor must not have comment - ignore empty brace pairs
var text = ctorExpression.Body.GetText().ToString().ToCharArray();
if (text.Any(ch => !char.IsWhiteSpace(ch) && ch != '{' && ch != '}'))
{
return;
}

context.ReportDiagnostic(Diagnostic.Create(Rule, ctorExpression.GetLocation()));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeFixes;
using System.Composition;
using System.Collections.Immutable;
using System.Threading.Tasks;
using System.Linq;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CSharp.Syntax;

namespace VSDiagnostics.Diagnostics.General.SingleEmptyConstructor
{
[ExportCodeFixProvider("SingleEmptyConstructorCodeFix", LanguageNames.CSharp), Shared]
class SingleEmptyConstructorCodeFix : CodeFixProvider
{
public override ImmutableArray<string> FixableDiagnosticIds => ImmutableArray.Create(SingleEmptyConstructorAnalyzer.Rule.Id);

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 statement = root.FindNode(diagnosticSpan);
context.RegisterCodeFix(CodeAction.Create("Simplify expression", x => RemoveConstructorAsync(context.Document, root, statement), nameof(SingleEmptyConstructorAnalyzer)), diagnostic);
}

private Task<Solution> RemoveConstructorAsync(Document document, SyntaxNode root, SyntaxNode statement)
{
var ctorDeclarationExpression = (ConstructorDeclarationSyntax) statement;
var newRoot = root.RemoveNode(ctorDeclarationExpression, SyntaxRemoveOptions.KeepNoTrivia);

var newDocument = document.WithSyntaxRoot(newRoot);
return Task.FromResult(newDocument.Project.Solution);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@
<Compile Include="Diagnostics\General\OnPropertyChangedWithoutNameOfOperator\OnPropertyChangedWithoutNameOfOperatorCodeFix.cs" />
<Compile Include="Diagnostics\General\SimplifyExpressionBodiedMember\SimplifyExpressionBodiedMemberAnalyzer.cs" />
<Compile Include="Diagnostics\General\SimplifyExpressionBodiedMember\SimplifyExpressionBodiedMemberCodeFix.cs" />
<Compile Include="Diagnostics\General\SingleEmptyConstructor\SingleEmptyConstructorAnalyzer.cs" />
<Compile Include="Diagnostics\General\SingleEmptyConstructor\SingleEmptyConstructorCodeFix.cs" />
<Compile Include="Diagnostics\General\TryCastWithoutUsingAsNotNull\TryCastWithoutUsingAsNotNullAnalyzer.cs" />
<Compile Include="Diagnostics\General\TryCastWithoutUsingAsNotNull\TryCastWithoutUsingAsNotNullCodeFix.cs" />
<Compile Include="Diagnostics\General\TypeToVar\TypeToVarAnalyzer.cs" />
Expand Down

0 comments on commit 8fccdcb

Please sign in to comment.