-
-
Notifications
You must be signed in to change notification settings - Fork 51
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
9 changed files
with
263 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -20,4 +20,4 @@ Process.Start(new ProcessStartInfo("https://www.meziantou.net/") | |
UseShellExecute = true, | ||
}); | ||
|
||
```` | ||
```` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -40,4 +40,4 @@ Process.Start(new ProcessStartInfo("cmd") | |
UseShellExecute = false, | ||
}); | ||
|
||
```` | ||
```` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
# MA0164 - Use parentheses to not pattern clearer | ||
|
||
`not` patterns are often wrongly used in combination with the `or` operator. This rule suggests using parentheses to make evaluation order clearer. | ||
|
||
````c# | ||
DayOfWeek day = DayOfWeek.Tuesday; | ||
|
||
var isWeekday = day is not DayOfWeek.Saturday or DayOfWeek.Sunday; // wrong | ||
var isWeekday = day is not (DayOfWeek.Saturday or DayOfWeek.Sunday); // ok | ||
var isWeekday = day is not DayOfWeek.Saturday and not DayOfWeek.Sunday; // ok | ||
```` | ||
|
||
````c# | ||
_ = value is not null or ""; // not-compliant | ||
_ = value is (not null) or ""; // ok | ||
_ = value is not (null or ""); // ok | ||
```` | ||
|
||
> **Warning** | ||
Note that the provided code fix may not always be correct. It adds parenthesis to show the current evaluation order, but this may not be what is expected. It is recommended to review the code after applying the fix. |
72 changes: 72 additions & 0 deletions
72
src/Meziantou.Analyzer.CodeFixers/Rules/NotPatternShouldBeParenthesizedCodeFixer.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
using System.Collections.Immutable; | ||
using System.Composition; | ||
using System.Linq; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
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 NotPatternShouldBeParenthesizedCodeFixer : CodeFixProvider | ||
{ | ||
public override ImmutableArray<string> FixableDiagnosticIds => ImmutableArray.Create(RuleIdentifiers.NotPatternShouldBeParenthesized); | ||
|
||
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 null) | ||
return; | ||
|
||
{ | ||
var title = "Add parentheses around not"; | ||
var codeAction = CodeAction.Create( | ||
title, | ||
ct => ParenthesizeNotPattern(context.Document, nodeToFix, ct), | ||
equivalenceKey: title); | ||
context.RegisterCodeFix(codeAction, context.Diagnostics); | ||
} | ||
{ | ||
var title = "Negate all or patterns"; | ||
var codeAction = CodeAction.Create( | ||
title, | ||
ct => ParenthesizeOrPattern(context.Document, nodeToFix, ct), | ||
equivalenceKey: title); | ||
context.RegisterCodeFix(codeAction, context.Diagnostics); | ||
} | ||
} | ||
|
||
private static async Task<Document> ParenthesizeNotPattern(Document document, SyntaxNode nodeToFix, CancellationToken cancellationToken) | ||
{ | ||
var editor = await DocumentEditor.CreateAsync(document, cancellationToken).ConfigureAwait(false); | ||
editor.ReplaceNode(nodeToFix, SyntaxFactory.ParenthesizedPattern((PatternSyntax)nodeToFix)); | ||
return editor.GetChangedDocument(); | ||
} | ||
|
||
private static async Task<Document> ParenthesizeOrPattern(Document document, SyntaxNode nodeToFix, CancellationToken cancellationToken) | ||
{ | ||
var editor = await DocumentEditor.CreateAsync(document, cancellationToken).ConfigureAwait(false); | ||
|
||
if (nodeToFix is not UnaryPatternSyntax unary) | ||
return document; | ||
|
||
|
||
var root = unary.Ancestors().TakeWhile(IsOrPattern).LastOrDefault(); | ||
if (root is null) | ||
return document; | ||
|
||
editor.ReplaceNode(root, SyntaxFactory.UnaryPattern(SyntaxFactory.Token(SyntaxKind.NotKeyword), SyntaxFactory.ParenthesizedPattern((PatternSyntax)root.ReplaceNode(unary, unary.Pattern)))); | ||
|
||
return editor.GetChangedDocument(); | ||
} | ||
|
||
private static bool IsOrPattern(SyntaxNode? node) => node is BinaryPatternSyntax binaryPatternSyntax && binaryPatternSyntax.OperatorToken.IsKind(SyntaxKind.OrKeyword); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
48 changes: 48 additions & 0 deletions
48
src/Meziantou.Analyzer/Rules/NotPatternShouldBeParenthesizedAnalyzer.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
#if CSHARP9_OR_GREATER | ||
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 NotPatternShouldBeParenthesizedAnalyzer : DiagnosticAnalyzer | ||
{ | ||
private static readonly DiagnosticDescriptor Rule = new( | ||
RuleIdentifiers.NotPatternShouldBeParenthesized, | ||
title: "Use parentheses to not pattern clearer", | ||
messageFormat: "Use parentheses to make it clearer", | ||
RuleCategories.Style, | ||
DiagnosticSeverity.Warning, | ||
isEnabledByDefault: true, | ||
description: "", | ||
helpLinkUri: RuleIdentifiers.GetHelpUri(RuleIdentifiers.NotPatternShouldBeParenthesized)); | ||
|
||
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Rule); | ||
|
||
public override void Initialize(AnalysisContext context) | ||
{ | ||
context.EnableConcurrentExecution(); | ||
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); | ||
|
||
context.RegisterSyntaxNodeAction(AnalyzeNotPatternSyntax, SyntaxKind.NotPattern); | ||
} | ||
|
||
private void AnalyzeNotPatternSyntax(SyntaxNodeAnalysisContext context) | ||
{ | ||
var node = context.Node; | ||
if (node.Parent is null || node.Parent.IsKind(SyntaxKind.ParenthesizedPattern)) | ||
return; | ||
|
||
if (node.Parent is BinaryPatternSyntax binaryPattern && binaryPattern.OperatorToken.IsKind(SyntaxKind.OrKeyword)) | ||
{ | ||
if (binaryPattern.Left == node) | ||
{ | ||
context.ReportDiagnostic(Diagnostic.Create(Rule, node.GetLocation())); | ||
} | ||
} | ||
} | ||
} | ||
#endif |
111 changes: 111 additions & 0 deletions
111
tests/Meziantou.Analyzer.Test/Rules/NotPatternShouldBeParenthesizedAnalyzerTests.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
using System.Threading.Tasks; | ||
using Meziantou.Analyzer.Rules; | ||
using TestHelper; | ||
using Xunit; | ||
|
||
namespace Meziantou.Analyzer.Test.Rules; | ||
public sealed class NotPatternShouldBeParenthesizedAnalyzerTests | ||
{ | ||
private static ProjectBuilder CreateProjectBuilder() | ||
=> new ProjectBuilder() | ||
.WithAnalyzer<NotPatternShouldBeParenthesizedAnalyzer>() | ||
.WithCodeFixProvider<NotPatternShouldBeParenthesizedCodeFixer>() | ||
.WithOutputKind(Microsoft.CodeAnalysis.OutputKind.ConsoleApplication); | ||
|
||
[Fact] | ||
public async Task Not_Null() | ||
=> await CreateProjectBuilder() | ||
.WithSourceCode(""" | ||
string a = default; | ||
_ = a is not null; | ||
""") | ||
.ValidateAsync(); | ||
|
||
[Fact] | ||
public async Task Not_Null_Or_Empty() | ||
=> await CreateProjectBuilder() | ||
.WithSourceCode(""" | ||
string a = default; | ||
_ = a is [|not null|] or ""; | ||
""") | ||
.ShouldFixCodeWith(index: 0, """ | ||
string a = default; | ||
_ = a is (not null) or ""; | ||
""") | ||
.ValidateAsync(); | ||
|
||
[Fact] | ||
public async Task Not_Null_And_Empty() | ||
=> await CreateProjectBuilder() | ||
.WithSourceCode(""" | ||
string a = default; | ||
_ = a is not null and ""; | ||
""") | ||
.ValidateAsync(); | ||
|
||
[Fact] | ||
public async Task Not_Or_GreaterThan() | ||
=> await CreateProjectBuilder() | ||
.WithSourceCode(""" | ||
int a = default; | ||
_ = a is [|not 1|] or > 2; | ||
""") | ||
.ShouldFixCodeWith(index: 0, """ | ||
int a = default; | ||
_ = a is (not 1) or > 2; | ||
""") | ||
.ValidateAsync(); | ||
|
||
[Fact] | ||
public async Task Parentheses_Not_Or_GreaterThan() | ||
=> await CreateProjectBuilder() | ||
.WithSourceCode(""" | ||
int a = 1; | ||
_ = a is (not 1) or > 2; | ||
""") | ||
.ValidateAsync(); | ||
|
||
[Fact] | ||
public async Task GreaterThan_Or_Not() | ||
=> await CreateProjectBuilder() | ||
.WithSourceCode(""" | ||
int a = 1; | ||
_ = a is 1 or not (< 0); | ||
""") | ||
.ValidateAsync(); | ||
|
||
[Fact] | ||
public async Task GreaterThan_Or_Not_Or_Not() | ||
=> await CreateProjectBuilder() | ||
.WithSourceCode(""" | ||
int a = 1; | ||
_ = a is 1 or not < 0 or not > 1; | ||
""") | ||
.ValidateAsync(); | ||
|
||
[Fact] | ||
public async Task Not_Many_or_Fix1() | ||
=> await CreateProjectBuilder() | ||
.WithSourceCode(""" | ||
int a = 1; | ||
_ = a is [|not 1|] or 2 or 3 or 4; | ||
""") | ||
.ShouldFixCodeWith(index: 0, """ | ||
int a = 1; | ||
_ = a is (not 1) or 2 or 3 or 4; | ||
""") | ||
.ValidateAsync(); | ||
|
||
[Fact] | ||
public async Task Not_Many_or_Fix2() | ||
=> await CreateProjectBuilder() | ||
.WithSourceCode(""" | ||
int a = 1; | ||
_ = a is [|not 1|] or 2 or 3 or 4; | ||
""") | ||
.ShouldFixCodeWith(index: 1, """ | ||
int a = 1; | ||
_ = a is not (1 or 2 or 3 or 4); | ||
""") | ||
.ValidateAsync(); | ||
} |