@@ -132,7 +132,17 @@ public static Breakpoint SetBreakpoint(Debugger debugger, BreakpointDetailsBase
132
132
! string . IsNullOrWhiteSpace ( breakpoint . HitCondition ) ||
133
133
! string . IsNullOrWhiteSpace ( logMessage ) )
134
134
{
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
+ }
136
146
}
137
147
138
148
switch ( breakpoint )
@@ -158,165 +168,86 @@ public static bool RemoveBreakpoint(Debugger debugger, Breakpoint breakpoint, in
158
168
return RemoveBreakpointDelegate ( debugger , breakpoint , runspaceId ) ;
159
169
}
160
170
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
-
220
171
/// <summary>
221
172
/// Inspects the condition, putting in the appropriate scriptblock template
222
173
/// "if (expression) { break }". If errors are found in the condition, the
223
174
/// breakpoint passed in is updated to set Verified to false and an error
224
175
/// message is put into the breakpoint.Message property.
225
176
/// </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>
227
180
/// <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 )
230
182
{
183
+ errorMessage = null ;
184
+
231
185
try
232
186
{
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 } '") ;
235
191
236
192
// If HitCondition specified, parse and verify it.
237
- if ( ! ( string . IsNullOrWhiteSpace ( breakpoint . HitCondition ) ) )
193
+ if ( ! ( string . IsNullOrWhiteSpace ( hitCondition ) ) )
238
194
{
239
- if ( int . TryParse ( breakpoint . HitCondition , out int parsedHitCount ) )
195
+ if ( ! int . TryParse ( hitCondition , out int parsedHitCount ) )
240
196
{
241
- hitCount = parsedHitCount ;
197
+ throw new InvalidOperationException ( "Hit Count was not a valid integer." ) ;
242
198
}
243
- else
199
+
200
+ if ( string . IsNullOrWhiteSpace ( condition ) )
244
201
{
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 ( " }" ) ;
249
206
}
250
- }
251
207
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 ( " }" ) ;
259
215
}
260
- else if ( ! string . IsNullOrWhiteSpace ( breakpoint . Condition ) )
216
+
217
+ if ( ! string . IsNullOrWhiteSpace ( condition ) )
261
218
{
262
- // Must be either condition only OR condition and hit count.
263
- actionScriptBlock = ScriptBlock . Create ( breakpoint . Condition ) ;
219
+ ScriptBlock parsed = ScriptBlock . Create ( condition ) ;
264
220
265
221
// Check for simple, common errors that ScriptBlock parsing will not catch
266
222
// e.g. $i == 3 and $i > 3
267
- if ( ! ValidateBreakpointConditionAst ( actionScriptBlock . Ast , out string message ) )
223
+ if ( ! ValidateBreakpointConditionAst ( parsed . Ast , out string message ) )
268
224
{
269
- breakpoint . Verified = false ;
270
- breakpoint . Message = message ;
271
- return null ;
225
+ throw new InvalidOperationException ( message ) ;
272
226
}
273
227
274
228
// Check for "advanced" condition syntax i.e. if the user has specified
275
229
// a "break" or "continue" statement anywhere in their scriptblock,
276
230
// 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 )
285
233
{
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 ;
304
235
}
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 ( " }" ) ;
310
239
}
311
240
312
- return actionScriptBlock ;
241
+ return ScriptBlock . Create ( builder . ToString ( ) ) ;
242
+ }
243
+ catch ( ParseException e )
244
+ {
245
+ errorMessage = ExtractAndScrubParseExceptionMessage ( e , condition ) ;
246
+ return null ;
313
247
}
314
- catch ( ParseException ex )
248
+ catch ( InvalidOperationException e )
315
249
{
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 ;
320
251
return null ;
321
252
}
322
253
}
0 commit comments