Public/ConvertTo-SplatExpression.ps1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 |
using namespace Microsoft.PowerShell.EditorServices.Extensions using namespace System.Collections.Generic using namespace System.Management.Automation.Language function ConvertTo-SplatExpression { <# .EXTERNALHELP EditorServicesCommandSuite-help.xml #> [CmdletBinding()] [EditorCommand(DisplayName='Convert Command to Splat Expression')] param( [System.Management.Automation.Language.Ast] $Ast ) begin { function ConvertFromExpressionAst($expression) { $isStringExpression = $expression -is [StringConstantExpressionAst] -or $expression -is [ExpandableStringExpressionAst] if ($isStringExpression) { # If kind isn't BareWord then it's already enclosed in quotes. if ('BareWord' -ne $expression.StringConstantType) { return $expression.Extent.Text } $enclosure = "'" if ($expression.NestedExpressions) { $enclosure = '"' } return '{0}{1}{0}' -f $enclosure, $expression.Value } # When we handle switch parameters we don't create an AST. if ($pair.Value -isnot [Ast]) { return $expression } return $expression.Extent.Text } } end { $Ast = GetAncestorOrThrow $Ast -AstTypeName CommandAst -ErrorContext $PSCmdlet $commandName, $elements = $Ast.CommandElements.Where({ $true }, 'Split', 1) $splat = @{} $retainedArgs = [List[Ast]]::new() $elementsExtent = $elements.Extent | Join-ScriptExtent $boundParameters = [StaticParameterBinder]::BindCommand($Ast).BoundParameters # Start building the hash table of named parameters and values foreach ($parameter in $boundParameters.GetEnumerator()) { # If the command isn't loaded positional parameters come through as their numeric position. if ($parameter.Key -match '\d+' -and -not $parameter.Value.Parameter) { $retainedArgs.Add($parameter.Value.Value) continue } # The "Value" property for switches is the parameter AST (e.g. -Force) so we need to # manually build the expression. if ($parameter.Value.ConstantValue -is [bool]) { $splat.($parameter.Key) = '${0}' -f $parameter.Value.ConstantValue.ToString().ToLower() continue } $splat.($parameter.Key) = $parameter.Value.Value } # Remove the hypen, change to camelCase and add 'Splat' $variableName = [regex]::Replace( ($commandName.Extent.Text -replace '-'), '^[A-Z]', { $args[0].Value.ToLower() }) + 'Splat' $sb = [System.Text.StringBuilder]:: new('${0}' -f $variableName). AppendLine(' = @{') # All StringBuilder methods return itself so it can be chained. We null the whole scriptblock # here so unchained method calls don't add to our output. $null = & { foreach($pair in $splat.GetEnumerator()) { $sb.Append(' '). Append($pair.Key). Append(' = ') if ($pair.Value -is [ArrayLiteralAst]) { $sb.AppendLine($pair.Value.Elements.ForEach{ ConvertFromExpressionAst $PSItem } -join ', ') } else { $sb.AppendLine((ConvertFromExpressionAst $pair.Value)) } } $sb.Append('}') } $splatText = $sb.ToString() # New CommandAst will be `Command @splatvar [PositionalArguments]` $newCommandParameters = '@' + $variableName if ($retainedArgs) { $newCommandParameters += ' ' + ($retainedArgs.Extent.Text -join ' ') } # Change the command expression first so we don't need to track it's position. $elementsExtent | Set-ScriptExtent -Text $newCommandParameters # Get the parent PipelineAst so we don't add the splat in the middle of a pipeline. $pipeline = $Ast | Find-Ast -Ancestor -First { $PSItem -is [PipelineAst] } # Prepend the existing indent. $lineText = ($psEditor.GetEditorContext(). CurrentFile. Ast. Extent. Text -split '\r?\n')[$pipeline.Extent.StartLineNumber - 1] $lineIndent = $lineText -match '^\s*' | ForEach-Object { $matches[0] } $splatText = $lineIndent + ( $splatText -split '\r?\n' -join ([Environment]::NewLine + $lineIndent)) # HACK: Temporary workaround until https://github.com/PowerShell/PowerShellEditorServices/pull/541 #$splatTarget = ConvertTo-ScriptExtent -Line $pipeline.Extent.StartLineNumber $splatTarget = [Microsoft.PowerShell.EditorServices.FullScriptExtent]::new( $psEditor.GetEditorContext().CurrentFile, [Microsoft.PowerShell.EditorServices.BufferRange]::new( $pipeline.Extent.StartLineNumber, 1, $pipeline.Extent.StartLineNumber, 1)) $splatTarget | Set-ScriptExtent -Text ($splatText + [Environment]::NewLine) } } |