diff --git a/src/Meziantou.Analyzer/Rules/ValidateArgumentsCorrectlyAnalyzer.cs b/src/Meziantou.Analyzer/Rules/ValidateArgumentsCorrectlyAnalyzer.cs index 329e079e2..4d5eb2a40 100644 --- a/src/Meziantou.Analyzer/Rules/ValidateArgumentsCorrectlyAnalyzer.cs +++ b/src/Meziantou.Analyzer/Rules/ValidateArgumentsCorrectlyAnalyzer.cs @@ -84,7 +84,7 @@ internal void AnalyzeMethodDeclaration(SyntaxNodeAnalysisContext context) return; var lastThrowIndex = descendants - .Where(node => (node.IsKind(SyntaxKind.ThrowStatement) || node.IsKind(SyntaxKind.ThrowExpression)) && IsArgumentException(context, node)) + .Where(node => IsArgumentValidation(context, node)) .DefaultIfEmpty() .Max(node => GetEndOfBlockIndex(context, node)); @@ -97,6 +97,25 @@ internal void AnalyzeMethodDeclaration(SyntaxNodeAnalysisContext context) } } + private bool IsArgumentValidation(SyntaxNodeAnalysisContext context, SyntaxNode node) + { + if ((node.IsKind(SyntaxKind.ThrowStatement) || node.IsKind(SyntaxKind.ThrowExpression)) && IsArgumentException(context, node)) + return true; + + if (node is InvocationExpressionSyntax invocationExpression) + { + if (context.SemanticModel.GetOperation(invocationExpression, context.CancellationToken) is IInvocationOperation operation) + { + var targetMethod = operation.TargetMethod; + return targetMethod.IsStatic && + targetMethod.ContainingType.IsOrInheritFrom(_argumentExceptionSymbol) && + targetMethod.Name.Contains("Throw", System.StringComparison.Ordinal); + } + } + + return false; + } + public bool IsArgumentException(SyntaxNodeAnalysisContext context, SyntaxNode syntaxNode) { var exceptionExpression = syntaxNode switch diff --git a/tests/Meziantou.Analyzer.Test/Rules/ValidateArgumentsCorrectlyAnalyzerTests.cs b/tests/Meziantou.Analyzer.Test/Rules/ValidateArgumentsCorrectlyAnalyzerTests.cs index 3c17b061a..8e513c5bf 100644 --- a/tests/Meziantou.Analyzer.Test/Rules/ValidateArgumentsCorrectlyAnalyzerTests.cs +++ b/tests/Meziantou.Analyzer.Test/Rules/ValidateArgumentsCorrectlyAnalyzerTests.cs @@ -193,6 +193,35 @@ await CreateProjectBuilder() .ValidateAsync(); } + [Fact] + public async Task ValidValidation_ThrowIfNull() + { + const string SourceCode = @"using System.Collections.Generic; +class TypeName +{ + IEnumerable A(string a) + { + System.ArgumentNullException.ThrowIfNull(a); + + return A(); + + IEnumerable A() + { + yield return 0; + if (a == null) + { + yield return 1; + } + } + } +}"; + + await CreateProjectBuilder() + .WithSourceCode(SourceCode) + .WithTargetFramework(TargetFramework.Net8_0) + .ValidateAsync(); + } + [Fact] public async Task ReportDiagnostic_IAsyncEnumerable() { @@ -235,4 +264,43 @@ await CreateProjectBuilder() .ValidateAsync(); } + [Fact] + public async Task ReportDiagnostic_IAsyncEnumerable_ThrowIfNull() + { + const string SourceCode = @"using System.Collections.Generic; +class TypeName +{ + async IAsyncEnumerable [||]A(string a) + { + System.ArgumentNullException.ThrowIfNull(a); + + await System.Threading.Tasks.Task.Delay(1); + yield return 0; + + } +}"; + + const string CodeFix = @"using System.Collections.Generic; +class TypeName +{ + IAsyncEnumerable A(string a) + { + System.ArgumentNullException.ThrowIfNull(a); + + return A(); + + async IAsyncEnumerable A() + { + await System.Threading.Tasks.Task.Delay(1); + yield return 0; + } + } +}"; + + await CreateProjectBuilder() + .WithTargetFramework(TargetFramework.Net8_0) + .WithSourceCode(SourceCode) + .ShouldFixCodeWith(CodeFix) + .ValidateAsync(); + } }