Skip to content

Commit

Permalink
Recognize ArgumentNullException.ThrowIfNull (RCS1227) (dotnet#992)
Browse files Browse the repository at this point in the history
  • Loading branch information
josefpihrt authored and JochemHarmes committed Oct 30, 2023
1 parent a77032c commit ca934d7
Show file tree
Hide file tree
Showing 4 changed files with 56 additions and 32 deletions.
1 change: 1 addition & 0 deletions ChangeLog.md
Expand Up @@ -21,6 +21,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Fix retrieving of trusted platform assemblies - separator differs by OS ([#987](https://github.com/josefpihrt/roslynator/pull/987)).
- Fix refactoring ([RR0014](https://github.com/JosefPihrt/Roslynator/blob/main/docs/analyzers/RR0014.md)) ([#988](https://github.com/josefpihrt/roslynator/pull/988)).
- Fix refactoring ([RR0180](https://github.com/JosefPihrt/Roslynator/blob/main/docs/analyzers/RR0180.md)) ([#988](https://github.com/josefpihrt/roslynator/pull/988)).
- Recognize `ArgumentNullException.ThrowIfNull` [RCS1227](https://github.com/JosefPihrt/Roslynator/blob/main/docs/analyzers/RCS1227.md) ([#992](https://github.com/josefpihrt/roslynator/pull/992).

## [4.1.2] - 2022-10-31

Expand Down
Expand Up @@ -40,16 +40,17 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context)
return;

Diagnostic diagnostic = context.Diagnostics[0];
Document document = context.Document;

CodeAction codeAction = CodeAction.Create(
"Validate arguments correctly",
ct => RefactorAsync(context.Document, statement, ct),
ct => AddLocalFunctionWithIteratorAsync(document, statement, ct),
GetEquivalenceKey(diagnostic));

context.RegisterCodeFix(codeAction, diagnostic);
}

private static async Task<Document> RefactorAsync(
private static async Task<Document> AddLocalFunctionWithIteratorAsync(
Document document,
StatementSyntax statement,
CancellationToken cancellationToken)
Expand Down
Expand Up @@ -57,7 +57,8 @@ private static void AnalyzeMethodDeclaration(SyntaxNodeAnalysisContext context)
int index = -1;
for (int i = 0; i < statementCount; i++)
{
if (ArgumentNullCheckAnalysis.IsArgumentNullCheck(statements[i], context.SemanticModel, context.CancellationToken))
if (IsConditionWithThrow(statements[i])
|| ArgumentNullCheckAnalysis.IsArgumentNullExceptionThrowIfNullCheck(statements[i], context.SemanticModel, context.CancellationToken))
{
index++;
}
Expand Down Expand Up @@ -99,5 +100,12 @@ private static void AnalyzeMethodDeclaration(SyntaxNodeAnalysisContext context)
DiagnosticRules.ValidateArgumentsCorrectly,
Location.Create(body.SyntaxTree, new TextSpan(statements[index + 1].SpanStart, 0)));
}

private static bool IsConditionWithThrow(StatementSyntax statement)
{
return statement is IfStatementSyntax ifStatement
&& ifStatement.IsSimpleIf()
&& ifStatement.SingleNonBlockStatementOrDefault().IsKind(SyntaxKind.ThrowStatement);
}
}
}
72 changes: 43 additions & 29 deletions src/Common/ArgumentNullCheckAnalysis.cs
Expand Up @@ -10,17 +10,14 @@ namespace Roslynator.CSharp
{
internal readonly struct ArgumentNullCheckAnalysis
{
private ArgumentNullCheckAnalysis(ArgumentNullCheckStyle style, string name, bool success)
private ArgumentNullCheckAnalysis(ArgumentNullCheckStyle style, bool success)
{
Style = style;
Name = name;
Success = success;
}

public ArgumentNullCheckStyle Style { get; }

public string Name { get; }

public bool Success { get; }

public static ArgumentNullCheckAnalysis Create(
Expand All @@ -37,12 +34,11 @@ private ArgumentNullCheckAnalysis(ArgumentNullCheckStyle style, string name, boo
string name,
CancellationToken cancellationToken = default)
{
var style = ArgumentNullCheckStyle.None;
string identifier = null;
var success = false;

if (statement is IfStatementSyntax ifStatement)
{
var style = ArgumentNullCheckStyle.None;
var success = false;

if (ifStatement.SingleNonBlockStatementOrDefault() is ThrowStatementSyntax throwStatement
&& throwStatement.Expression is ObjectCreationExpressionSyntax objectCreation)
{
Expand All @@ -56,28 +52,39 @@ private ArgumentNullCheckAnalysis(ArgumentNullCheckStyle style, string name, boo
{
style = ArgumentNullCheckStyle.IfStatement;

if (nullCheck.Expression is IdentifierNameSyntax identifierName)
if (name is null
|| (nullCheck.Expression is IdentifierNameSyntax identifierName
&& string.Equals(name, identifierName.Identifier.ValueText, StringComparison.Ordinal)))
{
identifier = identifierName.Identifier.ValueText;

if (name is null
|| string.Equals(name, identifier, StringComparison.Ordinal))
if (semanticModel
.GetSymbol(objectCreation, cancellationToken)?
.ContainingType?
.HasMetadataName(MetadataNames.System_ArgumentNullException) == true)
{
if (semanticModel
.GetSymbol(objectCreation, cancellationToken)?
.ContainingType?
.HasMetadataName(MetadataNames.System_ArgumentNullException) == true)
{
success = true;
}
success = true;
}
}
}

return new ArgumentNullCheckAnalysis(style, identifier, success);
}

return new ArgumentNullCheckAnalysis(style, success);
}
else
{
return CreateFromArgumentNullExceptionThrowIfNullCheck(statement, semanticModel, name, cancellationToken);
}
else if (statement is ExpressionStatementSyntax expressionStatement)
}

private static ArgumentNullCheckAnalysis CreateFromArgumentNullExceptionThrowIfNullCheck(
StatementSyntax statement,
SemanticModel semanticModel,
string name,
CancellationToken cancellationToken)
{
var style = ArgumentNullCheckStyle.None;
var success = false;

if (statement is ExpressionStatementSyntax expressionStatement)
{
SimpleMemberInvocationStatementInfo invocationInfo = SyntaxInfo.SimpleMemberInvocationStatementInfo(expressionStatement);

Expand All @@ -90,17 +97,24 @@ private ArgumentNullCheckAnalysis(ArgumentNullCheckStyle style, string name, boo
{
style = ArgumentNullCheckStyle.ThrowIfNullMethod;

if (invocationInfo.Arguments.SingleOrDefault(shouldThrow: false)?.Expression is IdentifierNameSyntax identifierName)
if (name is null
|| (invocationInfo.Arguments.SingleOrDefault(shouldThrow: false)?.Expression is IdentifierNameSyntax identifierName
&& string.Equals(name, identifierName.Identifier.ValueText, StringComparison.Ordinal)))
{
identifier = identifierName.Identifier.ValueText;

if (string.Equals(name, identifier, StringComparison.Ordinal))
success = true;
success = true;
}
}
}

return new ArgumentNullCheckAnalysis(style, identifier, success);
return new ArgumentNullCheckAnalysis(style, success);
}

public static bool IsArgumentNullExceptionThrowIfNullCheck(
StatementSyntax statement,
SemanticModel semanticModel,
CancellationToken cancellationToken = default)
{
return CreateFromArgumentNullExceptionThrowIfNullCheck(statement, semanticModel, null, cancellationToken).Success;
}

public static bool IsArgumentNullCheck(
Expand Down

0 comments on commit ca934d7

Please sign in to comment.