Skip to content

Commit deb3512

Browse files
committed
wip: Before-Optimized UseConsistentWhitespace
1 parent 7e60fb3 commit deb3512

File tree

2 files changed

+374
-56
lines changed

2 files changed

+374
-56
lines changed

Rules/UseConsistentWhitespace.cs

Lines changed: 181 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)