Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/CSharp/CodeCracker/CodeCracker.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -102,13 +102,15 @@
<Compile Include="Style\StringFormatAnalyzer.cs" />
<Compile Include="Usage\DisposableFieldNotDisposedAnalyzer.cs" />
<Compile Include="Usage\DisposableVariableNotDisposedCodeFixProvider.cs" />
<Compile Include="Usage\RedundantFieldAssignmentCodeFixProvider.cs" />
<Compile Include="Usage\RemoveUnreachableCodeFixAllProvider.cs" />
<Compile Include="Usage\DisposableVariableNotDisposedFixAllProvider.cs" />
<Compile Include="Usage\RemovePrivateMethodNeverUsedAnalyzer.cs" />
<Compile Include="Usage\RemoveUnreachableCodeCodeFixProvider.cs" />
<Compile Include="Usage\RemovePrivateMethodNeverUsedCodeFixProvider.cs" />
<Compile Include="Usage\NoPrivateReadonlyFieldAnalyzer.cs" />
<Compile Include="Usage\RemoveUnusedVariablesCodeFixProvider.cs" />
<Compile Include="Usage\RedundantFieldAssignmentAnalyzer.cs" />
<Compile Include="Usage\StringFormatArgsAnalyzer.cs" />
<Compile Include="Usage\UnusedParametersCodeFixProvider.cs" />
<Compile Include="Usage\UnusedParametersAnalyzer.cs" />
Expand Down
143 changes: 143 additions & 0 deletions src/CSharp/CodeCracker/Usage/RedundantFieldAssignmentAnalyzer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using System;
using System.Collections.Immutable;
using System.Linq;

namespace CodeCracker.CSharp.Usage
{
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class RedundantFieldAssignmentAnalyzer : DiagnosticAnalyzer
{
internal const string Title = "Redundant field assignment";
internal const string MessageFormat = "Field {0} is assigning to default value {1}. Remove the assignment.";
internal const string Category = SupportedCategories.Usage;
const string Description = "It's recommend not to assign the default value to a field as a performance optimization.";

internal static DiagnosticDescriptor Rule = new DiagnosticDescriptor(
DiagnosticId.RedundantFieldAssignment.ToDiagnosticId(),
Title,
MessageFormat,
Category,
DiagnosticSeverity.Info,
isEnabledByDefault: true,
customTags: WellKnownDiagnosticTags.Unnecessary,
description: Description,
helpLinkUri: HelpLink.ForDiagnostic(DiagnosticId.RedundantFieldAssignment));

public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Rule);

public override void Initialize(AnalysisContext context) => context.RegisterSyntaxNodeAction(AnalyzeFieldDeclaration, SyntaxKind.FieldDeclaration);

private void AnalyzeFieldDeclaration(SyntaxNodeAnalysisContext context)
{
if (context.IsGenerated()) return;
var fieldDeclaration = context.Node as FieldDeclarationSyntax;
var variable = fieldDeclaration?.Declaration.Variables.LastOrDefault();
if (variable?.Initializer == null) return;
if (fieldDeclaration.Modifiers.Any(SyntaxKind.ConstKeyword)) return;
var initializerValue = variable.Initializer.Value;
if (initializerValue.IsKind(SyntaxKind.DefaultExpression))
{
ReportDiagnostic(context, variable, initializerValue);
return;
}
var semanticModel = context.SemanticModel;
var fieldSymbol = semanticModel.GetDeclaredSymbol(variable) as IFieldSymbol;
if (fieldSymbol == null) return;
if (!IsAssigningToDefault(fieldSymbol.Type, initializerValue, semanticModel)) return;
ReportDiagnostic(context, variable, initializerValue);
}

private static void ReportDiagnostic(SyntaxNodeAnalysisContext context, VariableDeclaratorSyntax variable, ExpressionSyntax initializerValue)
{
var diag = Diagnostic.Create(Rule, variable.GetLocation(), variable.Identifier.ValueText, initializerValue.ToString());
context.ReportDiagnostic(diag);
}

private static bool IsAssigningToDefault(ITypeSymbol fieldType, ExpressionSyntax initializerValue, SemanticModel semanticModel)
{
if (fieldType.IsReferenceType)
{
if (!initializerValue.IsKind(SyntaxKind.NullLiteralExpression)) return false;
}
else
{
if (!IsValueTypeAssigningToDefault(fieldType, initializerValue, semanticModel)) return false;
}
return true;
}

private static bool IsValueTypeAssigningToDefault(ITypeSymbol fieldType, ExpressionSyntax initializerValue, SemanticModel semanticModel)
{
switch (fieldType.SpecialType)
{
case SpecialType.System_Boolean:
{
var literal = initializerValue as LiteralExpressionSyntax;
if (literal == null) return false;
var boolValue = (bool)literal.Token.Value;
if (boolValue) return false;
break;
}
case SpecialType.System_SByte:
case SpecialType.System_Byte:
case SpecialType.System_Int16:
case SpecialType.System_UInt16:
case SpecialType.System_Int32:
case SpecialType.System_UInt32:
case SpecialType.System_Int64:
case SpecialType.System_UInt64:
case SpecialType.System_Decimal:
case SpecialType.System_Single:
case SpecialType.System_Double:
if (initializerValue.ToString() != "0")
{
var literal = initializerValue as LiteralExpressionSyntax;
if (literal == null) return false;
var possibleZero = Convert.ToDouble(literal.Token.Value);
if (possibleZero != 0) return false;
}
break;
case SpecialType.System_IntPtr:
{
var memberAccess = initializerValue as MemberAccessExpressionSyntax;
if (memberAccess == null) return false;
var memberAccessFieldSymbol = semanticModel.GetSymbolInfo(memberAccess).Symbol as IFieldSymbol;
if (memberAccessFieldSymbol?.ToString() != "System.IntPtr.Zero") return false;
break;
}
case SpecialType.System_UIntPtr:
{
var memberAccess = initializerValue as MemberAccessExpressionSyntax;
if (memberAccess == null) return false;
var memberAccessFieldSymbol = semanticModel.GetSymbolInfo(memberAccess).Symbol as IFieldSymbol;
if (memberAccessFieldSymbol?.ToString() != "System.UIntPtr.Zero") return false;
break;
}
case SpecialType.System_DateTime:
{
var memberAccess = initializerValue as MemberAccessExpressionSyntax;
if (memberAccess == null) return false;
var memberAccessFieldSymbol = semanticModel.GetSymbolInfo(memberAccess).Symbol as IFieldSymbol;
if (memberAccessFieldSymbol?.ToString() != "System.DateTime.MinValue") return false;
break;
}
//case SpecialType.System_Enum: //does not work, enums come back as none. Bug on roslyn? See solution below.
default:
if (fieldType.TypeKind != TypeKind.Enum) return false;
if (initializerValue.ToString() != "0")
{
var literal = initializerValue as LiteralExpressionSyntax;
if (literal == null) return false;
var possibleZero = Convert.ToDouble(literal.Token.Value);
if (possibleZero != 0) return false;
}
break;
}
return true;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Formatting;
using System.Collections.Immutable;
using System.Composition;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace CodeCracker.CSharp.Usage
{
[ExportCodeFixProvider("CodeCrackerRedundantFieldAssignmentCodeFixProvider", LanguageNames.CSharp), Shared]
public class RedundantFieldAssignmentCodeFixProvider : CodeFixProvider
{
public sealed override ImmutableArray<string> FixableDiagnosticIds =>
ImmutableArray.Create(DiagnosticId.RedundantFieldAssignment.ToDiagnosticId());

public sealed override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer;

public sealed override Task RegisterCodeFixesAsync(CodeFixContext context)
{
var diagnostic = context.Diagnostics.First();
context.RegisterCodeFix(CodeAction.Create("Remove assignment", c => RemoveAssignmentAsync(context.Document, diagnostic, c)), diagnostic);
return Task.FromResult(0);
}

private async Task<Document> RemoveAssignmentAsync(Document document, Diagnostic diagnostic, CancellationToken cancellationToken)
{
var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
var variable = root.FindNode(diagnostic.Location.SourceSpan) as VariableDeclaratorSyntax;
var newVariable = variable.WithInitializer(null).WithAdditionalAnnotations(Formatter.Annotation);
var newRoot = root.ReplaceNode(variable, newVariable);
var newDocument = document.WithSyntaxRoot(newRoot);
return newDocument;
}
}
}
1 change: 1 addition & 0 deletions src/Common/CodeCracker.Common/DiagnosticId.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ public enum DiagnosticId
DisposableFieldNotDisposed_Returned = 32,
DisposableFieldNotDisposed_Created = 33,
AllowMembersOrdering = 35,
RedundantFieldAssignment = 34,
RemoveCommentedCode = 37,
ConvertToExpressionBodiedMember = 38,
StringBuilderInLoop = 39,
Expand Down
1 change: 1 addition & 0 deletions test/CSharp/CodeCracker.Test/CodeCracker.Test.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@
<Compile Include="Performance\MakeLocalVariablesConstWhenItIsPossibleTests.cs" />
<Compile Include="Design\NameOfTests.cs" />
<Compile Include="Refactoring\InvertForTests.cs" />
<Compile Include="Usage\RedundantFieldAssignmentTests.cs" />
<Compile Include="Usage\RemoveUnreachableCodeTest.cs" />
<Compile Include="Usage\RemovePrivateMethodNeverUsedAnalyzerTest.cs" />
<Compile Include="Usage\NoPrivateReadonlyFieldTest.cs" />
Expand Down
Loading