diff --git a/src/Analyzers.xml b/src/Analyzers.xml
index 3668c8bb60..f9cb4d3e1a 100644
--- a/src/Analyzers.xml
+++ b/src/Analyzers.xml
@@ -1629,6 +1629,23 @@ public class C
+
+ RCS0062
+ PutExpressionBodyOnItsOwnLine
+ Put expression body on its own line
+ Info
+ false
+
+
+ null;]]>
+ null;]]>
+
+
+
+
+
+
RCS1001
AddBracesWhenExpressionSpansOverMultipleLines
@@ -7973,4 +7990,4 @@ class FooCodeFixProvider : CodeFixProvider
-
+
\ No newline at end of file
diff --git a/src/Formatting.Analyzers.CodeFixes/CSharp/SyntaxTokenCodeFixProvider.cs b/src/Formatting.Analyzers.CodeFixes/CSharp/SyntaxTokenCodeFixProvider.cs
index 6d6330cd05..c51dd4a5a6 100644
--- a/src/Formatting.Analyzers.CodeFixes/CSharp/SyntaxTokenCodeFixProvider.cs
+++ b/src/Formatting.Analyzers.CodeFixes/CSharp/SyntaxTokenCodeFixProvider.cs
@@ -25,7 +25,8 @@ public override ImmutableArray FixableDiagnosticIds
DiagnosticIdentifiers.PlaceNewLineAfterOrBeforeArrowToken,
DiagnosticIdentifiers.PlaceNewLineAfterOrBeforeEqualsToken,
DiagnosticIdentifiers.PutAttributeListOnItsOwnLine,
- DiagnosticIdentifiers.AddOrRemoveNewLineBeforeWhileInDoStatement);
+ DiagnosticIdentifiers.AddOrRemoveNewLineBeforeWhileInDoStatement,
+ DiagnosticIdentifiers.PutExpressionBodyOnItsOwnLine);
}
}
@@ -61,6 +62,11 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context)
await CodeActionFactory.RegisterCodeActionForNewLineAsync(context).ConfigureAwait(false);
break;
}
+ case DiagnosticIdentifiers.PutExpressionBodyOnItsOwnLine:
+ {
+ await CodeActionFactory.RegisterCodeActionForNewLineAsync(context, increaseIndentation: true).ConfigureAwait(false);
+ break;
+ }
}
}
}
diff --git a/src/Formatting.Analyzers/CSharp/DiagnosticIdentifiers.Generated.cs b/src/Formatting.Analyzers/CSharp/DiagnosticIdentifiers.Generated.cs
index 937b9df58b..d99ce3aab4 100644
--- a/src/Formatting.Analyzers/CSharp/DiagnosticIdentifiers.Generated.cs
+++ b/src/Formatting.Analyzers/CSharp/DiagnosticIdentifiers.Generated.cs
@@ -61,5 +61,6 @@ public static partial class DiagnosticIdentifiers
public const string PlaceNewLineAfterOrBeforeNullConditionalOperator = "RCS0059";
public const string BlankLineAfterFileScopedNamespaceDeclaration = "RCS0060";
public const string BlankLineBetweenSwitchSections = "RCS0061";
+ public const string PutExpressionBodyOnItsOwnLine = "RCS0062";
}
}
\ No newline at end of file
diff --git a/src/Formatting.Analyzers/CSharp/DiagnosticRules.Generated.cs b/src/Formatting.Analyzers/CSharp/DiagnosticRules.Generated.cs
index bb553cf2b9..26d94d82d7 100644
--- a/src/Formatting.Analyzers/CSharp/DiagnosticRules.Generated.cs
+++ b/src/Formatting.Analyzers/CSharp/DiagnosticRules.Generated.cs
@@ -645,5 +645,17 @@ public static partial class DiagnosticRules
helpLinkUri: DiagnosticIdentifiers.BlankLineBetweenSwitchSections,
customTags: Array.Empty());
+ /// RCS0062
+ public static readonly DiagnosticDescriptor PutExpressionBodyOnItsOwnLine = DiagnosticDescriptorFactory.Create(
+ id: DiagnosticIdentifiers.PutExpressionBodyOnItsOwnLine,
+ title: "Put expression body on its own line",
+ messageFormat: "Put expression body on its own line",
+ category: DiagnosticCategories.Roslynator,
+ defaultSeverity: DiagnosticSeverity.Info,
+ isEnabledByDefault: false,
+ description: null,
+ helpLinkUri: DiagnosticIdentifiers.PutExpressionBodyOnItsOwnLine,
+ customTags: Array.Empty());
+
}
}
\ No newline at end of file
diff --git a/src/Formatting.Analyzers/CSharp/PutExpressionBodyOnItsOwnLineAnalyzer.cs b/src/Formatting.Analyzers/CSharp/PutExpressionBodyOnItsOwnLineAnalyzer.cs
new file mode 100644
index 0000000000..2ccb453d5c
--- /dev/null
+++ b/src/Formatting.Analyzers/CSharp/PutExpressionBodyOnItsOwnLineAnalyzer.cs
@@ -0,0 +1,81 @@
+// Copyright (c) .NET Foundation and Contributors. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Collections.Immutable;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Microsoft.CodeAnalysis.Diagnostics;
+using Roslynator.CSharp;
+using Roslynator.CSharp.CodeStyle;
+
+namespace Roslynator.Formatting.CSharp;
+
+[DiagnosticAnalyzer(LanguageNames.CSharp)]
+public sealed class PutExpressionBodyOnItsOwnLineAnalyzer : BaseDiagnosticAnalyzer
+{
+ private static ImmutableArray _supportedDiagnostics;
+
+ public override ImmutableArray SupportedDiagnostics
+ {
+ get
+ {
+ if (_supportedDiagnostics.IsDefault)
+ Immutable.InterlockedInitialize(ref _supportedDiagnostics, DiagnosticRules.PutExpressionBodyOnItsOwnLine);
+
+ return _supportedDiagnostics;
+ }
+ }
+
+ public override void Initialize(AnalysisContext context)
+ {
+ base.Initialize(context);
+
+ context.RegisterSyntaxNodeAction(f => AnalyzeArrowExpressionClause(f), SyntaxKind.ArrowExpressionClause);
+ }
+
+ private static void AnalyzeArrowExpressionClause(SyntaxNodeAnalysisContext context)
+ {
+ var arrowExpressionClause = (ArrowExpressionClauseSyntax)context.Node;
+
+ switch (arrowExpressionClause.Parent.Kind())
+ {
+ case SyntaxKind.MethodDeclaration:
+ case SyntaxKind.ConstructorDeclaration:
+ case SyntaxKind.DestructorDeclaration:
+ case SyntaxKind.PropertyDeclaration:
+ case SyntaxKind.IndexerDeclaration:
+ case SyntaxKind.OperatorDeclaration:
+ case SyntaxKind.ConversionOperatorDeclaration:
+ AnalyzeArrowExpressionClause(arrowExpressionClause.ArrowToken, context);
+ break;
+ }
+ }
+
+ private static void AnalyzeArrowExpressionClause(SyntaxToken arrowToken, SyntaxNodeAnalysisContext context)
+ {
+ NewLinePosition newLinePosition = context.GetArrowTokenNewLinePosition();
+
+ SyntaxToken first;
+ SyntaxToken second;
+ if (newLinePosition == NewLinePosition.After)
+ {
+ first = arrowToken;
+ second = arrowToken.GetNextToken();
+ }
+ else
+ {
+ first = arrowToken.GetPreviousToken();
+ second = arrowToken;
+ }
+
+ TriviaBlock block = TriviaBlock.FromBetween(first, second);
+
+ if (block.Kind == TriviaBlockKind.NoNewLine)
+ {
+ DiagnosticHelpers.ReportDiagnostic(
+ context,
+ DiagnosticRules.PutExpressionBodyOnItsOwnLine,
+ block.GetLocation());
+ }
+ }
+}
diff --git a/src/Tests/Formatting.Analyzers.Tests/RCS0062PutExpressionBodyOnItsOwnLineTests.cs b/src/Tests/Formatting.Analyzers.Tests/RCS0062PutExpressionBodyOnItsOwnLineTests.cs
new file mode 100644
index 0000000000..be60edeae7
--- /dev/null
+++ b/src/Tests/Formatting.Analyzers.Tests/RCS0062PutExpressionBodyOnItsOwnLineTests.cs
@@ -0,0 +1,276 @@
+// Copyright (c) .NET Foundation and Contributors. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Threading.Tasks;
+using Microsoft.CodeAnalysis;
+using Roslynator.Formatting.CodeFixes.CSharp;
+using Roslynator.Testing.CSharp;
+using Xunit;
+
+namespace Roslynator.Formatting.CSharp.Tests;
+
+public class RCS0062PutExpressionBodyOnItsOwnLineTests : AbstractCSharpDiagnosticVerifier
+{
+ public override DiagnosticDescriptor Descriptor { get; } = DiagnosticRules.PutExpressionBodyOnItsOwnLine;
+
+ [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.PutExpressionBodyOnItsOwnLine)]
+ public async Task Test_Constructor_BreakAfterArrow()
+ {
+ await VerifyDiagnosticAndFixAsync(@"
+class C
+{
+ public C() =>[||] M();
+
+ void M() { }
+}
+", @"
+class C
+{
+ public C() =>
+ M();
+
+ void M() { }
+}
+", options: Options.AddConfigOption(ConfigOptionKeys.ArrowTokenNewLine, ConfigOptionValues.ArrowTokenNewLine_After));
+ }
+
+ [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.PutExpressionBodyOnItsOwnLine)]
+ public async Task Test_Constructor_BreakBeforeArrow()
+ {
+ await VerifyDiagnosticAndFixAsync(@"
+class C
+{
+ public C()[||] => M();
+
+ void M() { }
+}
+", @"
+class C
+{
+ public C()
+ => M();
+
+ void M() { }
+}
+", options: Options.AddConfigOption(ConfigOptionKeys.ArrowTokenNewLine, ConfigOptionValues.ArrowTokenNewLine_Before));
+ }
+
+ [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.PutExpressionBodyOnItsOwnLine)]
+ public async Task Test_Destructor_BreakAfterArrow()
+ {
+ await VerifyDiagnosticAndFixAsync(@"
+class C
+{
+ ~C() =>[||] M();
+
+ void M() { }
+}
+", @"
+class C
+{
+ ~C() =>
+ M();
+
+ void M() { }
+}
+", options: Options.AddConfigOption(ConfigOptionKeys.ArrowTokenNewLine, ConfigOptionValues.ArrowTokenNewLine_After));
+ }
+
+ [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.PutExpressionBodyOnItsOwnLine)]
+ public async Task Test_Destructor_BreakBeforeArrow()
+ {
+ await VerifyDiagnosticAndFixAsync(@"
+class C
+{
+ ~C()[||] => M();
+
+ void M() { }
+}
+", @"
+class C
+{
+ ~C()
+ => M();
+
+ void M() { }
+}
+", options: Options.AddConfigOption(ConfigOptionKeys.ArrowTokenNewLine, ConfigOptionValues.ArrowTokenNewLine_Before));
+ }
+
+ [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.PutExpressionBodyOnItsOwnLine)]
+ public async Task Test_Method_BreakAfterArrow()
+ {
+ await VerifyDiagnosticAndFixAsync(@"
+class C
+{
+ string M() =>[||] null;
+}
+", @"
+class C
+{
+ string M() =>
+ null;
+}
+", options: Options.AddConfigOption(ConfigOptionKeys.ArrowTokenNewLine, ConfigOptionValues.ArrowTokenNewLine_After));
+ }
+
+ [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.PutExpressionBodyOnItsOwnLine)]
+ public async Task Test_Method_BreakBeforeArrow()
+ {
+ await VerifyDiagnosticAndFixAsync(@"
+class C
+{
+ string M()[||] => null;
+}
+", @"
+class C
+{
+ string M()
+ => null;
+}
+", options: Options.AddConfigOption(ConfigOptionKeys.ArrowTokenNewLine, ConfigOptionValues.ArrowTokenNewLine_Before));
+ }
+
+ [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.PutExpressionBodyOnItsOwnLine)]
+ public async Task Test_Operator_BreakAfterArrow()
+ {
+ await VerifyDiagnosticAndFixAsync(@"
+class C
+{
+ public static C operator !(C value) =>[||] value;
+}
+", @"
+class C
+{
+ public static C operator !(C value) =>
+ value;
+}
+", options: Options.AddConfigOption(ConfigOptionKeys.ArrowTokenNewLine, ConfigOptionValues.ArrowTokenNewLine_After));
+ }
+
+ [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.PutExpressionBodyOnItsOwnLine)]
+ public async Task Test_Operator_BreakBeforeArrow()
+ {
+ await VerifyDiagnosticAndFixAsync(@"
+class C
+{
+ public static C operator !(C value)[||] => value;
+}
+", @"
+class C
+{
+ public static C operator !(C value)
+ => value;
+}
+", options: Options.AddConfigOption(ConfigOptionKeys.ArrowTokenNewLine, ConfigOptionValues.ArrowTokenNewLine_Before));
+ }
+
+ [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.PutExpressionBodyOnItsOwnLine)]
+ public async Task Test_ConversionOperator_BreakAfterArrow()
+ {
+ await VerifyDiagnosticAndFixAsync(@"
+class C
+{
+ public static explicit operator C(string value) =>[||] new C();
+}
+", @"
+class C
+{
+ public static explicit operator C(string value) =>
+ new C();
+}
+", options: Options.AddConfigOption(ConfigOptionKeys.ArrowTokenNewLine, ConfigOptionValues.ArrowTokenNewLine_After));
+ }
+
+ [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.PutExpressionBodyOnItsOwnLine)]
+ public async Task Test_ConversionOperator_BreakBeforeArrow()
+ {
+ await VerifyDiagnosticAndFixAsync(@"
+class C
+{
+ public static explicit operator C(string value)[||] => new C();
+}
+", @"
+class C
+{
+ public static explicit operator C(string value)
+ => new C();
+}
+", options: Options.AddConfigOption(ConfigOptionKeys.ArrowTokenNewLine, ConfigOptionValues.ArrowTokenNewLine_Before));
+ }
+
+ [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.PutExpressionBodyOnItsOwnLine)]
+ public async Task Test_PropertyWithoutAccessor_BreakAfterArrow()
+ {
+ await VerifyDiagnosticAndFixAsync(@"
+class C
+{
+ string P =>[||] null;
+}
+", @"
+class C
+{
+ string P =>
+ null;
+}
+", options: Options.AddConfigOption(ConfigOptionKeys.ArrowTokenNewLine, ConfigOptionValues.ArrowTokenNewLine_After));
+ }
+
+ [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.PutExpressionBodyOnItsOwnLine)]
+ public async Task Test_PropertyWithoutAccessor_BreakBeforeArrow()
+ {
+ await VerifyDiagnosticAndFixAsync(@"
+class C
+{
+ string P[||] => null;
+}
+", @"
+class C
+{
+ string P
+ => null;
+}
+", options: Options.AddConfigOption(ConfigOptionKeys.ArrowTokenNewLine, ConfigOptionValues.ArrowTokenNewLine_Before));
+ }
+
+ [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.PutExpressionBodyOnItsOwnLine)]
+ public async Task TestNoDiagnostic_LocalFunction()
+ {
+ await VerifyNoDiagnosticAsync(@"
+class C
+{
+ void M()
+ {
+ string LF() => null;
+ }
+}
+", options: Options.AddConfigOption(ConfigOptionKeys.ArrowTokenNewLine, ConfigOptionValues.ArrowTokenNewLine_Before));
+ }
+
+ [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.PutExpressionBodyOnItsOwnLine)]
+ public async Task TestNoDiagnostic_PropertyWithGetter()
+ {
+ await VerifyNoDiagnosticAsync(@"
+class C
+{
+ string P
+ {
+ get => null;
+ }
+}
+", options: Options.AddConfigOption(ConfigOptionKeys.ArrowTokenNewLine, ConfigOptionValues.ArrowTokenNewLine_Before));
+ }
+
+ [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.PutExpressionBodyOnItsOwnLine)]
+ public async Task TestNoDiagnostic_IndexerWithGetter()
+ {
+ await VerifyNoDiagnosticAsync(@"
+class C
+{
+ string this[int index]
+ {
+ get => null;
+ }
+}
+", options: Options.AddConfigOption(ConfigOptionKeys.ArrowTokenNewLine, ConfigOptionValues.ArrowTokenNewLine_Before));
+ }
+}
diff --git a/src/VisualStudioCode/package/src/configurationFiles.generated.ts b/src/VisualStudioCode/package/src/configurationFiles.generated.ts
index 4af6961de1..6a94390b6f 100644
--- a/src/VisualStudioCode/package/src/configurationFiles.generated.ts
+++ b/src/VisualStudioCode/package/src/configurationFiles.generated.ts
@@ -42,7 +42,7 @@ roslynator_analyzers.enabled_by_default = true|false
# Applicable to: rcs1014
#roslynator_arrow_token_new_line = after|before
-# Applicable to: rcs0032
+# Applicable to: rcs0032, rcs0062
#roslynator_binary_operator_new_line = after|before
# Applicable to: rcs0027
@@ -309,6 +309,10 @@ roslynator_analyzers.enabled_by_default = true|false
#dotnet_diagnostic.rcs0061.severity = none
# Options: roslynator_blank_line_between_switch_sections
+# Put expression body on its own line
+#dotnet_diagnostic.rcs0062.severity = none
+# Options: roslynator_arrow_token_new_line
+
# Add braces (when expression spans over multiple lines)
#dotnet_diagnostic.rcs1001.severity = suggestion