Skip to content

Commit 4b6dcf5

Browse files
cberschjosefpihrt
andauthored
Add analyzer RCS0062, put expression body on its own line (#1575) (#1593)
Co-authored-by: Christoph Bersch <[email protected]> Co-authored-by: Josef Pihrt <[email protected]>
1 parent e262b6b commit 4b6dcf5

File tree

13 files changed

+632
-57
lines changed

13 files changed

+632
-57
lines changed

ChangeLog.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Added
11+
12+
- Add analyzer "Put expression body on its own line" [RCS0062](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS0062) ([PR](https://github.com/dotnet/roslynator/pull/1593) by @cbersch)
13+
- Affects analyzer [RCS1016](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1016)
14+
- Affects refacoring [RR0169](https://josefpihrt.github.io/docs/roslynator/refactorings/RR0169)
15+
1016
## [4.12.11] - 2025-01-28
1117

1218
### Added

src/Analyzers.xml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1629,6 +1629,23 @@ public class C
16291629
</Sample>
16301630
</Samples>
16311631
</Analyzer>
1632+
<Analyzer>
1633+
<Id>RCS0062</Id>
1634+
<Identifier>PutExpressionBodyOnItsOwnLine</Identifier>
1635+
<Title>Put expression body on its own line</Title>
1636+
<DefaultSeverity>Info</DefaultSeverity>
1637+
<IsEnabledByDefault>false</IsEnabledByDefault>
1638+
<Samples>
1639+
<Sample>
1640+
<Before><![CDATA[object Foo() => null;]]></Before>
1641+
<After><![CDATA[object Foo()
1642+
=> null;]]></After>
1643+
</Sample>
1644+
</Samples>
1645+
<ConfigOptions>
1646+
<Option Key="arrow_token_new_line" IsRequired="false" />
1647+
</ConfigOptions>
1648+
</Analyzer>
16321649
<Analyzer>
16331650
<Id>RCS1001</Id>
16341651
<Identifier>AddBracesWhenExpressionSpansOverMultipleLines</Identifier>
@@ -1978,6 +1995,7 @@ foreach (var item in items)
19781995
<Option Key="body_style" IsRequired="true" />
19791996
<Option Key="use_block_body_when_declaration_spans_over_multiple_lines" IsRequired="true" />
19801997
<Option Key="use_block_body_when_expression_spans_over_multiple_lines" IsRequired="true" />
1998+
<Option Key="arrow_token_new_line" IsRequired="false" />
19811999
</ConfigOptions>
19822000
<Options>
19832001
<Option Identifier="ConvertExpressionBodyToBlockBodyWhenExpressionIsMultiLine">
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
using Microsoft.CodeAnalysis.CSharp;
2+
3+
namespace Roslynator.CSharp.Analysis;
4+
5+
internal static class ConvertExpressionBodyAnalysis
6+
{
7+
public static bool AllowPutExpressionBodyOnItsOwnLine(SyntaxKind syntaxKind)
8+
{
9+
// allow putting expression on new line for all method-like declarations, except for accessors.
10+
switch (syntaxKind)
11+
{
12+
case SyntaxKind.MethodDeclaration:
13+
case SyntaxKind.ConstructorDeclaration:
14+
case SyntaxKind.DestructorDeclaration:
15+
case SyntaxKind.PropertyDeclaration:
16+
case SyntaxKind.IndexerDeclaration:
17+
case SyntaxKind.OperatorDeclaration:
18+
case SyntaxKind.ConversionOperatorDeclaration:
19+
return true;
20+
default:
21+
return false;
22+
}
23+
}
24+
}

src/Common/DiagnosticIdentifiers.Generated.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ public static partial class DiagnosticIdentifiers
6161
public const string PlaceNewLineAfterOrBeforeNullConditionalOperator = "RCS0059";
6262
public const string BlankLineAfterFileScopedNamespaceDeclaration = "RCS0060";
6363
public const string BlankLineBetweenSwitchSections = "RCS0061";
64+
public const string PutExpressionBodyOnItsOwnLine = "RCS0062";
6465
public const string AddBracesWhenExpressionSpansOverMultipleLines = "RCS1001";
6566
public const string RemoveBraces = "RCS1002";
6667
public const string AddBracesToIfElseWhenExpressionSpansOverMultipleLines = "RCS1003";

src/Common/DiagnosticRules.Generated.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -645,6 +645,18 @@ public static partial class DiagnosticRules
645645
helpLinkUri: DiagnosticIdentifiers.BlankLineBetweenSwitchSections,
646646
customTags: []);
647647

648+
/// <summary>RCS0062</summary>
649+
public static readonly DiagnosticDescriptor PutExpressionBodyOnItsOwnLine = DiagnosticDescriptorFactory.Create(
650+
id: DiagnosticIdentifiers.PutExpressionBodyOnItsOwnLine,
651+
title: "Put expression body on its own line",
652+
messageFormat: "Put expression body on its own line",
653+
category: DiagnosticCategories.Roslynator,
654+
defaultSeverity: DiagnosticSeverity.Info,
655+
isEnabledByDefault: false,
656+
description: null,
657+
helpLinkUri: DiagnosticIdentifiers.PutExpressionBodyOnItsOwnLine,
658+
customTags: []);
659+
648660
/// <summary>RCS1001</summary>
649661
public static readonly DiagnosticDescriptor AddBracesWhenExpressionSpansOverMultipleLines = DiagnosticDescriptorFactory.Create(
650662
id: DiagnosticIdentifiers.AddBracesWhenExpressionSpansOverMultipleLines,

src/Formatting.Analyzers.CodeFixes/CSharp/SyntaxTokenCodeFixProvider.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ public override ImmutableArray<string> FixableDiagnosticIds
2424
DiagnosticIdentifiers.PlaceNewLineAfterOrBeforeArrowToken,
2525
DiagnosticIdentifiers.PlaceNewLineAfterOrBeforeEqualsToken,
2626
DiagnosticIdentifiers.PutAttributeListOnItsOwnLine,
27-
DiagnosticIdentifiers.AddOrRemoveNewLineBeforeWhileInDoStatement);
27+
DiagnosticIdentifiers.AddOrRemoveNewLineBeforeWhileInDoStatement,
28+
DiagnosticIdentifiers.PutExpressionBodyOnItsOwnLine);
2829
}
2930
}
3031

@@ -60,6 +61,11 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context)
6061
await CodeActionFactory.RegisterCodeActionForNewLineAsync(context).ConfigureAwait(false);
6162
break;
6263
}
64+
case DiagnosticIdentifiers.PutExpressionBodyOnItsOwnLine:
65+
{
66+
await CodeActionFactory.RegisterCodeActionForNewLineAsync(context, increaseIndentation: true).ConfigureAwait(false);
67+
break;
68+
}
6369
}
6470
}
6571
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
// Copyright (c) .NET Foundation and Contributors. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
2+
3+
using System.Collections.Immutable;
4+
using Microsoft.CodeAnalysis;
5+
using Microsoft.CodeAnalysis.CSharp;
6+
using Microsoft.CodeAnalysis.CSharp.Syntax;
7+
using Microsoft.CodeAnalysis.Diagnostics;
8+
using Roslynator.CSharp;
9+
using Roslynator.CSharp.Analysis;
10+
using Roslynator.CSharp.CodeStyle;
11+
12+
namespace Roslynator.Formatting.CSharp;
13+
14+
[DiagnosticAnalyzer(LanguageNames.CSharp)]
15+
public sealed class PutExpressionBodyOnItsOwnLineAnalyzer : BaseDiagnosticAnalyzer
16+
{
17+
private static ImmutableArray<DiagnosticDescriptor> _supportedDiagnostics;
18+
19+
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics
20+
{
21+
get
22+
{
23+
if (_supportedDiagnostics.IsDefault)
24+
Immutable.InterlockedInitialize(ref _supportedDiagnostics, DiagnosticRules.PutExpressionBodyOnItsOwnLine);
25+
26+
return _supportedDiagnostics;
27+
}
28+
}
29+
30+
public override void Initialize(AnalysisContext context)
31+
{
32+
base.Initialize(context);
33+
34+
context.RegisterSyntaxNodeAction(f => AnalyzeArrowExpressionClause(f), SyntaxKind.ArrowExpressionClause);
35+
}
36+
37+
private static void AnalyzeArrowExpressionClause(SyntaxNodeAnalysisContext context)
38+
{
39+
var arrowExpressionClause = (ArrowExpressionClauseSyntax)context.Node;
40+
41+
if (ConvertExpressionBodyAnalysis.AllowPutExpressionBodyOnItsOwnLine(arrowExpressionClause.Parent.Kind()))
42+
{
43+
AnalyzeArrowExpressionClause(arrowExpressionClause.ArrowToken, context);
44+
}
45+
}
46+
47+
private static void AnalyzeArrowExpressionClause(SyntaxToken arrowToken, SyntaxNodeAnalysisContext context)
48+
{
49+
NewLinePosition newLinePosition = context.GetArrowTokenNewLinePosition();
50+
51+
SyntaxToken first;
52+
SyntaxToken second;
53+
if (newLinePosition == NewLinePosition.After)
54+
{
55+
first = arrowToken;
56+
second = arrowToken.GetNextToken();
57+
}
58+
else
59+
{
60+
first = arrowToken.GetPreviousToken();
61+
second = arrowToken;
62+
}
63+
64+
TriviaBlock block = TriviaBlock.FromBetween(first, second);
65+
66+
if (block.Kind == TriviaBlockKind.NoNewLine)
67+
{
68+
DiagnosticHelpers.ReportDiagnostic(
69+
context,
70+
DiagnosticRules.PutExpressionBodyOnItsOwnLine,
71+
block.GetLocation());
72+
}
73+
}
74+
}

src/Refactorings.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1606,6 +1606,9 @@ i--;]]></After>
16061606
<Syntax>operator</Syntax>
16071607
</Syntaxes>
16081608
<Span>body or accessor list</Span>
1609+
<ConfigOptions>
1610+
<Option Key="arrow_token_new_line" IsRequired="false" />
1611+
</ConfigOptions>
16091612
</Refactoring>
16101613
<Refactoring Id="RR0170" Identifier="UseLambdaInsteadOfAnonymousMethod" Title="Use lambda instead of anonymous method">
16111614
<OptionKey>use_lambda_instead_of_anonymous_method</OptionKey>

0 commit comments

Comments
 (0)