Functions/GenXdev.Coding.PowerShell.Modules/Assert-RefactorFile.ps1

<##############################################################################
Part of PowerShell module : GenXdev.Coding.PowerShell.Modules
Original cmdlet filename : Assert-RefactorFile.ps1
Original author : René Vaessen / GenXdev
Version : 1.268.2025
################################################################################
MIT License
 
Copyright 2021-2025 GenXdev
 
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
 
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
 
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
################################################################################>

################################################################################
<#
.SYNOPSIS
Executes a refactoring operation on a source file using the specified IDE and
AI prompt template.
 
.DESCRIPTION
This function automates the refactoring process by preparing an AI prompt based
on the refactor definition, detecting or selecting the appropriate IDE (VS Code
or Visual Studio), and opening the target file with the prepared prompt. The
function handles prompt template processing, IDE detection, and automation of
the refactoring workflow.
 
.PARAMETER RefactorDefinition
The refactor definition object containing settings, prompt templates, and IDE
preferences for the refactoring operation.
 
.PARAMETER Path
The absolute or relative path to the source file that needs to be refactored.
The path will be expanded to an absolute path during processing.
 
.EXAMPLE
$refactorDef = Get-RefactorDefinition -Type "Documentation"
Assert-RefactorFile -RefactorDefinition $refactorDef -Path "MyScript.ps1"
 
.EXAMPLE
Assert-RefactorFile -RefactorDefinition $refactorDef -Path ".\Functions\Test.ps1"
#>

function Assert-RefactorFile {

    [CmdletBinding()]
    param(
        ###############################################################################
        [Parameter(
            Mandatory = $true,
            Position = 0,
            HelpMessage = 'The path to the source file to improve'
        )]
        [ValidateNotNullOrEmpty()]
        [Alias('FullName')]
        [string] $Path,
        ###############################################################################
        [Parameter(
            Mandatory = $false,
            Position = 1,
            HelpMessage = 'The refactor definition containing settings and prompt template'
        )]
        [ValidateNotNull()]
        [GenXdev.Helpers.RefactorSettings] $RefactorSettings = [GenXdev.Helpers.RefactorSettings]::new(),
        ###############################################################################
        [switch] $AllowLongRunningTests
    )

    begin {

        # prepare paths for scripts and modules directories
        $scriptsPath = GenXdev.FileSystem\Expand-Path `
            "$PSScriptRoot\..\..\..\..\..\Scripts\" `
            -CreateDirectory

        $modulesPath = GenXdev.FileSystem\Expand-Path `
            "$PSScriptRoot\..\..\..\..\" `
            -CreateDirectory

        # expand the target file path to absolute path
        $Path = GenXdev.FileSystem\Expand-Path $Path

        # extract prompt settings from the refactor definition
        $prompt = $RefactorSettings.Prompt

        $promptKey = $RefactorSettings.PromptKey

        # detect which IDE is currently active by examining running processes
        [System.Diagnostics.Process] $hostProcess = `
            GenXdev.Windows\Get-PowershellMainWindowProcess

        $isCode = $hostProcess.Name -eq 'Code' -or $hostProcess.Name -eq 'Code - Insiders'
        $isVisualStudio = $hostProcess.Name -eq 'devenv'

        # fallback to refactor definition preferences if no active IDE found
        if (-not ($isCode -or $isVisualStudio)) {

            # check if VS Code is preferred in settings
            if ($RefactorSettings.Code -ge 0) {

                $isCode = $RefactorSettings.Code -eq 1
            }

            # check if Visual Studio is preferred in settings
            if ($RefactorSettings.VisualStudio -ge 0) {

                $isVisualStudio = $RefactorSettings.VisualStudio -eq 1
            }

            # attempt to find any running IDE process as final fallback
            if (-not ($isCode -or $isVisualStudio)) {

                Microsoft.PowerShell.Utility\Write-Verbose (
                    'Attempting to detect running IDE instances')

                # search for running VS Code processes
                [System.Diagnostics.Process] $hostProcess = `
                    Microsoft.PowerShell.Management\Get-Process 'Code' `
                        -ErrorAction SilentlyContinue |
                        Microsoft.PowerShell.Utility\Sort-Object |
                        Microsoft.PowerShell.Utility\Select-Object -First 1

                $isCode = $null -ne $hostProcess

                # search for running Visual Studio processes
                $hostProcess = Microsoft.PowerShell.Management\Get-Process 'devenv' `
                    -ErrorAction SilentlyContinue |
                    Microsoft.PowerShell.Utility\Sort-Object |
                    Microsoft.PowerShell.Utility\Select-Object -First 1

                $isVisualStudio = $null -ne $hostProcess
            }
        }

        # prompt user to select IDE if no clear choice can be determined
        if (-not ($isCode -bxor $isVisualStudio)) {

            Microsoft.PowerShell.Utility\Write-Verbose 'Prompting user to select IDE'

            # use cached selection or prompt for new choice
            $userAnswer = $null -ne $Script:_CodeOrVisualStudioRefactor ?
                $Script:_CodeOrVisualStudioRefactor :
                ($host.ui.PromptForChoice(
                    'Make a choice',
                    'What IDE to use for refactoring?',
                    @('Visual Studio &Code', '&Visual Studio'),
                    0))

            # cache the user's selection for future operations
            $Script:_CodeOrVisualStudioRefactor = $userAnswer

            # apply the user's IDE selection
            switch ($userAnswer) {

                0 {
                    $isCode = $true

                    $isVisualStudio = $false

                    break;
                }
                1 {
                    $isCode = $false

                    $isVisualStudio = $true

                    break;
                }
            }
        }

        # process and load prompt template if a template key is specified
        if (-not [string]::IsNullOrWhiteSpace($PromptKey)) {

            # construct the base path for prompt templates
            $promptFilePath = GenXdev.FileSystem\Expand-Path `
                "$PSScriptRoot\..\..\Prompts\GenXdev.Coding.PowerShell.Modules\" `
                -CreateDirectory

            # build the template file path using the prompt key
            $promptFilePath = Microsoft.PowerShell.Management\Join-Path `
                $promptFilePath "Assert-$PromptKey.txt"

            # use script-specific template if target file is in scripts folder
            if ($Path -like "$scriptsPath\*.ps1") {

                $promptFilePath = GenXdev.FileSystem\Expand-Path (
                    "$PSScriptRoot\..\..\Prompts\GenXdev.Coding.PowerShell.Modules\" +
                    "Assert-$PromptKey-script.txt") -CreateFile
            }

            # load the template file and replace the prompt placeholder
            $Prompt = [System.IO.File]::ReadAllText($promptFilePath).Replace(
                "`$Prompt",
                $Prompt
            )
        }

        # replace template variables with actual values from the target file
        $Prompt = $Prompt.Replace(
            "`$CmdletName",
            [System.IO.Path]::GetFileNameWithoutExtension($Path)
        )

        # remove test suffix from cmdlet name for cleaner prompts
        $Prompt = $Prompt.Replace(
            "`$CmdLetNoTestName",
            [System.IO.Path]::GetFileNameWithoutExtension($Path).Replace(
                '.Tests', ''
            )
        )

        # insert the script filename into the prompt
        $Prompt = $Prompt.Replace(
            "`$ScriptFileName",
            [System.IO.Path]::GetFileName($Path)
        )

        # normalize tab characters to spaces for consistent formatting
        $Prompt = $Prompt.Replace("`t", ' ')

        # determine module context based on file location
        if ($Path.ToLowerInvariant().StartsWith($scriptsPath.ToLowerInvariant())) {

            # handle files in the scripts directory
            $Prompt = $Prompt.Replace(
                "`$FullModuleName",
                'GenXdev.Scripts'
            )

            $Prompt = $Prompt.Replace(
                "`$BaseModuleName",
                ''
            )
        }
        else {

            # extract base module name from the file path
            $baseModuleName = `
                "$($Path.Substring($modulesPath.Length + 1).Split('\')[0])"

            # construct paths for functions and tests directories
            $functionsPath = GenXdev.FileSystem\Expand-Path `
                "$modulesPath\$baseModuleName\1.268.2025\Functions\" `
                -CreateDirectory

            $testsPath = GenXdev.FileSystem\Expand-Path `
                "$modulesPath\$baseModuleName\1.268.2025\Tests\" `
                -CreateDirectory

            # determine the specific module context based on file location
            if ($Path.ToLowerInvariant().StartsWith($functionsPath.ToLowerInvariant())) {

                # extract sub-module name from functions path
                $Prompt = $Prompt.Replace(
                    "`$FullModuleName",
                    "$($Path.Substring($functionsPath.Length + 1).Split('\')[0])"
                )
            }
            elseif ($Path.ToLowerInvariant().StartsWith($testsPath.ToLowerInvariant())) {

                # extract sub-module name from tests path
                $Prompt = $Prompt.Replace(
                    "`$FullModuleName",
                    "$($Path.Substring($testsPath.Length + 1).Split('\')[0])"
                )
            }
            else {

                # use base module name for other locations
                $Prompt = $Prompt.Replace(
                    "`$FullModuleName",
                    "$baseModuleName"
                )
            }

            # set the base module name in the prompt
            $Prompt = $Prompt.Replace(
                "`$BaseModuleName",
                "$baseModuleName"
            )
        }

        # final normalization of tab characters
        $Prompt = $Prompt.Replace("`t", ' ')

        # preserve current clipboard content for later restoration
        $previousClipboard = Microsoft.PowerShell.Management\Get-Clipboard

        # copy the prepared prompt to clipboard for use in IDE
        $null = Microsoft.PowerShell.Management\Set-Clipboard -Value $prompt

        Microsoft.PowerShell.Utility\Write-Verbose (
            'Prepared prompt and copied to clipboard:')

        Microsoft.PowerShell.Utility\Write-Verbose $prompt

        # determine keyboard automation sequence based on selected IDE
        $keysToSend = $RefactorSettings.KeysToSend

        # use default key sequences if none specified in settings
        if (($null -eq $keysToSend) -or ($keysToSend.Count -eq 0)) {

            # configure VS Code key sequence for AI assistant integration
            if ($isCode) {

                $keysToSend = @("^``", "^``", '^+i', '^n', '^a', '{DELETE}', '^%b',
                    '^+%{F12}', '{ENTER}', '^v', '{ENTER}', '^{ENTER}', "^``") # '^%b',
            }
            elseif ($isVisualStudio) {

                # configure Visual Studio key sequence for copilot integration
                $keysToSend = @('^\', '^c', '^a', ' {DELETE}', '^v',
                    '{ENTER}', '^{ENTER}')
            }
        }
    }

    process {

        Microsoft.PowerShell.Utility\Write-Verbose (
            'Opening file in IDE for refactoring')

        # prepare parameter hashtable for IDE invocation
        $invocationParams = @{
            Path       = $Path
            KeysToSend = $keysToSend
        }

        # add IDE-specific parameters if they exist in current scope
        $invocationParams.Code = $Code

        $invocationParams.VisualStudio = $VisualStudio

        # launch the selected IDE with automation parameters
        GenXdev.Coding\Open-SourceFileInIde @invocationParams
    }

    end {

        # restore the original clipboard content to maintain user workflow
        $null = Microsoft.PowerShell.Management\Set-Clipboard -Value $previousClipboard
    }
}
################################################################################