@@ -22,8 +22,12 @@ namespace Microsoft.Windows.PowerShell.ScriptAnalyzer.BuiltinRules
2222#endif
2323 public class UseConsistentWhitespace : ConfigurableRule
2424 {
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+
2731 private const int whiteSpaceSize = 1 ;
2832 private const string whiteSpace = " " ;
2933 private readonly SortedSet < TokenKind > openParenKeywordAllowList = new SortedSet < TokenKind > ( )
@@ -33,7 +37,9 @@ private enum ErrorKind { BeforeOpeningBrace, Paren, Operator, SeparatorComma, Se
3337 TokenKind . Switch ,
3438 TokenKind . For ,
3539 TokenKind . Foreach ,
36- TokenKind . While
40+ TokenKind . While ,
41+ TokenKind . Until ,
42+ TokenKind . Do
3743 } ;
3844
3945 private List < Func < TokenOperations , IEnumerable < DiagnosticRecord > > > violationFinders
@@ -72,6 +78,7 @@ public override void ConfigureRule(IDictionary<string, object> paramValueMap)
7278 if ( CheckOpenBrace )
7379 {
7480 violationFinders . Add ( FindOpenBraceViolations ) ;
81+ violationFinders . Add ( FindKeywordAfterBraceViolations ) ;
7582 }
7683
7784 if ( CheckInnerBrace )
@@ -194,6 +201,7 @@ private bool IsOperator(Token token)
194201 return TokenTraits . HasTrait ( token . Kind , TokenFlags . AssignmentOperator )
195202 || TokenTraits . HasTrait ( token . Kind , TokenFlags . BinaryPrecedenceAdd )
196203 || TokenTraits . HasTrait ( token . Kind , TokenFlags . BinaryPrecedenceMultiply )
204+ || TokenTraits . HasTrait ( token . Kind , TokenFlags . UnaryOperator )
197205 || token . Kind == TokenKind . AndAnd
198206 || token . Kind == TokenKind . OrOr ;
199207 }
@@ -229,7 +237,6 @@ private IEnumerable<DiagnosticRecord> FindOpenBraceViolations(TokenOperations to
229237 {
230238 foreach ( var lcurly in tokenOperations . GetTokenNodes ( TokenKind . LCurly ) )
231239 {
232-
233240 if ( lcurly . Previous == null
234241 || ! IsPreviousTokenOnSameLine ( lcurly )
235242 || lcurly . Previous . Value . Kind == TokenKind . LCurly
@@ -239,11 +246,28 @@ private IEnumerable<DiagnosticRecord> FindOpenBraceViolations(TokenOperations to
239246 continue ;
240247 }
241248
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+
242266 if ( IsPreviousTokenApartByWhitespace ( lcurly ) || IsPreviousTokenLParen ( lcurly ) )
243267 {
244268 continue ;
245269 }
246-
270+
247271 yield return new DiagnosticRecord (
248272 GetError ( ErrorKind . BeforeOpeningBrace ) ,
249273 lcurly . Value . Extent ,
@@ -255,6 +279,46 @@ private IEnumerable<DiagnosticRecord> FindOpenBraceViolations(TokenOperations to
255279 }
256280 }
257281
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+
258322 private IEnumerable < DiagnosticRecord > FindInnerBraceViolations ( TokenOperations tokenOperations )
259323 {
260324 foreach ( var lCurly in tokenOperations . GetTokenNodes ( TokenKind . LCurly ) )
@@ -509,7 +573,7 @@ private static bool IsPreviousTokenApartByWhitespace(LinkedListNode<Token> token
509573 hasRedundantWhitespace = actualWhitespaceSize - whiteSpaceSize > 0 ;
510574 return whiteSpaceSize == actualWhitespaceSize ;
511575 }
512-
576+
513577 private static bool IsPreviousTokenLParen ( LinkedListNode < Token > tokenNode )
514578 {
515579 return tokenNode . Previous . Value . Kind == TokenKind . LParen ;
@@ -536,17 +600,30 @@ private IEnumerable<DiagnosticRecord> FindOperatorViolations(TokenOperations tok
536600 {
537601 foreach ( var tokenNode in tokenOperations . GetTokenNodes ( IsOperator ) )
538602 {
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 )
542606 {
543607 continue ;
544608 }
545609
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 )
550627 {
551628 continue ;
552629 }
@@ -561,22 +638,30 @@ private IEnumerable<DiagnosticRecord> FindOperatorViolations(TokenOperations tok
561638 }
562639 }
563640
641+ // Check whitespace
564642 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+ }
567652
568653 if ( ! hasWhitespaceAfter || ! hasWhitespaceBefore )
569654 {
570655 yield return new DiagnosticRecord (
571656 GetError ( ErrorKind . Operator ) ,
572- tokenNode . Value . Extent ,
657+ token . Extent ,
573658 GetName ( ) ,
574659 GetDiagnosticSeverity ( ) ,
575660 tokenOperations . Ast . Extent . File ,
576661 null ,
577662 GetCorrections (
578663 tokenNode . Previous . Value ,
579- tokenNode . Value ,
664+ token ,
580665 tokenNode . Next . Value ,
581666 hasWhitespaceBefore ,
582667 hasWhitespaceAfter ) ) ;
@@ -614,7 +699,9 @@ private List<CorrectionExtent> GetCorrections(
614699
615700 var extent = new ScriptExtent (
616701 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+
618705 return new List < CorrectionExtent > ( )
619706 {
620707 new CorrectionExtent (
@@ -630,6 +717,5 @@ private static bool IsPreviousTokenOnSameLine(LinkedListNode<Token> lparen)
630717 {
631718 return lparen . Previous . Value . Extent . EndLineNumber == lparen . Value . Extent . StartLineNumber ;
632719 }
633-
634720 }
635- }
721+ }
0 commit comments