Skip to content

Commit b4113eb

Browse files
Fix SourceCode tests: split private helpers into one-function-per-file
The PSModule SourceCode test suite enforces that each .ps1 file contains exactly one function whose name matches the filename. The three private helper files each contained multiple helper functions, causing FunctionCount and FunctionName test failures on all platforms. Split into 16 individual files: - ConvertFrom-YamlLineStream.ps1: extracted Remove-YamlInlineComment - ConvertFrom-YamlNode.ps1: extracted ConvertFrom-YamlMapping, ConvertFrom-YamlSequence, Find-YamlMappingColon, ConvertFrom-YamlScalar, Expand-YamlDoubleQuoted - ConvertTo-YamlNode.ps1: extracted Test-YamlMappingType, Test-YamlSequenceType, ConvertTo-YamlMapping, ConvertTo-YamlSequence, Get-YamlMappingPair, Format-YamlScalar, Format-YamlString, Format-YamlDoubleQuoted, Format-YamlKey, Test-YamlPlainSafe All 56 Pester tests pass locally.
1 parent a1c9b4e commit b4113eb

19 files changed

Lines changed: 716 additions & 732 deletions

src/functions/private/ConvertFrom-YamlLineStream.ps1

Lines changed: 0 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -64,45 +64,3 @@
6464

6565
return , $result
6666
}
67-
68-
function Remove-YamlInlineComment {
69-
<#
70-
.SYNOPSIS
71-
Removes an unquoted `# comment` suffix from a YAML content line.
72-
#>
73-
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '',
74-
Justification = 'This function operates on a string parameter, not system state.')]
75-
[CmdletBinding()]
76-
[OutputType([string])]
77-
param(
78-
[Parameter(Mandatory)]
79-
[AllowEmptyString()]
80-
[string] $Line
81-
)
82-
83-
$inSingle = $false
84-
$inDouble = $false
85-
for ($i = 0; $i -lt $Line.Length; $i++) {
86-
$c = $Line[$i]
87-
if ($c -eq '\' -and $inDouble) {
88-
# Skip escaped char inside double quotes.
89-
$i++
90-
continue
91-
}
92-
if ($c -eq "'" -and -not $inDouble) {
93-
$inSingle = -not $inSingle
94-
continue
95-
}
96-
if ($c -eq '"' -and -not $inSingle) {
97-
$inDouble = -not $inDouble
98-
continue
99-
}
100-
if ($c -eq '#' -and -not $inSingle -and -not $inDouble) {
101-
# Comment must be preceded by whitespace or be at start of line.
102-
if ($i -eq 0 -or $Line[$i - 1] -eq ' ' -or $Line[$i - 1] -eq "`t") {
103-
return $Line.Substring(0, $i)
104-
}
105-
}
106-
}
107-
return $Line
108-
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
function ConvertFrom-YamlMapping {
2+
<#
3+
.SYNOPSIS
4+
Parses a YAML block-style mapping into a PSCustomObject or OrderedDictionary.
5+
#>
6+
[CmdletBinding()]
7+
[OutputType([System.Collections.Specialized.OrderedDictionary], [pscustomobject])]
8+
param(
9+
[Parameter(Mandatory)] [pscustomobject] $Context,
10+
[Parameter(Mandatory)] [int] $Indent,
11+
[Parameter(Mandatory)] [int] $Depth
12+
)
13+
14+
$lines = $Context.Lines
15+
$map = [ordered]@{}
16+
17+
while ($Context.Index -lt $lines.Count) {
18+
$line = $lines[$Context.Index]
19+
if ($line.Indent -lt $Indent) { break }
20+
if ($line.Indent -gt $Indent) {
21+
throw "ConvertFrom-Yaml: unexpected indentation at line $($line.Number)."
22+
}
23+
if ($line.Content.StartsWith('- ') -or $line.Content -eq '-') {
24+
# A sequence at the same indent as a mapping key is a sibling, not part of mapping.
25+
break
26+
}
27+
28+
$colonIdx = Find-YamlMappingColon -Content $line.Content
29+
if ($colonIdx -lt 0) {
30+
throw "ConvertFrom-Yaml: expected mapping key at line $($line.Number): '$($line.Content)'."
31+
}
32+
33+
$key = ConvertFrom-YamlScalar -Raw $line.Content.Substring(0, $colonIdx).Trim()
34+
$rest = $line.Content.Substring($colonIdx + 1).Trim()
35+
36+
$Context.Index++
37+
38+
if ($rest.Length -gt 0) {
39+
$map[[string]$key] = ConvertFrom-YamlScalar -Raw $rest
40+
continue
41+
}
42+
43+
# Value on subsequent indented lines (mapping or sequence) or null.
44+
if ($Context.Index -ge $lines.Count) {
45+
$map[[string]$key] = $null
46+
continue
47+
}
48+
49+
$next = $lines[$Context.Index]
50+
if ($next.Indent -le $Indent) {
51+
$map[[string]$key] = $null
52+
continue
53+
}
54+
55+
# Sequences are allowed to start at the same indent as the parent key in YAML.
56+
# We require the child to be indented strictly greater than the key here for clarity.
57+
$childIndent = $next.Indent
58+
$value = ConvertFrom-YamlNode -Context $Context -Indent $childIndent -Depth ($Depth + 1)
59+
$map[[string]$key] = $value
60+
}
61+
62+
if ($Context.AsHashtable) {
63+
return $map
64+
}
65+
66+
$obj = [pscustomobject]@{}
67+
foreach ($k in $map.Keys) {
68+
Add-Member -InputObject $obj -MemberType NoteProperty -Name $k -Value $map[$k]
69+
}
70+
return $obj
71+
}

src/functions/private/ConvertFrom-YamlNode.ps1

Lines changed: 0 additions & 274 deletions
Original file line numberDiff line numberDiff line change
@@ -38,277 +38,3 @@
3838

3939
return ConvertFrom-YamlMapping -Context $Context -Indent $Indent -Depth $Depth
4040
}
41-
42-
function ConvertFrom-YamlMapping {
43-
<#
44-
.SYNOPSIS
45-
Parses a YAML block-style mapping into a PSCustomObject or OrderedDictionary.
46-
#>
47-
[CmdletBinding()]
48-
[OutputType([System.Collections.Specialized.OrderedDictionary], [pscustomobject])]
49-
param(
50-
[Parameter(Mandatory)] [pscustomobject] $Context,
51-
[Parameter(Mandatory)] [int] $Indent,
52-
[Parameter(Mandatory)] [int] $Depth
53-
)
54-
55-
$lines = $Context.Lines
56-
$map = [ordered]@{}
57-
58-
while ($Context.Index -lt $lines.Count) {
59-
$line = $lines[$Context.Index]
60-
if ($line.Indent -lt $Indent) { break }
61-
if ($line.Indent -gt $Indent) {
62-
throw "ConvertFrom-Yaml: unexpected indentation at line $($line.Number)."
63-
}
64-
if ($line.Content.StartsWith('- ') -or $line.Content -eq '-') {
65-
# A sequence at the same indent as a mapping key is a sibling, not part of mapping.
66-
break
67-
}
68-
69-
$colonIdx = Find-YamlMappingColon -Content $line.Content
70-
if ($colonIdx -lt 0) {
71-
throw "ConvertFrom-Yaml: expected mapping key at line $($line.Number): '$($line.Content)'."
72-
}
73-
74-
$key = ConvertFrom-YamlScalar -Raw $line.Content.Substring(0, $colonIdx).Trim()
75-
$rest = $line.Content.Substring($colonIdx + 1).Trim()
76-
77-
$Context.Index++
78-
79-
if ($rest.Length -gt 0) {
80-
$map[[string]$key] = ConvertFrom-YamlScalar -Raw $rest
81-
continue
82-
}
83-
84-
# Value on subsequent indented lines (mapping or sequence) or null.
85-
if ($Context.Index -ge $lines.Count) {
86-
$map[[string]$key] = $null
87-
continue
88-
}
89-
90-
$next = $lines[$Context.Index]
91-
if ($next.Indent -le $Indent) {
92-
$map[[string]$key] = $null
93-
continue
94-
}
95-
96-
# Sequences are allowed to start at the same indent as the parent key in YAML.
97-
# We require the child to be indented strictly greater than the key here for clarity.
98-
$childIndent = $next.Indent
99-
$value = ConvertFrom-YamlNode -Context $Context -Indent $childIndent -Depth ($Depth + 1)
100-
$map[[string]$key] = $value
101-
}
102-
103-
if ($Context.AsHashtable) {
104-
return $map
105-
}
106-
107-
$obj = [pscustomobject]@{}
108-
foreach ($k in $map.Keys) {
109-
Add-Member -InputObject $obj -MemberType NoteProperty -Name $k -Value $map[$k]
110-
}
111-
return $obj
112-
}
113-
114-
function ConvertFrom-YamlSequence {
115-
<#
116-
.SYNOPSIS
117-
Parses a YAML block-style sequence into a PowerShell array.
118-
#>
119-
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseOutputTypeCorrectly', '',
120-
Justification = 'Comma-unary operator preserves array type; PSScriptAnalyzer misdetects as Object[].')]
121-
[CmdletBinding()]
122-
[OutputType([object[]])]
123-
param(
124-
[Parameter(Mandatory)] [pscustomobject] $Context,
125-
[Parameter(Mandatory)] [int] $Indent,
126-
[Parameter(Mandatory)] [int] $Depth
127-
)
128-
129-
$lines = $Context.Lines
130-
$list = [System.Collections.Generic.List[object]]::new()
131-
132-
while ($Context.Index -lt $lines.Count) {
133-
$line = $lines[$Context.Index]
134-
if ($line.Indent -lt $Indent) { break }
135-
if ($line.Indent -gt $Indent) {
136-
throw "ConvertFrom-Yaml: unexpected indentation at line $($line.Number)."
137-
}
138-
if (-not ($line.Content.StartsWith('- ') -or $line.Content -eq '-')) {
139-
break
140-
}
141-
142-
$afterDash = if ($line.Content.Length -ge 2) { $line.Content.Substring(2).TrimEnd() } else { '' }
143-
144-
if ($afterDash.Length -eq 0) {
145-
# Value on subsequent indented lines.
146-
$Context.Index++
147-
if ($Context.Index -ge $lines.Count) {
148-
$list.Add($null)
149-
continue
150-
}
151-
$next = $lines[$Context.Index]
152-
if ($next.Indent -le $Indent) {
153-
$list.Add($null)
154-
continue
155-
}
156-
$list.Add((ConvertFrom-YamlNode -Context $Context -Indent $next.Indent -Depth ($Depth + 1)))
157-
continue
158-
}
159-
160-
# Inline element: could be a scalar, or a mapping like "- key: value" with possibly more
161-
# mapping keys on following lines indented at "Indent + 2" (under the dash).
162-
$colonIdx = Find-YamlMappingColon -Content $afterDash
163-
if ($colonIdx -ge 0) {
164-
# Treat this as a single-line entry into a mapping. Build a synthetic line stream:
165-
# the current "key: value" line gets re-interpreted at indent (Indent + 2), and any
166-
# continuation lines at indent > (Indent + 2) belong to the same mapping.
167-
$childIndent = $Indent + 2
168-
$synthetic = [pscustomobject]@{
169-
Indent = $childIndent
170-
Content = $afterDash
171-
Number = $line.Number
172-
}
173-
# Replace current line with synthetic and recurse as a mapping.
174-
$Context.Lines[$Context.Index] = $synthetic
175-
$value = ConvertFrom-YamlMapping -Context $Context -Indent $childIndent -Depth ($Depth + 1)
176-
$list.Add($value)
177-
continue
178-
}
179-
180-
# Plain scalar element.
181-
$list.Add((ConvertFrom-YamlScalar -Raw $afterDash))
182-
$Context.Index++
183-
}
184-
185-
return , $list.ToArray()
186-
}
187-
188-
function Find-YamlMappingColon {
189-
<#
190-
.SYNOPSIS
191-
Returns the index of the unquoted `:` separator in a content line, or -1 if not found.
192-
193-
.DESCRIPTION
194-
The colon must be followed by whitespace or end-of-line for it to be a YAML mapping
195-
separator. Colons inside quoted strings are ignored.
196-
#>
197-
[CmdletBinding()]
198-
[OutputType([int])]
199-
param(
200-
[Parameter(Mandatory)]
201-
[AllowEmptyString()]
202-
[string] $Content
203-
)
204-
205-
$inSingle = $false
206-
$inDouble = $false
207-
for ($i = 0; $i -lt $Content.Length; $i++) {
208-
$c = $Content[$i]
209-
if ($c -eq '\' -and $inDouble) { $i++; continue }
210-
if ($c -eq "'" -and -not $inDouble) { $inSingle = -not $inSingle; continue }
211-
if ($c -eq '"' -and -not $inSingle) { $inDouble = -not $inDouble; continue }
212-
if ($c -eq ':' -and -not $inSingle -and -not $inDouble) {
213-
if ($i -eq $Content.Length - 1) { return $i }
214-
$next = $Content[$i + 1]
215-
if ($next -eq ' ' -or $next -eq "`t") { return $i }
216-
}
217-
}
218-
return -1
219-
}
220-
221-
function ConvertFrom-YamlScalar {
222-
<#
223-
.SYNOPSIS
224-
Converts a raw YAML scalar token into the appropriate PowerShell type.
225-
#>
226-
[CmdletBinding()]
227-
[OutputType([string], [bool], [int], [long], [double])]
228-
param(
229-
[Parameter(Mandatory)]
230-
[AllowEmptyString()]
231-
[string] $Raw
232-
)
233-
234-
$value = $Raw.Trim()
235-
236-
if ($value.Length -eq 0) { return $null }
237-
238-
# Quoted strings.
239-
if ($value.Length -ge 2 -and $value.StartsWith("'") -and $value.EndsWith("'")) {
240-
$inner = $value.Substring(1, $value.Length - 2)
241-
return ($inner -replace "''", "'")
242-
}
243-
if ($value.Length -ge 2 -and $value.StartsWith('"') -and $value.EndsWith('"')) {
244-
$inner = $value.Substring(1, $value.Length - 2)
245-
return (Expand-YamlDoubleQuoted -Text $inner)
246-
}
247-
248-
# Null literal (YAML 1.2.2 core schema): empty, ~, null only. Case-sensitive.
249-
if ($value -ceq '~' -or $value -ceq 'null') { return $null }
250-
251-
# Boolean literal (YAML 1.2.2 core schema): true / false only. Case-sensitive.
252-
if ($value -ceq 'true') { return $true }
253-
if ($value -ceq 'false') { return $false }
254-
255-
# Integer.
256-
$intVal = 0
257-
if ([int]::TryParse($value, [System.Globalization.NumberStyles]::Integer, [cultureinfo]::InvariantCulture, [ref] $intVal)) {
258-
return $intVal
259-
}
260-
$longVal = [long]0
261-
if ([long]::TryParse($value, [System.Globalization.NumberStyles]::Integer, [cultureinfo]::InvariantCulture, [ref] $longVal)) {
262-
return $longVal
263-
}
264-
265-
# Float.
266-
$dblVal = 0.0
267-
if ([double]::TryParse($value, [System.Globalization.NumberStyles]::Float, [cultureinfo]::InvariantCulture, [ref] $dblVal)) {
268-
return $dblVal
269-
}
270-
271-
# Plain string.
272-
return $value
273-
}
274-
275-
function Expand-YamlDoubleQuoted {
276-
<#
277-
.SYNOPSIS
278-
Expands escape sequences inside a double-quoted YAML scalar.
279-
#>
280-
[CmdletBinding()]
281-
[OutputType([string])]
282-
param(
283-
[Parameter(Mandatory)]
284-
[AllowEmptyString()]
285-
[string] $Text
286-
)
287-
288-
$sb = [System.Text.StringBuilder]::new()
289-
$i = 0
290-
while ($i -lt $Text.Length) {
291-
$c = $Text[$i]
292-
if ($c -eq '\' -and $i + 1 -lt $Text.Length) {
293-
$next = $Text[$i + 1]
294-
$expanded = $true
295-
switch ($next) {
296-
'n' { $null = $sb.Append("`n") }
297-
't' { $null = $sb.Append("`t") }
298-
'r' { $null = $sb.Append("`r") }
299-
'"' { $null = $sb.Append('"') }
300-
'\' { $null = $sb.Append('\') }
301-
'0' { $null = $sb.Append([char]0) }
302-
default { $expanded = $false }
303-
}
304-
305-
if ($expanded) {
306-
$i += 2
307-
continue
308-
}
309-
}
310-
$null = $sb.Append($c)
311-
$i++
312-
}
313-
return $sb.ToString()
314-
}

0 commit comments

Comments
 (0)