UseAutoPropertyAnalyzer.cs 8.3 KB
Newer Older
1 2
// Copyright (c) Microsoft.  All Rights Reserved.  Licensed under the Apache License, Version 2.0.  See License.txt in the project root for license information.

3
using System.Collections.Generic;
4
using System.ComponentModel.Composition;
5
using System.Linq;
6
using System.Threading;
7
using Microsoft.CodeAnalysis.CSharp;
8
using Microsoft.CodeAnalysis.CSharp.Extensions;
9 10
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
11
using Microsoft.CodeAnalysis.UseAutoProperty;
12 13 14

namespace Microsoft.CodeAnalysis.Editor.CSharp.UseAutoProperty
{
15 16
    [Export]
    [DiagnosticAnalyzer(LanguageNames.CSharp)]
17
    internal class CSharpUseAutoPropertyAnalyzer : AbstractUseAutoPropertyAnalyzer<PropertyDeclarationSyntax, FieldDeclarationSyntax, VariableDeclaratorSyntax, ExpressionSyntax>
18
    {
C
Cyrus Najmabadi 已提交
19
        protected override bool SupportsReadOnlyProperties(Compilation compilation)
20 21 22 23 24 25 26 27 28 29 30 31 32
            => ((CSharpCompilation)compilation).LanguageVersion >= LanguageVersion.CSharp6;

        protected override bool SupportsPropertyInitializer(Compilation compilation)
            => ((CSharpCompilation)compilation).LanguageVersion >= LanguageVersion.CSharp6;

        protected override void AnalyzeCompilationUnit(
            SemanticModelAnalysisContext context, SyntaxNode root, List<AnalysisResult> analysisResults)
            => AnalyzeMembers(context, ((CompilationUnitSyntax)root).Members, analysisResults);

        private void AnalyzeMembers(
            SemanticModelAnalysisContext context, 
            SyntaxList<MemberDeclarationSyntax> members,
            List<AnalysisResult> analysisResults)
33
        {
34 35 36 37
            foreach (var memberDeclaration in members)
            {
                AnalyzeMemberDeclaration(context, memberDeclaration, analysisResults);
            }
38 39
        }

40 41 42 43
        private void AnalyzeMemberDeclaration(
            SemanticModelAnalysisContext context,
            MemberDeclarationSyntax member,
            List<AnalysisResult> analysisResults)
44
        {
45 46 47 48
            if (member.IsKind(SyntaxKind.NamespaceDeclaration, out NamespaceDeclarationSyntax namespaceDeclaration))
            {
                AnalyzeMembers(context, namespaceDeclaration.Members, analysisResults);
            }
C
Cyrus Najmabadi 已提交
49
            else if (member.IsKind(SyntaxKind.ClassDeclaration, out TypeDeclarationSyntax typeDeclaration) ||
50 51
                member.IsKind(SyntaxKind.StructDeclaration, out typeDeclaration))
            {
C
Cyrus Najmabadi 已提交
52
                // If we have a class or struct, recurse inwards.
53 54
                AnalyzeMembers(context, typeDeclaration.Members, analysisResults);
            }
C
Cyrus Najmabadi 已提交
55
            else if (member.IsKind(SyntaxKind.PropertyDeclaration, out PropertyDeclarationSyntax propertyDeclaration))
56
            {
C
Cyrus Najmabadi 已提交
57
                AnalyzeProperty(context, propertyDeclaration, analysisResults);
58
            }
59 60
        }

61 62 63
        protected override void RegisterIneligibleFieldsAction(
            List<AnalysisResult> analysisResults, HashSet<IFieldSymbol> ineligibleFields,
            Compilation compilation, CancellationToken cancellationToken)
64
        {
65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81
            var groups = analysisResults.Select(r => (TypeDeclarationSyntax)r.PropertyDeclaration.Parent)
                                        .Distinct()
                                        .GroupBy(n => n.SyntaxTree);

            foreach (var group in groups)
            {
                var tree = group.Key;
                var semanticModel = compilation.GetSemanticModel(tree);

                foreach (var typeDeclaration in group)
                {
                    foreach (var argument in typeDeclaration.DescendantNodesAndSelf().OfType<ArgumentSyntax>())
                    {
                        AnalyzeArgument(semanticModel, argument, ineligibleFields, cancellationToken);
                    }
                }
            }
82
        }
83

84 85 86 87 88
        protected override ExpressionSyntax GetFieldInitializer(VariableDeclaratorSyntax variable, CancellationToken cancellationToken)
        {
            return variable.Initializer?.Value;
        }

89 90 91
        private void AnalyzeArgument(
            SemanticModel semanticModel, ArgumentSyntax argument,
            HashSet<IFieldSymbol> ineligibleFields, CancellationToken cancellationToken)
92 93 94 95 96 97 98 99
        {
            // An argument will disqualify a field if that field is used in a ref/out position.  
            // We can't change such field references to be property references in C#.
            if (argument.RefOrOutKeyword.Kind() == SyntaxKind.None)
            {
                return;
            }

100
            var symbolInfo = semanticModel.GetSymbolInfo(argument.Expression, cancellationToken);
101 102
            AddIneligibleField(symbolInfo.Symbol, ineligibleFields);
            foreach (var symbol in symbolInfo.CandidateSymbols)
103
            {
104
                AddIneligibleField(symbol, ineligibleFields);
105 106 107
            }
        }

108
        private static void AddIneligibleField(ISymbol symbol, HashSet<IFieldSymbol> ineligibleFields)
109
        {
C
CyrusNajmabadi 已提交
110
            if (symbol is IFieldSymbol field)
111 112 113 114 115
            {
                ineligibleFields.Add(field);
            }
        }

116
        private bool CheckExpressionSyntactically(ExpressionSyntax expression)
117
        {
B
Bart Koelman 已提交
118
            if (expression.IsKind(SyntaxKind.SimpleMemberAccessExpression))
119
            {
120 121 122
                var memberAccessExpression = (MemberAccessExpressionSyntax)expression;
                return memberAccessExpression.Expression.Kind() == SyntaxKind.ThisExpression &&
                    memberAccessExpression.Name.Kind() == SyntaxKind.IdentifierName;
123
            }
B
Bart Koelman 已提交
124 125 126 127
            else if (expression.IsKind(SyntaxKind.IdentifierName))
            {
                return true;
            }
128

B
Bart Koelman 已提交
129
            return false;
130
        }
131

132 133
        protected override ExpressionSyntax GetGetterExpression(IMethodSymbol getMethod, CancellationToken cancellationToken)
        {
134 135 136 137
            // Getter has to be of the form:
            //
            //     get { return field; } or
            //     get { return this.field; }
138
            var getAccessor = getMethod.DeclaringSyntaxReferences[0].GetSyntax(cancellationToken) as AccessorDeclarationSyntax;
139 140
            var statements = getAccessor?.Body?.Statements;
            if (statements?.Count == 1)
141
            {
142 143
                var statement = statements.Value[0];
                if (statement.Kind() == SyntaxKind.ReturnStatement)
144
                {
145
                    var expr = ((ReturnStatementSyntax)statement).Expression;
146 147
                    return CheckExpressionSyntactically(expr) ? expr : null;
                }
148 149
            }

150
            return null;
151 152
        }

153
        protected override ExpressionSyntax GetSetterExpression(IMethodSymbol setMethod, SemanticModel semanticModel, CancellationToken cancellationToken)
154
        {
155 156 157 158
            // Setter has to be of the form:
            //
            //     set { field = value; } or
            //     set { this.field = value; }
159
            var setAccessor = setMethod.DeclaringSyntaxReferences[0].GetSyntax(cancellationToken) as AccessorDeclarationSyntax;
160 161
            var statements = setAccessor?.Body?.Statements;
            if (statements?.Count == 1)
162
            {
163
                var statement = statements.Value[0];
B
Bart Koelman 已提交
164
                if (statement.IsKind(SyntaxKind.ExpressionStatement))
165
                {
166
                    var expressionStatement = (ExpressionStatementSyntax)statement;
167
                    if (expressionStatement.Expression.Kind() == SyntaxKind.SimpleAssignmentExpression)
168
                    {
169 170 171 172 173 174
                        var assignmentExpression = (AssignmentExpressionSyntax)expressionStatement.Expression;
                        if (assignmentExpression.Right.Kind() == SyntaxKind.IdentifierName &&
                            ((IdentifierNameSyntax)assignmentExpression.Right).Identifier.ValueText == "value")
                        {
                            return CheckExpressionSyntactically(assignmentExpression.Left) ? assignmentExpression.Left : null;
                        }
175 176 177 178
                    }
                }
            }

179
            return null;
180 181
        }

182
        protected override SyntaxNode GetNodeToFade(FieldDeclarationSyntax fieldDeclaration, VariableDeclaratorSyntax variableDeclarator)
183
        {
184
            return fieldDeclaration.Declaration.Variables.Count == 1
185 186 187 188
                ? fieldDeclaration
                : (SyntaxNode)variableDeclarator;
        }
    }
S
Sam Harwell 已提交
189
}