diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 887ee3754..23738d655 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -48,6 +48,62 @@ The analyzer must use `IOperation` or `ISymbol` to analyze the content. Only fal Code snippets in tests must use raw string literals (`"""`) and must be minimized to only include the necessary code to reproduce the issue. Avoid including unnecessary code that does not contribute to the test case. When reporting a diagnostic, the snippet must use the `[|code|]` syntax or `{|id:code|}` syntax. Do not explicitly indicates lines or columns. +### Code fixer best practice: validate before registering + +In `RegisterCodeFixesAsync`, validate **all** conditions that could prevent the fix from being applied **before** calling `context.RegisterCodeFix`. Do not register a code fix whose action would return the document unchanged. + +**Wrong** — registers the fix without validating whether it can be applied: +```csharp +public override async Task RegisterCodeFixesAsync(CodeFixContext context) +{ + var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); + var nodeToFix = root?.FindNode(context.Span, getInnermostNodeForTie: true); + if (nodeToFix is null) + return; + + context.RegisterCodeFix(CodeAction.Create(title, ct => FixAsync(context.Document, nodeToFix, ct), equivalenceKey: title), context.Diagnostics); +} + +private static async Task FixAsync(Document document, SyntaxNode nodeToFix, CancellationToken cancellationToken) +{ + if (nodeToFix is not BinaryExpressionSyntax binaryExpression) + return document; // Fix not applied — but it was already shown to the user! + + var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); + var mySymbol = semanticModel!.Compilation.GetBestTypeByMetadataName("System.SomeType"); + if (mySymbol is null) + return document; // Fix not applied — but it was already shown to the user! + // ... +} +``` + +**Correct** — validates all conditions first, then registers the fix: +```csharp +public override async Task RegisterCodeFixesAsync(CodeFixContext context) +{ + var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); + var nodeToFix = root?.FindNode(context.Span, getInnermostNodeForTie: true); + if (nodeToFix is not BinaryExpressionSyntax binaryExpression) + return; + + var semanticModel = await context.Document.GetSemanticModelAsync(context.CancellationToken).ConfigureAwait(false); + if (semanticModel is null) + return; + + if (semanticModel.Compilation.GetBestTypeByMetadataName("System.SomeType") is null) + return; + + context.RegisterCodeFix(CodeAction.Create(title, ct => FixAsync(context.Document, binaryExpression, ct), equivalenceKey: title), context.Diagnostics); +} + +private static async Task FixAsync(Document document, BinaryExpressionSyntax binaryExpression, CancellationToken cancellationToken) +{ + var editor = await DocumentEditor.CreateAsync(document, cancellationToken).ConfigureAwait(false); + // ... fix logic — all preconditions are guaranteed to hold + return editor.GetChangedDocument(); +} +``` + ## Testing with different Roslyn versions This project supports multiple versions of Roslyn to ensure compatibility with different versions of Visual Studio and the .NET SDK. The supported Roslyn versions are configured in `Directory.Build.targets`: diff --git a/src/Meziantou.Analyzer.CodeFixers/Rules/AbstractTypesShouldNotHaveConstructorsFixer.cs b/src/Meziantou.Analyzer.CodeFixers/Rules/AbstractTypesShouldNotHaveConstructorsFixer.cs index 91925b53d..312fe788d 100644 --- a/src/Meziantou.Analyzer.CodeFixers/Rules/AbstractTypesShouldNotHaveConstructorsFixer.cs +++ b/src/Meziantou.Analyzer.CodeFixers/Rules/AbstractTypesShouldNotHaveConstructorsFixer.cs @@ -20,27 +20,22 @@ public sealed class AbstractTypesShouldNotHaveConstructorsFixer : CodeFixProvide public override async Task RegisterCodeFixesAsync(CodeFixContext context) { var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); - var nodeToFix = root?.FindNode(context.Span, getInnermostNodeForTie: true); - if (nodeToFix is null) + if (root?.FindNode(context.Span, getInnermostNodeForTie: true) is not ConstructorDeclarationSyntax ctorSyntax) return; var title = "Make constructor protected"; var codeAction = CodeAction.Create( title, - ct => MakeConstructorProtected(context.Document, nodeToFix, ct), + ct => MakeConstructorProtected(context.Document, ctorSyntax, ct), equivalenceKey: title); context.RegisterCodeFix(codeAction, context.Diagnostics); } - private static async Task MakeConstructorProtected(Document document, SyntaxNode nodeToFix, CancellationToken cancellationToken) + private static async Task MakeConstructorProtected(Document document, ConstructorDeclarationSyntax ctorSyntax, CancellationToken cancellationToken) { var editor = await DocumentEditor.CreateAsync(document, cancellationToken).ConfigureAwait(false); - var ctorSyntax = (ConstructorDeclarationSyntax)nodeToFix; - if (ctorSyntax is null) - return document; - var modifiers = ctorSyntax.Modifiers; foreach (var modifier in modifiers.Where(m => m.IsKind(SyntaxKind.PublicKeyword) || m.IsKind(SyntaxKind.InternalKeyword))) { diff --git a/src/Meziantou.Analyzer.CodeFixers/Rules/AvoidComparisonWithBoolConstantFixer.cs b/src/Meziantou.Analyzer.CodeFixers/Rules/AvoidComparisonWithBoolConstantFixer.cs index 509d89b7e..3fbf2e36a 100644 --- a/src/Meziantou.Analyzer.CodeFixers/Rules/AvoidComparisonWithBoolConstantFixer.cs +++ b/src/Meziantou.Analyzer.CodeFixers/Rules/AvoidComparisonWithBoolConstantFixer.cs @@ -24,7 +24,7 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context) { var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); var nodeToFix = root?.FindNode(context.Span, getInnermostNodeForTie: true); - if (nodeToFix is null) + if (nodeToFix is not BinaryExpressionSyntax) return; var diagnostic = context.Diagnostics[0]; @@ -40,12 +40,6 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context) private static async Task RemoveComparisonWithBoolConstant(Document document, Diagnostic diagnostic, SyntaxNode nodeToFix, CancellationToken cancellationToken) { - if (nodeToFix is not BinaryExpressionSyntax binaryExpressionSyntax) - return document; - - if (binaryExpressionSyntax.Left is null || binaryExpressionSyntax.Right is null) - return document; - var nodeToKeepSpanStart = int.Parse(diagnostic.Properties["NodeToKeepSpanStart"]!, NumberStyles.Integer, CultureInfo.InvariantCulture); var nodeToKeepSpanLength = int.Parse(diagnostic.Properties["NodeToKeepSpanLength"]!, NumberStyles.Integer, CultureInfo.InvariantCulture); var logicalNotOperatorNeeded = bool.Parse(diagnostic.Properties["LogicalNotOperatorNeeded"]!); diff --git a/src/Meziantou.Analyzer.CodeFixers/Rules/AvoidUnusedInternalTypesFixer.cs b/src/Meziantou.Analyzer.CodeFixers/Rules/AvoidUnusedInternalTypesFixer.cs index 1d70fee40..6fde2cbe6 100644 --- a/src/Meziantou.Analyzer.CodeFixers/Rules/AvoidUnusedInternalTypesFixer.cs +++ b/src/Meziantou.Analyzer.CodeFixers/Rules/AvoidUnusedInternalTypesFixer.cs @@ -33,13 +33,19 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context) // Code fix 1: Add DynamicallyAccessedMembers attribute { - var title = "Add DynamicallyAccessedMembers attribute"; - var codeAction = CodeAction.Create( - title, - ct => AddDynamicallyAccessedMembersAttribute(context.Document, typeDeclarationSyntax, ct), - equivalenceKey: title); + var semanticModel = await context.Document.GetSemanticModelAsync(context.CancellationToken).ConfigureAwait(false); + var dynamicallyAccessedMembersAttribute = semanticModel?.Compilation.GetBestTypeByMetadataName("System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute"); + var dynamicallyAccessedMemberTypes = semanticModel?.Compilation.GetBestTypeByMetadataName("System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes"); + if (dynamicallyAccessedMembersAttribute is not null && dynamicallyAccessedMemberTypes is not null) + { + var title = "Add DynamicallyAccessedMembers attribute"; + var codeAction = CodeAction.Create( + title, + ct => AddDynamicallyAccessedMembersAttribute(context.Document, typeDeclarationSyntax, dynamicallyAccessedMembersAttribute, dynamicallyAccessedMemberTypes, ct), + equivalenceKey: title); - context.RegisterCodeFix(codeAction, context.Diagnostics); + context.RegisterCodeFix(codeAction, context.Diagnostics); + } } // Code fix 2: Remove the type @@ -54,18 +60,11 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context) } } - private static async Task AddDynamicallyAccessedMembersAttribute(Document document, TypeDeclarationSyntax typeDeclarationSyntax, CancellationToken cancellationToken) + private static async Task AddDynamicallyAccessedMembersAttribute(Document document, TypeDeclarationSyntax typeDeclarationSyntax, INamedTypeSymbol dynamicallyAccessedMembersAttribute, INamedTypeSymbol dynamicallyAccessedMemberTypes, CancellationToken cancellationToken) { var editor = await DocumentEditor.CreateAsync(document, cancellationToken).ConfigureAwait(false); - var semanticModel = editor.SemanticModel; var generator = editor.Generator; - var dynamicallyAccessedMembersAttribute = semanticModel.Compilation.GetBestTypeByMetadataName("System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute"); - var dynamicallyAccessedMemberTypes = semanticModel.Compilation.GetBestTypeByMetadataName("System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes"); - - if (dynamicallyAccessedMembersAttribute is null || dynamicallyAccessedMemberTypes is null) - return document; - var attribute = generator.Attribute( generator.TypeExpression(dynamicallyAccessedMembersAttribute, addImport: true), [ diff --git a/src/Meziantou.Analyzer.CodeFixers/Rules/DoNotRemoveOriginalExceptionFromThrowStatementFixer.cs b/src/Meziantou.Analyzer.CodeFixers/Rules/DoNotRemoveOriginalExceptionFromThrowStatementFixer.cs index 30a2d196e..2053b847f 100644 --- a/src/Meziantou.Analyzer.CodeFixers/Rules/DoNotRemoveOriginalExceptionFromThrowStatementFixer.cs +++ b/src/Meziantou.Analyzer.CodeFixers/Rules/DoNotRemoveOriginalExceptionFromThrowStatementFixer.cs @@ -20,27 +20,23 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context) { var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); var nodeToFix = root?.FindNode(context.Span, getInnermostNodeForTie: true); - if (nodeToFix is null) + if (nodeToFix is not ThrowStatementSyntax throwStatement) return; var title = "Throw original exception"; var codeAction = CodeAction.Create( title, - ct => Fix(context.Document, nodeToFix, ct), + ct => Fix(context.Document, throwStatement, ct), equivalenceKey: title); context.RegisterCodeFix(codeAction, context.Diagnostics); } - private static async Task Fix(Document document, SyntaxNode nodeToFix, CancellationToken cancellationToken) + private static async Task Fix(Document document, ThrowStatementSyntax throwStatement, CancellationToken cancellationToken) { var editor = await DocumentEditor.CreateAsync(document, cancellationToken).ConfigureAwait(false); - var syntax = (ThrowStatementSyntax)nodeToFix; - if (syntax is null) - return document; - - editor.ReplaceNode(syntax, syntax.WithExpression(null).WithAdditionalAnnotations(Formatter.Annotation)); + editor.ReplaceNode(throwStatement, throwStatement.WithExpression(null).WithAdditionalAnnotations(Formatter.Annotation)); return editor.GetChangedDocument(); } } diff --git a/src/Meziantou.Analyzer.CodeFixers/Rules/DoNotUseBlockingCallInAsyncContextFixer.cs b/src/Meziantou.Analyzer.CodeFixers/Rules/DoNotUseBlockingCallInAsyncContextFixer.cs index 03b5b6ee8..1cbfb24c5 100644 --- a/src/Meziantou.Analyzer.CodeFixers/Rules/DoNotUseBlockingCallInAsyncContextFixer.cs +++ b/src/Meziantou.Analyzer.CodeFixers/Rules/DoNotUseBlockingCallInAsyncContextFixer.cs @@ -32,9 +32,14 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context) { case DoNotUseBlockingCallInAsyncContextData.Thread_Sleep: { + var sm = await context.Document.GetSemanticModelAsync(context.CancellationToken).ConfigureAwait(false); + var taskSymbol = sm?.Compilation.GetBestTypeByMetadataName("System.Threading.Tasks.Task"); + if (taskSymbol is null) + break; + var codeAction = CodeAction.Create( "Use Task.Delay", - ct => UseTaskDelay(context.Document, nodeToFix, ct), + ct => UseTaskDelay(context.Document, nodeToFix, taskSymbol, ct), equivalenceKey: "Thread_Sleep"); context.RegisterCodeFix(codeAction, context.Diagnostics); @@ -43,6 +48,10 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context) case DoNotUseBlockingCallInAsyncContextData.Task_Wait: { + if (nodeToFix is not InvocationExpressionSyntax taskWaitInvocation || + (taskWaitInvocation.Expression as MemberAccessExpressionSyntax)?.Expression is null) + break; + var codeAction = CodeAction.Create( "Use await", ct => ReplaceTaskWaitWithAwait(context.Document, nodeToFix, ct), @@ -140,9 +149,6 @@ private static async Task ReplaceTaskResultWithAwait(Document document var generator = editor.Generator; var expr = ((MemberAccessExpressionSyntax)nodeToFix).Expression; - if (expr is null) - return document; - var newExpression = generator.AwaitExpression(expr).Parentheses(); editor.ReplaceNode(nodeToFix, newExpression); @@ -155,25 +161,18 @@ private static async Task ReplaceTaskWaitWithAwait(Document document, var generator = editor.Generator; var invocation = (InvocationExpressionSyntax)nodeToFix; - var expr = (invocation.Expression as MemberAccessExpressionSyntax)?.Expression; - if (expr is null) - return document; - + var expr = (invocation.Expression as MemberAccessExpressionSyntax)!.Expression; var newExpression = generator.AwaitExpression(expr).Parentheses(); editor.ReplaceNode(nodeToFix, newExpression); return editor.GetChangedDocument(); } - private static async Task UseTaskDelay(Document document, SyntaxNode nodeToFix, CancellationToken cancellationToken) + private static async Task UseTaskDelay(Document document, SyntaxNode nodeToFix, INamedTypeSymbol taskSymbol, CancellationToken cancellationToken) { var editor = await DocumentEditor.CreateAsync(document, cancellationToken).ConfigureAwait(false); var generator = editor.Generator; - var taskSymbol = editor.SemanticModel.Compilation.GetBestTypeByMetadataName("System.Threading.Tasks.Task"); - if (taskSymbol is null) - return document; - var invocation = (InvocationExpressionSyntax)nodeToFix; var delay = invocation.ArgumentList.Arguments[0].Expression; diff --git a/src/Meziantou.Analyzer.CodeFixers/Rules/DoNotUseEqualityComparerDefaultOfStringFixer.cs b/src/Meziantou.Analyzer.CodeFixers/Rules/DoNotUseEqualityComparerDefaultOfStringFixer.cs index c5dd8ed3b..5998381bb 100644 --- a/src/Meziantou.Analyzer.CodeFixers/Rules/DoNotUseEqualityComparerDefaultOfStringFixer.cs +++ b/src/Meziantou.Analyzer.CodeFixers/Rules/DoNotUseEqualityComparerDefaultOfStringFixer.cs @@ -23,6 +23,14 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context) if (nodeToFix is null) return; + var semanticModel = await context.Document.GetSemanticModelAsync(context.CancellationToken).ConfigureAwait(false); + if (semanticModel is null) + return; + + var stringComparerSymbol = semanticModel.Compilation.GetBestTypeByMetadataName("System.StringComparer"); + if (stringComparerSymbol is null) + return; + RegisterCodeFix(nameof(StringComparer.Ordinal)); RegisterCodeFix(nameof(StringComparer.OrdinalIgnoreCase)); @@ -31,25 +39,20 @@ void RegisterCodeFix(string comparerName) var title = "Use StringComparer." + comparerName; var codeAction = CodeAction.Create( title, - ct => MakeConstructorProtected(context.Document, nodeToFix, comparerName, ct), + ct => MakeConstructorProtected(context.Document, nodeToFix, comparerName, stringComparerSymbol, ct), equivalenceKey: title); context.RegisterCodeFix(codeAction, context.Diagnostics); } } - private static async Task MakeConstructorProtected(Document document, SyntaxNode nodeToFix, string comparerName, CancellationToken cancellationToken) + private static async Task MakeConstructorProtected(Document document, SyntaxNode nodeToFix, string comparerName, INamedTypeSymbol stringComparer, CancellationToken cancellationToken) { var editor = await DocumentEditor.CreateAsync(document, cancellationToken).ConfigureAwait(false); - var semanticModel = editor.SemanticModel; var generator = editor.Generator; var syntax = (MemberAccessExpressionSyntax)nodeToFix; - var stringComparer = semanticModel.Compilation.GetBestTypeByMetadataName("System.StringComparer"); - if (stringComparer is null) - return document; - var newSyntax = generator.MemberAccessExpression( generator.TypeExpression(stringComparer), comparerName); diff --git a/src/Meziantou.Analyzer.CodeFixers/Rules/DoNotUseEqualityOperatorsForSpanOfCharFixer.cs b/src/Meziantou.Analyzer.CodeFixers/Rules/DoNotUseEqualityOperatorsForSpanOfCharFixer.cs index b22f331fd..32e3c16e5 100644 --- a/src/Meziantou.Analyzer.CodeFixers/Rules/DoNotUseEqualityOperatorsForSpanOfCharFixer.cs +++ b/src/Meziantou.Analyzer.CodeFixers/Rules/DoNotUseEqualityOperatorsForSpanOfCharFixer.cs @@ -26,25 +26,27 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context) if (nodeToFix is null) return; + var semanticModel = await context.Document.GetSemanticModelAsync(context.CancellationToken).ConfigureAwait(false); + if (semanticModel is null) + return; + + if (semanticModel.GetOperation(nodeToFix, context.CancellationToken) is not IBinaryOperation operation) + return; + var title = "Use SequenceEquals"; var codeAction = CodeAction.Create( title, - ct => Refactor(context.Document, nodeToFix, ct), + ct => Refactor(context.Document, operation, ct), equivalenceKey: title); context.RegisterCodeFix(codeAction, context.Diagnostics); } - private static async Task Refactor(Document document, SyntaxNode nodeToFix, CancellationToken cancellationToken) + private static async Task Refactor(Document document, IBinaryOperation operation, CancellationToken cancellationToken) { var editor = await DocumentEditor.CreateAsync(document, cancellationToken).ConfigureAwait(false); - var semanticModel = editor.SemanticModel; var generator = editor.Generator; - var operation = (IBinaryOperation?)semanticModel.GetOperation(nodeToFix, cancellationToken); - if (operation is null) - return document; - var newExpression = generator.InvocationExpression( generator.MemberAccessExpression(operation.LeftOperand.Syntax, "SequenceEqual"), operation.RightOperand.Syntax); @@ -53,7 +55,7 @@ private static async Task Refactor(Document document, SyntaxNode nodeT newExpression = generator.LogicalNotExpression(newExpression); } - editor.ReplaceNode(nodeToFix, newExpression.WithAdditionalAnnotations(Formatter.Annotation)); + editor.ReplaceNode(operation.Syntax, newExpression.WithAdditionalAnnotations(Formatter.Annotation)); return editor.GetChangedDocument(); } } diff --git a/src/Meziantou.Analyzer.CodeFixers/Rules/DoNotUseImplicitCultureSensitiveToStringInterpolationFixer.cs b/src/Meziantou.Analyzer.CodeFixers/Rules/DoNotUseImplicitCultureSensitiveToStringInterpolationFixer.cs index 7db07ae9b..f63042483 100644 --- a/src/Meziantou.Analyzer.CodeFixers/Rules/DoNotUseImplicitCultureSensitiveToStringInterpolationFixer.cs +++ b/src/Meziantou.Analyzer.CodeFixers/Rules/DoNotUseImplicitCultureSensitiveToStringInterpolationFixer.cs @@ -27,6 +27,10 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context) if (semanticModel is null || !CanUseStringCreate(semanticModel.Compilation)) return; + var cultureInfoType = semanticModel.Compilation.GetBestTypeByMetadataName("System.Globalization.CultureInfo"); + if (cultureInfoType is null) + return; + var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); var nodeToFix = root?.FindNode(context.Span, getInnermostNodeForTie: true); if (nodeToFix?.AncestorsAndSelf().OfType().FirstOrDefault() is not InterpolatedStringExpressionSyntax interpolatedString) @@ -36,21 +40,15 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context) context.RegisterCodeFix( CodeAction.Create( title, - ct => Fix(context.Document, interpolatedString, ct), + ct => Fix(context.Document, interpolatedString, cultureInfoType, ct), equivalenceKey: title), context.Diagnostics); } - private static async Task Fix(Document document, InterpolatedStringExpressionSyntax interpolatedStringExpression, CancellationToken cancellationToken) + private static async Task Fix(Document document, InterpolatedStringExpressionSyntax interpolatedStringExpression, INamedTypeSymbol cultureInfoType, CancellationToken cancellationToken) { var editor = await DocumentEditor.CreateAsync(document, cancellationToken).ConfigureAwait(false); var compilation = editor.SemanticModel.Compilation; - if (!CanUseStringCreate(compilation)) - return document; - - var cultureInfoType = compilation.GetBestTypeByMetadataName("System.Globalization.CultureInfo"); - if (cultureInfoType is null) - return document; var generator = editor.Generator; var replacement = generator.InvocationExpression( diff --git a/src/Meziantou.Analyzer.CodeFixers/Rules/DoNotUseStringGetHashCodeFixer.cs b/src/Meziantou.Analyzer.CodeFixers/Rules/DoNotUseStringGetHashCodeFixer.cs index 557b297c1..11f8f3651 100644 --- a/src/Meziantou.Analyzer.CodeFixers/Rules/DoNotUseStringGetHashCodeFixer.cs +++ b/src/Meziantou.Analyzer.CodeFixers/Rules/DoNotUseStringGetHashCodeFixer.cs @@ -25,29 +25,29 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context) if (nodeToFix.Expression is not MemberAccessExpressionSyntax) return; + var semanticModel = await context.Document.GetSemanticModelAsync(context.CancellationToken).ConfigureAwait(false); + if (semanticModel is null) + return; + + var stringComparerSymbol = semanticModel.Compilation.GetBestTypeByMetadataName("System.StringComparer"); + if (stringComparerSymbol is null) + return; + var title = "Use StringComparer.Ordinal"; var codeAction = CodeAction.Create( title, - ct => AddStringComparison(context.Document, nodeToFix, ct), + ct => AddStringComparison(context.Document, nodeToFix, stringComparerSymbol, ct), equivalenceKey: title); context.RegisterCodeFix(codeAction, context.Diagnostics); } - private static async Task AddStringComparison(Document document, SyntaxNode nodeToFix, CancellationToken cancellationToken) + private static async Task AddStringComparison(Document document, SyntaxNode nodeToFix, INamedTypeSymbol stringComparer, CancellationToken cancellationToken) { var editor = await DocumentEditor.CreateAsync(document, cancellationToken).ConfigureAwait(false); - var semanticModel = editor.SemanticModel; var generator = editor.Generator; var invocationExpression = (InvocationExpressionSyntax)nodeToFix; - if (invocationExpression is null) - return document; - - var stringComparer = semanticModel.Compilation.GetBestTypeByMetadataName("System.StringComparer"); - if (stringComparer is null) - return document; - var memberAccessExpression = (MemberAccessExpressionSyntax)invocationExpression.Expression; var newExpression = generator.InvocationExpression( diff --git a/src/Meziantou.Analyzer.CodeFixers/Rules/EqualityShouldBeCorrectlyImplementedFixer.cs b/src/Meziantou.Analyzer.CodeFixers/Rules/EqualityShouldBeCorrectlyImplementedFixer.cs index 35faf59d5..8262a6d90 100755 --- a/src/Meziantou.Analyzer.CodeFixers/Rules/EqualityShouldBeCorrectlyImplementedFixer.cs +++ b/src/Meziantou.Analyzer.CodeFixers/Rules/EqualityShouldBeCorrectlyImplementedFixer.cs @@ -22,33 +22,34 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context) if (nodeToFix is null) return; - var title = "Implement System.IEquatable"; - var codeAction = CodeAction.Create( - title, - ct => ImplementIEquatable(context.Document, nodeToFix, ct), - equivalenceKey: title); - - context.RegisterCodeFix(codeAction, context.Diagnostics); - } - - private static async Task ImplementIEquatable(Document document, SyntaxNode nodeToFix, CancellationToken cancellationToken) - { - var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); + var semanticModel = await context.Document.GetSemanticModelAsync(context.CancellationToken).ConfigureAwait(false); + if (semanticModel is null) + return; - if (semanticModel is null || semanticModel.GetDeclaredSymbol(nodeToFix, cancellationToken: cancellationToken) is not ITypeSymbol declaredTypeSymbol) - return document; + if (semanticModel.GetDeclaredSymbol(nodeToFix, cancellationToken: context.CancellationToken) is not ITypeSymbol declaredTypeSymbol) + return; var genericInterfaceSymbol = semanticModel.Compilation.GetBestTypeByMetadataName("System.IEquatable`1"); if (genericInterfaceSymbol is null) - return document; + return; - // Retrieve Nullable Annotation from the Equals method and use it to construct the concrete interface var equalsMethod = declaredTypeSymbol.GetMembers().OfType().SingleOrDefault(m => EqualityShouldBeCorrectlyImplementedAnalyzerCommon.IsEqualsOfTMethod(m) && m is not null); if (equalsMethod is null) - return document; + return; var nullableAnnotation = equalsMethod.Parameters[0].NullableAnnotation; + var title = "Implement System.IEquatable"; + var codeAction = CodeAction.Create( + title, + ct => ImplementIEquatable(context.Document, nodeToFix, genericInterfaceSymbol, declaredTypeSymbol, nullableAnnotation, ct), + equivalenceKey: title); + + context.RegisterCodeFix(codeAction, context.Diagnostics); + } + + private static async Task ImplementIEquatable(Document document, SyntaxNode nodeToFix, INamedTypeSymbol genericInterfaceSymbol, ITypeSymbol declaredTypeSymbol, NullableAnnotation nullableAnnotation, CancellationToken cancellationToken) + { var concreteInterfaceSymbol = genericInterfaceSymbol.Construct( ImmutableArray.Create(declaredTypeSymbol), ImmutableArray.Create(nullableAnnotation)); diff --git a/src/Meziantou.Analyzer.CodeFixers/Rules/ILoggerParameterTypeShouldMatchContainingTypeFixer.cs b/src/Meziantou.Analyzer.CodeFixers/Rules/ILoggerParameterTypeShouldMatchContainingTypeFixer.cs index e7e139a50..f2181368b 100644 --- a/src/Meziantou.Analyzer.CodeFixers/Rules/ILoggerParameterTypeShouldMatchContainingTypeFixer.cs +++ b/src/Meziantou.Analyzer.CodeFixers/Rules/ILoggerParameterTypeShouldMatchContainingTypeFixer.cs @@ -27,6 +27,24 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context) if (nodeToFix is null) return; + var parameter = nodeToFix.AncestorsAndSelf().OfType().FirstOrDefault(); + if (parameter is null) + return; + + if (parameter.Type is not GenericNameSyntax) + return; + + var containingTypeDeclaration = parameter.Ancestors().OfType().FirstOrDefault(); + if (containingTypeDeclaration is null) + return; + + var semanticModel = await context.Document.GetSemanticModelAsync(context.CancellationToken).ConfigureAwait(false); + if (semanticModel is null) + return; + + if (semanticModel.GetDeclaredSymbol(containingTypeDeclaration, context.CancellationToken) is null) + return; + var title = "Use ILogger with matching type parameter"; var codeAction = CodeAction.Create( title, diff --git a/src/Meziantou.Analyzer.CodeFixers/Rules/MarkAttributesWithAttributeUsageAttributeFixer.cs b/src/Meziantou.Analyzer.CodeFixers/Rules/MarkAttributesWithAttributeUsageAttributeFixer.cs index 0e5d8263f..53890fee6 100644 --- a/src/Meziantou.Analyzer.CodeFixers/Rules/MarkAttributesWithAttributeUsageAttributeFixer.cs +++ b/src/Meziantou.Analyzer.CodeFixers/Rules/MarkAttributesWithAttributeUsageAttributeFixer.cs @@ -26,29 +26,34 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context) if (nodeToFix is null) return; + var semanticModel = await context.Document.GetSemanticModelAsync(context.CancellationToken).ConfigureAwait(false); + if (semanticModel is null) + return; + + var attributeUsageAttribute = semanticModel.Compilation.GetBestTypeByMetadataName("System.AttributeUsageAttribute"); + if (attributeUsageAttribute is null) + return; + + var attributeTargets = semanticModel.Compilation.GetBestTypeByMetadataName("System.AttributeTargets"); + if (attributeTargets is null) + return; + var title = "Add AttributeUsage attribute"; var codeAction = CodeAction.Create( title, - ct => Refactor(context.Document, nodeToFix, ct), + ct => Refactor(context.Document, nodeToFix, attributeUsageAttribute, attributeTargets, ct), equivalenceKey: title); context.RegisterCodeFix(codeAction, context.Diagnostics); } - private static async Task Refactor(Document document, SyntaxNode nodeToFix, CancellationToken cancellationToken) + private static async Task Refactor(Document document, SyntaxNode nodeToFix, INamedTypeSymbol attributeUsageAttribute, INamedTypeSymbol attributeTargets, CancellationToken cancellationToken) { var editor = await DocumentEditor.CreateAsync(document, cancellationToken).ConfigureAwait(false); - var semanticModel = editor.SemanticModel; var generator = editor.Generator; var classNode = (ClassDeclarationSyntax)nodeToFix; - var attributeUsageAttribute = semanticModel.Compilation.GetBestTypeByMetadataName("System.AttributeUsageAttribute"); - var attributeTargets = semanticModel.Compilation.GetBestTypeByMetadataName("System.AttributeTargets"); - - if (attributeUsageAttribute is null || attributeTargets is null) - return document; - var attribute = editor.Generator.Attribute( generator.TypeExpression(attributeUsageAttribute, addImport: true), [ diff --git a/src/Meziantou.Analyzer.CodeFixers/Rules/NamedParameterFixer.cs b/src/Meziantou.Analyzer.CodeFixers/Rules/NamedParameterFixer.cs index 62cb60762..a872b0aa0 100644 --- a/src/Meziantou.Analyzer.CodeFixers/Rules/NamedParameterFixer.cs +++ b/src/Meziantou.Analyzer.CodeFixers/Rules/NamedParameterFixer.cs @@ -28,6 +28,21 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context) if (nodeToFix is null) return; + var argument = nodeToFix.FirstAncestorOrSelf(); + if (argument is null || argument.NameColon is not null) + return; + + var semanticModel = await context.Document.GetSemanticModelAsync(context.CancellationToken).ConfigureAwait(false); + if (semanticModel is null) + return; + + if (FindParameters(semanticModel, argument, context.CancellationToken) is not { } parameters) + return; + + var index = NamedParameterAnalyzerCommon.ArgumentIndex(argument); + if (index < 0 || index >= parameters.Length) + return; + var title = "Add parameter name"; var codeAction = CodeAction.Create( title, diff --git a/src/Meziantou.Analyzer.CodeFixers/Rules/NonFlagsEnumsShouldNotBeMarkedWithFlagsAttributeFixer.cs b/src/Meziantou.Analyzer.CodeFixers/Rules/NonFlagsEnumsShouldNotBeMarkedWithFlagsAttributeFixer.cs index f79c4513f..a07fe086d 100644 --- a/src/Meziantou.Analyzer.CodeFixers/Rules/NonFlagsEnumsShouldNotBeMarkedWithFlagsAttributeFixer.cs +++ b/src/Meziantou.Analyzer.CodeFixers/Rules/NonFlagsEnumsShouldNotBeMarkedWithFlagsAttributeFixer.cs @@ -24,26 +24,30 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context) if (enumDeclaration is null) return; + var semanticModel = await context.Document.GetSemanticModelAsync(context.CancellationToken).ConfigureAwait(false); + if (semanticModel is null) + return; + + var flagsAttributeSymbol = semanticModel.Compilation.GetBestTypeByMetadataName("System.FlagsAttribute"); + if (flagsAttributeSymbol is null) + return; + var title = "Remove [Flags] attribute"; context.RegisterCodeFix( CodeAction.Create( title, - ct => RemoveFlagsAttribute(context.Document, enumDeclaration, ct), + ct => RemoveFlagsAttribute(context.Document, enumDeclaration, flagsAttributeSymbol, ct), equivalenceKey: title), context.Diagnostics); } - private static async Task RemoveFlagsAttribute(Document document, EnumDeclarationSyntax enumDeclaration, CancellationToken cancellationToken) + private static async Task RemoveFlagsAttribute(Document document, EnumDeclarationSyntax enumDeclaration, INamedTypeSymbol flagsAttributeSymbol, CancellationToken cancellationToken) { var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); if (root is null || semanticModel is null) return document; - var flagsAttributeSymbol = semanticModel.Compilation.GetBestTypeByMetadataName("System.FlagsAttribute"); - if (flagsAttributeSymbol is null) - return document; - var newAttributeLists = new List(enumDeclaration.AttributeLists.Count); foreach (var attributeList in enumDeclaration.AttributeLists) { diff --git a/src/Meziantou.Analyzer.CodeFixers/Rules/NotPatternShouldBeParenthesizedCodeFixer.cs b/src/Meziantou.Analyzer.CodeFixers/Rules/NotPatternShouldBeParenthesizedCodeFixer.cs index 3e7befa46..c54d6f685 100644 --- a/src/Meziantou.Analyzer.CodeFixers/Rules/NotPatternShouldBeParenthesizedCodeFixer.cs +++ b/src/Meziantou.Analyzer.CodeFixers/Rules/NotPatternShouldBeParenthesizedCodeFixer.cs @@ -32,13 +32,18 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context) context.RegisterCodeFix(codeAction, context.Diagnostics); } + if (nodeToFix is UnaryPatternSyntax unary) { - var title = "Negate all or patterns"; - var codeAction = CodeAction.Create( - title, - ct => ParenthesizeOrPattern(context.Document, nodeToFix, ct), - equivalenceKey: title); - context.RegisterCodeFix(codeAction, context.Diagnostics); + var orRoot = unary.Ancestors().TakeWhile(IsOrPattern).LastOrDefault(); + if (orRoot is not null) + { + var title = "Negate all or patterns"; + var codeAction = CodeAction.Create( + title, + ct => ParenthesizeOrPattern(context.Document, nodeToFix, ct), + equivalenceKey: title); + context.RegisterCodeFix(codeAction, context.Diagnostics); + } } } @@ -53,9 +58,7 @@ private static async Task ParenthesizeOrPattern(Document document, Syn { var editor = await DocumentEditor.CreateAsync(document, cancellationToken).ConfigureAwait(false); - if (nodeToFix is not UnaryPatternSyntax unary) - return document; - + var unary = (UnaryPatternSyntax)nodeToFix; var root = unary.Ancestors().TakeWhile(IsOrPattern).LastOrDefault(); if (root is null) return document; diff --git a/src/Meziantou.Analyzer.CodeFixers/Rules/ReplaceEnumToStringWithNameofFixer.cs b/src/Meziantou.Analyzer.CodeFixers/Rules/ReplaceEnumToStringWithNameofFixer.cs index b4ccf5f70..06c6074ce 100644 --- a/src/Meziantou.Analyzer.CodeFixers/Rules/ReplaceEnumToStringWithNameofFixer.cs +++ b/src/Meziantou.Analyzer.CodeFixers/Rules/ReplaceEnumToStringWithNameofFixer.cs @@ -27,6 +27,13 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context) if (nodeToFix is null) return; + var semanticModel = await context.Document.GetSemanticModelAsync(context.CancellationToken).ConfigureAwait(false); + if (semanticModel is null) + return; + + if (semanticModel.GetOperation(nodeToFix, context.CancellationToken) is null) + return; + var title = "Use nameof"; context.RegisterCodeFix(CodeAction.Create(title, ct => UseNameof(context.Document, nodeToFix, ct), equivalenceKey: title), context.Diagnostics); } @@ -47,9 +54,6 @@ private static async Task UseNameof(Document document, SyntaxNode node editor.ReplaceNode(nodeToFix, newExpression); } - if (operation is null) - return document; - return editor.GetChangedDocument(); } } diff --git a/src/Meziantou.Analyzer.CodeFixers/Rules/ReturnTaskFromResultInsteadOfReturningNullFixer.cs b/src/Meziantou.Analyzer.CodeFixers/Rules/ReturnTaskFromResultInsteadOfReturningNullFixer.cs index 6fd24d1ac..6564b3243 100644 --- a/src/Meziantou.Analyzer.CodeFixers/Rules/ReturnTaskFromResultInsteadOfReturningNullFixer.cs +++ b/src/Meziantou.Analyzer.CodeFixers/Rules/ReturnTaskFromResultInsteadOfReturningNullFixer.cs @@ -33,27 +33,27 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context) if (ReturnTaskFromResultInsteadOfReturningNullAnalyzerCommon.FindContainingMethod(semanticModel, nodeToFix, context.CancellationToken)?.ReturnType is not INamedTypeSymbol type) return; + var taskTypeSymbol = semanticModel.Compilation.GetBestTypeByMetadataName("System.Threading.Tasks.Task"); + if (taskTypeSymbol is null) + return; + if (!type.IsGenericType) { var title = "Use Task.CompletedTask"; - context.RegisterCodeFix(CodeAction.Create(title, ct => UseTaskCompleted(context.Document, nodeToFix, ct), equivalenceKey: title), context.Diagnostics); + context.RegisterCodeFix(CodeAction.Create(title, ct => UseTaskCompleted(context.Document, nodeToFix, taskTypeSymbol, ct), equivalenceKey: title), context.Diagnostics); } else { var title = "Use Task.FromResult"; - context.RegisterCodeFix(CodeAction.Create(title, ct => UseTaskFromResult(context.Document, nodeToFix, type, ct), equivalenceKey: title), context.Diagnostics); + context.RegisterCodeFix(CodeAction.Create(title, ct => UseTaskFromResult(context.Document, nodeToFix, taskTypeSymbol, type, ct), equivalenceKey: title), context.Diagnostics); } } - private static async Task UseTaskCompleted(Document document, SyntaxNode nodeToFix, CancellationToken cancellationToken) + private static async Task UseTaskCompleted(Document document, SyntaxNode nodeToFix, INamedTypeSymbol typeSymbol, CancellationToken cancellationToken) { var editor = await DocumentEditor.CreateAsync(document, cancellationToken).ConfigureAwait(false); var generator = editor.Generator; - var typeSymbol = editor.SemanticModel.Compilation.GetBestTypeByMetadataName("System.Threading.Tasks.Task"); - if (typeSymbol is null) - return document; - var newExpression = generator.MemberAccessExpression(generator.TypeExpression(typeSymbol), nameof(Task.CompletedTask)); if (nodeToFix is ReturnStatementSyntax { Expression: { } } returnStatementSyntax) @@ -68,15 +68,11 @@ private static async Task UseTaskCompleted(Document document, SyntaxNo return editor.GetChangedDocument(); } - private static async Task UseTaskFromResult(Document document, SyntaxNode nodeToFix, INamedTypeSymbol typeSymbol, CancellationToken cancellationToken) + private static async Task UseTaskFromResult(Document document, SyntaxNode nodeToFix, INamedTypeSymbol taskTypeSymbol, INamedTypeSymbol typeSymbol, CancellationToken cancellationToken) { var editor = await DocumentEditor.CreateAsync(document, cancellationToken).ConfigureAwait(false); var generator = editor.Generator; - var taskTypeSymbol = editor.SemanticModel.Compilation.GetBestTypeByMetadataName("System.Threading.Tasks.Task"); - if (taskTypeSymbol is null) - return document; - var newExpression = generator.MemberAccessExpression(generator.TypeExpression(taskTypeSymbol), generator.GenericName("FromResult", typeSymbol.TypeArguments[0])); newExpression = generator.InvocationExpression(newExpression, generator.DefaultExpression(typeSymbol.TypeArguments[0])); diff --git a/src/Meziantou.Analyzer.CodeFixers/Rules/UseArrayEmptyFixer.cs b/src/Meziantou.Analyzer.CodeFixers/Rules/UseArrayEmptyFixer.cs index f2611affc..9df9b2b63 100644 --- a/src/Meziantou.Analyzer.CodeFixers/Rules/UseArrayEmptyFixer.cs +++ b/src/Meziantou.Analyzer.CodeFixers/Rules/UseArrayEmptyFixer.cs @@ -26,6 +26,14 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context) if (nodeToFix is null) return; + var semanticModel = await context.Document.GetSemanticModelAsync(context.CancellationToken).ConfigureAwait(false); + if (semanticModel is null) + return; + + var typeInfo = semanticModel.GetTypeInfo(nodeToFix, context.CancellationToken); + if ((IArrayTypeSymbol?)(typeInfo.Type ?? typeInfo.ConvertedType) is null) + return; + var title = "Use Array.Empty()"; var codeAction = CodeAction.Create( title, @@ -41,9 +49,7 @@ private static async Task ConvertToArrayEmpty(Document document, Synta var semanticModel = editor.SemanticModel; var generator = editor.Generator; - var elementType = GetArrayElementType(nodeToFix, semanticModel, cancellationToken); - if (elementType is null) - return document; + var elementType = GetArrayElementType(nodeToFix, semanticModel, cancellationToken)!; var arrayEmptyInvocation = GenerateArrayEmptyInvocation(generator, elementType, semanticModel).WithTriviaFrom(nodeToFix); editor.ReplaceNode(nodeToFix, arrayEmptyInvocation); diff --git a/src/Meziantou.Analyzer.CodeFixers/Rules/UseContainsKeyInsteadOfTryGetValueFixer.cs b/src/Meziantou.Analyzer.CodeFixers/Rules/UseContainsKeyInsteadOfTryGetValueFixer.cs index 0815f3ff2..313f5c753 100644 --- a/src/Meziantou.Analyzer.CodeFixers/Rules/UseContainsKeyInsteadOfTryGetValueFixer.cs +++ b/src/Meziantou.Analyzer.CodeFixers/Rules/UseContainsKeyInsteadOfTryGetValueFixer.cs @@ -25,6 +25,23 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context) if (nodeToFix is null) return; + var semanticModel = await context.Document.GetSemanticModelAsync(context.CancellationToken).ConfigureAwait(false); + if (semanticModel is null) + return; + + if (FindInvocation(semanticModel, nodeToFix, context.CancellationToken) is not { Arguments.Length: 2 } operation) + return; + + if (operation.TargetMethod.Name != "TryGetValue") + return; + + if (operation.Arguments[1].Value is not IDiscardOperation) + return; + + if (operation.Syntax is not InvocationExpressionSyntax invocationSyntax || + invocationSyntax.Expression is not MemberAccessExpressionSyntax) + return; + const string Title = "Use ContainsKey"; context.RegisterCodeFix( CodeAction.Create(Title, ct => UseContainsKey(context.Document, nodeToFix, ct), equivalenceKey: Title), diff --git a/src/Meziantou.Analyzer.CodeFixers/Rules/UseDateTimeUnixEpochFixer.cs b/src/Meziantou.Analyzer.CodeFixers/Rules/UseDateTimeUnixEpochFixer.cs index 99729b805..d8b57b12b 100644 --- a/src/Meziantou.Analyzer.CodeFixers/Rules/UseDateTimeUnixEpochFixer.cs +++ b/src/Meziantou.Analyzer.CodeFixers/Rules/UseDateTimeUnixEpochFixer.cs @@ -25,8 +25,15 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context) if (nodeToFix is null) return; + var semanticModel = await context.Document.GetSemanticModelAsync(context.CancellationToken).ConfigureAwait(false); + if (semanticModel is null) + return; + if (context.Diagnostics[0].Id == RuleIdentifiers.UseDateTimeUnixEpoch) { + if (semanticModel.Compilation.GetBestTypeByMetadataName("System.DateTime") is null) + return; + context.RegisterCodeFix( CodeAction.Create( "Use DateTime.UnixEpoch", @@ -36,6 +43,9 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context) } else { + if (semanticModel.Compilation.GetBestTypeByMetadataName("System.DateTimeOffset") is null) + return; + context.RegisterCodeFix( CodeAction.Create( "Use DateTimeOffset.UnixEpoch", @@ -48,9 +58,7 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context) private static async Task Remove(Document document, SyntaxNode node, string type, CancellationToken cancellationToken) { var editor = await DocumentEditor.CreateAsync(document, cancellationToken).ConfigureAwait(false); - var symbol = editor.SemanticModel.Compilation.GetBestTypeByMetadataName(type); - if (symbol is null) - return document; + var symbol = editor.SemanticModel.Compilation.GetBestTypeByMetadataName(type)!; var generator = editor.Generator; var member = generator.MemberAccessExpression(generator.TypeExpression(symbol), "UnixEpoch"); diff --git a/src/Meziantou.Analyzer.CodeFixers/Rules/UseEqualsMethodInsteadOfOperatorFixer.cs b/src/Meziantou.Analyzer.CodeFixers/Rules/UseEqualsMethodInsteadOfOperatorFixer.cs index 7287bf6fc..719d40684 100644 --- a/src/Meziantou.Analyzer.CodeFixers/Rules/UseEqualsMethodInsteadOfOperatorFixer.cs +++ b/src/Meziantou.Analyzer.CodeFixers/Rules/UseEqualsMethodInsteadOfOperatorFixer.cs @@ -26,6 +26,20 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context) if (nodeToFix is null) return; + var semanticModel = await context.Document.GetSemanticModelAsync(context.CancellationToken).ConfigureAwait(false); + if (semanticModel is null) + return; + + var binaryOp = FindBinaryOperation(semanticModel, nodeToFix, context.CancellationToken); + if (binaryOp is null) + return; + + if (binaryOp.Syntax is not BinaryExpressionSyntax) + return; + + if (binaryOp.LeftOperand.Syntax is not ExpressionSyntax || binaryOp.RightOperand.Syntax is not ExpressionSyntax) + return; + const string Title = "Use Equals"; context.RegisterCodeFix( CodeAction.Create(Title, ct => UseEquals(context.Document, nodeToFix, ct), equivalenceKey: Title), diff --git a/src/Meziantou.Analyzer.CodeFixers/Rules/UseIsPatternInsteadOfSequenceEqualFixer.cs b/src/Meziantou.Analyzer.CodeFixers/Rules/UseIsPatternInsteadOfSequenceEqualFixer.cs index 1dcf08310..f2e018c7c 100644 --- a/src/Meziantou.Analyzer.CodeFixers/Rules/UseIsPatternInsteadOfSequenceEqualFixer.cs +++ b/src/Meziantou.Analyzer.CodeFixers/Rules/UseIsPatternInsteadOfSequenceEqualFixer.cs @@ -27,6 +27,13 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context) if (nodeToFix is null) return; + var semanticModel = await context.Document.GetSemanticModelAsync(context.CancellationToken).ConfigureAwait(false); + if (semanticModel is null) + return; + + if (semanticModel.GetOperation(nodeToFix, context.CancellationToken) is not IInvocationOperation) + return; + var title = "Use 'is' pattern"; context.RegisterCodeFix(CodeAction.Create(title, ct => UseIs(context.Document, nodeToFix, ct), equivalenceKey: title), context.Diagnostics); } @@ -34,8 +41,7 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context) private static async Task UseIs(Document document, SyntaxNode nodeToFix, CancellationToken cancellationToken) { var editor = await DocumentEditor.CreateAsync(document, cancellationToken).ConfigureAwait(false); - if (editor.SemanticModel.GetOperation(nodeToFix, cancellationToken) is not IInvocationOperation operation) - return document; + var operation = (IInvocationOperation)editor.SemanticModel.GetOperation(nodeToFix, cancellationToken)!; var newExpression = SyntaxFactory.IsPatternExpression((ExpressionSyntax)operation.Arguments[0].Value.Syntax, SyntaxFactory.ConstantPattern((ExpressionSyntax)operation.Arguments[1].Value.Syntax)); editor.ReplaceNode(nodeToFix, newExpression); diff --git a/src/Meziantou.Analyzer.CodeFixers/Rules/UseLazyInitializerEnsureInitializeFixer.cs b/src/Meziantou.Analyzer.CodeFixers/Rules/UseLazyInitializerEnsureInitializeFixer.cs index 6caad9816..4871ff814 100644 --- a/src/Meziantou.Analyzer.CodeFixers/Rules/UseLazyInitializerEnsureInitializeFixer.cs +++ b/src/Meziantou.Analyzer.CodeFixers/Rules/UseLazyInitializerEnsureInitializeFixer.cs @@ -27,15 +27,26 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context) if (nodeToFix is null) return; + var semanticModel = await context.Document.GetSemanticModelAsync(context.CancellationToken).ConfigureAwait(false); + if (semanticModel is null) + return; + + if (semanticModel.GetOperation(nodeToFix, context.CancellationToken) is not IInvocationOperation) + return; + + var lazyInitializerType = semanticModel.Compilation.GetBestTypeByMetadataName("System.Threading.LazyInitializer"); + if (lazyInitializerType is null) + return; + context.RegisterCodeFix( CodeAction.Create( "Use LazyInitializer.EnsureInitialized", - ct => Update(context.Document, nodeToFix, ct), + ct => Update(context.Document, nodeToFix, lazyInitializerType, ct), equivalenceKey: "Use LazyInitializer.EnsureInitialized"), context.Diagnostics); } - private static async Task Update(Document document, SyntaxNode nodeToFix, CancellationToken cancellationToken) + private static async Task Update(Document document, SyntaxNode nodeToFix, INamedTypeSymbol lazyInitializerType, CancellationToken cancellationToken) { var editor = await DocumentEditor.CreateAsync(document, cancellationToken).ConfigureAwait(false); var semanticModel = editor.SemanticModel; @@ -43,10 +54,6 @@ private static async Task Update(Document document, SyntaxNode nodeToF if (semanticModel.GetOperation(nodeToFix, cancellationToken) is not IInvocationOperation invocation) return document; - var lazyInitializerType = semanticModel.Compilation.GetBestTypeByMetadataName("System.Threading.LazyInitializer"); - if (lazyInitializerType is null) - return document; - var generator = editor.Generator; var refArgSyntax = (ArgumentSyntax)invocation.Arguments[0].Syntax; var valueExpression = ((ArgumentSyntax)invocation.Arguments[1].Syntax).Expression; diff --git a/src/Meziantou.Analyzer.CodeFixers/Rules/UseOperatingSystemInsteadOfRuntimeInformationFixer.cs b/src/Meziantou.Analyzer.CodeFixers/Rules/UseOperatingSystemInsteadOfRuntimeInformationFixer.cs index 5b5ec6945..e03d03d5a 100644 --- a/src/Meziantou.Analyzer.CodeFixers/Rules/UseOperatingSystemInsteadOfRuntimeInformationFixer.cs +++ b/src/Meziantou.Analyzer.CodeFixers/Rules/UseOperatingSystemInsteadOfRuntimeInformationFixer.cs @@ -24,20 +24,15 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context) if (nodeToFix is null) return; - const string Title = "Use System.OperatingSystem"; - context.RegisterCodeFix( - CodeAction.Create(Title, ct => UseOperatingSystem(context.Document, nodeToFix, ct), equivalenceKey: Title), - context.Diagnostics); - } + var semanticModel = await context.Document.GetSemanticModelAsync(context.CancellationToken).ConfigureAwait(false); + if (semanticModel is null) + return; - private static async Task UseOperatingSystem(Document document, SyntaxNode nodeToFix, CancellationToken cancellationToken) - { - var editor = await DocumentEditor.CreateAsync(document, cancellationToken).ConfigureAwait(false); - if (FindInvocation(editor.SemanticModel, nodeToFix, cancellationToken) is not { Arguments.Length: 1 } operation) - return document; + if (FindInvocation(semanticModel, nodeToFix, context.CancellationToken) is not { Arguments.Length: 1 } operation) + return; if (operation.Arguments[0].Value is not IMemberReferenceOperation { Member.Name: var osPlatformName }) - return document; + return; var methodName = osPlatformName switch { @@ -48,16 +43,26 @@ private static async Task UseOperatingSystem(Document document, Syntax _ => null, }; if (methodName is null) - return document; + return; - var operatingSystemType = editor.SemanticModel.Compilation.GetBestTypeByMetadataName("System.OperatingSystem"); + var operatingSystemType = semanticModel.Compilation.GetBestTypeByMetadataName("System.OperatingSystem"); if (operatingSystemType is null) - return document; + return; + + const string Title = "Use System.OperatingSystem"; + context.RegisterCodeFix( + CodeAction.Create(Title, ct => UseOperatingSystem(context.Document, operation.Syntax, methodName, operatingSystemType, ct), equivalenceKey: Title), + context.Diagnostics); + } + + private static async Task UseOperatingSystem(Document document, SyntaxNode operationSyntax, string methodName, INamedTypeSymbol operatingSystemType, CancellationToken cancellationToken) + { + var editor = await DocumentEditor.CreateAsync(document, cancellationToken).ConfigureAwait(false); var invocationExpression = editor.Generator.InvocationExpression( editor.Generator.MemberAccessExpression(editor.Generator.TypeExpression(operatingSystemType), methodName)); - editor.ReplaceNode(operation.Syntax, invocationExpression.WithTriviaFrom(operation.Syntax).WithAdditionalAnnotations(Formatter.Annotation)); + editor.ReplaceNode(operationSyntax, invocationExpression.WithTriviaFrom(operationSyntax).WithAdditionalAnnotations(Formatter.Annotation)); return editor.GetChangedDocument(); } diff --git a/src/Meziantou.Analyzer.CodeFixers/Rules/UsePatternMatchingForEqualityComparisonsFixer.cs b/src/Meziantou.Analyzer.CodeFixers/Rules/UsePatternMatchingForEqualityComparisonsFixer.cs index f64fc7443..f8061ef3a 100644 --- a/src/Meziantou.Analyzer.CodeFixers/Rules/UsePatternMatchingForEqualityComparisonsFixer.cs +++ b/src/Meziantou.Analyzer.CodeFixers/Rules/UsePatternMatchingForEqualityComparisonsFixer.cs @@ -27,6 +27,13 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context) if (nodeToFix is not BinaryExpressionSyntax invocation) return; + var semanticModel = await context.Document.GetSemanticModelAsync(context.CancellationToken).ConfigureAwait(false); + if (semanticModel is null) + return; + + if (semanticModel.GetOperation(invocation, context.CancellationToken) is not IBinaryOperation) + return; + context.RegisterCodeFix( CodeAction.Create( "Use pattern matching", @@ -38,8 +45,7 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context) private static async Task Update(Document document, BinaryExpressionSyntax node, CancellationToken cancellationToken) { var editor = await DocumentEditor.CreateAsync(document, cancellationToken).ConfigureAwait(false); - if (editor.SemanticModel.GetOperation(node, cancellationToken) is not IBinaryOperation operation) - return document; + var operation = (IBinaryOperation)editor.SemanticModel.GetOperation(node, cancellationToken)!; if (UsePatternMatchingForEqualityComparisonsCommon.IsNull(operation.LeftOperand)) { diff --git a/src/Meziantou.Analyzer.CodeFixers/Rules/UsePatternMatchingInsteadOfHasvalueFixer.cs b/src/Meziantou.Analyzer.CodeFixers/Rules/UsePatternMatchingInsteadOfHasvalueFixer.cs index 086d58618..b68510ce8 100644 --- a/src/Meziantou.Analyzer.CodeFixers/Rules/UsePatternMatchingInsteadOfHasvalueFixer.cs +++ b/src/Meziantou.Analyzer.CodeFixers/Rules/UsePatternMatchingInsteadOfHasvalueFixer.cs @@ -27,6 +27,13 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context) if (nodeToFix is not MemberAccessExpressionSyntax memberAccess) return; + var semanticModel = await context.Document.GetSemanticModelAsync(context.CancellationToken).ConfigureAwait(false); + if (semanticModel is null) + return; + + if (semanticModel.GetOperation(memberAccess, context.CancellationToken) is not IPropertyReferenceOperation) + return; + context.RegisterCodeFix( CodeAction.Create( "Use pattern matching", @@ -38,8 +45,7 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context) private static async Task Update(Document document, MemberAccessExpressionSyntax node, CancellationToken cancellationToken) { var editor = await DocumentEditor.CreateAsync(document, cancellationToken).ConfigureAwait(false); - if (editor.SemanticModel.GetOperation(node, cancellationToken) is not IPropertyReferenceOperation operation) - return document; + var operation = (IPropertyReferenceOperation)editor.SemanticModel.GetOperation(node, cancellationToken)!; var (nodeToReplace, negate) = GetNodeToReplace(operation); var target = operation.Instance?.Syntax as ExpressionSyntax; diff --git a/src/Meziantou.Analyzer.CodeFixers/Rules/UseStringComparerFixer.cs b/src/Meziantou.Analyzer.CodeFixers/Rules/UseStringComparerFixer.cs index 692bcc988..d1b11c66f 100644 --- a/src/Meziantou.Analyzer.CodeFixers/Rules/UseStringComparerFixer.cs +++ b/src/Meziantou.Analyzer.CodeFixers/Rules/UseStringComparerFixer.cs @@ -25,6 +25,14 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context) if (nodeToFix is null) return; + var semanticModel = await context.Document.GetSemanticModelAsync(context.CancellationToken).ConfigureAwait(false); + if (semanticModel is null) + return; + + var stringComparerSymbol = semanticModel.Compilation.GetBestTypeByMetadataName("System.StringComparer"); + if (stringComparerSymbol is null) + return; + RegisterCodeFix(nameof(StringComparer.Ordinal)); RegisterCodeFix(nameof(StringComparer.OrdinalIgnoreCase)); @@ -33,22 +41,17 @@ void RegisterCodeFix(string comparerName) var title = "Add StringComparer." + comparerName; var codeAction = CodeAction.Create( title, - ct => AddStringComparer(context.Document, nodeToFix, comparerName, ct), + ct => AddStringComparer(context.Document, nodeToFix, comparerName, stringComparerSymbol, ct), equivalenceKey: title); context.RegisterCodeFix(codeAction, context.Diagnostics); } } - private static async Task AddStringComparer(Document document, SyntaxNode nodeToFix, string comparerName, CancellationToken cancellationToken) + private static async Task AddStringComparer(Document document, SyntaxNode nodeToFix, string comparerName, INamedTypeSymbol stringComparer, CancellationToken cancellationToken) { var editor = await DocumentEditor.CreateAsync(document, cancellationToken).ConfigureAwait(false); var generator = editor.Generator; - var semanticModel = editor.SemanticModel; - - var stringComparer = semanticModel.Compilation.GetBestTypeByMetadataName("System.StringComparer"); - if (stringComparer is null) - return document; var newArgument = (ArgumentSyntax)generator.Argument( generator.MemberAccessExpression( diff --git a/src/Meziantou.Analyzer.CodeFixers/Rules/UseStringComparisonFixer.cs b/src/Meziantou.Analyzer.CodeFixers/Rules/UseStringComparisonFixer.cs index d4610be67..08818116e 100644 --- a/src/Meziantou.Analyzer.CodeFixers/Rules/UseStringComparisonFixer.cs +++ b/src/Meziantou.Analyzer.CodeFixers/Rules/UseStringComparisonFixer.cs @@ -26,6 +26,17 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context) if (nodeToFix is null) return; + if (nodeToFix is not InvocationExpressionSyntax) + return; + + var semanticModel = await context.Document.GetSemanticModelAsync(context.CancellationToken).ConfigureAwait(false); + if (semanticModel is null) + return; + + var stringComparisonSymbol = semanticModel.Compilation.GetBestTypeByMetadataName("System.StringComparison"); + if (stringComparisonSymbol is null) + return; + AddCodeFix(nameof(StringComparison.Ordinal)); AddCodeFix(nameof(StringComparison.OrdinalIgnoreCase)); @@ -34,26 +45,20 @@ void AddCodeFix(string comparisonMode) var title = "Add StringComparison." + comparisonMode; var codeAction = CodeAction.Create( title, - ct => AddStringComparison(context.Document, nodeToFix, comparisonMode, ct), + ct => AddStringComparison(context.Document, nodeToFix, comparisonMode, stringComparisonSymbol, ct), equivalenceKey: title); context.RegisterCodeFix(codeAction, context.Diagnostics); } } - private static async Task AddStringComparison(Document document, SyntaxNode nodeToFix, string stringComparisonMode, CancellationToken cancellationToken) + private static async Task AddStringComparison(Document document, SyntaxNode nodeToFix, string stringComparisonMode, INamedTypeSymbol stringComparison, CancellationToken cancellationToken) { var editor = await DocumentEditor.CreateAsync(document, cancellationToken).ConfigureAwait(false); var semanticModel = editor.SemanticModel; var generator = editor.Generator; var invocationExpression = (InvocationExpressionSyntax)nodeToFix; - if (invocationExpression is null) - return document; - - var stringComparison = semanticModel.Compilation.GetBestTypeByMetadataName("System.StringComparison"); - if (stringComparison is null) - return document; var newArgument = (ArgumentSyntax)generator.Argument( generator.MemberAccessExpression( diff --git a/src/Meziantou.Analyzer.CodeFixers/Rules/UseStringCreateInsteadOfFormattableStringFixer.cs b/src/Meziantou.Analyzer.CodeFixers/Rules/UseStringCreateInsteadOfFormattableStringFixer.cs index 6a3415a88..ffd5852be 100644 --- a/src/Meziantou.Analyzer.CodeFixers/Rules/UseStringCreateInsteadOfFormattableStringFixer.cs +++ b/src/Meziantou.Analyzer.CodeFixers/Rules/UseStringCreateInsteadOfFormattableStringFixer.cs @@ -26,27 +26,33 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context) if (root?.FindNode(context.Span, getInnermostNodeForTie: true) is not InvocationExpressionSyntax nodeToFix) return; + var semanticModel = await context.Document.GetSemanticModelAsync(context.CancellationToken).ConfigureAwait(false); + if (semanticModel is null) + return; + + var cultureInfoSymbol = semanticModel.Compilation.GetBestTypeByMetadataName("System.Globalization.CultureInfo"); + if (cultureInfoSymbol is null) + return; + + if (semanticModel.GetOperation(nodeToFix, context.CancellationToken) is not IInvocationOperation) + return; + var title = "Use string.Create"; var codeAction = CodeAction.Create( title, - ct => Fix(context.Document, nodeToFix, ct), + ct => Fix(context.Document, nodeToFix, cultureInfoSymbol, ct), equivalenceKey: title); context.RegisterCodeFix(codeAction, context.Diagnostics); } - private static async Task Fix(Document document, InvocationExpressionSyntax nodeToFix, CancellationToken cancellationToken) + private static async Task Fix(Document document, InvocationExpressionSyntax nodeToFix, INamedTypeSymbol cultureInfo, CancellationToken cancellationToken) { var editor = await DocumentEditor.CreateAsync(document, cancellationToken).ConfigureAwait(false); var generator = editor.Generator; - var cultureInfo = editor.SemanticModel.Compilation.GetBestTypeByMetadataName("System.Globalization.CultureInfo"); - if (cultureInfo is null) - return document; - - if (editor.SemanticModel.GetOperation(nodeToFix, cancellationToken) is not IInvocationOperation op) - return document; + var op = (IInvocationOperation)editor.SemanticModel.GetOperation(nodeToFix, cancellationToken)!; var methodName = op.TargetMethod.Name; diff --git a/src/Meziantou.Analyzer.CodeFixers/Rules/UseStringEqualsFixer.cs b/src/Meziantou.Analyzer.CodeFixers/Rules/UseStringEqualsFixer.cs index 0e6baa656..b8cd996c6 100644 --- a/src/Meziantou.Analyzer.CodeFixers/Rules/UseStringEqualsFixer.cs +++ b/src/Meziantou.Analyzer.CodeFixers/Rules/UseStringEqualsFixer.cs @@ -28,36 +28,43 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context) if (nodeToFix is null) return; + var semanticModel = await context.Document.GetSemanticModelAsync(context.CancellationToken).ConfigureAwait(false); + if (semanticModel is null) + return; + + if (semanticModel.GetOperation(nodeToFix, context.CancellationToken) is not IBinaryOperation) + return; + + var stringComparisonSymbol = semanticModel.Compilation.GetBestTypeByMetadataName("System.StringComparison"); + if (stringComparisonSymbol is null) + return; + RegisterCodeFix(nameof(StringComparison.Ordinal)); RegisterCodeFix(nameof(StringComparison.OrdinalIgnoreCase)); - var semanticModel = await context.Document.GetSemanticModelAsync(context.CancellationToken).ConfigureAwait(false); - if (semanticModel is not null) + var extensionsType = semanticModel.Compilation.GetBestTypeByMetadataName("Meziantou.Framework.StringExtensions"); + if (extensionsType is not null) { - var type = semanticModel.Compilation.GetBestTypeByMetadataName("Meziantou.Framework.StringExtensions"); - if (type is not null) + if (extensionsType.GetMembers("EqualsOrdinal").Length > 0) { - if (type.GetMembers("EqualsOrdinal").Length > 0) - { - var title = "Use EqualsOrdinal"; - var codeAction = CodeAction.Create( - title, - ct => RefactorExtensionMethod(context.Document, nodeToFix, "EqualsOrdinal", ct), - equivalenceKey: title); - - context.RegisterCodeFix(codeAction, context.Diagnostics); - } - - if (type.GetMembers("EqualsIgnoreCase").Length > 0) - { - var title = "Use EqualsIgnoreCase"; - var codeAction = CodeAction.Create( - title, - ct => RefactorExtensionMethod(context.Document, nodeToFix, "EqualsIgnoreCase", ct), - equivalenceKey: title); - - context.RegisterCodeFix(codeAction, context.Diagnostics); - } + var title = "Use EqualsOrdinal"; + var codeAction = CodeAction.Create( + title, + ct => RefactorExtensionMethod(context.Document, nodeToFix, "EqualsOrdinal", extensionsType, ct), + equivalenceKey: title); + + context.RegisterCodeFix(codeAction, context.Diagnostics); + } + + if (extensionsType.GetMembers("EqualsIgnoreCase").Length > 0) + { + var title = "Use EqualsIgnoreCase"; + var codeAction = CodeAction.Create( + title, + ct => RefactorExtensionMethod(context.Document, nodeToFix, "EqualsIgnoreCase", extensionsType, ct), + equivalenceKey: title); + + context.RegisterCodeFix(codeAction, context.Diagnostics); } } @@ -66,26 +73,20 @@ void RegisterCodeFix(string comparisonMode) var title = "Use String.Equals " + comparisonMode; var codeAction = CodeAction.Create( title, - ct => RefactorStringEquals(context.Document, nodeToFix, comparisonMode, ct), + ct => RefactorStringEquals(context.Document, nodeToFix, comparisonMode, stringComparisonSymbol, ct), equivalenceKey: title); context.RegisterCodeFix(codeAction, context.Diagnostics); } } - private static async Task RefactorStringEquals(Document document, SyntaxNode nodeToFix, string comparisonMode, CancellationToken cancellationToken) + private static async Task RefactorStringEquals(Document document, SyntaxNode nodeToFix, string comparisonMode, INamedTypeSymbol stringComparison, CancellationToken cancellationToken) { var editor = await DocumentEditor.CreateAsync(document, cancellationToken).ConfigureAwait(false); var semanticModel = editor.SemanticModel; var generator = editor.Generator; - var operation = (IBinaryOperation?)semanticModel.GetOperation(nodeToFix, cancellationToken); - if (operation is null) - return document; - - var stringComparison = semanticModel.Compilation.GetBestTypeByMetadataName("System.StringComparison"); - if (stringComparison is null) - return document; + var operation = (IBinaryOperation)semanticModel.GetOperation(nodeToFix, cancellationToken)!; var newExpression = generator.InvocationExpression( generator.MemberAccessExpression(generator.TypeExpression(SpecialType.System_String), nameof(string.Equals)), @@ -102,19 +103,13 @@ private static async Task RefactorStringEquals(Document document, Synt return editor.GetChangedDocument(); } - private static async Task RefactorExtensionMethod(Document document, SyntaxNode nodeToFix, string methodName, CancellationToken cancellationToken) + private static async Task RefactorExtensionMethod(Document document, SyntaxNode nodeToFix, string methodName, INamedTypeSymbol type, CancellationToken cancellationToken) { var editor = await DocumentEditor.CreateAsync(document, cancellationToken).ConfigureAwait(false); var semanticModel = editor.SemanticModel; var generator = editor.Generator; - var operation = (IBinaryOperation?)semanticModel.GetOperation(nodeToFix, cancellationToken); - if (operation is null) - return document; - - var type = semanticModel.Compilation.GetBestTypeByMetadataName("Meziantou.Framework.StringExtensions"); - if (type is null) - return document; + var operation = (IBinaryOperation)semanticModel.GetOperation(nodeToFix, cancellationToken)!; var newExpression = generator.InvocationExpression( generator.MemberAccessExpression(generator.TypeExpression(type), methodName), diff --git a/src/Meziantou.Analyzer.CodeFixers/Rules/UseStructLayoutAttributeFixer.cs b/src/Meziantou.Analyzer.CodeFixers/Rules/UseStructLayoutAttributeFixer.cs index 4cf70f633..52df01911 100644 --- a/src/Meziantou.Analyzer.CodeFixers/Rules/UseStructLayoutAttributeFixer.cs +++ b/src/Meziantou.Analyzer.CodeFixers/Rules/UseStructLayoutAttributeFixer.cs @@ -28,31 +28,37 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context) if (nodeToFix is null or not TypeDeclarationSyntax) return; + var semanticModel = await context.Document.GetSemanticModelAsync(context.CancellationToken).ConfigureAwait(false); + if (semanticModel is null) + return; + + var structLayoutAttribute = semanticModel.Compilation.GetBestTypeByMetadataName("System.Runtime.InteropServices.StructLayoutAttribute"); + if (structLayoutAttribute is null) + return; + + var layoutKindEnum = semanticModel.Compilation.GetBestTypeByMetadataName("System.Runtime.InteropServices.LayoutKind"); + if (layoutKindEnum is null) + return; + context.RegisterCodeFix( CodeAction.Create( "Add Auto StructLayout attribute", - ct => Refactor(context.Document, nodeToFix, LayoutKind.Auto, ct), + ct => Refactor(context.Document, nodeToFix, LayoutKind.Auto, structLayoutAttribute, layoutKindEnum, ct), equivalenceKey: "Add Auto StructLayout attribute"), context.Diagnostics); context.RegisterCodeFix( CodeAction.Create( "Add Sequential StructLayout attribute", - ct => Refactor(context.Document, nodeToFix, LayoutKind.Sequential, ct), + ct => Refactor(context.Document, nodeToFix, LayoutKind.Sequential, structLayoutAttribute, layoutKindEnum, ct), equivalenceKey: "Add Sequential StructLayout attribute"), context.Diagnostics); } - private static async Task Refactor(Document document, SyntaxNode nodeToFix, LayoutKind layoutKind, CancellationToken cancellationToken) + private static async Task Refactor(Document document, SyntaxNode nodeToFix, LayoutKind layoutKind, INamedTypeSymbol structLayoutAttribute, INamedTypeSymbol layoutKindEnum, CancellationToken cancellationToken) { var editor = await DocumentEditor.CreateAsync(document, cancellationToken).ConfigureAwait(false); var generator = editor.Generator; - var semanticModel = editor.SemanticModel; - - var structLayoutAttribute = semanticModel.Compilation.GetBestTypeByMetadataName("System.Runtime.InteropServices.StructLayoutAttribute"); - var layoutKindEnum = semanticModel.Compilation.GetBestTypeByMetadataName("System.Runtime.InteropServices.LayoutKind"); - if (structLayoutAttribute is null || layoutKindEnum is null) - return document; var attribute = editor.Generator.Attribute( generator.TypeExpression(structLayoutAttribute).WithAdditionalAnnotations(Simplifier.AddImportsAnnotation), diff --git a/src/Meziantou.Analyzer.CodeFixers/Rules/UseSystemThreadingLockInsteadOfObjectFixer.cs b/src/Meziantou.Analyzer.CodeFixers/Rules/UseSystemThreadingLockInsteadOfObjectFixer.cs index 1692acf6c..3896eb13b 100644 --- a/src/Meziantou.Analyzer.CodeFixers/Rules/UseSystemThreadingLockInsteadOfObjectFixer.cs +++ b/src/Meziantou.Analyzer.CodeFixers/Rules/UseSystemThreadingLockInsteadOfObjectFixer.cs @@ -27,18 +27,33 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context) if (nodeToFix is null) return; + var semanticModel = await context.Document.GetSemanticModelAsync(context.CancellationToken).ConfigureAwait(false); + if (semanticModel is null) + return; + + var lockType = semanticModel.Compilation.GetBestTypeByMetadataName("System.Threading.Lock"); + if (lockType is null) + return; + + var variableDeclarator = nodeToFix.FirstAncestorOrSelf(); + if (variableDeclarator is null) + return; + + if (semanticModel.GetDeclaredSymbol(variableDeclarator, context.CancellationToken) is null) + return; + + if (variableDeclarator.Parent is not VariableDeclarationSyntax { } declaration || declaration.Variables.Count != 1) + return; + const string Title = "Use System.Threading.Lock"; context.RegisterCodeFix( - CodeAction.Create(Title, ct => UseLockType(context.Document, nodeToFix, ct), equivalenceKey: Title), + CodeAction.Create(Title, ct => UseLockType(context.Document, nodeToFix, lockType, ct), equivalenceKey: Title), context.Diagnostics); } - private static async Task UseLockType(Document document, SyntaxNode nodeToFix, CancellationToken cancellationToken) + private static async Task UseLockType(Document document, SyntaxNode nodeToFix, INamedTypeSymbol lockType, CancellationToken cancellationToken) { var editor = await DocumentEditor.CreateAsync(document, cancellationToken).ConfigureAwait(false); - var lockType = editor.SemanticModel.Compilation.GetBestTypeByMetadataName("System.Threading.Lock"); - if (lockType is null) - return document; var variableDeclarator = nodeToFix.FirstAncestorOrSelf(); if (variableDeclarator is null) diff --git a/src/Meziantou.Analyzer.CodeFixers/Rules/UseTaskUnwrapFixer.cs b/src/Meziantou.Analyzer.CodeFixers/Rules/UseTaskUnwrapFixer.cs index 91f32079a..b7722bea0 100644 --- a/src/Meziantou.Analyzer.CodeFixers/Rules/UseTaskUnwrapFixer.cs +++ b/src/Meziantou.Analyzer.CodeFixers/Rules/UseTaskUnwrapFixer.cs @@ -26,6 +26,17 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context) if (nodeToFix is null) return; + var semanticModel = await context.Document.GetSemanticModelAsync(context.CancellationToken).ConfigureAwait(false); + if (semanticModel is null) + return; + + var awaitOp = FindAwait(semanticModel, nodeToFix, context.CancellationToken); + if (awaitOp is null) + return; + + if (awaitOp.Syntax is not AwaitExpressionSyntax) + return; + const string Title = "Use Unwrap"; context.RegisterCodeFix( CodeAction.Create(Title, ct => UseUnwrap(context.Document, nodeToFix, ct), equivalenceKey: Title), diff --git a/src/Meziantou.Analyzer.CodeFixers/Rules/ValidateArgumentsCorrectlyFixer.cs b/src/Meziantou.Analyzer.CodeFixers/Rules/ValidateArgumentsCorrectlyFixer.cs index 356cc333f..0702d55b6 100755 --- a/src/Meziantou.Analyzer.CodeFixers/Rules/ValidateArgumentsCorrectlyFixer.cs +++ b/src/Meziantou.Analyzer.CodeFixers/Rules/ValidateArgumentsCorrectlyFixer.cs @@ -29,6 +29,16 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context) if (nodeToFix is null) return; + if (nodeToFix is not MethodDeclarationSyntax methodDeclaration) + return; + + if (methodDeclaration.Body is null) + return; + + var semanticModel = await context.Document.GetSemanticModelAsync(context.CancellationToken).ConfigureAwait(false); + if (semanticModel?.GetDeclaredSymbol(nodeToFix, cancellationToken: context.CancellationToken) is not IMethodSymbol) + return; + var diagnostic = context.Diagnostics[0]; var title = "Use local function";