private/implicitwindow/Test-UiDeferredBlock.ps1
|
function Test-UiDeferredBlock { <# .SYNOPSIS Determines whether a scriptblock runs where it is or if something store it for later. -Content { } and .ForEach({ }) run now, -Action { } waitsfor a click, and $build = { } never runs at all. #> [CmdletBinding()] [OutputType([bool])] param( [Parameter(Mandatory)] [System.Management.Automation.Language.ScriptBlockExpressionAst]$Block, [Parameter(Mandatory)] $Deferrers, $BlockStorers ) # These take a definition block that runs while the window builds. # RowContextMenu and ResultActions read like click handlers but aren't, since the block builds the items rather than answering a click. $immediate = 'Content', 'Columns', 'RowContextMenu', 'CustomButtons', 'ResultActions' # Parentheses around the block change nothing about what runs. $node = $Block while ($node.Parent -is [System.Management.Automation.Language.CommandExpressionAst] -or $node.Parent -is [System.Management.Automation.Language.PipelineAst] -or $node.Parent -is [System.Management.Automation.Language.ParenExpressionAst]) { $node = $node.Parent } $parent = $node.Parent # .ForEach and .Where run the block once per item before the statement is finished, so a control in one is already being shown $invoke = $parent -as [System.Management.Automation.Language.InvokeMemberExpressionAst] if ($invoke) { $member = $invoke.Member -as [System.Management.Automation.Language.StringConstantExpressionAst] if (!$member) { return $true } if ($invoke.Expression -ne $node) { return $member.Value -notin 'ForEach', 'Where' } $runs = 'Invoke', 'InvokeReturnAsIs', 'InvokeWithContext' while ($true) { if ($member.Value -in $runs) { return $false } if ($member.Value -ne 'GetNewClosure') { return $true } $next = $invoke.Parent -as [System.Management.Automation.Language.InvokeMemberExpressionAst] if (!$next -or $next.Expression -ne $invoke) { return $true } $invoke = $next $member = $invoke.Member -as [System.Management.Automation.Language.StringConstantExpressionAst] if (!$member) { return $true } } } # -Content:{ } hangs the block off the parameter instead of the command. $colon = $parent -as [System.Management.Automation.Language.CommandParameterAst] if ($colon) { $runner = $colon.Parent -as [System.Management.Automation.Language.CommandAst] if (!$runner) { return $true } $colonName = Get-UiPlainCommandName -Command $runner if (!$colonName) { return $false } if ($BlockStorers -and $BlockStorers.Contains($colonName)) { return $true } if (!$Deferrers.Contains($colonName)) { return $false } return $colon.ParameterName -notin $immediate } # Stored in a variable, a hashtable or an array, so it runs later. $runner = $parent -as [System.Management.Automation.Language.CommandAst] if (!$runner) { return $true } # No name means it runs the block on the spot. $name = Get-UiPlainCommandName -Command $runner if (!$name) { return $false } # A helper the script wrote itself is judged by what its own body does with the block. if ($BlockStorers -and $BlockStorers.Contains($name)) { return $true } if (!$Deferrers.Contains($name)) { return $false } return (Get-UiBoundBlockParameter -Command $runner -Block $Block) -notin $immediate } |