Skip to content

Quoting in subexpressions in string literals is quite confused #17887

@jporkka

Description

@jporkka

Prerequisites

Steps to reproduce

I was attempting to write a function to escape a string to correctly print a string for generated powershell code.
The rules for escaping double-quotes inside a double-quoted string are obscure at best, and quite broken depending on your point of view.

The coloring in the powershell console appears to mostly match the code behavior, but tools like VSCode do not match powershell behavior.

Here is a block of code demonstrating a number of issues
#1: Escaping isn't required, but sometimes it is allowed
#2: To have double-quotes inside an embedded string requires double backticks, yet only a single backtick is necessary for things like newline.
#3: Parans inside a nested string confuse powershell
#3: Doubling nested double-quotes seems to escape them, but there is a lot of wiggle room for how many double quotes is needed.

##########################################################################################
Write-Output "Begin Sample 1"
# Inside a "$()" subexpression it turns out you don't need to escape quotes
$b = "'$($a.replace("'", "''"))'"

# Oddly, PowerShell accepts the back-tick double-quote (`") though...
"...$("hello")..."     # OK - as expected, escaping inner quotes isn't necessary

# Yet, escaping is accepted by powershell, but confuses VSCode coloring.
"...$(`"hello")..."    # OK, VSCode miscolors "hello" as not a string and open "(" as mismatched
"...$(`"hello`")..."   # OK, VSCode miscolors "hello" as not a string

# Escaping here even produces a newline character
"...$(`"hello`n")..."  # Treats `n as newline.

Write-Output "End of test 1"

##########################################################################################
Write-Output "Begin Sample 2"
# To have embedded double-quotes inside the subexpression it is necessary to use Double-BackTick
# Weirdly, doubling the back-ticks appears to behave like a single back-tick outside of a subexpression by escaping the double-quote.
Write-Output "$(`"hello``"...``" ")" # Prints hello"...". Also confuses VSCode.

# Write-Output "$("hello`"...`" ")" # Fails: You must provide a value expression following the '..' operator.

Write-Output $("hello`"...`" ") # Prints hello"..."


##########################################################################################
Write-Output "Begin Sample 3"
# Here, Powershell interprets the closing paran, inside quotes, as being not quoted.
# this does not compile - even though the error message appears to correctly color the "hello)" portion as a string.
# Also, back-ticks don't seem to be able to escape nested parans.
Write-Output "$( Write-Output "hello)" )"  # Does not run: The string is missing the terminator: ".
Write-Output "$( Write-Output "(hello)" )" # Prints  (hello)

##########################################################################################
Write-Output "Begin Sample 4"
# Funky handling of multiple double-quotes.
# 0 to 2 double quotes: Not output
# 3 to 6 double quotes: Single double-quote
# 7 to 10 double quotes: Two double-quotes
# 11 to 14 double quotes: Three double-quotes
# For 2, 5, 10 and 14 the closing ")" doesn't print either.
Write-Output "$( Write-Output " 2 hell(o"" 5+5)")"             # Prints:  2 hell(o 5+5      
Write-Output "$( Write-Output " 3 hell(o""" 5+5)")"            # Prints:  3 hell(o" 5+5)    
Write-Output "$( Write-Output " 4 hell(o"""" 5+5)")"           # Prints:  4 hell(o" 5+5)    
Write-Output "$( Write-Output " 5 hell(o""""" 5+5)")"          # Prints:  5 hell(o" 5+5     
Write-Output "$( Write-Output " 7 hell(o""""""" 5+5)")"        # Prints:  7 hell(o"" 5+5)   
Write-Output "$( Write-Output "10 hell(o"""""""""" 5+5)")"     # Prints: 10 hell(o"" 5+5    
Write-Output "$( Write-Output "11 hell(o""""""""""" 5+5)")"    # Prints: 11 hell(o""" 5+5)  -- Also, this confuses VSCode coloring.
Write-Output "$( Write-Output "12 hell(o"""""""""""" 5+5)")"   # Prints: 12 hell(o""" 5+5)  
Write-Output "$( Write-Output "14 hell(o"""""""""""""" 5+5)")" # Prints: 14 hell(o""" 5+5   

# More samples of bad handling of nested parans.
# In both cases there are no errors, but the text after the close ")" is dropped.
Write-Output "$(Write-Output "he(llo" )after )"                        # Prints: he(llo
Write-Output "$(Write-Output "he(llo" ) after$(Write-Output more)"string)"     # Prints: he(llo -- And VSCode color is confused: " Extra quote to fix VSCode coloring

Write-Output "End"

Expected behavior

Inside a "$()" subexpression, I would expect to have to escape double quotes
"$(`"hello`")"
But, there appears to be some special case so that isn't necessary.

The problem is that this special case only works as expected in trivial cases like the above.

Actual behavior

it is difficult to determine how to properly escape a string in a nested expression.
Sometimes backticks are not needed, but accepted.
Sometimes they are required.

Also, parans inside a nested string are interpreted as not inside a string - they interfere with paran outside of the subexpression.

Error details

Various, including:
You must provide a value expression following the '..' operator.
The string is missing the terminator: ".

Environment data

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

Visuals

image

image

image

VSCode
image

image
image

Metadata

Metadata

Assignees

No one assigned

    Labels

    Issue-BugIssue has been identified as a bug in the productResolution-Won't FixThe issue won't be fixed, possibly due to compatibility reason.WG-Enginecore PowerShell engine, interpreter, and runtimeWG-ReviewedA Working Group has reviewed this and made a recommendation

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions