Skip to content

Commit

Permalink
Improve indentation analysis (#1188)
Browse files Browse the repository at this point in the history
  • Loading branch information
josefpihrt committed Aug 25, 2023
1 parent f6fdc7a commit 971754b
Show file tree
Hide file tree
Showing 34 changed files with 375 additions and 148 deletions.
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

0 comments on commit 971754b

Please sign in to comment.