Skip to content

Commit 50595fd

Browse files
author
Kapil Borle
authored
Merge pull request #781 from PowerShell/kapilmb/fix-place-close-brace
Fix PlaceCloseBrace rule behavior for NewLineAfter switch
2 parents 5c77603 + c67e81e commit 50595fd

File tree

3 files changed

+241
-16
lines changed

3 files changed

+241
-16
lines changed

Rules/PlaceCloseBrace.cs

+87-5
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ public class PlaceCloseBrace : ConfigurableRule
3333
///
3434
/// Default value is false.
3535
/// </summary>
36-
[ConfigurableRuleProperty(defaultValue:false)]
36+
[ConfigurableRuleProperty(defaultValue: false)]
3737
public bool NoEmptyLineBefore { get; protected set; }
3838

3939
/// <summary>
@@ -82,11 +82,11 @@ public override IEnumerable<DiagnosticRecord> AnalyzeScript(Ast ast, string file
8282

8383
var tokens = Helper.Instance.Tokens;
8484
var diagnosticRecords = new List<DiagnosticRecord>();
85-
var curlyStack = new Stack<Tuple<Token, int>> ();
85+
var curlyStack = new Stack<Tuple<Token, int>>();
8686

8787
// TODO move part common with PlaceOpenBrace to one place
8888
var tokenOps = new TokenOperations(tokens, ast);
89-
tokensToIgnore = new HashSet<Token> (tokenOps.GetCloseBracesInCommandElements());
89+
tokensToIgnore = new HashSet<Token>(tokenOps.GetCloseBracesInCommandElements());
9090

9191
// Ignore close braces that are part of a one line if-else statement
9292
// E.g. $x = if ($true) { "blah" } else { "blah blah" }
@@ -138,6 +138,12 @@ public override IEnumerable<DiagnosticRecord> AnalyzeScript(Ast ast, string file
138138
GetViolationForBraceShouldHaveNewLineAfter(tokens, k, openBracePos, fileName),
139139
ref diagnosticRecords);
140140
}
141+
else
142+
{
143+
AddToDiagnosticRecords(
144+
GetViolationsForUncuddledBranches(tokens, k, openBracePos, fileName),
145+
ref diagnosticRecords);
146+
}
141147
}
142148
else
143149
{
@@ -317,7 +323,7 @@ private DiagnosticRecord GetViolationForBraceShouldHaveNewLineAfter(
317323
if ((tokens[expectedNewLinePos].Kind == TokenKind.Else
318324
|| tokens[expectedNewLinePos].Kind == TokenKind.ElseIf)
319325
&& !tokensToIgnore.Contains(closeBraceToken))
320-
{
326+
{
321327
return new DiagnosticRecord(
322328
GetError(Strings.PlaceCloseBraceErrorShouldFollowNewLine),
323329
closeBraceToken.Extent,
@@ -326,12 +332,74 @@ private DiagnosticRecord GetViolationForBraceShouldHaveNewLineAfter(
326332
fileName,
327333
null,
328334
GetCorrectionsForBraceShouldHaveNewLineAfter(tokens, closeBracePos, openBracePos, fileName));
329-
}
335+
}
330336
}
331337

332338
return null;
333339
}
334340

341+
private DiagnosticRecord GetViolationsForUncuddledBranches(
342+
Token[] tokens,
343+
int closeBracePos,
344+
int openBracePos,
345+
string fileName)
346+
{
347+
// this will not work if there is a comment in between any tokens.
348+
var closeBraceToken = tokens[closeBracePos];
349+
if (tokens.Length <= closeBracePos + 2 ||
350+
tokensToIgnore.Contains(closeBraceToken))
351+
{
352+
return null;
353+
}
354+
355+
var token1 = tokens[closeBracePos + 1];
356+
var token2 = tokens[closeBracePos + 2];
357+
var branchTokenPos = IsBranchingStatementToken(token1) && !ApartByWhitespace(closeBraceToken, token1) ?
358+
closeBracePos + 1 :
359+
token1.Kind == TokenKind.NewLine && IsBranchingStatementToken(token2) ?
360+
closeBracePos + 2 :
361+
-1;
362+
363+
return branchTokenPos == -1 ?
364+
null :
365+
new DiagnosticRecord(
366+
GetError(Strings.PlaceCloseBraceErrorShouldCuddleBranchStatement),
367+
closeBraceToken.Extent,
368+
GetName(),
369+
GetDiagnosticSeverity(),
370+
fileName,
371+
null,
372+
GetCorrectionsForUncuddledBranches(tokens, closeBracePos, branchTokenPos, fileName));
373+
}
374+
375+
private static bool ApartByWhitespace(Token token1, Token token2)
376+
{
377+
var e1 = token1.Extent;
378+
var e2 = token2.Extent;
379+
return e1.StartLineNumber == e2.StartLineNumber &&
380+
e2.StartColumnNumber - e1.EndColumnNumber == 1;
381+
}
382+
383+
private List<CorrectionExtent> GetCorrectionsForUncuddledBranches(
384+
Token[] tokens,
385+
int closeBracePos,
386+
int branchStatementPos,
387+
string fileName)
388+
{
389+
var corrections = new List<CorrectionExtent>();
390+
var closeBraceToken = tokens[closeBracePos];
391+
var branchStatementToken = tokens[branchStatementPos];
392+
corrections.Add(new CorrectionExtent(
393+
closeBraceToken.Extent.StartLineNumber,
394+
branchStatementToken.Extent.EndLineNumber,
395+
closeBraceToken.Extent.StartColumnNumber,
396+
branchStatementToken.Extent.EndColumnNumber,
397+
closeBraceToken.Extent.Text + ' ' + branchStatementToken.Extent.Text,
398+
fileName));
399+
return corrections;
400+
401+
}
402+
335403
private DiagnosticRecord GetViolationForBraceShouldBeOnNewLine(
336404
Token[] tokens,
337405
int closeBracePos,
@@ -376,6 +444,20 @@ private List<CorrectionExtent> GetCorrectionsForBraceShouldBeOnNewLine(
376444
return corrections;
377445
}
378446

447+
private bool IsBranchingStatementToken(Token token)
448+
{
449+
switch (token.Kind)
450+
{
451+
case TokenKind.Else:
452+
case TokenKind.ElseIf:
453+
case TokenKind.Catch:
454+
case TokenKind.Finally:
455+
return true;
456+
457+
default:
458+
return false;
459+
}
460+
}
379461
private void AddToDiagnosticRecords(
380462
DiagnosticRecord diagnosticRecord,
381463
ref List<DiagnosticRecord> diagnosticRecords)

Rules/Strings.resx

+3
Original file line numberDiff line numberDiff line change
@@ -906,6 +906,9 @@
906906
<data name="PlaceCloseBraceErrorShouldFollowNewLine" xml:space="preserve">
907907
<value>Close brace does not follow a new line.</value>
908908
</data>
909+
<data name="PlaceCloseBraceErrorShouldCuddleBranchStatement" xml:space="preserve">
910+
<value>Close brace before a branch statement is followed by a new line.</value>
911+
</data>
909912
<data name="UseConsistentIndentationName" xml:space="preserve">
910913
<value>UseConsistentIndentation</value>
911914
</data>

Tests/Rules/PlaceCloseBrace.tests.ps1

+151-11
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,15 @@ Import-Module PSScriptAnalyzer
55
Import-Module (Join-Path $testRootDirectory "PSScriptAnalyzerTestHelper.psm1")
66

77
$ruleConfiguration = @{
8-
Enable = $true
9-
NoEmptyLineBefore = $true
8+
Enable = $true
9+
NoEmptyLineBefore = $true
1010
IgnoreOneLineBlock = $true
11-
NewLineAfter = $true
11+
NewLineAfter = $true
1212
}
1313

1414
$settings = @{
1515
IncludeRules = @("PSPlaceCloseBrace")
16-
Rules = @{
16+
Rules = @{
1717
PSPlaceCloseBrace = $ruleConfiguration
1818
}
1919
}
@@ -130,7 +130,7 @@ $x = if ($true) { "blah" } else { "blah blah" }
130130
}
131131
}
132132

133-
Context "When a close brace should be follow a new line" {
133+
Context "When a close brace should be followed by a new line" {
134134
BeforeAll {
135135
$ruleConfiguration.'NoEmptyLineBefore' = $false
136136
$ruleConfiguration.'IgnoreOneLineBlock' = $false
@@ -148,11 +148,11 @@ if (Test-Path "blah") {
148148
$violations = Invoke-ScriptAnalyzer -ScriptDefinition $def -Settings $settings
149149
$violations.Count | Should Be 1
150150
$params = @{
151-
RawContent = $def
151+
RawContent = $def
152152
DiagnosticRecord = $violations[0]
153153
CorrectionsCount = 1
154-
ViolationText = '}'
155-
CorrectionText = '}' + [System.Environment]::NewLine
154+
ViolationText = '}'
155+
CorrectionText = '}' + [System.Environment]::NewLine
156156
}
157157
Test-CorrectionExtentFromContent @params
158158
}
@@ -168,11 +168,11 @@ if (Test-Path "blah") {
168168
$violations = Invoke-ScriptAnalyzer -ScriptDefinition $def -Settings $settings
169169
$violations.Count | Should Be 1
170170
$params = @{
171-
RawContent = $def
171+
RawContent = $def
172172
DiagnosticRecord = $violations[0]
173173
CorrectionsCount = 1
174-
ViolationText = '}'
175-
CorrectionText = '}' + [System.Environment]::NewLine
174+
ViolationText = '}'
175+
CorrectionText = '}' + [System.Environment]::NewLine
176176
}
177177
Test-CorrectionExtentFromContent @params
178178
}
@@ -194,6 +194,146 @@ Some-Command -Param1 @{
194194
Some-Command -Param1 @{
195195
key="value"
196196
} -Param2
197+
'@
198+
$violations = Invoke-ScriptAnalyzer -ScriptDefinition $def -Settings $settings
199+
$violations.Count | Should Be 0
200+
}
201+
}
202+
203+
Context "When a close brace should not be followed by a new line" {
204+
BeforeAll {
205+
$ruleConfiguration.'NoEmptyLineBefore' = $false
206+
$ruleConfiguration.'IgnoreOneLineBlock' = $false
207+
$ruleConfiguration.'NewLineAfter' = $false
208+
}
209+
210+
It "Should find a violation if a branching statement is on a new line if open brance is on same line" {
211+
$def = @'
212+
if ($true) {
213+
$true
214+
}
215+
else {
216+
$false
217+
}
218+
'@
219+
$violations = Invoke-ScriptAnalyzer -ScriptDefinition $def -Settings $settings
220+
$violations.Count | Should Be 1
221+
}
222+
223+
It "Should correct violation by cuddling the else branch statement" {
224+
$def = @'
225+
if ($true) {
226+
$true
227+
}
228+
else {
229+
$false
230+
}
231+
'@
232+
$expected = @'
233+
if ($true) {
234+
$true
235+
} else {
236+
$false
237+
}
238+
'@
239+
Invoke-Formatter -ScriptDefinition $def -Settings $settings | Should Be $expected
240+
}
241+
242+
It "Should correct violation if the close brace and following keyword are apart by less than a space" {
243+
$def = @'
244+
if ($true) {
245+
$true
246+
}else {
247+
$false
248+
}
249+
'@
250+
$expected = @'
251+
if ($true) {
252+
$true
253+
} else {
254+
$false
255+
}
256+
'@
257+
Invoke-Formatter -ScriptDefinition $def -Settings $settings | Should Be $expected
258+
}
259+
260+
It "Should correct violation if the close brace and following keyword are apart by more than a space" {
261+
$def = @'
262+
if ($true) {
263+
$true
264+
} else {
265+
$false
266+
}
267+
'@
268+
$expected = @'
269+
if ($true) {
270+
$true
271+
} else {
272+
$false
273+
}
274+
'@
275+
Invoke-Formatter -ScriptDefinition $def -Settings $settings | Should Be $expected
276+
}
277+
278+
It "Should correct violations in an if-else-elseif block" {
279+
$def = @'
280+
$x = 1
281+
if ($x -eq 1) {
282+
"1"
283+
}
284+
elseif ($x -eq 2) {
285+
"2"
286+
}
287+
else {
288+
"3"
289+
}
290+
'@
291+
$expected = @'
292+
$x = 1
293+
if ($x -eq 1) {
294+
"1"
295+
} elseif ($x -eq 2) {
296+
"2"
297+
} else {
298+
"3"
299+
}
300+
'@
301+
Invoke-Formatter -ScriptDefinition $def -Settings $settings | Should Be $expected
302+
}
303+
304+
It "Should correct violations in a try-catch-finally block" {
305+
$def = @'
306+
try {
307+
"try"
308+
}
309+
catch {
310+
"catch"
311+
}
312+
finally {
313+
"finally"
314+
}
315+
'@
316+
$expected = @'
317+
try {
318+
"try"
319+
} catch {
320+
"catch"
321+
} finally {
322+
"finally"
323+
}
324+
'@
325+
Invoke-Formatter -ScriptDefinition $def -Settings $settings | Should Be $expected
326+
}
327+
328+
It "Should not find violations when a script has two close braces in succession" {
329+
$def = @'
330+
function foo {
331+
if ($true) {
332+
"true"
333+
} else {
334+
"false"
335+
}
336+
}
197337
'@
198338
$violations = Invoke-ScriptAnalyzer -ScriptDefinition $def -Settings $settings
199339
$violations.Count | Should Be 0

0 commit comments

Comments
 (0)