Skip to content

Commit f68504f

Browse files
committed
Update docs test
1 parent d04cdc6 commit f68504f

2 files changed

Lines changed: 150 additions & 35 deletions

File tree

Lines changed: 148 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,167 @@
11
Describe "Validate rule documentation files" {
22
BeforeAll {
3-
$ruleDocDirectory = Join-Path $PSScriptRoot '../../docs/Rules'
4-
$docs = Get-ChildItem $ruleDocDirectory/*.md -Exclude README.md |
5-
ForEach-Object { "PS" + $_.BaseName} | Sort-Object
3+
$ruleDocDirectory = 'C:\Git\PS-Src\PSScriptAnalyzer\docs\Rules' #Join-Path $PSScriptRoot '../../docs/Rules'
4+
$docInfoList = @{}
5+
Get-ChildItem $ruleDocDirectory/*.md -Exclude README.md |
6+
ForEach-Object {
7+
$sev = Select-String -Path $_ -Pattern '\*\*Severity Level: (?<sev>\w+)\*\*'
8+
$def = Select-String -Path $_ -Pattern '\*\*Default state: (?<def>\w+\s?\w+)\*\*'
9+
$docInfoList.Add(('PS' + $_.BaseName), [pscustomobject]@{
10+
FileName = $_.Name
11+
Severity = $sev.Matches.Groups.Where({$_.Name -eq 'sev'}).Value
12+
DefState = $def.Matches.Groups.Where({$_.Name -eq 'def'}).Value
13+
})
14+
}
15+
#$docInfoList
616

7-
$rules = Get-ScriptAnalyzerRule | ForEach-Object RuleName | Sort-Object
17+
$ruleList = Get-ScriptAnalyzerRule |
18+
Sort-Object RuleName |
19+
Select-Object -Property RuleName, Severity
20+
#$ruleList
821

9-
$readmeLinks = @{}
10-
$readmeRules = Get-Content -LiteralPath $ruleDocDirectory/README.md |
11-
Foreach-Object { if ($_ -match '^\s*\|\s*\[([^]]+)\]\(([^)]+)\)\s*\|') {
12-
$ruleName = $matches[1] -replace '<sup>.</sup>$', ''
13-
$readmeLinks["$ruleName"] = $matches[2]
14-
"PS${ruleName}"
15-
}} |
16-
Sort-Object
22+
$ruleTable = @()
23+
$linkDefs = @{}
24+
$linkDefLine = @{}
25+
$usedRefs = @{}
26+
# Regex patterns.
27+
# Table rule cell: | [RuleName][ref] | Severity | Default state |... | -> capture name, ref, severity, defstate.
28+
$ruleRowRegex = '^\|\s*\[(?<name>[^\]]+)\]\[(?<ref>[^\]]+)\]\s*\|\s*(?<severity>[^|]+?)\s*\|(?<defstate>[^|]+?)\s*\|'
29+
# Link definition: [ref]: target (target may include an #anchor).
30+
$linkDefRegex = '^\[(?<ref>[^\]]+)\]:\s*(?<target>\S+)'
31+
# Any reference-style usage anywhere: ...][ref]...
32+
$refUsageRegex = '\]\[(?<ref>[^\]]+)\]'
33+
$lines = Get-Content 'C:\Git\PS-Src\PSScriptAnalyzer\docs\Rules\README.md'
34+
$lineNumber = 0
35+
foreach ($line in $lines) {
36+
$lineNumber++
37+
if ($line -match $ruleRowRegex) {
38+
$ruleTable += [pscustomobject]@{
39+
RowName = $Matches['name'].Trim()
40+
RuleName = 'PS' + $Matches['name'].Trim()
41+
Ref = $Matches['ref'].Trim()
42+
Severity = $Matches['severity'].Trim()
43+
DefState = $Matches['defstate'].Trim()
44+
Line = $lineNumber
45+
}
46+
}
1747

18-
$rulesDocsDiff = Compare-Object -ReferenceObject $rules -DifferenceObject $docs -SyncWindow 25
19-
$rulesReadmeDiff = Compare-Object -ReferenceObject $rules -DifferenceObject $readmeRules -SyncWindow 25
48+
if ($line -match $linkDefRegex) {
49+
$ref = $Matches['ref'].Trim()
50+
$linkDefs[$ref] = $Matches['target'].Trim()
51+
$linkDefLine[$ref] = $lineNumber
52+
}
53+
54+
# Collect every reference usage (table rows and prose) for orphan detection.
55+
foreach ($match in [regex]::matches($line, $refUsageRegex)) {
56+
$usedRefs[$match.Groups['ref'].Value] = 1
57+
}
58+
}
2059
}
2160

22-
It "Every rule must have a rule documentation file" {
23-
$rulesDocsDiff | Where-Object SideIndicator -eq "<=" | Foreach-Object InputObject | Should -BeNullOrEmpty
61+
#########################################################################################
62+
63+
It 'Every rule documentation file must be a defined rule' {
64+
$result = $true
65+
foreach ($rule in $docInfoList.Keys) {
66+
if ($rule -notin $ruleList.RuleName) {
67+
Write-Host "Rule not defined for file: $($docInfoList[$rule].FileName)"
68+
$result = $false
69+
}
70+
}
71+
$result | Should -Be $true
72+
}
73+
74+
It 'Every defined rule must have a documentation file' {
75+
$result = $true
76+
foreach ($rule in $ruleList.RuleName) {
77+
if ($rule -notin $docInfoList.Keys) {
78+
Write-Host "Missing documentation file for rule: $($rule)"
79+
$result = $false
80+
}
81+
}
82+
$result | Should -Be $true
2483
}
25-
It "Every rule documentation file must have a corresponding rule" {
26-
$rulesDocsDiff | Where-Object SideIndicator -eq "=>" | Foreach-Object InputObject | Should -BeNullOrEmpty
84+
85+
It 'Every rule doc must have the correct defined severity' {
86+
$result = $true
87+
foreach ($rule in $ruleList) {
88+
if ($rule.Severity -ne $docInfoList[$rule.RuleName].Severity) {
89+
Write-Host "Severity mismatch for rule: $($rule.RuleName). Defined: $($rule.Severity), Doc: $($docInfoList[$rule.RuleName].Severity)"
90+
$result = $false
91+
}
92+
}
93+
$result | Should -Be $true
2794
}
2895

29-
It "Every rule must have an entry in the rule documentation README.md file" {
30-
$rulesReadmeDiff | Where-Object SideIndicator -eq "<=" | Foreach-Object InputObject | Should -BeNullOrEmpty
96+
It 'Every rule in the table must have the correct severity and default state' {
97+
$result = $true
98+
foreach ($ruleRow in $ruleTable) {
99+
$definedRule = $ruleList | Where-Object { $_.RuleName -eq $ruleRow.RuleName }
100+
if ($null -eq $definedRule) {
101+
Write-Host "Rule in table not found in defined rules: $($ruleRow.RowName)"
102+
$result = $false
103+
continue
104+
}
105+
if ($ruleRow.RuleName -notin $docInfoList.Keys) {
106+
Write-Host "Rule in table not found in documentation: $($ruleRow.RowName)"
107+
$result = $false
108+
continue
109+
}
110+
$docInfo = $docInfoList[$ruleRow.RuleName]
111+
if ($ruleRow.Severity -ne $docInfo.Severity) {
112+
Write-Host "Severity mismatch for rule: $($ruleRow.RowName). Table: $($ruleRow.Severity), Doc: $($docInfo.Severity)"
113+
$result = $false
114+
}
115+
if ($ruleRow.DefState -ne $docInfo.DefState) {
116+
Write-Host "Default state mismatch for rule: $($ruleRow.RowName). Table: $($ruleRow.DefState), Doc: $($docInfo.DefState)"
117+
$result = $false
118+
}
119+
}
120+
$result | Should -Be $true
31121
}
32-
It "Every entry in the rule documentation README.md file must correspond to a rule" {
33-
$rulesReadmeDiff | Where-Object SideIndicator -eq "=>" | Foreach-Object InputObject | Should -BeNullOrEmpty
122+
123+
It 'Every link definition must be used at least once' {
124+
$result = $true
125+
foreach ($ref in $linkDefs.Keys) {
126+
if ($ref -notin $usedRefs.Keys) {
127+
Write-Host "Unused link definition: $ref (defined at line $($linkDefLine[$ref]))"
128+
$result = $false
129+
}
130+
}
131+
$result | Should -Be $true
34132
}
35133

36-
It "Every entry in the rule documentation README.md file must have a valid link to the documentation file" {
37-
foreach ($key in $readmeLinks.Keys) {
38-
$link = $readmeLinks[$key]
39-
$filePath = Join-Path $ruleDocDirectory $link
40-
$filePath | Should -Exist
134+
It 'Every link definition that points to a rule must have a matching rule in the table' {
135+
$result = $true
136+
foreach ($ref in $linkDefs.Keys) {
137+
$target = $linkDefs[$ref]
138+
$isRuleFile = $targetFile -match '\.md$' -and $targetFile -notmatch '/'
139+
140+
if ($isRuleFile) {
141+
# A rule-page link definition with no matching table row.
142+
if ($ref -notin $ruleTable.Ref) {
143+
Write-Host "Orphan rule target: Link definition [$ref] -> '$target' has no matching rule in the table (defined at line $($linkDefLine[$ref]))."
144+
$result = $false
145+
}
146+
}
41147
}
148+
$result | Should -Be $true
42149
}
43150

44-
It "Every rule name in the rule documentation README.md file must match the documentation file's basename" {
45-
foreach ($key in $readmeLinks.Keys) {
46-
$link = $readmeLinks[$key]
47-
$filePath = Join-Path $ruleDocDirectory $link
48-
$fileName = Split-Path $filePath -Leaf
49-
$fileName | Should -BeExactly "${key}.md"
151+
It 'Every link definition target must have a valid file path' {
152+
$result = $true
153+
foreach ($ref in $linkDefs.Keys) {
154+
$target = $linkDefs[$ref]
155+
$isRuleFile = $targetFile -match '\.md$' -and $targetFile -notmatch '/'
156+
157+
if ($isRuleFile) {
158+
# A rule-page link definition with no matching table row.
159+
if ($target -notin $ruleTable.FileName) {
160+
Write-Host "Orphan rule target: Link definition [$ref] -> '$target' has no matching rule file."
161+
$result = $false
162+
}
163+
}
50164
}
165+
$result | Should -Be $true
51166
}
52167
}

docs/Rules/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
description: List of PSScriptAnalyzer rules
3-
ms.date: 07/22/2026
3+
ms.date: 07/24/2026
44
ms.topic: reference
55
title: List of PSScriptAnalyzer rules
66
---
@@ -48,7 +48,7 @@ title: List of PSScriptAnalyzer rules
4848
| [AvoidUsingEmptyCatchBlock][28] | Warning | Always enabled | |
4949
| [AvoidUsingInvokeExpression][29] | Warning | Always enabled | |
5050
| [AvoidUsingPlainTextForPassword][30] | Warning | Always enabled | |
51-
| [AvoidUsingPositionalParameters][31] | Information | Always enabled | |
51+
| [AvoidUsingPositionalParameters][31] | Information | Enabled | |
5252
| [AvoidUsingUsernameAndPasswordParams][32] | Error | Always enabled | |
5353
| [AvoidUsingWMICmdlet][33] | Warning | Always enabled | |
5454
| [AvoidUsingWriteHost][34] | Warning | Always enabled | |

0 commit comments

Comments
 (0)