-
Notifications
You must be signed in to change notification settings - Fork 129
RFC for expanded implicit line continuance feature #176
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
Changes from 2 commits
53b8b3a
b1880c9
95051f0
ed40b38
aef259b
d8c5ea0
f47942f
f84afbb
7211f19
65e4ba3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,148 @@ | ||
--- | ||
RFC: RFCnnnn | ||
Author: Kirk Munro | ||
Status: Draft | ||
SupercededBy: N/A | ||
Version: 1.0 | ||
Area: Parser/Tokenizer | ||
Comments Due: June 16, 2019 | ||
Plan to implement: Yes | ||
--- | ||
|
||
# Support implicit line continuance when using named parameters or splatting in commands | ||
|
||
Nobody likes having to use the backtick to wrap commands in PowerShell, yet many users still use it to get the style they prefer in their scripts. | ||
|
||
For example, PowerShell has long supported explicit line continuance when you end a pipelined command line with a pipe symbol, like this: | ||
|
||
```PowerShell | ||
Get-Process -Id $PID | | ||
Stop-Process | ||
``` | ||
|
||
Even though that is available, when you have a pipeline with many stages (commands) in it, the style is not as desirable as it could be if you could place the pipeline on the beginning of the line instead, like this: | ||
|
||
```PowerShell | ||
Get-Process -Id $PID | ||
| Stop-Process | ||
``` | ||
|
||
Historically that syntax would not be possible unless you ended each line before the last line in the pipeline with a backtick. That shouldn't be necessary though, and PowerShell should be smart enough to recognize implicit line continuance when pipelines are placed at the beginning of the line. This was discussed in detail in [Issue #3020](https://github.com/PowerShell/PowerShell/issues/3020), and [PR #8938](https://github.com/PowerShell/PowerShell/pull/8938) that was recently merged added this support to PowerShell for version 7. | ||
|
||
While that is helpful, there is another potential improvement where PowerShell could support implicit line continuance: when using named parameters or splatting in commands, and that's what this RFC is about. | ||
|
||
For example, consider this example of a New-ADUser command invocation: | ||
|
||
```PowerShell | ||
New-ADUser -Name 'Jack Robinson' -GivenName 'Jack' -Surname 'Robinson' -SamAccountName 'J.Robinson' -UserPrincipalName '[email protected]' -Path 'OU=Users,DC=enterprise,DC=com' -AccountPassword (Read-Host -AsSecureString 'Input Password') -Enabled $true | ||
``` | ||
|
||
By itself it's not too much to handle, but in a script commands with many parameters like this can be difficult to manage it in a script. To wrap this command across multiple lines, users can either use backticks, or they can use splatting. The former is a nuisance which should really only be used in situations when PowerShell cannot implicitly intuit how lines are wrapped. The latter is helpful, but users lose the benefits of tab completion and Intellisense for parameters when they use splatting. As a workaround, they can work out the parameters they want to use for the command first, and then convert it into a splatted command, but that's generally onerous. Even though Visual Studio Code has an extension that makes splatting easier, as can be seen [here](https://sqldbawithabeard.com/2018/03/11/easily-splatting-powershell-with-vs-code/), once you've converted to splatting you still lose Intellisense for future updates unless you work from the command first and then add to your splatted collection, and that's just in Visual Studio Code. Other editors may or may not support that functionality, and users working in a standalone terminal won't have that available to them either. | ||
|
||
Instead, why not allow users to do this by supporting implicit line continuance when using named parameters: | ||
|
||
```PowerShell | ||
New-ADUser | ||
-Name 'Jack Robinson' | ||
-GivenName 'Jack' | ||
-Surname 'Robinson' | ||
-SamAccountName 'J.Robinson' | ||
-UserPrincipalName '[email protected]' | ||
-Path 'OU=Users,DC=enterprise,DC=com' | ||
-AccountPassword (Read-Host -AsSecureString 'Input Password') | ||
-Enabled $true | ||
``` | ||
|
||
Further, if they have some parameters they want to splat in (because splatting is not just about shortening lines -- it's very useful for users to apply common parameters/values to multiple commands), let them do this as well: | ||
|
||
```PowerShell | ||
$commonParams = @{ | ||
Path = 'OU=Users,DC=enterprise,DC=com' | ||
Enabled = $true | ||
} | ||
New-ADUser | ||
-Name 'Jack Robinson' | ||
-GivenName 'Jack' | ||
-Surname 'Robinson' | ||
-SamAccountName 'J.Robinson' | ||
-UserPrincipalName '[email protected]' | ||
-AccountPassword (Read-Host -AsSecureString 'Input Password') | ||
@commonParams | ||
``` | ||
|
||
## Motivation | ||
|
||
As a script/module author, | ||
I can wrap long commands across multiple lines at any named parameter or splatted collection, | ||
so that my scripts are easier to maintain while I still get the benefits of Intellisense and tab completion. | ||
|
||
## Specification | ||
|
||
Note: This RFC is already implemented and submitted as [PR #9614](https://github.com/PowerShell/PowerShell/pull/9614). | ||
|
||
Since this RFC includes some breaking changes (see below), it will be initially implemented with the `PSImplicitLineContinuanceForNamedParameters` experimental feature flag. If the PowerShell Team and the community agree that the risk of this breaking change is low enough, and the workaround is sufficient for those low frequency use cases where it does become an issue, I would much prefer not using an experimental feature flag at all. | ||
|
||
For the implementation, this RFC builds on the work done in [PR #8938](https://github.com/PowerShell/PowerShell/pull/8938), evolving the logic used in the tokenizer that supports implicit line continuance for lines starting with a pipe such that it can be used to check for implicit line continuance for named parameters and splatted collections as well. To keep performance optimal, the parser logic that identifies command parameters where the implicit line continuance checks will be invoked will recognize if a pipe is found on a subsequent line as well, to ensure the lookahead logic is only invoked once per line in commands/pipelines. | ||
|
||
## Alternate Proposals and Considerations | ||
|
||
While implicit line continuance for splatted collections is not a breaking change (PowerShell syntax does not support `@variableName` at the start of a command), implicit line continuance for named parameters is a breaking change in some scenarios, because PowerShell syntax currently supports commands that start with dash, and PowerShell supports unary operators whose name starts with a single dash. | ||
|
||
For example, consider this script: | ||
|
||
```PowerShell | ||
function -dash { | ||
'run' | ||
} | ||
|
||
Get-Process -Id $PID | ||
-dash | ||
``` | ||
|
||
In PowerShell 6.x and earlier, two commands will be executed: `Get-Process`, and `-dash`. With this PR in place and the experimental feature enabled, only one command would be executed: `Get-Process`. The reason is that the parser would identify `-dash` as being intended as a parameter on the command on the previous line. That's how the parser needs to work to make this implicit line continuance functional. | ||
|
||
Similarly, consider this script: | ||
|
||
```PowerShell | ||
Get-Process -Id $PID | ||
-split 'a b c d' | ||
``` | ||
|
||
Similar to the previous example, in PowerShell 6.x and earlier, one command and one statement will be executed: `Get-Process`, and the unary `-split` statement. With this PR in place and the experimental feature enabled, only one command would be executed: `Get-Process`, because again, the parser would identify `-split` as being intended as a parameter on the command on the previous line. The same applies to the `-join` unary operator as well. It does not apply to the `--` prefix arithmetic operator because the parser knows parameter names cannot start with a dash. | ||
|
||
To fix this, users can do one of the following: | ||
|
||
1. For either example, insert a blank line between `Get-Process` and the next command that starts with dash, as shown here: | ||
|
||
```PowerShell | ||
function -dash { | ||
'run' | ||
} | ||
|
||
Get-Process -Id $PID | ||
|
||
-dash # Runs the -dash command because implicit line continuance stops looking when it encounters a blank line | ||
|
||
Get-Process -Id $PID | ||
|
||
-split 'a b c d' # splits the string 'a b c d' into an array with four items | ||
``` | ||
|
||
1. In the example with a command that starts with a dash, invoke the command using the call operator, as shown here: | ||
|
||
```PowerShell | ||
function -dash { | ||
'run' | ||
} | ||
|
||
Get-Process -Id $PID | ||
& -dash # Runs the `-dash` command because implicit line continuance sees the call operator and recognizes that the line does not continue further | ||
``` | ||
|
||
Unfortunately this workaround does not work for the -split or -join unary operators, because you cannot invoke statement that starts with a unary operator with the call operator (the call operator is only for invoking commands). | ||
|
||
In practice, I believe that there are not many commands out there that start with a dash. While researching this, I couldn't find a single command on Windows or Linux that starts with a dash, so that's not a very risky scenario. However, since the `-split` and `-join` unary operators exist in PowerShell, there is a good likelihood that those may be used on a line following a command, and that is where this breaking change would have the most potential impact. While I don't feel that risk is enough to reject this proposal entirely, because it adds significant value to script authors, it is worth considering what the best approach would be. | ||
KirkMunro marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
Special casing the named unary operators could work (among the thousands of commands on my system, none of them have split or join parameters, but some may exist somewhere), but that would mean future unary operators be special cased as well, so that approach isn't desirable because it risks future breaking changes. | ||
KirkMunro marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
If the risk is deemed to be too high because of the risk with named unary operators, at a bare minimum I feel this feature offers enough significant value to the community that it should not be rejected, but instead offered as an optional feature so that users wanting the benefit can opt-into the functionality, and mark it as enabled for their scripts/modules. I also suspect the majority of scripters would want it turned on and would then simply write their scripts accordingly. It's really too bad though that unary operators with string names use the same single first character as parameter names because they get in the way here. In hindsight, named operators should probably have been prefixed with something other than a single dash to differentiate them from named parameters (something to consider if PowerShell ever comes out with a version with significant breaking changes plus conversion scripts). | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would it reduce the risk if we require some character on the line after the command to initiate support for parameter line wrapping? For instance, what if we put the splat character (and ignored whitespace) at the end of the line as an indicator that the parameters continue on multiple lines: Get-Process @
-Id $pid vs Get-Process -Id $PID
-split 'a b c d' There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's interesting. That would eliminate the breaking change factor entirely, making it opt-in. A little unfortunate to have to add something to each command you want to wrap, vs having a feature/flag that allows script/module authors to enable this for their content without impacting end users ad-hoc use. Another syntax that came to mind for me when I read your suggestion: Get-Process ...
-Id $pid 😆 Regardless of the character(s) used to indicate you want to wrap the command, that could also make it possible to use ad-hoc in a terminal, with PowerShell only terminating the command once you have pressed enter twice. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My initial read of this RFC was negative simply because it would change the functionality of non-visable characters in a non-obvious way. Choosing the begin wrapping with the '@' character makes the most sense to me. This is way better than using a back tick and if fully supported, should also provide intellisense. That would make this method preferable when compared to splatting which has no intellisense. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Now I'm thinking it might be interesting if the Windows terminal supported a multi-line command mode, allowing users to enter multiple-line commands without executing them until enter is pressed after a blank line at the end of the command. And the terminal is open source now. Hmmm..... There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @ngetchell It would only be non-obvious if you followed a command with the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is it worth pointing out that |
Uh oh!
There was an error while loading. Please reload this page.