@@ -20,12 +20,9 @@ namespace Microsoft.PowerShell.EditorServices.Services
20
20
{
21
21
internal class BreakpointService
22
22
{
23
- private const string s_psesGlobalVariableNamePrefix = "__psEditorServices_" ;
24
23
private readonly ILogger < BreakpointService > _logger ;
25
24
private readonly PowerShellContextService _powerShellContextService ;
26
25
27
- private static int breakpointHitCounter ;
28
-
29
26
public BreakpointService (
30
27
ILoggerFactory factory ,
31
28
PowerShellContextService powerShellContextService )
@@ -79,7 +76,7 @@ public async Task<BreakpointDetails[]> SetBreakpointsAsync(string escapedScriptP
79
76
! string . IsNullOrWhiteSpace ( breakpoint . HitCondition ) )
80
77
{
81
78
ScriptBlock actionScriptBlock =
82
- GetBreakpointActionScriptBlock ( breakpoint ) ;
79
+ BreakpointApiUtils . GetBreakpointActionScriptBlock ( breakpoint ) ;
83
80
84
81
// If there was a problem with the condition string,
85
82
// move onto the next breakpoint.
@@ -140,7 +137,7 @@ public async Task<IEnumerable<CommandBreakpointDetails>> SetCommandBreakpoints(I
140
137
! string . IsNullOrWhiteSpace ( breakpoint . HitCondition ) )
141
138
{
142
139
ScriptBlock actionScriptBlock =
143
- GetBreakpointActionScriptBlock ( breakpoint ) ;
140
+ BreakpointApiUtils . GetBreakpointActionScriptBlock ( breakpoint ) ;
144
141
145
142
// If there was a problem with the condition string,
146
143
// move onto the next breakpoint.
@@ -200,7 +197,7 @@ public async Task RemoveAllBreakpointsAsync()
200
197
}
201
198
}
202
199
203
- public async Task RemoveBreakpoints ( IEnumerable < Breakpoint > breakpoints )
200
+ public async Task RemoveBreakpointsAsync ( IEnumerable < Breakpoint > breakpoints )
204
201
{
205
202
if ( VersionUtils . IsPS7OrGreater )
206
203
{
@@ -226,187 +223,6 @@ public async Task RemoveBreakpoints(IEnumerable<Breakpoint> breakpoints)
226
223
}
227
224
}
228
225
229
- /// <summary>
230
- /// Inspects the condition, putting in the appropriate scriptblock template
231
- /// "if (expression) { break }". If errors are found in the condition, the
232
- /// breakpoint passed in is updated to set Verified to false and an error
233
- /// message is put into the breakpoint.Message property.
234
- /// </summary>
235
- /// <param name="breakpoint"></param>
236
- /// <returns></returns>
237
- private ScriptBlock GetBreakpointActionScriptBlock (
238
- BreakpointDetailsBase breakpoint )
239
- {
240
- try
241
- {
242
- ScriptBlock actionScriptBlock ;
243
- int ? hitCount = null ;
244
-
245
- // If HitCondition specified, parse and verify it.
246
- if ( ! ( string . IsNullOrWhiteSpace ( breakpoint . HitCondition ) ) )
247
- {
248
- if ( int . TryParse ( breakpoint . HitCondition , out int parsedHitCount ) )
249
- {
250
- hitCount = parsedHitCount ;
251
- }
252
- else
253
- {
254
- breakpoint . Verified = false ;
255
- breakpoint . Message = $ "The specified HitCount '{ breakpoint . HitCondition } ' is not valid. " +
256
- "The HitCount must be an integer number." ;
257
- return null ;
258
- }
259
- }
260
-
261
- // Create an Action scriptblock based on condition and/or hit count passed in.
262
- if ( hitCount . HasValue && string . IsNullOrWhiteSpace ( breakpoint . Condition ) )
263
- {
264
- // In the HitCount only case, this is simple as we can just use the HitCount
265
- // property on the breakpoint object which is represented by $_.
266
- string action = $ "if ($_.HitCount -eq { hitCount } ) {{ break }}";
267
- actionScriptBlock = ScriptBlock . Create ( action ) ;
268
- }
269
- else if ( ! string . IsNullOrWhiteSpace ( breakpoint . Condition ) )
270
- {
271
- // Must be either condition only OR condition and hit count.
272
- actionScriptBlock = ScriptBlock . Create ( breakpoint . Condition ) ;
273
-
274
- // Check for simple, common errors that ScriptBlock parsing will not catch
275
- // e.g. $i == 3 and $i > 3
276
- if ( ! ValidateBreakpointConditionAst ( actionScriptBlock . Ast , out string message ) )
277
- {
278
- breakpoint . Verified = false ;
279
- breakpoint . Message = message ;
280
- return null ;
281
- }
282
-
283
- // Check for "advanced" condition syntax i.e. if the user has specified
284
- // a "break" or "continue" statement anywhere in their scriptblock,
285
- // pass their scriptblock through to the Action parameter as-is.
286
- Ast breakOrContinueStatementAst =
287
- actionScriptBlock . Ast . Find (
288
- ast => ( ast is BreakStatementAst || ast is ContinueStatementAst ) , true ) ;
289
-
290
- // If this isn't advanced syntax then the conditions string should be a simple
291
- // expression that needs to be wrapped in a "if" test that conditionally executes
292
- // a break statement.
293
- if ( breakOrContinueStatementAst == null )
294
- {
295
- string wrappedCondition ;
296
-
297
- if ( hitCount . HasValue )
298
- {
299
- Interlocked . Increment ( ref breakpointHitCounter ) ;
300
-
301
- string globalHitCountVarName =
302
- $ "$global:{ s_psesGlobalVariableNamePrefix } BreakHitCounter_{ breakpointHitCounter } ";
303
-
304
- wrappedCondition =
305
- $ "if ({ breakpoint . Condition } ) {{ if (++{ globalHitCountVarName } -eq { hitCount } ) {{ break }} }}";
306
- }
307
- else
308
- {
309
- wrappedCondition = $ "if ({ breakpoint . Condition } ) {{ break }}";
310
- }
311
-
312
- actionScriptBlock = ScriptBlock . Create ( wrappedCondition ) ;
313
- }
314
- }
315
- else
316
- {
317
- // Shouldn't get here unless someone called this with no condition and no hit count.
318
- actionScriptBlock = ScriptBlock . Create ( "break" ) ;
319
- _logger . LogWarning ( "No condition and no hit count specified by caller." ) ;
320
- }
321
-
322
- return actionScriptBlock ;
323
- }
324
- catch ( ParseException ex )
325
- {
326
- // Failed to create conditional breakpoint likely because the user provided an
327
- // invalid PowerShell expression. Let the user know why.
328
- breakpoint . Verified = false ;
329
- breakpoint . Message = ExtractAndScrubParseExceptionMessage ( ex , breakpoint . Condition ) ;
330
- return null ;
331
- }
332
- }
333
226
334
- private static bool ValidateBreakpointConditionAst ( Ast conditionAst , out string message )
335
- {
336
- message = string . Empty ;
337
-
338
- // We are only inspecting a few simple scenarios in the EndBlock only.
339
- if ( conditionAst is ScriptBlockAst scriptBlockAst &&
340
- scriptBlockAst . BeginBlock == null &&
341
- scriptBlockAst . ProcessBlock == null &&
342
- scriptBlockAst . EndBlock != null &&
343
- scriptBlockAst . EndBlock . Statements . Count == 1 )
344
- {
345
- StatementAst statementAst = scriptBlockAst . EndBlock . Statements [ 0 ] ;
346
- string condition = statementAst . Extent . Text ;
347
-
348
- if ( statementAst is AssignmentStatementAst )
349
- {
350
- message = FormatInvalidBreakpointConditionMessage ( condition , "Use '-eq' instead of '=='." ) ;
351
- return false ;
352
- }
353
-
354
- if ( statementAst is PipelineAst pipelineAst
355
- && pipelineAst . PipelineElements . Count == 1
356
- && pipelineAst . PipelineElements [ 0 ] . Redirections . Count > 0 )
357
- {
358
- message = FormatInvalidBreakpointConditionMessage ( condition , "Use '-gt' instead of '>'." ) ;
359
- return false ;
360
- }
361
- }
362
-
363
- return true ;
364
- }
365
-
366
- private static string ExtractAndScrubParseExceptionMessage ( ParseException parseException , string condition )
367
- {
368
- string [ ] messageLines = parseException . Message . Split ( '\n ' ) ;
369
-
370
- // Skip first line - it is a location indicator "At line:1 char: 4"
371
- for ( int i = 1 ; i < messageLines . Length ; i ++ )
372
- {
373
- string line = messageLines [ i ] ;
374
- if ( line . StartsWith ( "+" ) )
375
- {
376
- continue ;
377
- }
378
-
379
- if ( ! string . IsNullOrWhiteSpace ( line ) )
380
- {
381
- // Note '==' and '>" do not generate parse errors
382
- if ( line . Contains ( "'!='" ) )
383
- {
384
- line += " Use operator '-ne' instead of '!='." ;
385
- }
386
- else if ( line . Contains ( "'<'" ) && condition . Contains ( "<=" ) )
387
- {
388
- line += " Use operator '-le' instead of '<='." ;
389
- }
390
- else if ( line . Contains ( "'<'" ) )
391
- {
392
- line += " Use operator '-lt' instead of '<'." ;
393
- }
394
- else if ( condition . Contains ( ">=" ) )
395
- {
396
- line += " Use operator '-ge' instead of '>='." ;
397
- }
398
-
399
- return FormatInvalidBreakpointConditionMessage ( condition , line ) ;
400
- }
401
- }
402
-
403
- // If the message format isn't in a form we expect, just return the whole message.
404
- return FormatInvalidBreakpointConditionMessage ( condition , parseException . Message ) ;
405
- }
406
-
407
- private static string FormatInvalidBreakpointConditionMessage ( string condition , string message )
408
- {
409
- return $ "'{ condition } ' is not a valid PowerShell expression. { message } ";
410
- }
411
227
}
412
228
}
0 commit comments