File: src\Analyzers\CSharp\Analyzers\SimplifyPropertyAccessor\CSharpSimplifyPropertyAccessorDiagnosticAnalyzer.cs
Web Access
Project: src\src\CodeStyle\CSharp\Analyzers\Microsoft.CodeAnalysis.CSharp.CodeStyle.csproj (Microsoft.CodeAnalysis.CSharp.CodeStyle)
// 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.
 
using System.Linq;
using Microsoft.CodeAnalysis.CodeStyle;
using Microsoft.CodeAnalysis.CSharp.CodeStyle;
using Microsoft.CodeAnalysis.CSharp.Extensions;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
 
namespace Microsoft.CodeAnalysis.CSharp.SimplifyPropertyAccessor;
 
[DiagnosticAnalyzer(LanguageNames.CSharp)]
internal sealed class CSharpSimplifyPropertyAccessorDiagnosticAnalyzer : AbstractBuiltInCodeStyleDiagnosticAnalyzer
{
    public CSharpSimplifyPropertyAccessorDiagnosticAnalyzer()
        : base(IDEDiagnosticIds.SimplifyPropertyAccessorDiagnosticId,
               EnforceOnBuildValues.SimplifyPropertyAccessor,
               CSharpCodeStyleOptions.PreferSimplePropertyAccessors,
               new LocalizableResourceString(nameof(CSharpAnalyzersResources.Simplify_property_accessor), CSharpAnalyzersResources.ResourceManager, typeof(CSharpAnalyzersResources)),
               new LocalizableResourceString(nameof(CSharpAnalyzersResources.Property_accessor_can_be_simplified), CSharpAnalyzersResources.ResourceManager, typeof(CSharpAnalyzersResources)))
    {
    }
 
    public override DiagnosticAnalyzerCategory GetAnalyzerCategory()
        => DiagnosticAnalyzerCategory.SyntaxTreeWithoutSemanticsAnalysis;
 
    protected override void InitializeWorker(AnalysisContext context)
        => context.RegisterSyntaxNodeAction(AnalyzePropertyDeclaration, SyntaxKind.PropertyDeclaration);
 
    private void AnalyzePropertyDeclaration(SyntaxNodeAnalysisContext context)
    {
        var option = context.GetCSharpAnalyzerOptions().PreferSimplePropertyAccessors;
        if (!option.Value || ShouldSkipAnalysis(context, option.Notification))
            return;
 
        var propertyDeclaration = (PropertyDeclarationSyntax)context.Node;
 
        if (propertyDeclaration.AccessorList is not { } accessorList ||
            accessorList.GetDiagnostics().Any(d => d.Severity == DiagnosticSeverity.Error))
        {
            return;
        }
 
        foreach (var accessor in accessorList.Accessors)
        {
            // get { return field; }
            // get => field;
            if (accessor is (SyntaxKind.GetAccessorDeclaration) { Body.Statements: [ReturnStatementSyntax { Expression.RawKind: (int)SyntaxKind.FieldExpression }] }
                         or (SyntaxKind.GetAccessorDeclaration) { ExpressionBody.Expression.RawKind: (int)SyntaxKind.FieldExpression })
            {
                ReportIfValid(accessor);
            }
 
            // set/init { field = value; }
            if (accessor is (SyntaxKind.SetAccessorDeclaration or SyntaxKind.InitAccessorDeclaration) { Body.Statements: [ExpressionStatementSyntax { Expression: var innerBlockBodyExpression }] } &&
                IsFieldValueAssignmentExpression(innerBlockBodyExpression))
            {
                ReportIfValid(accessor);
            }
 
            // set/init => field = value;
            if (accessor is (SyntaxKind.SetAccessorDeclaration or SyntaxKind.InitAccessorDeclaration) { ExpressionBody.Expression: var innerExpressionBodyExpression } &&
                IsFieldValueAssignmentExpression(innerExpressionBodyExpression))
            {
                ReportIfValid(accessor);
            }
        }
 
        static bool IsFieldValueAssignmentExpression(ExpressionSyntax expression)
        {
            return expression is AssignmentExpressionSyntax(SyntaxKind.SimpleAssignmentExpression)
            {
                Left.RawKind: (int)SyntaxKind.FieldExpression,
                Right: IdentifierNameSyntax { Identifier.ValueText: "value" }
            };
        }
 
        void ReportIfValid(AccessorDeclarationSyntax accessorDeclaration)
        {
            // If we are analyzing an accessor of a partial property and all other accessors have no bodies
            // then if we simplify our current accessor the property will no longer be a valid
            // implementation part. Thus we block that case
            if (accessorDeclaration is { Parent: AccessorListSyntax { Parent: BasePropertyDeclarationSyntax containingPropertyDeclaration } containingAccessorList } &&
                containingPropertyDeclaration.Modifiers.Any(SyntaxKind.PartialKeyword) &&
                containingAccessorList.Accessors.All(a => ReferenceEquals(a, accessorDeclaration) || a is { Body: null, ExpressionBody: null }))
            {
                return;
            }
 
            context.ReportDiagnostic(DiagnosticHelper.Create(
                Descriptor,
                accessorDeclaration.GetLocation(),
                option.Notification,
                context.Options,
                additionalLocations: null,
                properties: null));
        }
    }
}