- Notifications
You must be signed in to change notification settings - Fork481
Add DoNotPassStructToArgumentNullExceptionThrowIfNullAnalyzer#6815
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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.
Already on GitHub?Sign in to your account
Uh oh!
There was an error while loading.Please reload this page.
Changes fromall commits
a595294d2b1342096abd5dade7bb0ceb7b4171faf817ef3b423986e5aed0b6a6e216bcb3eebbbFile filter
Filter by extension
Conversations
Uh oh!
There was an error while loading.Please reload this page.
Jump to
Uh oh!
There was an error while loading.Please reload this page.
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| // Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. | ||
| using System.Composition; | ||
| using System.Threading; | ||
| using System.Threading.Tasks; | ||
| using Microsoft.CodeAnalysis; | ||
| using Microsoft.CodeAnalysis.CodeFixes; | ||
| using Microsoft.CodeAnalysis.CSharp.Syntax; | ||
| using Microsoft.CodeAnalysis.Editing; | ||
| using Microsoft.NetCore.Analyzers.Usage; | ||
| namespace Microsoft.NetCore.CSharp.Analyzers.Usage | ||
| { | ||
| [ExportCodeFixProvider(LanguageNames.CSharp), Shared] | ||
| public sealed class CSharpDoNotPassNonNullableValueToArgumentNullExceptionThrowIfNullFixer : DoNotPassNonNullableValueToArgumentNullExceptionThrowIfNullFixer<InvocationExpressionSyntax> | ||
| { | ||
| protected override async Task<SyntaxNode> GetNewRootForNullableStructAsync(Document document, InvocationExpressionSyntax invocation, CancellationToken cancellationToken) | ||
| { | ||
| var editor = await DocumentEditor.CreateAsync(document, cancellationToken).ConfigureAwait(false); | ||
| var generator = editor.Generator; | ||
| var nullableStructExpression = invocation.ArgumentList.Arguments[0].Expression; | ||
| var condition = generator.LogicalNotExpression(generator.MemberAccessExpression(nullableStructExpression, HasValue)); | ||
| var nameOfExpression = generator.NameOfExpression(nullableStructExpression); | ||
| var argumentNullException = generator.ObjectCreationExpression(generator.IdentifierName(ArgumentNullException), nameOfExpression); | ||
| var throwExpression = generator.ThrowStatement(argumentNullException); | ||
| var ifStatement = editor.Generator.IfStatement(condition, new[] { throwExpression }); | ||
| if (invocation.Parent is not null) | ||
| { | ||
| editor.ReplaceNode(invocation.Parent, ifStatement); | ||
| } | ||
| return editor.GetChangedRoot(); | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,74 @@ | ||
| // Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. | ||
| using System; | ||
| using System.Collections.Immutable; | ||
| using System.Threading; | ||
| using System.Threading.Tasks; | ||
| using Analyzer.Utilities; | ||
| using Microsoft.CodeAnalysis; | ||
| using Microsoft.CodeAnalysis.CodeActions; | ||
| using Microsoft.CodeAnalysis.CodeFixes; | ||
| namespace Microsoft.NetCore.Analyzers.Usage | ||
| { | ||
| public abstract class DoNotPassNonNullableValueToArgumentNullExceptionThrowIfNullFixer<TInvocationExpression> : CodeFixProvider | ||
| where TInvocationExpression : SyntaxNode | ||
| { | ||
| protected const string HasValue = nameof(Nullable<int>.HasValue); | ||
| protected const string ArgumentNullException = nameof(System.ArgumentNullException); | ||
| public override async Task RegisterCodeFixesAsync(CodeFixContext context) | ||
| { | ||
| foreach (var diagnostic in context.Diagnostics) | ||
| { | ||
| var root = await context.Document.GetRequiredSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); | ||
| if (root.FindNode(context.Span, getInnermostNodeForTie: true) is not TInvocationExpression invocation) | ||
| { | ||
| continue; | ||
| } | ||
| if (diagnostic.Id == DoNotPassNonNullableValueToArgumentNullExceptionThrowIfNull.NonNullableValueRuleId && invocation.Parent is not null) | ||
| { | ||
| var codeAction = CodeAction.Create( | ||
| MicrosoftNetCoreAnalyzersResources.DoNotPassNonNullableValueToArgumentNullExceptionThrowIfNullCodeFixTitle, | ||
| _ => | ||
| { | ||
| var newRoot = root.RemoveNode(invocation.Parent, SyntaxRemoveOptions.KeepNoTrivia); | ||
| if (newRoot is null) | ||
| { | ||
| return Task.FromResult(context.Document); | ||
| } | ||
| return Task.FromResult(context.Document.WithSyntaxRoot(newRoot)); | ||
| }, MicrosoftNetCoreAnalyzersResources.DoNotPassNonNullableValueToArgumentNullExceptionThrowIfNullCodeFixTitle); | ||
| context.RegisterCodeFix(codeAction, diagnostic); | ||
| } | ||
| else if (diagnostic.Id == DoNotPassNonNullableValueToArgumentNullExceptionThrowIfNull.NullableStructRuleId) | ||
| { | ||
| var codeAction = CodeAction.Create( | ||
| MicrosoftNetCoreAnalyzersResources.DoNotPassNullableStructToArgumentNullExceptionThrowIfNullCodeFixTitle, | ||
| async ct => | ||
| { | ||
| var newRoot = await GetNewRootForNullableStructAsync(context.Document, invocation, ct).ConfigureAwait(false); | ||
| if (newRoot is null) | ||
| { | ||
| return context.Document; | ||
| } | ||
| return context.Document.WithSyntaxRoot(newRoot); | ||
| }, MicrosoftNetCoreAnalyzersResources.DoNotPassNullableStructToArgumentNullExceptionThrowIfNullCodeFixTitle); | ||
| context.RegisterCodeFix(codeAction, diagnostic); | ||
| } | ||
| } | ||
| } | ||
| protected abstract Task<SyntaxNode> GetNewRootForNullableStructAsync(Document document, TInvocationExpression invocation, CancellationToken cancellationToken); | ||
| public override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; | ||
| public override ImmutableArray<string> FixableDiagnosticIds { get; } = ImmutableArray.Create( | ||
| DoNotPassNonNullableValueToArgumentNullExceptionThrowIfNull.NonNullableValueRuleId, | ||
| DoNotPassNonNullableValueToArgumentNullExceptionThrowIfNull.NullableStructRuleId | ||
| ); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,80 @@ | ||
| // Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. | ||
| using System.Collections.Immutable; | ||
| using System.Linq; | ||
| using Analyzer.Utilities; | ||
| using Analyzer.Utilities.Extensions; | ||
| using Microsoft.CodeAnalysis; | ||
| using Microsoft.CodeAnalysis.Diagnostics; | ||
| using Microsoft.CodeAnalysis.Operations; | ||
| using static Microsoft.NetCore.Analyzers.MicrosoftNetCoreAnalyzersResources; | ||
| namespace Microsoft.NetCore.Analyzers.Usage | ||
| { | ||
| [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)] | ||
| public sealed class DoNotPassNonNullableValueToArgumentNullExceptionThrowIfNull : DiagnosticAnalyzer | ||
| { | ||
| internal const string NonNullableValueRuleId = "CA2264"; | ||
| internal const string NullableStructRuleId = "CA1871"; | ||
| internal static readonly DiagnosticDescriptor DoNotPassNonNullableValueDiagnostic = DiagnosticDescriptorHelper.Create( | ||
| NonNullableValueRuleId, | ||
| CreateLocalizableResourceString(nameof(DoNotPassNonNullableValueToArgumentNullExceptionThrowIfNullTitle)), | ||
| CreateLocalizableResourceString(nameof(DoNotPassNonNullableValueToArgumentNullExceptionThrowIfNullMessage)), | ||
| DiagnosticCategory.Usage, | ||
| RuleLevel.BuildWarning, | ||
| CreateLocalizableResourceString(nameof(DoNotPassNonNullableValueToArgumentNullExceptionThrowIfNullDescription)), | ||
| isPortedFxCopRule: false, | ||
| isDataflowRule: false); | ||
| internal static readonly DiagnosticDescriptor DoNotPassNullableStructDiagnostic = DiagnosticDescriptorHelper.Create( | ||
| NullableStructRuleId, | ||
| CreateLocalizableResourceString(nameof(DoNotPassNullableStructToArgumentNullExceptionThrowIfNullTitle)), | ||
| CreateLocalizableResourceString(nameof(DoNotPassNullableStructToArgumentNullExceptionThrowIfNullMessage)), | ||
| DiagnosticCategory.Performance, | ||
| RuleLevel.IdeSuggestion, | ||
| CreateLocalizableResourceString(nameof(DoNotPassNullableStructToArgumentNullExceptionThrowIfNullDescription)), | ||
| isPortedFxCopRule: false, | ||
| isDataflowRule: false); | ||
| public override void Initialize(AnalysisContext context) | ||
| { | ||
| context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); | ||
| context.EnableConcurrentExecution(); | ||
| context.RegisterCompilationStartAction(static context => | ||
| { | ||
| var typeProvider = WellKnownTypeProvider.GetOrCreate(context.Compilation); | ||
| var throwIfNullMethod = typeProvider.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemArgumentNullException) | ||
| ?.GetMembers("ThrowIfNull") | ||
buyaa-n marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
| .OfType<IMethodSymbol>() | ||
| .FirstOrDefault(m => m.Parameters is [{ Type.SpecialType: SpecialType.System_Object }, _]); | ||
| if (throwIfNullMethod is null) | ||
| { | ||
| return; | ||
| } | ||
| context.RegisterOperationAction(ctx => AnalyzeInvocation(ctx, throwIfNullMethod), OperationKind.Invocation); | ||
| }); | ||
| } | ||
| private static void AnalyzeInvocation(OperationAnalysisContext context, IMethodSymbol throwIfNullMethod) | ||
| { | ||
| var invocation = (IInvocationOperation)context.Operation; | ||
| if (invocation.TargetMethod.Equals(throwIfNullMethod, SymbolEqualityComparer.Default)) | ||
| { | ||
| if (invocation.Arguments[0].Value.WalkDownConversion().Type.IsNonNullableValueType() | ||
| || invocation.Arguments[0].Value.WalkDownConversion().Kind is OperationKind.NameOf or OperationKind.ObjectCreation or OperationKind.ObjectOrCollectionInitializer) | ||
| { | ||
| context.ReportDiagnostic(invocation.CreateDiagnostic(DoNotPassNonNullableValueDiagnostic)); | ||
| } | ||
| if (invocation.Arguments[0].Value.WalkDownConversion().Type.IsNullableValueType()) | ||
| { | ||
| context.ReportDiagnostic(invocation.CreateDiagnostic(DoNotPassNullableStructDiagnostic)); | ||
| } | ||
| } | ||
| } | ||
| public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } = ImmutableArray.Create(DoNotPassNonNullableValueDiagnostic, DoNotPassNullableStructDiagnostic); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -858,6 +858,46 @@ Obecné přetypování (IL unbox.any) používané sekvencí vrácenou metodou E | ||
| <target state="translated">Nepředávejte literály jako lokalizované parametry</target> | ||
| <note /> | ||
| </trans-unit> | ||
| <trans-unit id="DoNotPassNonNullableValueToArgumentNullExceptionThrowIfNullCodeFixTitle"> | ||
| <source>Remove superfluous 'ArgumentNullException.ThrowIfNull' call</source> | ||
| <target state="new">Remove superfluous 'ArgumentNullException.ThrowIfNull' call</target> | ||
| <note /> | ||
| </trans-unit> | ||
| <trans-unit id="DoNotPassNonNullableValueToArgumentNullExceptionThrowIfNullDescription"> | ||
| <source>'ArgumentNullException.ThrowIfNull' throws when the passed argument is 'null'. Certain constructs like non-nullable structs, 'nameof()' and 'new' expressions are known to never be null, so 'ArgumentNullException.ThrowIfNull' will never throw.</source> | ||
| <target state="new">'ArgumentNullException.ThrowIfNull' throws when the passed argument is 'null'. Certain constructs like non-nullable structs, 'nameof()' and 'new' expressions are known to never be null, so 'ArgumentNullException.ThrowIfNull' will never throw.</target> | ||
| <note /> | ||
| </trans-unit> | ||
| <trans-unit id="DoNotPassNonNullableValueToArgumentNullExceptionThrowIfNullMessage"> | ||
| <source>Calling 'ArgumentNullException.ThrowIfNull' and passing a non-nullable value is a no-op</source> | ||
| <target state="new">Calling 'ArgumentNullException.ThrowIfNull' and passing a non-nullable value is a no-op</target> | ||
| <note /> | ||
| </trans-unit> | ||
| <trans-unit id="DoNotPassNonNullableValueToArgumentNullExceptionThrowIfNullTitle"> | ||
| <source>Do not pass a non-nullable value to 'ArgumentNullException.ThrowIfNull'</source> | ||
| <target state="new">Do not pass a non-nullable value to 'ArgumentNullException.ThrowIfNull'</target> | ||
| <note /> | ||
| </trans-unit> | ||
| <trans-unit id="DoNotPassNullableStructToArgumentNullExceptionThrowIfNullCodeFixTitle"> | ||
| <source>Replace the 'ArgumentNullException.ThrowIfNull' call with a conditional</source> | ||
| <target state="new">Replace the 'ArgumentNullException.ThrowIfNull' call with a conditional</target> | ||
| <note /> | ||
| </trans-unit> | ||
| <trans-unit id="DoNotPassNullableStructToArgumentNullExceptionThrowIfNullDescription"> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. ❓ Do we really need this? SharpLab suggests that release builds will not box a non-null value in this case. Author There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. Can you share an example? I can see the | ||
| <source>'ArgumentNullException.ThrowIfNull' accepts an 'object', so passing a nullable struct may cause the value to be boxed.</source> | ||
| <target state="new">'ArgumentNullException.ThrowIfNull' accepts an 'object', so passing a nullable struct may cause the value to be boxed.</target> | ||
| <note /> | ||
| </trans-unit> | ||
| <trans-unit id="DoNotPassNullableStructToArgumentNullExceptionThrowIfNullMessage"> | ||
| <source>Passing a nullable struct to 'ArgumentNullException.ThrowIfNull' may cause boxing</source> | ||
| <target state="new">Passing a nullable struct to 'ArgumentNullException.ThrowIfNull' may cause boxing</target> | ||
| <note /> | ||
| </trans-unit> | ||
| <trans-unit id="DoNotPassNullableStructToArgumentNullExceptionThrowIfNullTitle"> | ||
| <source>Do not pass a nullable struct to 'ArgumentNullException.ThrowIfNull'</source> | ||
| <target state="new">Do not pass a nullable struct to 'ArgumentNullException.ThrowIfNull'</target> | ||
| <note /> | ||
| </trans-unit> | ||
| <trans-unit id="DoNotRaiseReservedExceptionTypesDescription"> | ||
| <source>An exception of type that is not sufficiently specific or reserved by the runtime should never be raised by user code. This makes the original error difficult to detect and debug. If this exception instance might be thrown, use a different exception type.</source> | ||
| <target state="translated">Uživatelský kód by nikdy neměl vyvolat výjimku typu, který není dostatečně konkrétní nebo je rezervovaný modulem runtime. V takovém případě je totiž obtížné zjistit a ladit původní chybu. Pokud může dojít k vyvolání této instance výjimky, použijte jiný typ výjimky.</target> | ||
Uh oh!
There was an error while loading.Please reload this page.