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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,7 @@ If you are already using other analyzers, you can check [which rules are duplica
|[MA0201](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0201.md)|Usage|Do not use zero-valued enum flags in flag checks|⚠️|✔️|❌|
|[MA0202](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0202.md)|Design|Conditional compilation branches have identical code|⚠️|✔️|✔️|
|[MA0203](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0203.md)|Design|Do not use return tag for void method|⚠️|✔️|❌|
|[MA0204](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0204.md)|Design|Remove unnecessary partial modifier|ℹ️|✔️|✔️|

<!-- rules -->

Expand Down
1 change: 1 addition & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,7 @@
|[MA0201](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0201.md)|Usage|Do not use zero-valued enum flags in flag checks|<span title='Warning'>⚠️</span>|✔️|❌|
|[MA0202](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0202.md)|Design|Conditional compilation branches have identical code|<span title='Warning'>⚠️</span>|✔️|✔️|
|[MA0203](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0203.md)|Design|Do not use return tag for void method|<span title='Warning'>⚠️</span>|✔️|❌|
|[MA0204](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0204.md)|Design|Remove unnecessary partial modifier|<span title='Info'>ℹ️</span>|✔️|✔️|

|Id|Suppressed rule|Justification|
|--|---------------|-------------|
Expand Down
24 changes: 24 additions & 0 deletions docs/Rules/MA0204.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# MA0204 - Remove unnecessary partial modifier
<!-- sources -->
Sources: [RemoveUnnecessaryPartialModifierAnalyzer.cs](https://github.com/meziantou/Meziantou.Analyzer/blob/main/src/Meziantou.Analyzer/Rules/RemoveUnnecessaryPartialModifierAnalyzer.cs), [RemoveUnnecessaryPartialModifierFixer.cs](https://github.com/meziantou/Meziantou.Analyzer/blob/main/src/Meziantou.Analyzer.CodeFixers/Rules/RemoveUnnecessaryPartialModifierFixer.cs)
<!-- sources -->

This rule reports a `partial` modifier on a type when the type has a single declaration and no partial members.

The `partial` modifier can be removed when there is no other declaration to merge with.

## Non-compliant code

````csharp
partial class Sample
{
}
````

## Compliant code

````csharp
class Sample
{
}
````
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
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;
using Microsoft.CodeAnalysis.Formatting;

namespace Meziantou.Analyzer.Rules;

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

public override FixAllProvider GetFixAllProvider()
{
return WellKnownFixAllProviders.BatchFixer;
}

public override async Task RegisterCodeFixesAsync(CodeFixContext context)
{
var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);
if (root is null)
return;

var partialKeyword = root.FindToken(context.Span.Start);
if (!partialKeyword.IsKind(SyntaxKind.PartialKeyword))
return;

var typeDeclaration = partialKeyword.Parent?.AncestorsAndSelf().OfType<TypeDeclarationSyntax>().FirstOrDefault();
if (typeDeclaration is null || !typeDeclaration.Modifiers.Contains(partialKeyword))
return;

var title = "Remove partial modifier";
var codeAction = CodeAction.Create(
title,
ct => RemovePartialModifierAsync(context.Document, partialKeyword, ct),
equivalenceKey: title);

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

private static async Task<Document> RemovePartialModifierAsync(Document document, SyntaxToken partialKeyword, CancellationToken cancellationToken)
{
var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
if (root is null)
return document;

var currentPartialKeyword = root.FindToken(partialKeyword.SpanStart);
var typeDeclaration = currentPartialKeyword.Parent?.AncestorsAndSelf().OfType<TypeDeclarationSyntax>().FirstOrDefault();
if (typeDeclaration is null || !typeDeclaration.Modifiers.Contains(currentPartialKeyword))
return document;

var newTypeDeclaration = RemovePartialModifier(typeDeclaration, currentPartialKeyword);
return document.WithSyntaxRoot(root.ReplaceNode(typeDeclaration, newTypeDeclaration));
}

private static TypeDeclarationSyntax RemovePartialModifier(TypeDeclarationSyntax typeDeclaration, SyntaxToken partialKeyword)
{
var modifiers = typeDeclaration.Modifiers;
var partialKeywordIndex = modifiers.IndexOf(partialKeyword);
if (partialKeywordIndex < 0)
return typeDeclaration;

var nonspaceTrivia = partialKeyword.LeadingTrivia.Concat(partialKeyword.TrailingTrivia).Where(t => !t.IsKind(SyntaxKind.WhitespaceTrivia) && !t.IsKind(SyntaxKind.EndOfLineTrivia)).ToList();

if (partialKeywordIndex + 1 < modifiers.Count)
{
var nextModifier = modifiers[partialKeywordIndex + 1];
modifiers = modifiers.Replace(nextModifier, nextModifier.WithLeadingTrivia(nonspaceTrivia.Concat(nextModifier.LeadingTrivia)));
return typeDeclaration.WithModifiers(modifiers.Remove(partialKeyword));
}

return typeDeclaration
.WithModifiers(modifiers.Remove(partialKeyword))
.WithKeyword(typeDeclaration.Keyword.WithLeadingTrivia(nonspaceTrivia.Concat(typeDeclaration.Keyword.LeadingTrivia)));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -604,3 +604,6 @@ dotnet_diagnostic.MA0202.severity = error

# MA0203: Do not use return tag for void method
dotnet_diagnostic.MA0203.severity = error

# MA0204: Remove unnecessary partial modifier
dotnet_diagnostic.MA0204.severity = error
Original file line number Diff line number Diff line change
Expand Up @@ -604,3 +604,6 @@ dotnet_diagnostic.MA0202.severity = suggestion

# MA0203: Do not use return tag for void method
dotnet_diagnostic.MA0203.severity = suggestion

# MA0204: Remove unnecessary partial modifier
dotnet_diagnostic.MA0204.severity = suggestion
Original file line number Diff line number Diff line change
Expand Up @@ -604,3 +604,6 @@ dotnet_diagnostic.MA0202.severity = warning

# MA0203: Do not use return tag for void method
dotnet_diagnostic.MA0203.severity = warning

# MA0204: Remove unnecessary partial modifier
dotnet_diagnostic.MA0204.severity = warning
Original file line number Diff line number Diff line change
Expand Up @@ -604,3 +604,6 @@ dotnet_diagnostic.MA0202.severity = warning

# MA0203: Do not use return tag for void method
dotnet_diagnostic.MA0203.severity = warning

# MA0204: Remove unnecessary partial modifier
dotnet_diagnostic.MA0204.severity = suggestion
3 changes: 3 additions & 0 deletions src/Meziantou.Analyzer.Pack/configuration/none.editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -604,3 +604,6 @@ dotnet_diagnostic.MA0202.severity = none

# MA0203: Do not use return tag for void method
dotnet_diagnostic.MA0203.severity = none

# MA0204: Remove unnecessary partial modifier
dotnet_diagnostic.MA0204.severity = none
1 change: 1 addition & 0 deletions src/Meziantou.Analyzer/RuleIdentifiers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,7 @@ internal static class RuleIdentifiers
public const string DoNotUseZeroValuedEnumFlagsInFlagChecks = "MA0201";
public const string ConditionalCompilationBranchesAreIdentical = "MA0202";
public const string DoNotUseReturnTagForVoidMethod = "MA0203";
public const string RemoveUnnecessaryPartialModifier = "MA0204";

public static string GetHelpUri(string identifier)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
using System.Collections.Immutable;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;

namespace Meziantou.Analyzer.Rules;

[DiagnosticAnalyzer(LanguageNames.CSharp)]
public sealed class RemoveUnnecessaryPartialModifierAnalyzer : DiagnosticAnalyzer
{
private static readonly DiagnosticDescriptor Rule = new(
RuleIdentifiers.RemoveUnnecessaryPartialModifier,
title: "Remove unnecessary partial modifier",
messageFormat: "Remove unnecessary partial modifier",
RuleCategories.Design,
DiagnosticSeverity.Info,
isEnabledByDefault: true,
description: "",
helpLinkUri: RuleIdentifiers.GetHelpUri(RuleIdentifiers.RemoveUnnecessaryPartialModifier));

public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Rule);

public override void Initialize(AnalysisContext context)
{
context.EnableConcurrentExecution();
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze);

context.RegisterSymbolAction(AnalyzeNamedTypeSymbol, SymbolKind.NamedType);
}

private static void AnalyzeNamedTypeSymbol(SymbolAnalysisContext context)
{
var symbol = (INamedTypeSymbol)context.Symbol;
if (symbol.TypeKind is not (TypeKind.Class or TypeKind.Struct or TypeKind.Interface))
return;

if (symbol.DeclaringSyntaxReferences.Length != 1)
return;

var typeDeclaration = symbol.DeclaringSyntaxReferences[0].GetSyntax(context.CancellationToken) as TypeDeclarationSyntax;
if (typeDeclaration is null)
return;

var partialToken = typeDeclaration.Modifiers.FirstOrDefault(modifier => modifier.IsKind(SyntaxKind.PartialKeyword));
if (partialToken == default)
return;

context.ReportDiagnostic(Diagnostic.Create(Rule, partialToken.GetLocation()));
}
}
Loading