Add PowerShell language support

Implement PowerShell language server integration using PowerShell Editor
Services (PSES). The implementation includes:

- PowerShellLanguageServer class with automatic PSES download and setup
- Support for PowerShell file extensions: .ps1, .psm1, .psd1
- Symbol detection using PSES format: "function FuncName ()"
- 7 basic tests + 2 integration tests covering:
  - Document symbols and function detection
  - Within-file references
  - Cross-file go-to-definition
  - SerenaAgent find_symbol and find_references

Requires: pwsh (PowerShell 7+) in PATH. PSES auto-downloads on first use.
This commit is contained in:
Michael Leone committed 2025-12-10 16:11:27 +00:00
1 parent fb01cc38f7
commit 032493972c
12 files changed
+809 -2

No files matched your search

@@ -0,0 +1 @@
{"status":"started","languageServiceTransport":"Stdio","powerShellVersion":"7.5.3"}
@@ -0,0 +1,109 @@
# Main script demonstrating various PowerShell features
# Import utility functions
. "$PSScriptRoot\utils.ps1"
# Global variables
$Script:ScriptName = "Main Script"
$Script:Counter = 0
<#
.SYNOPSIS
Greets a user with various greeting styles.
.PARAMETER Username
The name of the user to greet.
.PARAMETER GreetingType
The type of greeting (formal, casual, or default).
#>
function Greet-User {
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)]
[string]$Username,
[Parameter(Mandatory = $false)]
[ValidateSet("formal", "casual", "default")]
[string]$GreetingType = "default"
)
switch ($GreetingType) {
"formal" {
Write-Output "Good day, $Username!"
}
"casual" {
Write-Output "Hey $Username!"
}
default {
Write-Output "Hello, $Username!"
}
}
}
<#
.SYNOPSIS
Processes an array of items with the specified operation.
.PARAMETER Items
The array of items to process.
.PARAMETER Operation
The operation to perform (count, uppercase).
#>
function Process-Items {
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)]
[string[]]$Items,
[Parameter(Mandatory = $true)]
[ValidateSet("count", "uppercase")]
[string]$Operation
)
foreach ($item in $Items) {
switch ($Operation) {
"count" {
$Script:Counter++
Write-Output "Processing item $($Script:Counter): $item"
}
"uppercase" {
Write-Output $item.ToUpper()
}
}
}
}
<#
.SYNOPSIS
Main entry point for the script.
#>
function Main {
[CmdletBinding()]
param(
[Parameter(Mandatory = $false)]
[string]$User = "World",
[Parameter(Mandatory = $false)]
[string]$Greeting = "default"
)
Write-Output "Starting $Script:ScriptName"
# Use the Greet-User function
Greet-User -Username $User -GreetingType $Greeting
# Process some items
$items = @("item1", "item2", "item3")
Write-Output "Processing items..."
Process-Items -Items $items -Operation "count"
# Use utility functions from utils.ps1
$upperName = Convert-ToUpperCase -InputString $User
Write-Output "Uppercase name: $upperName"
$trimmed = Remove-Whitespace -InputString " Hello World "
Write-Output "Trimmed: '$trimmed'"
Write-Output "Script completed successfully"
}
# Run main function
Main @args
@@ -0,0 +1,185 @@
# Utility functions for PowerShell operations
<#
.SYNOPSIS
Converts a string to uppercase.
.PARAMETER InputString
The string to convert.
.OUTPUTS
System.String - The uppercase string.
#>
function Convert-ToUpperCase {
[CmdletBinding()]
[OutputType([string])]
param(
[Parameter(Mandatory = $true, ValueFromPipeline = $true)]
[string]$InputString
)
return $InputString.ToUpper()
}
<#
.SYNOPSIS
Converts a string to lowercase.
.PARAMETER InputString
The string to convert.
.OUTPUTS
System.String - The lowercase string.
#>
function Convert-ToLowerCase {
[CmdletBinding()]
[OutputType([string])]
param(
[Parameter(Mandatory = $true, ValueFromPipeline = $true)]
[string]$InputString
)
return $InputString.ToLower()
}
<#
.SYNOPSIS
Removes leading and trailing whitespace from a string.
.PARAMETER InputString
The string to trim.
.OUTPUTS
System.String - The trimmed string.
#>
function Remove-Whitespace {
[CmdletBinding()]
[OutputType([string])]
param(
[Parameter(Mandatory = $true, ValueFromPipeline = $true)]
[string]$InputString
)
return $InputString.Trim()
}
<#
.SYNOPSIS
Creates a backup of a file.
.PARAMETER FilePath
The path to the file to backup.
.PARAMETER BackupDirectory
The directory where the backup will be created.
.OUTPUTS
System.String - The path to the backup file.
#>
function Backup-File {
[CmdletBinding()]
[OutputType([string])]
param(
[Parameter(Mandatory = $true)]
[string]$FilePath,
[Parameter(Mandatory = $false)]
[string]$BackupDirectory = "."
)
if (-not (Test-Path $FilePath)) {
throw "File not found: $FilePath"
}
$fileName = Split-Path $FilePath -Leaf
$timestamp = Get-Date -Format "yyyyMMdd_HHmmss"
$backupName = "$fileName.$timestamp.bak"
$backupPath = Join-Path $BackupDirectory $backupName
Copy-Item -Path $FilePath -Destination $backupPath
return $backupPath
}
<#
.SYNOPSIS
Checks if an array contains a specific element.
.PARAMETER Array
The array to search.
.PARAMETER Element
The element to find.
.OUTPUTS
System.Boolean - True if the element is found, false otherwise.
#>
function Test-ArrayContains {
[CmdletBinding()]
[OutputType([bool])]
param(
[Parameter(Mandatory = $true)]
[array]$Array,
[Parameter(Mandatory = $true)]
$Element
)
return $Array -contains $Element
}
<#
.SYNOPSIS
Writes a log message with timestamp.
.PARAMETER Message
The message to log.
.PARAMETER Level
The log level (Info, Warning, Error).
#>
function Write-LogMessage {
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)]
[string]$Message,
[Parameter(Mandatory = $false)]
[ValidateSet("Info", "Warning", "Error")]
[string]$Level = "Info"
)
$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
$logEntry = "[$timestamp] [$Level] $Message"
switch ($Level) {
"Info" { Write-Host $logEntry -ForegroundColor White }
"Warning" { Write-Host $logEntry -ForegroundColor Yellow }
"Error" { Write-Host $logEntry -ForegroundColor Red }
}
}
<#
.SYNOPSIS
Validates if a string is a valid email address.
.PARAMETER Email
The email address to validate.
.OUTPUTS
System.Boolean - True if the email is valid, false otherwise.
#>
function Test-ValidEmail {
[CmdletBinding()]
[OutputType([bool])]
param(
[Parameter(Mandatory = $true)]
[string]$Email
)
$emailRegex = "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$"
return $Email -match $emailRegex
}
<#
.SYNOPSIS
Checks if a string is a valid number.
.PARAMETER Value
The string to check.
.OUTPUTS
System.Boolean - True if the string is a valid number, false otherwise.
#>
function Test-IsNumber {
[CmdletBinding()]
[OutputType([bool])]
param(
[Parameter(Mandatory = $true)]
[string]$Value
)
$number = 0
return [double]::TryParse($Value, [ref]$number)
}