-
Notifications
You must be signed in to change notification settings - Fork 4.2k
Implement "Simplify property accessor" feature #79754
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 17 commits
4fe3f05
68f3eab
e577563
3668730
6bc785b
bcd7e2f
4c205b1
73cfcc4
b6bc96d
4c1a286
df79dd2
6de3138
4e834c0
c4a811e
f37fb7c
cd9d9b9
941dc06
02263a7
68d831d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
// 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); | ||
} | ||
} | ||
|
||
// One can argue that these are semantic-level checks and they would be correct. | ||
// However it is very simple to perform them on our own to not fall | ||
// from the quick and efficient syntax-only analyzer path | ||
static bool IsValidAccessorList(AccessorListSyntax accessorList) | ||
Check failure on line 74 in src/Analyzers/CSharp/Analyzers/SimplifyPropertyAccessor/CSharpSimplifyPropertyAccessorDiagnosticAnalyzer.cs
|
||
{ | ||
var accessors = accessorList.Accessors; | ||
if (accessors.Count > 2) | ||
return false; | ||
|
||
if (accessors is [var accessor1, var accessor2] && | ||
accessor1.RawKind == accessor2.RawKind) | ||
{ | ||
return false; | ||
} | ||
|
||
return !accessorList.GetDiagnostics().Any(d => d.Severity == DiagnosticSeverity.Error); | ||
} | ||
|
||
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.
Uh oh!
There was an error while loading. Please reload this page.