Skip to content

Commit c366328

Browse files
authored
Fix PSUseConsistentIndentationRule to handle pipes correctly when there is a multi-line statement after a pipe and add PipelineIndentation customisation option for it (#1102)
* Fix PSUseConsistentIndentationRule to handle pipes correctly when there is a multi-line statement after it: Do not just add temporary indentation for the next line but keep the indentation level * correctly decrement indentation count for multiple pipes * add customisation * take pipeline into account and add docs. TODO: Change shipped setting files * update formatting setting files * add tests and refactor/improve test suite * Apply suggestions from code review Co-Authored-By: bergmeister <c.bergmeister@gmail.com> * Remove else block by using break
1 parent e592dc4 commit c366328

File tree

7 files changed

+200
-53
lines changed

7 files changed

+200
-53
lines changed

Engine/Settings/CodeFormatting.psd1

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
PSUseConsistentIndentation = @{
2626
Enable = $true
2727
Kind = 'space'
28+
PipelineIndentation = 'IncreaseIndentationAfterEveryPipeline'
2829
IndentationSize = 4
2930
}
3031

Engine/Settings/CodeFormattingAllman.psd1

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
PSUseConsistentIndentation = @{
2626
Enable = $true
2727
Kind = 'space'
28+
PipelineIndentation = 'IncreaseIndentationAfterEveryPipeline'
2829
IndentationSize = 4
2930
}
3031

Engine/Settings/CodeFormattingOTBS.psd1

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
PSUseConsistentIndentation = @{
2626
Enable = $true
2727
Kind = 'space'
28+
PipelineIndentation = 'IncreaseIndentationAfterEveryPipeline'
2829
IndentationSize = 4
2930
}
3031

Engine/Settings/CodeFormattingStroustrup.psd1

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
PSUseConsistentIndentation = @{
2727
Enable = $true
2828
Kind = 'space'
29+
PipelineIndentation = 'IncreaseIndentationAfterEveryPipeline'
2930
IndentationSize = 4
3031
}
3132

RuleDocumentation/UseConsistentIndentation.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ Indentation should be consistent throughout the source file.
1515
PSUseConsistentIndentation = @{
1616
Enable = $true
1717
IndentationSize = 4
18+
PipelineIndentation = 'IncreaseIndentationForFirstPipeline'
1819
Kind = 'space'
1920
}
2021
}
@@ -30,6 +31,29 @@ Enable or disable the rule during ScriptAnalyzer invocation.
3031

3132
Indentation size in the number of space characters.
3233

34+
#### PipelineIndentation: string (Default value is `IncreaseIndentationForFirstPipeline`)
35+
36+
Whether to increase indentation after a pipeline for multi-line statements. The settings are:
37+
38+
- IncreaseIndentationForFirstPipeline (default): Indent once after the first pipeline and keep this indentation. Example:
39+
```powershell
40+
foo |
41+
bar |
42+
baz
43+
```
44+
- IncreaseIndentationAfterEveryPipeline: Indent more after the first pipeline and keep this indentation. Example:
45+
```powershell
46+
foo |
47+
bar |
48+
baz
49+
```
50+
- NoIndentation: Do not increase indentation. Example:
51+
```powershell
52+
foo |
53+
bar |
54+
baz
55+
```
56+
3357
#### Kind: string (Default value is `space`)
3458

3559
Represents the kind of indentation to be used. Possible values are: `space`, `tab`. If any invalid value is given, the property defaults to `space`.

Rules/UseConsistentIndentation.cs

Lines changed: 84 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,24 @@ public string Kind
5757
}
5858
}
5959

60+
61+
[ConfigurableRuleProperty(defaultValue: "IncreaseIndentationForFirstPipeline")]
62+
public string PipelineIndentation
63+
{
64+
get
65+
{
66+
return pipelineIndentationStyle.ToString();
67+
}
68+
set
69+
{
70+
if (String.IsNullOrWhiteSpace(value) ||
71+
!Enum.TryParse(value, true, out pipelineIndentationStyle))
72+
{
73+
pipelineIndentationStyle = PipelineIndentationStyle.IncreaseIndentationAfterEveryPipeline;
74+
}
75+
}
76+
}
77+
6078
private bool insertSpaces;
6179
private char indentationChar;
6280
private int indentationLevelMultiplier;
@@ -68,9 +86,17 @@ private enum IndentationKind {
6886
// Auto
6987
};
7088

89+
private enum PipelineIndentationStyle
90+
{
91+
IncreaseIndentationForFirstPipeline,
92+
IncreaseIndentationAfterEveryPipeline,
93+
NoIndentation
94+
}
95+
7196
// TODO make this configurable
7297
private IndentationKind indentationKind = IndentationKind.Space;
7398

99+
private PipelineIndentationStyle pipelineIndentationStyle = PipelineIndentationStyle.IncreaseIndentationAfterEveryPipeline;
74100

75101
/// <summary>
76102
/// Analyzes the given ast to find violations.
@@ -104,6 +130,7 @@ public override IEnumerable<DiagnosticRecord> AnalyzeScript(Ast ast, string file
104130
var diagnosticRecords = new List<DiagnosticRecord>();
105131
var indentationLevel = 0;
106132
var onNewLine = true;
133+
var pipelineAsts = ast.FindAll(testAst => testAst is PipelineAst && (testAst as PipelineAst).PipelineElements.Count > 1, true);
107134
for (int k = 0; k < tokens.Length; k++)
108135
{
109136
var token = tokens[k];
@@ -123,6 +150,28 @@ public override IEnumerable<DiagnosticRecord> AnalyzeScript(Ast ast, string file
123150
AddViolation(token, indentationLevel++, diagnosticRecords, ref onNewLine);
124151
break;
125152

153+
case TokenKind.Pipe:
154+
bool pipelineIsFollowedByNewlineOrLineContinuation = k < tokens.Length - 1 && k > 0 &&
155+
(tokens[k + 1].Kind == TokenKind.NewLine || tokens[k + 1].Kind == TokenKind.LineContinuation);
156+
if (!pipelineIsFollowedByNewlineOrLineContinuation)
157+
{
158+
break;
159+
}
160+
if (pipelineIndentationStyle == PipelineIndentationStyle.IncreaseIndentationAfterEveryPipeline)
161+
{
162+
AddViolation(token, indentationLevel++, diagnosticRecords, ref onNewLine);
163+
break;
164+
}
165+
if (pipelineIndentationStyle == PipelineIndentationStyle.IncreaseIndentationForFirstPipeline)
166+
{
167+
bool isFirstPipeInPipeline = pipelineAsts.Any(pipelineAst => PositionIsEqual(((PipelineAst)pipelineAst).PipelineElements[0].Extent.EndScriptPosition, tokens[k - 1].Extent.EndScriptPosition));
168+
if (isFirstPipeInPipeline)
169+
{
170+
AddViolation(token, indentationLevel++, diagnosticRecords, ref onNewLine);
171+
}
172+
}
173+
break;
174+
126175
case TokenKind.RParen:
127176
case TokenKind.RCurly:
128177
indentationLevel = ClipNegative(indentationLevel - 1);
@@ -156,22 +205,44 @@ public override IEnumerable<DiagnosticRecord> AnalyzeScript(Ast ast, string file
156205
{
157206
--j;
158207
}
159-
160-
if (j >= 0 && tokens[j].Kind == TokenKind.Pipe)
161-
{
162-
++tempIndentationLevel;
163-
}
164208
}
165209

166-
AddViolation(token, tempIndentationLevel, diagnosticRecords, ref onNewLine);
210+
var lineHasPipelineBeforeToken = tokens.Any(oneToken =>
211+
oneToken.Kind == TokenKind.Pipe &&
212+
oneToken.Extent.StartLineNumber == token.Extent.StartLineNumber &&
213+
oneToken.Extent.StartColumnNumber < token.Extent.StartColumnNumber);
214+
215+
AddViolation(token, tempIndentationLevel, diagnosticRecords, ref onNewLine, lineHasPipelineBeforeToken);
167216
}
168217
break;
169218
}
219+
220+
// Check if the current token matches the end of a PipelineAst
221+
var matchingPipeLineAstEnd = pipelineAsts.FirstOrDefault(pipelineAst =>
222+
PositionIsEqual(pipelineAst.Extent.EndScriptPosition, token.Extent.EndScriptPosition)) as PipelineAst;
223+
if (matchingPipeLineAstEnd != null)
224+
{
225+
if (pipelineIndentationStyle == PipelineIndentationStyle.IncreaseIndentationForFirstPipeline)
226+
{
227+
indentationLevel = ClipNegative(indentationLevel - 1);
228+
}
229+
else if (pipelineIndentationStyle == PipelineIndentationStyle.IncreaseIndentationAfterEveryPipeline)
230+
{
231+
indentationLevel = ClipNegative(indentationLevel - (matchingPipeLineAstEnd.PipelineElements.Count - 1));
232+
}
233+
}
170234
}
171235

172236
return diagnosticRecords;
173237
}
174238

239+
private static bool PositionIsEqual(IScriptPosition position1, IScriptPosition position2)
240+
{
241+
return position1.ColumnNumber == position2.ColumnNumber &&
242+
position1.LineNumber == position2.LineNumber &&
243+
position1.File == position2.File;
244+
}
245+
175246
/// <summary>
176247
/// Retrieves the common name of this rule.
177248
/// </summary>
@@ -237,7 +308,8 @@ private void AddViolation(
237308
Token token,
238309
int expectedIndentationLevel,
239310
List<DiagnosticRecord> diagnosticRecords,
240-
ref bool onNewLine)
311+
ref bool onNewLine,
312+
bool lineHasPipelineBeforeToken = false)
241313
{
242314
if (onNewLine)
243315
{
@@ -265,26 +337,28 @@ private void AddViolation(
265337
GetDiagnosticSeverity(),
266338
fileName,
267339
null,
268-
GetSuggestedCorrections(token, expectedIndentationLevel)));
340+
GetSuggestedCorrections(token, expectedIndentationLevel, lineHasPipelineBeforeToken)));
269341
}
270342
}
271343
}
272344

273345
private List<CorrectionExtent> GetSuggestedCorrections(
274346
Token token,
275-
int indentationLevel)
347+
int indentationLevel,
348+
bool lineHasPipelineBeforeToken = false)
276349
{
277350
// TODO Add another constructor for correction extent that takes extent
278351
// TODO handle param block
279352
// TODO handle multiline commands
280353

281354
var corrections = new List<CorrectionExtent>();
355+
var optionalPipeline = lineHasPipelineBeforeToken ? "| " : string.Empty;
282356
corrections.Add(new CorrectionExtent(
283357
token.Extent.StartLineNumber,
284358
token.Extent.EndLineNumber,
285359
1,
286360
token.Extent.EndColumnNumber,
287-
GetIndentationString(indentationLevel) + token.Extent.Text,
361+
GetIndentationString(indentationLevel) + optionalPipeline + token.Extent.Text,
288362
token.Extent.File));
289363
return corrections;
290364
}

0 commit comments

Comments
 (0)