@@ -39,7 +39,10 @@ private enum ErrorKind
3939 TokenKind . Foreach ,
4040 TokenKind . While ,
4141 TokenKind . Until ,
42- TokenKind . Do
42+ TokenKind . Do ,
43+ TokenKind . Else ,
44+ TokenKind . Catch ,
45+ TokenKind . Finally
4346 } ;
4447
4548 private List < Func < TokenOperations , IEnumerable < DiagnosticRecord > > > violationFinders
@@ -78,6 +81,7 @@ public override void ConfigureRule(IDictionary<string, object> paramValueMap)
7881 if ( CheckOpenBrace )
7982 {
8083 violationFinders . Add ( FindOpenBraceViolations ) ;
84+ violationFinders . Add ( FindSpaceAfterClosingBraceViolations ) ;
8185 violationFinders . Add ( FindKeywordAfterBraceViolations ) ;
8286 }
8387
@@ -301,7 +305,7 @@ private IEnumerable<DiagnosticRecord> FindKeywordAfterBraceViolations(TokenOpera
301305 keyword . Extent . StartLineNumber ,
302306 keywordNode . Previous . Value . Extent . EndColumnNumber ,
303307 keyword . Extent . StartColumnNumber ,
304- " " ,
308+ whiteSpace ,
305309 keyword . Extent . File )
306310 } ;
307311
@@ -321,54 +325,131 @@ private IEnumerable<DiagnosticRecord> FindKeywordAfterBraceViolations(TokenOpera
321325
322326 private IEnumerable < DiagnosticRecord > FindInnerBraceViolations ( TokenOperations tokenOperations )
323327 {
328+ // Handle opening braces
324329 foreach ( var lCurly in tokenOperations . GetTokenNodes ( TokenKind . LCurly ) )
325330 {
326331 if ( lCurly . Next == null
327- || ! ( lCurly . Previous == null || IsPreviousTokenOnSameLine ( lCurly ) )
332+ || ( lCurly . Previous != null && ! IsPreviousTokenOnSameLine ( lCurly ) )
328333 || lCurly . Next . Value . Kind == TokenKind . NewLine
329- || lCurly . Next . Value . Kind == TokenKind . LineContinuation
330- || lCurly . Next . Value . Kind == TokenKind . RCurly
331- )
334+ || lCurly . Next . Value . Kind == TokenKind . LineContinuation )
335+ {
336+ continue ;
337+ }
338+
339+ // Special handling for empty braces - they should have a space
340+ if ( lCurly . Next . Value . Kind == TokenKind . RCurly )
332341 {
342+ if ( ! IsNextTokenApartByWhitespace ( lCurly ) )
343+ {
344+ var prevToken = lCurly . Previous ? . Value ?? lCurly . Value ;
345+ var nextToken = lCurly . Next ? . Value ?? lCurly . Value ;
346+
347+ yield return new DiagnosticRecord (
348+ GetError ( ErrorKind . AfterOpeningBrace ) ,
349+ lCurly . Value . Extent ,
350+ GetName ( ) ,
351+ GetDiagnosticSeverity ( ) ,
352+ tokenOperations . Ast . Extent . File ,
353+ null ,
354+ GetCorrections ( prevToken , lCurly . Value , nextToken , true , false ) . ToList ( ) ) ;
355+ }
333356 continue ;
334357 }
335358
336359 if ( ! IsNextTokenApartByWhitespace ( lCurly ) )
337360 {
361+ var prevToken = lCurly . Previous ? . Value ?? lCurly . Value ;
338362 yield return new DiagnosticRecord (
339363 GetError ( ErrorKind . AfterOpeningBrace ) ,
340364 lCurly . Value . Extent ,
341365 GetName ( ) ,
342366 GetDiagnosticSeverity ( ) ,
343367 tokenOperations . Ast . Extent . File ,
344368 null ,
345- GetCorrections ( lCurly . Previous . Value , lCurly . Value , lCurly . Next . Value , true , false ) . ToList ( ) ) ;
369+ GetCorrections ( prevToken , lCurly . Value , lCurly . Next . Value , true , false ) . ToList ( ) ) ;
346370 }
347371 }
348372
373+ // Handle closing braces
349374 foreach ( var rCurly in tokenOperations . GetTokenNodes ( TokenKind . RCurly ) )
350375 {
351- if ( rCurly . Previous == null
352- || ! IsPreviousTokenOnSameLine ( rCurly )
353- || rCurly . Previous . Value . Kind == TokenKind . LCurly
376+ if ( rCurly . Previous == null )
377+ {
378+ continue ;
379+ }
380+
381+ if ( ! IsPreviousTokenOnSameLine ( rCurly )
354382 || rCurly . Previous . Value . Kind == TokenKind . NewLine
355383 || rCurly . Previous . Value . Kind == TokenKind . LineContinuation
356- || rCurly . Previous . Value . Kind == TokenKind . AtCurly
357- )
384+ || rCurly . Previous . Value . Kind == TokenKind . AtCurly )
385+ {
386+ continue ;
387+ }
388+
389+ // Skip empty braces that already have space
390+ if ( rCurly . Previous . Value . Kind == TokenKind . LCurly && IsPreviousTokenApartByWhitespace ( rCurly ) )
358391 {
359392 continue ;
360393 }
361394
362- if ( ! IsPreviousTokenApartByWhitespace ( rCurly ) )
395+ // Use AST to check if this is a hashtable
396+ var ast = tokenOperations . GetAstPosition ( rCurly . Value ) ;
397+
398+ if ( ast is HashtableAst hashtableAst )
399+ {
400+ if ( rCurly . Value . Extent . EndOffset == hashtableAst . Extent . EndOffset )
401+ {
402+ continue ;
403+ }
404+ }
405+
406+ bool hasSpace = IsPreviousTokenApartByWhitespace ( rCurly ) ;
407+
408+ if ( ! hasSpace )
363409 {
410+ var nextToken = rCurly . Next ? . Value ?? rCurly . Value ;
364411 yield return new DiagnosticRecord (
365412 GetError ( ErrorKind . BeforeClosingBrace ) ,
366413 rCurly . Value . Extent ,
367414 GetName ( ) ,
368415 GetDiagnosticSeverity ( ) ,
369416 tokenOperations . Ast . Extent . File ,
370417 null ,
371- GetCorrections ( rCurly . Previous . Value , rCurly . Value , rCurly . Next . Value , false , true ) . ToList ( ) ) ;
418+ GetCorrections ( rCurly . Previous . Value , rCurly . Value , nextToken , false , true ) . ToList ( ) ) ;
419+ }
420+ }
421+ }
422+
423+ private IEnumerable < DiagnosticRecord > FindSpaceAfterClosingBraceViolations ( TokenOperations tokenOperations )
424+ {
425+ foreach ( var rCurly in tokenOperations . GetTokenNodes ( TokenKind . RCurly ) )
426+ {
427+ if ( rCurly . Next == null
428+ || ! IsPreviousTokenOnSameLine ( rCurly . Next )
429+ || rCurly . Next . Value . Kind == TokenKind . NewLine
430+ || rCurly . Next . Value . Kind == TokenKind . EndOfInput
431+ || rCurly . Next . Value . Kind == TokenKind . Semi
432+ || rCurly . Next . Value . Kind == TokenKind . Comma
433+ || rCurly . Next . Value . Kind == TokenKind . RParen )
434+ {
435+ continue ;
436+ }
437+
438+ // Need space after } before keywords, numbers, or another }
439+ if ( ( IsKeyword ( rCurly . Next . Value )
440+ || rCurly . Next . Value . Kind == TokenKind . Number
441+ || rCurly . Next . Value . Kind == TokenKind . RCurly )
442+ && ! IsNextTokenApartByWhitespace ( rCurly ) )
443+ {
444+ var prevToken = rCurly . Previous ? . Value ?? rCurly . Value ;
445+ yield return new DiagnosticRecord (
446+ GetError ( ErrorKind . BeforeOpeningBrace ) ,
447+ rCurly . Value . Extent ,
448+ GetName ( ) ,
449+ GetDiagnosticSeverity ( ) ,
450+ tokenOperations . Ast . Extent . File ,
451+ null ,
452+ GetCorrections ( prevToken , rCurly . Value , rCurly . Next . Value , true , false ) . ToList ( ) ) ;
372453 }
373454 }
374455 }
@@ -456,8 +537,7 @@ private IEnumerable<DiagnosticRecord> FindOpenParenViolations(TokenOperations to
456537
457538 private IEnumerable < DiagnosticRecord > FindParameterViolations ( Ast ast )
458539 {
459- IEnumerable < Ast > commandAsts = ast . FindAll (
460- testAst => testAst is CommandAst , true ) ;
540+ IEnumerable < Ast > commandAsts = ast . FindAll ( testAst => testAst is CommandAst , true ) ;
461541 foreach ( CommandAst commandAst in commandAsts )
462542 {
463543 /// When finding all the command parameter elements, there is no guarantee that
@@ -471,6 +551,7 @@ private IEnumerable<DiagnosticRecord> FindParameterViolations(Ast ast)
471551 ) . ThenBy (
472552 e => e . Extent . StartColumnNumber
473553 ) . ToList ( ) ;
554+
474555 for ( int i = 0 ; i < commandParameterAstElements . Count - 1 ; i ++ )
475556 {
476557 IScriptExtent leftExtent = commandParameterAstElements [ i ] . Extent ;
@@ -511,33 +592,90 @@ private bool IsSeparator(Token token)
511592
512593 private IEnumerable < DiagnosticRecord > FindSeparatorViolations ( TokenOperations tokenOperations )
513594 {
514- Func < LinkedListNode < Token > , bool > predicate = node =>
595+ foreach ( var tokenNode in tokenOperations . GetTokenNodes ( IsSeparator ) )
515596 {
516- return node . Next != null
517- && node . Next . Value . Kind != TokenKind . NewLine
518- && node . Next . Value . Kind != TokenKind . Comment
519- && node . Next . Value . Kind != TokenKind . EndOfInput // semicolon can be followed by end of input
520- && ! IsPreviousTokenApartByWhitespace ( node . Next ) ;
521- } ;
597+ if ( tokenNode . Next == null
598+ || tokenNode . Next . Value . Kind == TokenKind . NewLine
599+ || tokenNode . Next . Value . Kind == TokenKind . Comment
600+ || tokenNode . Next . Value . Kind == TokenKind . EndOfInput )
601+ {
602+ continue ;
603+ }
522604
523- foreach ( var tokenNode in tokenOperations . GetTokenNodes ( IsSeparator ) . Where ( predicate ) )
524- {
525- var errorKind = tokenNode . Value . Kind == TokenKind . Comma
526- ? ErrorKind . SeparatorComma
527- : ErrorKind . SeparatorSemi ;
528- yield return getDiagnosticRecord (
529- tokenNode . Value ,
530- errorKind ,
531- GetCorrections (
532- tokenNode . Previous . Value ,
533- tokenNode . Value ,
534- tokenNode . Next . Value ,
535- true ,
536- false ) ) ;
605+ var separator = tokenNode . Value ;
606+
607+ // Check if comma is part of a parameter value by looking at surrounding tokens
608+ if ( separator . Kind == TokenKind . Comma )
609+ {
610+ // Look for pattern: word,word (no spaces) which indicates parameter value
611+ if ( tokenNode . Previous != null && tokenNode . Next != null )
612+ {
613+ var prevTok = tokenNode . Previous . Value ;
614+ var nextTok = tokenNode . Next . Value ;
615+
616+ // Skip if comma appears to be within a parameter value (no spaces around it)
617+ if ( ( prevTok . Kind == TokenKind . Identifier || prevTok . Kind == TokenKind . Generic ) &&
618+ ( nextTok . Kind == TokenKind . Identifier || nextTok . Kind == TokenKind . Generic ) &&
619+ prevTok . Extent . EndColumnNumber == separator . Extent . StartColumnNumber &&
620+ separator . Extent . EndColumnNumber == nextTok . Extent . StartColumnNumber )
621+ {
622+ // This looks like key=value,key=value pattern
623+ continue ;
624+ }
625+ }
626+ }
627+
628+ var prevToken = tokenNode . Previous . Value ;
629+ var nextToken = tokenNode . Next . Value ;
630+
631+ // Check for space before separator (should not exist)
632+ if ( tokenNode . Previous != null && IsPreviousTokenOnSameLine ( tokenNode ) )
633+ {
634+ var spaceBefore = separator . Extent . StartColumnNumber - prevToken . Extent . EndColumnNumber ;
635+ if ( spaceBefore > 0 )
636+ {
637+ // Remove space before separator
638+ yield return new DiagnosticRecord (
639+ GetError ( separator . Kind == TokenKind . Comma ? ErrorKind . SeparatorComma : ErrorKind . SeparatorSemi ) ,
640+ separator . Extent ,
641+ GetName ( ) ,
642+ GetDiagnosticSeverity ( ) ,
643+ separator . Extent . File ,
644+ null ,
645+ new List < CorrectionExtent > {
646+ new CorrectionExtent (
647+ prevToken . Extent . EndLineNumber ,
648+ separator . Extent . StartLineNumber ,
649+ prevToken . Extent . EndColumnNumber ,
650+ separator . Extent . StartColumnNumber ,
651+ string . Empty ,
652+ separator . Extent . File )
653+ } ) ;
654+ }
655+ }
656+
657+ // Check for space after separator (should exist)
658+ if ( ! IsPreviousTokenApartByWhitespace ( tokenNode . Next ) )
659+ {
660+ var errorKind = separator . Kind == TokenKind . Comma ? ErrorKind . SeparatorComma : ErrorKind . SeparatorSemi ;
661+
662+ yield return GetDiagnosticRecord (
663+ separator ,
664+ errorKind ,
665+ new List < CorrectionExtent > {
666+ new CorrectionExtent (
667+ separator . Extent . EndLineNumber ,
668+ nextToken . Extent . StartLineNumber ,
669+ separator . Extent . EndColumnNumber ,
670+ nextToken . Extent . StartColumnNumber ,
671+ whiteSpace ,
672+ separator . Extent . File )
673+ } ) ;
674+ }
537675 }
538676 }
539677
540- private DiagnosticRecord getDiagnosticRecord (
678+ private DiagnosticRecord GetDiagnosticRecord (
541679 Token token ,
542680 ErrorKind errKind ,
543681 List < CorrectionExtent > corrections )
@@ -602,6 +740,11 @@ private IEnumerable<DiagnosticRecord> FindOperatorViolations(TokenOperations tok
602740 {
603741 var token = tokenNode . Value ;
604742
743+ if ( IsSeparator ( token ) )
744+ {
745+ continue ;
746+ }
747+
605748 if ( tokenNode . Previous == null || tokenNode . Next == null || token . Kind == TokenKind . DotDot )
606749 {
607750 continue ;
@@ -617,7 +760,6 @@ private IEnumerable<DiagnosticRecord> FindOperatorViolations(TokenOperations tok
617760 tokenNode . Previous . Previous != null )
618761 {
619762 var beforeLParen = tokenNode . Previous . Previous . Value ;
620-
621763 isUnaryInMethodCall = beforeLParen . Kind == TokenKind . Dot ||
622764 ( beforeLParen . TokenFlags & TokenFlags . MemberName ) == TokenFlags . MemberName ;
623765 }
0 commit comments