-
Notifications
You must be signed in to change notification settings - Fork 232
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
RenameProvider for variable/function renaming #2152
base: main
Are you sure you want to change the base?
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 |
test/PowerShellEditorServices.Test/Refactoring/RefactorVariableTests.cs
Outdated
Show resolved
Hide resolved
test/PowerShellEditorServices.Test.Shared/Refactoring/Functions/ForeachFunction.ps1
Outdated
Show resolved
Hide resolved
test/PowerShellEditorServices.Test.Shared/Refactoring/Functions/ForeachFunction.ps1
Outdated
Show resolved
Hide resolved
test/PowerShellEditorServices.Test.Shared/Refactoring/Functions/ForeachFunction.ps1
Outdated
Show resolved
Hide resolved
src/PowerShellEditorServices/Services/PowerShell/Refactoring/IterativeVariableVisitor.cs
Outdated
Show resolved
Hide resolved
test/PowerShellEditorServices.Test/Refactoring/RefactorFunctionTests.cs
Outdated
Show resolved
Hide resolved
ff9c25b
to
386b707
Compare
…educe some unnecessary ast searches
63bfac1
to
8dc1e3c
Compare
As was explained in the README that you clicked "I Accept" on, rename is limited to a single file only :). Whereas the references may or may not be able to find cross-file references, there's no guarantee those are the same reference, and that's fine when read-only but potentially disastrous when making changes. Also the Alias is now an opt-in setting, but I think it needs a little more work, you'll need to check settings for it and enable it. |
Should have RTFM. My bad! This seems like the kind of thing that would be nice to have opt-in. It requires an extra step so users have to explicitly decide and at that point we can warn them of the possible issues (e.g. ensuring all the code base is loaded into AST, how to check if all the references are correct, how to exclude files from being referenced, etc.). With the rename functionality you also have the option to hit
Why opt in? If we start with the assumption that the user isn't familiar with the Alias attribute, at worst they have an extra/unused line of code. This may prompt them to investigate what it is and then make an informed decision. Compared to a user not knowing and not realizing they can offer a simple backwards compatible feature. |
I know, but the amount of unit tests to cover a proper implementation, not to mention what the model would have to look like, is extremely ugly, only to have it only ever be a half-baked, "works sometimes" implementation. There's just too many ways that dot sourcing etc. break this model. It might be somewhat feasible for functions in a best effort manner given the same limitations, but for variables it's far too risky as people reuse variable names within functions constantly. The intent of this feature is for simple refactors within a single file, e.g. renaming a variable within a function. It's not meant for multi-file project-wide refactors. You really need a type-safe language for something like that to be effective which PowerShell isn't.
During development a lot of people felt the ergonomics of the Alias attribute coming in was annoying, as they weren't looking for backwards compatibility, just to rename a variable/parameter of a function-in-development, so more often than not they were going back and deleting the alias than it being actually useful. |
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
RenameService
which controls the messaging and registers the handlers. The general flow is to:The
RenameHandlerTests
andPrepareRenameHandlerTests
follow the rename behavior, and theHandler
is 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
IPrepareRenameHandler
andIRenameHandler
ScriptExtentAdapter
andScriptPositionAdapter
types 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)AstVisitor
AstVisitor
Potential Additional Features
More Tests