diff --git a/ChangeLog.md b/ChangeLog.md index f81d50c04c..1bd8336bba 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -10,19 +10,28 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Add SECURITY.md ([#1147](https://github.com/josefpihrt/roslynator/pull/1147)) -- Add custom FixAllProvider for [RCS1014](https://github.com/JosefPihrt/Roslynator/blob/main/docs/analyzers/RCS1014.md) ([#1070](https://github.com/JosefPihrt/Roslynator/pull/1070)). -- Add more cases to [RCS1097](https://github.com/JosefPihrt/Roslynator/blob/main/docs/analyzers/RCS1097.md) ([#1160](https://github.com/JosefPihrt/Roslynator/pull/1160)). +- Add custom FixAllProvider for [RCS1014](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1014.md) ([#1070](https://github.com/JosefPihrt/Roslynator/pull/1070)). +- Add more cases to [RCS1097](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1097.md) ([#1160](https://github.com/JosefPihrt/Roslynator/pull/1160)). - Add analyzer "Use enum field explicitly" ([RCS1257](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1257)) ([#889](https://github.com/josefpihrt/roslynator/pull/889)). - Enabled by default. -- Add analyzer "Unnecessary enum flag" [RCS1258](https://github.com/JosefPihrt/Roslynator/blob/main/docs/analyzers/RCS1258.md) ([#886](https://github.com/JosefPihrt/Roslynator/pull/886)). +- Add analyzer "Unnecessary enum flag" [RCS1258](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1258.md) ([#886](https://github.com/JosefPihrt/Roslynator/pull/886)). - Enabled by default. - Make `Roslynator.Rename.SymbolRenamer` public ([#1161](https://github.com/josefpihrt/roslynator/pull/1161)) +- Analyzer 'Remove empty syntax' ([RCS1259](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1259.md)) ([#913](https://github.com/josefpihrt/roslynator/pull/913)). + - This analyzer replaces following analyzers: + - Remove empty empty statement ([RCS1038](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1038.md)) + - Remove empty 'else' clause ([RCS1040](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1040.md)) + - Remove empty object initializer ([RCS1041](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1041.md)) + - Remove empty 'finally' clause ([RCS1066](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1066.md)) + - Remove empty namespace declaration ([RCS1072](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1072.md)) + - Remove empty region directive ([RCS1091](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1091.md)) + - Remove empty destructor ([RCS1106](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1106.md)) ### Fixed -- Fix [RCS1187](https://github.com/JosefPihrt/Roslynator/blob/main/docs/analyzers/RCS1187.md) ([#1150](https://github.com/JosefPihrt/Roslynator/pull/1150)). -- Fix [RCS1056](https://github.com/JosefPihrt/Roslynator/blob/main/docs/analyzers/RCS1056.md) ([#1154](https://github.com/JosefPihrt/Roslynator/pull/1154)). -- Fix [RCS1208](https://github.com/JosefPihrt/Roslynator/blob/main/docs/analyzers/RCS1208.md) ([#1153](https://github.com/JosefPihrt/Roslynator/pull/1153)) +- Fix [RCS1187](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1187.md) ([#1150](https://github.com/JosefPihrt/Roslynator/pull/1150)). +- Fix [RCS1056](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1056.md) ([#1154](https://github.com/JosefPihrt/Roslynator/pull/1154)). +- Fix [RCS1208](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1208.md) ([#1153](https://github.com/JosefPihrt/Roslynator/pull/1153)) ## [4.4.0] - 2023-08-01 @@ -51,13 +60,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fix [RCS1154](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1154) ([#1105](https://github.com/JosefPihrt/Roslynator/pull/1105)). - Fix [RCS1211](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1211) ([#1095](https://github.com/JosefPihrt/Roslynator/pull/1095)). - Fix [RCS0005](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS0005) ([#1114](https://github.com/JosefPihrt/Roslynator/pull/1114)). -- Fix [RCS1176](https://github.com/JosefPihrt/Roslynator/blob/main/docs/analyzers/RCS1176.md) ([#1122](https://github.com/JosefPihrt/Roslynator/pull/1122), [#1140](https://github.com/JosefPihrt/Roslynator/pull/1140)). -- Fix [RCS1085](https://github.com/JosefPihrt/Roslynator/blob/main/docs/analyzers/RCS1085.md) ([#1120](https://github.com/josefpihrt/roslynator/pull/1120)). -- Fix [RCS1208](https://github.com/JosefPihrt/Roslynator/blob/main/docs/analyzers/RCS1208.md) ([#1119](https://github.com/JosefPihrt/Roslynator/pull/1119)). +- Fix [RCS1176](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1176.md) ([#1122](https://github.com/JosefPihrt/Roslynator/pull/1122), [#1140](https://github.com/JosefPihrt/Roslynator/pull/1140)). +- Fix [RCS1085](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1085.md) ([#1120](https://github.com/josefpihrt/roslynator/pull/1120)). +- Fix [RCS1208](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1208.md) ([#1119](https://github.com/JosefPihrt/Roslynator/pull/1119)). - [CLI] Fix member full declaration in generated documentation (command `generate-doc`) ([#1130](https://github.com/josefpihrt/roslynator/pull/1130)). - Append `?` to nullable reference types. -- Fix [RCS1179](https://github.com/JosefPihrt/Roslynator/blob/main/docs/analyzers/RCS1179.md) ([#1129](https://github.com/JosefPihrt/Roslynator/pull/1129)). -- Fix [RCS0060](https://github.com/JosefPihrt/Roslynator/blob/main/docs/analyzers/RCS0060.md) ([#1139](https://github.com/JosefPihrt/Roslynator/pull/1139)). +- Fix [RCS1179](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1179.md) ([#1129](https://github.com/JosefPihrt/Roslynator/pull/1129)). +- Fix [RCS0060](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS0060.md) ([#1139](https://github.com/JosefPihrt/Roslynator/pull/1139)). ## [4.3.0] - 2023-04-24 diff --git a/src/Analyzers.CodeFixes/CSharp/CodeFixes/ElseClauseCodeFixProvider.cs b/src/Analyzers.CodeFixes/CSharp/CodeFixes/ElseClauseCodeFixProvider.cs index ee758ba8b2..ff1fd349d7 100644 --- a/src/Analyzers.CodeFixes/CSharp/CodeFixes/ElseClauseCodeFixProvider.cs +++ b/src/Analyzers.CodeFixes/CSharp/CodeFixes/ElseClauseCodeFixProvider.cs @@ -62,7 +62,7 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context) } } - private static async Task RemoveEmptyElseClauseAsync( + internal static async Task RemoveEmptyElseClauseAsync( Document document, ElseClauseSyntax elseClause, CancellationToken cancellationToken) diff --git a/src/Analyzers.CodeFixes/CSharp/CodeFixes/FinallyClauseCodeFixProvider.cs b/src/Analyzers.CodeFixes/CSharp/CodeFixes/FinallyClauseCodeFixProvider.cs index 8aa176ceb0..1859f99b28 100644 --- a/src/Analyzers.CodeFixes/CSharp/CodeFixes/FinallyClauseCodeFixProvider.cs +++ b/src/Analyzers.CodeFixes/CSharp/CodeFixes/FinallyClauseCodeFixProvider.cs @@ -38,7 +38,7 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context) context.RegisterCodeFix(codeAction, context.Diagnostics[0]); } - private static async Task RemoveEmptyFinallyClauseAsync( + internal static async Task RemoveEmptyFinallyClauseAsync( Document document, FinallyClauseSyntax finallyClause, CancellationToken cancellationToken) diff --git a/src/Analyzers.CodeFixes/CSharp/CodeFixes/RemoveEmptySyntaxCodeFixProvider.cs b/src/Analyzers.CodeFixes/CSharp/CodeFixes/RemoveEmptySyntaxCodeFixProvider.cs new file mode 100644 index 0000000000..d9b3ee604b --- /dev/null +++ b/src/Analyzers.CodeFixes/CSharp/CodeFixes/RemoveEmptySyntaxCodeFixProvider.cs @@ -0,0 +1,135 @@ +// Copyright (c) Josef Pihrt and Contributors. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Composition; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Roslynator.CodeFixes; +using Roslynator.CSharp.Refactorings; + +namespace Roslynator.CSharp.CodeFixes; + +[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(RemoveEmptySyntaxCodeFixProvider))] +[Shared] +public sealed class RemoveEmptySyntaxCodeFixProvider : BaseCodeFixProvider +{ + public override ImmutableArray FixableDiagnosticIds + { + get { return ImmutableArray.Create(DiagnosticIdentifiers.RemoveEmptySyntax); } + } + + public override async Task RegisterCodeFixesAsync(CodeFixContext context) + { + SyntaxNode root = await context.GetSyntaxRootAsync().ConfigureAwait(false); + + if (!TryFindFirstAncestorOrSelf( + root, + context.Span, + out SyntaxNode node, + findInsideTrivia: true, + predicate: f => + { + switch (f.Kind()) + { + case SyntaxKind.DestructorDeclaration: + case SyntaxKind.ElseClause: + case SyntaxKind.EmptyStatement: + case SyntaxKind.FinallyClause: + case SyntaxKind.NamespaceDeclaration: + case SyntaxKind.ObjectCreationExpression: + case SyntaxKind.RegionDirectiveTrivia: + return true; + default: + return false; + } + })) + { + return; + } + + Document document = context.Document; + Diagnostic diagnostic = context.Diagnostics[0]; + + switch (node) + { + case DestructorDeclarationSyntax destructorDeclaration: + { + CodeAction codeAction = CodeActionFactory.RemoveMemberDeclaration( + document, + destructorDeclaration, + title: "Remove empty destructor", + equivalenceKey: GetEquivalenceKey(diagnostic)); + + context.RegisterCodeFix(codeAction, diagnostic); + break; + } + case ElseClauseSyntax elseClause: + { + CodeAction codeAction = CodeAction.Create( + "Remove empty 'else' clause", + ct => ElseClauseCodeFixProvider.RemoveEmptyElseClauseAsync(document, elseClause, ct), + GetEquivalenceKey(diagnostic)); + + context.RegisterCodeFix(codeAction, diagnostic); + break; + } + case FinallyClauseSyntax finallyClause: + { + CodeAction codeAction = CodeAction.Create( + "Remove empty 'finally' clause", + ct => FinallyClauseCodeFixProvider.RemoveEmptyFinallyClauseAsync(document, finallyClause, ct), + equivalenceKey: GetEquivalenceKey(diagnostic)); + + context.RegisterCodeFix(codeAction, diagnostic); + break; + } + case ObjectCreationExpressionSyntax objectCreationExpression: + { + CodeAction codeAction = CodeAction.Create( + "Remove empty initializer", + ct => RemoveEmptyInitializerRefactoring.RefactorAsync(document, objectCreationExpression, ct), + GetEquivalenceKey(diagnostic)); + + context.RegisterCodeFix(codeAction, diagnostic); + break; + } + case NamespaceDeclarationSyntax namespaceDeclaration: + { + CodeAction codeAction = CodeAction.Create( + "Remove empty namespace declaration", + ct => RemoveEmptyNamespaceDeclarationRefactoring.RefactorAsync(document, namespaceDeclaration, ct), + GetEquivalenceKey(diagnostic)); + + context.RegisterCodeFix(codeAction, diagnostic); + break; + } + case RegionDirectiveTriviaSyntax regionDirective: + { + CodeAction codeAction = CodeAction.Create( + "Remove empty region", + ct => RemoveEmptyRegionRefactoring.RefactorAsync(document, SyntaxInfo.RegionInfo(regionDirective), ct), + GetEquivalenceKey(diagnostic)); + + context.RegisterCodeFix(codeAction, diagnostic); + break; + } + case EmptyStatementSyntax emptyStatement: + { + CodeAction codeAction = CodeAction.Create( + "Remove empty statement", + ct => RemoveEmptyStatementRefactoring.RefactorAsync(document, emptyStatement, ct), + GetEquivalenceKey(diagnostic)); + + context.RegisterCodeFix(codeAction, diagnostic); + break; + } + } + } +} diff --git a/src/Analyzers.xml b/src/Analyzers.xml index 99eaf79f86..136339d3b9 100644 --- a/src/Analyzers.xml +++ b/src/Analyzers.xml @@ -2435,10 +2435,13 @@ if (f) RCS1038 RemoveEmptyStatement + Obsolete + Use RCS1259 instead Remove empty statement. Info - true + false true + HideFromConfiguration RCS1040 RemoveEmptyElseClause + Obsolete + Use RCS1259 instead Remove empty 'else' clause. Hidden - true + false true + HideFromConfiguration RCS1041 RemoveEmptyInitializer + Obsolete + Use RCS1259 instead Remove empty initializer. Info - true + false true + HideFromConfiguration () { };]]> @@ -3093,11 +3102,14 @@ while (true);]]> RCS1066 RemoveEmptyFinallyClause + Obsolete + Use RCS1259 instead Remove empty 'finally' clause. Hidden - true + false true true + HideFromConfiguration RCS1072 RemoveEmptyNamespaceDeclaration + Obsolete + Use RCS1259 instead Remove empty namespace declaration. Info - true + false true + HideFromConfiguration RCS1091 RemoveEmptyRegion + Obsolete + Use RCS1259 instead Remove empty region. Hidden - true + false true + HideFromConfiguration RCS1106 RemoveEmptyDestructor + Obsolete + Use RCS1259 instead Remove empty destructor. Info - true + false true + HideFromConfiguration + + RCS1259 + Remove empty syntax. + Remove empty {0}. + Info + true + true + This analyzer reports unnecessary syntax that can be safely removed such as: + * empty destructor + * empty 'else' clause + * empty empty statement + * empty 'finally' clause + * empty namespace declaration + * empty object initializer + * empty region directive + + RCS9001 UsePatternMatching diff --git a/src/Analyzers/CSharp/Analysis/RemoveEmptySyntaxAnalyzer.cs b/src/Analyzers/CSharp/Analysis/RemoveEmptySyntaxAnalyzer.cs new file mode 100644 index 0000000000..03d5557cbc --- /dev/null +++ b/src/Analyzers/CSharp/Analysis/RemoveEmptySyntaxAnalyzer.cs @@ -0,0 +1,211 @@ +// Copyright (c) Josef Pihrt and Contributors. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Immutable; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; +using Roslynator.CSharp.Syntax; + +namespace Roslynator.CSharp.Analysis; + +[DiagnosticAnalyzer(LanguageNames.CSharp)] +public sealed class RemoveEmptySyntaxAnalyzer : BaseDiagnosticAnalyzer +{ + private static ImmutableArray _supportedDiagnostics; + + public override ImmutableArray SupportedDiagnostics + { + get + { + if (_supportedDiagnostics.IsDefault) + Immutable.InterlockedInitialize(ref _supportedDiagnostics, DiagnosticRules.RemoveEmptySyntax); + + return _supportedDiagnostics; + } + } + + public override void Initialize(AnalysisContext context) + { + base.Initialize(context); + + context.RegisterSyntaxNodeAction(f => AnalyzeDestructorDeclaration(f), SyntaxKind.DestructorDeclaration); + context.RegisterSyntaxNodeAction(f => AnalyzeElseClause(f), SyntaxKind.ElseClause); + context.RegisterSyntaxNodeAction(f => AnalyzeFinallyClause(f), SyntaxKind.FinallyClause); + context.RegisterSyntaxNodeAction(f => AnalyzeObjectCreationExpression(f), SyntaxKind.ObjectCreationExpression); + context.RegisterSyntaxNodeAction(f => AnalyzeNamespaceDeclaration(f), SyntaxKind.NamespaceDeclaration); + context.RegisterSyntaxNodeAction(f => AnalyzeRegionDirective(f), SyntaxKind.RegionDirectiveTrivia); + context.RegisterSyntaxNodeAction(f => AnalyzeEmptyStatement(f), SyntaxKind.EmptyStatement); + } + + private static void AnalyzeDestructorDeclaration(SyntaxNodeAnalysisContext context) + { + var destructor = (DestructorDeclarationSyntax)context.Node; + + if (!destructor.ContainsDiagnostics + && !destructor.ContainsUnbalancedIfElseDirectives() + && !destructor.AttributeLists.Any() + && destructor.Body?.Statements.Count == 0) + { + ReportDiagnostic(context, destructor, "destructor"); + } + } + + private static void AnalyzeElseClause(SyntaxNodeAnalysisContext context) + { + var elseClause = (ElseClauseSyntax)context.Node; + + StatementSyntax statement = elseClause.Statement; + + if (statement is not BlockSyntax block) + return; + + if (block.Statements.Any()) + return; + + IfStatementSyntax topmostIf = elseClause.GetTopmostIf(); + + if (topmostIf.Parent is IfStatementSyntax parentIf + && parentIf.Else != null) + { + return; + } + + if (!elseClause.ElseKeyword.TrailingTrivia.IsEmptyOrWhitespace()) + return; + + if (!SyntaxTriviaAnalysis.IsExteriorTriviaEmptyOrWhitespace(block.OpenBraceToken)) + return; + + if (!block.CloseBraceToken.LeadingTrivia.IsEmptyOrWhitespace()) + return; + + ReportDiagnostic(context, elseClause, "'else' clause"); + } + + private static void AnalyzeFinallyClause(SyntaxNodeAnalysisContext context) + { + var finallyClause = (FinallyClauseSyntax)context.Node; + + if (finallyClause.Parent is not TryStatementSyntax tryStatement) + return; + + BlockSyntax finallyBlock = finallyClause.Block; + + if (finallyBlock?.Statements.Any() != false) + return; + + if (!finallyClause.FinallyKeyword.TrailingTrivia.IsEmptyOrWhitespace()) + return; + + if (!SyntaxTriviaAnalysis.IsExteriorTriviaEmptyOrWhitespace(finallyBlock.OpenBraceToken)) + return; + + if (!finallyBlock.CloseBraceToken.LeadingTrivia.IsEmptyOrWhitespace()) + return; + + if (tryStatement.Catches.Any()) + { + ReportDiagnostic(context, finallyClause, "'finally' clause"); + } + else + { + BlockSyntax tryBlock = tryStatement.Block; + + if (tryBlock?.Statements.Any() != true) + return; + + if (!SyntaxTriviaAnalysis.IsExteriorTriviaEmptyOrWhitespace(tryStatement.TryKeyword)) + return; + + if (!SyntaxTriviaAnalysis.IsExteriorTriviaEmptyOrWhitespace(tryBlock.OpenBraceToken)) + return; + + if (!SyntaxTriviaAnalysis.IsExteriorTriviaEmptyOrWhitespace(tryBlock.CloseBraceToken)) + return; + + if (!finallyClause.FinallyKeyword.LeadingTrivia.IsEmptyOrWhitespace()) + return; + + ReportDiagnostic(context, finallyClause, "'finally' clause"); + } + } + + private static void AnalyzeObjectCreationExpression(SyntaxNodeAnalysisContext context) + { + var objectCreationExpression = (ObjectCreationExpressionSyntax)context.Node; + + if (objectCreationExpression.ContainsDiagnostics) + return; + + InitializerExpressionSyntax initializer = objectCreationExpression.Initializer; + + if (initializer?.Expressions.Any() != false) + return; + + if (!initializer.OpenBraceToken.TrailingTrivia.IsEmptyOrWhitespace()) + return; + + if (!initializer.CloseBraceToken.LeadingTrivia.IsEmptyOrWhitespace()) + return; + + if (initializer.IsInExpressionTree(context.SemanticModel, context.CancellationToken)) + return; + + ReportDiagnostic(context, initializer, "initializer"); + } + + private static void AnalyzeNamespaceDeclaration(SyntaxNodeAnalysisContext context) + { + var declaration = (NamespaceDeclarationSyntax)context.Node; + + if (declaration.Members.Any()) + return; + + SyntaxToken openBrace = declaration.OpenBraceToken; + SyntaxToken closeBrace = declaration.CloseBraceToken; + + if (openBrace.IsMissing) + return; + + if (closeBrace.IsMissing) + return; + + if (!openBrace.TrailingTrivia.IsEmptyOrWhitespace()) + return; + + if (!closeBrace.LeadingTrivia.IsEmptyOrWhitespace()) + return; + + ReportDiagnostic(context, declaration, "namespace declaration"); + } + + private static void AnalyzeRegionDirective(SyntaxNodeAnalysisContext context) + { + var regionDirective = (RegionDirectiveTriviaSyntax)context.Node; + + RegionInfo region = SyntaxInfo.RegionInfo(regionDirective); + + if (region.Success + && region.IsEmpty) + { + ReportDiagnostic(context, regionDirective, "#region"); + } + } + + private static void AnalyzeEmptyStatement(SyntaxNodeAnalysisContext context) + { + SyntaxNode emptyStatement = context.Node; + + if (emptyStatement.Parent?.IsKind(SyntaxKind.LabeledStatement) == false + && !CSharpFacts.CanHaveEmbeddedStatement(emptyStatement.Parent.Kind())) + { + ReportDiagnostic(context, emptyStatement, "statement"); + } + } + + private static void ReportDiagnostic(SyntaxNodeAnalysisContext context, SyntaxNode node, string name) + { + DiagnosticHelpers.ReportDiagnostic(context, DiagnosticRules.RemoveEmptySyntax, node, name); + } +} diff --git a/src/Analyzers/CSharp/DiagnosticIdentifiers.Generated.cs b/src/Analyzers/CSharp/DiagnosticIdentifiers.Generated.cs index a98c7280ed..9576d27404 100644 --- a/src/Analyzers/CSharp/DiagnosticIdentifiers.Generated.cs +++ b/src/Analyzers/CSharp/DiagnosticIdentifiers.Generated.cs @@ -215,5 +215,6 @@ public static partial class DiagnosticIdentifiers public const string InvalidArgumentNullCheck = "RCS1256"; public const string UseEnumFieldExplicitly = "RCS1257"; public const string UnnecessaryEnumFlag = "RCS1258"; + public const string RemoveEmptySyntax = "RCS1259"; } } \ No newline at end of file diff --git a/src/Analyzers/CSharp/DiagnosticRules.Generated.cs b/src/Analyzers/CSharp/DiagnosticRules.Generated.cs index fa2337e1f9..516fe48767 100644 --- a/src/Analyzers/CSharp/DiagnosticRules.Generated.cs +++ b/src/Analyzers/CSharp/DiagnosticRules.Generated.cs @@ -341,10 +341,10 @@ public static partial class DiagnosticRules public static readonly DiagnosticDescriptor RemoveEmptyStatement = DiagnosticDescriptorFactory.Create( id: DiagnosticIdentifiers.RemoveEmptyStatement, title: "Remove empty statement.", - messageFormat: "Remove empty statement.", + messageFormat: "([deprecated] Use RCS1259 instead) Remove empty statement.", category: DiagnosticCategories.Roslynator, defaultSeverity: DiagnosticSeverity.Info, - isEnabledByDefault: true, + isEnabledByDefault: false, description: null, helpLinkUri: DiagnosticIdentifiers.RemoveEmptyStatement, customTags: WellKnownDiagnosticTags.Unnecessary); @@ -365,10 +365,10 @@ public static partial class DiagnosticRules public static readonly DiagnosticDescriptor RemoveEmptyElseClause = DiagnosticDescriptorFactory.Create( id: DiagnosticIdentifiers.RemoveEmptyElseClause, title: "Remove empty 'else' clause.", - messageFormat: "Remove empty 'else' clause.", + messageFormat: "([deprecated] Use RCS1259 instead) Remove empty 'else' clause.", category: DiagnosticCategories.Roslynator, defaultSeverity: DiagnosticSeverity.Hidden, - isEnabledByDefault: true, + isEnabledByDefault: false, description: null, helpLinkUri: DiagnosticIdentifiers.RemoveEmptyElseClause, customTags: WellKnownDiagnosticTags.Unnecessary); @@ -377,10 +377,10 @@ public static partial class DiagnosticRules public static readonly DiagnosticDescriptor RemoveEmptyInitializer = DiagnosticDescriptorFactory.Create( id: DiagnosticIdentifiers.RemoveEmptyInitializer, title: "Remove empty initializer.", - messageFormat: "Remove empty initializer.", + messageFormat: "([deprecated] Use RCS1259 instead) Remove empty initializer.", category: DiagnosticCategories.Roslynator, defaultSeverity: DiagnosticSeverity.Info, - isEnabledByDefault: true, + isEnabledByDefault: false, description: null, helpLinkUri: DiagnosticIdentifiers.RemoveEmptyInitializer, customTags: WellKnownDiagnosticTags.Unnecessary); @@ -627,10 +627,10 @@ public static partial class DiagnosticRules public static readonly DiagnosticDescriptor RemoveEmptyFinallyClause = DiagnosticDescriptorFactory.Create( id: DiagnosticIdentifiers.RemoveEmptyFinallyClause, title: "Remove empty 'finally' clause.", - messageFormat: "Remove empty 'finally' clause.", + messageFormat: "([deprecated] Use RCS1259 instead) Remove empty 'finally' clause.", category: DiagnosticCategories.Roslynator, defaultSeverity: DiagnosticSeverity.Hidden, - isEnabledByDefault: true, + isEnabledByDefault: false, description: null, helpLinkUri: DiagnosticIdentifiers.RemoveEmptyFinallyClause, customTags: WellKnownDiagnosticTags.Unnecessary); @@ -689,10 +689,10 @@ public static partial class DiagnosticRules public static readonly DiagnosticDescriptor RemoveEmptyNamespaceDeclaration = DiagnosticDescriptorFactory.Create( id: DiagnosticIdentifiers.RemoveEmptyNamespaceDeclaration, title: "Remove empty namespace declaration.", - messageFormat: "Remove empty namespace declaration.", + messageFormat: "([deprecated] Use RCS1259 instead) Remove empty namespace declaration.", category: DiagnosticCategories.Roslynator, defaultSeverity: DiagnosticSeverity.Info, - isEnabledByDefault: true, + isEnabledByDefault: false, description: null, helpLinkUri: DiagnosticIdentifiers.RemoveEmptyNamespaceDeclaration, customTags: WellKnownDiagnosticTags.Unnecessary); @@ -851,10 +851,10 @@ public static partial class DiagnosticRules public static readonly DiagnosticDescriptor RemoveEmptyRegion = DiagnosticDescriptorFactory.Create( id: DiagnosticIdentifiers.RemoveEmptyRegion, title: "Remove empty region.", - messageFormat: "Remove empty region.", + messageFormat: "([deprecated] Use RCS1259 instead) Remove empty region.", category: DiagnosticCategories.Roslynator, defaultSeverity: DiagnosticSeverity.Hidden, - isEnabledByDefault: true, + isEnabledByDefault: false, description: null, helpLinkUri: DiagnosticIdentifiers.RemoveEmptyRegion, customTags: Array.Empty()); @@ -1009,10 +1009,10 @@ public static partial class DiagnosticRules public static readonly DiagnosticDescriptor RemoveEmptyDestructor = DiagnosticDescriptorFactory.Create( id: DiagnosticIdentifiers.RemoveEmptyDestructor, title: "Remove empty destructor.", - messageFormat: "Remove empty destructor.", + messageFormat: "([deprecated] Use RCS1259 instead) Remove empty destructor.", category: DiagnosticCategories.Roslynator, defaultSeverity: DiagnosticSeverity.Info, - isEnabledByDefault: true, + isEnabledByDefault: false, description: null, helpLinkUri: DiagnosticIdentifiers.RemoveEmptyDestructor, customTags: WellKnownDiagnosticTags.Unnecessary); @@ -2545,5 +2545,17 @@ public static partial class DiagnosticRules helpLinkUri: DiagnosticIdentifiers.UnnecessaryEnumFlag, customTags: WellKnownDiagnosticTags.Unnecessary); + /// RCS1259 + public static readonly DiagnosticDescriptor RemoveEmptySyntax = DiagnosticDescriptorFactory.Create( + id: DiagnosticIdentifiers.RemoveEmptySyntax, + title: "Remove empty syntax.", + messageFormat: "Remove empty {0}.", + category: DiagnosticCategories.Roslynator, + defaultSeverity: DiagnosticSeverity.Info, + isEnabledByDefault: true, + description: null, + helpLinkUri: DiagnosticIdentifiers.RemoveEmptySyntax, + customTags: WellKnownDiagnosticTags.Unnecessary); + } } \ No newline at end of file diff --git a/src/Tests.Old/Analyzers.Tests.Old/RemoveEmptyDestructor.cs b/src/Tests.Old/Analyzers.Tests.Old/RemoveEmptyDestructor.cs deleted file mode 100644 index 959cdeb4c7..0000000000 --- a/src/Tests.Old/Analyzers.Tests.Old/RemoveEmptyDestructor.cs +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (c) Josef Pihrt and Contributors. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -namespace Roslynator.CSharp.Analyzers.Tests -{ - public static class RemoveEmptyDestructor - { - public class Foo - { - ~Foo() - { - } - } - - public class Foo2 - { - ~Foo2() - { -#if DEBUG - } -#endif - } - - public class Foo3 - { - ~Foo3() - { - object x = null; - } - } - } -} diff --git a/src/Tests.Old/Analyzers.Tests.Old/RemoveEmptyElseClause.cs b/src/Tests.Old/Analyzers.Tests.Old/RemoveEmptyElseClause.cs deleted file mode 100644 index ec5c8600e5..0000000000 --- a/src/Tests.Old/Analyzers.Tests.Old/RemoveEmptyElseClause.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) Josef Pihrt and Contributors. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -namespace Roslynator.CSharp.Analyzers.Tests -{ - internal static class RemoveEmptyElseClause - { - private static void Foo() - { - bool condition = false; - - if (condition) - { - Foo(); - } - else - { - } - } - } -} diff --git a/src/Tests.Old/Analyzers.Tests.Old/RemoveEmptyInitializer.cs b/src/Tests.Old/Analyzers.Tests.Old/RemoveEmptyInitializer.cs deleted file mode 100644 index 0427ee4d78..0000000000 --- a/src/Tests.Old/Analyzers.Tests.Old/RemoveEmptyInitializer.cs +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright (c) Josef Pihrt and Contributors. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Text; - -#pragma warning disable RCS1050, RCS1067 - -namespace Roslynator.CSharp.Analyzers.Tests -{ - internal static class RemoveEmptyInitializer - { - private static void Foo(StringBuilder x) - { - x = new StringBuilder() { }; - - x = new StringBuilder { }; - - x = new StringBuilder() - { - }; - - x = new StringBuilder - { - }; - - x = new StringBuilder() /**/ { }; //x - - x = new StringBuilder /**/ { }; //x - - x = new StringBuilder() //x - { - }; //x - - x = new StringBuilder //x - { - }; //x - } - } -} diff --git a/src/Tests.Old/Analyzers.Tests.Old/RemoveEmptyNamespaceDeclaration.cs b/src/Tests.Old/Analyzers.Tests.Old/RemoveEmptyNamespaceDeclaration.cs deleted file mode 100644 index f6e63d1bc8..0000000000 --- a/src/Tests.Old/Analyzers.Tests.Old/RemoveEmptyNamespaceDeclaration.cs +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright (c) Josef Pihrt and Contributors. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -namespace Roslynator.CSharp.Analyzers.Tests -{ - namespace RemoveEmptyNamespaceDeclaration - { - } -} diff --git a/src/Tests.Old/Analyzers.Tests.Old/RemoveEmptyRegion.cs b/src/Tests.Old/Analyzers.Tests.Old/RemoveEmptyRegion.cs deleted file mode 100644 index 5155ce7640..0000000000 --- a/src/Tests.Old/Analyzers.Tests.Old/RemoveEmptyRegion.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) Josef Pihrt and Contributors. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -namespace Roslynator.CSharp.Analyzers.Tests -{ - internal static class RemoveEmptyRegion - { - #region Methods - public static void Foo() - #endregion - { - #region Empty - - #endregion - } - #region Empty - - - #endregion - } -} diff --git a/src/Tests.Old/Analyzers.Tests.Old/RemoveRedundantEmptyStatement.cs b/src/Tests.Old/Analyzers.Tests.Old/RemoveRedundantEmptyStatement.cs deleted file mode 100644 index aeb312d632..0000000000 --- a/src/Tests.Old/Analyzers.Tests.Old/RemoveRedundantEmptyStatement.cs +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) Josef Pihrt and Contributors. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -namespace Roslynator.CSharp.Analyzers.Tests -{ - internal static class RemoveRedundantEmptyStatement - { - private static void Foo() - { - ; - } - } -} diff --git a/src/Tests/Analyzers.Tests/RCS1259RemoveEmptySyntaxTests.cs b/src/Tests/Analyzers.Tests/RCS1259RemoveEmptySyntaxTests.cs new file mode 100644 index 0000000000..8c3e3ebef5 --- /dev/null +++ b/src/Tests/Analyzers.Tests/RCS1259RemoveEmptySyntaxTests.cs @@ -0,0 +1,495 @@ +// Copyright (c) Josef Pihrt and Contributors. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Roslynator.CSharp.CodeFixes; +using Roslynator.Testing.CSharp; +using Xunit; + +namespace Roslynator.CSharp.Analysis.Tests; + +public class RCS1259RemoveEmptySyntaxTests : AbstractCSharpDiagnosticVerifier +{ + public override DiagnosticDescriptor Descriptor { get; } = DiagnosticRules.RemoveEmptySyntax; + + [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.RemoveEmptySyntax)] + public async Task Test_Destructor() + { + await VerifyDiagnosticAndFixAsync(@" +class C +{ + void M() + { + } + + [|~C() + { +#if DEBUG +#endif + }|] +} +", @" +class C +{ + void M() + { + } +} +"); + } + + [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.RemoveEmptySyntax)] + public async Task TestNoDiagnostic_Destructor_IfDirective() + { + await VerifyNoDiagnosticAsync(@" +#define A +class C +{ + ~C() + { +#if A + } +#endif +} +"); + } + + [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.RemoveEmptySyntax)] + public async Task TestNoDiagnostic_Destructor_NotEmpty() + { + await VerifyNoDiagnosticAsync(@" +class C +{ + void M() + { + } + + ~C() + { + M(); + } +} +"); + } + + [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.RemoveEmptySyntax)] + public async Task Test_ElseClause() + { + await VerifyDiagnosticAndFixAsync(@" +class C +{ + void M() + { + bool f = false; + + if (f) + { + } + [|else + { + }|] + } +} +", @" +class C +{ + void M() + { + bool f = false; + + if (f) + { + } + } +} +"); + } + + [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.RemoveEmptySyntax)] + public async Task TestNoDiagnostic_ElseClause_ElseIf() + { + await VerifyNoDiagnosticAsync(@" +class C +{ + void M() + { + bool f = false; + + if (f) + { + } + else if (f) + { + } +} +} +"); + } + + [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.RemoveEmptySyntax)] + public async Task TestNoDiagnostic_ElseClause_NonEmptyElse() + { + await VerifyNoDiagnosticAsync(@" +class C +{ + void M() + { + bool f = false; + + if (f) + { + } + else + { + M(); + } +} +} +"); + } + + [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.RemoveEmptySyntax)] + public async Task TestNoDiagnostic_ElseClause_IfElseEmbeddedInIfWithElse() + { + await VerifyNoDiagnosticAsync(@" +class C +{ + void M() + { + bool f = false; + + if (f) + if (f) M(); else { } + else + { + M(); + } + } +} +"); + } + + [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.RemoveEmptySyntax)] + public async Task TestNoDiagnostic_ElseClause_IfElseEmbeddedInIfWithElse2() + { + await VerifyNoDiagnosticAsync(@" +class C +{ + void M() + { + bool f = false; + + if (f) + if (f) M(); else if (f) M(); else { } + else + { + M(); + } + } +} +"); + } + + [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.RemoveEmptySyntax)] + public async Task Test_FinallyClause_TryCatchFinally() + { + await VerifyDiagnosticAndFixAsync(@" +class C +{ + void M() + { + try + { + } + catch + { + } + [|finally + { + }|] + } +} +", @" +class C +{ + void M() + { + try + { + } + catch + { + } + } +} +"); + } + + [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.RemoveEmptySyntax)] + public async Task Test_FinallyClause_TryFinally() + { + await VerifyDiagnosticAndFixAsync(@" +class C +{ + void M() + { + try + { + //x + M(); + M2(); + } + [|finally + { + }|] + } + + string M2() => null; +} +", @" +class C +{ + void M() + { + //x + M(); + M2(); + } + + string M2() => null; +} +"); + } + + [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.RemoveEmptySyntax)] + public async Task TestNoDiagnostic_FinallyClause_NonEmptyFinally() + { + await VerifyNoDiagnosticAsync(@" +class C +{ + void M() + { + try + { + } + catch + { + } + finally + { + string foo = null; + } + } +} +"); + } + + [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.RemoveEmptySyntax)] + public async Task Test_ObjectInitializer() + { + await VerifyDiagnosticAndFixAsync(@" +class C +{ + void M() + { + var x = new C [|{ }|]; + } +} +", @" +class C +{ + void M() + { + var x = new C(); + } +} +"); + } + + [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.RemoveEmptySyntax)] + public async Task Test_ObjectInitializer2() + { + await VerifyDiagnosticAndFixAsync(@" +using System.Text; + +class C +{ + void M() + { + var x = new StringBuilder(); + + x = new StringBuilder() [|{ }|]; + + x = new StringBuilder [|{ }|]; + + x = new StringBuilder() + [|{ + }|]; + + x = new StringBuilder + [|{ + }|]; + + x = new StringBuilder() /**/ [|{ }|]; //x + + x = new StringBuilder /**/ [|{ }|]; //x + + x = new StringBuilder() //x + [|{ + }|]; //x + + x = new StringBuilder //x + [|{ + }|]; //x + } +} +", @" +using System.Text; + +class C +{ + void M() + { + var x = new StringBuilder(); + + x = new StringBuilder(); + + x = new StringBuilder(); + + x = new StringBuilder(); + + x = new StringBuilder(); + + x = new StringBuilder() /**/ ; //x + + x = new StringBuilder() /**/ ; //x + + x = new StringBuilder() //x + + ; //x + + x = new StringBuilder() //x + + ; //x + } +} +"); + } + + [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.RemoveEmptySyntax)] + public async Task TestNoDiagnostic_ObjectInitializer_ExpressionTree() + { + await VerifyNoDiagnosticAsync(@" +using System; +using System.Linq.Expressions; + +class C +{ + public void M(Expression> e) + { + M(() => new C { }); + } +} +"); + } + + [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.RemoveEmptySyntax)] + public async Task Test_Namespace() + { + await VerifyDiagnosticAndFixAsync(@" +using System; +using System.Linq.Expressions; + +namespace N1 +{ + class C + { + } +} + +[|namespace N2 +{ +}|] +", @" +using System; +using System.Linq.Expressions; + +namespace N1 +{ + class C + { + } +} +"); + } + + [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.RemoveEmptySyntax)] + public async Task Test_RegionDirective() + { + await VerifyDiagnosticAndFixAsync(@" +namespace N1 +{ + class C + { +[|#region|] +#endregion + } +} +", @" +namespace N1 +{ + class C + { + } +} +"); + } + + [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.RemoveEmptySyntax)] + public async Task Test_EmptyStatement() + { + await VerifyDiagnosticAndFixAsync(@" +namespace N1 +{ + class C + { + void M() + { + M(); + [|;|] + } + } +} +", @" +namespace N1 +{ + class C + { + void M() + { + M(); + + } + } +} +"); + } + + [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.RemoveEmptySyntax)] + public async Task TestNoDiagnostic_EmptyStatement() + { + await VerifyNoDiagnosticAsync(@" +namespace N1 +{ + class C + { + void M(bool p) + { + if (p) + ; + } + } +} +", options: Options.AddAllowedCompilerDiagnosticId("CS0642")); + } +} diff --git a/src/VisualStudioCode/package/src/configurationFiles.generated.ts b/src/VisualStudioCode/package/src/configurationFiles.generated.ts index e8978a1f93..91d3b9d873 100644 --- a/src/VisualStudioCode/package/src/configurationFiles.generated.ts +++ b/src/VisualStudioCode/package/src/configurationFiles.generated.ts @@ -375,18 +375,9 @@ roslynator_analyzers.enabled_by_default = true|false # Remove trailing white-space #dotnet_diagnostic.rcs1037.severity = suggestion -# Remove empty statement -#dotnet_diagnostic.rcs1038.severity = suggestion - # Remove argument list from attribute #dotnet_diagnostic.rcs1039.severity = silent -# Remove empty 'else' clause -#dotnet_diagnostic.rcs1040.severity = silent - -# Remove empty initializer -#dotnet_diagnostic.rcs1041.severity = suggestion - # Remove enum default underlying type #dotnet_diagnostic.rcs1042.severity = silent @@ -437,9 +428,6 @@ roslynator_analyzers.enabled_by_default = true|false # Merge 'if' with nested 'if' #dotnet_diagnostic.rcs1061.severity = silent -# Remove empty 'finally' clause -#dotnet_diagnostic.rcs1066.severity = silent - # Simplify logical negation #dotnet_diagnostic.rcs1068.severity = suggestion @@ -452,9 +440,6 @@ roslynator_analyzers.enabled_by_default = true|false # Remove redundant base constructor call #dotnet_diagnostic.rcs1071.severity = silent -# Remove empty namespace declaration -#dotnet_diagnostic.rcs1072.severity = suggestion - # Convert 'if' to 'return' statement #dotnet_diagnostic.rcs1073.severity = suggestion @@ -493,9 +478,6 @@ roslynator_analyzers.enabled_by_default = true|false #dotnet_diagnostic.rcs1090.severity = none # Options: roslynator_configure_await -# Remove empty region -#dotnet_diagnostic.rcs1091.severity = silent - # Remove file with no code #dotnet_diagnostic.rcs1093.severity = suggestion @@ -527,9 +509,6 @@ roslynator_analyzers.enabled_by_default = true|false # Unnecessary interpolation #dotnet_diagnostic.rcs1105.severity = suggestion -# Remove empty destructor -#dotnet_diagnostic.rcs1106.severity = suggestion - # Remove redundant 'ToCharArray' call #dotnet_diagnostic.rcs1107.severity = suggestion @@ -912,6 +891,9 @@ roslynator_analyzers.enabled_by_default = true|false # Unnecessary enum flag #dotnet_diagnostic.rcs1258.severity = suggestion +# Remove empty syntax +#dotnet_diagnostic.rcs1259.severity = suggestion + # Use pattern matching #dotnet_diagnostic.rcs9001.severity = silent