Skip to content

Commit 9c93673

Browse files
authored
[CI Example Analyzer] Show line for ExampleIssues (#19111)
* Show line for exampleissues * update * Update Debugging-StaticAnalysis-Errors.md * Update Debugging-StaticAnalysis-Errors.md * revision * revise rule name * update
1 parent 8d709bd commit 9c93673

File tree

7 files changed

+425
-322
lines changed

7 files changed

+425
-322
lines changed

documentation/Debugging-StaticAnalysis-Errors.md

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ Our StaticAnalysis tools help us ensure our modules follow PowerShell guidelines
99
- [Signature Issues](#signature-issues)
1010
- [Help Issues](#help-issues)
1111
- [Example Issues](#example-issues)
12+
- [Troubleshotting Example Issues](#troubleshotting-example-issues)
1213

1314
## How to know if you have a StaticAnalysis Error
1415
If your build is failing, click on the Jenkins job inside the PR (marked as "Default" within checks). Then check the Console Output within the Jenkins job. If you have this error, then you have failed StaticAnalysis:
@@ -63,4 +64,39 @@ Example issues occur when your changed markdown files in the `help` folder (_e.g
6364
- Copy each of the errors you would like to suppress directly from the ExampleIssues.csv file output in the CI pipeline artifacts
6465
- Push the changes to the .csv file and ensure the errors no longer show up in the `ExampleIssues.csv` file output from the CI pipeline artifacts.
6566

66-
If you have unexpected errors, please check whether you have splitted outputs from codes. If outputs cannot be separated from codes, then please add the tag `<!-- Skip: Output cannot be splitted from code -->` to the next line of the example title and in front of the code block.
67+
## Troubleshotting Example Issues
68+
### Scenario 1: Unexpected errors caused by the mixture of outputs and codes
69+
PowerShell code and output are required to be in sepreated code blocks (```). If you have put outputs in the code block, then the outputs will be recognized as invalid PowerShell syntax. Please make sure you have splitted outputs from codes. The following shows the correct scene. Note that if the example has no output, you don't need to add an output block.
70+
### Example: Codes and outputs are split correctly
71+
````
72+
```powershell
73+
Get-AzConfig -EnableDataCollection
74+
```
75+
76+
```output
77+
Key Value Applies To Scope Help Message
78+
--- ----- ---------- ----- ------------
79+
EnableDataCollection False Az CurrentUser When enabled, Azure PowerShell cmdlets send telemetry data to Microsoft to improve the custom…
80+
```
81+
````
82+
If outputs cannot be separated from codes, then please add the tag `<!-- Skip: Output cannot be splitted from code -->` to the next line of the example title and in front of the code block. The following is an example.
83+
### Example: Add skip tag to the example whose outputs cannot be separated from codes
84+
````
85+
<!-- Skip: Output cannot be splitted from code -->
86+
```powershell
87+
$Context = Get-AzBatchAccountKey -AccountName myaccount
88+
$Context.PrimaryAccountKey
89+
ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMN==
90+
$Context.SecondaryAccountKey
91+
ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMN==
92+
```
93+
````
94+
95+
### Scenario 2: Unexpected errors caused by unpaired quotes or brackets
96+
Please check whether you have matched the correct number of **quotes** and **brackets**. The common error messages in this scenario are as follows.
97+
- MissingEndParenthesisInExpression: Missing closing ')' in expression.
98+
- MissingEndCurlyBrace: Missing closing '}' in statement block or type definition.
99+
- MissingArrayIndexExpression: Array index expression is missing or not valid.
100+
- UnexpectedToken: Unexpected token xxx. (Check whether you have missed or added extra quote)
101+
102+
In this scenario, many other unreasonable errors will occur. Leave them alone. Just make sure you have correct the number of **quotes** and **brackets** and rerun the CI verification.

tools/StaticAnalysis/ExampleAnalyzer/AnalyzeRules/CommandName.psm1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ function Measure-CommandName {
113113
}
114114
$ModuleCmdletExNum = $($CommandParameterPair[$i].ModuleCmdletExNum)
115115
$Result = [Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic.DiagnosticRecord]@{
116-
Message = "$ModuleCmdletExNum-#@#$Message#@#$Remediation";
116+
Message = "$Message#@#$Remediation";
117117
Extent = $Asts[$i].Extent;
118118
RuleName = $RuleName;
119119
Severity = $Severity

tools/StaticAnalysis/ExampleAnalyzer/AnalyzeRules/ParameterNameAndValue.psm1

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -699,43 +699,43 @@ function Measure-ParameterNameAndValue {
699699
[System.Management.Automation.Language.Ast[]]$Asts = $ScriptBlockAst.FindAll($Predicate, $false)
700700
for ($i = 0; $i -lt $Asts.Count; $i++) {
701701
if ($global:CommandParameterPair[$i].ParameterName -eq "" -and $global:CommandParameterPair[$i].ExpressionToParameter -eq "") {
702-
$Message = "$($CommandParameterPair[$i].ModuleCmdletExNum)-#@#$($CommandParameterPair[$i].CommandName) has a parameter not in the same ParameterSet as others."
702+
$Message = "$($CommandParameterPair[$i].CommandName) has a parameter not in the same ParameterSet as others."
703703
$RuleName = [RuleNames]::Unknown_Parameter_Set
704704
$Severity = "Error"
705705
$RuleSuppressionID = "5010"
706706
$Remediation = "Make sure the parameters are from the same parameter set."
707707
}
708708
elseif ($global:CommandParameterPair[$i].ExpressionToParameter -eq "") {
709-
$Message = "$($CommandParameterPair[$i].ModuleCmdletExNum)-#@#$($CommandParameterPair[$i].CommandName) -$($CommandParameterPair[$i].ParameterName) is not a valid parameter name."
709+
$Message = "$($CommandParameterPair[$i].CommandName) -$($CommandParameterPair[$i].ParameterName) is not a valid parameter name."
710710
$RuleName = [RuleNames]::Invalid_Parameter_Name
711711
$Severity = "Error"
712712
$RuleSuppressionID = "5011"
713713
$Remediation = "Check validity of the parameter -$($CommandParameterPair[$i].ParameterName)."
714714
}
715715
elseif ($global:CommandParameterPair[$i].ExpressionToParameter -eq "<duplicate>") {
716-
$Message = "$($CommandParameterPair[$i].ModuleCmdletExNum)-#@#$($CommandParameterPair[$i].CommandName) -$($CommandParameterPair[$i].ParameterName) appeared more than once."
716+
$Message = "$($CommandParameterPair[$i].CommandName) -$($CommandParameterPair[$i].ParameterName) appeared more than once."
717717
$RuleName = [RuleNames]::Duplicate_Parameter_Name
718718
$Severity = "Error"
719719
$RuleSuppressionID = "5012"
720720
$Remediation = "Remove redundant parameter -$($CommandParameterPair[$i].ParameterName)."
721721
}
722722
elseif ($null -eq $global:CommandParameterPair[$i].ExpressionToParameter) {
723-
$Message = "$($CommandParameterPair[$i].ModuleCmdletExNum)-#@#$($CommandParameterPair[$i].CommandName) -$($CommandParameterPair[$i].ParameterName) must be assigned with a value."
723+
$Message = "$($CommandParameterPair[$i].CommandName) -$($CommandParameterPair[$i].ParameterName) must be assigned with a value."
724724
$RuleName = [RuleNames]::Unassigned_Parameter
725725
$Severity = "Error"
726726
$RuleSuppressionID = "5013"
727727
$Remediation = "Assign value for the parameter -$($CommandParameterPair[$i].ParameterName)."
728728
}
729729
elseif ($global:CommandParameterPair[$i].ExpressionToParameter.EndsWith(" is a null-valued parameter value.")) {
730-
$Message = "$($CommandParameterPair[$i].ModuleCmdletExNum)-#@#$($CommandParameterPair[$i].CommandName) -$($CommandParameterPair[$i].ParameterName) $($CommandParameterPair[$i].ExpressionToParameter)"
730+
$Message = "$($CommandParameterPair[$i].CommandName) -$($CommandParameterPair[$i].ParameterName) $($CommandParameterPair[$i].ExpressionToParameter)"
731731
$RuleName = [RuleNames]::Unassigned_Variable
732732
$Severity = "Warning"
733733
$RuleSuppressionID = "5110"
734734
$variable = $CommandParameterPair[$i].ExpressionToParameter -replace " is a null-valued parameter value."
735735
$Remediation = "Assign value for $variable."
736736
}
737737
elseif ($global:CommandParameterPair[$i].ParameterName -eq "<unknown>") {
738-
$Message = "$($CommandParameterPair[$i].ModuleCmdletExNum)-#@#$($CommandParameterPair[$i].CommandName) $($CommandParameterPair[$i].ExpressionToParameter) is not explicitly assigned to a parameter."
738+
$Message = "$($CommandParameterPair[$i].CommandName) $($CommandParameterPair[$i].ExpressionToParameter) is not explicitly assigned to a parameter."
739739
$RuleName = [RuleNames]::Unbinded_Expression
740740
$Severity = "Error"
741741
$RuleSuppressionID = "5014"
@@ -744,7 +744,7 @@ function Measure-ParameterNameAndValue {
744744
else {
745745
$ExpressionToParameter = ($CommandParameterPair[$i].ExpressionToParameter -split "-#-")[0]
746746
$ExpectedType = ($CommandParameterPair[$i].ExpressionToParameter -split "-#-")[1]
747-
$Message = "$($CommandParameterPair[$i].ModuleCmdletExNum)-#@#$($CommandParameterPair[$i].CommandName) -$($CommandParameterPair[$i].ParameterName) $ExpressionToParameter is not an expected parameter value type."
747+
$Message = "$($CommandParameterPair[$i].CommandName) -$($CommandParameterPair[$i].ParameterName) $ExpressionToParameter is not an expected parameter value type."
748748
$RuleName = [RuleNames]::Mismatched_Parameter_Value_Type
749749
$Severity = "Warning"
750750
$RuleSuppressionID = "5111"

tools/StaticAnalysis/ExampleAnalyzer/ExampleIssue.cs

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ public class ExampleIssue : IReportRecord
2424
public string Module { get; set; }
2525
public string Cmdlet { get; set; }
2626
public int Example { get; set; }
27+
public string Line { get; set; }
2728
public string RuleName { get; set; }
2829
public string Extent { get; set; }
2930
public int ProblemId { get; set; }
@@ -32,13 +33,13 @@ public class ExampleIssue : IReportRecord
3233
public string Remediation { get; set; }
3334
public string PrintHeaders()
3435
{
35-
return "\"Module\",\"Cmdlet\",\"Example\",\"RuleName\",\"ProblemId\",\"Severity\",\"Description\",\"Extent\",\"Remediation\"";
36+
return "\"Module\",\"Cmdlet\",\"Example\",\"Line\",\"RuleName\",\"ProblemId\",\"Severity\",\"Description\",\"Extent\",\"Remediation\"";
3637
}
3738

3839
public string FormatRecord()
3940
{
40-
return string.Format("\"{0}\",\"{1}\",\"{2}\",\"{3}\",\"{4}\",\"{5}\",\"{6}\",\"{7}\",\"{8}\"",
41-
Module, Cmdlet, Example, RuleName, ProblemId, Severity, Description, Extent, Remediation);
41+
return string.Format("\"{0}\",\"{1}\",\"{2}\",\"{3}\",\"{4}\",\"{5}\",\"{6}\",\"{7}\",\"{8}\",\"{9}\"",
42+
Module, Cmdlet, Example, Line, RuleName, ProblemId, Severity, Description, Extent, Remediation);
4243
}
4344

4445
// The code that excludes exceptions is in tools/StaticAnalysis/ExampleAnalyzer/utils.ps1 Get-NonExceptionRecord.
@@ -51,28 +52,30 @@ public bool Match(IReportRecord other)
5152
result = (record.Module == Module)&&
5253
(record.Cmdlet == Cmdlet)&&
5354
(record.Example == Example)&&
55+
(record.Line == Line)&&
5456
(record.Description == Description);
5557
}
5658
return result;
5759
}
5860

5961
public IReportRecord Parse(string line)
6062
{
61-
var matcher = "\"([^\"]*)\",\"([^\"]*)\",\"([^\"]*)\",\"([^\"]*)\",\"([^\"]*)\",\"([^\"]*)\",\"([^\"]*)\",\"([^\"]*)\",\"([^\"]*)\"";
63+
var matcher = "\"([^\"]*)\",\"([^\"]*)\",\"([^\"]*)\",\"([^\"]*)\",\"([^\"]*)\",\"([^\"]*)\",\"([^\"]*)\",\"([^\"]*)\",\"([^\"]*)\",\"([^\"]*)\"";
6264
var match = Regex.Match(line, matcher);
63-
if (!match.Success || match.Groups.Count < 10)
65+
if (!match.Success || match.Groups.Count < 11)
6466
{
6567
throw new InvalidOperationException(string.Format("Could not parse '{0}' as ExampleIssue record", line));
6668
}
6769
Module = match.Groups[1].Value;
6870
Cmdlet = match.Groups[2].Value;
6971
Example = int.Parse(match.Groups[3].Value);
70-
RuleName = match.Groups[4].Value;
71-
ProblemId = int.Parse(match.Groups[5].Value);
72-
Severity = int.Parse(match.Groups[6].Value);
73-
Description = match.Groups[7].Value;
74-
Extent = match.Groups[8].Value;
75-
Remediation = match.Groups[9].Value;
72+
Line = match.Groups[4].Value;
73+
RuleName = match.Groups[5].Value;
74+
ProblemId = int.Parse(match.Groups[6].Value);
75+
Severity = int.Parse(match.Groups[7].Value);
76+
Description = match.Groups[8].Value;
77+
Extent = match.Groups[9].Value;
78+
Remediation = match.Groups[10].Value;
7679
return this;
7780
}
7881
}

tools/StaticAnalysis/ExampleAnalyzer/Measure-MarkdownOrScript.ps1

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ param (
1717
[string[]]$ScriptPaths,
1818
[Parameter(HelpMessage = "PSScriptAnalyzer custom rules paths. Empty for current path. Supports wildcard.")]
1919
[string[]]$RulePaths,
20+
[string]$CodeMapPath,
2021
[switch]$Recurse,
2122
[switch]$IncludeDefaultRules,
2223
[string]$OutputFolder = "$PSScriptRoot\..\..\..\artifacts\StaticAnalysisResults\ExampleAnalysis",
@@ -28,16 +29,18 @@ param (
2829
. $PSScriptRoot\utils.ps1
2930

3031
$analysisResultsTable = @()
31-
32-
# Clean caches, remove files in "output" folder
33-
if ($OutputScriptsInFile.IsPresent) {
34-
Remove-Item $OutputFolder\TempScript.ps1 -ErrorAction SilentlyContinue
35-
Remove-Item $PSScriptRoot\..\..\..\artifacts\StaticAnalysisResults\ExampleIssues.csv -ErrorAction SilentlyContinue
36-
Remove-Item $OutputFolder -ErrorAction SilentlyContinue
37-
}
32+
$codeMap = @()
33+
$totalLine = 1
3834

3935
# Find examples in "help\*.md", output ".ps1"
4036
if ($PSCmdlet.ParameterSetName -eq "Markdown") {
37+
# Clean caches, remove files in "output" folder
38+
if ($OutputScriptsInFile.IsPresent) {
39+
Remove-Item $OutputFolder\TempScript.ps1 -ErrorAction SilentlyContinue
40+
Remove-Item $OutputFolder\TempCodeMap.csv -ErrorAction SilentlyContinue
41+
Remove-Item $PSScriptRoot\..\..\..\artifacts\StaticAnalysisResults\ExampleIssues.csv -ErrorAction SilentlyContinue
42+
Remove-Item $OutputFolder -ErrorAction SilentlyContinue
43+
}
4144
$null = New-Item -ItemType Directory -Path $OutputFolder -ErrorAction SilentlyContinue
4245
$null = New-Item -ItemType File $OutputFolder\TempScript.ps1
4346
# When the input $MarkdownPaths is the path of txt file
@@ -64,20 +67,27 @@ if ($PSCmdlet.ParameterSetName -eq "Markdown") {
6467
$cmdlet = $_.BaseName
6568
$result = Measure-SectionMissingAndOutputScript $module $cmdlet $_.FullName `
6669
-OutputScriptsInFile:$OutputScriptsInFile.IsPresent `
67-
-OutputFolder $OutputFolder
70+
-OutputFolder $OutputFolder `
71+
-TotalLine $totalLine
6872
$analysisResultsTable += $result.Errors
73+
$codeMap += $result.CodeMap
74+
$totalLine = $result.TotalLine
6975
}
7076
}
77+
$codeMap| Export-Csv "$OutputFolder\TempCodeMap.csv" -NoTypeInformation
7178
if ($AnalyzeScriptsInFile.IsPresent) {
7279
$ScriptPaths = "$OutputFolder\TempScript.ps1"
80+
$CodeMapPath = "$OutputFolder\TempCodeMap.csv"
7381
}
7482
}
7583

7684
# Analyze scripts
7785
if ($PSCmdlet.ParameterSetName -eq "Script" -or $AnalyzeScriptsInFile.IsPresent) {
86+
# Read code map from file
87+
$codeMap = Import-Csv $CodeMapPath
7888
# Read and analyze ".ps1" in \ScriptsByExample
7989
Write-Output "Analyzing file ..."
80-
$analysisResultsTable += Get-ScriptAnalyzerResult (Get-Item -Path $ScriptPaths) $RulePaths -IncludeDefaultRules:$IncludeDefaultRules.IsPresent -ErrorAction Continue
90+
$analysisResultsTable += Get-ScriptAnalyzerResult (Get-Item -Path $ScriptPaths) $RulePaths -IncludeDefaultRules:$IncludeDefaultRules.IsPresent $codeMap -ErrorAction Continue
8191

8292
# Summarize analysis results, output in Result.csv
8393
if($analysisResultsTable){

0 commit comments

Comments
 (0)