Skip to content

Tab completion for Where-Object and Foreach-Object does not escape single quotes in property names #24493

Open

Description

Prerequisites

Steps to reproduce

For PSCustomObject's (and likely others) properties containing characters like spaces and quotes, tab completion automatically quotes and escapes them. As example payload, take:

> $employees = "ID,Employee's name`n1,Alice`n2,Bob"

While $employees[0].E<TAB> will correctly produce $employees[0].'Employee''s name', doubling single quote to escape it, it is not done for Where-Object and ForEach-Object.

Expected behavior

> $Employees | % E<TAB>
$Employees | % 'Employee''s name'

Actual behavior

> $Employees | % E<TAB>
$Employees | % 'Employee's name'

Error details

Pressing enter waits for input due to unclosed quote.

Environment data

Name                           Value
----                           -----
PSVersion                      7.4.5
PSEdition                      Core
GitCommitId                    7.4.5
OS                             Microsoft Windows 10.0.22631
Platform                       Win32NT
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0…}
PSRemotingProtocolVersion      2.3
SerializationVersion           1.1.0.1
WSManStackVersion              3.0

Visuals

No response

Activity

  1. dkaszews commented on Oct 25, 2024

    @dkaszews
    ContributorAuthor

    Paging MartinGC94 who I believe is local expert on tab completion

  2. changed the title [-]Where-Object and Foreach-object do not escape single quotes in property names[/-] [+]Tab completion for Where-Object and Foreach-Object does not escape single quotes in property names[/+] on Oct 25, 2024
  3. MartinGC94 commented on Oct 25, 2024

    @MartinGC94
    Contributor

    This can be fixed here: https://github.com/PowerShell/PowerShell/blob/master/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs#L6882 by changing it from completionText = $"'{memberName}'"; to completionText = $"'{memberName.Replace("'", "''")}'"; which is how it's done in other places. Though like in those other places it won't work if the member name contains special single quote chars like ’.

    GitHub
    PowerShell for every system! Contribute to PowerShell/PowerShell development by creating an account on GitHub.
  4. dkaszews commented on Oct 26, 2024

    @dkaszews
    ContributorAuthor

    Wouldn't it make more sense to try and centralise ilthe code, so that commands use the same escaping as [0].E<YAB>?Otherwise there will be more mismatches

  5. MartinGC94 commented on Oct 26, 2024

    @MartinGC94
    Contributor

    Definitely. What I suggested before was just a quick fix. Ideally someone should do what this PR tried to do: #10226 which is to go through all the completers to make the escaping methods work consistently.
    Personally though I don't feel like doing the work because:
    1: I have a bunch of other pending PRs that I don't want to risk merge conflicts with.
    2: It's rarely something that bothers me because member names are typically simple enough that the current escaping works well enough for me.

  6. dkaszews commented on Oct 26, 2024

    @dkaszews
    ContributorAuthor

    Sure thing, refactors are usually done when there is no other pending work.

  7. santisq commented on Oct 26, 2024

    @santisq

    This can be fixed here: https://github.com/PowerShell/PowerShell/blob/master/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs#L6882 by changing it from completionText = $"'{memberName}'"; to completionText = $"'{memberName.Replace("'", "''")}'"; which is how it's done in other places. Though like in those other places it won't work if the member name contains special single quote chars like ’.

    Wouldn't it make sense to use CodeGeneration.EscapeSingleQuotedStringContent instead of .Replace ?

  8. MartinGC94 commented on Oct 26, 2024

    @MartinGC94
    Contributor

    I didn't know about that method but I don't see any reason why it should be preferred over .Replace.

  9. santisq commented on Oct 26, 2024

    @santisq

    Well, main reason is for what you mentioned in the last comment:

    Though like in those other places it won't work if the member name contains special single quote chars like ’

    This method handles that:

    [System.Management.Automation.Language.CodeGeneration]::EscapeSingleQuotedStringContent("foo’bar")
    # Outputs: foo’’bar
  10. dkaszews commented on Oct 27, 2024

    @dkaszews
    ContributorAuthor

    Would something like this work?

    var completion Text = (memberName.IndexOfAny(s_charactersRequiringQuotes) != 0)
        ? $"'{EscapeSingleQuotedStringContent(memberName)'"
        : memberName;
    if (isMethod && addMethodParenthesis)
    {
        completionText += "(";
    }
  11. santisq commented on Oct 30, 2024

    @santisq

    Would something like this work?

    var completion Text = (memberName.IndexOfAny(s_charactersRequiringQuotes) != 0)
        ? $"'{EscapeSingleQuotedStringContent(memberName)'"
        : memberName;
    if (isMethod && addMethodParenthesis)
    {
        completionText += "(";
    }

    I don't think methods can have a ' (might be mistaken) but also you shouldn't try to add ( when using the -MemberName + -ArgumentList parameter set. ForEach-Object will correctly bind the method arguments using spaces, i.e.:

    class Test {
        [string] Method([string] $foo, [string] $bar) {
            return "$foo $bar"
        }
    }
    
    [Test]::new() | % Method hello world
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Needs-TriageThe issue is new and needs to be triaged by a work group.WG-Interactive-IntelliSensetab completionWG-NeedsReviewNeeds a review by the labeled Working Group

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions