From e73ef1e07c2cbc8328720eb57cf4eeea1c9137af Mon Sep 17 00:00:00 2001 From: Giovanni Bassi Date: Thu, 7 May 2015 21:56:52 +0300 Subject: [PATCH] add analyzer/fix for redudant field initializers closes #9 --- src/CSharp/CodeCracker/CodeCracker.csproj | 2 + .../Usage/RedundantFieldAssignmentAnalyzer.cs | 143 ++++++ ...RedundantFieldAssignmentCodeFixProvider.cs | 39 ++ src/Common/CodeCracker.Common/DiagnosticId.cs | 1 + .../CodeCracker.Test/CodeCracker.Test.csproj | 1 + .../Usage/RedundantFieldAssignmentTests.cs | 472 ++++++++++++++++++ 6 files changed, 658 insertions(+) create mode 100644 src/CSharp/CodeCracker/Usage/RedundantFieldAssignmentAnalyzer.cs create mode 100644 src/CSharp/CodeCracker/Usage/RedundantFieldAssignmentCodeFixProvider.cs create mode 100644 test/CSharp/CodeCracker.Test/Usage/RedundantFieldAssignmentTests.cs diff --git a/src/CSharp/CodeCracker/CodeCracker.csproj b/src/CSharp/CodeCracker/CodeCracker.csproj index c939d3115..a40a4316c 100644 --- a/src/CSharp/CodeCracker/CodeCracker.csproj +++ b/src/CSharp/CodeCracker/CodeCracker.csproj @@ -102,6 +102,7 @@ + @@ -109,6 +110,7 @@ + diff --git a/src/CSharp/CodeCracker/Usage/RedundantFieldAssignmentAnalyzer.cs b/src/CSharp/CodeCracker/Usage/RedundantFieldAssignmentAnalyzer.cs new file mode 100644 index 000000000..5c4f59c29 --- /dev/null +++ b/src/CSharp/CodeCracker/Usage/RedundantFieldAssignmentAnalyzer.cs @@ -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 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; + } + } +} \ No newline at end of file diff --git a/src/CSharp/CodeCracker/Usage/RedundantFieldAssignmentCodeFixProvider.cs b/src/CSharp/CodeCracker/Usage/RedundantFieldAssignmentCodeFixProvider.cs new file mode 100644 index 000000000..edb59d7f8 --- /dev/null +++ b/src/CSharp/CodeCracker/Usage/RedundantFieldAssignmentCodeFixProvider.cs @@ -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 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 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; + } + } +} \ No newline at end of file diff --git a/src/Common/CodeCracker.Common/DiagnosticId.cs b/src/Common/CodeCracker.Common/DiagnosticId.cs index 694078298..0d75a5c97 100644 --- a/src/Common/CodeCracker.Common/DiagnosticId.cs +++ b/src/Common/CodeCracker.Common/DiagnosticId.cs @@ -34,6 +34,7 @@ public enum DiagnosticId DisposableFieldNotDisposed_Returned = 32, DisposableFieldNotDisposed_Created = 33, AllowMembersOrdering = 35, + RedundantFieldAssignment = 34, RemoveCommentedCode = 37, ConvertToExpressionBodiedMember = 38, StringBuilderInLoop = 39, diff --git a/test/CSharp/CodeCracker.Test/CodeCracker.Test.csproj b/test/CSharp/CodeCracker.Test/CodeCracker.Test.csproj index 6cb66a58a..3ea9cab90 100644 --- a/test/CSharp/CodeCracker.Test/CodeCracker.Test.csproj +++ b/test/CSharp/CodeCracker.Test/CodeCracker.Test.csproj @@ -169,6 +169,7 @@ + diff --git a/test/CSharp/CodeCracker.Test/Usage/RedundantFieldAssignmentTests.cs b/test/CSharp/CodeCracker.Test/Usage/RedundantFieldAssignmentTests.cs new file mode 100644 index 000000000..97caea241 --- /dev/null +++ b/test/CSharp/CodeCracker.Test/Usage/RedundantFieldAssignmentTests.cs @@ -0,0 +1,472 @@ +using CodeCracker.CSharp.Usage; +using Microsoft.CodeAnalysis; +using System.Threading.Tasks; +using Xunit; + +namespace CodeCracker.Test.CSharp.Usage +{ + public class RedundantFieldAssignmentTests : CodeFixVerifier + { + [Fact] + public async Task FieldWithoutAssignmentDoesNotCreateDiagnostic() + { + const string source = @" +class TypeName +{ + private int i; +}"; + await VerifyCSharpHasNoDiagnosticsAsync(source); + } + + [Fact] + public async Task IntFieldWithAssignmentToOneIntDoesNotCreateDiagnostic() + { + const string source = @" +class TypeName +{ + private int i = 1; +}"; + await VerifyCSharpHasNoDiagnosticsAsync(source); + } + + [Fact] + public async Task IntFieldWithAssignmentToOneDecimalDoesNotCreateDiagnostic() + { + const string source = @" +class TypeName +{ + private decimal i = 1.1m; +}"; + await VerifyCSharpHasNoDiagnosticsAsync(source); + } + + [Fact] + public async Task IntFieldWithAssignmentToNearZeroDecimalDoesNotCreateDiagnostic() + { + const string source = @" +class TypeName +{ + private decimal i = 0.1m; +}"; + await VerifyCSharpHasNoDiagnosticsAsync(source); + } + + [Fact] + public async Task IntFieldWithAssignmentToDoubleMaxLiteralDoesNotCreateDiagnostic() + { + const string source = @" +class TypeName +{ + private double i = 1.7976931348623157E+308; +}"; + await VerifyCSharpHasNoDiagnosticsAsync(source); + } + + [Fact] + public async Task IntFieldWithAssignmentToIntMaxLiteralDoesNotCreateDiagnostic() + { + const string source = @" +class TypeName +{ + private int i = 2147483647; +}"; + await VerifyCSharpHasNoDiagnosticsAsync(source); + } + + [Fact] + public async Task IntFieldWithAssignmentToUintMaxLiteralDoesNotCreateDiagnostic() + { + const string source = @" +class TypeName +{ + private uint i = 4294967295; +}"; + await VerifyCSharpHasNoDiagnosticsAsync(source); + } + + [Fact] + public async Task StringFieldWithAssignmentToAStringDoesNotCreatesDiagnostic() + { + const string source = @" +class TypeName +{ + private string s = ""a""; +}"; + await VerifyCSharpHasNoDiagnosticsAsync(source); + } + + [Fact] + public async Task ConstStringFieldWithAssignmentToNullDoesNotCreatesDiagnostic() + { + const string source = @" +class TypeName +{ + private const string s = null; +}"; + await VerifyCSharpHasNoDiagnosticsAsync(source); + } + + [Fact] + public async Task IntPtrWithAssignmentToNewSystemIntPtrDoesNotCreateDiagnostic() + { + const string source = @" +class TypeName +{ + private System.IntPtr i = new System.IntPtr(1); +}"; + await VerifyCSharpHasNoDiagnosticsAsync(source); + } + + [Fact] + public async Task UIntPtrWithAssignmentToNewSystemUIntPtrDoesNotCreateDiagnostic() + { + const string source = @" +class TypeName +{ + private System.UIntPtr i = new System.UIntPtr(1); +}"; + await VerifyCSharpHasNoDiagnosticsAsync(source); + } + + [Fact] + public async Task DateTimeWithAssignmentToNewDateTimeDoesNotCreateDiagnostic() + { + const string source = @" +class TypeName +{ + private System.DateTime d = new System.DateTime(1); +}"; + await VerifyCSharpHasNoDiagnosticsAsync(source); + } + + [Fact] + public async Task EnumWithAssignmentToSomeEnumValueDoesNotCreateDiagnostic() + { + const string source = @" +enum E { A = 1, B = 2 } +class TypeName +{ + private E e = E.A; +}"; + await VerifyCSharpHasNoDiagnosticsAsync(source); + } + + [Fact] + public async Task BoolWithAssignmentToTrueDoesNotCreateDiagnostic() + { + const string source = @" +class TypeName +{ + private bool b = true; +}"; + await VerifyCSharpHasNoDiagnosticsAsync(source); + } + + [Fact] + public async Task IntFieldWithAssignmentToZeroOnDeclarationCreatesDiagnostic() + { + const string source = @" +class TypeName +{ + private int i = 0; +}"; + var expected = new DiagnosticResult + { + Id = DiagnosticId.RedundantFieldAssignment.ToDiagnosticId(), + Message = string.Format(RedundantFieldAssignmentAnalyzer.MessageFormat, "i", 0), + Severity = DiagnosticSeverity.Info, + Locations = new[] { new DiagnosticResultLocation("Test0.cs", 4, 17) } + }; + await VerifyCSharpDiagnosticAsync(source, expected); + } + + [Fact] + public async Task IntFieldWithAssignmentToDefaultCreatesDiagnostic() + { + const string source = @" +class TypeName +{ + private int i = default(int); +}"; + var expected = new DiagnosticResult + { + Id = DiagnosticId.RedundantFieldAssignment.ToDiagnosticId(), + Message = string.Format(RedundantFieldAssignmentAnalyzer.MessageFormat, "i", "default(int)"), + Severity = DiagnosticSeverity.Info, + Locations = new[] { new DiagnosticResultLocation("Test0.cs", 4, 17) } + }; + await VerifyCSharpDiagnosticAsync(source, expected); + } + + [Fact] + public async Task StringFieldWithAssignmentToNullCreatesDiagnostic() + { + const string source = @" +class TypeName +{ + private string s = null; +}"; + var expected = new DiagnosticResult + { + Id = DiagnosticId.RedundantFieldAssignment.ToDiagnosticId(), + Message = string.Format(RedundantFieldAssignmentAnalyzer.MessageFormat, "s", "null"), + Severity = DiagnosticSeverity.Info, + Locations = new[] { new DiagnosticResultLocation("Test0.cs", 4, 20) } + }; + await VerifyCSharpDiagnosticAsync(source, expected); + } + + [Fact] + public async Task LongFieldWithAssignmentTo0LCreatesDiagnostic() + { + const string source = @" +class TypeName +{ + private long i = 0L; +}"; + var expected = new DiagnosticResult + { + Id = DiagnosticId.RedundantFieldAssignment.ToDiagnosticId(), + Message = string.Format(RedundantFieldAssignmentAnalyzer.MessageFormat, "i", "0L"), + Severity = DiagnosticSeverity.Info, + Locations = new[] { new DiagnosticResultLocation("Test0.cs", 4, 18) } + }; + await VerifyCSharpDiagnosticAsync(source, expected); + } + + [Fact] + public async Task LongFieldWithAssignmentToZeroCreatesDiagnostic() + { + const string source = @" +class TypeName +{ + private long i = 0; +}"; + var expected = new DiagnosticResult + { + Id = DiagnosticId.RedundantFieldAssignment.ToDiagnosticId(), + Message = string.Format(RedundantFieldAssignmentAnalyzer.MessageFormat, "i", "0"), + Severity = DiagnosticSeverity.Info, + Locations = new[] { new DiagnosticResultLocation("Test0.cs", 4, 18) } + }; + await VerifyCSharpDiagnosticAsync(source, expected); + } + + [Fact] + public async Task IntPtrWithAssignmentToSystemIntPtrZeroCreatesDiagnostic() + { + const string source = @" +class TypeName +{ + private System.IntPtr i = System.IntPtr.Zero; +}"; + var expected = new DiagnosticResult + { + Id = DiagnosticId.RedundantFieldAssignment.ToDiagnosticId(), + Message = string.Format(RedundantFieldAssignmentAnalyzer.MessageFormat, "i", "System.IntPtr.Zero"), + Severity = DiagnosticSeverity.Info, + Locations = new[] { new DiagnosticResultLocation("Test0.cs", 4, 27) } + }; + await VerifyCSharpDiagnosticAsync(source, expected); + } + + [Fact] + public async Task IntPtrWithAssignmentToIntPtrZeroCreatesDiagnostic() + { + const string source = @" +using System; +class TypeName +{ + private IntPtr i = IntPtr.Zero; +}"; + var expected = new DiagnosticResult + { + Id = DiagnosticId.RedundantFieldAssignment.ToDiagnosticId(), + Message = string.Format(RedundantFieldAssignmentAnalyzer.MessageFormat, "i", "IntPtr.Zero"), + Severity = DiagnosticSeverity.Info, + Locations = new[] { new DiagnosticResultLocation("Test0.cs", 5, 20) } + }; + await VerifyCSharpDiagnosticAsync(source, expected); + } + + [Fact] + public async Task UIntPtrWithAssignmentToSystemUIntPtrZeroCreatesDiagnostic() + { + const string source = @" +class TypeName +{ + private System.UIntPtr i = System.UIntPtr.Zero; +}"; + var expected = new DiagnosticResult + { + Id = DiagnosticId.RedundantFieldAssignment.ToDiagnosticId(), + Message = string.Format(RedundantFieldAssignmentAnalyzer.MessageFormat, "i", "System.UIntPtr.Zero"), + Severity = DiagnosticSeverity.Info, + Locations = new[] { new DiagnosticResultLocation("Test0.cs", 4, 28) } + }; + await VerifyCSharpDiagnosticAsync(source, expected); + } + + [Fact] + public async Task DateTimeWithAssignmentToDateTimeMinValueCreatesDiagnostic() + { + const string source = @" +class TypeName +{ + private System.DateTime d = System.DateTime.MinValue; +}"; + var expected = new DiagnosticResult + { + Id = DiagnosticId.RedundantFieldAssignment.ToDiagnosticId(), + Message = string.Format(RedundantFieldAssignmentAnalyzer.MessageFormat, "d", "System.DateTime.MinValue"), + Severity = DiagnosticSeverity.Info, + Locations = new[] { new DiagnosticResultLocation("Test0.cs", 4, 29) } + }; + await VerifyCSharpDiagnosticAsync(source, expected); + } + + [Fact] + public async Task EnumWithAssignmentToZeroCreatesDiagnostic() + { + const string source = @" +enum E { A = 1, B = 2 } +class TypeName +{ + private E e = 0; +}"; + var expected = new DiagnosticResult + { + Id = DiagnosticId.RedundantFieldAssignment.ToDiagnosticId(), + Message = string.Format(RedundantFieldAssignmentAnalyzer.MessageFormat, "e", "0"), + Severity = DiagnosticSeverity.Info, + Locations = new[] { new DiagnosticResultLocation("Test0.cs", 5, 15) } + }; + await VerifyCSharpDiagnosticAsync(source, expected); + } + + [Fact] + public async Task EnumWithAssignmentToZeroDoubleCreatesDiagnostic() + { + const string source = @" +enum E { A = 1, B = 2 } +class TypeName +{ + private E e = 0.0; +}"; + var expected = new DiagnosticResult + { + Id = DiagnosticId.RedundantFieldAssignment.ToDiagnosticId(), + Message = string.Format(RedundantFieldAssignmentAnalyzer.MessageFormat, "e", "0.0"), + Severity = DiagnosticSeverity.Info, + Locations = new[] { new DiagnosticResultLocation("Test0.cs", 5, 15) } + }; + await VerifyCSharpDiagnosticAsync(source, expected); + } + + [Fact] + public async Task BoolWithAssignmentToFalseCreatesDiagnostic() + { + const string source = @" +class TypeName +{ + private bool b = false; +}"; + var expected = new DiagnosticResult + { + Id = DiagnosticId.RedundantFieldAssignment.ToDiagnosticId(), + Message = string.Format(RedundantFieldAssignmentAnalyzer.MessageFormat, "b", "false"), + Severity = DiagnosticSeverity.Info, + Locations = new[] { new DiagnosticResultLocation("Test0.cs", 4, 18) } + }; + await VerifyCSharpDiagnosticAsync(source, expected); + } + + [Fact] + public async Task IntFieldWithAssignmentToZeroWithMultipleVariableDeclarationsOnTheSameFieldOnDeclarationCreatesDiagnostic() + { + const string source = @" +class TypeName +{ + private int i, j, k = 0; +}"; + var expected = new DiagnosticResult + { + Id = DiagnosticId.RedundantFieldAssignment.ToDiagnosticId(), + Message = string.Format(RedundantFieldAssignmentAnalyzer.MessageFormat, "k", 0), + Severity = DiagnosticSeverity.Info, + Locations = new[] { new DiagnosticResultLocation("Test0.cs", 4, 23) } + }; + await VerifyCSharpDiagnosticAsync(source, expected); + } + + [Fact] + public async Task RemoveInitializerFromSimpleIntField() + { + const string source = @" +class TypeName +{ + //comment 1 + private int i = 0;//comment 2 +}"; + const string fixtest = @" +class TypeName +{ + //comment 1 + private int i;//comment 2 +}"; + await VerifyCSharpFixAsync(source, fixtest); + } + + [Fact] + public async Task RemoveInitializerFromMultipleVariables() + { + const string source = @" +class TypeName +{ + //comment 1 + private int i, j, k = 0;//comment 2 +}"; + const string fixtest = @" +class TypeName +{ + //comment 1 + private int i, j, k;//comment 2 +}"; + await VerifyCSharpFixAsync(source, fixtest); + } + + [Fact] + public async Task FixAllRemoveInitializerFromTwoSimpleIntFields() + { + const string source = @" +class TypeName +{ + //comment 1 + private int i = 0;//comment 2 + private int j = 0;//comment 3 + class TypeName2 + { + //comment 4 + private int k = 0;//comment 5 + private int l = 0;//comment 6 + } + private int m = 0;//comment 7 +}"; + const string fixtest = @" +class TypeName +{ + //comment 1 + private int i;//comment 2 + private int j;//comment 3 + class TypeName2 + { + //comment 4 + private int k;//comment 5 + private int l;//comment 6 + } + private int m;//comment 7 +}"; + await VerifyCSharpFixAllAsync(source, fixtest); + } + } +} \ No newline at end of file