Public/Expand-MemberExpression.ps1

using namespace Antlr4.StringTemplate.Compiler
using namespace Microsoft.PowerShell.EditorServices.Extensions
using namespace System.Collections.Generic
using namespace System.Management.Automation.Language
using namespace System.Reflection

function Expand-MemberExpression {
    <#
    .EXTERNALHELP EditorServicesCommandSuite-help.xml
    #>

    [EditorCommand(DisplayName='Expand Member Expression')]
    [CmdletBinding()]
    param(
        [Parameter(Position=1, ValueFromPipeline, ValueFromPipelineByPropertyName)]
        [ValidateNotNullOrEmpty()]
        [System.Management.Automation.Language.Ast]
        $Ast = (Find-Ast -AtCursor),

        [ValidateSet('GetMethod', 'InvokeMember', 'VerboseGetMethod', 'GetValue', 'SetValue')]
        [string]
        $TemplateName,

        [switch]
        $NoParameterNameComments
    )
    begin {
        try {
            $groupSource = Get-Content -Raw $PSScriptRoot\..\Templates\MemberExpression.stg
            $group = New-StringTemplateGroup -Definition $groupSource -ErrorAction Stop

            $instance = $group.GetType().
                GetProperty('Instance', [BindingFlags]'Instance, NonPublic').
                GetValue($group)

            $renderer = [MemberExpressionRenderer]::new()
            $instance.RegisterRenderer([string], $renderer)
            $instance.RegisterRenderer([type], [TypeRenderer]::new())
        } catch {
            ThrowError -Exception ([TemplateException]::new($Strings.TemplateGroupCompileError, $null)) `
                       -Id        TemplateGroupCompileError `
                       -Category  InvalidData `
                       -Target    $PSItem
        }
}
    process {
        $context = $psEditor.GetEditorContext()
        $memberExpressionAst = $Ast

        if ($memberExpressionAst -isnot [MemberExpressionAst]) {

            $memberExpressionAst = $Ast | Find-Ast { $PSItem -is [MemberExpressionAst] } -Ancestor -First

            if ($memberExpressionAst -isnot [MemberExpressionAst]) {
                ThrowError -Exception ([InvalidOperationException]::new($Strings.MissingMemberExpressionAst)) `
                           -Id        MissingMemberExpressionAst `
                           -Category  InvalidOperation `
                           -Target    $Ast `
                           -Show
            }
        }
        [Stack[ExtendedMemberExpressionAst]]$expressionAsts = $memberExpressionAst
        if ($memberExpressionAst.Expression -is [MemberExpressionAst]) {
            for ($nested = $memberExpressionAst.Expression; $nested; $nested = $nested.Expression) {
                if ($nested -is [MemberExpressionAst]) {
                    $expressionAsts.Push($nested)
                } else { break }
            }
        }
        [List[string]]$expressions = @()
        while ($expressionAsts.Count -and ($current = $expressionAsts.Pop())) {

            # Throw if we couldn't find member information at any point.
            if (-not ($current.InferredMember)) {
                ThrowError -Exception ([MissingMemberException]::new($current.Expression, $current.Member.Value)) `
                           -Id        MissingMember `
                           -Category  InvalidResult `
                           -Target    $Ast `
                           -Show
            }

            switch ($current.Expression) {
                { $PSItem -is [MemberExpressionAst] } {
                    $variable = $renderer.TransformMemberName($PSItem.InferredMember.Name)
                }
                { $PSItem -is [VariableExpressionAst] } {
                    $variable = $PSItem.VariablePath.UserPath
                }
                { $PSItem -is [TypeExpressionAst] } {
                    $source = $current.InferredMember.ReflectedType
                }
            }
            if ($variable) {
                $source = '${0}' -f $variable

                # We don't want to build out reflection expressions for public members so we chain
                # them together in one of the expressions.
                while (($current.InferredMember.IsPublic            -or
                        $current.InferredMember.GetMethod.IsPublic) -and
                        $expressionAsts.Count) {
                    $source += '.{0}' -f $current.InferredMember.Name

                    if ($current.InferredMember.MemberType -eq 'Method') {
                        $source += '({0})' -f $current.Arguments.Extent.Text
                    }
                    $current = $expressionAsts.Pop()
                }
            }

            if ($psEditor) {
                $scriptFile     = $context.CurrentFile.GetType().
                                                       GetField('scriptFile', 60).
                                                       GetValue($context.CurrentFile)

                $line           = $scriptFile.GetLine($memberExpressionAst.Extent.StartLineNumber)
                $indentOffset   = [regex]::Match($line, '^\s*').Value
            }

            $templateParameters = @{
                ast                  = $current
                source               = $source
                includeParamComments = -not $NoParameterNameComments
            }
            $member = $current.InferredMember

            # Automatically use the more explicit VerboseGetMethod template if building a reflection
            # statement for a method with multiple overloads with the same parameter count.
            $needsVerbose = $member -is [MethodInfo] -and -not
                            $member.IsPublic -and
                            $member.ReflectedType.GetMethods(60).Where{
                                $PSItem.Name -eq $current.InferredMember.Name -and
                                $PSItem.GetParameters().Count -eq $member.GetParameters().Count }.
                                Count -gt 1

            if ($TemplateName -and -not $expressionAsts.Count) {
                $templateParameters.template = $TemplateName
            } elseif ($needsVerbose) {
                $templateParameters.template = 'VerboseGetMethod'
            }
            $expression = Invoke-StringTemplate -Group $group -Name Main -Parameters $templateParameters
            $expressions.Add($expression)
        }

        $result = $expressions -join (,[Environment]::NewLine * 2) `
                               -split '\r?\n' `
                               -join ([Environment]::NewLine + $indentOffset)
        if ($psEditor) {
            Set-ScriptExtent -Extent $memberExpressionAst.Extent `
                             -Text   $result
        } else {
            $result
        }
    }
}