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
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