Skip to content

Commit aa73a21

Browse files
committed
Move to IReadOnlyList from IEnumerable to prevent double enumerations
Seems to be the easiest "clean and safe" way to handle this.
1 parent 6790594 commit aa73a21

File tree

5 files changed

+56
-67
lines changed

5 files changed

+56
-67
lines changed

src/PowerShellEditorServices/Services/DebugAdapter/BreakpointService.cs

+5-7
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ public BreakpointService(
4040
_debugStateService = debugStateService;
4141
}
4242

43-
public async Task<List<Breakpoint>> GetBreakpointsAsync()
43+
public async Task<IReadOnlyList<Breakpoint>> GetBreakpointsAsync()
4444
{
4545
if (BreakpointApiUtils.SupportsBreakpointApis(_editorServicesHost.CurrentRunspace))
4646
{
@@ -52,14 +52,12 @@ public async Task<List<Breakpoint>> GetBreakpointsAsync()
5252

5353
// Legacy behavior
5454
PSCommand psCommand = new PSCommand().AddCommand(@"Microsoft.PowerShell.Utility\Get-PSBreakpoint");
55-
IEnumerable<Breakpoint> breakpoints = await _executionService
55+
return await _executionService
5656
.ExecutePSCommandAsync<Breakpoint>(psCommand, CancellationToken.None)
5757
.ConfigureAwait(false);
58-
59-
return breakpoints.ToList();
6058
}
6159

62-
public async Task<IEnumerable<BreakpointDetails>> SetBreakpointsAsync(string escapedScriptPath, IEnumerable<BreakpointDetails> breakpoints)
60+
public async Task<IReadOnlyList<BreakpointDetails>> SetBreakpointsAsync(string escapedScriptPath, IReadOnlyList<BreakpointDetails> breakpoints)
6361
{
6462
if (BreakpointApiUtils.SupportsBreakpointApis(_editorServicesHost.CurrentRunspace))
6563
{
@@ -147,7 +145,7 @@ public async Task<IEnumerable<BreakpointDetails>> SetBreakpointsAsync(string esc
147145
return configuredBreakpoints;
148146
}
149147

150-
public async Task<IEnumerable<CommandBreakpointDetails>> SetCommandBreakpointsAsync(IEnumerable<CommandBreakpointDetails> breakpoints)
148+
public async Task<IReadOnlyList<CommandBreakpointDetails>> SetCommandBreakpointsAsync(IReadOnlyList<CommandBreakpointDetails> breakpoints)
151149
{
152150
if (BreakpointApiUtils.SupportsBreakpointApis(_editorServicesHost.CurrentRunspace))
153151
{
@@ -216,7 +214,7 @@ public async Task<IEnumerable<CommandBreakpointDetails>> SetCommandBreakpointsAs
216214
// If no PSCommand was created then there are no breakpoints to set.
217215
if (psCommand is not null)
218216
{
219-
IEnumerable<Breakpoint> setBreakpoints = await _executionService
217+
IReadOnlyList<Breakpoint> setBreakpoints = await _executionService
220218
.ExecutePSCommandAsync<Breakpoint>(psCommand, CancellationToken.None)
221219
.ConfigureAwait(false);
222220
configuredBreakpoints.AddRange(setBreakpoints.Select(CommandBreakpointDetails.Create));

src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs

+10-15
Original file line numberDiff line numberDiff line change
@@ -130,9 +130,9 @@ public DebugService(
130130
/// <param name="breakpoints">BreakpointDetails for each breakpoint that will be set.</param>
131131
/// <param name="clearExisting">If true, causes all existing breakpoints to be cleared before setting new ones.</param>
132132
/// <returns>An awaitable Task that will provide details about the breakpoints that were set.</returns>
133-
public async Task<IEnumerable<BreakpointDetails>> SetLineBreakpointsAsync(
133+
public async Task<IReadOnlyList<BreakpointDetails>> SetLineBreakpointsAsync(
134134
ScriptFile scriptFile,
135-
IEnumerable<BreakpointDetails> breakpoints,
135+
IReadOnlyList<BreakpointDetails> breakpoints,
136136
bool clearExisting = true)
137137
{
138138
DscBreakpointCapability dscBreakpoints = await _debugContext.GetDscBreakpointCapabilityAsync().ConfigureAwait(false);
@@ -146,15 +146,15 @@ public async Task<IEnumerable<BreakpointDetails>> SetLineBreakpointsAsync(
146146
if (!_remoteFileManager.IsUnderRemoteTempPath(scriptPath))
147147
{
148148
_logger.LogTrace($"Could not set breakpoints for local path '{scriptPath}' in a remote session.");
149-
return Enumerable.Empty<BreakpointDetails>();
149+
return Array.Empty<BreakpointDetails>();
150150
}
151151

152152
scriptPath = _remoteFileManager.GetMappedPath(scriptPath, _psesHost.CurrentRunspace);
153153
}
154154
else if (temporaryScriptListingPath?.Equals(scriptPath, StringComparison.CurrentCultureIgnoreCase) == true)
155155
{
156156
_logger.LogTrace($"Could not set breakpoint on temporary script listing path '{scriptPath}'.");
157-
return Enumerable.Empty<BreakpointDetails>();
157+
return Array.Empty<BreakpointDetails>();
158158
}
159159

160160
// Fix for issue #123 - file paths that contain wildcard chars [ and ] need to
@@ -182,25 +182,20 @@ public async Task<IEnumerable<BreakpointDetails>> SetLineBreakpointsAsync(
182182
/// <param name="breakpoints">CommandBreakpointDetails for each command breakpoint that will be set.</param>
183183
/// <param name="clearExisting">If true, causes all existing function breakpoints to be cleared before setting new ones.</param>
184184
/// <returns>An awaitable Task that will provide details about the breakpoints that were set.</returns>
185-
public async Task<CommandBreakpointDetails[]> SetCommandBreakpointsAsync(
186-
CommandBreakpointDetails[] breakpoints,
185+
public async Task<IReadOnlyList<CommandBreakpointDetails>> SetCommandBreakpointsAsync(
186+
IReadOnlyList<CommandBreakpointDetails> breakpoints,
187187
bool clearExisting = true)
188188
{
189-
CommandBreakpointDetails[] resultBreakpointDetails = null;
190-
191189
if (clearExisting)
192190
{
193191
// Flatten dictionary values into one list and remove them all.
194-
IEnumerable<Breakpoint> existingBreakpoints = await _breakpointService.GetBreakpointsAsync().ConfigureAwait(false);
192+
IReadOnlyList<Breakpoint> existingBreakpoints = await _breakpointService.GetBreakpointsAsync().ConfigureAwait(false);
195193
await _breakpointService.RemoveBreakpointsAsync(existingBreakpoints.OfType<CommandBreakpoint>()).ConfigureAwait(false);
196194
}
197195

198-
if (breakpoints.Length > 0)
199-
{
200-
resultBreakpointDetails = (await _breakpointService.SetCommandBreakpointsAsync(breakpoints).ConfigureAwait(false)).ToArray();
201-
}
202-
203-
return resultBreakpointDetails ?? Array.Empty<CommandBreakpointDetails>();
196+
return breakpoints.Count > 0
197+
? await _breakpointService.SetCommandBreakpointsAsync(breakpoints).ConfigureAwait(false)
198+
: Array.Empty<CommandBreakpointDetails>();
204199
}
205200

206201
/// <summary>

src/PowerShellEditorServices/Services/DebugAdapter/Handlers/BreakpointHandlers.cs

+8-13
Original file line numberDiff line numberDiff line change
@@ -83,17 +83,17 @@ public async Task<SetBreakpointsResponse> Handle(SetBreakpointsArguments request
8383
}
8484

8585
// At this point, the source file has been verified as a PowerShell script.
86-
IEnumerable<BreakpointDetails> breakpointDetails = request.Breakpoints
86+
IReadOnlyList<BreakpointDetails> breakpointDetails = request.Breakpoints
8787
.Select((srcBreakpoint) => BreakpointDetails.Create(
8888
scriptFile.FilePath,
8989
srcBreakpoint.Line,
9090
srcBreakpoint.Column,
9191
srcBreakpoint.Condition,
9292
srcBreakpoint.HitCondition,
93-
srcBreakpoint.LogMessage));
93+
srcBreakpoint.LogMessage)).ToList();
9494

9595
// If this is a "run without debugging (Ctrl+F5)" session ignore requests to set breakpoints.
96-
IEnumerable<BreakpointDetails> updatedBreakpointDetails = breakpointDetails;
96+
IReadOnlyList<BreakpointDetails> updatedBreakpointDetails = breakpointDetails;
9797
if (!_debugStateService.NoDebug)
9898
{
9999
await _debugStateService.WaitForSetBreakpointHandleAsync().ConfigureAwait(false);
@@ -125,23 +125,20 @@ await _debugService.SetLineBreakpointsAsync(
125125

126126
public async Task<SetFunctionBreakpointsResponse> Handle(SetFunctionBreakpointsArguments request, CancellationToken cancellationToken)
127127
{
128-
CommandBreakpointDetails[] breakpointDetails = request.Breakpoints
128+
IReadOnlyList<CommandBreakpointDetails> breakpointDetails = request.Breakpoints
129129
.Select((funcBreakpoint) => CommandBreakpointDetails.Create(
130130
funcBreakpoint.Name,
131-
funcBreakpoint.Condition))
132-
.ToArray();
131+
funcBreakpoint.Condition)).ToList();
133132

134133
// If this is a "run without debugging (Ctrl+F5)" session ignore requests to set breakpoints.
135-
CommandBreakpointDetails[] updatedBreakpointDetails = breakpointDetails;
134+
IReadOnlyList<CommandBreakpointDetails> updatedBreakpointDetails = breakpointDetails;
136135
if (!_debugStateService.NoDebug)
137136
{
138137
await _debugStateService.WaitForSetBreakpointHandleAsync().ConfigureAwait(false);
139138

140139
try
141140
{
142-
updatedBreakpointDetails =
143-
await _debugService.SetCommandBreakpointsAsync(
144-
breakpointDetails).ConfigureAwait(false);
141+
updatedBreakpointDetails = await _debugService.SetCommandBreakpointsAsync(breakpointDetails).ConfigureAwait(false);
145142
}
146143
catch (Exception e)
147144
{
@@ -156,9 +153,7 @@ await _debugService.SetCommandBreakpointsAsync(
156153

157154
return new SetFunctionBreakpointsResponse
158155
{
159-
Breakpoints = updatedBreakpointDetails
160-
.Select(LspDebugUtils.CreateBreakpoint)
161-
.ToArray()
156+
Breakpoints = updatedBreakpointDetails.Select(LspDebugUtils.CreateBreakpoint).ToList()
162157
};
163158
}
164159

src/PowerShellEditorServices/Services/PowerShell/Debugging/DscBreakpointCapability.cs

+5-4
Original file line numberDiff line numberDiff line change
@@ -21,17 +21,18 @@ internal class DscBreakpointCapability
2121
private string[] dscResourceRootPaths = Array.Empty<string>();
2222
private readonly Dictionary<string, int[]> breakpointsPerFile = new();
2323

24-
public async Task<IEnumerable<BreakpointDetails>> SetLineBreakpointsAsync(
24+
public async Task<IReadOnlyList<BreakpointDetails>> SetLineBreakpointsAsync(
2525
IInternalPowerShellExecutionService executionService,
2626
string scriptPath,
27-
IEnumerable<BreakpointDetails> breakpoints)
27+
IReadOnlyList<BreakpointDetails> breakpoints)
2828
{
2929
// We always get the latest array of breakpoint line numbers
3030
// so store that for future use
31-
if (breakpoints.Any())
31+
int[] lineNumbers = breakpoints.Select(b => b.LineNumber).ToArray();
32+
if (lineNumbers.Length > 0)
3233
{
3334
// Set the breakpoints for this scriptPath
34-
breakpointsPerFile[scriptPath] = breakpoints.Select(b => b.LineNumber).ToArray();
35+
breakpointsPerFile[scriptPath] = lineNumbers;
3536
}
3637
else
3738
{

test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs

+28-28
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,7 @@ await debugService.SetCommandBreakpointsAsync(
203203
[MemberData(nameof(DebuggerAcceptsScriptArgsTestData))]
204204
public async Task DebuggerAcceptsScriptArgs(string[] args)
205205
{
206-
IEnumerable<BreakpointDetails> breakpoints = await debugService.SetLineBreakpointsAsync(
206+
IReadOnlyList<BreakpointDetails> breakpoints = await debugService.SetLineBreakpointsAsync(
207207
oddPathScriptFile,
208208
new[] { BreakpointDetails.Create(oddPathScriptFile.FilePath, 3) }).ConfigureAwait(true);
209209

@@ -257,15 +257,15 @@ public async Task DebuggerAcceptsScriptArgs(string[] args)
257257
[Fact]
258258
public async Task DebuggerSetsAndClearsFunctionBreakpoints()
259259
{
260-
CommandBreakpointDetails[] breakpoints = await debugService.SetCommandBreakpointsAsync(
260+
IReadOnlyList<CommandBreakpointDetails> breakpoints = await debugService.SetCommandBreakpointsAsync(
261261
new[] {
262262
CommandBreakpointDetails.Create("Write-Host"),
263263
CommandBreakpointDetails.Create("Get-Date")
264264
}).ConfigureAwait(true);
265265

266-
Assert.Equal(2, breakpoints.Length);
267-
Assert.Equal("Write-Host", breakpoints.ElementAt(0).Name);
268-
Assert.Equal("Get-Date", breakpoints.ElementAt(1).Name);
266+
Assert.Equal(2, breakpoints.Count);
267+
Assert.Equal("Write-Host", breakpoints[0].Name);
268+
Assert.Equal("Get-Date", breakpoints[1].Name);
269269

270270
breakpoints = await debugService.SetCommandBreakpointsAsync(
271271
new[] { CommandBreakpointDetails.Create("Get-Host") }).ConfigureAwait(true);
@@ -281,7 +281,7 @@ public async Task DebuggerSetsAndClearsFunctionBreakpoints()
281281
[Fact]
282282
public async Task DebuggerStopsOnFunctionBreakpoints()
283283
{
284-
CommandBreakpointDetails[] breakpoints = await debugService.SetCommandBreakpointsAsync(
284+
IReadOnlyList<CommandBreakpointDetails> breakpoints = await debugService.SetCommandBreakpointsAsync(
285285
new[] { CommandBreakpointDetails.Create("Write-Host") }).ConfigureAwait(true);
286286

287287
Task _ = ExecuteDebugFileAsync();
@@ -311,7 +311,7 @@ public async Task DebuggerStopsOnFunctionBreakpoints()
311311
[Fact]
312312
public async Task DebuggerSetsAndClearsLineBreakpoints()
313313
{
314-
IEnumerable<BreakpointDetails> breakpoints =
314+
IReadOnlyList<BreakpointDetails> breakpoints =
315315
await debugService.SetLineBreakpointsAsync(
316316
debugScriptFile,
317317
new[] {
@@ -322,16 +322,16 @@ await debugService.SetLineBreakpointsAsync(
322322
IReadOnlyList<LineBreakpoint> confirmedBreakpoints = await GetConfirmedBreakpoints(debugScriptFile).ConfigureAwait(true);
323323

324324
Assert.Equal(2, confirmedBreakpoints.Count);
325-
Assert.Equal(5, breakpoints.ElementAt(0).LineNumber);
326-
Assert.Equal(10, breakpoints.ElementAt(1).LineNumber);
325+
Assert.Equal(5, breakpoints[0].LineNumber);
326+
Assert.Equal(10, breakpoints[1].LineNumber);
327327

328328
breakpoints = await debugService.SetLineBreakpointsAsync(
329329
debugScriptFile,
330330
new[] { BreakpointDetails.Create(debugScriptFile.FilePath, 2) }).ConfigureAwait(true);
331331
confirmedBreakpoints = await GetConfirmedBreakpoints(debugScriptFile).ConfigureAwait(true);
332332

333333
Assert.Single(confirmedBreakpoints);
334-
Assert.Equal(2, breakpoints.ElementAt(0).LineNumber);
334+
Assert.Equal(2, breakpoints[0].LineNumber);
335335

336336
await debugService.SetLineBreakpointsAsync(
337337
debugScriptFile,
@@ -442,7 +442,7 @@ await debugService.SetLineBreakpointsAsync(
442442
[Fact]
443443
public async Task DebuggerProvidesMessageForInvalidConditionalBreakpoint()
444444
{
445-
IEnumerable<BreakpointDetails> breakpoints =
445+
IReadOnlyList<BreakpointDetails> breakpoints =
446446
await debugService.SetLineBreakpointsAsync(
447447
debugScriptFile,
448448
new[] {
@@ -457,36 +457,36 @@ await debugService.SetLineBreakpointsAsync(
457457
}).ConfigureAwait(true);
458458

459459
Assert.Single(breakpoints);
460-
// Assert.Equal(5, breakpoints.ElementAt(0).LineNumber);
461-
// Assert.True(breakpoints.ElementAt(0).Verified);
462-
// Assert.Null(breakpoints.ElementAt(0).Message);
463-
464-
Assert.Equal(10, breakpoints.ElementAt(0).LineNumber);
465-
Assert.False(breakpoints.ElementAt(0).Verified);
466-
Assert.NotNull(breakpoints.ElementAt(0).Message);
467-
Assert.Contains("Unexpected token '-ez'", breakpoints.ElementAt(0).Message);
460+
// Assert.Equal(5, breakpoints[0].LineNumber);
461+
// Assert.True(breakpoints[0].Verified);
462+
// Assert.Null(breakpoints[0].Message);
463+
464+
Assert.Equal(10, breakpoints[0].LineNumber);
465+
Assert.False(breakpoints[0].Verified);
466+
Assert.NotNull(breakpoints[0].Message);
467+
Assert.Contains("Unexpected token '-ez'", breakpoints[0].Message);
468468
}
469469

470470
[Fact]
471471
public async Task DebuggerFindsParsableButInvalidSimpleBreakpointConditions()
472472
{
473-
IEnumerable<BreakpointDetails> breakpoints =
473+
IReadOnlyList<BreakpointDetails> breakpoints =
474474
await debugService.SetLineBreakpointsAsync(
475475
debugScriptFile,
476476
new[] {
477477
BreakpointDetails.Create(debugScriptFile.FilePath, 5, column: null, condition: "$i == 100"),
478478
BreakpointDetails.Create(debugScriptFile.FilePath, 7, column: null, condition: "$i > 100")
479479
}).ConfigureAwait(true);
480480

481-
Assert.Equal(2, breakpoints.Count());
482-
Assert.Equal(5, breakpoints.ElementAt(0).LineNumber);
483-
Assert.False(breakpoints.ElementAt(0).Verified);
484-
Assert.Contains("Use '-eq' instead of '=='", breakpoints.ElementAt(0).Message);
481+
Assert.Equal(2, breakpoints.Count);
482+
Assert.Equal(5, breakpoints[0].LineNumber);
483+
Assert.False(breakpoints[0].Verified);
484+
Assert.Contains("Use '-eq' instead of '=='", breakpoints[0].Message);
485485

486-
Assert.Equal(7, breakpoints.ElementAt(1).LineNumber);
487-
Assert.False(breakpoints.ElementAt(1).Verified);
488-
Assert.NotNull(breakpoints.ElementAt(1).Message);
489-
Assert.Contains("Use '-gt' instead of '>'", breakpoints.ElementAt(1).Message);
486+
Assert.Equal(7, breakpoints[1].LineNumber);
487+
Assert.False(breakpoints[1].Verified);
488+
Assert.NotNull(breakpoints[1].Message);
489+
Assert.Contains("Use '-gt' instead of '>'", breakpoints[1].Message);
490490
}
491491

492492
[Fact]

0 commit comments

Comments
 (0)