Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Recognize ArgumentNullException.ThrowIfNull (RCS1227) #992

Merged
merged 5 commits into from Nov 18, 2022
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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