diff --git a/.editorconfig b/.editorconfig
index 1d4c9ced5b..ab81a354fb 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -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
diff --git a/ChangeLog.md b/ChangeLog.md
index bebc5db2a6..ac8853a0b1 100644
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -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
diff --git a/src/Analyzers.xml b/src/Analyzers.xml
index 136339d3b9..24a119220a 100644
--- a/src/Analyzers.xml
+++ b/src/Analyzers.xml
@@ -1446,6 +1446,7 @@ void M(
Default maximal length is 140.
+
diff --git a/src/Analyzers/CSharp/Analysis/RemoveEmptySyntaxAnalyzer.cs b/src/Analyzers/CSharp/Analysis/RemoveEmptySyntaxAnalyzer.cs
index 03d5557cbc..f1318bb645 100644
--- a/src/Analyzers/CSharp/Analysis/RemoveEmptySyntaxAnalyzer.cs
+++ b/src/Analyzers/CSharp/Analysis/RemoveEmptySyntaxAnalyzer.cs
@@ -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;
}
diff --git a/src/Analyzers/CSharp/Analysis/RemovePartialModifierFromTypeWithSinglePartAnalyzer.cs b/src/Analyzers/CSharp/Analysis/RemovePartialModifierFromTypeWithSinglePartAnalyzer.cs
index 37a784cdc8..4fcc5c2f96 100644
--- a/src/Analyzers/CSharp/Analysis/RemovePartialModifierFromTypeWithSinglePartAnalyzer.cs
+++ b/src/Analyzers/CSharp/Analysis/RemovePartialModifierFromTypeWithSinglePartAnalyzer.cs
@@ -14,7 +14,7 @@ public sealed class RemovePartialModifierFromTypeWithSinglePartAnalyzer : BaseDi
{
private static ImmutableArray _supportedDiagnostics;
- private static readonly MetadataName[] _metadataNames = new MetadataName[] {
+ private static readonly MetadataName[] _metadataNames = new[] {
// ASP.NET Core
MetadataName.Parse("Microsoft.AspNetCore.Components.ComponentBase"),
// WPF
diff --git a/src/CodeFixes/CSharp/CodeFixes/AssignDefaultValueToOutParameterCodeFixProvider.cs b/src/CodeFixes/CSharp/CodeFixes/AssignDefaultValueToOutParameterCodeFixProvider.cs
index fb85bc8b84..54a044d07c 100644
--- a/src/CodeFixes/CSharp/CodeFixes/AssignDefaultValueToOutParameterCodeFixProvider.cs
+++ b/src/CodeFixes/CSharp/CodeFixes/AssignDefaultValueToOutParameterCodeFixProvider.cs
@@ -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;
@@ -155,7 +156,9 @@ private static Task RefactorAsync(
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);
}
diff --git a/src/Common/CSharp/Extensions/CodeStyleExtensions.cs b/src/Common/CSharp/Extensions/CodeStyleExtensions.cs
index e06730a91f..00fbc39fb7 100644
--- a/src/Common/CSharp/Extensions/CodeStyleExtensions.cs
+++ b/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;
@@ -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))
diff --git a/src/CSharp/CSharp/IndentationAnalysis.cs b/src/Common/CSharp/IndentationAnalysis.cs
similarity index 83%
rename from src/CSharp/CSharp/IndentationAnalysis.cs
rename to src/Common/CSharp/IndentationAnalysis.cs
index 0de4dda8cc..74b8bdb931 100644
--- a/src/CSharp/CSharp/IndentationAnalysis.cs
+++ b/src/Common/CSharp/IndentationAnalysis.cs
@@ -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
{
@@ -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 "";
@@ -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)
diff --git a/src/CSharp/CSharp/SyntaxTriviaAnalysis.cs b/src/Common/CSharp/SyntaxTriviaAnalysis.cs
similarity index 93%
rename from src/CSharp/CSharp/SyntaxTriviaAnalysis.cs
rename to src/Common/CSharp/SyntaxTriviaAnalysis.cs
index d185efe922..2c8f052462 100644
--- a/src/CSharp/CSharp/SyntaxTriviaAnalysis.cs
+++ b/src/Common/CSharp/SyntaxTriviaAnalysis.cs
@@ -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;
@@ -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)
@@ -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 FindIndentations(SyntaxNode node)
@@ -367,9 +363,10 @@ public static IEnumerable FindIndentations(SyntaxNode node, Tex
public static TNode SetIndentation(
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))
diff --git a/src/Common/ConfigOptionKeys.Generated.cs b/src/Common/ConfigOptionKeys.Generated.cs
index 18b43bc31c..5c4844bfe2 100644
--- a/src/Common/ConfigOptionKeys.Generated.cs
+++ b/src/Common/ConfigOptionKeys.Generated.cs
@@ -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";
diff --git a/src/Common/ConfigOptions.Generated.cs b/src/Common/ConfigOptions.Generated.cs
index 3a84f2d161..ca1648383a 100644
--- a/src/Common/ConfigOptions.Generated.cs
+++ b/src/Common/ConfigOptions.Generated.cs
@@ -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: "",
+ description: "A length of a tab character.");
+
public static readonly ConfigOptionDescriptor UseAnonymousFunctionOrMethodGroup = new(
key: ConfigOptionKeys.UseAnonymousFunctionOrMethodGroup,
defaultValue: null,
diff --git a/src/Common/IndentStyle.cs b/src/Common/IndentStyle.cs
new file mode 100644
index 0000000000..47ff3c822c
--- /dev/null
+++ b/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,
+}
diff --git a/src/ConfigOptions.xml b/src/ConfigOptions.xml
index 8c2f55806a..06fafe0530 100644
--- a/src/ConfigOptions.xml
+++ b/src/ConfigOptions.xml
@@ -124,6 +124,11 @@
<NUM>
Max line length
+