Skip to content

Commit 1b4a357

Browse files
get rid of extra GetBreakpointActionScriptBlock
1 parent 8ce4d5c commit 1b4a357

File tree

2 files changed

+74
-136
lines changed

2 files changed

+74
-136
lines changed

src/PowerShellEditorServices/Services/DebugAdapter/BreakpointService.cs

+18-11
Original file line numberDiff line numberDiff line change
@@ -86,17 +86,18 @@ public async Task<IEnumerable<BreakpointDetails>> SetBreakpointsAsync(string esc
8686
!string.IsNullOrWhiteSpace(breakpoint.HitCondition) ||
8787
!string.IsNullOrWhiteSpace(breakpoint.LogMessage))
8888
{
89-
try
90-
{
91-
actionScriptBlock = BreakpointApiUtils.GetBreakpointActionScriptBlock(
92-
breakpoint.Condition,
93-
breakpoint.HitCondition,
94-
breakpoint.LogMessage);
95-
}
96-
catch (InvalidOperationException e)
89+
actionScriptBlock = BreakpointApiUtils.GetBreakpointActionScriptBlock(
90+
breakpoint.Condition,
91+
breakpoint.HitCondition,
92+
breakpoint.LogMessage,
93+
out string errorMessage);
94+
95+
if (!string.IsNullOrEmpty(errorMessage))
9796
{
9897
breakpoint.Verified = false;
99-
breakpoint.Message = e.Message;
98+
breakpoint.Message = errorMessage;
99+
configuredBreakpoints.Add(breakpoint);
100+
continue;
100101
}
101102
}
102103

@@ -188,12 +189,18 @@ public async Task<IEnumerable<CommandBreakpointDetails>> SetCommandBreakpoints(I
188189
!string.IsNullOrWhiteSpace(breakpoint.HitCondition))
189190
{
190191
ScriptBlock actionScriptBlock =
191-
BreakpointApiUtils.GetBreakpointActionScriptBlock(breakpoint);
192+
BreakpointApiUtils.GetBreakpointActionScriptBlock(
193+
breakpoint.Condition,
194+
breakpoint.HitCondition,
195+
logMessage: null,
196+
out string errorMessage);
192197

193198
// If there was a problem with the condition string,
194199
// move onto the next breakpoint.
195-
if (actionScriptBlock == null)
200+
if (!string.IsNullOrEmpty(errorMessage))
196201
{
202+
breakpoint.Verified = false;
203+
breakpoint.Message = errorMessage;
197204
configuredBreakpoints.Add(breakpoint);
198205
continue;
199206
}

src/PowerShellEditorServices/Services/DebugAdapter/Debugging/BreakpointApiUtils.cs

+56-125
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,17 @@ public static Breakpoint SetBreakpoint(Debugger debugger, BreakpointDetailsBase
132132
!string.IsNullOrWhiteSpace(breakpoint.HitCondition) ||
133133
!string.IsNullOrWhiteSpace(logMessage))
134134
{
135-
actionScriptBlock = GetBreakpointActionScriptBlock(breakpoint.Condition, breakpoint.HitCondition, logMessage);
135+
actionScriptBlock = GetBreakpointActionScriptBlock(
136+
breakpoint.Condition,
137+
breakpoint.HitCondition,
138+
logMessage,
139+
out string errorMessage);
140+
141+
if (!string.IsNullOrEmpty(errorMessage))
142+
{
143+
// This is handled by the caller where it will set the 'Message' and 'Verified' on the BreakpointDetails
144+
throw new InvalidOperationException(errorMessage);
145+
}
136146
}
137147

138148
switch (breakpoint)
@@ -158,165 +168,86 @@ public static bool RemoveBreakpoint(Debugger debugger, Breakpoint breakpoint, in
158168
return RemoveBreakpointDelegate(debugger, breakpoint, runspaceId);
159169
}
160170

161-
public static ScriptBlock GetBreakpointActionScriptBlock(string condition, string hitCondition, string logMessage)
162-
{
163-
StringBuilder builder = new StringBuilder(
164-
string.IsNullOrEmpty(logMessage)
165-
? "break"
166-
: $"Microsoft.PowerShell.Utility\\Write-Host '{logMessage}'");
167-
168-
// If HitCondition specified, parse and verify it.
169-
if (!(string.IsNullOrWhiteSpace(hitCondition)))
170-
{
171-
if (!int.TryParse(hitCondition, out int parsedHitCount))
172-
{
173-
throw new InvalidOperationException("Hit Count was not a valid integer.");
174-
}
175-
176-
if(string.IsNullOrWhiteSpace(condition))
177-
{
178-
// In the HitCount only case, this is simple as we can just use the HitCount
179-
// property on the breakpoint object which is represented by $_.
180-
builder.Insert(0, $"if ($_.HitCount -eq {parsedHitCount}) {{ ")
181-
.Append(" }");
182-
}
183-
184-
Interlocked.Increment(ref breakpointHitCounter);
185-
186-
string globalHitCountVarName =
187-
$"$global:{s_psesGlobalVariableNamePrefix}BreakHitCounter_{breakpointHitCounter}";
188-
189-
builder.Insert(0, $"if (++{globalHitCountVarName} -eq {parsedHitCount}) {{ ")
190-
.Append(" }");
191-
}
192-
193-
if (!string.IsNullOrWhiteSpace(condition))
194-
{
195-
ScriptBlock parsed = ScriptBlock.Create(condition);
196-
197-
// Check for simple, common errors that ScriptBlock parsing will not catch
198-
// e.g. $i == 3 and $i > 3
199-
if (!ValidateBreakpointConditionAst(parsed.Ast, out string message))
200-
{
201-
throw new InvalidOperationException(message);
202-
}
203-
204-
// Check for "advanced" condition syntax i.e. if the user has specified
205-
// a "break" or "continue" statement anywhere in their scriptblock,
206-
// pass their scriptblock through to the Action parameter as-is.
207-
if (parsed.Ast.Find(ast =>
208-
(ast is BreakStatementAst || ast is ContinueStatementAst), true) != null)
209-
{
210-
return parsed;
211-
}
212-
213-
builder.Insert(0, $"if ({condition}) {{ ")
214-
.Append(" }}");
215-
}
216-
217-
return ScriptBlock.Create(builder.ToString());
218-
}
219-
220171
/// <summary>
221172
/// Inspects the condition, putting in the appropriate scriptblock template
222173
/// "if (expression) { break }". If errors are found in the condition, the
223174
/// breakpoint passed in is updated to set Verified to false and an error
224175
/// message is put into the breakpoint.Message property.
225176
/// </summary>
226-
/// <param name="breakpoint"></param>
177+
/// <param name="condition">The expression that needs to be true for the breakpoint to be triggered.</param>
178+
/// <param name="hitCondition">The amount of times this line should be hit til the breakpoint is triggered.</param>
179+
/// <param name="logMessage">The log message to write instead of calling 'break'. In VS Code, this is called a 'logPoint'.</param>
227180
/// <returns>ScriptBlock</returns>
228-
public static ScriptBlock GetBreakpointActionScriptBlock(
229-
BreakpointDetailsBase breakpoint)
181+
public static ScriptBlock GetBreakpointActionScriptBlock(string condition, string hitCondition, string logMessage, out string errorMessage)
230182
{
183+
errorMessage = null;
184+
231185
try
232186
{
233-
ScriptBlock actionScriptBlock;
234-
int? hitCount = null;
187+
StringBuilder builder = new StringBuilder(
188+
string.IsNullOrEmpty(logMessage)
189+
? "break"
190+
: $"Microsoft.PowerShell.Utility\\Write-Host '{logMessage}'");
235191

236192
// If HitCondition specified, parse and verify it.
237-
if (!(string.IsNullOrWhiteSpace(breakpoint.HitCondition)))
193+
if (!(string.IsNullOrWhiteSpace(hitCondition)))
238194
{
239-
if (int.TryParse(breakpoint.HitCondition, out int parsedHitCount))
195+
if (!int.TryParse(hitCondition, out int parsedHitCount))
240196
{
241-
hitCount = parsedHitCount;
197+
throw new InvalidOperationException("Hit Count was not a valid integer.");
242198
}
243-
else
199+
200+
if(string.IsNullOrWhiteSpace(condition))
244201
{
245-
breakpoint.Verified = false;
246-
breakpoint.Message = $"The specified HitCount '{breakpoint.HitCondition}' is not valid. " +
247-
"The HitCount must be an integer number.";
248-
return null;
202+
// In the HitCount only case, this is simple as we can just use the HitCount
203+
// property on the breakpoint object which is represented by $_.
204+
builder.Insert(0, $"if ($_.HitCount -eq {parsedHitCount}) {{ ")
205+
.Append(" }");
249206
}
250-
}
251207

252-
// Create an Action scriptblock based on condition and/or hit count passed in.
253-
if (hitCount.HasValue && string.IsNullOrWhiteSpace(breakpoint.Condition))
254-
{
255-
// In the HitCount only case, this is simple as we can just use the HitCount
256-
// property on the breakpoint object which is represented by $_.
257-
string action = $"if ($_.HitCount -eq {hitCount}) {{ break }}";
258-
actionScriptBlock = ScriptBlock.Create(action);
208+
int incrementResult = Interlocked.Increment(ref breakpointHitCounter);
209+
210+
string globalHitCountVarName =
211+
$"$global:{s_psesGlobalVariableNamePrefix}BreakHitCounter_{incrementResult}";
212+
213+
builder.Insert(0, $"if (++{globalHitCountVarName} -eq {parsedHitCount}) {{ ")
214+
.Append(" }");
259215
}
260-
else if (!string.IsNullOrWhiteSpace(breakpoint.Condition))
216+
217+
if (!string.IsNullOrWhiteSpace(condition))
261218
{
262-
// Must be either condition only OR condition and hit count.
263-
actionScriptBlock = ScriptBlock.Create(breakpoint.Condition);
219+
ScriptBlock parsed = ScriptBlock.Create(condition);
264220

265221
// Check for simple, common errors that ScriptBlock parsing will not catch
266222
// e.g. $i == 3 and $i > 3
267-
if (!ValidateBreakpointConditionAst(actionScriptBlock.Ast, out string message))
223+
if (!ValidateBreakpointConditionAst(parsed.Ast, out string message))
268224
{
269-
breakpoint.Verified = false;
270-
breakpoint.Message = message;
271-
return null;
225+
throw new InvalidOperationException(message);
272226
}
273227

274228
// Check for "advanced" condition syntax i.e. if the user has specified
275229
// a "break" or "continue" statement anywhere in their scriptblock,
276230
// pass their scriptblock through to the Action parameter as-is.
277-
Ast breakOrContinueStatementAst =
278-
actionScriptBlock.Ast.Find(
279-
ast => (ast is BreakStatementAst || ast is ContinueStatementAst), true);
280-
281-
// If this isn't advanced syntax then the conditions string should be a simple
282-
// expression that needs to be wrapped in a "if" test that conditionally executes
283-
// a break statement.
284-
if (breakOrContinueStatementAst == null)
231+
if (parsed.Ast.Find(ast =>
232+
(ast is BreakStatementAst || ast is ContinueStatementAst), true) != null)
285233
{
286-
string wrappedCondition;
287-
288-
if (hitCount.HasValue)
289-
{
290-
Interlocked.Increment(ref breakpointHitCounter);
291-
292-
string globalHitCountVarName =
293-
$"$global:{s_psesGlobalVariableNamePrefix}BreakHitCounter_{breakpointHitCounter}";
294-
295-
wrappedCondition =
296-
$"if ({breakpoint.Condition}) {{ if (++{globalHitCountVarName} -eq {hitCount}) {{ break }} }}";
297-
}
298-
else
299-
{
300-
wrappedCondition = $"if ({breakpoint.Condition}) {{ break }}";
301-
}
302-
303-
actionScriptBlock = ScriptBlock.Create(wrappedCondition);
234+
return parsed;
304235
}
305-
}
306-
else
307-
{
308-
// Shouldn't get here unless someone called this with no condition and no hit count.
309-
actionScriptBlock = ScriptBlock.Create("break");
236+
237+
builder.Insert(0, $"if ({condition}) {{ ")
238+
.Append(" }");
310239
}
311240

312-
return actionScriptBlock;
241+
return ScriptBlock.Create(builder.ToString());
242+
}
243+
catch (ParseException e)
244+
{
245+
errorMessage = ExtractAndScrubParseExceptionMessage(e, condition);
246+
return null;
313247
}
314-
catch (ParseException ex)
248+
catch (InvalidOperationException e)
315249
{
316-
// Failed to create conditional breakpoint likely because the user provided an
317-
// invalid PowerShell expression. Let the user know why.
318-
breakpoint.Verified = false;
319-
breakpoint.Message = ExtractAndScrubParseExceptionMessage(ex, breakpoint.Condition);
250+
errorMessage = e.Message;
320251
return null;
321252
}
322253
}

0 commit comments

Comments
 (0)