@@ -22,8 +22,12 @@ namespace Microsoft.Windows.PowerShell.ScriptAnalyzer.BuiltinRules
22
22
#endif
23
23
public class UseConsistentWhitespace : ConfigurableRule
24
24
{
25
- private enum ErrorKind { BeforeOpeningBrace , Paren , Operator , SeparatorComma , SeparatorSemi ,
26
- AfterOpeningBrace , BeforeClosingBrace , BeforePipe , AfterPipe , BetweenParameter } ;
25
+ private enum ErrorKind
26
+ {
27
+ BeforeOpeningBrace , Paren , Operator , SeparatorComma , SeparatorSemi ,
28
+ AfterOpeningBrace , BeforeClosingBrace , BeforePipe , AfterPipe , BetweenParameter
29
+ } ;
30
+
27
31
private const int whiteSpaceSize = 1 ;
28
32
private const string whiteSpace = " " ;
29
33
private readonly SortedSet < TokenKind > openParenKeywordAllowList = new SortedSet < TokenKind > ( )
@@ -33,7 +37,9 @@ private enum ErrorKind { BeforeOpeningBrace, Paren, Operator, SeparatorComma, Se
33
37
TokenKind . Switch ,
34
38
TokenKind . For ,
35
39
TokenKind . Foreach ,
36
- TokenKind . While
40
+ TokenKind . While ,
41
+ TokenKind . Until ,
42
+ TokenKind . Do
37
43
} ;
38
44
39
45
private List < Func < TokenOperations , IEnumerable < DiagnosticRecord > > > violationFinders
@@ -72,6 +78,7 @@ public override void ConfigureRule(IDictionary<string, object> paramValueMap)
72
78
if ( CheckOpenBrace )
73
79
{
74
80
violationFinders . Add ( FindOpenBraceViolations ) ;
81
+ violationFinders . Add ( FindKeywordAfterBraceViolations ) ;
75
82
}
76
83
77
84
if ( CheckInnerBrace )
@@ -194,6 +201,7 @@ private bool IsOperator(Token token)
194
201
return TokenTraits . HasTrait ( token . Kind , TokenFlags . AssignmentOperator )
195
202
|| TokenTraits . HasTrait ( token . Kind , TokenFlags . BinaryPrecedenceAdd )
196
203
|| TokenTraits . HasTrait ( token . Kind , TokenFlags . BinaryPrecedenceMultiply )
204
+ || TokenTraits . HasTrait ( token . Kind , TokenFlags . UnaryOperator )
197
205
|| token . Kind == TokenKind . AndAnd
198
206
|| token . Kind == TokenKind . OrOr ;
199
207
}
@@ -229,7 +237,6 @@ private IEnumerable<DiagnosticRecord> FindOpenBraceViolations(TokenOperations to
229
237
{
230
238
foreach ( var lcurly in tokenOperations . GetTokenNodes ( TokenKind . LCurly ) )
231
239
{
232
-
233
240
if ( lcurly . Previous == null
234
241
|| ! IsPreviousTokenOnSameLine ( lcurly )
235
242
|| lcurly . Previous . Value . Kind == TokenKind . LCurly
@@ -239,11 +246,28 @@ private IEnumerable<DiagnosticRecord> FindOpenBraceViolations(TokenOperations to
239
246
continue ;
240
247
}
241
248
249
+ if ( lcurly . Previous . Value . Kind == TokenKind . RCurly && lcurly . Previous . Previous != null )
250
+ {
251
+ var keywordBeforeBrace = lcurly . Previous . Previous . Value ;
252
+ if ( IsKeyword ( keywordBeforeBrace ) && ! IsPreviousTokenApartByWhitespace ( lcurly . Previous ) )
253
+ {
254
+ yield return new DiagnosticRecord (
255
+ GetError ( ErrorKind . BeforeOpeningBrace ) ,
256
+ lcurly . Previous . Value . Extent ,
257
+ GetName ( ) ,
258
+ GetDiagnosticSeverity ( ) ,
259
+ tokenOperations . Ast . Extent . File ,
260
+ null ,
261
+ GetCorrections ( keywordBeforeBrace , lcurly . Previous . Value , lcurly . Value , false , true ) . ToList ( ) ) ;
262
+ }
263
+ continue ;
264
+ }
265
+
242
266
if ( IsPreviousTokenApartByWhitespace ( lcurly ) || IsPreviousTokenLParen ( lcurly ) )
243
267
{
244
268
continue ;
245
269
}
246
-
270
+
247
271
yield return new DiagnosticRecord (
248
272
GetError ( ErrorKind . BeforeOpeningBrace ) ,
249
273
lcurly . Value . Extent ,
@@ -255,6 +279,46 @@ private IEnumerable<DiagnosticRecord> FindOpenBraceViolations(TokenOperations to
255
279
}
256
280
}
257
281
282
+ private IEnumerable < DiagnosticRecord > FindKeywordAfterBraceViolations ( TokenOperations tokenOperations )
283
+ {
284
+ foreach ( var keywordNode in tokenOperations . GetTokenNodes ( IsKeyword ) )
285
+ {
286
+ var keyword = keywordNode . Value ;
287
+
288
+ if ( keywordNode . Previous != null )
289
+ {
290
+ if ( keywordNode . Previous . Value . Kind == TokenKind . RCurly &&
291
+ IsPreviousTokenOnSameLine ( keywordNode ) )
292
+ {
293
+ var hasWhitespace = IsPreviousTokenApartByWhitespace ( keywordNode ) ;
294
+
295
+ if ( ! hasWhitespace )
296
+ {
297
+ var corrections = new List < CorrectionExtent >
298
+ {
299
+ new CorrectionExtent (
300
+ keywordNode . Previous . Value . Extent . EndLineNumber ,
301
+ keyword . Extent . StartLineNumber ,
302
+ keywordNode . Previous . Value . Extent . EndColumnNumber ,
303
+ keyword . Extent . StartColumnNumber ,
304
+ " " ,
305
+ keyword . Extent . File )
306
+ } ;
307
+
308
+ yield return new DiagnosticRecord (
309
+ GetError ( ErrorKind . BeforeOpeningBrace ) ,
310
+ keyword . Extent ,
311
+ GetName ( ) ,
312
+ GetDiagnosticSeverity ( ) ,
313
+ tokenOperations . Ast . Extent . File ,
314
+ null ,
315
+ corrections ) ;
316
+ }
317
+ }
318
+ }
319
+ }
320
+ }
321
+
258
322
private IEnumerable < DiagnosticRecord > FindInnerBraceViolations ( TokenOperations tokenOperations )
259
323
{
260
324
foreach ( var lCurly in tokenOperations . GetTokenNodes ( TokenKind . LCurly ) )
@@ -509,7 +573,7 @@ private static bool IsPreviousTokenApartByWhitespace(LinkedListNode<Token> token
509
573
hasRedundantWhitespace = actualWhitespaceSize - whiteSpaceSize > 0 ;
510
574
return whiteSpaceSize == actualWhitespaceSize ;
511
575
}
512
-
576
+
513
577
private static bool IsPreviousTokenLParen ( LinkedListNode < Token > tokenNode )
514
578
{
515
579
return tokenNode . Previous . Value . Kind == TokenKind . LParen ;
@@ -536,17 +600,30 @@ private IEnumerable<DiagnosticRecord> FindOperatorViolations(TokenOperations tok
536
600
{
537
601
foreach ( var tokenNode in tokenOperations . GetTokenNodes ( IsOperator ) )
538
602
{
539
- if ( tokenNode . Previous == null
540
- || tokenNode . Next == null
541
- || tokenNode . Value . Kind == TokenKind . DotDot )
603
+ var token = tokenNode . Value ;
604
+
605
+ if ( tokenNode . Previous == null || tokenNode . Next == null || token . Kind == TokenKind . DotDot )
542
606
{
543
607
continue ;
544
608
}
545
609
546
- // exclude unary operator for cases like $foo.bar(-$Var)
547
- if ( TokenTraits . HasTrait ( tokenNode . Value . Kind , TokenFlags . UnaryOperator ) &&
548
- tokenNode . Previous . Value . Kind == TokenKind . LParen &&
549
- tokenNode . Next . Value . Kind == TokenKind . Variable )
610
+ // Check unary operator handling
611
+ bool isUnaryInMethodCall = false ;
612
+ if ( TokenTraits . HasTrait ( token . Kind , TokenFlags . UnaryOperator ) )
613
+ {
614
+ // Only skip if it's a unary operator in a method call like $foo.bar(-$var)
615
+ if ( tokenNode . Previous . Value . Kind == TokenKind . LParen &&
616
+ tokenNode . Next . Value . Kind == TokenKind . Variable &&
617
+ tokenNode . Previous . Previous != null )
618
+ {
619
+ var beforeLParen = tokenNode . Previous . Previous . Value ;
620
+
621
+ isUnaryInMethodCall = beforeLParen . Kind == TokenKind . Dot ||
622
+ ( beforeLParen . TokenFlags & TokenFlags . MemberName ) == TokenFlags . MemberName ;
623
+ }
624
+ }
625
+
626
+ if ( isUnaryInMethodCall )
550
627
{
551
628
continue ;
552
629
}
@@ -561,22 +638,30 @@ private IEnumerable<DiagnosticRecord> FindOperatorViolations(TokenOperations tok
561
638
}
562
639
}
563
640
641
+ // Check whitespace
564
642
var hasWhitespaceBefore = IsPreviousTokenOnSameLineAndApartByWhitespace ( tokenNode ) ;
565
- var hasWhitespaceAfter = tokenNode . Next . Value . Kind == TokenKind . NewLine
566
- || IsPreviousTokenOnSameLineAndApartByWhitespace ( tokenNode . Next ) ;
643
+ var hasWhitespaceAfter = tokenNode . Next . Value . Kind == TokenKind . NewLine ||
644
+ IsPreviousTokenOnSameLineAndApartByWhitespace ( tokenNode . Next ) ;
645
+
646
+ // Special case: Don't require space before unary operator if preceded by LParen
647
+ if ( TokenTraits . HasTrait ( token . Kind , TokenFlags . UnaryOperator ) &&
648
+ tokenNode . Previous . Value . Kind == TokenKind . LParen )
649
+ {
650
+ hasWhitespaceBefore = true ;
651
+ }
567
652
568
653
if ( ! hasWhitespaceAfter || ! hasWhitespaceBefore )
569
654
{
570
655
yield return new DiagnosticRecord (
571
656
GetError ( ErrorKind . Operator ) ,
572
- tokenNode . Value . Extent ,
657
+ token . Extent ,
573
658
GetName ( ) ,
574
659
GetDiagnosticSeverity ( ) ,
575
660
tokenOperations . Ast . Extent . File ,
576
661
null ,
577
662
GetCorrections (
578
663
tokenNode . Previous . Value ,
579
- tokenNode . Value ,
664
+ token ,
580
665
tokenNode . Next . Value ,
581
666
hasWhitespaceBefore ,
582
667
hasWhitespaceAfter ) ) ;
@@ -614,7 +699,9 @@ private List<CorrectionExtent> GetCorrections(
614
699
615
700
var extent = new ScriptExtent (
616
701
new ScriptPosition ( e1 . File , e1 . EndLineNumber , e1 . EndColumnNumber , null ) ,
617
- new ScriptPosition ( e2 . File , e2 . StartLineNumber , e2 . StartColumnNumber , null ) ) ;
702
+ new ScriptPosition ( e2 . File , e2 . StartLineNumber , e2 . StartColumnNumber , null )
703
+ ) ;
704
+
618
705
return new List < CorrectionExtent > ( )
619
706
{
620
707
new CorrectionExtent (
@@ -630,6 +717,5 @@ private static bool IsPreviousTokenOnSameLine(LinkedListNode<Token> lparen)
630
717
{
631
718
return lparen . Previous . Value . Extent . EndLineNumber == lparen . Value . Extent . StartLineNumber ;
632
719
}
633
-
634
720
}
635
- }
721
+ }
0 commit comments