Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/Analyzers/CSharp/Analyzers/CSharpAnalyzers.projitems
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@
<Compile Include="$(MSBuildThisFileDirectory)SimplifyInterpolation\CSharpSimplifyInterpolationDiagnosticAnalyzer.cs" />
<Compile Include="$(MSBuildThisFileDirectory)SimplifyLinqExpression\CSharpSimplifyLinqExpressionDiagnosticAnalyzer.cs" />
<Compile Include="$(MSBuildThisFileDirectory)SimplifyLinqExpression\CSharpSimplifyLinqTypeCheckAndCastDiagnosticAnalyzer.cs" />
<Compile Include="$(MSBuildThisFileDirectory)SimplifyPropertyAccessor\CSharpSimplifyPropertyAccessorDiagnosticAnalyzer.cs" />
<Compile Include="$(MSBuildThisFileDirectory)SimplifyPropertyPattern\CSharpSimplifyPropertyPatternDiagnosticAnalyzer.cs" />
<Compile Include="$(MSBuildThisFileDirectory)SimplifyPropertyPattern\SimplifyPropertyPatternHelpers.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ConvertProgram\ConvertToProgramMainDiagnosticAnalyzer.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -434,4 +434,10 @@
<data name="Implement_with_Copilot" xml:space="preserve">
<value>Implement with Copilot</value>
</data>
<data name="Simplify_property_accessor" xml:space="preserve">
<value>Simplify property accessor</value>
</data>
<data name="Property_accessor_can_be_simplified" xml:space="preserve">
<value>Property accessor can be simplified</value>
</data>
</root>
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ internal CSharpSimplifierOptions GetSimplifierOptions()
public CodeStyleOption2<bool> PreferPrimaryConstructors => GetOption(CSharpCodeStyleOptions.PreferPrimaryConstructors);
public CodeStyleOption2<bool> PreferSystemThreadingLock => GetOption(CSharpCodeStyleOptions.PreferSystemThreadingLock);
public CodeStyleOption2<bool> PreferUnboundGenericTypeInNameOf => GetOption(CSharpCodeStyleOptions.PreferUnboundGenericTypeInNameOf);
public CodeStyleOption2<bool> PreferSimplePropertyAccessors => GetOption(CSharpCodeStyleOptions.PreferSimplePropertyAccessors);

// CodeGenerationOptions

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
// 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));
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading