RenameProvider for variable/function renaming#2152
Conversation
caa9e27 to
677f42c
Compare
|
Looks like there's some tests that need to be fixed after pulling in latest main |
|
Hey @JustinGrote I've updated the tests to use Async, they should at least run / compile now |
|
Latest MacOS/Ubuntu test failures look like a path combine issue. Note the backslash after Refactoring, may want to make sure you are using EDIT: Found the backslash hardcoded, refactored to use the three-argument path.combine, this should still work with net462 |
|
OK, all tests pass in CI now :). I'll do my best to get a first-pass review done sometime this week, and provide devcontainer/codespaces instructions to allow people to test for edge cases we need to document. |
|
Looking pretty good so far in terms of design, some cleanup will be needed. Several foreach rename issues I've already come across, I'll try to define tests for these when I get a chance. #Doesnt rename testvar
$a = 1..5
$b = 6..10
function test {
process {
foreach ($testvar in $a) {
$testvar
}
foreach ($testvar in $b) {
$testvar
}
}
}#Renames both foreach local variables ($aPath), should only rename the one in scope (agreed this is bad practice though)
function Import-FileNoWildcard {
[CmdletBinding(SupportsShouldProcess=$true)]
param(
# Specifies a path to one or more locations.
[Parameter(Mandatory=$true,
Position=0,
ParameterSetName="Path",
ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true,
HelpMessage="Path to one or more locations.")]
[Alias("PSPath", "Path")]
[ValidateNotNullOrEmpty()]
[string[]]
$Path2
)
begin {
}
process {
# Modify [CmdletBinding()] to [CmdletBinding(SupportsShouldProcess=$true)]
$paths = @()
foreach ($aPath in $Path2) {
if (!(Test-Path -LiteralPath $aPath)) {
$ex = New-Object System.Management.Automation.ItemNotFoundException "Cannot find path '$aPath' because it does not exist."
$category = [System.Management.Automation.ErrorCategory]::ObjectNotFound
$errRecord = New-Object System.Management.Automation.ErrorRecord $ex,'PathNotFound',$category,$aPath
$psCmdlet.WriteError($errRecord)
continue
}
# Resolve any relative paths
$paths += $psCmdlet.SessionState.Path.GetUnresolvedProviderPathFromPSPath($aPath)
}
foreach ($aPath in $paths) {
if ($pscmdlet.ShouldProcess($aPath, 'Operation')) {
# Process each path
$aPath
}
}
}
end {
}
}
|
|
We will also need to make sure we account for the scoping issues as mentioned by Rob that are very difficult to handle, even if it means we warn about this as best effort, or straight up refuse to do it. |
|
I made some test cases for renaming from within a for / each loop and limiting the rename to the scope if the var is defined within the creation of the statement. My only concern is a case like below. If you run the code powershell treats the $a = 1..5
$b = 6..10
function test {
process {
$i = 10
for ($Renamed = 0; $Renamed -lt $b.Count; $Renamed++) {
$null = $Renamed
}
for ($i = 0; $i -lt $a.Count; $i++) {
write-output $i
}
write-output "I will be 5 : $i not 10"
}
}
test |
ff9c25b to
386b707
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 100 out of 100 changed files in this pull request and generated 3 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Co-authored-by: Copilot <[email protected]>
Co-authored-by: Copilot <[email protected]>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 100 out of 100 changed files in this pull request and generated 3 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Co-authored-by: Copilot Autofix powered by AI <[email protected]>
Co-authored-by: Copilot <[email protected]>
|
@andyleejordan @SeeminglyScience OK, I think this is ready to be merged. Two things:
|
|
@andyleejordan @SeeminglyScience friendly two-week review request bump. |
andyleejordan
left a comment
There was a problem hiding this comment.
I actually did read through and attempt to review as much of this as I could. At this point, I'm going to trust @JustinGrote's assessment and posit that the only real way to further validate this work is to get it out to users. So with the very explicit disclaimer it includes, I think it's ready to merge!
| if (DisclaimerDeclinedForSession) { throw new HandlerErrorException(disclaimerDeclinedMessage); } | ||
| if (acceptDisclaimerOption || DisclaimerAcceptedForSession) { return true; } | ||
|
|
||
| const string renameDisclaimer = "PowerShell rename functionality is only supported in a limited set of circumstances. [Please review the notice](https://github.com/PowerShell/PowerShellEditorServices?tab=readme-ov-file#rename-disclaimer) and accept the limitations and risks."; | ||
| const string acceptAnswer = "I Accept"; | ||
| const string declineAnswer = "Decline"; | ||
|
|
||
| ShowMessageRequestParams reqParams = new() | ||
| { | ||
| Type = MessageType.Warning, | ||
| Message = renameDisclaimer, | ||
| Actions = new MessageActionItem[] { | ||
| new() { Title = acceptAnswer }, | ||
| new() { Title = declineAnswer } | ||
| } | ||
| }; | ||
|
|
||
| MessageActionItem? result = await lsp.SendRequest(reqParams, cancellationToken).ConfigureAwait(false); | ||
|
|
||
| // null happens if the user closes the dialog rather than making a selection. | ||
| if (result is null || result.Title == declineAnswer) | ||
| { | ||
| const string renameDisabledNotice = "PowerShell Rename functionality will be disabled for this session and you will not be prompted again until restart."; | ||
|
|
||
| ShowMessageParams msgParams = new() | ||
| { | ||
| Message = renameDisabledNotice, | ||
| Type = MessageType.Info | ||
| }; | ||
| lsp.SendNotification(msgParams); | ||
| DisclaimerDeclinedForSession = true; | ||
| throw new HandlerErrorException(disclaimerDeclinedMessage); | ||
| } | ||
| if (result.Title == acceptAnswer) | ||
| { | ||
| // Unfortunately the LSP spec has no spec for the server to change a client setting, so we have a suboptimal experience to tell the user to change the setting rather than doing it for them without implementing custom client behavior. | ||
| const string acceptDisclaimerNotice = "PowerShell rename functionality has been enabled for this session. To avoid this prompt in the future, set the powershell.rename.acceptDisclaimer to true in your settings."; | ||
| ShowMessageParams msgParams = new() | ||
| { | ||
| Message = acceptDisclaimerNotice, | ||
| Type = MessageType.Info | ||
| }; | ||
| lsp.SendNotification(msgParams); | ||
|
|
||
| DisclaimerAcceptedForSession = true; | ||
| return DisclaimerAcceptedForSession; | ||
| } |
There was a problem hiding this comment.
I get that we've already agreed that this needs a disclaimer, and so I'm not rehashing this, just now that I'm reading through it I realize it's a bit ridiculous. Side note: it's not localized...but nor is anything else in the extension. our bad.
There was a problem hiding this comment.
I figured once we get this big chunk out of the way we can do some of those more "sweeping" changes like code style updates and localization.
| ## v3.30.0 (mistakenly released as v3.3.0) | ||
| ### Friday, November 15, 2024 | ||
|
|
||
| See more details at the GitHub Release for [v3.3.0](https://github.com/PowerShell/PowerShellEditorServices/releases/tag/v3.3.0). | ||
|
|
||
| Logging updates and dropped EOL PowerShell | ||
|
|
There was a problem hiding this comment.
Did we mean to include this?
There was a problem hiding this comment.
@andyleejordan I can split it out a separate commit but it was sort of a drive-by issue I noticed that broke.
|
Is there a way to feature flag this? I agree that getting actual users is going to be the real test. |
|
@HeyItsGilbert as of today, F2 doesn't work anyways, so the feature flag is "pressing F2" :). It doesn't break or change any existing functionality. |
JustinGrote
left a comment
There was a problem hiding this comment.
Performed final spot test checks, seems to be working fine. Lets see what happens!
Issue: Smart Variable Rename
PR Summary
This pull request implements the LSP textDocument/rename and textDocument/prepareRename handlers for PowerShell.
#2152 exposes the related service settings in vscode-powershell.
Reviewer's Guide
PowerShell is not a statically typed language and as such, some variable definitions and relations are determined at runtime, therefore true static analysis of a full PowerShell project is not possible. However, by presenting a disclaimer of the limitations, we feel we can provide fairly stable rename support within a single document for several scenarios without turning this into a bug/issue farm.
All work is driven through a
RenameServicewhich controls the messaging and registers the handlers. The general flow is to:The
RenameHandlerTestsandPrepareRenameHandlerTestsfollow the rename behavior, and theHandleris considered our public API for these purposes and what we test against, even though there is a lot of implementation detail inside.Taking this approach minimizes state maintenance in the Service at the expense of probably more-than-necessary AST walks, but current testing performance finds this to be sufficient even for large documents. There are several places caching of some AST walks could be performed to optimize performance but they are out of scope for this initial PR.
Reviewer Asks
TODO List
IPrepareRenameHandlerandIRenameHandlerScriptExtentAdapterandScriptPositionAdaptertypes to translate between script and lsp positions to avoid "off-by-one" errors since lsp is zero-based and script is one-basedShowMessageRequest(cannot set the setting directly ATM as it's not an LSP standard scenario)AstVisitorAstVisitorPotential Additional Features
More Tests