diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f45972fde..45b218c9f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -56,6 +56,7 @@ jobs: - run: dotnet build src/Meziantou.Analyzer/Meziantou.Analyzer.csproj --configuration Release /p:RoslynVersion=roslyn3.8 /p:Version=${{ needs.compute_package_version.outputs.package_version }} - run: dotnet build src/Meziantou.Analyzer/Meziantou.Analyzer.csproj --configuration Release /p:RoslynVersion=roslyn4.2 /p:Version=${{ needs.compute_package_version.outputs.package_version }} - run: dotnet build src/Meziantou.Analyzer/Meziantou.Analyzer.csproj --configuration Release /p:RoslynVersion=roslyn4.4 /p:Version=${{ needs.compute_package_version.outputs.package_version }} + - run: dotnet build src/Meziantou.Analyzer/Meziantou.Analyzer.csproj --configuration Release /p:RoslynVersion=roslyn4.6 /p:Version=${{ needs.compute_package_version.outputs.package_version }} - run: dotnet restore src/Meziantou.Analyzer.pack.csproj - run: dotnet pack src/Meziantou.Analyzer.pack.csproj --configuration Release --no-build /p:Version=${{ needs.compute_package_version.outputs.package_version }} diff --git a/Directory.Build.props b/Directory.Build.props index 59d5e028e..3521c756b 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -39,7 +39,7 @@ all runtime; build; native; contentfiles; analyzers - + all runtime; build; native; contentfiles; analyzers diff --git a/Directory.Build.targets b/Directory.Build.targets index 496df64db..0c6ebf26d 100644 --- a/Directory.Build.targets +++ b/Directory.Build.targets @@ -42,6 +42,18 @@ + + + + + + + $(DefineConstants);ROSLYN_4_6;ROSLYN_4_2_OR_GREATER;ROSLYN_4_4_OR_GREATER;ROSLYN_4_6_OR_GREATER + $(DefineConstants);CSHARP9_OR_GREATER;CSHARP10_OR_GREATER;CSHARP11_OR_GREATER + $(NoWarn);nullable + + + @@ -49,7 +61,7 @@ $(DefineConstants);ROSLYN4_4;ROSLYN_4_2_OR_GREATER;ROSLYN_4_4_OR_GREATER;ROSLYN_4_5_OR_GREATER;ROSLYN_4_6_OR_GREATER - $(DefineConstants);CSHARP9_OR_GREATER;CSHARP10_OR_GREATER;CSHARP11_OR_GREATER + $(DefineConstants);CSHARP9_OR_GREATER;CSHARP10_OR_GREATER;CSHARP11_OR_GREATER;CSHARP12_OR_GREATER $(NoWarn);CS0618 diff --git a/src/Meziantou.Analyzer.pack.csproj b/src/Meziantou.Analyzer.pack.csproj index 36dd02b59..f9fcda286 100644 --- a/src/Meziantou.Analyzer.pack.csproj +++ b/src/Meziantou.Analyzer.pack.csproj @@ -19,5 +19,6 @@ + \ No newline at end of file diff --git a/src/Meziantou.Analyzer/Internals/OperationExtensions.cs b/src/Meziantou.Analyzer/Internals/OperationExtensions.cs index d18295d1e..a116101bc 100644 --- a/src/Meziantou.Analyzer/Internals/OperationExtensions.cs +++ b/src/Meziantou.Analyzer/Internals/OperationExtensions.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Linq; +using System.Runtime.CompilerServices; using System.Threading; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; @@ -98,7 +99,7 @@ public static IOperation UnwrapImplicitConversionOperations(this IOperation oper return operation; } - + public static IOperation UnwrapConversionOperations(this IOperation operation) { if (operation is IConversionOperation conversionOperation) @@ -133,4 +134,111 @@ public static bool HasArgumentOfType(this IInvocationOperation operation, ITypeS return null; } + + public static bool IsInStaticContext(this IOperation operation, CancellationToken cancellationToken) => IsInStaticContext(operation, cancellationToken, out _); + public static bool IsInStaticContext(this IOperation operation, CancellationToken cancellationToken, out int parentStaticMemberStartPosition) + { + // Local functions can be nested, and an instance local function can be declared + // in a static local function. So, you need to continue to check ancestors when a + // local function is not static. + foreach (var member in operation.Syntax.Ancestors()) + { + if (member is LocalFunctionStatementSyntax localFunction) + { + var symbol = operation.SemanticModel!.GetDeclaredSymbol(localFunction, cancellationToken); + if (symbol != null && symbol.IsStatic) + { + parentStaticMemberStartPosition = localFunction.GetLocation().SourceSpan.Start; + return true; + } + } + else if (member is LambdaExpressionSyntax lambdaExpression) + { + var symbol = operation.SemanticModel!.GetSymbolInfo(lambdaExpression, cancellationToken).Symbol; + if (symbol != null && symbol.IsStatic) + { + parentStaticMemberStartPosition = lambdaExpression.GetLocation().SourceSpan.Start; + return true; + } + } + else if (member is AnonymousMethodExpressionSyntax anonymousMethod) + { + var symbol = operation.SemanticModel!.GetSymbolInfo(anonymousMethod, cancellationToken).Symbol; + if (symbol != null && symbol.IsStatic) + { + parentStaticMemberStartPosition = anonymousMethod.GetLocation().SourceSpan.Start; + return true; + } + } + else if (member is MethodDeclarationSyntax methodDeclaration) + { + parentStaticMemberStartPosition = methodDeclaration.GetLocation().SourceSpan.Start; + + var symbol = operation.SemanticModel!.GetDeclaredSymbol(methodDeclaration, cancellationToken); + return symbol != null && symbol.IsStatic; + } + } + + parentStaticMemberStartPosition = -1; + return false; + } + + public static IEnumerable LookupAvailableSymbols(this IOperation operation, CancellationToken cancellationToken) + { + // Find available symbols + var operationLocation = operation.Syntax.GetLocation().SourceSpan.Start; + var isInStaticContext = operation.IsInStaticContext(cancellationToken, out var parentStaticMemberStartPosition); + foreach (var symbol in operation.SemanticModel!.LookupSymbols(operationLocation)) + { + // LookupSymbols check the accessibility of the symbol, but it can + // suggest instance members when the current context is static. + if (symbol is IFieldSymbol field && isInStaticContext && !field.IsStatic) + continue; + + if (symbol is IPropertySymbol { GetMethod: not null } property && isInStaticContext && !property.IsStatic) + continue; + + // Locals can be returned even if there are not valid in the current context. For instance, + // it can return locals declared after the current location. Or it can return locals that + // should not be accessible in a static local function. + // + // void Sample() + // { + // int local = 0; + // static void LocalFunction() => local; <-- local is invalid here but LookupSymbols suggests it + // } + // + // Parameters from the ancestor methods are also returned even if the operation is in a static local function. + if (symbol.Kind is SymbolKind.Local or SymbolKind.Parameter) + { + var isValid = true; + foreach (var location in symbol.Locations) + { + isValid &= IsValid(location, operationLocation, isInStaticContext ? parentStaticMemberStartPosition : null); + if (!isValid) + break; + } + + if (!isValid) + continue; + + static bool IsValid(Location location, int operationLocation, int? staticContextStart) + { + var localPosition = location.SourceSpan.Start; + + // The local is declared after the current expression + if (localPosition > operationLocation) + return false; + + // The local is declared outside the static local function + if (staticContextStart.HasValue && localPosition < staticContextStart.GetValueOrDefault()) + return false; + + return true; + } + } + + yield return symbol; + } + } } diff --git a/src/Meziantou.Analyzer/Internals/SymbolExtensions.cs b/src/Meziantou.Analyzer/Internals/SymbolExtensions.cs index 5d054acc6..bc4f80aea 100644 --- a/src/Meziantou.Analyzer/Internals/SymbolExtensions.cs +++ b/src/Meziantou.Analyzer/Internals/SymbolExtensions.cs @@ -132,4 +132,16 @@ public static bool IsTopLevelStatementsEntryPointType([NotNullWhen(true)] this I return false; } + + public static ITypeSymbol? GetSymbolType(this ISymbol symbol) + { + return symbol switch + { + IParameterSymbol parameter => parameter.Type, + IFieldSymbol field => field.Type, + IPropertySymbol { GetMethod: not null } property => property.Type, + ILocalSymbol local => local.Type, + _ => null, + }; + } } diff --git a/src/Meziantou.Analyzer/Rules/ArgumentExceptionShouldSpecifyArgumentNameAnalyzer.cs b/src/Meziantou.Analyzer/Rules/ArgumentExceptionShouldSpecifyArgumentNameAnalyzer.cs index 658e5fb6c..b94203777 100644 --- a/src/Meziantou.Analyzer/Rules/ArgumentExceptionShouldSpecifyArgumentNameAnalyzer.cs +++ b/src/Meziantou.Analyzer/Rules/ArgumentExceptionShouldSpecifyArgumentNameAnalyzer.cs @@ -120,128 +120,15 @@ private static void Analyze(OperationAnalysisContext context) private static IEnumerable GetParameterNames(IOperation operation, CancellationToken cancellationToken) { - var semanticModel = operation.SemanticModel!; - var node = operation.Syntax; - while (node != null) + var symbols = operation.LookupAvailableSymbols(cancellationToken); + foreach (var symbol in symbols) { - switch (node) + switch (symbol) { - case AccessorDeclarationSyntax accessor: - if (accessor.IsKind(SyntaxKind.SetAccessorDeclaration)) - { - yield return "value"; - } - + case IParameterSymbol parameterSymbol: + yield return parameterSymbol.Name; break; - - case PropertyDeclarationSyntax _: - yield break; - - case IndexerDeclarationSyntax indexerDeclarationSyntax: - { - var symbol = semanticModel.GetDeclaredSymbol(indexerDeclarationSyntax, cancellationToken); - if (symbol != null) - { - foreach (var parameter in symbol.Parameters) - yield return parameter.Name; - } - - yield break; - } - - case MethodDeclarationSyntax methodDeclaration: - { - var symbol = semanticModel.GetDeclaredSymbol(methodDeclaration, cancellationToken); - if (symbol != null) - { - foreach (var parameter in symbol.Parameters) - yield return parameter.Name; - } - - yield break; - } - - case LocalFunctionStatementSyntax localFunctionStatement: - { - if (semanticModel.GetDeclaredSymbol(localFunctionStatement, cancellationToken) is IMethodSymbol symbol) - { - foreach (var parameter in symbol.Parameters) - yield return parameter.Name; - } - - break; - } - - case ConstructorDeclarationSyntax constructorDeclaration: - { - var symbol = semanticModel.GetDeclaredSymbol(constructorDeclaration, cancellationToken); - if (symbol != null) - { - foreach (var parameter in symbol.Parameters) - yield return parameter.Name; - } - - yield break; - } - - case OperatorDeclarationSyntax operatorDeclaration: - { - var symbol = semanticModel.GetDeclaredSymbol(operatorDeclaration, cancellationToken); - if (symbol != null) - { - foreach (var parameter in symbol.Parameters) - yield return parameter.Name; - } - - yield break; - } - - case ParenthesizedLambdaExpressionSyntax parenthesizedLambdaExpressionSyntax: - { - foreach (var parameter in parenthesizedLambdaExpressionSyntax.ParameterList.Parameters) - { - if (!string.IsNullOrEmpty(parameter.Identifier.ValueText)) - yield return parameter.Identifier.ValueText; - } - - if (parenthesizedLambdaExpressionSyntax.Modifiers.Any(m => m.IsKind(SyntaxKind.StaticKeyword))) - yield break; - - break; - } - - case SimpleLambdaExpressionSyntax lambdaExpressionSyntax: - { - if (!string.IsNullOrEmpty(lambdaExpressionSyntax.Parameter?.Identifier.ValueText)) - { - yield return lambdaExpressionSyntax.Parameter.Identifier.ValueText; - } - - if (lambdaExpressionSyntax.Modifiers.Any(m => m.IsKind(SyntaxKind.StaticKeyword))) - yield break; - - break; - } - - case AnonymousMethodExpressionSyntax anonymousMethodExpressionSyntax: - { - if (anonymousMethodExpressionSyntax.ParameterList != null) - { - foreach (var parameter in anonymousMethodExpressionSyntax.ParameterList.Parameters) - { - if (!string.IsNullOrEmpty(parameter.Identifier.ValueText)) - yield return parameter.Identifier.ValueText; - } - } - - if (anonymousMethodExpressionSyntax.Modifiers.Any(m => m.IsKind(SyntaxKind.StaticKeyword))) - yield break; - - break; - } } - - node = node.Parent; } } } diff --git a/src/Meziantou.Analyzer/Rules/UseAnOverloadThatHasCancellationTokenAnalyzer.cs b/src/Meziantou.Analyzer/Rules/UseAnOverloadThatHasCancellationTokenAnalyzer.cs index a7f7c7e31..b1de52e67 100644 --- a/src/Meziantou.Analyzer/Rules/UseAnOverloadThatHasCancellationTokenAnalyzer.cs +++ b/src/Meziantou.Analyzer/Rules/UseAnOverloadThatHasCancellationTokenAnalyzer.cs @@ -308,57 +308,21 @@ public void AnalyzeLoop(OperationAnalysisContext context) private string[] FindCancellationTokens(IOperation operation, CancellationToken cancellationToken) { - // Find available symbols - var operationLocation = operation.Syntax.GetLocation().SourceSpan.Start; - var isInStaticContext = IsInStaticContext(operation, cancellationToken, out var parentStaticMemberStartPosition); var availableSymbols = new List(); - foreach (var symbol in operation.SemanticModel!.LookupSymbols(operationLocation)) + foreach (var symbol in operation.LookupAvailableSymbols(cancellationToken)) { - // LookupSymbols check the accessibility of the symbol, but it can - // suggest instance members when the current context is static. - var symbolType = symbol switch - { - IParameterSymbol parameter => parameter.Type, - IFieldSymbol field when !isInStaticContext || field.IsStatic => field.Type, - IPropertySymbol { GetMethod: not null } property when !isInStaticContext || property.IsStatic => property.Type, - ILocalSymbol local => local.Type, - _ => null, - }; - + var symbolType = symbol.GetSymbolType(); if (symbolType == null) continue; - // Locals can be returned even if there are not valid in the current context. For instance, - // it can return locals declared after the current location. Or it can return locals that - // should not be accessible in a static local function. - // - // void Sample() - // { - // int local = 0; - // static void LocalFunction() => local; <-- local is invalid here but LookupSymbols suggests it - // } - // - // Parameters from the ancestor methods are also returned even if the operation is in a static local function. - if (symbol.Kind is SymbolKind.Local or SymbolKind.Parameter) - { - var localPosition = symbol.DeclaringSyntaxReferences.FirstOrDefault()?.GetSyntax(cancellationToken).GetLocation().SourceSpan.Start; - - // The local is not part of the source tree - if (localPosition == null) - continue; - - // The local is declared after the current expression - if (localPosition > operationLocation) - continue; - - // The local is declared outside the static local function - if (isInStaticContext && localPosition < parentStaticMemberStartPosition) - continue; - } - availableSymbols.Add(new(symbol.Name, symbolType)); } + if (availableSymbols.Count == 0) + return Array.Empty(); + + var isInStaticContext = operation.IsInStaticContext(cancellationToken); + // For each symbol, get their members var paths = new List(); foreach (var availableSymbol in availableSymbols) @@ -426,53 +390,6 @@ static bool IsSymbolAccessibleFromOperation(ISymbol symbol, IOperation operation return operation.SemanticModel!.GetDeclaredSymbol(ancestor, cancellationToken) as ITypeSymbol; } - private static bool IsInStaticContext(IOperation operation, CancellationToken cancellationToken, out int parentStaticMemberStartPosition) - { - // Local functions can be nested, and an instance local function can be declared - // in a static local function. So, you need to continue to check ancestors when a - // local function is not static. - foreach (var member in operation.Syntax.Ancestors()) - { - if (member is LocalFunctionStatementSyntax localFunction) - { - var symbol = operation.SemanticModel!.GetDeclaredSymbol(localFunction, cancellationToken); - if (symbol != null && symbol.IsStatic) - { - parentStaticMemberStartPosition = localFunction.GetLocation().SourceSpan.Start; - return true; - } - } - else if (member is LambdaExpressionSyntax lambdaExpression) - { - var symbol = operation.SemanticModel!.GetSymbolInfo(lambdaExpression, cancellationToken).Symbol; - if (symbol != null && symbol.IsStatic) - { - parentStaticMemberStartPosition = lambdaExpression.GetLocation().SourceSpan.Start; - return true; - } - } - else if (member is AnonymousMethodExpressionSyntax anonymousMethod) - { - var symbol = operation.SemanticModel!.GetSymbolInfo(anonymousMethod, cancellationToken).Symbol; - if (symbol != null && symbol.IsStatic) - { - parentStaticMemberStartPosition = anonymousMethod.GetLocation().SourceSpan.Start; - return true; - } - } - else if (member is MethodDeclarationSyntax methodDeclaration) - { - parentStaticMemberStartPosition = methodDeclaration.GetLocation().SourceSpan.Start; - - var symbol = operation.SemanticModel!.GetDeclaredSymbol(methodDeclaration, cancellationToken); - return symbol != null && symbol.IsStatic; - } - } - - parentStaticMemberStartPosition = -1; - return false; - } - private static IEnumerable Prepend(T value, IEnumerable items) { yield return value; diff --git a/src/Meziantou.Analyzer/Rules/UseDateTimeUnixEpochAnalyzer.cs b/src/Meziantou.Analyzer/Rules/UseDateTimeUnixEpochAnalyzer.cs index efc77117e..1bdc1f36d 100644 --- a/src/Meziantou.Analyzer/Rules/UseDateTimeUnixEpochAnalyzer.cs +++ b/src/Meziantou.Analyzer/Rules/UseDateTimeUnixEpochAnalyzer.cs @@ -85,7 +85,7 @@ bool IsDateTimeOffsetUnixEpoch() } else if (operation.Arguments.Length == 2) { - if (ArgumentsEquals(operation.Arguments.AsSpan().Slice(0, 1), new object[] { 621355968000000000L }) && IsTimeSpanZero(operation.Arguments[1])) + if (ArgumentsEquals(operation.Arguments.AsSpan(0, 1), new object[] { 621355968000000000L }) && IsTimeSpanZero(operation.Arguments[1])) return true; if (IsUnixEpochProperty(operation.Arguments[0]) && IsTimeSpanZero(operation.Arguments[1])) @@ -93,17 +93,17 @@ bool IsDateTimeOffsetUnixEpoch() } else if (operation.Arguments.Length == 7) { - if (ArgumentsEquals(operation.Arguments.AsSpan().Slice(0, 6), new object[] { 1970, 1, 1, 0, 0, 0 }) && IsTimeSpanZero(operation.Arguments[6])) + if (ArgumentsEquals(operation.Arguments.AsSpan(0, 6), new object[] { 1970, 1, 1, 0, 0, 0 }) && IsTimeSpanZero(operation.Arguments[6])) return true; } else if (operation.Arguments.Length == 8) { - if (ArgumentsEquals(operation.Arguments.AsSpan().Slice(0, 7), new object[] { 1970, 1, 1, 0, 0, 0, 0 }) && IsTimeSpanZero(operation.Arguments[7])) + if (ArgumentsEquals(operation.Arguments.AsSpan(0, 7), new object[] { 1970, 1, 1, 0, 0, 0, 0 }) && IsTimeSpanZero(operation.Arguments[7])) return true; } else if (operation.Arguments.Length == 9) { - if (ArgumentsEquals(operation.Arguments.AsSpan().Slice(0, 8), new object[] { 1970, 1, 1, 0, 0, 0, 0, 0 }) && IsTimeSpanZero(operation.Arguments[8])) + if (ArgumentsEquals(operation.Arguments.AsSpan(0, 8), new object[] { 1970, 1, 1, 0, 0, 0, 0, 0 }) && IsTimeSpanZero(operation.Arguments[8])) return true; } @@ -134,7 +134,7 @@ private static bool IsDateTimeUnixEpoch(IObjectCreationOperation operation, Comp } else if (operation.Arguments.Length == 2) { - if (ArgumentsEquals(operation.Arguments.AsSpan().Slice(0, 1), new object[] { 621355968000000000L }) && IsDateTimeKindUtc(compilation, operation.Arguments[1])) + if (ArgumentsEquals(operation.Arguments.AsSpan(0, 1), new object[] { 621355968000000000L }) && IsDateTimeKindUtc(compilation, operation.Arguments[1])) return true; } else if (operation.Arguments.Length == 3) @@ -149,7 +149,7 @@ private static bool IsDateTimeUnixEpoch(IObjectCreationOperation operation, Comp } else if (operation.Arguments.Length == 7) { - if (ArgumentsEquals(operation.Arguments.AsSpan().Slice(0, 6), new object[] { 1970, 1, 1, 0, 0, 0 }) && IsDateTimeKindUtc(compilation, operation.Arguments[6])) + if (ArgumentsEquals(operation.Arguments.AsSpan(0, 6), new object[] { 1970, 1, 1, 0, 0, 0 }) && IsDateTimeKindUtc(compilation, operation.Arguments[6])) return true; } diff --git a/tests/Meziantou.Analyzer.Test/Rules/ArgumentExceptionShouldSpecifyArgumentNameAnalyzerTests.cs b/tests/Meziantou.Analyzer.Test/Rules/ArgumentExceptionShouldSpecifyArgumentNameAnalyzerTests.cs index 829499cea..721894227 100644 --- a/tests/Meziantou.Analyzer.Test/Rules/ArgumentExceptionShouldSpecifyArgumentNameAnalyzerTests.cs +++ b/tests/Meziantou.Analyzer.Test/Rules/ArgumentExceptionShouldSpecifyArgumentNameAnalyzerTests.cs @@ -13,50 +13,142 @@ private static ProjectBuilder CreateProjectBuilder() .WithAnalyzer(id: "MA0015"); } + // TODO test local function should find parent parameter + [Fact] public async Task ArgumentNameIsSpecified_ShouldNotReportError() { - var sourceCode = @" -class Sample -{ - string Prop + var sourceCode = """ + class Sample + { + void Test(string test) + { + throw new System.Exception(); + throw new System.ArgumentException("message", nameof(test)); + throw new System.ArgumentNullException(nameof(test)); + throw new System.ComponentModel.InvalidEnumArgumentException(nameof(test), 0, typeof(System.Enum)); + } + } + """; + + await CreateProjectBuilder() + .WithSourceCode(sourceCode) + .ValidateAsync(); + } + + [Fact] + public async Task ArgumentNameIsSpecified_LocalFunction_ShouldNotReportError() { - get { throw null; } - set { throw new System.ArgumentNullException(nameof(value)); } + var sourceCode = """ + class Sample + { + void Test(string test) + { + void LocalFunction(string a) + { + throw new System.ArgumentNullException(nameof(a)); + } + } + } + """; + + await CreateProjectBuilder() + .WithSourceCode(sourceCode) + .ValidateAsync(); } - string this[int index] + [Fact] + public async Task ArgumentNameIsSpecified_LocalFunction_ArgumentFromParentMethod_ShouldNotReportError() { - get { throw new System.ArgumentNullException(nameof(index)); } - set { throw new System.ArgumentNullException(nameof(index)); } + var sourceCode = """ + class Sample + { + void Test(string test) + { + void LocalFunction() + { + throw new System.ArgumentNullException(nameof(test)); + } + } + } + """; + + await CreateProjectBuilder() + .WithSourceCode(sourceCode) + .ValidateAsync(); } - Sample(string test) + [Fact] + public async Task ArgumentNameIsSpecified_Operator_ShouldNotReportError() { - throw new System.Exception(); - throw new System.ArgumentException(""message"", nameof(test)); - throw new System.ArgumentNullException(nameof(test)); + var sourceCode = """ + class Sample + { + public static Sample operator +(Sample first, Sample second) + { + throw new System.ArgumentNullException(nameof(first)); + throw new System.ArgumentNullException(nameof(second)); + } + } + """; + + await CreateProjectBuilder() + .WithSourceCode(sourceCode) + .ValidateAsync(); } - void Test(string test) + [Fact] + public async Task ArgumentNameIsSpecified_Method_ShouldNotReportError() { - throw new System.Exception(); - throw new System.ArgumentException(""message"", nameof(test)); - throw new System.ArgumentNullException(nameof(test)); - throw new System.ComponentModel.InvalidEnumArgumentException(nameof(test), 0, typeof(System.Enum)); + var sourceCode = """ + class Sample + { + Sample(string test) + { + throw new System.Exception(); + throw new System.ArgumentException("message", nameof(test)); + throw new System.ArgumentNullException(nameof(test)); + } + } + """; - void LocalFunction(string a) - { - throw new System.ArgumentNullException(nameof(a)); - } + await CreateProjectBuilder() + .WithSourceCode(sourceCode) + .ValidateAsync(); } - public static Sample operator +(Sample first, Sample second) + [Fact] + public async Task ArgumentNameIsSpecified_Indexer_ShouldNotReportError() { - throw new System.ArgumentNullException(nameof(first)); - throw new System.ArgumentNullException(nameof(second)); + var sourceCode = """ + class Sample + { + string this[int index] + { + get { throw new System.ArgumentNullException(nameof(index)); } + set { throw new System.ArgumentNullException(nameof(index)); } + } + } + """; + + await CreateProjectBuilder() + .WithSourceCode(sourceCode) + .ValidateAsync(); } -}"; + + [Fact] + public async Task ArgumentNameIsSpecified_Setter_ShouldNotReportError() + { + var sourceCode = """ + class Sample + { + string Prop + { + get { throw null; } + set { throw new System.ArgumentNullException(nameof(value)); } + } + } + """; await CreateProjectBuilder() .WithSourceCode(sourceCode) @@ -327,4 +419,23 @@ await CreateProjectBuilder() .WithOutputKind(Microsoft.CodeAnalysis.OutputKind.ConsoleApplication) .ValidateAsync(); } + +#if CSHARP12_OR_GREATER + [Fact] + public async Task PrimaryConstructor() + { + const string SourceCode = """" + using System; + + public class Sample(string id) + { + void A() => throw new ArgumentException("", nameof(id)); + } + """"; + await CreateProjectBuilder() + .WithSourceCode(SourceCode) + .WithLanguageVersion(Microsoft.CodeAnalysis.CSharp.LanguageVersion.Preview) + .ValidateAsync(); + } +#endif }