From 3c897e11dd838ea660b17c22b33884c3c8511d49 Mon Sep 17 00:00:00 2001 From: Clint Rutkas Date: Tue, 31 Mar 2026 22:53:34 -0700 Subject: [PATCH 1/2] Fix SYSLIB1045: Convert Regex to GeneratedRegex source generators Convert 115 Regex instances across 38 files to use [GeneratedRegex] source-generated regular expressions for compile-time code generation and improved performance. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Common.cs | 7 +- .../AdvancedPaste/Helpers/JsonHelper.cs | 49 +++++--- .../AdvancedPaste/Helpers/MarkdownHelper.cs | 17 ++- .../HostsUILib/Helpers/ValidationHelper.cs | 14 ++- .../Tests/ScreenRuler.UITests/TestHelper.cs | 12 +- .../App/Form/Settings/SetupPage2a.cs | 5 +- .../MouseWithoutBorders/App/Form/frmMatrix.cs | 5 +- .../PowerOCR/Helpers/OcrExtensions.cs | 9 +- .../PowerOCR/Helpers/StringHelpers.cs | 8 +- .../Models/BaseApplication.cs | 6 +- .../Sanitizer/FilenameMaskRuleProvider.cs | 19 +-- .../Programs/Win32Program.cs | 7 +- .../Helper/CalculateEngine.cs | 7 +- .../Helper/CalculateHelper.cs | 59 ++++----- .../Helper/NumberTranslator.cs | 11 +- .../Helpers/Analyzers/TextMetadataAnalyzer.cs | 20 ++- .../Helpers/AvailableResultsList.cs | 12 +- .../Helpers/TimeAndDateHelper.cs | 109 +++++++++++------ .../Controls/ColorPickerControl.xaml.cs | 17 ++- .../InputInterpreter.cs | 16 ++- .../SshConfigParser/SshConfig.cs | 13 +- .../SystemPath.cs | 7 +- .../Main.cs | 9 +- .../Sources/ShellAction.cs | 7 +- .../InferredProgramArgumentParser.cs | 7 +- .../Programs/Win32Program.cs | 7 +- .../UriHelper/ExtendedUriParser.cs | 26 ++-- .../CalculateEngine.cs | 7 +- .../CalculateHelper.cs | 67 +++++----- .../NumberTranslator.cs | 11 +- .../Helper/QueryHelper.cs | 7 +- .../Components/AvailableResultsList.cs | 12 +- .../Components/SearchController.cs | 10 +- .../Components/TimeAndDateHelper.cs | 115 ++++++++++++------ .../Extensions/IFileSystemItemExtensions.cs | 9 +- .../MarkdownPreviewHandlerControl.cs | 6 +- .../RegistryPreviewMainPage.Utilities.cs | 5 +- .../SettingsBackupAndRestoreUtils.cs | 19 ++- .../OOBE/Views/ScoobeReleaseNotesPage.xaml.cs | 28 ++--- 39 files changed, 504 insertions(+), 277 deletions(-) diff --git a/src/dsc/PowerToys.Settings.DSC.Schema.Generator/Common.cs b/src/dsc/PowerToys.Settings.DSC.Schema.Generator/Common.cs index 5bfaee5a5b88..11b3923c3f2f 100644 --- a/src/dsc/PowerToys.Settings.DSC.Schema.Generator/Common.cs +++ b/src/dsc/PowerToys.Settings.DSC.Schema.Generator/Common.cs @@ -12,13 +12,16 @@ namespace PowerToys.Settings.DSC.Schema; -internal sealed class Common +internal sealed partial class Common { private static string[] TypeParts(string name) { - return Regex.Split(name.ToLower(CultureInfo.CurrentCulture), @"(? word.Equals("Bool", StringComparison.OrdinalIgnoreCase) || word.Equals("Boolean", StringComparison.OrdinalIgnoreCase)); diff --git a/src/modules/AdvancedPaste/AdvancedPaste/Helpers/JsonHelper.cs b/src/modules/AdvancedPaste/AdvancedPaste/Helpers/JsonHelper.cs index 8329e15230e9..0cc8314850b2 100644 --- a/src/modules/AdvancedPaste/AdvancedPaste/Helpers/JsonHelper.cs +++ b/src/modules/AdvancedPaste/AdvancedPaste/Helpers/JsonHelper.cs @@ -16,23 +16,34 @@ namespace AdvancedPaste.Helpers { - internal static class JsonHelper + internal static partial class JsonHelper { - // Ini parts regex - private static readonly Regex IniSectionNameRegex = new Regex(@"^\[(.+)\]"); - private static readonly Regex IniValueLineRegex = new Regex(@"(.+?)\s*=\s*(.*)"); - - // List of supported CSV delimiters and Regex to detect separator property + // List of supported CSV delimiters private static readonly char[] CsvDelimArry = [',', ';', '\t']; - private static readonly Regex CsvSepIdentifierRegex = new Regex(@"^sep=(.)$", RegexOptions.IgnoreCase); // CSV: Split on every occurrence of the delimiter except if it is enclosed by " and ignore two " as escaped " private static readonly string CsvDelimSepRegexStr = @"(?=(?:[^""]*""[^""]*"")*(?![^""]*""))"; + // Ini parts regex + [GeneratedRegex(@"^\[(.+)\]")] + private static partial Regex IniSectionNameRegex(); + + [GeneratedRegex(@"(.+?)\s*=\s*(.*)")] + private static partial Regex IniValueLineRegex(); + + // Regex to detect separator property + [GeneratedRegex(@"^sep=(.)$", RegexOptions.IgnoreCase)] + private static partial Regex CsvSepIdentifierRegex(); + // CSV: Regex to remove/replace quotation marks - private static readonly Regex CsvRemoveSingleQuotationMarksRegex = new Regex(@"^""(?!"")|(? ToJsonFromXmlOrCsvAsync(DataPackageView clipb // Validate content as ini // (First line is a section name and second line is a section name or a key-value-pair. // For the second line we check both, in case the first ini section is empty.) - if (lines.Length >= 2 && IniSectionNameRegex.IsMatch(lines[0]) && - (IniSectionNameRegex.IsMatch(lines[1]) || IniValueLineRegex.IsMatch(lines[1]))) + if (lines.Length >= 2 && IniSectionNameRegex().IsMatch(lines[0]) && + (IniSectionNameRegex().IsMatch(lines[1]) || IniValueLineRegex().IsMatch(lines[1]))) { // Parse and convert Ini foreach (string line in lines) { - Match lineSectionNameCheck = IniSectionNameRegex.Match(line); - Match lineKeyValuePairCheck = IniValueLineRegex.Match(line); + Match lineSectionNameCheck = IniSectionNameRegex().Match(line); + Match lineKeyValuePairCheck = IniValueLineRegex().Match(line); if (lineSectionNameCheck.Success) { @@ -163,7 +174,7 @@ internal static async Task ToJsonFromXmlOrCsvAsync(DataPackageView clipb foreach (var line in lines) { // If line is separator property line, then skip it - if (CsvSepIdentifierRegex.IsMatch(line)) + if (CsvSepIdentifierRegex().IsMatch(line)) { continue; } @@ -222,7 +233,7 @@ private static void GetCsvDelimiter(in string[] csvLines, out char delimiter, ou if (csvLines.Length > 1) { // Try to select the delimiter based on the separator property. - Match matchChar = CsvSepIdentifierRegex.Match(csvLines[0]); + Match matchChar = CsvSepIdentifierRegex().Match(csvLines[0]); if (matchChar.Success) { // We can do matchChar[0] as the match only returns one character. @@ -273,15 +284,15 @@ private static void GetCsvDelimiter(in string[] csvLines, out char delimiter, ou private static string ReplaceQuotationMarksInCsvData(string str) { // Remove first and last single quotation mark (enclosing quotation marks) and remove quotation marks of an empty data set (""). - str = CsvRemoveSingleQuotationMarksRegex.Replace(str, string.Empty); + str = CsvRemoveSingleQuotationMarksRegex().Replace(str, string.Empty); // Remove first quotation mark if followed by pairs of quotation marks // and remove last quotation mark if precede by pairs of quotation marks. // (Removes enclosing quotation marks around the cell data for data like /"""abc"""/.) - str = CsvRemoveStartAndEndQuotationMarksRegex.Replace(str, string.Empty); + str = CsvRemoveStartAndEndQuotationMarksRegex().Replace(str, string.Empty); // Replace pairs of two quotation marks with a single quotation mark. (Escaped quotation marks.) - str = CsvReplaceDoubleQuotationMarksRegex.Replace(str, "\""); + str = CsvReplaceDoubleQuotationMarksRegex().Replace(str, "\""); return str; } diff --git a/src/modules/AdvancedPaste/AdvancedPaste/Helpers/MarkdownHelper.cs b/src/modules/AdvancedPaste/AdvancedPaste/Helpers/MarkdownHelper.cs index 697fb41034cb..8b03cf9b453e 100644 --- a/src/modules/AdvancedPaste/AdvancedPaste/Helpers/MarkdownHelper.cs +++ b/src/modules/AdvancedPaste/AdvancedPaste/Helpers/MarkdownHelper.cs @@ -13,8 +13,17 @@ namespace AdvancedPaste.Helpers { - internal static class MarkdownHelper + internal static partial class MarkdownHelper { + [GeneratedRegex(@"|")] + private static partial Regex FragmentCommentsRegex(); + + [GeneratedRegex(@"\s{2,}")] + private static partial Regex ExcessiveWhitespaceRegex(); + + [GeneratedRegex(@"[\r\n]+")] + private static partial Regex LineBreaksRegex(); + public static async Task ToMarkdownAsync(DataPackageView clipboardData) { Logger.LogTrace(); @@ -31,7 +40,7 @@ private static string CleanHtml(string html) Logger.LogTrace(); // Remove the "StartFragment" and "EndFragment" comments - html = Regex.Replace(html, @"|", string.Empty); + html = FragmentCommentsRegex().Replace(html, string.Empty); HtmlDocument document = new HtmlDocument(); document.LoadHtml(html); @@ -95,8 +104,8 @@ private static void CleanUpWhitespace(HtmlNode node) // Clean up line breaks and excessive whitespace if (node.NodeType == HtmlNodeType.Text) { - node.InnerHtml = Regex.Replace(node.InnerHtml, @"\s{2,}", " "); - node.InnerHtml = Regex.Replace(node.InnerHtml, @"[\r\n]+", string.Empty); + node.InnerHtml = ExcessiveWhitespaceRegex().Replace(node.InnerHtml, " "); + node.InnerHtml = LineBreaksRegex().Replace(node.InnerHtml, string.Empty); } else { diff --git a/src/modules/Hosts/HostsUILib/Helpers/ValidationHelper.cs b/src/modules/Hosts/HostsUILib/Helpers/ValidationHelper.cs index e0c70142ef9a..b529ea73c280 100644 --- a/src/modules/Hosts/HostsUILib/Helpers/ValidationHelper.cs +++ b/src/modules/Hosts/HostsUILib/Helpers/ValidationHelper.cs @@ -7,7 +7,7 @@ namespace HostsUILib.Helpers { - public static class ValidationHelper + public static partial class ValidationHelper { /// /// Determines whether the address is a valid IPv4 @@ -19,8 +19,7 @@ public static bool ValidIPv4(string address) return false; } - var regex = new Regex("^(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$"); - return regex.IsMatch(address); + return IPv4Regex().IsMatch(address); } /// @@ -33,8 +32,7 @@ public static bool ValidIPv6(string address) return false; } - var regex = new Regex("^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$"); - return regex.IsMatch(address); + return IPv6Regex().IsMatch(address); } /// @@ -64,5 +62,11 @@ public static bool ValidHosts(string hosts, bool validateHostsLength) return true; } + + [GeneratedRegex("^(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$")] + private static partial Regex IPv4Regex(); + + [GeneratedRegex("^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$")] + private static partial Regex IPv6Regex(); } } diff --git a/src/modules/MeasureTool/Tests/ScreenRuler.UITests/TestHelper.cs b/src/modules/MeasureTool/Tests/ScreenRuler.UITests/TestHelper.cs index c06a0a436f64..af0764e2fc4f 100644 --- a/src/modules/MeasureTool/Tests/ScreenRuler.UITests/TestHelper.cs +++ b/src/modules/MeasureTool/Tests/ScreenRuler.UITests/TestHelper.cs @@ -12,7 +12,7 @@ namespace ScreenRuler.UITests { - public static class TestHelper + public static partial class TestHelper { private static readonly string[] ShortcutSeparators = { " + ", "+", " " }; @@ -330,8 +330,8 @@ public static bool ValidateSpacingClipboardContent(string clipboardText, string return spacingType switch { - "Spacing" => Regex.IsMatch(clipboardText, @"\d+\s*[�x×]\s*\d+"), - "Horizontal Spacing" or "Vertical Spacing" => Regex.IsMatch(clipboardText, @"^\d+$"), + "Spacing" => SpacingPatternRegex().IsMatch(clipboardText), + "Horizontal Spacing" or "Vertical Spacing" => DigitsOnlyRegex().IsMatch(clipboardText), _ => false, }; } @@ -462,5 +462,11 @@ private static void ValidateClipboardResults(string testName) containsValidPattern, $"{testName}: Clipboard should contain valid spacing measurement, but contained: '{clipboardText}'"); } + + [GeneratedRegex(@"\d+\s*[�x×]\s*\d+")] + private static partial Regex SpacingPatternRegex(); + + [GeneratedRegex(@"^\d+$")] + private static partial Regex DigitsOnlyRegex(); } } diff --git a/src/modules/MouseWithoutBorders/App/Form/Settings/SetupPage2a.cs b/src/modules/MouseWithoutBorders/App/Form/Settings/SetupPage2a.cs index 5fcee5dc54b4..3f7412a844c6 100644 --- a/src/modules/MouseWithoutBorders/App/Form/Settings/SetupPage2a.cs +++ b/src/modules/MouseWithoutBorders/App/Form/Settings/SetupPage2a.cs @@ -89,7 +89,7 @@ private void LinkButtonClick(object sender, System.EventArgs e) { if (GetSecureKey() != SecurityCodeField.Text) { - Encryption.MyKey = Regex.Replace(SecurityCodeField.Text, @"\s+", string.Empty); + Encryption.MyKey = WhitespaceRegex().Replace(SecurityCodeField.Text, string.Empty); SecurityCode = Encryption.MyKey; } @@ -113,5 +113,8 @@ private void CollapseHelpButtonClick(object sender, System.EventArgs e) HelpLabel.Hide(); CollapseHelpButton.Hide(); } + + [GeneratedRegex(@"\s+")] + private static partial Regex WhitespaceRegex(); } } diff --git a/src/modules/MouseWithoutBorders/App/Form/frmMatrix.cs b/src/modules/MouseWithoutBorders/App/Form/frmMatrix.cs index 703ad8ef9163..71a9043ee848 100644 --- a/src/modules/MouseWithoutBorders/App/Form/frmMatrix.cs +++ b/src/modules/MouseWithoutBorders/App/Form/frmMatrix.cs @@ -72,7 +72,7 @@ private void ButtonOK_Click(object sender, EventArgs e) { buttonOK.Enabled = false; - if (!UpdateKey(Regex.Replace(textBoxEnc.Text, @"\s+", string.Empty))) + if (!UpdateKey(WhitespaceRegex().Replace(textBoxEnc.Text, string.Empty))) { buttonOK.Enabled = true; return; @@ -1233,5 +1233,8 @@ private void PaintMyLogo() g2.Dispose(); } #endif + + [GeneratedRegex(@"\s+")] + private static partial Regex WhitespaceRegex(); } } diff --git a/src/modules/PowerOCR/PowerOCR/Helpers/OcrExtensions.cs b/src/modules/PowerOCR/PowerOCR/Helpers/OcrExtensions.cs index baecdfc3b329..45544e96c246 100644 --- a/src/modules/PowerOCR/PowerOCR/Helpers/OcrExtensions.cs +++ b/src/modules/PowerOCR/PowerOCR/Helpers/OcrExtensions.cs @@ -20,7 +20,7 @@ namespace PowerOCR.Helpers { - internal static class OcrExtensions + internal static partial class OcrExtensions { public static void GetTextFromOcrLine(this OcrLine ocrLine, bool isSpaceJoiningOCRLang, StringBuilder text) { @@ -40,13 +40,11 @@ public static void GetTextFromOcrLine(this OcrLine ocrLine, bool isSpaceJoiningO bool isFirstWord = true; bool isPrevWordSpaceJoining = false; - Regex regexSpaceJoiningWord = new(@"(^[\p{L}-[\p{Lo}]]|\p{Nd}$)|.{2,}"); - foreach (OcrWord ocrWord in ocrLine.Words) { string wordString = ocrWord.Text; - bool isThisWordSpaceJoining = regexSpaceJoiningWord.IsMatch(wordString); + bool isThisWordSpaceJoining = SpaceJoiningWordRegex().IsMatch(wordString); if (isFirstWord || (!isThisWordSpaceJoining && !isPrevWordSpaceJoining)) { @@ -104,5 +102,8 @@ internal static async Task GetOcrResultFromImageAsync(Bitmap bmp, Lan OcrEngine ocrEngine = OcrEngine.TryCreateFromLanguage(language); return await ocrEngine.RecognizeAsync(softwareBmp); } + + [GeneratedRegex(@"(^[\p{L}-[\p{Lo}]]|\p{Nd}$)|.{2,}")] + private static partial Regex SpaceJoiningWordRegex(); } } diff --git a/src/modules/PowerOCR/PowerOCR/Helpers/StringHelpers.cs b/src/modules/PowerOCR/PowerOCR/Helpers/StringHelpers.cs index 92ac1becec53..7e25fd64d79c 100644 --- a/src/modules/PowerOCR/PowerOCR/Helpers/StringHelpers.cs +++ b/src/modules/PowerOCR/PowerOCR/Helpers/StringHelpers.cs @@ -8,7 +8,7 @@ namespace PowerOCR.Helpers; -internal static class StringHelpers +internal static partial class StringHelpers { public static string MakeStringSingleLine(this string textToEdit) { @@ -25,8 +25,7 @@ public static string MakeStringSingleLine(this string textToEdit) workingString.Replace('\n', ' '); workingString.Replace('\r', ' '); - Regex regex = new("[ ]{2,}"); - string temp = regex.Replace(workingString.ToString(), " "); + string temp = MultipleSpacesRegex().Replace(workingString.ToString(), " "); workingString.Clear(); workingString.Append(temp); if (workingString[0] == ' ') @@ -41,4 +40,7 @@ public static string MakeStringSingleLine(this string textToEdit) return workingString.ToString(); } + + [GeneratedRegex("[ ]{2,}")] + private static partial Regex MultipleSpacesRegex(); } diff --git a/src/modules/Workspaces/WorkspacesCsharpLibrary/Models/BaseApplication.cs b/src/modules/Workspaces/WorkspacesCsharpLibrary/Models/BaseApplication.cs index 897bd97de513..603033213349 100644 --- a/src/modules/Workspaces/WorkspacesCsharpLibrary/Models/BaseApplication.cs +++ b/src/modules/Workspaces/WorkspacesCsharpLibrary/Models/BaseApplication.cs @@ -192,8 +192,7 @@ public bool IsPackagedApp else { string appPath = AppPath.Replace("C:\\Program Files\\WindowsApps\\", string.Empty); - Regex packagedAppPathRegex = new Regex(@"(?[^_]*)_\d+.\d+.\d+.\d+_(:?x64|arm64)__(?[^\\]*)", RegexOptions.ExplicitCapture | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase); - Match match = packagedAppPathRegex.Match(appPath); + Match match = PackagedAppPathRegex().Match(appPath); _isPackagedApp = match.Success; if (match.Success) { @@ -213,5 +212,8 @@ public void Dispose() { GC.SuppressFinalize(this); } + + [GeneratedRegex(@"(?[^_]*)_\d+.\d+.\d+.\d+_(:?x64|arm64)__(?[^\\]*)", RegexOptions.ExplicitCapture | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase)] + private static partial Regex PackagedAppPathRegex(); } } diff --git a/src/modules/cmdpal/Microsoft.CmdPal.Common/Services/Sanitizer/FilenameMaskRuleProvider.cs b/src/modules/cmdpal/Microsoft.CmdPal.Common/Services/Sanitizer/FilenameMaskRuleProvider.cs index 600360db8890..f58ea9152b18 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.Common/Services/Sanitizer/FilenameMaskRuleProvider.cs +++ b/src/modules/cmdpal/Microsoft.CmdPal.Common/Services/Sanitizer/FilenameMaskRuleProvider.cs @@ -8,7 +8,7 @@ namespace Microsoft.CmdPal.Common.Services.Sanitizer; -internal sealed class FilenameMaskRuleProvider : ISanitizationRuleProvider +internal sealed partial class FilenameMaskRuleProvider : ISanitizationRuleProvider { private static readonly FrozenSet CommonFileStemExclusions = new[] { @@ -30,14 +30,7 @@ internal sealed class FilenameMaskRuleProvider : ISanitizationRuleProvider public IEnumerable GetRules() { - const string pattern = """ - (? - (?: [A-Za-z]: )? (?: [\\/][^\\/:*?""<>|\s]+ )+ # drive-rooted or UNC-like - | [^\\/:*?""<>|\s]+ (?: [\\/][^\\/:*?""<>|\s]+ )+ # relative with at least one sep - ) - """; - - var rx = new Regex(pattern, SanitizerDefaults.DefaultOptions | RegexOptions.IgnorePatternWhitespace, TimeSpan.FromMilliseconds(SanitizerDefaults.DefaultMatchTimeoutMs)); + var rx = FilePathRegex(); yield return new SanitizationRule(rx, MatchEvaluator, "Mask filename in any path"); yield break; @@ -138,4 +131,12 @@ private static bool IsVersionSegment(string file) return hasDot; } + + [GeneratedRegex(""" + (? + (?: [A-Za-z]: )? (?: [\\/][^\\/:*?""<>|\s]+ )+ # drive-rooted or UNC-like + | [^\\/:*?""<>|\s]+ (?: [\\/][^\\/:*?""<>|\s]+ )+ # relative with at least one sep + ) + """, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.IgnorePatternWhitespace)] + private static partial Regex FilePathRegex(); } diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/Programs/Win32Program.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/Programs/Win32Program.cs index ffd00ce7c881..8f4cf9a12088 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/Programs/Win32Program.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/Programs/Win32Program.cs @@ -22,7 +22,7 @@ namespace Microsoft.CmdPal.Ext.Apps.Programs; [Serializable] -public class Win32Program : IProgram +public partial class Win32Program : IProgram { public static readonly Win32Program InvalidProgram = new() { Valid = false, Enabled = false }; @@ -284,7 +284,8 @@ private static Win32Program CreateWin32Program(string path) } } - private static readonly Regex InternetShortcutURLPrefixes = new(@"^steam:\/\/(rungameid|run|open)\/|^com\.epicgames\.launcher:\/\/apps\/", RegexOptions.Compiled); + [GeneratedRegex(@"^steam:\/\/(rungameid|run|open)\/|^com\.epicgames\.launcher:\/\/apps\/")] + private static partial Regex InternetShortcutURLPrefixes(); // This function filters Internet Shortcut programs private static Win32Program InternetShortcutProgram(string path) @@ -313,7 +314,7 @@ private static Win32Program InternetShortcutProgram(string path) } // To filter out only those steam shortcuts which have 'run' or 'rungameid' as the hostname - if (InternetShortcutURLPrefixes.Match(urlPath).Success) + if (InternetShortcutURLPrefixes().Match(urlPath).Success) { validApp = true; } diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Calc/Helper/CalculateEngine.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Calc/Helper/CalculateEngine.cs index fea869a49701..ea0b2479fbed 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Calc/Helper/CalculateEngine.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Calc/Helper/CalculateEngine.cs @@ -10,7 +10,7 @@ namespace Microsoft.CmdPal.Ext.Calc.Helper; -public static class CalculateEngine +public static partial class CalculateEngine { private static readonly PropertySet _constants = new() { @@ -45,7 +45,7 @@ public static CalculateResult Interpret(ISettingsInterface settings, string inpu // check for division by zero // We check if the string contains a slash followed by space (optional) and zero. Whereas the zero must not be followed by a dot, comma, 'b', 'o' or 'x' as these indicate a number with decimal digits or a binary/octal/hexadecimal value respectively. The zero must also not be followed by other digits. - if (new Regex("\\/\\s*0(?!(?:[,\\.0-9]|[box]0*[1-9a-f]))", RegexOptions.IgnoreCase).Match(input).Success) + if (DivisionByZeroRegex().Match(input).Success) { error = Properties.Resources.calculator_division_by_zero; return default; @@ -134,4 +134,7 @@ public static decimal FormatMax15Digits(decimal value, CultureInfo cultureInfo) var rounded = Math.Round(value, maxDecimalDigits, MidpointRounding.AwayFromZero); return rounded / 1.000000000000000000000000000000000m; } + + [GeneratedRegex("\\/\\s*0(?!(?:[,\\.0-9]|[box]0*[1-9a-f]))", RegexOptions.IgnoreCase)] + private static partial Regex DivisionByZeroRegex(); } diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Calc/Helper/CalculateHelper.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Calc/Helper/CalculateHelper.cs index 0ad44bedd72f..2416ed0d4f8a 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Calc/Helper/CalculateHelper.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Calc/Helper/CalculateHelper.cs @@ -10,21 +10,7 @@ namespace Microsoft.CmdPal.Ext.Calc.Helper; public static partial class CalculateHelper { - private static readonly Regex RegValidExpressChar = new Regex( - @"^(" + - @"%|" + - @"ceil\s*\(|floor\s*\(|exp\s*\(|max\s*\(|min\s*\(|abs\s*\(|log(?:2|10)?\s*\(|ln\s*\(|sqrt\s*\(|pow\s*\(|" + - @"factorial\s*\(|sign\s*\(|round\s*\(|rand\s*\(\)|randi\s*\([^\)]|" + - @"sin\s*\(|cos\s*\(|tan\s*\(|arcsin\s*\(|arccos\s*\(|arctan\s*\(|" + - @"sinh\s*\(|cosh\s*\(|tanh\s*\(|arsinh\s*\(|arcosh\s*\(|artanh\s*\(|" + - @"rad\s*\(|deg\s*\(|grad\s*\(|" + /* trigonometry unit conversion macros */ - @"pi|" + - @"==|~=|&&|\|\||" + - @"((\d+(?:\.\d*)?|\.\d+)[eE](-?\d+))|" + /* expression from CheckScientificNotation between parenthesis */ - @"e|[0-9]|0[xX][0-9a-fA-F]+|0[bB][01]+|0[oO][0-7]+|[\+\-\*\/\^\., ""]|[\(\)\|\!\[\]]" + - @")+$", - RegexOptions.Compiled); - + // RegValidExpressChar is now a [GeneratedRegex] method at the bottom of the class. private const string DegToRad = "(pi / 180) * "; private const string DegToGrad = "(10 / 9) * "; private const string GradToRad = "(pi / 200) * "; @@ -127,7 +113,7 @@ public static bool InputValid(string input) return false; } - if (!RegValidExpressChar.IsMatch(input)) + if (!RegValidExpressCharRegex().IsMatch(input)) { return false; } @@ -186,7 +172,7 @@ private static string CheckNumberOrConstantThenParenthesisExpr(string input) do { input = output; - output = Regex.Replace(input, @"(\d+|pi|e)\s*(\()", m => + output = NumberOrConstantThenParenthesisExprRegex().Replace(input, m => { if (m.Index > 0 && char.IsLetter(input[m.Index - 1])) { @@ -211,7 +197,7 @@ private static string CheckNumberOrConstantThenFunc(string input) do { input = output; - output = Regex.Replace(input, @"(\d+|pi|e)\s*([a-zA-Z]+[0-9]*\s*\()", m => + output = NumberOrConstantThenFuncRegex().Replace(input, m => { if (input[m.Index] == 'e' && input[m.Index + 1] == 'x' && input[m.Index + 2] == 'p') { @@ -237,9 +223,7 @@ private static string CheckNumberOrConstantThenFunc(string input) */ private static string CheckParenthesisExprThenFunc(string input) { - var p = @"(\))\s*([a-zA-Z]+[0-9]*\s*\()"; - var r = "$1 * $2"; - return Regex.Replace(input, p, r); + return ParenthesisExprThenFuncRegex().Replace(input, "$1 * $2"); } /* @@ -248,9 +232,7 @@ private static string CheckParenthesisExprThenFunc(string input) */ private static string CheckParenthesisExprThenParenthesisExpr(string input) { - var p = @"(\))\s*(\()"; - var r = "$1 * $2"; - return Regex.Replace(input, p, r); + return ParenthesisExprThenParenthesisExprRegex().Replace(input, "$1 * $2"); } /* @@ -262,7 +244,7 @@ private static string CheckNumberThenConstant(string input) do { input = output; - output = Regex.Replace(input, @"(\d+)\s*(pi|e)", m => + output = NumberThenConstantRegex().Replace(input, m => { if (m.Index > 0 && char.IsLetter(input[m.Index - 1])) { @@ -286,7 +268,7 @@ private static string CheckConstantThenConstant(string input) do { input = output; - output = Regex.Replace(input, @"(pi|e)\s*(pi|e)", m => + output = ConstantThenConstantRegex().Replace(input, m => { if (m.Index > 0 && char.IsLetter(input[m.Index - 1])) { @@ -332,7 +314,7 @@ private static string ModifyTrigFunction(string input, string function, string m var index = 0; // Index for match to ensure that the same match is not found twice - Regex regex = new Regex(pattern); + Regex regex = new Regex(pattern); // Dynamic pattern: can't use GeneratedRegex Match match; while ((match = regex.Match(input, index)).Success) @@ -466,7 +448,7 @@ private static string ModifyMathFunction(string input, string function, string m { // Create the pattern to match the function, opening bracket, and any spaces in between var pattern = $@"{function}\s*\("; - return Regex.Replace(input, pattern, modification + "("); + return Regex.Replace(input, pattern, modification + "("); // Dynamic pattern: can't use GeneratedRegex } public static string ExpandTrigConversions(string input, CalculateEngine.TrigMode mode) @@ -532,6 +514,27 @@ private static int FindOpeningBracketIndexInFrontOfIndex(string input, int end) * e: Captures 'e' or 'E' * (?\d+): Captures an integer number (e.g. "-1" or "23") */ + [GeneratedRegex(@"^(%|ceil\s*\(|floor\s*\(|exp\s*\(|max\s*\(|min\s*\(|abs\s*\(|log(?:2|10)?\s*\(|ln\s*\(|sqrt\s*\(|pow\s*\(|factorial\s*\(|sign\s*\(|round\s*\(|rand\s*\(\)|randi\s*\([^\)]|sin\s*\(|cos\s*\(|tan\s*\(|arcsin\s*\(|arccos\s*\(|arctan\s*\(|sinh\s*\(|cosh\s*\(|tanh\s*\(|arsinh\s*\(|arcosh\s*\(|artanh\s*\(|rad\s*\(|deg\s*\(|grad\s*\(|pi|==|~=|&&|\|\||((\d+(?:\.\d*)?|\.\d+)[eE](-?\d+))|e|[0-9]|0[xX][0-9a-fA-F]+|0[bB][01]+|0[oO][0-7]+|[\+\-\*\/\^\., ""]|[\(\)\|\!\[\]])+$")] + private static partial Regex RegValidExpressCharRegex(); + + [GeneratedRegex(@"(\d+|pi|e)\s*(\()")] + private static partial Regex NumberOrConstantThenParenthesisExprRegex(); + + [GeneratedRegex(@"(\d+|pi|e)\s*([a-zA-Z]+[0-9]*\s*\()")] + private static partial Regex NumberOrConstantThenFuncRegex(); + + [GeneratedRegex(@"(\))\s*([a-zA-Z]+[0-9]*\s*\()")] + private static partial Regex ParenthesisExprThenFuncRegex(); + + [GeneratedRegex(@"(\))\s*(\()")] + private static partial Regex ParenthesisExprThenParenthesisExprRegex(); + + [GeneratedRegex(@"(\d+)\s*(pi|e)")] + private static partial Regex NumberThenConstantRegex(); + + [GeneratedRegex(@"(pi|e)\s*(pi|e)")] + private static partial Regex ConstantThenConstantRegex(); + [GeneratedRegex(@"(\d+(?:\.\d*)?|\.\d+)e(-?\d+)", RegexOptions.IgnoreCase, "en-US")] private static partial Regex CreateReplaceScientificNotationRegex(); } diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Calc/Helper/NumberTranslator.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Calc/Helper/NumberTranslator.cs index 34da2872cfca..ed68e863ad39 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Calc/Helper/NumberTranslator.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Calc/Helper/NumberTranslator.cs @@ -13,7 +13,7 @@ namespace Microsoft.CmdPal.Ext.Calc.Helper; /// /// Tries to convert all numbers in a text from one culture format to another. /// -public class NumberTranslator +public partial class NumberTranslator { private readonly CultureInfo sourceCulture; private readonly CultureInfo targetCulture; @@ -98,9 +98,7 @@ private static string Translate(string input, CultureInfo cultureFrom, CultureIn // Match numbers in hexadecimal (0x..), binary (0b..), or octal (0o..) format, // and convert them to decimal form for compatibility with ExprTk (which only supports decimal input). - var baseNumberRegex = new Regex(@"(0[xX][\da-fA-F]+|0[bB][0-9]+|0[oO][0-9]+)"); - - var tokens = baseNumberRegex.Split(input); + var tokens = BaseNumberRegex().Split(input); foreach (var token in tokens) { @@ -158,6 +156,9 @@ private static Regex GetSplitRegex(CultureInfo culture) var splitPattern = $"([0-9{Regex.Escape(culture.NumberFormat.NumberDecimalSeparator)}" + $"{Regex.Escape(groupSeparator)}]+)"; - return new Regex(splitPattern); + return new Regex(splitPattern); // Dynamic pattern from culture: can't use GeneratedRegex } + + [GeneratedRegex(@"(0[xX][\da-fA-F]+|0[bB][0-9]+|0[oO][0-9]+)")] + private static partial Regex BaseNumberRegex(); } diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Helpers/Analyzers/TextMetadataAnalyzer.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Helpers/Analyzers/TextMetadataAnalyzer.cs index 83992f6428ea..564a1092e912 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Helpers/Analyzers/TextMetadataAnalyzer.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.ClipboardHistory/Helpers/Analyzers/TextMetadataAnalyzer.cs @@ -27,9 +27,9 @@ public TextMetadata Analyze(string input) private LineEndingType DetectLineEnding(string text) { - var crlfCount = Regex.Matches(text, "\r\n").Count; - var lfCount = Regex.Matches(text, "(? 0 ? 1 : 0) + (lfCount > 0 ? 1 : 0) + (crCount > 0 ? 1 : 0); @@ -87,7 +87,7 @@ private int CountWords(string text) return 0; } - return Regex.Matches(text, @"\b\w+\b").Count; + return WordBoundaryRegex().Matches(text).Count; } private int CountSentences(string text) @@ -101,6 +101,18 @@ private int CountSentences(string text) return matches.Count > 0 ? matches.Count : (text.Trim().Length > 0 ? 1 : 0); } + [GeneratedRegex("\r\n")] + private static partial Regex CrlfRegex(); + + [GeneratedRegex("(? /// Returns a list with all available date time formats /// @@ -105,7 +111,7 @@ internal static List GetList(bool isKeywordSearch, ISettingsInt } // Get formatted date - var value = TimeAndDateHelper.ConvertToCustomFormat(dtObject, unixTimestamp, unixTimestampMilliseconds, weekOfYear, eraShort, Regex.Replace(formatSyntax, "^UTC:", string.Empty), firstWeekRule, firstDayOfTheWeek); + var value = TimeAndDateHelper.ConvertToCustomFormat(dtObject, unixTimestamp, unixTimestampMilliseconds, weekOfYear, eraShort, RegexUtcPrefix().Replace(formatSyntax, string.Empty), firstWeekRule, firstDayOfTheWeek); try { value = dtObject.ToString(value, CultureInfo.CurrentCulture); @@ -120,7 +126,7 @@ internal static List GetList(bool isKeywordSearch, ISettingsInt else { // Do not fail as we have custom format syntax. Instead fix backslashes. - value = Regex.Replace(value, @"(?True if yes and otherwise false internal static bool StringContainsCustomFormatSyntax(string str) { - return _regexCustomDateTimeFormats.IsMatch(str); + return RegexCustomDateTimeFormats().IsMatch(str); } /// diff --git a/src/modules/colorPicker/ColorPickerUI/Controls/ColorPickerControl.xaml.cs b/src/modules/colorPicker/ColorPickerUI/Controls/ColorPickerControl.xaml.cs index 9f65234c5ae7..382d12d7749d 100644 --- a/src/modules/colorPicker/ColorPickerUI/Controls/ColorPickerControl.xaml.cs +++ b/src/modules/colorPicker/ColorPickerUI/Controls/ColorPickerControl.xaml.cs @@ -286,9 +286,7 @@ private void HexCode_TextChanged(object sender, TextChangedEventArgs e) var newValue = (sender as System.Windows.Controls.TextBox).Text; // support hex with 3 and 6 characters and optional with hashtag - var reg = new Regex("^#?([0-9A-Fa-f]{3}){1,2}$"); - - if (!reg.IsMatch(newValue)) + if (!HexColorRegex().IsMatch(newValue)) { return; } @@ -342,7 +340,7 @@ private static string FormatHexColorString(string hexCodeText) if (hexCodeText.Length == 3 || hexCodeText.Length == 4) { // Hex with or without hashtag and three characters - return Regex.Replace(hexCodeText, "^#?([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])$", "#$1$1$2$2$3$3"); + return ShortHexColorRegex().Replace(hexCodeText, "#$1$1$2$2$3$3"); } else { @@ -358,7 +356,7 @@ private void HexCode_GotKeyboardFocus(object sender, KeyboardFocusChangedEventAr private void TextBox_PreviewTextInput(object sender, TextCompositionEventArgs e) { - e.Handled = !System.Text.RegularExpressions.Regex.IsMatch(e.Text, "^[0-9]+$"); + e.Handled = !DigitsOnlyRegex().IsMatch(e.Text); } private void RGBNumberBox_TextChanged(object sender, TextChangedEventArgs e) @@ -433,6 +431,15 @@ public static T GetChildOfType(DependencyObject depObj) return null; } + + [GeneratedRegex("^#?([0-9A-Fa-f]{3}){1,2}$")] + private static partial Regex HexColorRegex(); + + [GeneratedRegex("^#?([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])$")] + private static partial Regex ShortHexColorRegex(); + + [GeneratedRegex("^[0-9]+$")] + private static partial Regex DigitsOnlyRegex(); } #pragma warning disable SA1402 // File may only contain a single type diff --git a/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.UnitConverter/InputInterpreter.cs b/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.UnitConverter/InputInterpreter.cs index cbd1d36f4af8..f65ca434e9ea 100644 --- a/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.UnitConverter/InputInterpreter.cs +++ b/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.UnitConverter/InputInterpreter.cs @@ -13,13 +13,17 @@ namespace Community.PowerToys.Run.Plugin.UnitConverter { - public static class InputInterpreter + public static partial class InputInterpreter { - private static readonly string Pattern = @"(?<=\d)(?![,.\-])(?=[\D])|(?<=[\D])(? @@ -32,7 +36,7 @@ public static void InputSpaceInserter(ref string[] split) return; } - string[] parseInputWithoutSpace = Regex.Split(split[0], Pattern); + string[] parseInputWithoutSpace = SplitPattern().Split(split[0]); if (parseInputWithoutSpace.Length > 1) { @@ -261,8 +265,8 @@ public static void OunceHandler(ref string[] split, CultureInfo culture) public static void SquareHandler(ref string[] split) { - split[1] = Regex.Replace(split[1], "sq(s|μm|mm|cm|dm|m|km|mil|in|ft|yd|mi|nmi)", "$1²"); - split[3] = Regex.Replace(split[3], "sq(s|μm|mm|cm|dm|m|km|mil|in|ft|yd|mi|nmi)", "$1²"); + split[1] = SquareUnitRegex().Replace(split[1], "$1²"); + split[3] = SquareUnitRegex().Replace(split[3], "$1²"); } public static ConvertModel Parse(Query query) diff --git a/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.VSCodeWorkspaces/SshConfigParser/SshConfig.cs b/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.VSCodeWorkspaces/SshConfigParser/SshConfig.cs index 76c515cec8ad..ed9c8c66cc7d 100644 --- a/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.VSCodeWorkspaces/SshConfigParser/SshConfig.cs +++ b/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.VSCodeWorkspaces/SshConfigParser/SshConfig.cs @@ -9,10 +9,13 @@ namespace Community.PowerToys.Run.Plugin.VSCodeWorkspaces.SshConfigParser { - public class SshConfig + public partial class SshConfig { - private static readonly Regex _sshConfig = new Regex(@"^(\w[\s\S]*?\w)$(?=(?:\s+^\w|\z))", RegexOptions.Multiline); - private static readonly Regex _keyValue = new Regex(@"(\w+\s\S+)", RegexOptions.Multiline); + [GeneratedRegex(@"^(\w[\s\S]*?\w)$(?=(?:\s+^\w|\z))", RegexOptions.Multiline)] + private static partial Regex SshConfigRegex(); + + [GeneratedRegex(@"(\w+\s\S+)", RegexOptions.Multiline)] + private static partial Regex KeyValueRegex(); public static IEnumerable ParseFile(string path) { @@ -23,11 +26,11 @@ public static IEnumerable Parse(string str) { str = str.Replace("\r", string.Empty); var list = new List(); - foreach (Match match in _sshConfig.Matches(str)) + foreach (Match match in SshConfigRegex().Matches(str)) { var sshHost = new SshHost(); string content = match.Groups.Values.ToList()[0].Value; - foreach (Match match1 in _keyValue.Matches(content)) + foreach (Match match1 in KeyValueRegex().Matches(content)) { var split = match1.Value.Split(" "); var key = split[0]; diff --git a/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.VSCodeWorkspaces/SystemPath.cs b/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.VSCodeWorkspaces/SystemPath.cs index f937e5e9d470..0de043bad189 100644 --- a/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.VSCodeWorkspaces/SystemPath.cs +++ b/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.VSCodeWorkspaces/SystemPath.cs @@ -6,13 +6,14 @@ namespace Community.PowerToys.Run.Plugin.VSCodeWorkspaces { - internal sealed class SystemPath + internal sealed partial class SystemPath { - private static readonly Regex WindowsPath = new Regex(@"^([a-zA-Z]:)", RegexOptions.Compiled); + [GeneratedRegex(@"^([a-zA-Z]:)")] + private static partial Regex WindowsPath(); public static string RealPath(string path) { - if (WindowsPath.IsMatch(path)) + if (WindowsPath().IsMatch(path)) { string windowsPath = path.Replace("/", "\\"); return $"{windowsPath[0]}".ToUpperInvariant() + windowsPath.Remove(0, 1); diff --git a/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.WebSearch/Main.cs b/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.WebSearch/Main.cs index 2021b965dfd3..3e151ad99147 100644 --- a/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.WebSearch/Main.cs +++ b/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.WebSearch/Main.cs @@ -9,6 +9,7 @@ using System.Text; using System.Windows.Controls; +using System.Text.RegularExpressions; using ManagedCommon; using Microsoft.PowerToys.Settings.UI.Library; using Wox.Infrastructure; @@ -19,7 +20,7 @@ namespace Community.PowerToys.Run.Plugin.WebSearch { - public class Main : IPlugin, IPluginI18n, IContextMenu, ISettingProvider, IReloadable, IDisposable + public partial class Main : IPlugin, IPluginI18n, IContextMenu, ISettingProvider, IReloadable, IDisposable { // Should only be set in Init() private Action onPluginError; @@ -141,8 +142,7 @@ bool IsURI(string input) && !input.StartsWith("http", StringComparison.OrdinalIgnoreCase) && !input.Contains('/', StringComparison.OrdinalIgnoreCase) && !input.All(char.IsDigit) - && System.Text.RegularExpressions.Regex.IsMatch(input, @"^([a-z][a-z0-9+\-.]*):")) - { + && UriSchemeRegex().IsMatch(input)) { return true; } @@ -252,5 +252,8 @@ protected virtual void Dispose(bool disposing) _disposed = true; } } + + [GeneratedRegex(@"^([a-z][a-z0-9+\-.]*):")] + private static partial Regex UriSchemeRegex(); } } diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.Folder/Sources/ShellAction.cs b/src/modules/launcher/Plugins/Microsoft.Plugin.Folder/Sources/ShellAction.cs index f059f06683b4..064c453020fe 100644 --- a/src/modules/launcher/Plugins/Microsoft.Plugin.Folder/Sources/ShellAction.cs +++ b/src/modules/launcher/Plugins/Microsoft.Plugin.Folder/Sources/ShellAction.cs @@ -11,7 +11,7 @@ namespace Microsoft.Plugin.Folder.Sources { - public class ShellAction : IShellAction + public partial class ShellAction : IShellAction { public bool Execute(string sanitizedPath, IPublicAPI contextApi) { @@ -29,7 +29,7 @@ public bool ExecuteSanitized(string search, IPublicAPI contextApi) private static string SanitizedPath(string search) { - var sanitizedPath = Regex.Replace(search, @"[\/\\]+", "\\"); + var sanitizedPath = PathSeparatorRegex().Replace(search, "\\"); // A network path must start with \\ // Using Ordinal since this is internal and used with a symbol @@ -51,5 +51,8 @@ private static bool OpenFileOrFolder(string path, IPublicAPI contextApi) return true; } + + [GeneratedRegex(@"[\/\\]+")] + private static partial Regex PathSeparatorRegex(); } } diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.Program/ProgramArgumentParser/InferredProgramArgumentParser.cs b/src/modules/launcher/Plugins/Microsoft.Plugin.Program/ProgramArgumentParser/InferredProgramArgumentParser.cs index 2a96db1c8cad..9ca3b6e6297f 100644 --- a/src/modules/launcher/Plugins/Microsoft.Plugin.Program/ProgramArgumentParser/InferredProgramArgumentParser.cs +++ b/src/modules/launcher/Plugins/Microsoft.Plugin.Program/ProgramArgumentParser/InferredProgramArgumentParser.cs @@ -9,9 +9,10 @@ namespace Microsoft.Plugin.Program { - public class InferredProgramArgumentParser : IProgramArgumentParser + public partial class InferredProgramArgumentParser : IProgramArgumentParser { - private static readonly Regex ArgumentPrefixRegex = new Regex("^(-|--|/)[a-zA-Z]+", RegexOptions.Compiled); + [GeneratedRegex("^(-|--|/)[a-zA-Z]+")] + private static partial Regex ArgumentPrefixRegex(); public bool Enabled { get; } = true; @@ -24,7 +25,7 @@ public bool TryParse(Query query, out string program, out string programArgument { for (var i = 1; i < query.Terms.Count; i++) { - if (!ArgumentPrefixRegex.IsMatch(query.Terms[i])) + if (!ArgumentPrefixRegex().IsMatch(query.Terms[i])) { continue; } diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.Program/Programs/Win32Program.cs b/src/modules/launcher/Plugins/Microsoft.Plugin.Program/Programs/Win32Program.cs index d16d0e32ca41..a14d86ddf346 100644 --- a/src/modules/launcher/Plugins/Microsoft.Plugin.Program/Programs/Win32Program.cs +++ b/src/modules/launcher/Plugins/Microsoft.Plugin.Program/Programs/Win32Program.cs @@ -28,7 +28,7 @@ namespace Microsoft.Plugin.Program.Programs { [Serializable] - public class Win32Program : IProgram + public partial class Win32Program : IProgram { public static readonly Win32Program InvalidProgram = new Win32Program { Valid = false, Enabled = false }; @@ -420,7 +420,8 @@ private static Win32Program CreateWin32Program(string path) } } - private static readonly Regex InternetShortcutURLPrefixes = new Regex(@"^steam:\/\/(rungameid|run|open)\/|^com\.epicgames\.launcher:\/\/apps\/", RegexOptions.Compiled); + [GeneratedRegex(@"^steam:\/\/(rungameid|run|open)\/|^com\.epicgames\.launcher:\/\/apps\/")] + private static partial Regex InternetShortcutURLPrefixes(); // This function filters Internet Shortcut programs private static Win32Program InternetShortcutProgram(string path) @@ -450,7 +451,7 @@ private static Win32Program InternetShortcutProgram(string path) } // To filter out only those steam shortcuts which have 'run' or 'rungameid' as the hostname - if (InternetShortcutURLPrefixes.Match(urlPath).Success) + if (InternetShortcutURLPrefixes().Match(urlPath).Success) { validApp = true; } diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/UriHelper/ExtendedUriParser.cs b/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/UriHelper/ExtendedUriParser.cs index 7ef13efb1ec4..4af002eeaf66 100644 --- a/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/UriHelper/ExtendedUriParser.cs +++ b/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/UriHelper/ExtendedUriParser.cs @@ -10,7 +10,7 @@ namespace Microsoft.Plugin.Uri.UriHelper { - public class ExtendedUriParser : IUriParser + public partial class ExtendedUriParser : IUriParser { // When updating this method, also update the local method IsUri() in Community.PowerToys.Run.Plugin.WebSearch.Main.Query public bool TryParse(string input, out System.Uri webUri, out System.Uri systemUri) @@ -26,12 +26,11 @@ public bool TryParse(string input, out System.Uri webUri, out System.Uri systemU // Handling URL with only scheme, typically mailto or application uri. // Do nothing, return the result without urlBuilder // And check if scheme match RFC3986 (issue #15035) - const string schemeRegex = @"^([a-z][a-z0-9+\-.]*):"; if (input.EndsWith(":", StringComparison.OrdinalIgnoreCase) && !input.StartsWith("http", StringComparison.OrdinalIgnoreCase) && !input.Contains('/', StringComparison.OrdinalIgnoreCase) && !input.All(char.IsDigit) - && Regex.IsMatch(input, schemeRegex)) + && SchemeRegex().IsMatch(input)) { systemUri = new System.Uri(input); return true; @@ -50,8 +49,6 @@ public bool TryParse(string input, out System.Uri webUri, out System.Uri systemU try { - string isDomainPortRegex = @"^[\w\.]+:\d+"; - string isIPv6PortRegex = @"^\[([\w:]+:+)+[\w]+\]:\d+"; var urlBuilder = new UriBuilder(input); urlBuilder.Port = urlBuilder.Uri.IsDefaultPort ? -1 : urlBuilder.Port; @@ -59,8 +56,8 @@ public bool TryParse(string input, out System.Uri webUri, out System.Uri systemU { urlBuilder.Scheme = System.Uri.UriSchemeHttp; } - else if (Regex.IsMatch(input, isDomainPortRegex) || - Regex.IsMatch(input, isIPv6PortRegex)) + else if (DomainPortRegex().IsMatch(input) || + IPv6PortRegex().IsMatch(input)) { var secondUrlBuilder = urlBuilder; @@ -80,8 +77,7 @@ public bool TryParse(string input, out System.Uri webUri, out System.Uri systemU // The catch ensures it will at least still try to return a systemUri } - string singleLabelRegex = @"[\.:]+|^http$|^https$|^localhost$"; - systemUri = Regex.IsMatch(urlBuilder.Host, singleLabelRegex) ? null : secondUrlBuilder.Uri; + systemUri = SingleLabelRegex().IsMatch(urlBuilder.Host) ? null : secondUrlBuilder.Uri; } else if (input.Contains(':', StringComparison.OrdinalIgnoreCase) && !input.StartsWith("http", StringComparison.OrdinalIgnoreCase) && @@ -108,5 +104,17 @@ public bool TryParse(string input, out System.Uri webUri, out System.Uri systemU return false; } } + + [GeneratedRegex(@"^([a-z][a-z0-9+\-.]*):")] + private static partial Regex SchemeRegex(); + + [GeneratedRegex(@"^[\w\.]+:\d+")] + private static partial Regex DomainPortRegex(); + + [GeneratedRegex(@"^\[([\w:]+:+)+[\w]+\]:\d+")] + private static partial Regex IPv6PortRegex(); + + [GeneratedRegex(@"[\.:]+|^http$|^https$|^localhost$")] + private static partial Regex SingleLabelRegex(); } } diff --git a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Calculator/CalculateEngine.cs b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Calculator/CalculateEngine.cs index ef7e84fbd84a..c47aa238a846 100644 --- a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Calculator/CalculateEngine.cs +++ b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Calculator/CalculateEngine.cs @@ -11,7 +11,7 @@ namespace Microsoft.PowerToys.Run.Plugin.Calculator { - public class CalculateEngine + public partial class CalculateEngine { private readonly Engine _magesEngine = new Engine(new Configuration { @@ -45,7 +45,7 @@ public CalculateResult Interpret(string input, CultureInfo cultureInfo, out stri // check for division by zero // We check if the string contains a slash followed by space (optional) and zero. Whereas the zero must not be followed by a dot, comma, 'b', 'o' or 'x' as these indicate a number with decimal digits or a binary/octal/hexadecimal value respectively. The zero must also not be followed by other digits. - if (new Regex("\\/\\s*0(?!(?:[,\\.0-9]|[box]0*[1-9a-f]))", RegexOptions.IgnoreCase).Match(input).Success) + if (DivisionByZeroRegex().Match(input).Success) { error = Properties.Resources.wox_plugin_calculator_division_by_zero; return default; @@ -124,5 +124,8 @@ private static dynamic TransformResult(object result) return result; } + + [GeneratedRegex("\\/\\s*0(?!(?:[,\\.0-9]|[box]0*[1-9a-f]))", RegexOptions.IgnoreCase)] + private static partial Regex DivisionByZeroRegex(); } } diff --git a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Calculator/CalculateHelper.cs b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Calculator/CalculateHelper.cs index 73a1435eed57..e3d5c65177ed 100644 --- a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Calculator/CalculateHelper.cs +++ b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Calculator/CalculateHelper.cs @@ -10,23 +10,9 @@ namespace Microsoft.PowerToys.Run.Plugin.Calculator { - public static class CalculateHelper + public static partial class CalculateHelper { - private static readonly Regex RegValidExpressChar = new Regex( - @"^(" + - @"%|" + - @"ceil\s*\(|floor\s*\(|exp\s*\(|max\s*\(|min\s*\(|abs\s*\(|log(?:2|10)?\s*\(|ln\s*\(|sqrt\s*\(|pow\s*\(|" + - @"factorial\s*\(|sign\s*\(|round\s*\(|rand\s*\(\)|randi\s*\([^\)]|" + - @"sin\s*\(|cos\s*\(|tan\s*\(|arcsin\s*\(|arccos\s*\(|arctan\s*\(|" + - @"sinh\s*\(|cosh\s*\(|tanh\s*\(|arsinh\s*\(|arcosh\s*\(|artanh\s*\(|" + - @"rad\s*\(|deg\s*\(|grad\s*\(|" + /* trigonometry unit conversion macros */ - @"pi|" + - @"==|~=|&&|\|\||" + - @"((-?(\d+(\.\d*)?)|-?(\.\d+))[Ee](-?\d+))|" + /* expression from CheckScientificNotation between parenthesis */ - @"e|[0-9]|0[xX][0-9a-fA-F]+|0[bB][01]+|0[oO][0-7]+|[\+\-\*\/\^\., ""]|[\(\)\|\!\[\]]" + - @")+$", - RegexOptions.Compiled); - + // RegValidExpressChar is now a [GeneratedRegex] method at the bottom of the class. private const string DegToRad = "(pi / 180) * "; private const string DegToGrad = "(10 / 9) * "; private const string GradToRad = "(pi / 200) * "; @@ -41,7 +27,7 @@ public static bool InputValid(string input) throw new ArgumentNullException(paramName: nameof(input)); } - if (!RegValidExpressChar.IsMatch(input)) + if (!RegValidExpressCharRegex().IsMatch(input)) { return false; } @@ -85,8 +71,7 @@ private static string CheckScientificNotation(string input) * e: Captures 'e' or 'E' * (-?\d+): Captures an integer number (e.g. "-1" or "23") */ - var p = @"(-?(\d+(\.\d*)?)|-?(\.\d+))e(-?\d+)"; - return Regex.Replace(input, p, "($1 * 10^($5))", RegexOptions.IgnoreCase); + return ScientificNotationRegex().Replace(input, "($1 * 10^($5))"); } /* @@ -99,7 +84,7 @@ private static string CheckNumberOrConstantThenParenthesisExpr(string input) do { input = output; - output = Regex.Replace(input, @"(\d+|pi|e)\s*(\()", m => + output = NumberOrConstantThenParenthesisExprRegex().Replace(input, m => { if (m.Index > 0 && char.IsLetter(input[m.Index - 1])) { @@ -124,7 +109,7 @@ private static string CheckNumberOrConstantThenFunc(string input) do { input = output; - output = Regex.Replace(input, @"(\d+|pi|e)\s*([a-zA-Z]+[0-9]*\s*\()", m => + output = NumberOrConstantThenFuncRegex().Replace(input, m => { if (input[m.Index] == 'e' && input[m.Index + 1] == 'x' && input[m.Index + 2] == 'p') { @@ -150,9 +135,7 @@ private static string CheckNumberOrConstantThenFunc(string input) */ private static string CheckParenthesisExprThenFunc(string input) { - var p = @"(\))\s*([a-zA-Z]+[0-9]*\s*\()"; - var r = "$1 * $2"; - return Regex.Replace(input, p, r); + return ParenthesisExprThenFuncRegex().Replace(input, "$1 * $2"); } /* @@ -161,9 +144,7 @@ private static string CheckParenthesisExprThenFunc(string input) */ private static string CheckParenthesisExprThenParenthesisExpr(string input) { - var p = @"(\))\s*(\()"; - var r = "$1 * $2"; - return Regex.Replace(input, p, r); + return ParenthesisExprThenParenthesisExprRegex().Replace(input, "$1 * $2"); } /* @@ -175,7 +156,7 @@ private static string CheckNumberThenConstant(string input) do { input = output; - output = Regex.Replace(input, @"(\d+)\s*(pi|e)", m => + output = NumberThenConstantRegex().Replace(input, m => { if (m.Index > 0 && char.IsLetter(input[m.Index - 1])) { @@ -199,7 +180,7 @@ private static string CheckConstantThenConstant(string input) do { input = output; - output = Regex.Replace(input, @"(pi|e)\s*(pi|e)", m => + output = ConstantThenConstantRegex().Replace(input, m => { if (m.Index > 0 && char.IsLetter(input[m.Index - 1])) { @@ -245,7 +226,7 @@ private static string ModifyTrigFunction(string input, string function, string m int index = 0; // Index for match to ensure that the same match is not found twice - Regex regex = new Regex(pattern); + Regex regex = new Regex(pattern); // Dynamic pattern: can't use GeneratedRegex Match match; while ((match = regex.Match(input, index)).Success) @@ -299,7 +280,7 @@ private static string ModifyMathFunction(string input, string function, string m { // Create the pattern to match the function, opening bracket, and any spaces in between string pattern = $@"{function}\s*\("; - return Regex.Replace(input, pattern, modification + "("); + return Regex.Replace(input, pattern, modification + "("); // Dynamic pattern: can't use GeneratedRegex } public static string ExpandTrigConversions(string input, TrigMode mode) @@ -328,5 +309,29 @@ public static string ExpandTrigConversions(string input, TrigMode mode) return modifiedInput; } + + [GeneratedRegex(@"^(%|ceil\s*\(|floor\s*\(|exp\s*\(|max\s*\(|min\s*\(|abs\s*\(|log(?:2|10)?\s*\(|ln\s*\(|sqrt\s*\(|pow\s*\(|factorial\s*\(|sign\s*\(|round\s*\(|rand\s*\(\)|randi\s*\([^\)]|sin\s*\(|cos\s*\(|tan\s*\(|arcsin\s*\(|arccos\s*\(|arctan\s*\(|sinh\s*\(|cosh\s*\(|tanh\s*\(|arsinh\s*\(|arcosh\s*\(|artanh\s*\(|rad\s*\(|deg\s*\(|grad\s*\(|pi|==|~=|&&|\|\||((-?(\d+(\.\d*)?)|-?(\.\d+))[Ee](-?\d+))|e|[0-9]|0[xX][0-9a-fA-F]+|0[bB][01]+|0[oO][0-7]+|[\+\-\*\/\^\., ""]|[\(\)\|\!\[\]])+$")] + private static partial Regex RegValidExpressCharRegex(); + + [GeneratedRegex(@"(-?(\d+(\.\d*)?)|-?(\.\d+))e(-?\d+)", RegexOptions.IgnoreCase)] + private static partial Regex ScientificNotationRegex(); + + [GeneratedRegex(@"(\d+|pi|e)\s*(\()")] + private static partial Regex NumberOrConstantThenParenthesisExprRegex(); + + [GeneratedRegex(@"(\d+|pi|e)\s*([a-zA-Z]+[0-9]*\s*\()")] + private static partial Regex NumberOrConstantThenFuncRegex(); + + [GeneratedRegex(@"(\))\s*([a-zA-Z]+[0-9]*\s*\()")] + private static partial Regex ParenthesisExprThenFuncRegex(); + + [GeneratedRegex(@"(\))\s*(\()")] + private static partial Regex ParenthesisExprThenParenthesisExprRegex(); + + [GeneratedRegex(@"(\d+)\s*(pi|e)")] + private static partial Regex NumberThenConstantRegex(); + + [GeneratedRegex(@"(pi|e)\s*(pi|e)")] + private static partial Regex ConstantThenConstantRegex(); } } diff --git a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Calculator/NumberTranslator.cs b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Calculator/NumberTranslator.cs index 946198e1227b..bb67e25b03e6 100644 --- a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Calculator/NumberTranslator.cs +++ b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Calculator/NumberTranslator.cs @@ -12,7 +12,7 @@ namespace Microsoft.PowerToys.Run.Plugin.Calculator /// /// Tries to convert all numbers in a text from one culture format to another. /// - public class NumberTranslator + public partial class NumberTranslator { private readonly CultureInfo sourceCulture; private readonly CultureInfo targetCulture; @@ -66,9 +66,7 @@ public string TranslateBack(string input) private static string Translate(string input, CultureInfo cultureFrom, CultureInfo cultureTo, Regex splitRegex) { var outputBuilder = new StringBuilder(); - var hexRegex = new Regex(@"(?:(0x[\da-fA-F]+))"); - - string[] hexTokens = hexRegex.Split(input); + string[] hexTokens = HexNumberRegex().Split(input); foreach (string hexToken in hexTokens) { @@ -139,7 +137,10 @@ private static Regex GetSplitRegex(CultureInfo culture) } splitPattern += ")+)"; - return new Regex(splitPattern); + return new Regex(splitPattern); // Dynamic pattern from culture: can't use GeneratedRegex } + + [GeneratedRegex(@"(?:(0x[\da-fA-F]+))")] + private static partial Regex HexNumberRegex(); } } diff --git a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Registry/Helper/QueryHelper.cs b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Registry/Helper/QueryHelper.cs index ee59f96fde44..80b64358648d 100644 --- a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Registry/Helper/QueryHelper.cs +++ b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Registry/Helper/QueryHelper.cs @@ -14,7 +14,7 @@ namespace Microsoft.PowerToys.Run.Plugin.Registry.Helper /// /// Helper class to easier work with queries /// - internal static class QueryHelper + internal static partial class QueryHelper { /// /// The character to distinguish if the search query contain multiple parts (typically "\\") @@ -41,7 +41,7 @@ internal static class QueryHelper /// A string replacing all the front-slashes with back-slashes private static string SanitizeQuery(in string query) { - var sanitizedQuery = Regex.Replace(query, @"/(?<=^(?:[^""]*""[^""]*"")*[^""]*)(? /// Returns a list with all available date time formats /// @@ -99,7 +105,7 @@ internal static List GetList(bool isKeywordSearch, bool? timeLo } // Get formated date - var value = TimeAndDateHelper.ConvertToCustomFormat(dtObject, unixTimestamp, unixTimestampMilliseconds, weekOfYear, eraShort, Regex.Replace(formatSyntax, "^UTC:", string.Empty), firstWeekRule, firstDayOfTheWeek); + var value = TimeAndDateHelper.ConvertToCustomFormat(dtObject, unixTimestamp, unixTimestampMilliseconds, weekOfYear, eraShort, RegexUtcPrefix().Replace(formatSyntax, string.Empty), firstWeekRule, firstDayOfTheWeek); try { value = dtObject.ToString(value, CultureInfo.CurrentCulture); @@ -113,7 +119,7 @@ internal static List GetList(bool isKeywordSearch, bool? timeLo else { // Do not fail as we have custom format syntax. Instead fix backslashes. - value = Regex.Replace(value, @"(? class /// - internal static class SearchController + internal static partial class SearchController { /// /// Var that holds the delimiter between format and date @@ -30,6 +30,12 @@ internal static class SearchController /// private static readonly string[] _conjunctionList = Resources.Microsoft_plugin_timedate_Search_ConjunctionList.Split("; "); + [GeneratedRegex(@"\w+[+-]?\d+.*$")] + private static partial Regex RegexWordDigitSuffix(); + + [GeneratedRegex(@"\d+[\.:/]\d+")] + private static partial Regex RegexDigitSeparatorDigit(); + /// /// Searches for results /// @@ -125,7 +131,7 @@ internal static List ExecuteSearch(Query query, string iconTheme) } // If search term is only a number that can't be parsed return an error message - if (!isEmptySearchInput && results.Count == 0 && Regex.IsMatch(searchTerm, @"\w+[+-]?\d+.*$") && !searchTerm.Any(char.IsWhiteSpace) && (TimeAndDateHelper.IsSpecialInputParsing(searchTerm) || !Regex.IsMatch(searchTerm, @"\d+[\.:/]\d+"))) + if (!isEmptySearchInput && results.Count == 0 && RegexWordDigitSuffix().IsMatch(searchTerm) && !searchTerm.Any(char.IsWhiteSpace) && (TimeAndDateHelper.IsSpecialInputParsing(searchTerm) || !RegexDigitSeparatorDigit().IsMatch(searchTerm))) { string title = !string.IsNullOrEmpty(lastInputParsingErrorReason) ? Resources.Microsoft_plugin_timedate_ErrorResultValue : Resources.Microsoft_plugin_timedate_ErrorResultTitle; string message = !string.IsNullOrEmpty(lastInputParsingErrorReason) ? lastInputParsingErrorReason : Resources.Microsoft_plugin_timedate_ErrorResultSubTitle; diff --git a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate/Components/TimeAndDateHelper.cs b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate/Components/TimeAndDateHelper.cs index 75ffed195859..7fd6a3069a04 100644 --- a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate/Components/TimeAndDateHelper.cs +++ b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate/Components/TimeAndDateHelper.cs @@ -13,21 +13,64 @@ namespace Microsoft.PowerToys.Run.Plugin.TimeDate.Components { - internal static class TimeAndDateHelper + internal static partial class TimeAndDateHelper { - private static readonly Regex _regexSpecialInputFormats = new Regex(@"^.*(::)?(u|ums|ft|oa|exc|exf)[+-]\d"); - private static readonly Regex _regexCustomDateTimeFormats = new Regex(@"(?True if yes; otherwise, false internal static bool IsSpecialInputParsing(string input) { - return _regexSpecialInputFormats.IsMatch(input); + return RegexSpecialInputFormats().IsMatch(input); } /// @@ -315,48 +358,48 @@ internal static string ConvertToCustomFormat(DateTime date, long unix, long unix string result = format; // DOW: Number of day in week - result = _regexCustomDateTimeDow.Replace(result, GetNumberOfDayInWeek(date, firstDayOfTheWeek).ToString(CultureInfo.CurrentCulture)); + result = RegexCustomDateTimeDow().Replace(result, GetNumberOfDayInWeek(date, firstDayOfTheWeek).ToString(CultureInfo.CurrentCulture)); // DIM: Days in Month - result = _regexCustomDateTimeDim.Replace(result, DateTime.DaysInMonth(date.Year, date.Month).ToString(CultureInfo.CurrentCulture)); + result = RegexCustomDateTimeDim().Replace(result, DateTime.DaysInMonth(date.Year, date.Month).ToString(CultureInfo.CurrentCulture)); // WOM: Week of Month - result = _regexCustomDateTimeWom.Replace(result, GetWeekOfMonth(date, firstDayOfTheWeek).ToString(CultureInfo.CurrentCulture)); + result = RegexCustomDateTimeWom().Replace(result, GetWeekOfMonth(date, firstDayOfTheWeek).ToString(CultureInfo.CurrentCulture)); // WOY: Week of Year - result = _regexCustomDateTimeWoy.Replace(result, calWeek.ToString(CultureInfo.CurrentCulture)); + result = RegexCustomDateTimeWoy().Replace(result, calWeek.ToString(CultureInfo.CurrentCulture)); // EAB: Era abbreviation - result = _regexCustomDateTimeEab.Replace(result, eraShortFormat); + result = RegexCustomDateTimeEab().Replace(result, eraShortFormat); // WFT: Week of Month - if (_regexCustomDateTimeWft.IsMatch(result)) + if (RegexCustomDateTimeWft().IsMatch(result)) { // Special handling as very early dates can't convert. - result = _regexCustomDateTimeWft.Replace(result, date.ToFileTime().ToString(CultureInfo.CurrentCulture)); + result = RegexCustomDateTimeWft().Replace(result, date.ToFileTime().ToString(CultureInfo.CurrentCulture)); } // UXT: Unix time stamp - result = _regexCustomDateTimeUxt.Replace(result, unix.ToString(CultureInfo.CurrentCulture)); + result = RegexCustomDateTimeUxt().Replace(result, unix.ToString(CultureInfo.CurrentCulture)); // UMS: Unix time stamp milli seconds - result = _regexCustomDateTimeUms.Replace(result, unixMilliseconds.ToString(CultureInfo.CurrentCulture)); + result = RegexCustomDateTimeUms().Replace(result, unixMilliseconds.ToString(CultureInfo.CurrentCulture)); // OAD: OLE Automation date - result = _regexCustomDateTimeOad.Replace(result, ConvertToOleAutomationFormat(date, OADateFormats.OLEAutomation).ToString(CultureInfo.CurrentCulture)); + result = RegexCustomDateTimeOad().Replace(result, ConvertToOleAutomationFormat(date, OADateFormats.OLEAutomation).ToString(CultureInfo.CurrentCulture)); // EXC: Excel date value with base 1900 - if (_regexCustomDateTimeExc.IsMatch(result)) + if (RegexCustomDateTimeExc().IsMatch(result)) { // Special handling as very early dates can't convert. - result = _regexCustomDateTimeExc.Replace(result, ConvertToOleAutomationFormat(date, OADateFormats.Excel1900).ToString(CultureInfo.CurrentCulture)); + result = RegexCustomDateTimeExc().Replace(result, ConvertToOleAutomationFormat(date, OADateFormats.Excel1900).ToString(CultureInfo.CurrentCulture)); } // EXF: Excel date value with base 1904 - if (_regexCustomDateTimeExf.IsMatch(result)) + if (RegexCustomDateTimeExf().IsMatch(result)) { // Special handling as very early dates can't convert. - result = _regexCustomDateTimeExf.Replace(result, ConvertToOleAutomationFormat(date, OADateFormats.Excel1904).ToString(CultureInfo.CurrentCulture)); + result = RegexCustomDateTimeExf().Replace(result, ConvertToOleAutomationFormat(date, OADateFormats.Excel1904).ToString(CultureInfo.CurrentCulture)); } return result; @@ -369,7 +412,7 @@ internal static string ConvertToCustomFormat(DateTime date, long unix, long unix /// True if yes and otherwise false internal static bool StringContainsCustomFormatSyntax(string str) { - return _regexCustomDateTimeFormats.IsMatch(str); + return RegexCustomDateTimeFormats().IsMatch(str); } /// diff --git a/src/modules/peek/Peek.Common/Extensions/IFileSystemItemExtensions.cs b/src/modules/peek/Peek.Common/Extensions/IFileSystemItemExtensions.cs index 44f307336df7..184f249580b4 100644 --- a/src/modules/peek/Peek.Common/Extensions/IFileSystemItemExtensions.cs +++ b/src/modules/peek/Peek.Common/Extensions/IFileSystemItemExtensions.cs @@ -17,7 +17,7 @@ namespace Peek.Common.Extensions { - public static class IFileSystemItemExtensions + public static partial class IFileSystemItemExtensions { public static Size? GetImageSize(this IFileSystemItem item) { @@ -49,8 +49,8 @@ public static class IFileSystemItemExtensions string? height = reader.GetAttribute("height"); if (width != null && height != null) { - int widthValue = int.Parse(Regex.Match(width, @"\d+").Value, NumberFormatInfo.InvariantInfo); - int heightValue = int.Parse(Regex.Match(height, @"\d+").Value, NumberFormatInfo.InvariantInfo); + int widthValue = int.Parse(DigitsRegex().Match(width).Value, NumberFormatInfo.InvariantInfo); + int heightValue = int.Parse(DigitsRegex().Match(height).Value, NumberFormatInfo.InvariantInfo); size = new Size(widthValue, heightValue); } else @@ -110,5 +110,8 @@ public static async Task GetContentTypeAsync(this IFileSystemItem item) }; return contentType; } + + [GeneratedRegex(@"\d+")] + private static partial Regex DigitsRegex(); } } diff --git a/src/modules/previewpane/MarkdownPreviewHandler/MarkdownPreviewHandlerControl.cs b/src/modules/previewpane/MarkdownPreviewHandler/MarkdownPreviewHandlerControl.cs index 9c3927074a5b..2d8f4c1fff69 100644 --- a/src/modules/previewpane/MarkdownPreviewHandler/MarkdownPreviewHandlerControl.cs +++ b/src/modules/previewpane/MarkdownPreviewHandler/MarkdownPreviewHandlerControl.cs @@ -117,8 +117,7 @@ public override void DoPreview(T dataSource) } string fileText = File.ReadAllText(filePath); - Regex imageTagRegex = new Regex(@"<[ ]*img.*>"); - if (imageTagRegex.IsMatch(fileText)) + if (ImageTagRegex().IsMatch(fileText)) { _infoBarDisplayed = true; } @@ -309,5 +308,8 @@ private void ImagesBlockedCallBack() { _infoBarDisplayed = true; } + + [GeneratedRegex(@"<[ ]*img.*>")] + private static partial Regex ImageTagRegex(); } } diff --git a/src/modules/registrypreview/RegistryPreviewUILib/RegistryPreviewMainPage.Utilities.cs b/src/modules/registrypreview/RegistryPreviewUILib/RegistryPreviewMainPage.Utilities.cs index 2513a7adb06c..90c0be97396d 100644 --- a/src/modules/registrypreview/RegistryPreviewUILib/RegistryPreviewMainPage.Utilities.cs +++ b/src/modules/registrypreview/RegistryPreviewUILib/RegistryPreviewMainPage.Utilities.cs @@ -958,7 +958,7 @@ public void ChangeCursor(UIElement uiElement, bool wait) public void UpdateUnsavedFileState(bool unsavedChanges) { // get, cut and analyze the current title - string currentTitle = Regex.Replace(_mainWindow.Title, APPNAME + @"$|\s-\s" + APPNAME + @"$", string.Empty); + string currentTitle = AppNameSuffixRegex().Replace(_mainWindow.Title, string.Empty); bool titleContainsIndicator = currentTitle.StartsWith(_unsavedFileIndicator, StringComparison.CurrentCultureIgnoreCase); // update window title and save button state @@ -1193,5 +1193,8 @@ private bool CheckTreeForValidKey() return false; } + + [GeneratedRegex(@"RegistryPreview$|\s-\sRegistryPreview$")] + private static partial Regex AppNameSuffixRegex(); } } diff --git a/src/settings-ui/Settings.UI.Library/SettingsBackupAndRestoreUtils.cs b/src/settings-ui/Settings.UI.Library/SettingsBackupAndRestoreUtils.cs index 08708009d009..c5a935198853 100644 --- a/src/settings-ui/Settings.UI.Library/SettingsBackupAndRestoreUtils.cs +++ b/src/settings-ui/Settings.UI.Library/SettingsBackupAndRestoreUtils.cs @@ -21,7 +21,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library { - public class SettingsBackupAndRestoreUtils + public partial class SettingsBackupAndRestoreUtils { private static SettingsBackupAndRestoreUtils instance; private (bool Success, string Severity, bool LastBackupExists, DateTime? LastRan) lastBackupSettingsResults; @@ -402,7 +402,7 @@ public string GetSettingsBackupAndRestoreDir() private List GetBackupSettingsFiles(string settingsBackupAndRestoreDir) { - return Directory.GetFiles(settingsBackupAndRestoreDir, "settings_*.ptb", SearchOption.TopDirectoryOnly).ToList().Where(f => Regex.IsMatch(f, "settings_(\\d{1,19}).ptb")).ToList(); + return Directory.GetFiles(settingsBackupAndRestoreDir, "settings_*.ptb", SearchOption.TopDirectoryOnly).ToList().Where(f => SettingsBackupFileRegex().IsMatch(f)).ToList(); } /// @@ -945,11 +945,11 @@ private static void RemoveOldBackups(string location, int minNumberToKeep, TimeS { DateTime deleteIfOlder = DateTime.UtcNow.Subtract(deleteIfOlderThanTs); - var settingsBackupFolders = Directory.GetDirectories(location, "settings_*", SearchOption.TopDirectoryOnly).ToList().Where(f => Regex.IsMatch(f, "settings_(\\d{1,19})")).ToDictionary(x => long.Parse(Path.GetFileName(x).Replace("settings_", string.Empty), CultureInfo.InvariantCulture)).ToList(); + var settingsBackupFolders = Directory.GetDirectories(location, "settings_*", SearchOption.TopDirectoryOnly).ToList().Where(f => SettingsBackupFolderRegex().IsMatch(f)).ToDictionary(x => long.Parse(Path.GetFileName(x).Replace("settings_", string.Empty), CultureInfo.InvariantCulture)).ToList(); - settingsBackupFolders.AddRange(Directory.GetDirectories(location, "PowerToys_settings_*", SearchOption.TopDirectoryOnly).ToList().Where(f => Regex.IsMatch(f, "PowerToys_settings_(\\d{1,19})")).ToDictionary(x => long.Parse(Path.GetFileName(x).Replace("PowerToys_settings_", string.Empty), CultureInfo.InvariantCulture))); + settingsBackupFolders.AddRange(Directory.GetDirectories(location, "PowerToys_settings_*", SearchOption.TopDirectoryOnly).ToList().Where(f => PowerToysSettingsBackupFolderRegex().IsMatch(f)).ToDictionary(x => long.Parse(Path.GetFileName(x).Replace("PowerToys_settings_", string.Empty), CultureInfo.InvariantCulture))); - var settingsBackupFiles = Directory.GetFiles(location, "settings_*.ptb", SearchOption.TopDirectoryOnly).ToList().Where(f => Regex.IsMatch(f, "settings_(\\d{1,19}).ptb")).ToDictionary(x => long.Parse(Path.GetFileName(x).Replace("settings_", string.Empty).Replace(".ptb", string.Empty), CultureInfo.InvariantCulture)); + var settingsBackupFiles = Directory.GetFiles(location, "settings_*.ptb", SearchOption.TopDirectoryOnly).ToList().Where(f => SettingsBackupFileRegex().IsMatch(f)).ToDictionary(x => long.Parse(Path.GetFileName(x).Replace("settings_", string.Empty).Replace(".ptb", string.Empty), CultureInfo.InvariantCulture)); if (settingsBackupFolders.Count + settingsBackupFiles.Count <= minNumberToKeep) { @@ -1009,6 +1009,15 @@ private static void RemoveOldBackups(string location, int minNumberToKeep, TimeS } } + [GeneratedRegex("settings_(\\d{1,19}).ptb")] + private static partial Regex SettingsBackupFileRegex(); + + [GeneratedRegex("settings_(\\d{1,19})")] + private static partial Regex SettingsBackupFolderRegex(); + + [GeneratedRegex("PowerToys_settings_(\\d{1,19})")] + private static partial Regex PowerToysSettingsBackupFolderRegex(); + /// /// Class JsonNormalizer is a utility class to 'normalize' a JSON file so that it can be compared to another JSON file. /// This really just means to fully sort it. This does not work for any JSON file where the order of the node is relevant. diff --git a/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/ScoobeReleaseNotesPage.xaml.cs b/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/ScoobeReleaseNotesPage.xaml.cs index a26d15759c1d..c8da7fc505b6 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/ScoobeReleaseNotesPage.xaml.cs +++ b/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/ScoobeReleaseNotesPage.xaml.cs @@ -31,25 +31,25 @@ public ScoobeReleaseNotesPage() /// /// Regex to remove installer hash sections from the release notes. /// - private const string RemoveInstallerHashesRegex = @"(\r\n)+## Installer Hashes(\r\n.*)+## Highlights"; - private const string RemoveHotFixInstallerHashesRegex = @"(\r\n)+## Installer Hashes(\r\n.*)+$"; - private const RegexOptions RemoveInstallerHashesRegexOptions = RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant; + [GeneratedRegex(@"(\r\n)+## Installer Hashes(\r\n.*)+## Highlights", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant)] + private static partial Regex RemoveInstallerHashesRegex(); + + [GeneratedRegex(@"(\r\n)+## Installer Hashes(\r\n.*)+$", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant)] + private static partial Regex RemoveHotFixInstallerHashesRegex(); /// /// Regex to match markdown images with 'Hero' in the alt text. /// Matches: ![...Hero...](url) /// - private static readonly Regex HeroImageRegex = new Regex( - @"!\[([^\]]*Hero[^\]]*)\]\(([^)]+)\)", - RegexOptions.Compiled | RegexOptions.IgnoreCase); + [GeneratedRegex(@"!\[([^\]]*Hero[^\]]*)\]\(([^)]+)\)", RegexOptions.IgnoreCase)] + private static partial Regex HeroImageRegex(); /// /// Regex to match GitHub PR/Issue references (e.g., #41029). /// Only matches # followed by digits that are not already part of a markdown link. /// - private static readonly Regex GitHubPrReferenceRegex = new Regex( - @"(? + notes = GitHubPrReferenceRegex().Replace(notes, match => string.Format(CultureInfo.InvariantCulture, GitHubPrLinkTemplate, match.Groups[1].Value)); releaseNotesHtmlBuilder.AppendLine(notes); From 4829b51237c17213c8ea626415d6d1d39d93a6cf Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 1 Apr 2026 21:45:56 +0000 Subject: [PATCH 2/2] Fix SA1500 brace violation and misplaced using in WebSearch/Main.cs Agent-Logs-Url: https://github.com/microsoft/PowerToys/sessions/0fdf55a0-873f-486e-820e-f6dc17694189 Co-authored-by: crutkas <1462282+crutkas@users.noreply.github.com> --- .../Plugins/Community.PowerToys.Run.Plugin.WebSearch/Main.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.WebSearch/Main.cs b/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.WebSearch/Main.cs index 3e151ad99147..bc782b10c275 100644 --- a/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.WebSearch/Main.cs +++ b/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.WebSearch/Main.cs @@ -7,9 +7,9 @@ using System.Globalization; using System.Linq; using System.Text; +using System.Text.RegularExpressions; using System.Windows.Controls; -using System.Text.RegularExpressions; using ManagedCommon; using Microsoft.PowerToys.Settings.UI.Library; using Wox.Infrastructure; @@ -142,7 +142,8 @@ bool IsURI(string input) && !input.StartsWith("http", StringComparison.OrdinalIgnoreCase) && !input.Contains('/', StringComparison.OrdinalIgnoreCase) && !input.All(char.IsDigit) - && UriSchemeRegex().IsMatch(input)) { + && UriSchemeRegex().IsMatch(input)) + { return true; }