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 @@ -188,7 +188,7 @@ If you are already using other analyzers, you can check [which rules are duplica
|[MA0171](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0171.md)|Usage|Use pattern matching instead of HasValue for Nullable\<T\> check|ℹ️|❌|✔️|
|[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|ℹ️|❌||
|[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|ℹ️|❌|✔️|
|[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|ℹ️|❌|✔️|
Expand Down
2 changes: 1 addition & 1 deletion docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@
|[MA0171](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0171.md)|Usage|Use pattern matching instead of HasValue for Nullable\<T\> check|<span title='Info'>ℹ️</span>|❌|✔️|
|[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>|❌||
|[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>|❌|✔️|
|[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>|❌|✔️|
Expand Down
2 changes: 1 addition & 1 deletion docs/Rules/MA0174.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# MA0174 - Record should use explicit 'class' keyword
<!-- sources -->
Source: [RecordClassDeclarationShouldBeExplicitAnalyzer.cs](https://github.com/meziantou/Meziantou.Analyzer/blob/main/src/Meziantou.Analyzer/Rules/RecordClassDeclarationShouldBeExplicitAnalyzer.cs)
Sources: [RecordClassDeclarationShouldBeExplicitAnalyzer.cs](https://github.com/meziantou/Meziantou.Analyzer/blob/main/src/Meziantou.Analyzer/Rules/RecordClassDeclarationShouldBeExplicitAnalyzer.cs), [RecordClassDeclarationShouldBeExplicitFixer.cs](https://github.com/meziantou/Meziantou.Analyzer/blob/main/src/Meziantou.Analyzer.CodeFixers/Rules/RecordClassDeclarationShouldBeExplicitFixer.cs)
<!-- sources -->

This rule suggests adding the explicit `class` keyword to record declarations that don't specify it.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
#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;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Editing;

namespace Meziantou.Analyzer.Rules;

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

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

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

var title = "Add 'class' keyword";
var codeAction = CodeAction.Create(
title,
ct => AddClassKeyword(context.Document, nodeToFix, ct),
equivalenceKey: title);

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

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

var classKeyword = SyntaxFactory.Token(SyntaxKind.ClassKeyword)
.WithLeadingTrivia(nodeToFix.Keyword.TrailingTrivia)
.WithTrailingTrivia(SyntaxFactory.Space);

var newNode = nodeToFix
.WithKeyword(nodeToFix.Keyword.WithTrailingTrivia())
.WithClassOrStructKeyword(classKeyword);

editor.ReplaceNode(nodeToFix, newNode);
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<RecordClassDeclarationShouldBeExplicitAnalyzer>()
.WithCodeFixProvider<RecordClassDeclarationShouldBeExplicitFixer>()
.WithTargetFramework(TargetFramework.NetLatest);
}

Expand Down Expand Up @@ -150,5 +151,44 @@ public record class Target : BaseRecord { }
""")
.ValidateAsync();
}

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

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

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