Skip to content

Commit 9ea3dc5

Browse files
Index brace and pipeline lookups during formatting
1 parent 411c3d0 commit 9ea3dc5

3 files changed

Lines changed: 71 additions & 24 deletions

File tree

‎Engine/TokenOperations.cs‎

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -122,28 +122,37 @@ private IEnumerable<Token> GetBraceInCommandElement(TokenKind tokenKind)
122122
yield break;
123123
}
124124

125-
Func<Token, Ast, bool> predicate;
126-
127125
switch (tokenKind)
128126
{
129127
case TokenKind.LCurly:
130-
predicate = (x, cmdElemAst) =>
131-
x.Kind == TokenKind.LCurly && x.Extent.StartOffset == cmdElemAst.Extent.StartOffset;
132-
break;
133-
134128
case TokenKind.RCurly:
135-
predicate = (x, cmdElemAst) =>
136-
x.Kind == TokenKind.RCurly && x.Extent.EndOffset == cmdElemAst.Extent.EndOffset;
137129
break;
138130

139131
default:
140132
throw new ArgumentException("", nameof(tokenKind));
141133
}
142134

135+
// Index once rather than scanning every token for each script block.
136+
// Preserve the first matching token if callers supply duplicate offsets.
137+
var braceTokens = new Dictionary<int, Token>();
138+
foreach (var token in tokens)
139+
{
140+
if (token.Kind != tokenKind)
141+
{
142+
continue;
143+
}
144+
145+
int offset = tokenKind == TokenKind.LCurly ? token.Extent.StartOffset : token.Extent.EndOffset;
146+
if (!braceTokens.ContainsKey(offset))
147+
{
148+
braceTokens.Add(offset, token);
149+
}
150+
}
151+
143152
foreach (var cmdElemAst in cmdElemAsts)
144153
{
145-
var tokenFound = tokens.FirstOrDefault(token => predicate(token, cmdElemAst));
146-
if (tokenFound != null)
154+
int offset = tokenKind == TokenKind.LCurly ? cmdElemAst.Extent.StartOffset : cmdElemAst.Extent.EndOffset;
155+
if (braceTokens.TryGetValue(offset, out var tokenFound))
147156
{
148157
yield return tokenFound;
149158
}

‎Rules/UseConsistentIndentation.cs‎

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -133,13 +133,23 @@ public override IEnumerable<DiagnosticRecord> AnalyzeScript(Ast ast, string file
133133
var onNewLine = true;
134134
var pipelineAsts = ast.FindAll(testAst => testAst is PipelineAst && (testAst as PipelineAst).PipelineElements.Count > 1, true).ToList();
135135
// Sort by end position so that inner (nested) pipelines appear before outer ones.
136-
// This is required by MatchingPipelineAstEnd, whose early-break optimization
137-
// would otherwise skip nested pipelines that end before their outer pipeline.
136+
// Preserve this order when selecting the first matching pipeline on each line.
138137
pipelineAsts.Sort((a, b) =>
139138
{
140139
int lineCmp = a.Extent.EndScriptPosition.LineNumber.CompareTo(b.Extent.EndScriptPosition.LineNumber);
141140
return lineCmp != 0 ? lineCmp : a.Extent.EndScriptPosition.ColumnNumber.CompareTo(b.Extent.EndScriptPosition.ColumnNumber);
142141
});
142+
var pipelineEndsByLine = new Dictionary<int, List<Ast>>();
143+
foreach (var pipeline in pipelineAsts)
144+
{
145+
int endLine = pipeline.Extent.EndScriptPosition.LineNumber;
146+
if (!pipelineEndsByLine.TryGetValue(endLine, out var pipelinesOnLine))
147+
{
148+
pipelinesOnLine = new List<Ast>();
149+
pipelineEndsByLine.Add(endLine, pipelinesOnLine);
150+
}
151+
pipelinesOnLine.Add(pipeline);
152+
}
143153
// Track pipeline indentation increases per PipelineAst instead of as a single
144154
// flat counter. A flat counter caused all accumulated pipeline indentation to be
145155
// subtracted when *any* pipeline ended, instead of only the contribution from
@@ -306,7 +316,7 @@ public override IEnumerable<DiagnosticRecord> AnalyzeScript(Ast ast, string file
306316
if (pipelineIndentationStyle == PipelineIndentationStyle.None) { continue; }
307317

308318
// Check if the current token matches the end of a PipelineAst
309-
PipelineAst matchingPipeLineAstEnd = MatchingPipelineAstEnd(pipelineAsts, token);
319+
PipelineAst matchingPipeLineAstEnd = MatchingPipelineAstEnd(pipelineEndsByLine, token);
310320
if (matchingPipeLineAstEnd == null)
311321
{
312322
continue;
@@ -493,24 +503,22 @@ private static CommandBaseAst LastPipeOnFirstLineWithPipeUsage(PipelineAst pipel
493503
return lastPipeOnFirstLineWithPipeUsage;
494504
}
495505

496-
private static PipelineAst MatchingPipelineAstEnd(List<Ast> pipelineAsts, Token token)
506+
private static PipelineAst MatchingPipelineAstEnd(Dictionary<int, List<Ast>> pipelineEndsByLine, Token token)
497507
{
498-
PipelineAst matchingPipeLineAstEnd = null;
499-
for (int i = 0; i < pipelineAsts.Count; i++)
508+
if (!pipelineEndsByLine.TryGetValue(token.Extent.EndScriptPosition.LineNumber, out var pipelineAsts))
500509
{
501-
if (pipelineAsts[i].Extent.EndScriptPosition.LineNumber > token.Extent.EndScriptPosition.LineNumber)
502-
{
503-
break;
504-
}
510+
return null;
511+
}
505512

513+
for (int i = 0; i < pipelineAsts.Count; i++)
514+
{
506515
if (PositionIsEqual(pipelineAsts[i].Extent.EndScriptPosition, token.Extent.EndScriptPosition))
507516
{
508-
matchingPipeLineAstEnd = pipelineAsts[i] as PipelineAst;
509-
break;
517+
return pipelineAsts[i] as PipelineAst;
510518
}
511519
}
512520

513-
return matchingPipeLineAstEnd;
521+
return null;
514522
}
515523

516524
/// <summary>

‎Tests/Engine/TokenOperations.tests.ps1‎

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,36 @@
22
# Licensed under the MIT License.
33

44
Describe "TokenOperations" {
5+
It 'Should return only script-block expression braces in AST order' {
6+
$scriptText = @'
7+
function Test-Example { 'function body' }
8+
$value = @{ Handler = { 'hashtable value' } }
9+
Get-Item | ForEach-Object { Where-Object { $_.Name -ne '{literal}' } }
10+
& { 'invoked block' }
11+
'@
12+
$tokens = $null
13+
$parseErrors = $null
14+
$scriptAst = [System.Management.Automation.Language.Parser]::ParseInput($scriptText, [ref] $tokens, [ref] $parseErrors)
15+
$parseErrors | Should -BeNullOrEmpty
16+
$tokenOperations = [Microsoft.Windows.PowerShell.ScriptAnalyzer.TokenOperations]::new($tokens, $scriptAst)
17+
$openBraces = @($tokenOperations.GetOpenBracesInCommandElements())
18+
$closeBraces = @($tokenOperations.GetCloseBracesInCommandElements())
19+
$openBraces.Count | Should -Be 4
20+
$closeBraces.Count | Should -Be 4
21+
($openBraces.Extent.StartOffset -join ',') | Should -Be (@(
22+
$scriptText.IndexOf("{ 'hashtable value'")
23+
$scriptText.IndexOf('{ Where-Object')
24+
$scriptText.IndexOf('{ $_.Name')
25+
$scriptText.IndexOf("{ 'invoked block'")
26+
) -join ',')
27+
($closeBraces.Extent.EndOffset -join ',') | Should -Be (@(
28+
$scriptText.IndexOf("'hashtable value' }") + "'hashtable value' }".Length
29+
$scriptText.IndexOf("'{literal}' } }") + "'{literal}' } }".Length
30+
$scriptText.IndexOf("'{literal}' }") + "'{literal}' }".Length
31+
$scriptText.IndexOf("'invoked block' }") + "'invoked block' }".Length
32+
) -join ',')
33+
}
34+
535
It "Should return correct AST position for assignment operator in hash table" {
636
$scriptText = @'
737
$h = @{
@@ -195,4 +225,4 @@ $object.{MemberName
195225

196226
}
197227

198-
}
228+
}

0 commit comments

Comments
 (0)