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

Improve indentation analysis #1188

Merged
merged 13 commits into from Aug 25, 2023
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions .editorconfig
Expand Up @@ -6,6 +6,9 @@ root = true
# Code files
[*.{cs,csx}]

indent_style = space
indent_size = 4

dotnet_sort_system_directives_first = true

csharp_style_namespace_declarations = file_scoped:suggestion
Expand Down
1 change: 1 addition & 0 deletions ChangeLog.md
Expand Up @@ -39,6 +39,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Fix [RCS1208](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1208) ([#1153](https://github.com/JosefPihrt/Roslynator/pull/1153)).
- Fix [RCS1043](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1043) ([#1176](https://github.com/JosefPihrt/Roslynator/pull/1176)).
- [CLI] Fix exit code of `spellcheck` command ([#1177](https://github.com/JosefPihrt/Roslynator/pull/1177)).
- Improve indentation analysis ([#1188](https://github.com/JosefPihrt/Roslynator/pull/1188)).

## [4.4.0] - 2023-08-01

Expand Down
1 change: 1 addition & 0 deletions src/Analyzers.xml
Expand Up @@ -1446,6 +1446,7 @@ void M(
Default maximal length is 140.</Summary>
<ConfigOptions>
<Option Key="max_line_length" />
<Option Key="tab_length" />
</ConfigOptions>
</Analyzer>
<Analyzer>
Expand Down
2 changes: 1 addition & 1 deletion src/Analyzers/CSharp/Analysis/RemoveEmptySyntaxAnalyzer.cs
Expand Up @@ -66,7 +66,7 @@ private static void AnalyzeElseClause(SyntaxNodeAnalysisContext context)
IfStatementSyntax topmostIf = elseClause.GetTopmostIf();

if (topmostIf.Parent is IfStatementSyntax parentIf
&& parentIf.Else != null)
&& parentIf.Else is not null)
{
return;
}
Expand Down
Expand Up @@ -14,7 +14,7 @@ public sealed class RemovePartialModifierFromTypeWithSinglePartAnalyzer : BaseDi
{
private static ImmutableArray<DiagnosticDescriptor> _supportedDiagnostics;

private static readonly MetadataName[] _metadataNames = new MetadataName[] {
private static readonly MetadataName[] _metadataNames = new[] {
// ASP.NET Core
MetadataName.Parse("Microsoft.AspNetCore.Components.ComponentBase"),
// WPF
Expand Down
Expand Up @@ -12,6 +12,7 @@
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using Roslynator.CodeFixes;
using Roslynator.CSharp.Refactorings;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
Expand Down Expand Up @@ -155,7 +156,9 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context)

if (bodyOrExpressionBody is ArrowExpressionClauseSyntax expressionBody)
{
newNode = ConvertExpressionBodyToBlockBodyRefactoring.Refactor(expressionBody, semanticModel, cancellationToken);
AnalyzerConfigOptions configOptions = document.GetConfigOptions(node.SyntaxTree);

newNode = ConvertExpressionBodyToBlockBodyRefactoring.Refactor(expressionBody, configOptions, semanticModel, cancellationToken);

newNode = InsertStatements(newNode, expressionStatements);
}
Expand Down
37 changes: 37 additions & 0 deletions src/Common/CSharp/Extensions/CodeStyleExtensions.cs
@@ -1,6 +1,7 @@
// Copyright (c) Josef Pihrt and Contributors. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Globalization;
using Microsoft.CodeAnalysis.Diagnostics;
using Roslynator.Configuration;
using Roslynator.CSharp.CodeStyle;
Expand All @@ -9,6 +10,42 @@ namespace Roslynator.CSharp;

internal static class CodeStyleExtensions
{
public static bool TryGetTabLength(this AnalyzerConfigOptions configOptions, out int tabLength)
{
if (configOptions.TryGetValue(ConfigOptionKeys.TabLength, out string tabLengthStr)
&& int.TryParse(tabLengthStr, NumberStyles.None, CultureInfo.InvariantCulture, out tabLength))
{
return true;
}

tabLength = 0;
return false;
}

public static bool TryGetIndentSize(this AnalyzerConfigOptions configOptions, out int indentSize)
{
if (configOptions.TryGetValue("indent_size", out string indentSizeStr)
&& int.TryParse(indentSizeStr, NumberStyles.None, CultureInfo.InvariantCulture, out indentSize))
{
return true;
}

indentSize = 0;
return false;
}

public static bool TryGetIndentStyle(this AnalyzerConfigOptions configOptions, out IndentStyle indentStyle)
{
if (configOptions.TryGetValue("indent_style", out string indentStyleStr)
&& Enum.TryParse(indentStyleStr, ignoreCase: true, out indentStyle))
{
return true;
}

indentStyle = IndentStyle.Space;
return false;
}

public static bool GetPrefixFieldIdentifierWithUnderscore(this AnalyzerConfigOptions configOptions)
{
if (configOptions.TryGetValueAsBool(ConfigOptions.PrefixFieldIdentifierWithUnderscore, out bool value))
Expand Down
Expand Up @@ -5,62 +5,86 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;

namespace Roslynator.CSharp;

[DebuggerDisplay("{DebuggerDisplay,nq}")]
internal readonly struct IndentationAnalysis
internal sealed class IndentationAnalysis
{
private readonly int? _indentSize;
private readonly SyntaxTrivia? _singleIndentation;
private readonly SyntaxTrivia? _indentStep;

private IndentationAnalysis(SyntaxTrivia indentation, int? indentSize, SyntaxTrivia? singleIndentation)
private IndentationAnalysis(SyntaxTrivia indentation, IndentStyle? indentStyle, int? indentSize, SyntaxTrivia? indentStep)
{
Indentation = indentation;
IndentStyle = indentStyle;
_indentSize = indentSize;
_singleIndentation = singleIndentation;
_indentStep = indentStep;
}

public SyntaxTrivia Indentation { get; }

public int IndentSize => _indentSize ?? _singleIndentation?.Span.Length ?? 0;
public IndentStyle? IndentStyle { get; }

public int IndentationLength => Indentation.Span.Length;
public int IndentSize => _indentSize ?? _indentStep?.Span.Length ?? 0;

public int IncreasedIndentationLength => (IndentSize > 0) ? Indentation.Span.Length + IndentSize : 0;
public int IndentationLength => Indentation.Span.Length;

public bool IsDefault
public int IncreasedIndentationLength
{
get
{
return Indentation.IsKind(SyntaxKind.None)
&& _indentSize is null
&& _singleIndentation is null;
if (IndentSize > 0)
{
if (IndentStyle == Roslynator.IndentStyle.Tab)
return IndentationLength + 1;

return IndentationLength + IndentSize;
}

if (_indentStep is not null)
return IndentationLength + 4;

return IndentationLength;
}
}

[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private string DebuggerDisplay => $"Length = {Indentation.Span.Length} {nameof(IndentSize)} = {IndentSize}";
private string DebuggerDisplay => $"Length = {IndentationLength} {nameof(IndentSize)} = {IndentSize}";

public static IndentationAnalysis Create(SyntaxNode node, CancellationToken cancellationToken = default)
public static IndentationAnalysis Create(SyntaxNode node, AnalyzerConfigOptions configOptions, CancellationToken cancellationToken = default)
{
SyntaxTrivia indentation = SyntaxTriviaAnalysis.DetermineIndentation(node, cancellationToken);

if (configOptions.TryGetIndentStyle(out IndentStyle indentStyle)
&& indentStyle == Roslynator.IndentStyle.Tab)
{
if (!configOptions.TryGetTabLength(out int tabLength))
tabLength = 4;

return new IndentationAnalysis(indentation, indentStyle, tabLength, null);
}
else if (configOptions.TryGetIndentSize(out int indentSize))
{
return new IndentationAnalysis(indentation, Roslynator.IndentStyle.Space, indentSize, null);
}

(SyntaxTrivia trivia1, SyntaxTrivia trivia2, bool isFromCompilationUnit) = DetermineSingleIndentation(node, cancellationToken);

if (isFromCompilationUnit)
{
return new IndentationAnalysis(indentation, trivia1.Span.Length - trivia2.Span.Length, null);
return new IndentationAnalysis(indentation, null, trivia1.Span.Length - trivia2.Span.Length, null);
}
else if (indentation.Span.Length > 0)
{
return (trivia1.Span.Length > 0)
? new IndentationAnalysis(indentation, null, trivia1)
: new IndentationAnalysis(indentation, null, null);
? new IndentationAnalysis(indentation, null, null, trivia1)
: new IndentationAnalysis(indentation, null, null, null);
}
else if (trivia1.Span.Length > 0)
{
return new IndentationAnalysis(indentation, null, trivia1);
return new IndentationAnalysis(indentation, null, null, trivia1);
}
else
{
Expand All @@ -87,11 +111,14 @@ public SyntaxTriviaList GetIncreasedIndentationTriviaList()

public string GetSingleIndentation()
{
if (_singleIndentation is not null)
return _singleIndentation.ToString();
if (_indentStep is not null)
return _indentStep.ToString();

if (IndentStyle == Roslynator.IndentStyle.Tab)
return "\t";

if (_indentSize == -1)
return Indentation.ToString();
if (IndentStyle == Roslynator.IndentStyle.Space)
return GetSpaces();

if (Indentation.Span.Length == 0)
return "";
Expand All @@ -101,7 +128,18 @@ public string GetSingleIndentation()
if (indentation[indentation.Length - 1] == '\t')
return "\t";

return new string(indentation[0], IndentSize);
return GetSpaces();

string GetSpaces()
{
return IndentSize switch
{
2 => " ",
4 => " ",
8 => " ",
_ => new string(' ', IndentSize),
};
}
}

private static (SyntaxTrivia, SyntaxTrivia, bool isFromCompilationUnit) DetermineSingleIndentation(SyntaxNode node, CancellationToken cancellationToken = default)
Expand Down
Expand Up @@ -7,6 +7,7 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Text;
using Roslynator.CSharp;
using Roslynator.Text;
Expand Down Expand Up @@ -201,9 +202,9 @@ public static bool StartsWithOptionalWhitespaceThenEndOfLineTrivia(SyntaxTriviaL
return en.Current.IsEndOfLineTrivia();
}

public static IndentationAnalysis AnalyzeIndentation(SyntaxNode node, CancellationToken cancellationToken = default)
public static IndentationAnalysis AnalyzeIndentation(SyntaxNode node, AnalyzerConfigOptions configOptions, CancellationToken cancellationToken = default)
{
return IndentationAnalysis.Create(node, cancellationToken);
return IndentationAnalysis.Create(node, configOptions, cancellationToken);
}

public static SyntaxTrivia DetermineIndentation(SyntaxNodeOrToken nodeOrToken, CancellationToken cancellationToken = default)
Expand Down Expand Up @@ -302,24 +303,19 @@ static bool IsMemberDeclarationOrStatementOrAccessorDeclaration(SyntaxNode node)
}
}

public static string GetIncreasedIndentation(SyntaxNode node, CancellationToken cancellationToken = default)
public static string GetIncreasedIndentation(SyntaxNode node, AnalyzerConfigOptions configOptions, CancellationToken cancellationToken = default)
{
return AnalyzeIndentation(node, cancellationToken).GetIncreasedIndentation();
return AnalyzeIndentation(node, configOptions, cancellationToken).GetIncreasedIndentation();
}

public static int GetIncreasedIndentationLength(SyntaxNode node, CancellationToken cancellationToken = default)
public static SyntaxTrivia GetIncreasedIndentationTrivia(SyntaxNode node, AnalyzerConfigOptions configOptions, CancellationToken cancellationToken = default)
{
return AnalyzeIndentation(node, cancellationToken).IncreasedIndentationLength;
return AnalyzeIndentation(node, configOptions, cancellationToken).GetIncreasedIndentationTrivia();
}

public static SyntaxTrivia GetIncreasedIndentationTrivia(SyntaxNode node, CancellationToken cancellationToken = default)
public static SyntaxTriviaList GetIncreasedIndentationTriviaList(SyntaxNode node, AnalyzerConfigOptions configOptions, CancellationToken cancellationToken = default)
{
return AnalyzeIndentation(node, cancellationToken).GetIncreasedIndentationTrivia();
}

public static SyntaxTriviaList GetIncreasedIndentationTriviaList(SyntaxNode node, CancellationToken cancellationToken = default)
{
return AnalyzeIndentation(node, cancellationToken).GetIncreasedIndentationTriviaList();
return AnalyzeIndentation(node, configOptions, cancellationToken).GetIncreasedIndentationTriviaList();
}

public static IEnumerable<IndentationInfo> FindIndentations(SyntaxNode node)
Expand Down Expand Up @@ -367,9 +363,10 @@ public static IEnumerable<IndentationInfo> FindIndentations(SyntaxNode node, Tex
public static TNode SetIndentation<TNode>(
TNode expression,
SyntaxNode containingDeclaration,
AnalyzerConfigOptions configOptions,
int increaseCount = 0) where TNode : SyntaxNode
{
IndentationAnalysis analysis = AnalyzeIndentation(containingDeclaration);
IndentationAnalysis analysis = AnalyzeIndentation(containingDeclaration, configOptions);

string replacement = (increaseCount > 0)
? string.Concat(Enumerable.Repeat(analysis.GetSingleIndentation(), increaseCount))
Expand Down
1 change: 1 addition & 0 deletions src/Common/ConfigOptionKeys.Generated.cs
Expand Up @@ -35,6 +35,7 @@ internal static partial class ConfigOptionKeys
public const string ObjectCreationTypeStyle = "roslynator_object_creation_type_style";
public const string PrefixFieldIdentifierWithUnderscore = "roslynator_prefix_field_identifier_with_underscore";
public const string SuppressUnityScriptMethods = "roslynator_suppress_unity_script_methods";
public const string TabLength = "roslynator_tab_length";
public const string UseAnonymousFunctionOrMethodGroup = "roslynator_use_anonymous_function_or_method_group";
public const string UseBlockBodyWhenDeclarationSpansOverMultipleLines = "roslynator_use_block_body_when_declaration_spans_over_multiple_lines";
public const string UseBlockBodyWhenExpressionSpansOverMultipleLines = "roslynator_use_block_body_when_expression_spans_over_multiple_lines";
Expand Down
6 changes: 6 additions & 0 deletions src/Common/ConfigOptions.Generated.cs
Expand Up @@ -182,6 +182,12 @@ public static partial class ConfigOptions
defaultValuePlaceholder: "true|false",
description: "Suppress Unity script methods");

public static readonly ConfigOptionDescriptor TabLength = new(
key: ConfigOptionKeys.TabLength,
defaultValue: "4",
defaultValuePlaceholder: "<NUM>",
description: "A length of a tab character.");

public static readonly ConfigOptionDescriptor UseAnonymousFunctionOrMethodGroup = new(
key: ConfigOptionKeys.UseAnonymousFunctionOrMethodGroup,
defaultValue: null,
Expand Down
9 changes: 9 additions & 0 deletions src/Common/IndentStyle.cs
@@ -0,0 +1,9 @@
// Copyright (c) Josef Pihrt and Contributors. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

namespace Roslynator;

public enum IndentStyle
{
Space,
Tab,
}
5 changes: 5 additions & 0 deletions src/ConfigOptions.xml
Expand Up @@ -124,6 +124,11 @@
<ValuePlaceholder>&lt;NUM&gt;</ValuePlaceholder>
<Description>Max line length</Description>
</Option>
<Option Id="TabLength">
<DefaultValue>4</DefaultValue>
<ValuePlaceholder>&lt;NUM&gt;</ValuePlaceholder>
<Description>A number of spaces that are equivalent to a tab character.</Description>
</Option>
<Option Id="NewLineAtEndOfFile">
<ValuePlaceholder>true|false</ValuePlaceholder>
<Description>Include/omit new line at the end of a file</Description>
Expand Down