Skip to content
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

Add analyzer 'Add/remove blank line between switch sections' #1302

Merged
merged 41 commits into from
Dec 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
0d5cd14
Add analyzer 'Blank line between switch sections'
josefpihrt Nov 30, 2023
f46f0f3
update
josefpihrt Nov 30, 2023
e7d2b53
Merge branch 'main' into feature/switch-section-blank-line
josefpihrt Nov 30, 2023
c637ca6
update
josefpihrt Nov 30, 2023
4defb64
update
josefpihrt Nov 30, 2023
b345bda
x
josefpihrt Nov 30, 2023
ce14b24
update
josefpihrt Nov 30, 2023
5f70a9a
update
josefpihrt Nov 30, 2023
bb6ad87
update
josefpihrt Nov 30, 2023
70133ee
update
josefpihrt Dec 1, 2023
8a38c6a
update
josefpihrt Dec 1, 2023
1228a94
update
josefpihrt Dec 1, 2023
1720533
update
josefpihrt Dec 1, 2023
706aee3
update
josefpihrt Dec 1, 2023
81c1365
merge main
josefpihrt Dec 1, 2023
c223d93
update
josefpihrt Dec 2, 2023
4e56d47
update
josefpihrt Dec 2, 2023
5628cf0
update
josefpihrt Dec 2, 2023
7eff432
update
josefpihrt Dec 2, 2023
d2df0ef
update
josefpihrt Dec 2, 2023
7c7c4eb
x
josefpihrt Dec 2, 2023
a0b1b60
Merge branch 'main' into feature/switch-section-blank-line
josefpihrt Dec 2, 2023
78e934e
update
josefpihrt Dec 2, 2023
b7cd3b8
update
josefpihrt Dec 2, 2023
8646fcf
Merge branch 'main' into feature/switch-section-blank-line
josefpihrt Dec 2, 2023
baea694
Merge branch 'main' into feature/switch-section-blank-line
josefpihrt Dec 3, 2023
641f601
Merge branch 'main' into feature/switch-section-blank-line
josefpihrt Dec 3, 2023
1b76bcf
Merge branch 'main' into feature/switch-section-blank-line
josefpihrt Dec 3, 2023
e36c16f
Merge branch 'main' into feature/switch-section-blank-line
josefpihrt Dec 5, 2023
6b58c3d
Merge branch 'main' into feature/switch-section-blank-line
josefpihrt Dec 5, 2023
fc9bf58
update
josefpihrt Dec 5, 2023
ded0a3a
update
josefpihrt Dec 5, 2023
93e2434
update
josefpihrt Dec 5, 2023
20121dc
update
josefpihrt Dec 5, 2023
2a811c9
update
josefpihrt Dec 5, 2023
60c7539
update
josefpihrt Dec 5, 2023
01a9f9b
update
josefpihrt Dec 5, 2023
62ec229
update
josefpihrt Dec 5, 2023
dff4bb0
update
josefpihrt Dec 5, 2023
b2b2ecb
Merge branch 'main' into feature/switch-section-blank-line
josefpihrt Dec 5, 2023
a3c5c38
update
josefpihrt Dec 5, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
9 changes: 9 additions & 0 deletions ChangeLog.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- Add analyzer "Add/remove blank line between switch sections" ([RCS0061](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS0061)) ([PR](https://github.com/dotnet/roslynator/pull/1302))
- Option (required): `roslynator_blank_line_between_switch_sections = include|omit|omit_after_block`
- Make analyzer [RCS0014](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS0014) obsolete

### Changed

- Replace type declaration's empty braces with semicolon ([RCS1251](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1251) ([PR](https://github.com/dotnet/roslynator/pull/1323))
Expand All @@ -18,6 +24,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Add analyzer "Dispose resource asynchronously" ([RCS1261](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1261)) ([PR](https://github.com/dotnet/roslynator/pull/1285))
- Add analyzer "Unnecessary raw string literal" ([RCS1262](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1262)) ([PR](https://github.com/dotnet/roslynator/pull/1293))
- Add analyzer "Invalid reference in a documentation comment" ([RCS1263](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1263)) ([PR](https://github.com/dotnet/roslynator/pull/1295))
- Add analyzer "Add/remove blank line between switch sections" ([RCS0061](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS0061)) ([PR](https://github.com/dotnet/roslynator/pull/1302))
- Option (required): `roslynator_blank_line_between_switch_sections = include|omit|omit_after_block`
- Make analyzer [RCS0014](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS0014) obsolete

### Changed

Expand Down
99 changes: 99 additions & 0 deletions src/Analyzers.xml
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,8 @@ object this[int index] { get; }]]></After>
<Analyzer>
<Id>RCS0014</Id>
<Identifier>AddBlankLineBetweenSwitchSections</Identifier>
<Status>Obsolete</Status>
<ObsoleteMessage>Use RCS0061 instead</ObsoleteMessage>
<Title>Add blank line between switch sections</Title>
<DefaultSeverity>Info</DefaultSeverity>
<IsEnabledByDefault>false</IsEnabledByDefault>
Expand Down Expand Up @@ -1530,6 +1532,103 @@ public class C
</Link>
</Links>
</Analyzer>
<Analyzer>
<Id>RCS0061</Id>
<Identifier>BlankLineBetweenSwitchSections</Identifier>
<Title>Add/remove blank line between switch sections</Title>
<MessageFormat>{0} blank line between switch sections</MessageFormat>
<DefaultSeverity>Info</DefaultSeverity>
<IsEnabledByDefault>false</IsEnabledByDefault>
<ConfigOptions>
<Option Key="blank_line_between_switch_sections" IsRequired="true" />
</ConfigOptions>
<Samples>
<Sample>
<ConfigOptions>
<Option Key="blank_line_between_switch_sections" Value="include" />
</ConfigOptions>
<Before><![CDATA[switch (x)
{
case "foo":
return true;
case "bar":
return false;
default:
throw new InvalidOperationException();
}
]]></Before>
<After><![CDATA[switch (x)
{
case "foo":
return true;

case "bar":
return false;

default:
throw new InvalidOperationException();
}
]]></After>
</Sample>
<Sample>
<ConfigOptions>
<Option Key="blank_line_between_switch_sections" Value="omit" />
</ConfigOptions>
<Before><![CDATA[switch (x)
{
case "foo":
return true;

case "bar":
return false;

default:
throw new InvalidOperationException();
}
]]></Before>
<After><![CDATA[switch (x)
{
case "foo":
return true;
case "bar":
return false;
default:
throw new InvalidOperationException();
}
]]></After>
</Sample>
<Sample>
<ConfigOptions>
<Option Key="blank_line_between_switch_sections" Value="omit_after_block" />
</ConfigOptions>
<Before><![CDATA[switch (x)
{
case "foo":
{
return true;
}

case "bar":
{
return false;
}
}
]]></Before>
<After><![CDATA[switch (x)
{
case "foo":
{
return true;
}
case "bar":
{
return false;
}
}
]]></After>
</Sample>
</Samples>
</Analyzer>
<Analyzer>
<Id>RCS1001</Id>
<Identifier>AddBracesWhenExpressionSpansOverMultipleLines</Identifier>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ public override void Initialize(AnalysisContext context)
context.RegisterSyntaxNodeAction(f => AnalyzeInterfaceDeclaration(f), SyntaxKind.InterfaceDeclaration);
context.RegisterSyntaxNodeAction(f => AnalyzeEnumDeclaration(f), SyntaxKind.EnumDeclaration);
context.RegisterSyntaxNodeAction(f => AnalyzeNamespaceDeclaration(f), SyntaxKind.NamespaceDeclaration);
context.RegisterSyntaxNodeAction(f => AnalyzeSwitchStatement(f), SyntaxKind.SwitchStatement);
context.RegisterSyntaxNodeAction(f => AnalyzeTryStatement(f), SyntaxKind.TryStatement);
context.RegisterSyntaxNodeAction(f => AnalyzeElseClause(f), SyntaxKind.ElseClause);

Expand Down Expand Up @@ -147,35 +146,6 @@ private static void AnalyzeNamespaceDeclaration(SyntaxNodeAnalysisContext contex
AnalyzeEnd(context, members.Last(), namespaceDeclaration.CloseBraceToken);
}

private static void AnalyzeSwitchStatement(SyntaxNodeAnalysisContext context)
{
var switchStatement = (SwitchStatementSyntax)context.Node;

SyntaxList<SwitchSectionSyntax> sections = switchStatement.Sections;

if (sections.Any())
{
AnalyzeStart(context, sections[0], switchStatement.OpenBraceToken);
AnalyzeEnd(context, sections.Last(), switchStatement.CloseBraceToken);

if (sections.Count > 1
&& context.GetBlankLineBetweenClosingBraceAndSwitchSection() == false)
{
SwitchSectionSyntax prevSection = sections[0];

for (int i = 1; i < sections.Count; i++)
{
SwitchSectionSyntax section = sections[i];

if (prevSection.Statements.LastOrDefault() is BlockSyntax block)
Analyze(context, block.CloseBraceToken, section);

prevSection = section;
}
}
}
}

private static void AnalyzeTryStatement(SyntaxNodeAnalysisContext context)
{
var tryStatement = (TryStatementSyntax)context.Node;
Expand Down
11 changes: 11 additions & 0 deletions src/Common/CSharp/CodeStyle/BlankLineBetweenSwitchSections.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Copyright (c) .NET Foundation and Contributors. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

namespace Roslynator.CSharp.CodeStyle;

internal enum BlankLineBetweenSwitchSections
{
None,
Include,
Omit,
OmitAfterBlock,
}
17 changes: 17 additions & 0 deletions src/Common/CSharp/Extensions/CodeStyleExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -581,6 +581,23 @@ public static BlankLineStyle GetBlankLineAfterFileScopedNamespaceDeclaration(thi
return null;
}

public static BlankLineBetweenSwitchSections GetBlankLineBetweenSwitchSections(this SyntaxNodeAnalysisContext context)
{
if (ConfigOptions.TryGetValue(context.GetConfigOptions(), ConfigOptions.BlankLineBetweenSwitchSections, out string rawValue))
{
if (string.Equals(rawValue, ConfigOptionValues.BlankLineBetweenSwitchSections_Include, StringComparison.OrdinalIgnoreCase))
return BlankLineBetweenSwitchSections.Include;

if (string.Equals(rawValue, ConfigOptionValues.BlankLineBetweenSwitchSections_Omit, StringComparison.OrdinalIgnoreCase))
return BlankLineBetweenSwitchSections.Omit;

if (string.Equals(rawValue, ConfigOptionValues.BlankLineBetweenSwitchSections_OmitAfterBlock, StringComparison.OrdinalIgnoreCase))
return BlankLineBetweenSwitchSections.OmitAfterBlock;
}

return BlankLineBetweenSwitchSections.None;
}

public static NewLinePosition GetEqualsSignNewLinePosition(this SyntaxNodeAnalysisContext context)
{
return context.GetConfigOptions().GetEqualsTokenNewLinePosition();
Expand Down
1 change: 1 addition & 0 deletions src/Common/ConfigOptionKeys.Generated.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ internal static partial class ConfigOptionKeys
public const string BlankLineAfterFileScopedNamespaceDeclaration = "roslynator_blank_line_after_file_scoped_namespace_declaration";
public const string BlankLineBetweenClosingBraceAndSwitchSection = "roslynator_blank_line_between_closing_brace_and_switch_section";
public const string BlankLineBetweenSingleLineAccessors = "roslynator_blank_line_between_single_line_accessors";
public const string BlankLineBetweenSwitchSections = "roslynator_blank_line_between_switch_sections";
public const string BlankLineBetweenUsingDirectives = "roslynator_blank_line_between_using_directives";
public const string BlockBracesStyle = "roslynator_block_braces_style";
public const string BodyStyle = "roslynator_body_style";
Expand Down
3 changes: 3 additions & 0 deletions src/Common/ConfigOptionValues.Generated.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ internal static partial class ConfigOptionValues
public const string ArrowTokenNewLine_Before = "before";
public const string BinaryOperatorNewLine_After = "after";
public const string BinaryOperatorNewLine_Before = "before";
public const string BlankLineBetweenSwitchSections_Include = "include";
public const string BlankLineBetweenSwitchSections_Omit = "omit";
public const string BlankLineBetweenSwitchSections_OmitAfterBlock = "omit_after_block";
public const string BlankLineBetweenUsingDirectives_Never = "never";
public const string BlankLineBetweenUsingDirectives_SeparateGroups = "separate_groups";
public const string BlockBracesStyle_MultiLine = "multi_line";
Expand Down
7 changes: 7 additions & 0 deletions src/Common/ConfigOptions.Generated.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,12 @@ public static partial class ConfigOptions
defaultValuePlaceholder: "true|false",
description: "Add/remove blank line between single-line accessors");

public static readonly ConfigOptionDescriptor BlankLineBetweenSwitchSections = new(
key: ConfigOptionKeys.BlankLineBetweenSwitchSections,
defaultValue: null,
defaultValuePlaceholder: "include|omit|omit_after_block",
description: "Include/omit blank line between switch sections");

public static readonly ConfigOptionDescriptor BlankLineBetweenUsingDirectives = new(
key: ConfigOptionKeys.BlankLineBetweenUsingDirectives,
defaultValue: null,
Expand Down Expand Up @@ -240,6 +246,7 @@ public static partial class ConfigOptions
yield return new KeyValuePair<string, string>("RCS0058", JoinOptionKeys(ConfigOptionKeys.NewLineAtEndOfFile));
yield return new KeyValuePair<string, string>("RCS0059", JoinOptionKeys(ConfigOptionKeys.NullConditionalOperatorNewLine));
yield return new KeyValuePair<string, string>("RCS0060", JoinOptionKeys(ConfigOptionKeys.BlankLineAfterFileScopedNamespaceDeclaration));
yield return new KeyValuePair<string, string>("RCS0061", JoinOptionKeys(ConfigOptionKeys.BlankLineBetweenSwitchSections));
yield return new KeyValuePair<string, string>("RCS1014", JoinOptionKeys(ConfigOptionKeys.ArrayCreationTypeStyle));
yield return new KeyValuePair<string, string>("RCS1016", JoinOptionKeys(ConfigOptionKeys.BodyStyle, ConfigOptionKeys.UseBlockBodyWhenDeclarationSpansOverMultipleLines, ConfigOptionKeys.UseBlockBodyWhenExpressionSpansOverMultipleLines));
yield return new KeyValuePair<string, string>("RCS1018", JoinOptionKeys(ConfigOptionKeys.AccessibilityModifiers));
Expand Down
8 changes: 8 additions & 0 deletions src/ConfigOptions.xml
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,14 @@
<Value>omit_when_single_line</Value>
</Values>
</Option>
<Option Id="BlankLineBetweenSwitchSections">
<Description>Include/omit blank line between switch sections</Description>
<Values>
<Value>include</Value>
<Value>omit</Value>
<Value>omit_after_block</Value>
</Values>
</Option>
<!--
<Option Id="">
<Key></Key>
Expand Down
4 changes: 0 additions & 4 deletions src/Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,4 @@
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All"/>
</ItemGroup>

<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<NoWarn>$(NoWarn);1573</NoWarn>
</PropertyGroup>

</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,12 @@ public sealed class SwitchSectionCodeFixProvider : BaseCodeFixProvider
{
public override ImmutableArray<string> FixableDiagnosticIds
{
get { return ImmutableArray.Create(DiagnosticIdentifiers.AddBlankLineBetweenSwitchSections); }
get
{
return ImmutableArray.Create(
DiagnosticIdentifiers.AddBlankLineBetweenSwitchSections,
DiagnosticIdentifiers.BlankLineBetweenSwitchSections);
}
}

public override Task RegisterCodeFixesAsync(CodeFixContext context)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
// 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 BlankLineBetweenSwitchSectionsAnalyzer : BaseDiagnosticAnalyzer
{
private static ImmutableArray<DiagnosticDescriptor> _supportedDiagnostics;

public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics
{
get
{
if (_supportedDiagnostics.IsDefault)
Immutable.InterlockedInitialize(ref _supportedDiagnostics, DiagnosticRules.BlankLineBetweenSwitchSections);

return _supportedDiagnostics;
}
}

public override void Initialize(AnalysisContext context)
{
base.Initialize(context);

context.RegisterSyntaxNodeAction(c => AnalyzeSwitchStatement(c), SyntaxKind.SwitchStatement);
}

private static void AnalyzeSwitchStatement(SyntaxNodeAnalysisContext context)
{
var switchStatement = (SwitchStatementSyntax)context.Node;

BlankLineBetweenSwitchSections option = context.GetBlankLineBetweenSwitchSections();

if (option == BlankLineBetweenSwitchSections.None)
return;

SyntaxList<SwitchSectionSyntax> sections = switchStatement.Sections;
SyntaxList<SwitchSectionSyntax>.Enumerator en = sections.GetEnumerator();

if (!en.MoveNext())
return;

SwitchSectionSyntax previousSection = en.Current;
StatementSyntax previousLastStatement = previousSection.Statements.LastOrDefault();

while (en.MoveNext())
{
TriviaBlockAnalysis analysis = TriviaBlockAnalysis.FromBetween(previousSection, en.Current);

if (!analysis.Success)
continue;

if (analysis.Kind == TriviaBlockKind.BlankLine)
{
if (option == BlankLineBetweenSwitchSections.Omit)
{
ReportDiagnostic(context, analysis, "Remove");
}
else if (option == BlankLineBetweenSwitchSections.OmitAfterBlock
&& previousLastStatement.IsKind(SyntaxKind.Block))
{
ReportDiagnostic(context, analysis, "Remove");
}
}
else if (option == BlankLineBetweenSwitchSections.Include)
{
ReportDiagnostic(context, analysis, "Add");
}
else if (option == BlankLineBetweenSwitchSections.OmitAfterBlock
&& !previousLastStatement.IsKind(SyntaxKind.Block))
{
ReportDiagnostic(context, analysis, "Add");
}

previousSection = en.Current;
previousLastStatement = previousSection.Statements.LastOrDefault();
}
}

private static void ReportDiagnostic(SyntaxNodeAnalysisContext context, TriviaBlockAnalysis analysis, string messageArg)
{
context.ReportDiagnostic(
DiagnosticRules.BlankLineBetweenSwitchSections,
analysis.GetLocation(),
messageArg);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -60,5 +60,6 @@ public static partial class DiagnosticIdentifiers
public const string NormalizeWhitespaceAtEndOfFile = "RCS0058";
public const string PlaceNewLineAfterOrBeforeNullConditionalOperator = "RCS0059";
public const string BlankLineAfterFileScopedNamespaceDeclaration = "RCS0060";
public const string BlankLineBetweenSwitchSections = "RCS0061";
}
}