Skip to content

Commit afa5bc9

Browse files
Correct YAML mapping grammar edges
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
1 parent 800e0ed commit afa5bc9

9 files changed

Lines changed: 99 additions & 35 deletions

src/functions/private/Find-YamlMappingColon.ps1

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,8 @@ function Find-YamlMappingColon {
5050
while ($index -lt $Text.Length -and
5151
-not (Test-YamlWhiteSpace -Character $Text[$index]) -and
5252
$Text[$index] -notin @(',', '[', ']', '{', '}')) {
53-
if (Test-YamlMappingValueIndicator -Text $Text -Index $index) {
53+
if ($index + 1 -lt $Text.Length -and
54+
(Test-YamlMappingValueIndicator -Text $Text -Index $index)) {
5455
$anchorColonCandidate = $index
5556
}
5657
$index++
@@ -63,6 +64,10 @@ function Find-YamlMappingColon {
6364
while ($index -lt $Text.Length -and
6465
-not (Test-YamlWhiteSpace -Character $Text[$index]) -and
6566
$Text[$index] -notin @(',', '[', ']', '{', '}')) {
67+
if ($index + 1 -lt $Text.Length -and
68+
(Test-YamlMappingValueIndicator -Text $Text -Index $index)) {
69+
$anchorColonCandidate = $index
70+
}
6671
$index++
6772
}
6873
$index--

src/functions/private/Get-YamlImplicitKeyLength.ps1

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,18 @@ function Get-YamlImplicitKeyLength {
1010
[pscustomobject] $Node,
1111

1212
[Parameter(Mandatory)]
13-
[pscustomobject] $Context
13+
[pscustomobject] $Context,
14+
15+
[Parameter()]
16+
[ValidateRange(0, 2147483647)]
17+
[int] $EndIndex
1418
)
1519

16-
$sourceLength = [Math]::Max(0, $Node.End.Index - $Node.Start.Index)
20+
$sourceEnd = if ($PSBoundParameters.ContainsKey('EndIndex')) {
21+
$EndIndex
22+
} else {
23+
$Node.End.Index
24+
}
25+
$sourceLength = [Math]::Max(0, $sourceEnd - $Node.Start.Index)
1726
return Get-YamlRuneCount -Text $Context.Text.Substring($Node.Start.Index, $sourceLength)
1827
}

src/functions/private/Read-YamlBlockKey.ps1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ function Read-YamlBlockKey {
4848
}
4949
$node = Read-YamlFlowNode -Cursor $cursor -Context $Context -Depth $Depth `
5050
-PendingTag $properties.Tag -PendingUnknownTag $properties.HasUnknownTag `
51-
-PendingAnchor $properties.Anchor
51+
-PendingAnchor $properties.Anchor -InImplicitKey
5252
$expectedEnd = $Context.LineStarts[$Line] + $Column + $Text.Length
5353
while ($cursor.Index -lt $expectedEnd -and $Context.Text[$cursor.Index] -in @(' ', "`t")) {
5454
Move-YamlCursor -Cursor $cursor -Context $Context

src/functions/private/Read-YamlBlockMapping.ps1

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,8 +79,21 @@ function Read-YamlBlockMapping {
7979
if ($isExplicit) {
8080
$afterQuestion = $content.Substring(1)
8181
$leading = $afterQuestion.Length - $afterQuestion.TrimStart(' ', "`t").Length
82+
$separator = $afterQuestion.Substring(0, $leading)
8283
$keyText = $afterQuestion.TrimStart(' ', "`t")
8384
$keyColumn = $contentColumn + 1 + $leading
85+
if ($separator.IndexOf("`t", [System.StringComparison]::Ordinal) -ge 0 -and
86+
(
87+
(Test-YamlIndicator -Text $keyText -Indicator '-') -or
88+
(Test-YamlIndicator -Text $keyText -Indicator '?') -or
89+
(Find-YamlMappingColon -Text $keyText -AllowAnchorFallback) -ge 0
90+
)) {
91+
$mark = New-YamlMark -Index ($Context.LineStarts[$lineNumber] + $contentColumn + 1) `
92+
-Line $lineNumber -Column ($contentColumn + 1)
93+
throw (New-YamlException -Start $mark -End $mark -ErrorId 'YamlInvalidIndentation' -Message (
94+
'A tab cannot separate a mapping indicator from a compact block collection.'
95+
))
96+
}
8497
if ([string]::IsNullOrEmpty((Get-YamlContentWithoutComment -Text $keyText))) {
8598
$Context.LineIndex++
8699
Skip-YamlBlockTrivia -Context $Context
@@ -133,10 +146,25 @@ function Read-YamlBlockMapping {
133146
}
134147
if ($valueIndent -eq $Indent -and
135148
(Test-YamlIndicator -Text $valueContent -Indicator ':')) {
136-
$valueText = $valueContent.Substring(1)
137-
$leading = $valueText.Length - $valueText.TrimStart(' ', "`t").Length
138-
$valueText = $valueText.TrimStart(' ', "`t")
149+
$valueSource = $valueContent.Substring(1)
150+
$leading = $valueSource.Length - $valueSource.TrimStart(' ', "`t").Length
151+
$separator = $valueSource.Substring(0, $leading)
152+
$valueText = $valueSource.TrimStart(' ', "`t")
139153
$valueColumn = $Indent + 1 + $leading
154+
if ($separator.IndexOf("`t", [System.StringComparison]::Ordinal) -ge 0 -and
155+
(
156+
(Test-YamlIndicator -Text $valueText -Indicator '-') -or
157+
(Test-YamlIndicator -Text $valueText -Indicator '?') -or
158+
(Find-YamlMappingColon -Text $valueText -AllowAnchorFallback) -ge 0
159+
)) {
160+
$mark = New-YamlMark -Index (
161+
$Context.LineStarts[$Context.LineIndex] + $Indent + 1
162+
) -Line $Context.LineIndex -Column ($Indent + 1)
163+
throw (New-YamlException -Start $mark -End $mark `
164+
-ErrorId 'YamlInvalidIndentation' -Message (
165+
'A tab cannot separate a mapping indicator from a compact block collection.'
166+
))
167+
}
140168
if ([string]::IsNullOrEmpty((Get-YamlContentWithoutComment -Text $valueText))) {
141169
$valueLineNumber = $Context.LineIndex
142170
$Context.LineIndex++

src/functions/private/Read-YamlDirectiveBlock.ps1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ function Read-YamlDirectiveBlock {
4343
$yamlDirectiveSeen = $true
4444
} elseif ($directive -cmatch '^%TAG(?:[ \t]|$)') {
4545
if ($directive -cnotmatch (
46-
'^%TAG[ \t]+(!|!!|![0-9A-Za-z-]+!)[ \t]+([^ \t#]+)(?:[ \t]+#.*)?$'
46+
'^%TAG[ \t]+(!|!!|![0-9A-Za-z-]+!)[ \t]+([^ \t]+)(?:[ \t]+#.*)?$'
4747
)) {
4848
throw (New-YamlException -Start $mark -End $mark `
4949
-ErrorId 'YamlInvalidDirective' -Message (

src/functions/private/Read-YamlFlowNode.ps1

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@ function Read-YamlFlowNode {
2424
[AllowEmptyString()]
2525
[string] $PendingAnchor = '',
2626

27-
[switch] $InFlowCollection
27+
[switch] $InFlowCollection,
28+
29+
[switch] $InImplicitKey
2830
)
2931

3032
Skip-YamlFlowTrivia -Cursor $Cursor -Context $Context
@@ -85,8 +87,9 @@ function Read-YamlFlowNode {
8587
-not (Test-YamlWhiteSpace -Character $Context.Text[$Cursor.Index]) -and
8688
$Context.Text[$Cursor.Index] -cne "`n" -and
8789
$Context.Text[$Cursor.Index] -notin @(',', '[', ']', '{', '}') -and
88-
-not ($InFlowCollection -and
89-
(Test-YamlMappingValueIndicator -Text $Context.Text -Index $Cursor.Index -Flow))) {
90+
-not (($InFlowCollection -or $InImplicitKey) -and
91+
(Test-YamlMappingValueIndicator -Text $Context.Text `
92+
-Index $Cursor.Index -Flow:$InFlowCollection))) {
9093
Move-YamlCursor -Cursor $Cursor -Context $Context
9194
}
9295
$anchor = $Context.Text.Substring($anchorStart, $Cursor.Index - $anchorStart)
@@ -133,8 +136,9 @@ function Read-YamlFlowNode {
133136
-not (Test-YamlWhiteSpace -Character $Context.Text[$Cursor.Index]) -and
134137
$Context.Text[$Cursor.Index] -cne "`n" -and
135138
$Context.Text[$Cursor.Index] -notin @(',', '[', ']', '{', '}') -and
136-
-not ($InFlowCollection -and
137-
(Test-YamlMappingValueIndicator -Text $Context.Text -Index $Cursor.Index -Flow))) {
139+
-not (($InFlowCollection -or $InImplicitKey) -and
140+
(Test-YamlMappingValueIndicator -Text $Context.Text `
141+
-Index $Cursor.Index -Flow:$InFlowCollection))) {
138142
Move-YamlCursor -Cursor $Cursor -Context $Context
139143
}
140144
$alias = $Context.Text.Substring($aliasStart, $Cursor.Index - $aliasStart)
@@ -192,7 +196,8 @@ function Read-YamlFlowNode {
192196
if ($Cursor.Index -lt $Context.Text.Length -and $Context.Text[$Cursor.Index] -eq ':') {
193197
if (-not $explicitPair -and (
194198
$Cursor.Line -ne $itemStartLine -or
195-
(Get-YamlImplicitKeyLength -Node $item -Context $Context) -gt 1024
199+
(Get-YamlImplicitKeyLength -Node $item -Context $Context `
200+
-EndIndex $Cursor.Index) -gt 1024
196201
)) {
197202
$mark = New-YamlMark -Index $Cursor.Index -Line $Cursor.Line -Column $Cursor.Column
198203
throw (New-YamlException -Start $mark -End $mark -ErrorId 'YamlInvalidImplicitKey' -Message (
@@ -271,14 +276,6 @@ function Read-YamlFlowNode {
271276
}
272277
}
273278
Skip-YamlFlowTrivia -Cursor $Cursor -Context $Context
274-
if (-not $explicit -and (
275-
(Get-YamlImplicitKeyLength -Node $key -Context $Context) -gt 1024
276-
)) {
277-
$mark = New-YamlMark -Index $Cursor.Index -Line $Cursor.Line -Column $Cursor.Column
278-
throw (New-YamlException -Start $mark -End $mark -ErrorId 'YamlInvalidImplicitKey' -Message (
279-
'An implicit mapping key must fit on one line and cannot exceed 1024 Unicode scalar values.'
280-
))
281-
}
282279
if ($Cursor.Index -ge $Context.Text.Length -or $Context.Text[$Cursor.Index] -ne ':') {
283280
if (-not $explicit) {
284281
if ($Cursor.Index -lt $Context.Text.Length -and

src/functions/private/Test-YamlIndicator.ps1

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,5 @@ function Test-YamlIndicator {
2121
if ($Text.Length -eq 1) {
2222
return $true
2323
}
24-
if ($Indicator.Equals('-', [System.StringComparison]::Ordinal)) {
25-
return Test-YamlWhiteSpace -Character $Text[1]
26-
}
27-
return $Text[1].Equals([char] ' ')
24+
return Test-YamlWhiteSpace -Character $Text[1]
2825
}

tests/ConvertFrom-Yaml.Tests.ps1

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,15 @@ folded: >
181181
$sequencePair.Count | Should -Be 1
182182
$sequencePair[0].foo | Should -Be @('bar')
183183
}
184+
185+
It 'accepts tabs after explicit block mapping indicators' {
186+
$tabAfterQuestion = "?`tkey`n: value" | ConvertFrom-Yaml -AsHashtable
187+
$tabAfterColon = "? key`n:`tvalue" | ConvertFrom-Yaml -AsHashtable
188+
189+
$tabAfterQuestion['key'] | Should -Be 'value'
190+
$tabAfterColon['key'] | Should -Be 'value'
191+
$tabAfterColon.Count | Should -Be 1
192+
}
184193
}
185194

186195
Context 'Streams and pipeline input' {
@@ -231,11 +240,18 @@ folded: >
231240
$emptyKey = '&a: value' | ConvertFrom-Yaml -AsHashtable
232241
$aliasedKey = '{anchor: &a foo, *a: value}' |
233242
ConvertFrom-Yaml -AsHashtable
243+
$blockAliasedKey = "source: &a key`n*a: value" |
244+
ConvertFrom-Yaml -AsHashtable
245+
$colonAliasName = "&a: key: &a value`nfoo:`n *a:" |
246+
ConvertFrom-Yaml -AsHashtable
234247

235248
$emptyKey.Count | Should -Be 1
236249
$emptyKey[[System.DBNull]::Value] | Should -Be 'value'
237250
@($aliasedKey.Keys) | Should -Be @('anchor', 'foo')
238251
@($aliasedKey.Values) | Should -Be @('foo', 'value')
252+
@($blockAliasedKey.Keys) | Should -Be @('source', 'key')
253+
@($blockAliasedKey.Values) | Should -Be @('key', 'value')
254+
$colonAliasName['foo'] | Should -Be 'key'
239255
}
240256

241257
It 'constructs explicit standard scalar tags safely' {

tests/Test-Yaml.Tests.ps1

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ Describe 'Test-Yaml' {
3737
("a: &a value`nb: !foo *a" | Test-Yaml) | Should -BeFalse
3838
}
3939

40-
It 'enforces implicit-key line and Unicode scalar limits' {
40+
It 'enforces block and flow-sequence implicit-key limits' {
4141
$emoji = [char]::ConvertFromUtf32(0x1F600)
4242

4343
("['multi`n line': value]" | Test-Yaml) | Should -BeFalse
@@ -53,23 +53,19 @@ Describe 'Test-Yaml' {
5353
@{ Key = '!local ' + ('k' * 1017); Valid = $true }
5454
@{ Key = '!local ' + ('k' * 1018); Valid = $false }
5555
)) {
56-
foreach ($yaml in @(
57-
"[$($case.Key)`: value]",
58-
"{$($case.Key)`: value}"
59-
)) {
60-
($yaml | Test-Yaml) | Should -Be $case.Valid
61-
}
56+
("[$($case.Key)`: value]" | Test-Yaml) | Should -Be $case.Valid
6257
}
6358

6459
foreach ($length in @(513, 1024, 1025)) {
6560
$key = $emoji * $length
66-
("{$key`: value}" | Test-Yaml) |
61+
("[$key`: value]" | Test-Yaml) |
6762
Should -Be ($length -le 1024)
6863
}
6964

65+
("['$('k' * 1021)' : value]" | Test-Yaml) | Should -BeTrue
66+
("['$('k' * 1022)' : value]" | Test-Yaml) | Should -BeFalse
7067
((('k' * 1023) + ' : value') | Test-Yaml) | Should -BeTrue
7168
((('k' * 1024) + ' : value') | Test-Yaml) | Should -BeFalse
72-
7369
}
7470

7571
It 'resolves non-specific tags before comparing representation keys' {
@@ -84,9 +80,18 @@ Describe 'Test-Yaml' {
8480
("{multi`n line: value}" | Test-Yaml) | Should -BeTrue
8581
}
8682

83+
It 'does not apply implicit-key limits to ordinary flow mappings' {
84+
$emoji = [char]::ConvertFromUtf32(0x1F600)
85+
86+
(('{' + ('k' * 1025) + ': value}') | Test-Yaml) | Should -BeTrue
87+
(('{' + ($emoji * 1025) + ': value}') | Test-Yaml) | Should -BeTrue
88+
}
89+
8790
It 'validates tag directives, tag tokens, and alias properties' {
8891
("%TAG !! tag:example.com,2000:app/`n---`n!!int value" | Test-Yaml) |
8992
Should -BeTrue
93+
("%TAG !e! tag:example.com,2000:app/#fragment`n---`n!e!foo value" |
94+
Test-Yaml) | Should -BeTrue
9095
("%FOO-BAR baz`n---`nvalue" | Test-Yaml) | Should -BeTrue
9196
("%FOO:B alpha-beta p#q`n---`nvalue" | Test-Yaml) | Should -BeTrue
9297
("%TAG ! tag:first/`n%TAG ! tag:second/`n---`n!value data" | Test-Yaml) |
@@ -108,6 +113,13 @@ Describe 'Test-Yaml' {
108113
("key:`n ..." | Test-Yaml) | Should -BeTrue
109114
}
110115

116+
It 'rejects tabs that indent compact collections after mapping indicators' {
117+
("?`t-" | Test-Yaml) | Should -BeFalse
118+
("? -`n:`t-" | Test-Yaml) | Should -BeFalse
119+
("?`tkey:" | Test-Yaml) | Should -BeFalse
120+
("? key:`n:`tkey:" | Test-Yaml) | Should -BeFalse
121+
}
122+
111123
It 'returns false for duplicate mapping keys' {
112124
("key: one`nkey: two" | Test-Yaml) | Should -BeFalse
113125
("1: one`n01: two" | Test-Yaml) | Should -BeFalse

0 commit comments

Comments
 (0)