-
Notifications
You must be signed in to change notification settings - Fork 4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #46800 from CyrusNajmabadi/useNotPattern
Add a simple feature to recommend people use the new C# 9 'not' pattern.
- Loading branch information
Showing
63 changed files
with
863 additions
and
6 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
91 changes: 91 additions & 0 deletions
91
src/Analyzers/CSharp/Analyzers/UsePatternMatching/CSharpUseNotPatternDiagnosticAnalyzer.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,91 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
// See the LICENSE file in the project root for more information. | ||
|
||
#nullable enable | ||
|
||
using System.Collections.Immutable; | ||
using Microsoft.CodeAnalysis.CodeStyle; | ||
using Microsoft.CodeAnalysis.CSharp.CodeStyle; | ||
using Microsoft.CodeAnalysis.CSharp.Shared.Extensions; | ||
using Microsoft.CodeAnalysis.CSharp.Syntax; | ||
using Microsoft.CodeAnalysis.Diagnostics; | ||
|
||
namespace Microsoft.CodeAnalysis.CSharp.UsePatternMatching | ||
{ | ||
/// <summary> | ||
/// Looks for code of the forms: | ||
/// | ||
/// var x = o as Type; | ||
/// if (!(x is Y y)) ... | ||
/// | ||
/// and converts it to: | ||
/// | ||
/// if (x is not Y y) ... | ||
/// | ||
/// </summary> | ||
[DiagnosticAnalyzer(LanguageNames.CSharp)] | ||
internal partial class CSharpUseNotPatternDiagnosticAnalyzer : AbstractBuiltInCodeStyleDiagnosticAnalyzer | ||
{ | ||
public CSharpUseNotPatternDiagnosticAnalyzer() | ||
: base(IDEDiagnosticIds.UseNotPatternDiagnosticId, | ||
CSharpCodeStyleOptions.PreferNotPattern, | ||
LanguageNames.CSharp, | ||
new LocalizableResourceString( | ||
nameof(CSharpAnalyzersResources.Use_pattern_matching), CSharpAnalyzersResources.ResourceManager, typeof(CSharpAnalyzersResources))) | ||
{ | ||
} | ||
|
||
public override DiagnosticAnalyzerCategory GetAnalyzerCategory() | ||
=> DiagnosticAnalyzerCategory.SemanticSpanAnalysis; | ||
|
||
protected override void InitializeWorker(AnalysisContext context) | ||
{ | ||
#if !CODE_STYLE // CODE_STYLE layer doesn't currently support generating 'not patterns'. Do not bother analyzing. | ||
context.RegisterSyntaxNodeAction(SyntaxNodeAction, SyntaxKind.LogicalNotExpression); | ||
#endif | ||
} | ||
|
||
private void SyntaxNodeAction(SyntaxNodeAnalysisContext syntaxContext) | ||
{ | ||
var node = syntaxContext.Node; | ||
var syntaxTree = node.SyntaxTree; | ||
|
||
// "x is not Type y" is only available in C# 9.0 and above. Don't offer this refactoring | ||
// in projects targeting a lesser version. | ||
if (!((CSharpParseOptions)syntaxTree.Options).LanguageVersion.IsCSharp9OrAbove()) | ||
return; | ||
|
||
var options = syntaxContext.Options; | ||
var cancellationToken = syntaxContext.CancellationToken; | ||
|
||
// Bail immediately if the user has disabled this feature. | ||
var styleOption = options.GetOption(CSharpCodeStyleOptions.PreferNotPattern, syntaxTree, cancellationToken); | ||
if (!styleOption.Value) | ||
return; | ||
|
||
// Look for the form: !(x is Y y) | ||
if (!(node is PrefixUnaryExpressionSyntax | ||
{ | ||
Operand: ParenthesizedExpressionSyntax | ||
{ | ||
Expression: IsPatternExpressionSyntax | ||
{ | ||
Pattern: DeclarationPatternSyntax, | ||
} isPattern, | ||
}, | ||
} notExpression)) | ||
{ | ||
return; | ||
} | ||
|
||
// Put a diagnostic with the appropriate severity on `is` keyword. | ||
syntaxContext.ReportDiagnostic(DiagnosticHelper.Create( | ||
Descriptor, | ||
isPattern.IsKeyword.GetLocation(), | ||
styleOption.Notification.Severity, | ||
ImmutableArray.Create(notExpression.GetLocation()), | ||
properties: null)); | ||
} | ||
} | ||
} |
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
94 changes: 94 additions & 0 deletions
94
src/Analyzers/CSharp/CodeFixes/UsePatternMatching/CSharpUseNotPatternCodeFixProvider.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,94 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
// See the LICENSE file in the project root for more information. | ||
|
||
#nullable enable | ||
|
||
using System; | ||
using System.Collections.Immutable; | ||
using System.Composition; | ||
using System.Diagnostics.CodeAnalysis; | ||
using System.Linq; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
using Microsoft.CodeAnalysis.CodeActions; | ||
using Microsoft.CodeAnalysis.CodeFixes; | ||
using Microsoft.CodeAnalysis.CSharp.Syntax; | ||
using Microsoft.CodeAnalysis.Diagnostics; | ||
using Microsoft.CodeAnalysis.Editing; | ||
using Microsoft.CodeAnalysis.Shared.Extensions; | ||
using Roslyn.Utilities; | ||
|
||
namespace Microsoft.CodeAnalysis.CSharp.UsePatternMatching | ||
{ | ||
using static SyntaxFactory; | ||
|
||
[ExportCodeFixProvider(LanguageNames.CSharp), Shared] | ||
internal partial class CSharpUseNotPatternCodeFixProvider : SyntaxEditorBasedCodeFixProvider | ||
{ | ||
[ImportingConstructor] | ||
[SuppressMessage("RoslynDiagnosticsReliability", "RS0033:Importing constructor should be [Obsolete]", Justification = "Used in test code: https://github.com/dotnet/roslyn/issues/42814")] | ||
public CSharpUseNotPatternCodeFixProvider() | ||
{ | ||
} | ||
|
||
public override ImmutableArray<string> FixableDiagnosticIds | ||
=> ImmutableArray.Create(IDEDiagnosticIds.UseNotPatternDiagnosticId); | ||
|
||
internal sealed override CodeFixCategory CodeFixCategory => CodeFixCategory.CodeStyle; | ||
|
||
public override Task RegisterCodeFixesAsync(CodeFixContext context) | ||
{ | ||
context.RegisterCodeFix(new MyCodeAction( | ||
c => FixAsync(context.Document, context.Diagnostics.First(), c)), | ||
context.Diagnostics); | ||
return Task.CompletedTask; | ||
} | ||
|
||
protected override Task FixAllAsync( | ||
Document document, ImmutableArray<Diagnostic> diagnostics, | ||
SyntaxEditor editor, CancellationToken cancellationToken) | ||
{ | ||
foreach (var diagnostic in diagnostics) | ||
{ | ||
cancellationToken.ThrowIfCancellationRequested(); | ||
ProcessDiagnostic(editor, diagnostic, cancellationToken); | ||
} | ||
|
||
return Task.CompletedTask; | ||
} | ||
|
||
private static void ProcessDiagnostic( | ||
SyntaxEditor editor, | ||
Diagnostic diagnostic, | ||
CancellationToken cancellationToken) | ||
{ | ||
#if CODE_STYLE | ||
Contract.Fail("We should have never gotten here as CODE_STYLE doesn't support C# 9 yet."); | ||
#else | ||
|
||
var notExpressionLocation = diagnostic.AdditionalLocations[0]; | ||
|
||
var notExpression = (PrefixUnaryExpressionSyntax)notExpressionLocation.FindNode(getInnermostNodeForTie: true, cancellationToken); | ||
var parenthesizedExpression = (ParenthesizedExpressionSyntax)notExpression.Operand; | ||
var isPattern = (IsPatternExpressionSyntax)parenthesizedExpression.Expression; | ||
|
||
var updatedPattern = isPattern.WithPattern(UnaryPattern(Token(SyntaxKind.NotKeyword), isPattern.Pattern)); | ||
editor.ReplaceNode( | ||
notExpression, | ||
updatedPattern.WithPrependedLeadingTrivia(notExpression.GetLeadingTrivia()) | ||
.WithAppendedTrailingTrivia(notExpression.GetTrailingTrivia())); | ||
|
||
#endif | ||
|
||
} | ||
|
||
private class MyCodeAction : CustomCodeActions.DocumentChangeAction | ||
{ | ||
public MyCodeAction(Func<CancellationToken, Task<Document>> createChangedDocument) | ||
: base(CSharpAnalyzersResources.Use_pattern_matching, createChangedDocument, CSharpAnalyzersResources.Use_pattern_matching) | ||
{ | ||
} | ||
} | ||
} | ||
} |
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
74 changes: 74 additions & 0 deletions
74
src/Analyzers/CSharp/Tests/UsePatternMatching/CSharpUseNotPatternTests.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,74 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
// See the LICENSE file in the project root for more information. | ||
|
||
using System.Threading.Tasks; | ||
using Microsoft.CodeAnalysis.CSharp; | ||
using Microsoft.CodeAnalysis.CSharp.UsePatternMatching; | ||
using Microsoft.CodeAnalysis.Editor.UnitTests.CodeActions; | ||
using Microsoft.CodeAnalysis.Test.Utilities; | ||
using Roslyn.Test.Utilities; | ||
using Xunit; | ||
|
||
namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.UsePatternMatching | ||
{ | ||
using VerifyCS = CSharpCodeFixVerifier< | ||
CSharpUseNotPatternDiagnosticAnalyzer, | ||
CSharpUseNotPatternCodeFixProvider>; | ||
|
||
public partial class CSharpUseNotPatternTests | ||
{ | ||
#if !CODE_STYLE | ||
|
||
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseNotPattern)] | ||
[WorkItem(46699, "https://github.com/dotnet/roslyn/issues/46699")] | ||
public async Task UseNotPattern() | ||
{ | ||
await new VerifyCS.Test | ||
{ | ||
TestCode = | ||
@"class C | ||
{ | ||
void M(object x) | ||
{ | ||
if (!(x [|is|] string s)) | ||
{ | ||
} | ||
} | ||
}", | ||
FixedCode = | ||
@"class C | ||
{ | ||
void M(object x) | ||
{ | ||
if (x is not string s) | ||
{ | ||
} | ||
} | ||
}", | ||
LanguageVersion = LanguageVersion.CSharp9, | ||
}.RunAsync(); | ||
} | ||
|
||
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseNotPattern)] | ||
public async Task UnavailableInCSharp8() | ||
{ | ||
await new VerifyCS.Test | ||
{ | ||
TestCode = | ||
@"class C | ||
{ | ||
void M(object x) | ||
{ | ||
if (!(x is string s)) | ||
{ | ||
} | ||
} | ||
}", | ||
LanguageVersion = LanguageVersion.CSharp8, | ||
}.RunAsync(); | ||
} | ||
|
||
#endif | ||
} | ||
} |
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
82 changes: 82 additions & 0 deletions
82
...alyzers/VisualBasic/Analyzers/UseIsNotExpression/VisualBasicUseIsNotDiagnosticAnalyzer.vb
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,82 @@ | ||
' Licensed to the .NET Foundation under one or more agreements. | ||
' The .NET Foundation licenses this file to you under the MIT license. | ||
' See the LICENSE file in the project root for more information. | ||
|
||
Imports System.Collections.Immutable | ||
Imports Microsoft.CodeAnalysis.CodeStyle | ||
Imports Microsoft.CodeAnalysis.Diagnostics | ||
Imports Microsoft.CodeAnalysis.VisualBasic.CodeStyle | ||
Imports Microsoft.CodeAnalysis.VisualBasic.Syntax | ||
|
||
Namespace Microsoft.CodeAnalysis.VisualBasic.UseIsNotExpression | ||
''' <summary> | ||
''' Looks for code of the forms: | ||
''' | ||
''' if not x is ... | ||
''' | ||
''' and converts it to: | ||
''' | ||
''' if x isnot ... | ||
''' | ||
''' </summary> | ||
<DiagnosticAnalyzer(LanguageNames.VisualBasic)> | ||
Partial Friend Class VisualBasicUseIsNotExpressionDiagnosticAnalyzer | ||
Inherits AbstractBuiltInCodeStyleDiagnosticAnalyzer | ||
|
||
Public Sub New() | ||
MyBase.New(IDEDiagnosticIds.UseIsNotExpressionDiagnosticId, | ||
VisualBasicCodeStyleOptions.PreferIsNotExpression, | ||
LanguageNames.VisualBasic, | ||
New LocalizableResourceString( | ||
NameOf(VisualBasicAnalyzersResources.Use_IsNot_expression), VisualBasicAnalyzersResources.ResourceManager, GetType(VisualBasicAnalyzersResources))) | ||
End Sub | ||
|
||
Public Overrides Function GetAnalyzerCategory() As DiagnosticAnalyzerCategory | ||
Return DiagnosticAnalyzerCategory.SemanticSpanAnalysis | ||
End Function | ||
|
||
Protected Overrides Sub InitializeWorker(context As AnalysisContext) | ||
context.RegisterSyntaxNodeAction(AddressOf SyntaxNodeAction, SyntaxKind.NotExpression) | ||
End Sub | ||
|
||
Private Sub SyntaxNodeAction(syntaxContext As SyntaxNodeAnalysisContext) | ||
Dim node = syntaxContext.Node | ||
Dim syntaxTree = node.SyntaxTree | ||
|
||
' "x is not Type y" is only available in C# 9.0 and above. Don't offer this refactoring | ||
' in projects targeting a lesser version. | ||
If DirectCast(syntaxTree.Options, VisualBasicParseOptions).LanguageVersion < LanguageVersion.VisualBasic14 Then | ||
Return | ||
End If | ||
|
||
Dim options = syntaxContext.Options | ||
Dim cancellationToken = syntaxContext.CancellationToken | ||
|
||
' Bail immediately if the user has disabled this feature. | ||
Dim styleOption = options.GetOption(VisualBasicCodeStyleOptions.PreferIsNotExpression, syntaxTree, cancellationToken) | ||
If Not styleOption.Value Then | ||
Return | ||
End If | ||
|
||
Dim notExpression = DirectCast(node, UnaryExpressionSyntax) | ||
Dim operand = notExpression.Operand | ||
|
||
' Look for the form: not x is y, or not typeof x is y | ||
If Not operand.IsKind(SyntaxKind.IsExpression) AndAlso Not operand.IsKind(SyntaxKind.TypeOfIsExpression) Then | ||
Return | ||
End If | ||
|
||
Dim isKeyword = If(operand.IsKind(SyntaxKind.IsExpression), | ||
DirectCast(operand, BinaryExpressionSyntax).OperatorToken, | ||
DirectCast(operand, TypeOfExpressionSyntax).OperatorToken) | ||
|
||
' Put a diagnostic with the appropriate severity on `is` keyword. | ||
syntaxContext.ReportDiagnostic(DiagnosticHelper.Create( | ||
Descriptor, | ||
isKeyword.GetLocation(), | ||
styleOption.Notification.Severity, | ||
ImmutableArray.Create(notExpression.GetLocation()), | ||
properties:=Nothing)) | ||
End Sub | ||
End Class | ||
End Namespace |
Oops, something went wrong.