From 94d0361d592d221d7b645689df60a558d2fa8de0 Mon Sep 17 00:00:00 2001 From: Armaan Mcleod Date: Sun, 23 Mar 2025 00:04:54 +1100 Subject: [PATCH] Add `QuoteCompletionText` method to CompletionHelpers class (#25180) Unifies how quoting a given completion text works. --- .../CommandCompletion/CompletionCompleters.cs | 149 ++---------------- .../CommandCompletion/CompletionHelpers.cs | 33 +++- test/xUnit/csharp/test_CompletionHelpers.cs | 64 ++++++++ 3 files changed, 106 insertions(+), 140 deletions(-) diff --git a/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs b/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs index 2dd6807fa6..e278bf1e75 100644 --- a/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs +++ b/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs @@ -524,17 +524,7 @@ namespace System.Management.Automation + moduleInfo.ModuleType.ToString() + "\r\nPath: " + moduleInfo.Path; - if (CompletionHelpers.CompletionRequiresQuotes(completionText)) - { - var quoteInUse = quote == string.Empty ? "'" : quote; - if (quoteInUse == "'") - completionText = completionText.Replace("'", "''"); - completionText = quoteInUse + completionText + quoteInUse; - } - else - { - completionText = quote + completionText + quote; - } + completionText = CompletionHelpers.QuoteCompletionText(completionText, quote); result.Add(new CompletionResult(completionText, listItemText, CompletionResultType.ParameterValue, toolTip)); } @@ -1844,23 +1834,8 @@ namespace System.Management.Automation { string realEntry = entry; string completionText = entry; - if (quote == string.Empty) - { - if (CompletionHelpers.CompletionRequiresQuotes(entry)) - { - realEntry = CodeGeneration.EscapeSingleQuotedStringContent(entry); - completionText = "'" + realEntry + "'"; - } - } - else - { - if (quote.Equals("'", StringComparison.OrdinalIgnoreCase)) - { - realEntry = CodeGeneration.EscapeSingleQuotedStringContent(entry); - } - completionText = quote + realEntry + quote; - } + completionText = CompletionHelpers.QuoteCompletionText(completionText, quote); result.Add(new CompletionResult(completionText, entry, CompletionResultType.ParameterValue, entry)); } @@ -3220,17 +3195,7 @@ namespace System.Management.Automation var completionText = eventLog.Log.ToString(); var listItemText = completionText; - if (CompletionHelpers.CompletionRequiresQuotes(completionText)) - { - var quoteInUse = quote == string.Empty ? "'" : quote; - if (quoteInUse == "'") - completionText = completionText.Replace("'", "''"); - completionText = quoteInUse + completionText + quoteInUse; - } - else - { - completionText = quote + completionText + quote; - } + completionText = CompletionHelpers.QuoteCompletionText(completionText, quote); if (pattern.IsMatch(listItemText)) { @@ -3311,17 +3276,7 @@ namespace System.Management.Automation var completionText = psJob.Name; var listItemText = completionText; - if (CompletionHelpers.CompletionRequiresQuotes(completionText)) - { - var quoteInUse = quote == string.Empty ? "'" : quote; - if (quoteInUse == "'") - completionText = completionText.Replace("'", "''"); - completionText = quoteInUse + completionText + quoteInUse; - } - else - { - completionText = quote + completionText + quote; - } + completionText = CompletionHelpers.QuoteCompletionText(completionText, quote); result.Add(new CompletionResult(completionText, listItemText, CompletionResultType.ParameterValue, listItemText)); } @@ -3386,17 +3341,7 @@ namespace System.Management.Automation var completionText = psJob.Name; var listItemText = completionText; - if (CompletionHelpers.CompletionRequiresQuotes(completionText)) - { - var quoteInUse = quote == string.Empty ? "'" : quote; - if (quoteInUse == "'") - completionText = completionText.Replace("'", "''"); - completionText = quoteInUse + completionText + quoteInUse; - } - else - { - completionText = quote + completionText + quote; - } + completionText = CompletionHelpers.QuoteCompletionText(completionText, quote); result.Add(new CompletionResult(completionText, listItemText, CompletionResultType.ParameterValue, listItemText)); } @@ -3526,17 +3471,7 @@ namespace System.Management.Automation continue; uniqueSet.Add(completionText); - if (CompletionHelpers.CompletionRequiresQuotes(completionText)) - { - var quoteInUse = quote == string.Empty ? "'" : quote; - if (quoteInUse == "'") - completionText = completionText.Replace("'", "''"); - completionText = quoteInUse + completionText + quoteInUse; - } - else - { - completionText = quote + completionText + quote; - } + completionText = CompletionHelpers.QuoteCompletionText(completionText, quote); // on macOS, system processes names will be empty if PowerShell isn't run as `sudo` if (string.IsNullOrEmpty(listItemText)) @@ -3579,17 +3514,7 @@ namespace System.Management.Automation var completionText = providerInfo.Name; var listItemText = completionText; - if (CompletionHelpers.CompletionRequiresQuotes(completionText)) - { - var quoteInUse = quote == string.Empty ? "'" : quote; - if (quoteInUse == "'") - completionText = completionText.Replace("'", "''"); - completionText = quoteInUse + completionText + quoteInUse; - } - else - { - completionText = quote + completionText + quote; - } + completionText = CompletionHelpers.QuoteCompletionText(completionText, quote); result.Add(new CompletionResult(completionText, listItemText, CompletionResultType.ParameterValue, listItemText)); } @@ -3627,17 +3552,7 @@ namespace System.Management.Automation var completionText = driveInfo.Name; var listItemText = completionText; - if (CompletionHelpers.CompletionRequiresQuotes(completionText)) - { - var quoteInUse = quote == string.Empty ? "'" : quote; - if (quoteInUse == "'") - completionText = completionText.Replace("'", "''"); - completionText = quoteInUse + completionText + quoteInUse; - } - else - { - completionText = quote + completionText + quote; - } + completionText = CompletionHelpers.QuoteCompletionText(completionText, quote); result.Add(new CompletionResult(completionText, listItemText, CompletionResultType.ParameterValue, listItemText)); } @@ -3678,17 +3593,7 @@ namespace System.Management.Automation var completionText = serviceInfo.DisplayName; var listItemText = completionText; - if (CompletionHelpers.CompletionRequiresQuotes(completionText)) - { - var quoteInUse = quote == string.Empty ? "'" : quote; - if (quoteInUse == "'") - completionText = completionText.Replace("'", "''"); - completionText = quoteInUse + completionText + quoteInUse; - } - else - { - completionText = quote + completionText + quote; - } + completionText = CompletionHelpers.QuoteCompletionText(completionText, quote); result.Add(new CompletionResult(completionText, listItemText, CompletionResultType.ParameterValue, listItemText)); } @@ -3709,17 +3614,7 @@ namespace System.Management.Automation var completionText = serviceInfo.Name; var listItemText = completionText; - if (CompletionHelpers.CompletionRequiresQuotes(completionText)) - { - var quoteInUse = quote == string.Empty ? "'" : quote; - if (quoteInUse == "'") - completionText = completionText.Replace("'", "''"); - completionText = quoteInUse + completionText + quoteInUse; - } - else - { - completionText = quote + completionText + quote; - } + completionText = CompletionHelpers.QuoteCompletionText(completionText, quote); result.Add(new CompletionResult(completionText, listItemText, CompletionResultType.ParameterValue, listItemText)); } @@ -3815,17 +3710,7 @@ namespace System.Management.Automation var completionText = aliasInfo.Name; var listItemText = completionText; - if (CompletionHelpers.CompletionRequiresQuotes(completionText)) - { - var quoteInUse = quote == string.Empty ? "'" : quote; - if (quoteInUse == "'") - completionText = completionText.Replace("'", "''"); - completionText = quoteInUse + completionText + quoteInUse; - } - else - { - completionText = quote + completionText + quote; - } + completionText = CompletionHelpers.QuoteCompletionText(completionText, quote); result.Add(new CompletionResult(completionText, listItemText, CompletionResultType.ParameterValue, listItemText)); } @@ -3878,17 +3763,7 @@ namespace System.Management.Automation var completionText = trace.Name; var listItemText = completionText; - if (CompletionHelpers.CompletionRequiresQuotes(completionText)) - { - var quoteInUse = quote == string.Empty ? "'" : quote; - if (quoteInUse == "'") - completionText = completionText.Replace("'", "''"); - completionText = quoteInUse + completionText + quoteInUse; - } - else - { - completionText = quote + completionText + quote; - } + completionText = CompletionHelpers.QuoteCompletionText(completionText, quote); result.Add(new CompletionResult(completionText, listItemText, CompletionResultType.ParameterValue, listItemText)); } diff --git a/src/System.Management.Automation/engine/CommandCompletion/CompletionHelpers.cs b/src/System.Management.Automation/engine/CommandCompletion/CompletionHelpers.cs index 02303956dc..77bc966d7c 100644 --- a/src/System.Management.Automation/engine/CommandCompletion/CompletionHelpers.cs +++ b/src/System.Management.Automation/engine/CommandCompletion/CompletionHelpers.cs @@ -36,9 +36,7 @@ namespace System.Management.Automation { if (pattern.IsMatch(value)) { - string completionText = quote == string.Empty - ? value - : quote + value + quote; + string completionText = QuoteCompletionText(value, quote); string listItemText = value; @@ -140,5 +138,34 @@ namespace System.Management.Automation private static bool ContainsCharsToCheck(ReadOnlySpan text) => text.ContainsAny(s_defaultCharsToCheck); + + /// + /// Quotes a given completion text. + /// + /// + /// The text to be quoted. + /// + /// + /// The quote character to use for enclosing the text. Defaults to a single quote if not provided. + /// + /// + /// The quoted . + /// + internal static string QuoteCompletionText(string completionText, string quote) + { + if (!CompletionRequiresQuotes(completionText)) + { + return quote + completionText + quote; + } + + string quoteInUse = string.IsNullOrEmpty(quote) ? "'" : quote; + + if (quoteInUse == "'") + { + completionText = CodeGeneration.EscapeSingleQuotedStringContent(completionText); + } + + return quoteInUse + completionText + quoteInUse; + } } } diff --git a/test/xUnit/csharp/test_CompletionHelpers.cs b/test/xUnit/csharp/test_CompletionHelpers.cs index 540927f6b7..c91a478fd8 100644 --- a/test/xUnit/csharp/test_CompletionHelpers.cs +++ b/test/xUnit/csharp/test_CompletionHelpers.cs @@ -8,6 +8,70 @@ namespace PSTests.Parallel { public class CompletionHelpersTests { + [Theory] + [InlineData("", "'", "''")] + [InlineData("", "\"", "\"\"")] + [InlineData("'", "'", "''''")] + [InlineData("", "", "''")] + [InlineData("", null, "''")] + [InlineData("word", "'", "'word'")] + [InlineData("word", "\"", "\"word\"")] + [InlineData("word's", "'", "'word''s'")] + [InlineData("already 'quoted'", "'", "'already ''quoted'''")] + [InlineData("multiple 'quotes' in 'text'", "'", "'multiple ''quotes'' in ''text'''")] + [InlineData("\"word\"", "'", "'\"word\"'")] + [InlineData("'word'", "'", "''word''")] + [InlineData("word", "", "word")] + [InlineData("word", null, "word")] + [InlineData("\"word\"", "\"", "\"\"word\"\"")] + [InlineData("'word'", "\"", "\"'word'\"")] + [InlineData("word with space", "'", "'word with space'")] + [InlineData("word with space", "\"", "\"word with space\"")] + [InlineData("word\"with\"quotes", "'", "'word\"with\"quotes'")] + [InlineData("word'with'quotes", "\"", "\"word'with'quotes\"")] + [InlineData("while", "'", "'while'")] + [InlineData("while", "", "while")] + [InlineData("while", "\"", "\"while\"")] + [InlineData("$variable", "'", "'$variable'")] + [InlineData("$variable", "", "$variable")] + [InlineData("$variable", "\"", "\"$variable\"")] + [InlineData("key$word", "'", "'key$word'")] + [InlineData("key$word", "", "'key$word'")] + [InlineData("key$word", "\"", "\"key$word\"")] + public void TestQuoteCompletionText( + string completionText, + string quote, + string expected) + { + string result = CompletionHelpers.QuoteCompletionText(completionText, quote); + Assert.Equal(expected, result); + } + + [Theory] + [InlineData("normaltext", false)] + [InlineData("$variable", false)] + [InlineData("abc def", true)] + [InlineData("while", false)] // PowerShell keyword + [InlineData("key$word", true)] + [InlineData("abc`def", true)] + [InlineData("normal-text", false)] + [InlineData("\"doublequotes\"", false)] + [InlineData("'singlequotes'", false)] + [InlineData("normal 'text'", true)] + [InlineData("normal \"text\"", true)] + [InlineData("text with ' and \"", true)] + [InlineData("text\"with\"quotes", false)] + [InlineData("text'with'quotes", false)] + [InlineData("\"key$", true)] + [InlineData("\"", true)] + [InlineData("'", true)] + [InlineData("", true)] + public void TestCompletionRequiresQuotes(string completion, bool expected) + { + bool result = CompletionHelpers.CompletionRequiresQuotes(completion); + Assert.Equal(expected, result); + } + [Theory] [InlineData("", "", "")] [InlineData("\"", "", "\"")]