_InitModule.ps1

# Copyright (c) Matthias Wolf, Mawosoft.

using namespace System
using namespace System.Collections.Generic
using namespace System.Management.Automation
using namespace System.Reflection

<#
.SYNOPSIS
    Initializes module-wide variables.
.OUTPUTS
    None.
#>

function _InitModule {
    param()

    $script:PathComparer = [StringComparer]::OrdinalIgnoreCase
    if ($global:PSVersionTable.PSVersion.Major -ge 6 -and $global:IsLinux) {
        $script:PathComparer = [StringComparer]::Ordinal
    }

    $script:BuiltInPSVariables = [HashSet[string]]::new(
        [string[]]@(
            '_', 'PSItem', 'this', 'input', 'args', 'true', 'false', 'null',
            'PSDefaultParameterValues', 'Error', 'PSScriptRoot', 'PSCommandPath',
            'MyInvocation', 'ExecutionContext', 'StackTrace',
            'DebugPreference', 'VerbosePreference', 'ErrorActionPreference', 'WhatIfPreference',
            'WarningPreference', 'InformationPreference', 'ConfirmPreference', 'ProgressPreference',
            'foreach', 'switch', 'Matches'
        ),
        [StringComparer]::OrdinalIgnoreCase)

    $script:Reflector = $reflector = [Mawosoft.PSReflector.Reflector]::new()

    # Initialized later
    # $script:SMA
    # $script:PSES

    # Member name changes between pwsh 5.1 and core.
    $names = [PSCustomObject]@{
        s_cache = 's_cache' # SMA.Language.TypeCache
        # Unused
        # s_cachedScripts = 's_cachedScripts' # SMA.ScriptBlock
        # s_typeCache = 's_typeCache' # SMA.CompletionCompleters
        # _typesDefined = '_typesDefined' # SMA.Language.TypeResolutionState
    }
    if ($global:PSVersionTable.PSVersion.Major -lt 6) {
        $names.s_cache = '_cache'
        # $names.s_cachedScripts = '_cachedScripts'
        # $names.s_typeCache = 'typeCache'
        # $names._typesDefined = 'typesDefined'
    }

    $smaAssembly = [psobject].Assembly
    $smaPrefix = 'System.Management.Automation.'
    $tiTypeAccelerators = $smaAssembly.GetType($smaPrefix + 'TypeAccelerators')
    $tiTypeCache = $smaAssembly.GetType($smaPrefix + 'Language.TypeCache')
    $tiTypeResolutionState = $smaAssembly.GetType($smaPrefix + 'Language.TypeResolutionState')

    $reflector.Register([CompletionCompleters],
        'UpdateTypeCacheOnAssemblyLoad')
    $reflector.Register([EngineIntrinsics],
        '_context')
    $reflector.Register([FunctionInfo], # Actually on CommandInfo, but we only use FunctionInfo.
        'set_Module')
    $reflector.Register([psvariable],
        'SetModule')
    $reflector.Register([scriptblock], 
        'ClearScriptBlockCache')
    $reflector.Register([SessionState],
        'Internal')
    $reflector.Register($tiTypeAccelerators,
        'userTypeAccelerators')
    $reflector.Register($tiTypeCache,
        @{ Name = $names.s_cache; PSName = 's_cache' })
    $reflector.Register($tiTypeResolutionState, @(
            'assemblies',
            'namespaces',
            'CloneWithAddTypesDefined',
            # Use verbose, but 5.1 compatible GetConstructor overload.
            @{ MemberInfo = $tiTypeResolutionState.GetConstructor([BindingFlags]'NonPublic, Instance', $null, [type[]]@([string[]], [Assembly[]]), $null) }))
    $reflector.Register($smaAssembly.GetType($smaPrefix + 'ExecutionContext'), @(
            'TopLevelSessionState',
            @{ Name = 'PropagateExceptionsToEnclosingStatementBlock'; CanWrite = $true }))
    $reflector.Register($smaAssembly.GetType($smaPrefix + 'SessionStateScope'), @(
            '_typeResolutionState',
            'AddType',
            'FunctionTable',
            'NewVariable',
            'TypeTable',
            'Variables',
            @{ Name = 'SetFunction'; ParamCount = 7 },
            @{ Name = 'TypeResolutionState'; CanWrite = $true }))
    $reflector.Register($smaAssembly.GetType($smaPrefix + 'SessionStateInternal'), @(
            'ExecutionContext',
            'ExportedVariables',
            'GlobalScope',
            'ModuleScope'))

    $script:SMA = [PSCustomObject]@{
        TypeAccelerators     = $reflector.Expose($tiTypeAccelerators)
        TypeCache            = $reflector.Expose($tiTypeCache)
        TypeResolutionState  = $reflector.Expose($tiTypeResolutionState)
        ScriptBlock          = $reflector.Expose([scriptblock])
        CompletionCompleters = $reflector.Expose([CompletionCompleters])
    }

    $pses = [PSCustomObject]@{
        Error                = $null
        ServiceProvider      = $null
        AnalysisServiceType  = $null
        WorkspaceServiceType = $null
    }
    $psesPrefix = 'Microsoft.PowerShell.EditorServices.'
    if (-not (Test-Path -LiteralPath 'variable:global:psEditor')) {
        $pses.Error = 'PowerShell Editor Services (PSES) is not available in this session.'
    }
    elseif ($global:psEditor.GetType().FullName -cne $psesPrefix + 'Extensions.EditorObject') {
        $pses.Error = 'The global $psEditor (PSES) variable is of an unknown type: ' + $global:psEditor.GetType().FullName
    }
    else {
        $psesAssembly = $psEditor.GetType().Assembly
        if ($psesAssembly.GetName().Version -lt '3.21.0') {
            $pses.Error = "Version $($psesAssembly.GetName().Version) of PowerShell Editor Services (PSES) is not supported. The minimum required version is 3.21.0."
        }
        else {
            $pses.ServiceProvider = $psesAssembly.GetType($psesPrefix + 'Extensions.EditorObjectExtensions')::GetExtensionServiceProvider($psEditor)
            $pses.AnalysisServiceType = $psesAssembly.GetType($psesPrefix + 'Services.AnalysisService')
            $pses.WorkspaceServiceType = $psesAssembly.GetType($psesPrefix + 'Services.WorkspaceService')
            $tiScriptFile = $psesAssembly.GetType($psesPrefix + 'Services.TextDocument.ScriptFile')
            if ($null -eq $pses.ServiceProvider -or $null -eq $pses.AnalysisServiceType -or $null -eq $pses.WorkspaceServiceType -or $null -eq $tiScriptFile) {
                $pses.Error = 'Failed to get required services from PowerShell Editor Services (PSES).'
            }
            else {
                $reflector.Register($tiScriptFile, @('IsOpen', 'ParseFileContents'))
            }
        }
    }

    $script:PSES = $pses
}