tests/Hygiene/Test-NamingConventions.ps1
|
<#
.SYNOPSIS Validates naming conventions for files and functions. .DESCRIPTION Checks that files and functions follow PowerShell best practices for naming conventions. .EXAMPLE .\Test-NamingConventions.ps1 #> [CmdletBinding()] param( [string]$ProjectRoot = (Split-Path (Split-Path $PSScriptRoot -Parent) -Parent) ) Write-Host "`n=== Naming Convention Validation ===" -ForegroundColor Cyan Write-Host "Analyzing project: $ProjectRoot`n" $issues = @() # Get all PowerShell files $psFiles = Get-ChildItem -Path $ProjectRoot -Filter "*.ps1" -Recurse -File -ErrorAction SilentlyContinue | Where-Object { $_.FullName -notmatch '[\\/]\.git[\\/]' } $psModules = Get-ChildItem -Path $ProjectRoot -Filter "*.psm1" -Recurse -File -ErrorAction SilentlyContinue | Where-Object { $_.FullName -notmatch '[\\/]\.git[\\/]' } $allPsFiles = $psFiles + $psModules Write-Host "Checking $($allPsFiles.Count) PowerShell files...`n" foreach ($file in $allPsFiles) { $relativePath = $file.FullName.Replace($ProjectRoot, "").TrimStart("\") $fileName = $file.BaseName # Check for spaces in filename if ($file.Name -match '\s') { $issues += [PSCustomObject]@{ Path = $relativePath Issue = "Contains spaces" Severity = "High" } } # Check for special characters if ($file.Name -match '[^a-zA-Z0-9\-_\.]') { $issues += [PSCustomObject]@{ Path = $relativePath Issue = "Contains special characters" Severity = "Medium" } } # Check for PascalCase (for non-test files) if ($fileName -notmatch '^[A-Z][a-zA-Z0-9]*(-[A-Z][a-zA-Z0-9]*)*$' -and $fileName -notmatch '\.Tests$' -and $fileName -notmatch '^(test|temp|debug)') { # Allow some exceptions if ($fileName -notmatch '^(README|LICENSE|CHANGELOG)$') { $issues += [PSCustomObject]@{ Path = $relativePath Issue = "Should use PascalCase (e.g., Get-UserData)" Severity = "Low" } } } # Check for Verb-Noun pattern in .ps1 files if ($file.Extension -eq ".ps1" -and $fileName -notmatch '^[A-Z][a-z]+-[A-Z]' -and $fileName -notmatch '\.Tests$' -and $fileName -notmatch '^(temp|test|debug)') { $issues += [PSCustomObject]@{ Path = $relativePath Issue = "Should follow Verb-Noun pattern (e.g., Get-Data, Show-Menu)" Severity = "Medium" } } } # Calculate compliance $totalFiles = $allPsFiles.Count $compliantFiles = $totalFiles - ($issues | Select-Object -Unique Path).Count $complianceRate = if ($totalFiles -gt 0) { [Math]::Round(($compliantFiles / $totalFiles) * 100, 2) } else { 100 } # Build result $result = [PSCustomObject]@{ ProjectRoot = $ProjectRoot TotalFiles = $totalFiles CompliantFiles = $compliantFiles NonCompliantFiles = $issues ComplianceRate = $complianceRate } # Display results Write-Host "=== Validation Results ===" -ForegroundColor Yellow Write-Host "Total Files Checked: $totalFiles" Write-Host "Compliant Files: $compliantFiles" Write-Host "Non-Compliant Files: $($issues.Count)" Write-Host "Compliance Rate: $complianceRate%" if ($issues.Count -gt 0) { Write-Host "`n⚠️ Naming Convention Issues:" -ForegroundColor Yellow $issues | Group-Object Severity | ForEach-Object { Write-Host "`n$($_.Name) Severity ($($_.Count)):" -ForegroundColor $( switch ($_.Name) { "High" { "Red" } "Medium" { "Yellow" } "Low" { "Cyan" } } ) $_.Group | Format-Table Path, Issue -AutoSize } } else { Write-Host "`n✅ All files follow naming conventions!" -ForegroundColor Green } # Export to JSON $outputPath = Join-Path $PSScriptRoot "naming_conventions_results.json" $result | ConvertTo-Json -Depth 5 | Out-File $outputPath Write-Host "`nResults exported to: $outputPath" -ForegroundColor Green return $result |