Skip to content

Commit

Permalink
Improve analyzer RCS1251 (#1323)
Browse files Browse the repository at this point in the history
  • Loading branch information
josefpihrt authored Dec 5, 2023
1 parent d696055 commit c574129
Show file tree
Hide file tree
Showing 4 changed files with 129 additions and 39 deletions.
4 changes: 4 additions & 0 deletions ChangeLog.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Changed

- Replace type declaration's empty braces with semicolon ([RCS1251](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1251) ([PR](https://github.com/dotnet/roslynator/pull/1323))

## [4.7.0] - 2023-12-03

### Added
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@

namespace Roslynator.CSharp.CodeFixes;

[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(RecordDeclarationCodeFixProvider))]
[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(RemoveUnnecessaryBracesCodeFixProvider))]
[Shared]
public class RecordDeclarationCodeFixProvider : BaseCodeFixProvider
public class RemoveUnnecessaryBracesCodeFixProvider : BaseCodeFixProvider
{
public sealed override ImmutableArray<string> FixableDiagnosticIds
{
Expand All @@ -29,7 +29,7 @@ public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context)
if (!TryFindFirstAncestorOrSelf(
root,
context.Span,
out RecordDeclarationSyntax recordDeclaration))
out TypeDeclarationSyntax typeDeclaration))
{
return;
}
Expand All @@ -41,21 +41,13 @@ public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context)
"Remove unnecessary braces",
ct =>
{
RecordDeclarationSyntax newRecordDeclaration = recordDeclaration.Update(
recordDeclaration.AttributeLists,
recordDeclaration.Modifiers,
recordDeclaration.Keyword,
recordDeclaration.Identifier,
recordDeclaration.TypeParameterList,
recordDeclaration.ParameterList.WithoutTrailingTrivia(),
recordDeclaration.BaseList,
recordDeclaration.ConstraintClauses,
default,
default,
default,
Token(SyntaxKind.SemicolonToken));
TypeDeclarationSyntax newTypeDeclaration = typeDeclaration
.WithOpenBraceToken(default)
.WithCloseBraceToken(default)
.WithSemicolonToken(Token(SyntaxKind.SemicolonToken))
.WithTrailingTrivia(typeDeclaration.GetTrailingTrivia());
return document.ReplaceNodeAsync(recordDeclaration, newRecordDeclaration, ct);
return document.ReplaceNodeAsync(typeDeclaration, newTypeDeclaration, ct);
},
GetEquivalenceKey(diagnostic));

Expand Down
60 changes: 44 additions & 16 deletions src/Analyzers/CSharp/Analysis/RemoveUnnecessaryBracesAnalyzer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,19 @@ public override void Initialize(AnalysisContext context)
{
base.Initialize(context);

context.RegisterSyntaxNodeAction(f => AnalyzeRecordDeclaration(f), SyntaxKind.RecordDeclaration, SyntaxKind.RecordStructDeclaration);
context.RegisterSyntaxNodeAction(c => AnalyzeRecordDeclaration(c), SyntaxKind.RecordDeclaration, SyntaxKind.RecordStructDeclaration);

context.RegisterCompilationStartAction(startContext =>
{
if ((int)((CSharpCompilation)startContext.Compilation).LanguageVersion >= 1200)
{
startContext.RegisterSyntaxNodeAction(
c => AnalyzeTypeDeclaration(c),
SyntaxKind.ClassDeclaration,
SyntaxKind.StructDeclaration,
SyntaxKind.InterfaceDeclaration);
}
});
}

private static void AnalyzeRecordDeclaration(SyntaxNodeAnalysisContext context)
Expand All @@ -38,24 +50,40 @@ private static void AnalyzeRecordDeclaration(SyntaxNodeAnalysisContext context)
if (!recordDeclaration.Members.Any()
&& recordDeclaration.ParameterList is not null)
{
SyntaxToken openBrace = recordDeclaration.OpenBraceToken;
Analyze(context, recordDeclaration);
}
}

private static void Analyze(SyntaxNodeAnalysisContext context, TypeDeclarationSyntax typeDeclaration)
{
SyntaxToken openBrace = typeDeclaration.OpenBraceToken;

if (!openBrace.IsKind(SyntaxKind.None))
{
SyntaxToken closeBrace = typeDeclaration.CloseBraceToken;

if (!openBrace.IsKind(SyntaxKind.None))
if (!closeBrace.IsKind(SyntaxKind.None)
&& openBrace.TrailingTrivia.IsEmptyOrWhitespace()
&& closeBrace.LeadingTrivia.IsEmptyOrWhitespace()
&& typeDeclaration.ParameterList?.CloseParenToken.TrailingTrivia.IsEmptyOrWhitespace() != false)
{
SyntaxToken closeBrace = recordDeclaration.CloseBraceToken;

if (!closeBrace.IsKind(SyntaxKind.None)
&& openBrace.TrailingTrivia.IsEmptyOrWhitespace()
&& closeBrace.LeadingTrivia.IsEmptyOrWhitespace()
&& recordDeclaration.ParameterList?.CloseParenToken.TrailingTrivia.IsEmptyOrWhitespace() != false)
{
DiagnosticHelpers.ReportDiagnostic(
context,
DiagnosticRules.RemoveUnnecessaryBraces,
openBrace.GetLocation(),
additionalLocations: new Location[] { closeBrace.GetLocation() });
}
DiagnosticHelpers.ReportDiagnostic(
context,
DiagnosticRules.RemoveUnnecessaryBraces,
openBrace.GetLocation(),
additionalLocations: new Location[] { closeBrace.GetLocation() });
}
}
}

private static void AnalyzeTypeDeclaration(SyntaxNodeAnalysisContext context)
{
var typeDeclaration = (TypeDeclarationSyntax)context.Node;

if (!typeDeclaration.Members.Any()
&& typeDeclaration.ParameterList is null)
{
Analyze(context, typeDeclaration);
}
}
}
78 changes: 72 additions & 6 deletions src/Tests/Analyzers.Tests/RCS1251RemoveUnnecessaryBracesTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@

namespace Roslynator.CSharp.Analysis.Tests;

//TODO: remove double diagnostic (https://github.com/dotnet/roslyn/issues/53136)
public class RCS1251RemoveUnnecessaryBracesTests : AbstractCSharpDiagnosticVerifier<RemoveUnnecessaryBracesAnalyzer, RecordDeclarationCodeFixProvider>
public class RCS1251RemoveUnnecessaryBracesTests : AbstractCSharpDiagnosticVerifier<RemoveUnnecessaryBracesAnalyzer, RemoveUnnecessaryBracesCodeFixProvider>
{
public override DiagnosticDescriptor Descriptor { get; } = DiagnosticRules.RemoveUnnecessaryBraces;

Expand All @@ -24,14 +23,14 @@ record R(string Value)
}
}
namespace System.Runtime.CompilerServices { internal static class IsExternalInit {} }
namespace System.Runtime.CompilerServices { internal static class IsExternalInit; }
", @"
namespace N
{
record R(string Value);
}
namespace System.Runtime.CompilerServices { internal static class IsExternalInit {} }
namespace System.Runtime.CompilerServices { internal static class IsExternalInit; }
");
}

Expand All @@ -46,17 +45,84 @@ record struct R(string Value)
}
}
namespace System.Runtime.CompilerServices { internal static class IsExternalInit {} }
namespace System.Runtime.CompilerServices { internal static class IsExternalInit; }
", @"
namespace N
{
record struct R(string Value);
}
namespace System.Runtime.CompilerServices { internal static class IsExternalInit {} }
namespace System.Runtime.CompilerServices { internal static class IsExternalInit; }
");
}

[Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.RemoveUnnecessaryBraces)]
public async Task Test_Class()
{
await VerifyDiagnosticAndFixAsync(@"
namespace N
{
class C
[|{|]
}
}
", @"
namespace N
{
class C;
}
");
}

[Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.RemoveUnnecessaryBraces)]
public async Task Test_Struct()
{
await VerifyDiagnosticAndFixAsync(@"
namespace N
{
struct C
[|{|]
}
}
", @"
namespace N
{
struct C;
}
");
}

[Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.RemoveUnnecessaryBraces)]
public async Task Test_Interface()
{
await VerifyDiagnosticAndFixAsync(@"
namespace N
{
interface C
[|{|]
}
}
", @"
namespace N
{
interface C;
}
");
}

[Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.RemoveUnnecessaryBraces)]
public async Task Test_Class_CSharp11()
{
await VerifyNoDiagnosticAsync(@"
namespace N
{
class C
{
}
}
", options: WellKnownCSharpTestOptions.Default_CSharp11);
}

[Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.RemoveUnnecessaryBraces)]
public async Task Test_NoDiagnostic()
{
Expand Down

0 comments on commit c574129

Please sign in to comment.