Skip to content
Merged
41 changes: 29 additions & 12 deletions src/System.Management.Automation/engine/regex.cs
Original file line number Diff line number Diff line change
Expand Up @@ -73,18 +73,6 @@ public sealed class WildcardPattern
// Default is WildcardOptions.None.
internal WildcardOptions Options { get; }

/// <summary>
/// Wildcard pattern converted to regex pattern.
/// </summary>
internal string PatternConvertedToRegex
{
get
{
var patternRegex = WildcardPatternToRegexParser.Parse(this);
return patternRegex.ToString();
}
}

/// <summary>
/// Initializes and instance of the WildcardPattern class
/// for the specified wildcard pattern.
Expand Down Expand Up @@ -205,6 +193,35 @@ public bool IsMatch(string input)
return input != null && _isMatch(input);
}

/// <summary>
/// Converts the wildcard pattern to its regular expression equivalent.
/// </summary>
/// <returns>
/// A <see cref="Regex"/> object that represents the regular expression equivalent of the wildcard pattern.
/// The regex is configured with options matching the wildcard pattern's options.
/// </returns>
/// <remarks>
/// This method converts a wildcard pattern to a regular expression.
/// The conversion follows these rules:
/// <list type="bullet">
/// <item><description>* (asterisk) converts to .* (matches any string)</description></item>
/// <item><description>? (question mark) converts to . (matches any single character)</description></item>
/// <item><description>[abc] (bracket expression) converts to [abc] (matches any character in the set)</description></item>
/// <item><description>Literal characters are escaped as needed for regex</description></item>
/// </list>
/// </remarks>
/// <example>
/// <code>
/// var pattern = new WildcardPattern("*.txt");
/// Regex regex = pattern.ToRegex();
/// // regex.ToString() returns: "\.txt$"
/// </code>
/// </example>
public Regex ToRegex()
{
return WildcardPatternToRegexParser.Parse(this);
}

/// <summary>
/// Escape special chars, except for those specified in <paramref name="charsNotToEscape"/>, in a string by replacing them with their escape codes.
/// </summary>
Expand Down
77 changes: 77 additions & 0 deletions test/powershell/engine/WildcardPattern.Tests.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.

Describe "WildcardPattern.ToRegex Tests" -Tags "CI" {
It "Converts '<Pattern>' to regex pattern '<Expected>'" -TestCases @(
@{ Pattern = '*.txt'; Expected = '\.txt$' }
@{ Pattern = 'test?.log'; Expected = '^test.\.log$' }
@{ Pattern = 'file[0-9].txt'; Expected = '^file[0-9]\.txt$' }
@{ Pattern = 'test.log'; Expected = '^test\.log$' }
@{ Pattern = '*test*file*.txt'; Expected = 'test.*file.*\.txt$' }
@{ Pattern = 'file[0-9][a-z].txt'; Expected = '^file[0-9][a-z]\.txt$' }
@{ Pattern = 'test*'; Expected = '^test' }
@{ Pattern = '*test*'; Expected = 'test' }
) {
param($Pattern, $Expected)
$wildcardPattern = [System.Management.Automation.WildcardPattern]::new($Pattern)
$regex = $wildcardPattern.ToRegex()
$regex | Should -BeOfType ([regex])
$regex.ToString() | Should -BeExactly $Expected
}

It "Converts '<Pattern>' with <OptionName> option" -TestCases @(
@{ Pattern = 'TEST'; OptionName = 'IgnoreCase'; Option = [System.Management.Automation.WildcardOptions]::IgnoreCase; Expected = '^TEST$'; ExpectedRegexOptions = 'IgnoreCase, Singleline'; TestString = 'test'; ExpectedMatch = $true }
@{ Pattern = 'test'; OptionName = 'CultureInvariant'; Option = [System.Management.Automation.WildcardOptions]::CultureInvariant; Expected = '^test$'; ExpectedRegexOptions = 'Singleline, CultureInvariant'; TestString = 'test'; ExpectedMatch = $true }
@{ Pattern = 'test*'; OptionName = 'Compiled'; Option = [System.Management.Automation.WildcardOptions]::Compiled; Expected = '^test'; ExpectedRegexOptions = 'Compiled, Singleline'; TestString = 'testing'; ExpectedMatch = $true }
) {
param($Pattern, $OptionName, $Option, $Expected, $ExpectedRegexOptions, $TestString, $ExpectedMatch)
$wildcardPattern = [System.Management.Automation.WildcardPattern]::new($Pattern, $Option)
$regex = $wildcardPattern.ToRegex()
$regex | Should -BeOfType ([regex])
$regex.ToString() | Should -BeExactly $Expected
$regex.Options.ToString() | Should -BeExactly $ExpectedRegexOptions
$regex.IsMatch($TestString) | Should -Be $ExpectedMatch
}

It "Regex from '<Pattern>' matches '<TestString>': <ShouldMatch>" -TestCases @(
@{ Pattern = '*test*file*.txt'; TestString = 'mytestmyfile123.txt'; ShouldMatch = $true }
@{ Pattern = 'file[0-9][a-z].txt'; TestString = 'file5a.txt'; ShouldMatch = $true }
@{ Pattern = 'file[0-9][a-z].txt'; TestString = 'file55.txt'; ShouldMatch = $false }
) {
param($Pattern, $TestString, $ShouldMatch)
$regex = [System.Management.Automation.WildcardPattern]::new($Pattern).ToRegex()
$regex.IsMatch($TestString) | Should -Be $ShouldMatch
}

Context "Edge cases" {
It "Handles empty pattern" {
$pattern = [System.Management.Automation.WildcardPattern]::new("")
$regex = $pattern.ToRegex()
$regex | Should -BeOfType ([regex])
$regex.ToString() | Should -Be "^$"
}

It "Handles pattern with only asterisk" {
$pattern = [System.Management.Automation.WildcardPattern]::new("*")
$regex = $pattern.ToRegex()
$regex | Should -BeOfType ([regex])
$regex.ToString() | Should -BeExactly ""
$regex.IsMatch("anything") | Should -BeTrue
$regex.IsMatch("") | Should -BeTrue
}

It "Handles escaped '<Char>' wildcard character" -TestCases @(
@{ Char = '*'; Pattern = 'file`*.txt'; Expected = '^file\*\.txt$' }
@{ Char = '?'; Pattern = 'file`?.txt'; Expected = '^file\?\.txt$' }
@{ Char = '['; Pattern = 'file`[.txt'; Expected = '^file\[\.txt$' }
@{ Char = ']'; Pattern = 'file`].txt'; Expected = '^file]\.txt$' }
) {
param($Char, $Pattern, $Expected)
$wildcardPattern = [System.Management.Automation.WildcardPattern]::new($Pattern)
$regex = $wildcardPattern.ToRegex()
$regex | Should -BeOfType ([regex])
$regex.ToString() | Should -BeExactly $Expected
}

}
}
Loading