-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add IsNull analyzer and CodeFix provider. (#97)
- Loading branch information
1 parent
5c27163
commit 3b28bc7
Showing
12 changed files
with
699 additions
and
5 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
41 changes: 41 additions & 0 deletions
41
StyleChecker/StyleChecker.Test/Refactoring/IsNull/AnalyzerTest.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,41 @@ | ||
namespace StyleChecker.Test.Refactoring.IsNull | ||
{ | ||
using System.IO; | ||
using Microsoft.CodeAnalysis; | ||
using Microsoft.VisualStudio.TestTools.UnitTesting; | ||
using StyleChecker.Refactoring.IsNull; | ||
using StyleChecker.Test.Framework; | ||
|
||
[TestClass] | ||
public sealed class AnalyzerTest : CodeFixVerifier | ||
{ | ||
public AnalyzerTest() | ||
: base( | ||
Path.Combine(Categories.Refactoring, "IsNull"), | ||
new Analyzer(), | ||
new CodeFixer()) | ||
{ | ||
} | ||
|
||
[TestMethod] | ||
public void Okay() | ||
=> VerifyDiagnostic(ReadText("Okay"), Atmosphere.Default); | ||
|
||
[TestMethod] | ||
public void Code() | ||
{ | ||
var code = ReadText("Code"); | ||
var fix = ReadText("CodeFix"); | ||
Result Expected(Belief b) | ||
{ | ||
var token = b.Message; | ||
return b.ToResult( | ||
Analyzer.DiagnosticId, | ||
$"Use '{token}' operator instead of 'is' pattern matching.", | ||
DiagnosticSeverity.Info); | ||
} | ||
|
||
VerifyDiagnosticAndFix(code, Atmosphere.Default, Expected, fix); | ||
} | ||
} | ||
} |
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,41 @@ | ||
namespace StyleChecker.Test.Refactoring.IsNull | ||
{ | ||
public sealed class Code | ||
{ | ||
public void EqualToNull(string value) | ||
{ | ||
if (value is null) | ||
//@ ^== | ||
{ | ||
return; | ||
} | ||
} | ||
|
||
public void NotEqualToNull(string value) | ||
{ | ||
if (!(value is null)) | ||
//@ ^!= | ||
{ | ||
return; | ||
} | ||
} | ||
|
||
public void NullableValueType(int? value) | ||
{ | ||
if (value is null) | ||
//@ ^== | ||
{ | ||
return; | ||
} | ||
} | ||
|
||
public void KeepTrivia(string value) | ||
{ | ||
if ( /*A*/ ! /*B*/ ( /*C*/ value /*D*/ is /*E*/ null /*F*/ ) /*G*/ ) | ||
//@ ^!= | ||
{ | ||
return; | ||
} | ||
} | ||
} | ||
} |
37 changes: 37 additions & 0 deletions
37
StyleChecker/StyleChecker.Test/Refactoring/IsNull/CodeFix.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,37 @@ | ||
namespace StyleChecker.Test.Refactoring.IsNull | ||
{ | ||
public sealed class Code | ||
{ | ||
public void EqualToNull(string value) | ||
{ | ||
if (value == null) | ||
{ | ||
return; | ||
} | ||
} | ||
|
||
public void NotEqualToNull(string value) | ||
{ | ||
if (value != null) | ||
{ | ||
return; | ||
} | ||
} | ||
|
||
public void NullableValueType(int? value) | ||
{ | ||
if (value == null) | ||
{ | ||
return; | ||
} | ||
} | ||
|
||
public void KeepTrivia(string value) | ||
{ | ||
if ( /*A*/ /*B*/ /*C*/ value /*D*/ != /*E*/ null /*F*/ /*G*/ ) | ||
{ | ||
return; | ||
} | ||
} | ||
} | ||
} |
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,6 @@ | ||
namespace StyleChecker.Test.Refactoring.IsNull | ||
{ | ||
public sealed class Okay | ||
{ | ||
} | ||
} |
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
105 changes: 105 additions & 0 deletions
105
StyleChecker/StyleChecker/Refactoring/IsNull/Analyzer.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,105 @@ | ||
namespace StyleChecker.Refactoring.IsNull | ||
{ | ||
using System.Collections.Immutable; | ||
using System.Linq; | ||
using Microsoft.CodeAnalysis; | ||
using Microsoft.CodeAnalysis.CSharp; | ||
using Microsoft.CodeAnalysis.CSharp.Syntax; | ||
using Microsoft.CodeAnalysis.Diagnostics; | ||
using Microsoft.CodeAnalysis.Operations; | ||
using StyleChecker.Refactoring; | ||
using R = Resources; | ||
|
||
/// <summary> | ||
/// IsNull analyzer. | ||
/// </summary> | ||
[DiagnosticAnalyzer(LanguageNames.CSharp)] | ||
public sealed class Analyzer : DiagnosticAnalyzer | ||
{ | ||
/// <summary> | ||
/// The ID of this analyzer. | ||
/// </summary> | ||
public const string DiagnosticId = "IsNull"; | ||
|
||
private const string Category = Categories.Cleaning; | ||
private static readonly DiagnosticDescriptor Rule = NewRule(); | ||
|
||
/// <inheritdoc/> | ||
public override ImmutableArray<DiagnosticDescriptor> | ||
SupportedDiagnostics => ImmutableArray.Create(Rule); | ||
|
||
/// <inheritdoc/> | ||
public override void Initialize(AnalysisContext context) | ||
{ | ||
context.ConfigureGeneratedCodeAnalysis( | ||
GeneratedCodeAnalysisFlags.None); | ||
context.EnableConcurrentExecution(); | ||
context.RegisterSemanticModelAction(AnalyzeModel); | ||
} | ||
|
||
private static DiagnosticDescriptor NewRule() | ||
{ | ||
var localize = Localizers.Of<R>(R.ResourceManager); | ||
return new DiagnosticDescriptor( | ||
DiagnosticId, | ||
localize(nameof(R.Title)), | ||
localize(nameof(R.MessageFormat)), | ||
Category, | ||
DiagnosticSeverity.Info, | ||
isEnabledByDefault: true, | ||
description: localize(nameof(R.Description)), | ||
helpLinkUri: HelpLink.ToUri(DiagnosticId)); | ||
} | ||
|
||
private static void AnalyzeModel( | ||
SemanticModelAnalysisContext context) | ||
{ | ||
bool IsNullConstant(Optional<object> v) | ||
=> v.HasValue && v.Value == null; | ||
|
||
bool IsNullLiteral(IOperation o) | ||
=> (o.IsImplicit && o is IConversionOperation conversion) | ||
? IsNullLiteral(conversion.Operand) | ||
: (o is ILiteralOperation literal | ||
&& IsNullConstant(literal.ConstantValue)); | ||
|
||
bool IsNull(IPatternOperation o) | ||
=> o is IConstantPatternOperation constantPattern | ||
&& IsNullLiteral(constantPattern.Value); | ||
|
||
bool Matches(IIsPatternOperation o) | ||
=> IsNull(o.Pattern); | ||
|
||
var root = context.GetCompilationUnitRoot(); | ||
var model = context.SemanticModel; | ||
var all = root.DescendantNodes() | ||
.OfType<IsPatternExpressionSyntax>() | ||
.Select(n => model.GetOperation(n)) | ||
.OfType<IIsPatternOperation>() | ||
.Where(Matches); | ||
|
||
foreach (var o in all) | ||
{ | ||
if (!(o.Syntax is IsPatternExpressionSyntax node)) | ||
{ | ||
continue; | ||
} | ||
|
||
Diagnostic Of(SyntaxNode n, SyntaxKind k) | ||
=> Diagnostic.Create( | ||
Rule, | ||
n.GetLocation(), | ||
SyntaxFactory.Token(k)); | ||
|
||
var diagnostic | ||
= (node.Parent is ParenthesizedExpressionSyntax paren | ||
&& paren.Parent is PrefixUnaryExpressionSyntax prefix | ||
&& prefix.OperatorToken.Kind() | ||
== SyntaxKind.ExclamationToken) | ||
? Of(prefix, SyntaxKind.ExclamationEqualsToken) | ||
: Of(node, SyntaxKind.EqualsEqualsToken); | ||
context.ReportDiagnostic(diagnostic); | ||
} | ||
} | ||
} | ||
} |
Oops, something went wrong.