diff --git a/ChangeLog.md b/ChangeLog.md index d593683915..f469b88c58 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -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 diff --git a/src/Analyzers.CodeFixes/CSharp/CodeFixes/ValidateArgumentsCorrectlyCodeFixProvider.cs b/src/Analyzers.CodeFixes/CSharp/CodeFixes/ValidateArgumentsCorrectlyCodeFixProvider.cs index 1146c922ad..7af3a79820 100644 --- a/src/Analyzers.CodeFixes/CSharp/CodeFixes/ValidateArgumentsCorrectlyCodeFixProvider.cs +++ b/src/Analyzers.CodeFixes/CSharp/CodeFixes/ValidateArgumentsCorrectlyCodeFixProvider.cs @@ -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 RefactorAsync( + private static async Task AddLocalFunctionWithIteratorAsync( Document document, StatementSyntax statement, CancellationToken cancellationToken) diff --git a/src/Analyzers/CSharp/Analysis/ValidateArgumentsCorrectlyAnalyzer.cs b/src/Analyzers/CSharp/Analysis/ValidateArgumentsCorrectlyAnalyzer.cs index f5b4ee9006..5dff36f1f4 100644 --- a/src/Analyzers/CSharp/Analysis/ValidateArgumentsCorrectlyAnalyzer.cs +++ b/src/Analyzers/CSharp/Analysis/ValidateArgumentsCorrectlyAnalyzer.cs @@ -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++; } @@ -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); + } } } diff --git a/src/Common/ArgumentNullCheckAnalysis.cs b/src/Common/ArgumentNullCheckAnalysis.cs index 845772f4b2..f187f0c8b0 100644 --- a/src/Common/ArgumentNullCheckAnalysis.cs +++ b/src/Common/ArgumentNullCheckAnalysis.cs @@ -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( @@ -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) { @@ -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); @@ -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(