Skip to content

Commit b0a67c7

Browse files
bergmeisterrjmholt
andauthoredJan 7, 2020
Remove redundant whitespace between parameters with new option (disabled by default) in UseConsistentWhitespace (#1392)
* find extents of parameters elements * first working prototype * Productionize, add tests, docs and add to config files. TODO: message names * tidy up * add messages * Apply suggestions from code review Co-Authored-By: Robert Holt <rjmholt@gmail.com> * revert suggestion around removal around expectedStartColumnNumberOfRightExtent variable, which broke build. Variable is used 2 times and makes code more readable, therefore keeping it * Tweak DiagnosticRecord creation and add more tests * use named parameters for CorrectionExtent constructor Co-authored-by: Robert Holt <rjmholt@gmail.com>
1 parent a86fbfe commit b0a67c7

9 files changed

+177
-46
lines changed
 

‎Engine/Settings/CodeFormatting.psd1

+1
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
CheckOperator = $true
3939
CheckPipe = $true
4040
CheckSeparator = $true
41+
CheckParameter = $false
4142
}
4243

4344
PSAlignAssignmentStatement = @{

‎Engine/Settings/CodeFormattingAllman.psd1

+1
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
CheckOperator = $true
3939
CheckPipe = $true
4040
CheckSeparator = $true
41+
CheckParameter = $false
4142
}
4243

4344
PSAlignAssignmentStatement = @{

‎Engine/Settings/CodeFormattingOTBS.psd1

+1
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
CheckOperator = $true
3939
CheckPipe = $true
4040
CheckSeparator = $true
41+
CheckParameter = $false
4142
}
4243

4344
PSAlignAssignmentStatement = @{

‎Engine/Settings/CodeFormattingStroustrup.psd1

+1
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
CheckOperator = $true
4040
CheckPipe = $true
4141
CheckSeparator = $true
42+
CheckParameter = $false
4243
}
4344

4445
PSAlignAssignmentStatement = @{

‎RuleDocumentation/UseConsistentWhitespace.md

+6
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
CheckOperator = $true
2121
CheckPipe = $true
2222
CheckSeparator = $true
23+
CheckParameter = $false
2324
}
2425
}
2526
```
@@ -53,3 +54,8 @@ Checks if a comma or a semicolon is followed by a space. E.g. `@(1, 2, 3)` or `@
5354
#### CheckPipe: bool (Default value is `$true`)
5455

5556
Checks if a pipe is surrounded on both sides by a space. E.g. `foo | bar` instead of `foo|bar`.
57+
58+
#### CheckParameter: bool (Default value is `$false` at the moment due to the setting being new)
59+
60+
Checks if there is more than one space between parameters and values. E.g. `foo -bar $baz -bat` instead of `foo -bar $baz -bat`. This eliminates redundant whitespace that was probably added unintentionally.
61+
The rule does not check for whitespace between parameter and value when the colon syntax `-ParameterName:$ParameterValue` is used as some users prefer either 0 or 1 whitespace in this case.

‎Rules/Strings.Designer.cs

+45-44
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎Rules/Strings.resx

+4-1
Original file line numberDiff line numberDiff line change
@@ -1104,4 +1104,7 @@
11041104
<data name="UseProcessBlockForPipelineCommandName" xml:space="preserve">
11051105
<value>UseProcessBlockForPipelineCommand</value>
11061106
</data>
1107-
</root>
1107+
<data name="UseConsistentWhitespaceErrorSpaceBetweenParameter" xml:space="preserve">
1108+
<value>Use only 1 whitespace between parameter names or values.</value>
1109+
</data>
1110+
</root>

‎Rules/UseConsistentWhitespace.cs

+52-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ namespace Microsoft.Windows.PowerShell.ScriptAnalyzer.BuiltinRules
2222
#endif
2323
public class UseConsistentWhitespace : ConfigurableRule
2424
{
25-
private enum ErrorKind { BeforeOpeningBrace, Paren, Operator, SeparatorComma, SeparatorSemi, AfterOpeningBrace, BeforeClosingBrace, BeforePipe, AfterPipe };
25+
private enum ErrorKind { BeforeOpeningBrace, Paren, Operator, SeparatorComma, SeparatorSemi,
26+
AfterOpeningBrace, BeforeClosingBrace, BeforePipe, AfterPipe, BetweenParameter };
2627
private const int whiteSpaceSize = 1;
2728
private const string whiteSpace = " ";
2829
private readonly SortedSet<TokenKind> openParenKeywordWhitelist = new SortedSet<TokenKind>()
@@ -56,6 +57,9 @@ private List<Func<TokenOperations, IEnumerable<DiagnosticRecord>>> violationFind
5657
[ConfigurableRuleProperty(defaultValue: true)]
5758
public bool CheckSeparator { get; protected set; }
5859

60+
[ConfigurableRuleProperty(defaultValue: true)]
61+
public bool CheckParameter { get; protected set; }
62+
5963
public override void ConfigureRule(IDictionary<string, object> paramValueMap)
6064
{
6165
base.ConfigureRule(paramValueMap);
@@ -110,6 +114,11 @@ public override IEnumerable<DiagnosticRecord> AnalyzeScript(Ast ast, string file
110114
diagnosticRecords = diagnosticRecords.Concat(violationFinder(tokenOperations));
111115
}
112116

117+
if (CheckParameter)
118+
{
119+
diagnosticRecords = diagnosticRecords.Concat(FindParameterViolations(ast));
120+
}
121+
113122
return diagnosticRecords.ToArray(); // force evaluation here
114123
}
115124

@@ -203,6 +212,8 @@ private string GetError(ErrorKind kind)
203212
return string.Format(CultureInfo.CurrentCulture, Strings.UseConsistentWhitespaceErrorSeparatorComma);
204213
case ErrorKind.SeparatorSemi:
205214
return string.Format(CultureInfo.CurrentCulture, Strings.UseConsistentWhitespaceErrorSeparatorSemi);
215+
case ErrorKind.BetweenParameter:
216+
return string.Format(CultureInfo.CurrentCulture, Strings.UseConsistentWhitespaceErrorSpaceBetweenParameter);
206217
default:
207218
return string.Format(CultureInfo.CurrentCulture, Strings.UseConsistentWhitespaceErrorBeforeParen);
208219
}
@@ -364,6 +375,46 @@ private IEnumerable<DiagnosticRecord> FindOpenParenViolations(TokenOperations to
364375
}
365376
}
366377

378+
private IEnumerable<DiagnosticRecord> FindParameterViolations(Ast ast)
379+
{
380+
IEnumerable<Ast> commandAsts = ast.FindAll(
381+
testAst => testAst is CommandAst, true);
382+
foreach (CommandAst commandAst in commandAsts)
383+
{
384+
List<Ast> commandParameterAstElements = commandAst.FindAll(testAst => true, searchNestedScriptBlocks: false).ToList();
385+
for (int i = 0; i < commandParameterAstElements.Count - 1; i++)
386+
{
387+
IScriptExtent leftExtent = commandParameterAstElements[i].Extent;
388+
IScriptExtent rightExtent = commandParameterAstElements[i + 1].Extent;
389+
if (leftExtent.EndLineNumber != rightExtent.StartLineNumber)
390+
{
391+
continue;
392+
}
393+
394+
var expectedStartColumnNumberOfRightExtent = leftExtent.EndColumnNumber + 1;
395+
if (rightExtent.StartColumnNumber > expectedStartColumnNumberOfRightExtent)
396+
{
397+
int numberOfRedundantWhiteSpaces = rightExtent.StartColumnNumber - expectedStartColumnNumberOfRightExtent;
398+
var correction = new CorrectionExtent(
399+
startLineNumber: leftExtent.StartLineNumber,
400+
endLineNumber: leftExtent.EndLineNumber,
401+
startColumnNumber: leftExtent.EndColumnNumber + 1,
402+
endColumnNumber: leftExtent.EndColumnNumber + 1 + numberOfRedundantWhiteSpaces,
403+
text: string.Empty,
404+
file: leftExtent.File);
405+
406+
yield return new DiagnosticRecord(
407+
GetError(ErrorKind.BetweenParameter),
408+
leftExtent,
409+
GetName(),
410+
GetDiagnosticSeverity(),
411+
leftExtent.File,
412+
suggestedCorrections: new CorrectionExtent[] { correction });
413+
}
414+
}
415+
}
416+
}
417+
367418
private bool IsSeparator(Token token)
368419
{
369420
return token.Kind == TokenKind.Comma || token.Kind == TokenKind.Semi;

‎Tests/Rules/UseConsistentWhitespace.tests.ps1

+66
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ $ruleConfiguration = @{
1212
CheckOperator = $false
1313
CheckPipe = $false
1414
CheckSeparator = $false
15+
CheckParameter = $false
1516
}
1617

1718
$settings = @{
@@ -388,4 +389,69 @@ if ($true) { Get-Item `
388389
}
389390
}
390391

392+
393+
Context "CheckParameter" {
394+
BeforeAll {
395+
$ruleConfiguration.CheckInnerBrace = $true
396+
$ruleConfiguration.CheckOpenBrace = $false
397+
$ruleConfiguration.CheckOpenParen = $false
398+
$ruleConfiguration.CheckOperator = $false
399+
$ruleConfiguration.CheckPipe = $false
400+
$ruleConfiguration.CheckSeparator = $false
401+
$ruleConfiguration.CheckParameter = $true
402+
}
403+
404+
It "Should not find no violation when newlines are involved" {
405+
$def = {foo -a $b `
406+
-c d -d $e -f g `
407+
-h i |
408+
bar -h i `
409+
-switch}
410+
Invoke-ScriptAnalyzer -ScriptDefinition "$def" -Settings $settings | Should -Be $null
411+
}
412+
413+
It "Should not find no violation if there is always 1 space between parameters except when using colon syntax" {
414+
$def = 'foo -bar $baz @splattedVariable -bat -parameterName:$parameterValue'
415+
Invoke-ScriptAnalyzer -ScriptDefinition $def -Settings $settings | Should -Be $null
416+
}
417+
418+
It "Should find 1 violation if there is 1 space too much before a parameter" {
419+
$def = 'foo -bar'
420+
$violations = Invoke-ScriptAnalyzer -ScriptDefinition $def -Settings $settings
421+
$violations.Count | Should -Be 1
422+
$violations[0].Extent.Text | Should -Be 'foo'
423+
$violations[0].SuggestedCorrections[0].Text | Should -Be ([string]::Empty)
424+
}
425+
426+
It "Should find 1 violation if there is 1 space too much before a parameter value" {
427+
$def = 'foo $bar'
428+
$violations = Invoke-ScriptAnalyzer -ScriptDefinition $def -Settings $settings
429+
$violations.Count | Should -Be 1
430+
$violations[0].Extent.Text | Should -Be 'foo'
431+
$violations[0].SuggestedCorrections[0].Text | Should -Be ([string]::Empty)
432+
}
433+
434+
It "Should fix script to always have 1 space between parameters except when using colon syntax but not by default" {
435+
$def = 'foo -bar $baz -ParameterName: $ParameterValue'
436+
Invoke-Formatter -ScriptDefinition $def |
437+
Should -BeExactly $def -Because 'CheckParameter configuration is not turned on by default (yet) as the setting is new'
438+
Invoke-Formatter -ScriptDefinition $def -Settings $settings |
439+
Should -BeExactly 'foo -bar $baz -ParameterName: $ParameterValue'
440+
}
441+
442+
It "Should fix script when newlines are involved" {
443+
$def = {foo -a $b `
444+
-c d -d $e -f g `
445+
-h i |
446+
bar -h i `
447+
-switch}
448+
$expected = {foo -a $b `
449+
-c d -d $e -f g `
450+
-h i |
451+
bar -h i `
452+
-switch}
453+
Invoke-Formatter -ScriptDefinition "$def" -Settings $settings |
454+
Should -Be "$expected"
455+
}
456+
}
391457
}

0 commit comments

Comments
 (0)
Please sign in to comment.