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: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ If you are already using other analyzers, you can check [which rules are duplica
|[MA0172](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0172.md)|Usage|Both sides of the logical operation are identical|⚠️|❌|❌|
|[MA0173](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0173.md)|Design|Use LazyInitializer.EnsureInitialize|ℹ️|✔️|❌|
|[MA0174](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0174.md)|Style|Record should use explicit 'class' keyword|ℹ️|❌|❌|
|[MA0175](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0175.md)|Style|Record should not use explicit 'class' keyword|ℹ️|❌||
|[MA0175](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0175.md)|Style|Record should not use explicit 'class' keyword|ℹ️|❌|✔️|
|[MA0176](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0176.md)|Performance|Optimize guid creation|ℹ️|✔️|✔️|
|[MA0177](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0177.md)|Style|Use single-line XML comment syntax when possible|ℹ️|❌|✔️|
|[MA0178](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0178.md)|Design|Use TimeSpan.Zero instead of TimeSpan.FromXXX(0)|ℹ️|✔️|✔️|
Expand Down
2 changes: 1 addition & 1 deletion docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@
|[MA0172](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0172.md)|Usage|Both sides of the logical operation are identical|<span title='Warning'>⚠️</span>|❌|❌|
|[MA0173](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0173.md)|Design|Use LazyInitializer.EnsureInitialize|<span title='Info'>ℹ️</span>|✔️|❌|
|[MA0174](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0174.md)|Style|Record should use explicit 'class' keyword|<span title='Info'>ℹ️</span>|❌|❌|
|[MA0175](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0175.md)|Style|Record should not use explicit 'class' keyword|<span title='Info'>ℹ️</span>|❌||
|[MA0175](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0175.md)|Style|Record should not use explicit 'class' keyword|<span title='Info'>ℹ️</span>|❌|✔️|
|[MA0176](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0176.md)|Performance|Optimize guid creation|<span title='Info'>ℹ️</span>|✔️|✔️|
|[MA0177](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0177.md)|Style|Use single-line XML comment syntax when possible|<span title='Info'>ℹ️</span>|❌|✔️|
|[MA0178](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0178.md)|Design|Use TimeSpan.Zero instead of TimeSpan.FromXXX(0)|<span title='Info'>ℹ️</span>|✔️|✔️|
Expand Down
4 changes: 2 additions & 2 deletions docs/Rules/MA0175.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
# MA0175 - Record should not use explicit 'class' keyword
<!-- sources -->
Source: [RecordClassDeclarationShouldBeImplicitAnalyzer.cs](https://github.com/meziantou/Meziantou.Analyzer/blob/main/src/Meziantou.Analyzer/Rules/RecordClassDeclarationShouldBeImplicitAnalyzer.cs)
Sources: [RecordClassDeclarationShouldBeImplicitAnalyzer.cs](https://github.com/meziantou/Meziantou.Analyzer/blob/main/src/Meziantou.Analyzer/Rules/RecordClassDeclarationShouldBeImplicitAnalyzer.cs), [RecordClassDeclarationShouldBeImplicitFixer.cs](https://github.com/meziantou/Meziantou.Analyzer/blob/main/src/Meziantou.Analyzer.CodeFixers/Rules/RecordClassDeclarationShouldBeImplicitFixer.cs)
<!-- sources -->

This rule suggests adding the explicit `class` keyword to record declarations that don't specify it.
This rule suggests removing the explicit `class` keyword from record declarations.

```csharp
public sealed record class Customer; // non-compliant
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#if CSHARP10_OR_GREATER
using System.Collections.Immutable;
using System.Composition;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Editing;

namespace Meziantou.Analyzer.Rules;

[ExportCodeFixProvider(LanguageNames.CSharp), Shared]
public sealed class RecordClassDeclarationShouldBeImplicitFixer : CodeFixProvider
{
public override ImmutableArray<string> FixableDiagnosticIds => ImmutableArray.Create(RuleIdentifiers.RecordClassDeclarationShouldBeImplicit);

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

public override async Task RegisterCodeFixesAsync(CodeFixContext context)
{
var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);
var nodeToFix = root?.FindNode(context.Span, getInnermostNodeForTie: true);
if (nodeToFix is not RecordDeclarationSyntax recordDeclaration)
return;

var title = "Remove 'class' keyword";
var codeAction = CodeAction.Create(
title,
ct => Fix(context.Document, recordDeclaration, ct),
equivalenceKey: title);

context.RegisterCodeFix(codeAction, context.Diagnostics);
}

private static async Task<Document> Fix(Document document, RecordDeclarationSyntax recordDeclaration, CancellationToken cancellationToken)
{
var editor = await DocumentEditor.CreateAsync(document, cancellationToken).ConfigureAwait(false);

var classKeyword = recordDeclaration.ClassOrStructKeyword;
var newRecordKeyword = recordDeclaration.Keyword.WithTrailingTrivia(classKeyword.TrailingTrivia);
var newRecordDeclaration = recordDeclaration
.WithKeyword(newRecordKeyword)
.WithClassOrStructKeyword(default);

editor.ReplaceNode(recordDeclaration, newRecordDeclaration);
return editor.GetChangedDocument();
}
}
#endif
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ private static ProjectBuilder CreateProjectBuilder()
{
return new ProjectBuilder()
.WithAnalyzer<RecordClassDeclarationShouldBeImplicitAnalyzer>()
.WithCodeFixProvider<RecordClassDeclarationShouldBeImplicitFixer>()
.WithTargetFramework(TargetFramework.NetLatest);
}

Expand Down Expand Up @@ -138,5 +139,44 @@ public record [|class|] Target : BaseRecord { }
""")
.ValidateAsync();
}

[Fact]
public async Task Fix_ExplicitRecordClass()
{
await CreateProjectBuilder()
.WithSourceCode("""
public record [|class|] Target { }
""")
.ShouldFixCodeWith("""
public record Target { }
""")
.ValidateAsync();
}

[Fact]
public async Task Fix_ExplicitRecordClass_WithModifiers()
{
await CreateProjectBuilder()
.WithSourceCode("""
public sealed record [|class|] Target { }
""")
.ShouldFixCodeWith("""
public sealed record Target { }
""")
.ValidateAsync();
}

[Fact]
public async Task Fix_ExplicitRecordClass_WithParameters()
{
await CreateProjectBuilder()
.WithSourceCode("""
public record [|class|] Target(int Id) { }
""")
.ShouldFixCodeWith("""
public record Target(int Id) { }
""")
.ValidateAsync();
}
}
#endif
Loading