From 931f073c743ace3f39e6d17cd116030a5db1eb15 Mon Sep 17 00:00:00 2001 From: kwkam Date: Mon, 30 Jul 2018 20:34:20 +0800 Subject: [PATCH 1/6] [Feature] Fix file completion with literal wildcard char *System.Management.Automation/engine/CommandCompletion/CompletionCompleters: Use WildcardPattern::Escape to escape completion text of filename. *System.Management.Automation/engine/regex: WildcardPattern::Escape should also escape "`" since WildcardPattern::Unescape would unescape it, and the matcher parse it as escape character. *System.Management.Automation/namespaces/LocationGlobber: Do not pass regex escaped string to WildcardPattern::Get. This fails tab completion when doing "./path/to/`[f". --- .../CommandCompletion/CompletionCompleters.cs | 20 ++++++------------- .../engine/regex.cs | 5 +++-- .../namespaces/LocationGlobber.cs | 8 ++------ .../TabCompletion/TabCompletion.Tests.ps1 | 3 +++ .../LanguageAndParser.TestFollowup.Tests.ps1 | 13 ++++++++++++ 5 files changed, 27 insertions(+), 22 deletions(-) diff --git a/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs b/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs index 47e2d74f9b9..34d991bd8d0 100644 --- a/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs +++ b/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs @@ -4303,6 +4303,12 @@ internal static IEnumerable CompleteFilename(CompletionContext if (CompletionRequiresQuotes(completionText, !useLiteralPath)) { var quoteInUse = quote == string.Empty ? "'" : quote; + + if (!useLiteralPath) + { + completionText = WildcardPattern.Escape(completionText); + } + if (quoteInUse == "'") { completionText = completionText.Replace("'", "''"); @@ -4315,20 +4321,6 @@ internal static IEnumerable CompleteFilename(CompletionContext completionText = completionText.Replace("$", "`$"); } - if (!useLiteralPath) - { - if (quoteInUse == "'") - { - completionText = completionText.Replace("[", "`["); - completionText = completionText.Replace("]", "`]"); - } - else - { - completionText = completionText.Replace("[", "``["); - completionText = completionText.Replace("]", "``]"); - } - } - completionText = quoteInUse + completionText + quoteInUse; } else if (quote != string.Empty) diff --git a/src/System.Management.Automation/engine/regex.cs b/src/System.Management.Automation/engine/regex.cs index a8039ab2415..01b3af8b053 100644 --- a/src/System.Management.Automation/engine/regex.cs +++ b/src/System.Management.Automation/engine/regex.cs @@ -204,9 +204,10 @@ internal static string Escape(string pattern, char[] charsNotToEscape) char ch = pattern[i]; // - // if it is a wildcard char, escape it + // if it is a wildcard char or escape char, escape it // - if (IsWildcardChar(ch) && !charsNotToEscape.Contains(ch)) + if ((IsWildcardChar(ch) || ch == escapeChar) && + !charsNotToEscape.Contains(ch)) { temp[tempIndex++] = escapeChar; } diff --git a/src/System.Management.Automation/namespaces/LocationGlobber.cs b/src/System.Management.Automation/namespaces/LocationGlobber.cs index ab586d49997..a60fc6683b0 100644 --- a/src/System.Management.Automation/namespaces/LocationGlobber.cs +++ b/src/System.Management.Automation/namespaces/LocationGlobber.cs @@ -3630,13 +3630,11 @@ private List GenerateNewPSPathsWithGlobLeaf( StringContainsGlobCharacters(leafElement) || isLastLeaf) { - string regexEscapedLeafElement = ConvertMshEscapeToRegexEscape(leafElement); - // Construct the glob filter WildcardPattern stringMatcher = WildcardPattern.Get( - regexEscapedLeafElement, + leafElement, WildcardOptions.IgnoreCase); // Construct the include filter @@ -4270,13 +4268,11 @@ internal List GenerateNewPathsWithGlobLeaf( (StringContainsGlobCharacters(leafElement) || isLastLeaf)) { - string regexEscapedLeafElement = ConvertMshEscapeToRegexEscape(leafElement); - // Construct the glob filter WildcardPattern stringMatcher = WildcardPattern.Get( - regexEscapedLeafElement, + leafElement, WildcardOptions.IgnoreCase); // Construct the include filter diff --git a/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 b/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 index 505b11d87eb..ae79fb62a3e 100644 --- a/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 +++ b/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 @@ -739,6 +739,9 @@ dir -Recurse ` @{ inputStr = "Get-Process >'.\My ``[Path``]\'"; expected = "'.${separator}My ``[Path``]${separator}test.ps1'" } @{ inputStr = "Get-Process >${tempDir}\My"; expected = "'${tempDir}${separator}My ``[Path``]'" } @{ inputStr = "Get-Process > '${tempDir}\My ``[Path``]\'"; expected = "'${tempDir}${separator}My ``[Path``]${separator}test.ps1'" } + @{ inputStr = "cd 'My ``["; expected = "'.${separator}My ``[Path``]'" } + @{ inputStr = "Get-Process > 'My ``["; expected = "'.${separator}My ``[Path``]'" } + @{ inputStr = "Get-Process > '${tempDir}\My ``["; expected = "'${tempDir}${separator}My ``[Path``]'" } ) Push-Location -Path $tempDir diff --git a/test/powershell/Language/Parser/LanguageAndParser.TestFollowup.Tests.ps1 b/test/powershell/Language/Parser/LanguageAndParser.TestFollowup.Tests.ps1 index da50b2e5541..51d526b3381 100644 --- a/test/powershell/Language/Parser/LanguageAndParser.TestFollowup.Tests.ps1 +++ b/test/powershell/Language/Parser/LanguageAndParser.TestFollowup.Tests.ps1 @@ -303,3 +303,16 @@ Describe "Hash expression with if statement as value" -Tags "CI" { $hash['h'] | Should -BeExactly 'h' } } + +Describe "WildcardPattern" -Tags "CI" { + It "Unescaping escaped string '' should get the original" -TestCases @( + @{inputStr = '*This'} + @{inputStr = 'Is?'} + @{inputStr = 'Real[ly]'} + @{inputStr = 'Ba`sic'} + @{inputStr = 'Test `[more`]?'} + ) { + param($inputStr) + [WildcardPattern]::Unescape([WildcardPattern]::Escape($inputStr)) | Should -BeExactly $inputStr + } +} From b8ce02f38e45ac3d8e0a492b32f0473c33febf98 Mon Sep 17 00:00:00 2001 From: kwkam Date: Sat, 1 Sep 2018 04:51:29 +0800 Subject: [PATCH 2/6] Do not use alias in TabCompletion test --- test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 b/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 index ae79fb62a3e..6b507d1d21d 100644 --- a/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 +++ b/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 @@ -739,7 +739,7 @@ dir -Recurse ` @{ inputStr = "Get-Process >'.\My ``[Path``]\'"; expected = "'.${separator}My ``[Path``]${separator}test.ps1'" } @{ inputStr = "Get-Process >${tempDir}\My"; expected = "'${tempDir}${separator}My ``[Path``]'" } @{ inputStr = "Get-Process > '${tempDir}\My ``[Path``]\'"; expected = "'${tempDir}${separator}My ``[Path``]${separator}test.ps1'" } - @{ inputStr = "cd 'My ``["; expected = "'.${separator}My ``[Path``]'" } + @{ inputStr = "Set-Location -Path 'My ``["; expected = "'.${separator}My ``[Path``]'" } @{ inputStr = "Get-Process > 'My ``["; expected = "'.${separator}My ``[Path``]'" } @{ inputStr = "Get-Process > '${tempDir}\My ``["; expected = "'${tempDir}${separator}My ``[Path``]'" } ) From 27cd7376adafe77f19885eaa6db6397fd517c76f Mon Sep 17 00:00:00 2001 From: kwkam Date: Sat, 1 Sep 2018 04:52:30 +0800 Subject: [PATCH 3/6] Also test the [WildcardPattern]::Escape function --- .../LanguageAndParser.TestFollowup.Tests.ps1 | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/test/powershell/Language/Parser/LanguageAndParser.TestFollowup.Tests.ps1 b/test/powershell/Language/Parser/LanguageAndParser.TestFollowup.Tests.ps1 index 51d526b3381..252d0308bcf 100644 --- a/test/powershell/Language/Parser/LanguageAndParser.TestFollowup.Tests.ps1 +++ b/test/powershell/Language/Parser/LanguageAndParser.TestFollowup.Tests.ps1 @@ -305,14 +305,16 @@ Describe "Hash expression with if statement as value" -Tags "CI" { } Describe "WildcardPattern" -Tags "CI" { - It "Unescaping escaped string '' should get the original" -TestCases @( - @{inputStr = '*This'} - @{inputStr = 'Is?'} - @{inputStr = 'Real[ly]'} - @{inputStr = 'Ba`sic'} - @{inputStr = 'Test `[more`]?'} + It "Unescaping '' which escaped from '' should get the original" -TestCases @( + @{inputStr = '*This'; escapedStr = '`*This'} + @{inputStr = 'Is?'; escapedStr = 'Is`?'} + @{inputStr = 'Real[ly]'; escapedStr = 'Real`[ly`]'} + @{inputStr = 'Ba`sic'; escapedStr = 'Ba``sic'} + @{inputStr = 'Test `[more`]?'; escapedStr = 'Test ```[more```]`?'} ) { - param($inputStr) - [WildcardPattern]::Unescape([WildcardPattern]::Escape($inputStr)) | Should -BeExactly $inputStr + param($inputStr, $escapedStr) + + [WildcardPattern]::Escape($inputStr) | Should -BeExactly $escapedStr + [WildcardPattern]::Unescape($escapedStr) | Should -BeExactly $inputStr } } From 40fd00ae4949e52f398ba4c0e86f9dc9e4b4d9a4 Mon Sep 17 00:00:00 2001 From: kwkam Date: Wed, 6 Mar 2019 23:14:29 +0800 Subject: [PATCH 4/6] Update for ExperimentalFeature --- .../ExperimentalFeature.cs | 3 + .../engine/regex.cs | 22 +++++-- .../LanguageAndParser.TestFollowup.Tests.ps1 | 15 ----- .../engine/Api/WildcardPattern.Tests.ps1 | 65 +++++++++++++++++++ test/tools/TestMetadata.json | 3 +- 5 files changed, 87 insertions(+), 21 deletions(-) create mode 100644 test/powershell/engine/Api/WildcardPattern.Tests.ps1 diff --git a/src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs b/src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs index 9fe90d336d1..fa0faaa8450 100644 --- a/src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs +++ b/src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs @@ -114,6 +114,9 @@ static ExperimentalFeature() new ExperimentalFeature( name: "PSCommandNotFoundSuggestion", description: "Recommend potential commands based on fuzzy search on a CommandNotFoundException"), + new ExperimentalFeature( + name: "PSWildcardEscapeEscape", + description: "Fix WildcardPattern API: escape the escape character"), }; EngineExperimentalFeatures = new ReadOnlyCollection(engineFeatures); diff --git a/src/System.Management.Automation/engine/regex.cs b/src/System.Management.Automation/engine/regex.cs index 37fa2608039..921888f9e9b 100644 --- a/src/System.Management.Automation/engine/regex.cs +++ b/src/System.Management.Automation/engine/regex.cs @@ -194,16 +194,28 @@ internal static string Escape(string pattern, char[] charsNotToEscape) char[] temp = new char[pattern.Length * 2 + 1]; int tempIndex = 0; + bool charNeedsEscaping = false; for (int i = 0; i < pattern.Length; i++) { char ch = pattern[i]; - // - // if it is a wildcard char or escape char, escape it - // - if ((IsWildcardChar(ch) || ch == escapeChar) && - !charsNotToEscape.Contains(ch)) + if (ExperimentalFeature.IsEnabled("PSWildcardEscapeEscape")) + { + // + // if it is a wildcard char or escape char, escape it + // + charNeedsEscaping = IsWildcardChar(ch) || ch == escapeChar; + } + else + { + // + // if it is a wildcard char, escape it + // + charNeedsEscaping = IsWildcardChar(ch); + } + + if (charNeedsEscaping && !charsNotToEscape.Contains(ch)) { temp[tempIndex++] = escapeChar; } diff --git a/test/powershell/Language/Parser/LanguageAndParser.TestFollowup.Tests.ps1 b/test/powershell/Language/Parser/LanguageAndParser.TestFollowup.Tests.ps1 index ac0d9c761f7..edc6a297be0 100644 --- a/test/powershell/Language/Parser/LanguageAndParser.TestFollowup.Tests.ps1 +++ b/test/powershell/Language/Parser/LanguageAndParser.TestFollowup.Tests.ps1 @@ -309,18 +309,3 @@ Describe "Hashtable is case insensitive" -Tag CI { sh -c 'LANG=C.UTF-8 pwsh -NoProfile -Command ''$h=@{p=1};$h.P''' | Should -Be 1 } } - -Describe "WildcardPattern" -Tags "CI" { - It "Unescaping '' which escaped from '' should get the original" -TestCases @( - @{inputStr = '*This'; escapedStr = '`*This'} - @{inputStr = 'Is?'; escapedStr = 'Is`?'} - @{inputStr = 'Real[ly]'; escapedStr = 'Real`[ly`]'} - @{inputStr = 'Ba`sic'; escapedStr = 'Ba``sic'} - @{inputStr = 'Test `[more`]?'; escapedStr = 'Test ```[more```]`?'} - ) { - param($inputStr, $escapedStr) - - [WildcardPattern]::Escape($inputStr) | Should -BeExactly $escapedStr - [WildcardPattern]::Unescape($escapedStr) | Should -BeExactly $inputStr - } -} diff --git a/test/powershell/engine/Api/WildcardPattern.Tests.ps1 b/test/powershell/engine/Api/WildcardPattern.Tests.ps1 new file mode 100644 index 00000000000..3b67f195af7 --- /dev/null +++ b/test/powershell/engine/Api/WildcardPattern.Tests.ps1 @@ -0,0 +1,65 @@ +Describe "WildcardPattern Escape - Experimental-Feature-Disabled" -Tags "CI" { + + BeforeAll { + $testName = 'PSWildcardEscapeEscape' + $skipTest = $EnabledExperimentalFeatures.Contains($testName) + + if ($skipTest) { + Write-Verbose "Test Suite Skipped. The test suite requires the experimental feature '$testName' to be disabled." -Verbose + $originalDefaultParameterValues = $PSDefaultParameterValues.Clone() + $PSDefaultParameterValues["it:skip"] = $true + } + } + + AfterAll { + if ($skipTest) { + $global:PSDefaultParameterValues = $originalDefaultParameterValues + } + } + + It "Unescaping '' which escaped from '' should get the original" -TestCases @( + @{inputStr = '*This'; escapedStr = '`*This'} + @{inputStr = 'Is?'; escapedStr = 'Is`?'} + @{inputStr = 'Real[ly]'; escapedStr = 'Real`[ly`]'} + @{inputStr = 'Ba`sic'; escapedStr = 'Ba`sic'} + @{inputStr = 'Test `[more`]?'; escapedStr = 'Test ``[more``]`?'} + ) { + param($inputStr, $escapedStr) + + [WildcardPattern]::Escape($inputStr) | Should -BeExactly $escapedStr + [WildcardPattern]::Unescape($escapedStr) | Should -BeExactly $inputStr + } +} + +Describe "WildcardPattern Escape - Experimental-Feature-Enabled" -Tags "CI" { + + BeforeAll { + $testName = 'PSWildcardEscapeEscape' + $skipTest = -not $EnabledExperimentalFeatures.Contains($testName) + + if ($skipTest) { + Write-Verbose "Test Suite Skipped. The test suite requires the experimental feature '$testName' to be enabled." -Verbose + $originalDefaultParameterValues = $PSDefaultParameterValues.Clone() + $PSDefaultParameterValues["it:skip"] = $true + } + } + + AfterAll { + if ($skipTest) { + $global:PSDefaultParameterValues = $originalDefaultParameterValues + } + } + + It "Unescaping '' which escaped from '' should get the original" -TestCases @( + @{inputStr = '*This'; escapedStr = '`*This'} + @{inputStr = 'Is?'; escapedStr = 'Is`?'} + @{inputStr = 'Real[ly]'; escapedStr = 'Real`[ly`]'} + @{inputStr = 'Ba`sic'; escapedStr = 'Ba``sic'} + @{inputStr = 'Test `[more`]?'; escapedStr = 'Test ```[more```]`?'} + ) { + param($inputStr, $escapedStr) + + [WildcardPattern]::Escape($inputStr) | Should -BeExactly $escapedStr + [WildcardPattern]::Unescape($escapedStr) | Should -BeExactly $inputStr + } +} diff --git a/test/tools/TestMetadata.json b/test/tools/TestMetadata.json index c49ab1758c6..4a1a88f58fa 100644 --- a/test/tools/TestMetadata.json +++ b/test/tools/TestMetadata.json @@ -1,5 +1,6 @@ { "ExperimentalFeatures": { - "ExpTest.FeatureOne": [ "test/powershell/engine/ExperimentalFeature/ExperimentalFeature.Basic.Tests.ps1" ] + "ExpTest.FeatureOne": [ "test/powershell/engine/ExperimentalFeature/ExperimentalFeature.Basic.Tests.ps1" ], + "PSWildcardEscapeEscape": [ "test/powershell/engine/Api/WildcardPattern.Tests.ps1" ] } } From 8b8c2ed6048cfe565e5ca70925e8d6c562858f53 Mon Sep 17 00:00:00 2001 From: kwkam Date: Sat, 22 Jun 2019 14:14:28 +0800 Subject: [PATCH 5/6] Add copyright header --- test/powershell/engine/Api/WildcardPattern.Tests.ps1 | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/powershell/engine/Api/WildcardPattern.Tests.ps1 b/test/powershell/engine/Api/WildcardPattern.Tests.ps1 index 3b67f195af7..69cb986647b 100644 --- a/test/powershell/engine/Api/WildcardPattern.Tests.ps1 +++ b/test/powershell/engine/Api/WildcardPattern.Tests.ps1 @@ -1,3 +1,5 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. Describe "WildcardPattern Escape - Experimental-Feature-Disabled" -Tags "CI" { BeforeAll { From 811b5740519082ed6986b15e80b7a25e3811d8e5 Mon Sep 17 00:00:00 2001 From: kwkam Date: Mon, 24 Jun 2019 19:39:40 +0800 Subject: [PATCH 6/6] Insert blank line after header Co-Authored-By: Ilya --- test/powershell/engine/Api/WildcardPattern.Tests.ps1 | 1 + 1 file changed, 1 insertion(+) diff --git a/test/powershell/engine/Api/WildcardPattern.Tests.ps1 b/test/powershell/engine/Api/WildcardPattern.Tests.ps1 index 69cb986647b..ef2688fed53 100644 --- a/test/powershell/engine/Api/WildcardPattern.Tests.ps1 +++ b/test/powershell/engine/Api/WildcardPattern.Tests.ps1 @@ -1,5 +1,6 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. + Describe "WildcardPattern Escape - Experimental-Feature-Disabled" -Tags "CI" { BeforeAll {