CSharpAsAndNullCheckCodeFixProvider.cs 7.6 KB
Newer Older
J
Jonathon Marolf 已提交
1 2 3
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
4 5 6 7

using System;
using System.Collections.Immutable;
using System.Composition;
8
using System.Diagnostics.CodeAnalysis;
9 10 11 12 13
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeFixes;
C
CyrusNajmabadi 已提交
14
using Microsoft.CodeAnalysis.CSharp.Extensions;
15 16 17
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Editing;
18
using Microsoft.CodeAnalysis.PooledObjects;
19 20 21
using Microsoft.CodeAnalysis.Shared.Extensions;
using Roslyn.Utilities;

22
namespace Microsoft.CodeAnalysis.CSharp.UsePatternMatching
23 24
{
    [ExportCodeFixProvider(LanguageNames.CSharp), Shared]
25
    internal partial class CSharpAsAndNullCheckCodeFixProvider : SyntaxEditorBasedCodeFixProvider
26
    {
27
        [ImportingConstructor]
28
        [SuppressMessage("RoslynDiagnosticsReliability", "RS0033:Importing constructor should be [Obsolete]", Justification = "Used in test code: https://github.com/dotnet/roslyn/issues/42814")]
29 30 31 32
        public CSharpAsAndNullCheckCodeFixProvider()
        {
        }

33
        public override ImmutableArray<string> FixableDiagnosticIds
34
            => ImmutableArray.Create(IDEDiagnosticIds.InlineAsTypeCheckId);
35

36 37
        internal sealed override CodeFixCategory CodeFixCategory => CodeFixCategory.CodeStyle;

38 39 40 41 42
        public override Task RegisterCodeFixesAsync(CodeFixContext context)
        {
            context.RegisterCodeFix(new MyCodeAction(
                c => FixAsync(context.Document, context.Diagnostics.First(), c)),
                context.Diagnostics);
43
            return Task.CompletedTask;
44 45
        }

46
        protected override async Task FixAllAsync(
47 48
            Document document, ImmutableArray<Diagnostic> diagnostics,
            SyntaxEditor editor, CancellationToken cancellationToken)
49
        {
50 51
            using var _1 = PooledHashSet<Location>.GetInstance(out var declaratorLocations);
            using var _2 = PooledHashSet<SyntaxNode>.GetInstance(out var statementParentScopes);
52

53 54 55
            var tree = await document.GetRequiredSyntaxTreeAsync(cancellationToken).ConfigureAwait(false);
            var languageVersion = ((CSharpParseOptions)tree.Options).LanguageVersion;

56 57 58
            foreach (var diagnostic in diagnostics)
            {
                cancellationToken.ThrowIfCancellationRequested();
A
Alireza Habibi 已提交
59

A
Alireza Habibi 已提交
60
                if (declaratorLocations.Add(diagnostic.AdditionalLocations[0]))
A
Alireza Habibi 已提交
61
                {
62
                    AddEdits(editor, diagnostic, languageVersion, RemoveStatement, cancellationToken);
A
Alireza Habibi 已提交
63
                }
64 65
            }

66
            foreach (var parentScope in statementParentScopes)
E
Emile Fokkema 已提交
67
            {
68 69 70 71 72 73 74
                editor.ReplaceNode(parentScope, (newParentScope, syntaxGenerator) =>
                {
                    var firstStatement = newParentScope is BlockSyntax
                        ? ((BlockSyntax)newParentScope).Statements.First()
                        : ((SwitchSectionSyntax)newParentScope).Statements.First();
                    return syntaxGenerator.ReplaceNode(newParentScope, firstStatement, firstStatement.WithoutLeadingBlankLinesInTrivia());
                });
E
Emile Fokkema 已提交
75 76
            }

77
            return;
78 79 80 81 82 83 84 85 86

            void RemoveStatement(StatementSyntax statement)
            {
                editor.RemoveNode(statement, SyntaxRemoveOptions.KeepUnbalancedDirectives);
                if (statement.Parent is BlockSyntax || statement.Parent is SwitchSectionSyntax)
                {
                    statementParentScopes.Add(statement.Parent);
                }
            }
87 88
        }

89
        private static void AddEdits(
S
Sam Harwell 已提交
90 91
            SyntaxEditor editor,
            Diagnostic diagnostic,
92
            LanguageVersion languageVersion,
93
            Action<StatementSyntax> removeStatement,
94
            CancellationToken cancellationToken)
95
        {
A
Alireza Habibi 已提交
96
            var declaratorLocation = diagnostic.AdditionalLocations[0];
A
Alireza Habibi 已提交
97
            var comparisonLocation = diagnostic.AdditionalLocations[1];
A
Alireza Habibi 已提交
98
            var asExpressionLocation = diagnostic.AdditionalLocations[2];
99

A
Alireza Habibi 已提交
100
            var declarator = (VariableDeclaratorSyntax)declaratorLocation.FindNode(cancellationToken);
101
            var comparison = (ExpressionSyntax)comparisonLocation.FindNode(cancellationToken);
102
            var asExpression = (BinaryExpressionSyntax)asExpressionLocation.FindNode(cancellationToken);
103 104 105 106

            var rightSideOfComparison = comparison is BinaryExpressionSyntax binaryExpression
                ? (SyntaxNode)binaryExpression.Right
                : ((IsPatternExpressionSyntax)comparison).Pattern;
A
Alireza Habibi 已提交
107
            var newIdentifier = declarator.Identifier
108
                .WithoutTrivia().WithTrailingTrivia(rightSideOfComparison.GetTrailingTrivia());
109

110 111 112
            var declarationPattern = SyntaxFactory.DeclarationPattern(
                ((TypeSyntax)asExpression.Right).WithoutTrivia().WithTrailingTrivia(SyntaxFactory.ElasticMarker),
                SyntaxFactory.SingleVariableDesignation(newIdentifier));
113

114
            var condition = GetCondition(languageVersion, comparison, asExpression, declarationPattern);
115

D
dotnet-bot 已提交
116 117
            if (declarator.Parent is VariableDeclarationSyntax declaration &&
                declaration.Parent is LocalDeclarationStatementSyntax localDeclaration &&
A
Alireza Habibi 已提交
118
                declaration.Variables.Count == 1)
C
CyrusNajmabadi 已提交
119
            {
A
Alireza Habibi 已提交
120 121 122 123 124 125
                // Trivia on the local declaration will move to the next statement.
                // use the callback form as the next statement may be the place where we're
                // inlining the declaration, and thus need to see the effects of that change.
                editor.ReplaceNode(
                    localDeclaration.GetNextStatement(),
                    (s, g) => s.WithPrependedNonIndentationTriviaFrom(localDeclaration));
E
Emile Fokkema 已提交
126

127
                removeStatement(localDeclaration);
E
Emile Fokkema 已提交
128 129 130 131
            }
            else
            {
                editor.RemoveNode(declarator, SyntaxRemoveOptions.KeepUnbalancedDirectives);
A
Alireza Habibi 已提交
132 133
            }

134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159
            editor.ReplaceNode(comparison, condition.WithTriviaFrom(comparison));
        }

        private static ExpressionSyntax GetCondition(
            LanguageVersion languageVersion,
            ExpressionSyntax comparison,
            BinaryExpressionSyntax asExpression,
            DeclarationPatternSyntax declarationPattern)
        {
            var isPatternExpression = SyntaxFactory.IsPatternExpression(asExpression.Left, declarationPattern);

            // We should negate the is-expression if we have something like "x == null" or "x is null"
            if (!comparison.IsKind(SyntaxKind.EqualsExpression, SyntaxKind.IsPatternExpression))
                return isPatternExpression;

#if !CODE_STYLE
            if (languageVersion >= LanguageVersion.CSharp9)
            {
                // In C# 9 and higher, convert to `x is not string s`.
                return isPatternExpression.WithPattern(
                    SyntaxFactory.UnaryPattern(SyntaxFactory.Token(SyntaxKind.NotKeyword), isPatternExpression.Pattern));
            }
#endif

            // In C# 8 and lower, convert to `!(x is string s)`
            return SyntaxFactory.PrefixUnaryExpression(SyntaxKind.LogicalNotExpression, isPatternExpression.Parenthesize());
160 161
        }

162
        private class MyCodeAction : CustomCodeActions.DocumentChangeAction
163 164
        {
            public MyCodeAction(Func<CancellationToken, Task<Document>> createChangedDocument)
165
                : base(CSharpAnalyzersResources.Use_pattern_matching, createChangedDocument)
166 167 168 169
            {
            }
        }
    }
S
Sam Harwell 已提交
170
}