Skip to content

Commit ec17dd4

Browse files
tablackburnclaude
andauthored
style: enforce quoted literals, named parameters, validation; pin LF endings (#23)
* style: enforce quoted literals, named parameters, and parameter validation The repo's own powershell.instructions.md mandates three rules for in-tree code: - literal string parameter values are single-quoted - cmdlet calls use named parameters (when more than one arg is passed) - every function parameter has an appropriate validator This commit applies those rules to the template's first-party files — the example public/private functions, the test scaffolding, the meta helper modules, and build.ps1. Notable changes: - build.ps1 gains [ValidateNotNullOrEmpty()] on $Task - tests/MetaFixers.psm1 + tests/ManifestHelpers.psm1 gain validators on every parameter that didn't already have one - tests/Help.tests.ps1, tests/Manifest.tests.ps1, tests/Meta.tests.ps1 rename positional Split-Path / Join-Path / Get-Module / Get-Content calls to use named -Path / -ChildPath / -Name / -Pattern parameters - the example Get-{{Prefix}}Example.ps1 / its tests get the same treatment so newly-scaffolded modules start out compliant No behavioural changes. CI on the un-initialized template still skips build/test (the {{GUID}} placeholder is unparseable until init runs); parse-checks confirm every edited file is syntactically valid. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore: pin line endings to LF via .gitattributes The repo already stores every text file as LF in the index, but Windows clones with global core.autocrlf=true rewrite the working tree to CRLF on checkout. Subsequent edits made through tooling that writes LF (most editors and AI agents do) trigger 'LF will be replaced by CRLF the next time Git touches it' warnings on every commit. Pinning eol=lf in .gitattributes overrides the contributor's global autocrlf setting for this repo and silences the warning. PowerShell on Windows handles LF fine, and the devcontainer already runs Linux. NOTE: GitHub template repos are one-time copies at creation, so this fix does NOT propagate to repositories already created from this template. Existing downstream repos must apply the same .gitattributes change separately. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * style: remove unreachable IsNullOrEmpty guard in Split-SemVerString The previous commit added [ValidateNotNullOrEmpty()] to $VersionString, which makes the in-body IsNullOrEmpty check unreachable under normal parameter binding — both Copilot and CodeRabbit flagged it as dead code. Removing the redundant guard. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent d444b99 commit ec17dd4

10 files changed

Lines changed: 53 additions & 37 deletions

.gitattributes

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,10 @@
1+
# Normalize line endings to LF in both the repo and the working tree, regardless of
2+
# the contributor's global core.autocrlf setting. PowerShell on Windows handles LF
3+
# fine, and the devcontainer runs Linux. Without this, Windows clones with
4+
# core.autocrlf=true emit "LF will be replaced by CRLF the next time Git touches it"
5+
# warnings on every commit when files are edited by tooling that writes LF (most
6+
# editors and AI agents do).
7+
* text=auto eol=lf
8+
19
# The docs are generated by the build script and should be considered artifacts
210
docs/en-US/* linguist-generated

build.ps1

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ param(
4040
@()
4141
}
4242
})]
43+
[ValidateNotNullOrEmpty()]
4344
[string[]]$Task = 'default',
4445

4546
# Bootstrap dependencies

tests/Help.tests.ps1

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -57,10 +57,10 @@ BeforeDiscovery {
5757
}
5858

5959
# PowerShellBuild outputs to Output/<ModuleName>/<Version>/, override BHBuildOutput
60-
$projectRoot = Split-Path -Parent $PSScriptRoot
61-
$sourceManifest = Join-Path $projectRoot "$Env:BHProjectName/$Env:BHProjectName.psd1"
60+
$projectRoot = Split-Path -Path $PSScriptRoot -Parent
61+
$sourceManifest = Join-Path -Path $projectRoot -ChildPath "$Env:BHProjectName/$Env:BHProjectName.psd1"
6262
$moduleVersion = (Import-PowerShellDataFile -Path $sourceManifest).ModuleVersion
63-
$Env:BHBuildOutput = Join-Path $projectRoot "Output/$Env:BHProjectName/$moduleVersion"
63+
$Env:BHBuildOutput = Join-Path -Path $projectRoot -ChildPath "Output/$Env:BHProjectName/$moduleVersion"
6464

6565
# Define the path to the module manifest
6666
$moduleManifestFilename = $Env:BHProjectName + '.psd1'
@@ -72,18 +72,18 @@ BeforeDiscovery {
7272
'Classes'
7373
) | ForEach-Object {
7474
$path = Join-Path -Path $Env:BHBuildOutput -ChildPath $_
75-
if (Test-Path $path) {
75+
if (Test-Path -Path $path) {
7676
$global:CustomTypes += (Get-ChildItem -Path $path -Recurse -ErrorAction 'SilentlyContinue').BaseName
7777
}
7878
}
7979

8080
# Remove all versions of the module from the session. Pester can't handle multiple versions.
81-
Get-Module $Env:BHProjectName | Remove-Module -Force -ErrorAction 'Ignore'
81+
Get-Module -Name $Env:BHProjectName | Remove-Module -Force -ErrorAction 'Ignore'
8282
Import-Module -Name $moduleManifestPath -Verbose:$false -ErrorAction 'Stop'
8383

8484
# Get module commands
8585
$getCommandParameters = @{
86-
Module = (Get-Module $Env:BHProjectName)
86+
Module = (Get-Module -Name $Env:BHProjectName)
8787
CommandType = [System.Management.Automation.CommandTypes[]]'Cmdlet, Function' # Not alias
8888
}
8989
if ($PSVersionTable.PSVersion.Major -lt 6) {

tests/Manifest.tests.ps1

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -69,10 +69,10 @@ BeforeDiscovery {
6969
}
7070

7171
# PowerShellBuild outputs to Output/<ModuleName>/<Version>/, override BHBuildOutput
72-
$projectRoot = Split-Path -Parent $PSScriptRoot
73-
$sourceManifest = Join-Path $projectRoot "$Env:BHProjectName/$Env:BHProjectName.psd1"
72+
$projectRoot = Split-Path -Path $PSScriptRoot -Parent
73+
$sourceManifest = Join-Path -Path $projectRoot -ChildPath "$Env:BHProjectName/$Env:BHProjectName.psd1"
7474
$moduleVersion = (Import-PowerShellDataFile -Path $sourceManifest).ModuleVersion
75-
$Env:BHBuildOutput = Join-Path $projectRoot "Output/$Env:BHProjectName/$moduleVersion"
75+
$Env:BHBuildOutput = Join-Path -Path $projectRoot -ChildPath "Output/$Env:BHProjectName/$moduleVersion"
7676

7777
# Define the path to the module manifest
7878
$moduleManifestFilename = $Env:BHProjectName + '.psd1'
@@ -108,10 +108,10 @@ BeforeAll {
108108
}
109109

110110
# PowerShellBuild outputs to Output/<ModuleName>/<Version>/, override BHBuildOutput
111-
$projectRoot = Split-Path -Parent $PSScriptRoot
112-
$sourceManifest = Join-Path $projectRoot "$Env:BHProjectName/$Env:BHProjectName.psd1"
111+
$projectRoot = Split-Path -Path $PSScriptRoot -Parent
112+
$sourceManifest = Join-Path -Path $projectRoot -ChildPath "$Env:BHProjectName/$Env:BHProjectName.psd1"
113113
$moduleVersion = (Import-PowerShellDataFile -Path $sourceManifest).ModuleVersion
114-
$Env:BHBuildOutput = Join-Path $projectRoot "Output/$Env:BHProjectName/$moduleVersion"
114+
$Env:BHBuildOutput = Join-Path -Path $projectRoot -ChildPath "Output/$Env:BHProjectName/$moduleVersion"
115115

116116
# Define the path to the module manifest
117117
$moduleManifestFilename = $Env:BHProjectName + '.psd1'
@@ -135,12 +135,12 @@ BeforeAll {
135135
Import-Module -Name (Join-Path -Path $PSScriptRoot -ChildPath 'ManifestHelpers.psm1') -Verbose:$false -Force
136136

137137
$requirementsPath = Join-Path -Path $env:BHProjectPath -ChildPath 'requirements.psd1'
138-
$requirements = Import-PowerShellDataFile -Path $requirementsPath -ErrorAction Stop
138+
$requirements = Import-PowerShellDataFile -Path $requirementsPath -ErrorAction 'Stop'
139139

140140
# Parse the version from the changelog
141141
$changelogPath = Join-Path -Path $Env:BHProjectPath -ChildPath 'CHANGELOG.md'
142142
$changelogVersionPattern = '^##\s\\?\[(?<Version>(\d+\.){1,3}\d+)\\?\]' # Matches on a line that starts with '## [Version]' or '## \[Version\]'
143-
$changelogVersion = Get-Content $changelogPath | ForEach-Object {
143+
$changelogVersion = Get-Content -Path $changelogPath | ForEach-Object {
144144
if ($_ -match $changelogVersionPattern) {
145145
$changelogVersion = $matches.Version
146146
break

tests/ManifestHelpers.psm1

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,10 @@ function Split-SemVerString {
3636
[OutputType([hashtable])]
3737
param(
3838
[Parameter(Mandatory = $true)]
39+
[ValidateNotNullOrEmpty()]
3940
[string]$VersionString
4041
)
4142

42-
if ([string]::IsNullOrEmpty($VersionString)) {
43-
throw "VersionString cannot be empty or null"
44-
}
45-
4643
# Strip build metadata per SemVer 2.0.0 — it does not affect precedence and is
4744
# not valid for [System.Version], so it must be removed before further parsing.
4845
$coreVersion = ($VersionString -split '\+', 2)[0]
@@ -92,9 +89,11 @@ function Compare-SemVerPrerelease {
9289
[OutputType([int])]
9390
param(
9491
[Parameter(Mandatory = $true)]
92+
[ValidateNotNullOrEmpty()]
9593
[string]$FirstPrerelease,
9694

9795
[Parameter(Mandatory = $true)]
96+
[ValidateNotNullOrEmpty()]
9897
[string]$SecondPrerelease
9998
)
10099

@@ -208,13 +207,15 @@ function Test-VersionComparison {
208207
[OutputType([bool])]
209208
param(
210209
[Parameter(Mandatory = $true)]
210+
[ValidateNotNullOrEmpty()]
211211
[string]$FirstVersion,
212212

213213
[Parameter(Mandatory = $false)]
214214
[AllowNull()]
215215
[string]$FirstPrerelease,
216216

217217
[Parameter(Mandatory = $true)]
218+
[ValidateNotNullOrEmpty()]
218219
[string]$SecondVersion,
219220

220221
[Parameter(Mandatory = $false)]
@@ -325,9 +326,11 @@ function Test-VersionConstraint {
325326
[OutputType([bool])]
326327
param(
327328
[Parameter(Mandatory = $true)]
329+
[ValidateNotNullOrEmpty()]
328330
[string]$ManifestVersion,
329331

330332
[Parameter(Mandatory = $true)]
333+
[ValidateNotNullOrEmpty()]
331334
[string]$RequirementsVersion,
332335

333336
[Parameter(Mandatory = $true)]
@@ -337,14 +340,14 @@ function Test-VersionConstraint {
337340

338341
# Validate input versions are not empty
339342
if ([string]::IsNullOrWhiteSpace($ManifestVersion)) {
340-
throw "ManifestVersion cannot be empty or whitespace"
343+
throw 'ManifestVersion cannot be empty or whitespace'
341344
}
342345
if ([string]::IsNullOrWhiteSpace($RequirementsVersion)) {
343-
throw "RequirementsVersion cannot be empty or whitespace"
346+
throw 'RequirementsVersion cannot be empty or whitespace'
344347
}
345348

346-
$manifestParts = Split-SemVerString $ManifestVersion
347-
$requirementsParts = Split-SemVerString $RequirementsVersion
349+
$manifestParts = Split-SemVerString -VersionString $ManifestVersion
350+
$requirementsParts = Split-SemVerString -VersionString $RequirementsVersion
348351

349352
$comparisonParameters = @{
350353
FirstVersion = $requirementsParts.Version

tests/Meta.tests.ps1

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,11 @@ BeforeAll {
99
$projectRoot = $PSScriptRoot
1010
}
1111

12-
$allTextFiles = Get-TextFilesList $projectRoot
12+
$allTextFiles = Get-TextFilesList -Root $projectRoot
1313
$unicodeFilesCount = 0
1414
$totalTabsCount = 0
1515
foreach ($textFile in $allTextFiles) {
16-
if (Test-FileUnicode $textFile) {
16+
if (Test-FileUnicode -FileInfo $textFile) {
1717
$unicodeFilesCount++
1818
Write-Warning (
1919
"File $($textFile.FullName) contains 0x00 bytes." +
@@ -24,7 +24,7 @@ BeforeAll {
2424
$unicodeFilesCount | Should -Be 0
2525

2626
$fileName = $textFile.FullName
27-
(Get-Content $fileName -Raw) | Select-String "`t" | Foreach-Object {
27+
(Get-Content -Path $fileName -Raw) | Select-String -Pattern "`t" | Foreach-Object {
2828
Write-Warning (
2929
"There are tabs in $fileName." +
3030
' Use Fixer "Get-TextFilesList `$pwd | ConvertTo-SpaceIndentation".'

tests/MetaFixers.psm1

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ function ConvertTo-UTF8 {
2727
[OutputType([void])]
2828
param(
2929
[Parameter(Mandatory = $true, ValueFromPipeline = $true)]
30+
[ValidateNotNull()]
3031
[System.IO.FileInfo]$FileInfo
3132
)
3233

@@ -56,6 +57,7 @@ function ConvertTo-SpaceIndentation {
5657
[OutputType([void])]
5758
param(
5859
[Parameter(Mandatory = $true, ValueFromPipeline = $true)]
60+
[ValidateNotNull()]
5961
[System.IO.FileInfo]$FileInfo
6062
)
6163

@@ -85,6 +87,7 @@ function Get-TextFilesList {
8587
[OutputType([System.IO.FileInfo])]
8688
param(
8789
[Parameter(Mandatory = $true, ValueFromPipeline = $true)]
90+
[ValidateNotNullOrEmpty()]
8891
[string]$Root
8992
)
9093

@@ -159,10 +162,11 @@ function Get-UnicodeFilesList {
159162
[OutputType([System.IO.FileInfo])]
160163
param(
161164
[Parameter(Mandatory = $true)]
165+
[ValidateNotNullOrEmpty()]
162166
[string]$Root
163167
)
164168

165169
$root | Get-TextFilesList | Where-Object {
166-
Test-FileUnicode $_
170+
Test-FileUnicode -FileInfo $_
167171
}
168172
}

tests/Unit/Private/Invoke-{{Prefix}}Helper.tests.ps1

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,20 +17,20 @@ BeforeDiscovery {
1717
}
1818

1919
# PowerShellBuild outputs to Output/<ModuleName>/<Version>/
20-
$projectRoot = Split-Path -Parent (Split-Path -Parent (Split-Path -Parent $PSScriptRoot))
21-
$sourceManifest = Join-Path $projectRoot "$Env:BHProjectName/$Env:BHProjectName.psd1"
20+
$projectRoot = Split-Path -Path (Split-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -Parent) -Parent
21+
$sourceManifest = Join-Path -Path $projectRoot -ChildPath "$Env:BHProjectName/$Env:BHProjectName.psd1"
2222
$moduleVersion = (Import-PowerShellDataFile -Path $sourceManifest).ModuleVersion
23-
$Env:BHBuildOutput = Join-Path $projectRoot "Output/$Env:BHProjectName/$moduleVersion"
23+
$Env:BHBuildOutput = Join-Path -Path $projectRoot -ChildPath "Output/$Env:BHProjectName/$moduleVersion"
2424
}
2525

2626
BeforeAll {
2727
# Import the module from the build output
2828
$moduleManifestPath = Join-Path -Path $Env:BHBuildOutput -ChildPath "$Env:BHProjectName.psd1"
29-
Get-Module $Env:BHProjectName | Remove-Module -Force -ErrorAction 'Ignore'
29+
Get-Module -Name $Env:BHProjectName | Remove-Module -Force -ErrorAction 'Ignore'
3030
Import-Module -Name $moduleManifestPath -Force -ErrorAction 'Stop'
3131
}
3232

33-
InModuleScope $Env:BHProjectName {
33+
InModuleScope -ModuleName $Env:BHProjectName -ScriptBlock {
3434
Describe 'Invoke-{{Prefix}}Helper' {
3535

3636
Context 'Basic functionality' {

tests/Unit/Public/Get-{{Prefix}}Example.tests.ps1

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,16 @@ BeforeDiscovery {
1717
}
1818

1919
# PowerShellBuild outputs to Output/<ModuleName>/<Version>/
20-
$projectRoot = Split-Path -Parent (Split-Path -Parent (Split-Path -Parent $PSScriptRoot))
21-
$sourceManifest = Join-Path $projectRoot "$Env:BHProjectName/$Env:BHProjectName.psd1"
20+
$projectRoot = Split-Path -Path (Split-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -Parent) -Parent
21+
$sourceManifest = Join-Path -Path $projectRoot -ChildPath "$Env:BHProjectName/$Env:BHProjectName.psd1"
2222
$moduleVersion = (Import-PowerShellDataFile -Path $sourceManifest).ModuleVersion
23-
$Env:BHBuildOutput = Join-Path $projectRoot "Output/$Env:BHProjectName/$moduleVersion"
23+
$Env:BHBuildOutput = Join-Path -Path $projectRoot -ChildPath "Output/$Env:BHProjectName/$moduleVersion"
2424
}
2525

2626
BeforeAll {
2727
# Import the module from the build output
2828
$moduleManifestPath = Join-Path -Path $Env:BHBuildOutput -ChildPath "$Env:BHProjectName.psd1"
29-
Get-Module $Env:BHProjectName | Remove-Module -Force -ErrorAction 'Ignore'
29+
Get-Module -Name $Env:BHProjectName | Remove-Module -Force -ErrorAction 'Ignore'
3030
Import-Module -Name $moduleManifestPath -Force -ErrorAction 'Stop'
3131
}
3232

{{ModuleName}}/Public/Get-{{Prefix}}Example.ps1

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ function Get-{{Prefix}}Example {
3737
)
3838

3939
begin {
40-
Write-Verbose "Starting Get-{{Prefix}}Example"
40+
Write-Verbose 'Starting Get-{{Prefix}}Example'
4141
}
4242

4343
process {
@@ -51,6 +51,6 @@ function Get-{{Prefix}}Example {
5151
}
5252

5353
end {
54-
Write-Verbose "Completed Get-{{Prefix}}Example"
54+
Write-Verbose 'Completed Get-{{Prefix}}Example'
5555
}
5656
}

0 commit comments

Comments
 (0)