Skip to content

Commit b179b78

Browse files
committed
Fix hyphen-prefixed arguments being split at period
When passing arguments like -foo.bar or -foo=bar.baz, PowerShell incorrectly splits them at the first period. This fix modifies the tokenizer to treat such tokens as single arguments in command mode. Fixes #6291
1 parent 4d2da6e commit b179b78

2 files changed

Lines changed: 176 additions & 1 deletion

File tree

src/System.Management.Automation/engine/parser/tokenizer.cs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3208,7 +3208,6 @@ private Token ScanParameter()
32083208
case ',':
32093209
case '|':
32103210
case '&':
3211-
case '.':
32123211
case '[':
32133212
case '\r':
32143213
case '\n':
@@ -3217,6 +3216,20 @@ private Token ScanParameter()
32173216
scanning = false;
32183217
break;
32193218

3219+
case '.':
3220+
if (InCommandMode())
3221+
{
3222+
// Period is never part of a parameter name. Treat the token as an argument.
3223+
// This handles cases like -foo.bar which should be a single argument.
3224+
UngetChar();
3225+
sb.Insert(0, _script[_tokenStart]); // Insert the '-' that we skipped.
3226+
return ScanGenericToken(sb);
3227+
}
3228+
3229+
UngetChar();
3230+
scanning = false;
3231+
break;
3232+
32203233
case ':':
32213234
scanning = false;
32223235
sawColonAtEnd = true;

test/powershell/Language/Parser/Parser.Tests.ps1

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1352,4 +1352,166 @@ foo``u{2195}abc
13521352
$tokens[1] | Should -BeExactly $lastToken
13531353
}
13541354
}
1355+
1356+
Context "Hyphen-prefixed arguments should not be split at dot" {
1357+
BeforeAll {
1358+
function Out-Argument { ,$Args }
1359+
}
1360+
1361+
# Basic cases
1362+
It "Argument '-foo.bar' should be passed as a single argument" {
1363+
$result = Out-Argument -foo.bar
1364+
$result.Count | Should -Be 1
1365+
$result[0] | Should -BeExactly '-foo.bar'
1366+
}
1367+
1368+
It "Argument '-foo=bar.baz' should be passed as a single argument" {
1369+
$result = Out-Argument -foo=bar.baz
1370+
$result.Count | Should -Be 1
1371+
$result[0] | Should -BeExactly '-foo=bar.baz'
1372+
}
1373+
1374+
# Consecutive and multiple dots
1375+
It "Argument '-foo..bar' with consecutive dots should be passed as a single argument" {
1376+
$result = Out-Argument -foo..bar
1377+
$result.Count | Should -Be 1
1378+
$result[0] | Should -BeExactly '-foo..bar'
1379+
}
1380+
1381+
It "Argument '-foo...bar' with three consecutive dots should be passed as a single argument" {
1382+
$result = Out-Argument -foo...bar
1383+
$result.Count | Should -Be 1
1384+
$result[0] | Should -BeExactly '-foo...bar'
1385+
}
1386+
1387+
It "Argument '-foo.bar.baz' with multiple dots should be passed as a single argument" {
1388+
$result = Out-Argument -foo.bar.baz
1389+
$result.Count | Should -Be 1
1390+
$result[0] | Should -BeExactly '-foo.bar.baz'
1391+
}
1392+
1393+
It "Argument '-foo=1.2.3.4' with multiple dots in value should be passed as a single argument" {
1394+
$result = Out-Argument -foo=1.2.3.4
1395+
$result.Count | Should -Be 1
1396+
$result[0] | Should -BeExactly '-foo=1.2.3.4'
1397+
}
1398+
1399+
# Leading and trailing dots
1400+
It "Argument '-.foo' with leading dot should be passed as a single argument" {
1401+
$result = Out-Argument -.foo
1402+
$result.Count | Should -Be 1
1403+
$result[0] | Should -BeExactly '-.foo'
1404+
}
1405+
1406+
It "Argument '-foo.' with trailing dot should be passed as a single argument" {
1407+
$result = Out-Argument -foo.
1408+
$result.Count | Should -Be 1
1409+
$result[0] | Should -BeExactly '-foo.'
1410+
}
1411+
1412+
# Double hyphen (already worked in production)
1413+
It "Argument '--foo.bar' with double hyphen should be passed as a single argument" {
1414+
$result = Out-Argument --foo.bar
1415+
$result.Count | Should -Be 1
1416+
$result[0] | Should -BeExactly '--foo.bar'
1417+
}
1418+
1419+
# Multiple arguments
1420+
It "Multiple hyphen-prefixed arguments with dots should each be single arguments" {
1421+
$result = Out-Argument -a.b -c=d.e
1422+
$result.Count | Should -Be 2
1423+
$result[0] | Should -BeExactly '-a.b'
1424+
$result[1] | Should -BeExactly '-c=d.e'
1425+
}
1426+
1427+
# Real-world use cases (compiler flags, etc.)
1428+
It "Compiler-style argument '-DVERSION=1.2.3' should be passed as a single argument" {
1429+
$result = Out-Argument -DVERSION=1.2.3
1430+
$result.Count | Should -Be 1
1431+
$result[0] | Should -BeExactly '-DVERSION=1.2.3'
1432+
}
1433+
1434+
It "Compiler-style argument '-std=c++20' should be passed as a single argument" {
1435+
$result = Out-Argument -std=c++20
1436+
$result.Count | Should -Be 1
1437+
$result[0] | Should -BeExactly '-std=c++20'
1438+
}
1439+
1440+
# Splatting
1441+
It "Splatting should preserve hyphen-prefixed arguments with dots" {
1442+
function Outer { ,$Args }
1443+
$result = Outer -foo.bar
1444+
$result.Count | Should -Be 1
1445+
$result[0] | Should -BeExactly '-foo.bar'
1446+
}
1447+
1448+
It "Splatting should preserve arguments with equals and dots" {
1449+
function Outer { ,$Args }
1450+
$result = Outer -foo=bar.baz
1451+
$result.Count | Should -Be 1
1452+
$result[0] | Should -BeExactly '-foo=bar.baz'
1453+
}
1454+
1455+
# Native commands
1456+
It "Native command should receive hyphen-prefixed argument with dot as single argument" {
1457+
$result = cmd /c echo -foo.bar
1458+
$result | Should -BeExactly '-foo.bar'
1459+
}
1460+
1461+
It "Native command should receive argument with equals and dot as single argument" {
1462+
$result = cmd /c echo -foo=bar.baz
1463+
$result | Should -BeExactly '-foo=bar.baz'
1464+
}
1465+
1466+
It "Native command via splatting should receive hyphen-prefixed argument with dot as single argument" {
1467+
function Wrapper { cmd /c echo @Args }
1468+
$result = Wrapper -foo.bar
1469+
$result | Should -BeExactly '-foo.bar'
1470+
}
1471+
1472+
# Backward compatibility - parameter binding
1473+
It "Normal parameter binding with space should still work" {
1474+
function Test-Param { param($foo) $foo }
1475+
$result = Test-Param -foo bar
1476+
$result | Should -BeExactly 'bar'
1477+
}
1478+
1479+
It "Parameter binding with colon syntax should still work" {
1480+
function Test-Param { param($foo) $foo }
1481+
$result = Test-Param -foo:bar
1482+
$result | Should -BeExactly 'bar'
1483+
}
1484+
1485+
It "Switch parameter should not be affected" {
1486+
function Test-Switch { param([switch]$Verbose) $Verbose.IsPresent }
1487+
$result = Test-Switch -Verbose
1488+
$result | Should -BeTrue
1489+
}
1490+
1491+
It "Parameter with dot value using space should still work" {
1492+
function Test-Param { param($Path) $Path }
1493+
$result = Test-Param -Path .txt
1494+
$result | Should -BeExactly '.txt'
1495+
}
1496+
1497+
# Backward compatibility - expression mode
1498+
It "Member access in expression mode should still work" {
1499+
$obj = [PSCustomObject]@{ foo = 'hello' }
1500+
$obj.foo | Should -BeExactly 'hello'
1501+
}
1502+
1503+
It "Method call in expression mode should still work" {
1504+
'hello'.ToUpper() | Should -BeExactly 'HELLO'
1505+
}
1506+
1507+
It "Negative decimal number should still work" {
1508+
$result = -3.14
1509+
$result | Should -Be -3.14
1510+
}
1511+
1512+
It "Range operator with negative numbers should still work" {
1513+
$result = -3..-1
1514+
$result | Should -Be @(-3, -2, -1)
1515+
}
1516+
}
13551517
}

0 commit comments

Comments
 (0)