Skip to content

Commit e5ae9df

Browse files
authored
Support SuggestedCorrections property on DiagnosticRecord for script based rules (#1000)
* Populate SuggestedCorrections when using scriptule and make it public for easier construction in PowerShell * add documentation and test * use [Type]::new() constructor instead of new-object * Revert "use [Type]::new() constructor instead of new-object" Reason: this was only introduced in PowerShell v5 and therefore fails the PS v4 build and we should not give examples that do not work on any supported version This reverts commit aa90ea3.
1 parent f1222ef commit e5ae9df

File tree

5 files changed

+47
-7
lines changed

5 files changed

+47
-7
lines changed

Engine/Generic/DiagnosticRecord.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ public class DiagnosticRecord
1818
private DiagnosticSeverity severity;
1919
private string scriptPath;
2020
private string ruleSuppressionId;
21-
private List<CorrectionExtent> suggestedCorrections;
21+
private IEnumerable<CorrectionExtent> suggestedCorrections;
2222

2323
/// <summary>
2424
/// Represents a string from the rule about why this diagnostic was created.
@@ -89,6 +89,7 @@ public string RuleSuppressionID
8989
public IEnumerable<CorrectionExtent> SuggestedCorrections
9090
{
9191
get { return suggestedCorrections; }
92+
set { suggestedCorrections = value; }
9293
}
9394

9495
/// <summary>
@@ -108,7 +109,7 @@ public DiagnosticRecord()
108109
/// <param name="severity">The severity of this diagnostic</param>
109110
/// <param name="scriptPath">The full path of the script file being analyzed</param>
110111
/// <param name="suggestedCorrections">The correction suggested by the rule to replace the extent text</param>
111-
public DiagnosticRecord(string message, IScriptExtent extent, string ruleName, DiagnosticSeverity severity, string scriptPath, string ruleId = null, List<CorrectionExtent> suggestedCorrections = null)
112+
public DiagnosticRecord(string message, IScriptExtent extent, string ruleName, DiagnosticSeverity severity, string scriptPath, string ruleId = null, IEnumerable<CorrectionExtent> suggestedCorrections = null)
112113
{
113114
Message = message;
114115
RuleName = ruleName;

Engine/ScriptAnalyzer.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1267,6 +1267,7 @@ internal IEnumerable<DiagnosticRecord> GetExternalRecord(Ast ast, Token[] token,
12671267
IScriptExtent extent;
12681268
string message = string.Empty;
12691269
string ruleName = string.Empty;
1270+
IEnumerable<CorrectionExtent> suggestedCorrections;
12701271

12711272
if (psobject != null && psobject.ImmediateBaseObject != null)
12721273
{
@@ -1286,6 +1287,7 @@ internal IEnumerable<DiagnosticRecord> GetExternalRecord(Ast ast, Token[] token,
12861287
message = psobject.Properties["Message"].Value.ToString();
12871288
extent = (IScriptExtent)psobject.Properties["Extent"].Value;
12881289
ruleName = psobject.Properties["RuleName"].Value.ToString();
1290+
suggestedCorrections = (IEnumerable<CorrectionExtent>)psobject.Properties["SuggestedCorrections"].Value;
12891291
}
12901292
catch (Exception ex)
12911293
{
@@ -1295,7 +1297,7 @@ internal IEnumerable<DiagnosticRecord> GetExternalRecord(Ast ast, Token[] token,
12951297

12961298
if (!string.IsNullOrEmpty(message))
12971299
{
1298-
diagnostics.Add(new DiagnosticRecord(message, extent, ruleName, severity, filePath));
1300+
diagnostics.Add(new DiagnosticRecord(message, extent, ruleName, severity, filePath) { SuggestedCorrections = suggestedCorrections });
12991301
}
13001302
}
13011303
}

ScriptRuleDocumentation.md

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ Param
5151
)
5252
```
5353

54-
- DiagnosticRecord should have four properties: Message, Extent, RuleName and Severity
54+
- DiagnosticRecord should have at least four properties: Message, Extent, RuleName and Severity
5555

5656
``` PowerShell
5757
$result = [Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic.DiagnosticRecord[]]@{
@@ -61,6 +61,27 @@ $result = [Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic.DiagnosticRecord[
6161
"Severity" = "Warning"
6262
}
6363
```
64+
Optionally, since version 1.17.0, a `SuggestedCorrections` property of type `IEnumerable<CorrectionExtent>` can also be added in script rules but care must be taken that the type is correct, an example is:
65+
```powershell
66+
[int]$startLineNumber = $ast.Extent.StartLineNumber
67+
[int]$endLineNumber = $ast.Extent.EndLineNumber
68+
[int]$startColumnNumber = $ast.Extent.StartColumnNumber
69+
[int]$endColumnNumber = $ast.Extent.EndColumnNumber
70+
[string]$correction = 'Correct text that replaces Extent text'
71+
[string]$file = $MyInvocation.MyCommand.Definition
72+
[string]$optionalDescription = 'Useful but optional description text'
73+
$correctionExtent = New-Object 'Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic.CorrectionExtent' $startLineNumber,$endLineNumber,$startColumnNumber,$endColumnNumber,$correction,$description
74+
$suggestedCorrections = New-Object System.Collections.ObjectModel.Collection['Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic.CorrectionExtent']
75+
$suggestedCorrections.add($correctionExtent) | out-null
76+
77+
[Microsoft.Windows.Powershell.ScriptAnalyzer.Generic.DiagnosticRecord]@{
78+
"Message" = "This is a rule with a suggested correction"
79+
"Extent" = $ast.Extent
80+
"RuleName" = $PSCmdlet.MyInvocation.InvocationName
81+
"Severity" = "Warning"
82+
"SuggestedCorrections" = $suggestedCorrections
83+
}
84+
```
6485

6586
- Make sure you export the function(s) at the end of the script using Export-ModuleMember
6687

Tests/Engine/CustomizedRule.tests.ps1

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,19 @@ Describe "Test importing correct customized rules" {
149149
$violations[0].ScriptPath | Should -Be $expectedScriptPath
150150
}
151151

152+
It "will set SuggestedCorrections" {
153+
$violations = Invoke-ScriptAnalyzer $directory\TestScript.ps1 -CustomizedRulePath $directory\samplerule
154+
$expectedScriptPath = Join-Path $directory 'TestScript.ps1'
155+
$violations[0].SuggestedCorrections | Should -Not -BeNullOrEmpty
156+
$violations[0].SuggestedCorrections.StartLineNumber | Should -Be 1
157+
$violations[0].SuggestedCorrections.EndLineNumber | Should -Be 2
158+
$violations[0].SuggestedCorrections.StartColumnNumber | Should -Be 3
159+
$violations[0].SuggestedCorrections.EndColumnNumber | Should -Be 4
160+
$violations[0].SuggestedCorrections.Text | Should -Be 'text'
161+
$violations[0].SuggestedCorrections.File | Should -Be 'filePath'
162+
$violations[0].SuggestedCorrections.Description | Should -Be 'description'
163+
}
164+
152165
if (!$testingLibraryUsage)
153166
{
154167
It "will show the custom rule in the results when given a rule folder path with trailing backslash" {

Tests/Engine/samplerule/samplerule.psm1

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,12 @@ function Measure-RequiresRunAsAdministrator
2828
[System.Management.Automation.Language.ScriptBlockAst]
2929
$testAst
3030
)
31-
$dr = New-Object `
31+
$l=(new-object System.Collections.ObjectModel.Collection["Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic.CorrectionExtent"])
32+
$c = (new-object Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic.CorrectionExtent 1,2,3,4,'text','filePath','description')
33+
$l.Add($c)
34+
$dr = New-Object `
3235
-Typename "Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic.DiagnosticRecord" `
33-
-ArgumentList "This is help",$ast.Extent,$PSCmdlet.MyInvocation.InvocationName,Warning,$null
34-
return @($dr)
36+
-ArgumentList "This is help",$ast.Extent,$PSCmdlet.MyInvocation.InvocationName,Warning,$null,$null,$l
37+
return $dr
3538
}
3639
Export-ModuleMember -Function Measure*

0 commit comments

Comments
 (0)