From 9c9367376e20fec1c44b8f4f38e8db6241c17d23 Mon Sep 17 00:00:00 2001 From: Shiying Chen <72982571+MoChilia@users.noreply.github.com> Date: Thu, 4 Aug 2022 13:02:23 +0800 Subject: [PATCH] [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 --- .../Debugging-StaticAnalysis-Errors.md | 38 +- .../AnalyzeRules/CommandName.psm1 | 2 +- .../AnalyzeRules/ParameterNameAndValue.psm1 | 14 +- .../ExampleAnalyzer/ExampleIssue.cs | 25 +- .../Measure-MarkdownOrScript.ps1 | 28 +- .../StaticAnalysis/ExampleAnalyzer/utils.ps1 | 624 ++++++++++-------- .../Exceptions/Az.Accounts/ExampleIssues.csv | 16 +- 7 files changed, 425 insertions(+), 322 deletions(-) diff --git a/documentation/Debugging-StaticAnalysis-Errors.md b/documentation/Debugging-StaticAnalysis-Errors.md index 8490968e8205..91fc44b906fe 100644 --- a/documentation/Debugging-StaticAnalysis-Errors.md +++ b/documentation/Debugging-StaticAnalysis-Errors.md @@ -9,6 +9,7 @@ Our StaticAnalysis tools help us ensure our modules follow PowerShell guidelines - [Signature Issues](#signature-issues) - [Help Issues](#help-issues) - [Example Issues](#example-issues) +- [Troubleshotting Example Issues](#troubleshotting-example-issues) ## How to know if you have a StaticAnalysis Error 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 - Copy each of the errors you would like to suppress directly from the ExampleIssues.csv file output in the CI pipeline artifacts - 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. -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 `` to the next line of the example title and in front of the code block. \ No newline at end of file +## Troubleshotting Example Issues +### Scenario 1: Unexpected errors caused by the mixture of outputs and codes +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. +### Example: Codes and outputs are split correctly +```` +```powershell +Get-AzConfig -EnableDataCollection +``` + +```output +Key Value Applies To Scope Help Message +--- ----- ---------- ----- ------------ +EnableDataCollection False Az CurrentUser When enabled, Azure PowerShell cmdlets send telemetry data to Microsoft to improve the custom… +``` +```` +If outputs cannot be separated from codes, then please add the tag `` to the next line of the example title and in front of the code block. The following is an example. +### Example: Add skip tag to the example whose outputs cannot be separated from codes +```` + +```powershell +$Context = Get-AzBatchAccountKey -AccountName myaccount +$Context.PrimaryAccountKey +ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMN== +$Context.SecondaryAccountKey +ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMN== +``` +```` + +### Scenario 2: Unexpected errors caused by unpaired quotes or brackets +Please check whether you have matched the correct number of **quotes** and **brackets**. The common error messages in this scenario are as follows. +- MissingEndParenthesisInExpression: Missing closing ')' in expression. +- MissingEndCurlyBrace: Missing closing '}' in statement block or type definition. +- MissingArrayIndexExpression: Array index expression is missing or not valid. +- UnexpectedToken: Unexpected token xxx. (Check whether you have missed or added extra quote) + +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. \ No newline at end of file diff --git a/tools/StaticAnalysis/ExampleAnalyzer/AnalyzeRules/CommandName.psm1 b/tools/StaticAnalysis/ExampleAnalyzer/AnalyzeRules/CommandName.psm1 index 3d0d4570318f..c2a526c92508 100644 --- a/tools/StaticAnalysis/ExampleAnalyzer/AnalyzeRules/CommandName.psm1 +++ b/tools/StaticAnalysis/ExampleAnalyzer/AnalyzeRules/CommandName.psm1 @@ -113,7 +113,7 @@ function Measure-CommandName { } $ModuleCmdletExNum = $($CommandParameterPair[$i].ModuleCmdletExNum) $Result = [Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic.DiagnosticRecord]@{ - Message = "$ModuleCmdletExNum-#@#$Message#@#$Remediation"; + Message = "$Message#@#$Remediation"; Extent = $Asts[$i].Extent; RuleName = $RuleName; Severity = $Severity diff --git a/tools/StaticAnalysis/ExampleAnalyzer/AnalyzeRules/ParameterNameAndValue.psm1 b/tools/StaticAnalysis/ExampleAnalyzer/AnalyzeRules/ParameterNameAndValue.psm1 index c8a0fbdc561e..3a12d76a3a2c 100644 --- a/tools/StaticAnalysis/ExampleAnalyzer/AnalyzeRules/ParameterNameAndValue.psm1 +++ b/tools/StaticAnalysis/ExampleAnalyzer/AnalyzeRules/ParameterNameAndValue.psm1 @@ -699,35 +699,35 @@ function Measure-ParameterNameAndValue { [System.Management.Automation.Language.Ast[]]$Asts = $ScriptBlockAst.FindAll($Predicate, $false) for ($i = 0; $i -lt $Asts.Count; $i++) { if ($global:CommandParameterPair[$i].ParameterName -eq "" -and $global:CommandParameterPair[$i].ExpressionToParameter -eq "") { - $Message = "$($CommandParameterPair[$i].ModuleCmdletExNum)-#@#$($CommandParameterPair[$i].CommandName) has a parameter not in the same ParameterSet as others." + $Message = "$($CommandParameterPair[$i].CommandName) has a parameter not in the same ParameterSet as others." $RuleName = [RuleNames]::Unknown_Parameter_Set $Severity = "Error" $RuleSuppressionID = "5010" $Remediation = "Make sure the parameters are from the same parameter set." } elseif ($global:CommandParameterPair[$i].ExpressionToParameter -eq "") { - $Message = "$($CommandParameterPair[$i].ModuleCmdletExNum)-#@#$($CommandParameterPair[$i].CommandName) -$($CommandParameterPair[$i].ParameterName) is not a valid parameter name." + $Message = "$($CommandParameterPair[$i].CommandName) -$($CommandParameterPair[$i].ParameterName) is not a valid parameter name." $RuleName = [RuleNames]::Invalid_Parameter_Name $Severity = "Error" $RuleSuppressionID = "5011" $Remediation = "Check validity of the parameter -$($CommandParameterPair[$i].ParameterName)." } elseif ($global:CommandParameterPair[$i].ExpressionToParameter -eq "") { - $Message = "$($CommandParameterPair[$i].ModuleCmdletExNum)-#@#$($CommandParameterPair[$i].CommandName) -$($CommandParameterPair[$i].ParameterName) appeared more than once." + $Message = "$($CommandParameterPair[$i].CommandName) -$($CommandParameterPair[$i].ParameterName) appeared more than once." $RuleName = [RuleNames]::Duplicate_Parameter_Name $Severity = "Error" $RuleSuppressionID = "5012" $Remediation = "Remove redundant parameter -$($CommandParameterPair[$i].ParameterName)." } elseif ($null -eq $global:CommandParameterPair[$i].ExpressionToParameter) { - $Message = "$($CommandParameterPair[$i].ModuleCmdletExNum)-#@#$($CommandParameterPair[$i].CommandName) -$($CommandParameterPair[$i].ParameterName) must be assigned with a value." + $Message = "$($CommandParameterPair[$i].CommandName) -$($CommandParameterPair[$i].ParameterName) must be assigned with a value." $RuleName = [RuleNames]::Unassigned_Parameter $Severity = "Error" $RuleSuppressionID = "5013" $Remediation = "Assign value for the parameter -$($CommandParameterPair[$i].ParameterName)." } elseif ($global:CommandParameterPair[$i].ExpressionToParameter.EndsWith(" is a null-valued parameter value.")) { - $Message = "$($CommandParameterPair[$i].ModuleCmdletExNum)-#@#$($CommandParameterPair[$i].CommandName) -$($CommandParameterPair[$i].ParameterName) $($CommandParameterPair[$i].ExpressionToParameter)" + $Message = "$($CommandParameterPair[$i].CommandName) -$($CommandParameterPair[$i].ParameterName) $($CommandParameterPair[$i].ExpressionToParameter)" $RuleName = [RuleNames]::Unassigned_Variable $Severity = "Warning" $RuleSuppressionID = "5110" @@ -735,7 +735,7 @@ function Measure-ParameterNameAndValue { $Remediation = "Assign value for $variable." } elseif ($global:CommandParameterPair[$i].ParameterName -eq "") { - $Message = "$($CommandParameterPair[$i].ModuleCmdletExNum)-#@#$($CommandParameterPair[$i].CommandName) $($CommandParameterPair[$i].ExpressionToParameter) is not explicitly assigned to a parameter." + $Message = "$($CommandParameterPair[$i].CommandName) $($CommandParameterPair[$i].ExpressionToParameter) is not explicitly assigned to a parameter." $RuleName = [RuleNames]::Unbinded_Expression $Severity = "Error" $RuleSuppressionID = "5014" @@ -744,7 +744,7 @@ function Measure-ParameterNameAndValue { else { $ExpressionToParameter = ($CommandParameterPair[$i].ExpressionToParameter -split "-#-")[0] $ExpectedType = ($CommandParameterPair[$i].ExpressionToParameter -split "-#-")[1] - $Message = "$($CommandParameterPair[$i].ModuleCmdletExNum)-#@#$($CommandParameterPair[$i].CommandName) -$($CommandParameterPair[$i].ParameterName) $ExpressionToParameter is not an expected parameter value type." + $Message = "$($CommandParameterPair[$i].CommandName) -$($CommandParameterPair[$i].ParameterName) $ExpressionToParameter is not an expected parameter value type." $RuleName = [RuleNames]::Mismatched_Parameter_Value_Type $Severity = "Warning" $RuleSuppressionID = "5111" diff --git a/tools/StaticAnalysis/ExampleAnalyzer/ExampleIssue.cs b/tools/StaticAnalysis/ExampleAnalyzer/ExampleIssue.cs index fb9b1cddbfcd..a9d5f7e453be 100644 --- a/tools/StaticAnalysis/ExampleAnalyzer/ExampleIssue.cs +++ b/tools/StaticAnalysis/ExampleAnalyzer/ExampleIssue.cs @@ -24,6 +24,7 @@ public class ExampleIssue : IReportRecord public string Module { get; set; } public string Cmdlet { get; set; } public int Example { get; set; } + public string Line { get; set; } public string RuleName { get; set; } public string Extent { get; set; } public int ProblemId { get; set; } @@ -32,13 +33,13 @@ public class ExampleIssue : IReportRecord public string Remediation { get; set; } public string PrintHeaders() { - return "\"Module\",\"Cmdlet\",\"Example\",\"RuleName\",\"ProblemId\",\"Severity\",\"Description\",\"Extent\",\"Remediation\""; + return "\"Module\",\"Cmdlet\",\"Example\",\"Line\",\"RuleName\",\"ProblemId\",\"Severity\",\"Description\",\"Extent\",\"Remediation\""; } public string FormatRecord() { - return string.Format("\"{0}\",\"{1}\",\"{2}\",\"{3}\",\"{4}\",\"{5}\",\"{6}\",\"{7}\",\"{8}\"", - Module, Cmdlet, Example, RuleName, ProblemId, Severity, Description, Extent, Remediation); + return string.Format("\"{0}\",\"{1}\",\"{2}\",\"{3}\",\"{4}\",\"{5}\",\"{6}\",\"{7}\",\"{8}\",\"{9}\"", + Module, Cmdlet, Example, Line, RuleName, ProblemId, Severity, Description, Extent, Remediation); } // The code that excludes exceptions is in tools/StaticAnalysis/ExampleAnalyzer/utils.ps1 Get-NonExceptionRecord. @@ -51,6 +52,7 @@ public bool Match(IReportRecord other) result = (record.Module == Module)&& (record.Cmdlet == Cmdlet)&& (record.Example == Example)&& + (record.Line == Line)&& (record.Description == Description); } return result; @@ -58,21 +60,22 @@ public bool Match(IReportRecord other) public IReportRecord Parse(string line) { - var matcher = "\"([^\"]*)\",\"([^\"]*)\",\"([^\"]*)\",\"([^\"]*)\",\"([^\"]*)\",\"([^\"]*)\",\"([^\"]*)\",\"([^\"]*)\",\"([^\"]*)\""; + var matcher = "\"([^\"]*)\",\"([^\"]*)\",\"([^\"]*)\",\"([^\"]*)\",\"([^\"]*)\",\"([^\"]*)\",\"([^\"]*)\",\"([^\"]*)\",\"([^\"]*)\",\"([^\"]*)\""; var match = Regex.Match(line, matcher); - if (!match.Success || match.Groups.Count < 10) + if (!match.Success || match.Groups.Count < 11) { throw new InvalidOperationException(string.Format("Could not parse '{0}' as ExampleIssue record", line)); } Module = match.Groups[1].Value; Cmdlet = match.Groups[2].Value; Example = int.Parse(match.Groups[3].Value); - RuleName = match.Groups[4].Value; - ProblemId = int.Parse(match.Groups[5].Value); - Severity = int.Parse(match.Groups[6].Value); - Description = match.Groups[7].Value; - Extent = match.Groups[8].Value; - Remediation = match.Groups[9].Value; + Line = match.Groups[4].Value; + RuleName = match.Groups[5].Value; + ProblemId = int.Parse(match.Groups[6].Value); + Severity = int.Parse(match.Groups[7].Value); + Description = match.Groups[8].Value; + Extent = match.Groups[9].Value; + Remediation = match.Groups[10].Value; return this; } } diff --git a/tools/StaticAnalysis/ExampleAnalyzer/Measure-MarkdownOrScript.ps1 b/tools/StaticAnalysis/ExampleAnalyzer/Measure-MarkdownOrScript.ps1 index e1c983a78bde..0ffd4bac55c5 100644 --- a/tools/StaticAnalysis/ExampleAnalyzer/Measure-MarkdownOrScript.ps1 +++ b/tools/StaticAnalysis/ExampleAnalyzer/Measure-MarkdownOrScript.ps1 @@ -17,6 +17,7 @@ param ( [string[]]$ScriptPaths, [Parameter(HelpMessage = "PSScriptAnalyzer custom rules paths. Empty for current path. Supports wildcard.")] [string[]]$RulePaths, + [string]$CodeMapPath, [switch]$Recurse, [switch]$IncludeDefaultRules, [string]$OutputFolder = "$PSScriptRoot\..\..\..\artifacts\StaticAnalysisResults\ExampleAnalysis", @@ -28,16 +29,18 @@ param ( . $PSScriptRoot\utils.ps1 $analysisResultsTable = @() - -# Clean caches, remove files in "output" folder -if ($OutputScriptsInFile.IsPresent) { - Remove-Item $OutputFolder\TempScript.ps1 -ErrorAction SilentlyContinue - Remove-Item $PSScriptRoot\..\..\..\artifacts\StaticAnalysisResults\ExampleIssues.csv -ErrorAction SilentlyContinue - Remove-Item $OutputFolder -ErrorAction SilentlyContinue -} +$codeMap = @() +$totalLine = 1 # Find examples in "help\*.md", output ".ps1" if ($PSCmdlet.ParameterSetName -eq "Markdown") { + # Clean caches, remove files in "output" folder + if ($OutputScriptsInFile.IsPresent) { + Remove-Item $OutputFolder\TempScript.ps1 -ErrorAction SilentlyContinue + Remove-Item $OutputFolder\TempCodeMap.csv -ErrorAction SilentlyContinue + Remove-Item $PSScriptRoot\..\..\..\artifacts\StaticAnalysisResults\ExampleIssues.csv -ErrorAction SilentlyContinue + Remove-Item $OutputFolder -ErrorAction SilentlyContinue + } $null = New-Item -ItemType Directory -Path $OutputFolder -ErrorAction SilentlyContinue $null = New-Item -ItemType File $OutputFolder\TempScript.ps1 # When the input $MarkdownPaths is the path of txt file @@ -64,20 +67,27 @@ if ($PSCmdlet.ParameterSetName -eq "Markdown") { $cmdlet = $_.BaseName $result = Measure-SectionMissingAndOutputScript $module $cmdlet $_.FullName ` -OutputScriptsInFile:$OutputScriptsInFile.IsPresent ` - -OutputFolder $OutputFolder + -OutputFolder $OutputFolder ` + -TotalLine $totalLine $analysisResultsTable += $result.Errors + $codeMap += $result.CodeMap + $totalLine = $result.TotalLine } } + $codeMap| Export-Csv "$OutputFolder\TempCodeMap.csv" -NoTypeInformation if ($AnalyzeScriptsInFile.IsPresent) { $ScriptPaths = "$OutputFolder\TempScript.ps1" + $CodeMapPath = "$OutputFolder\TempCodeMap.csv" } } # Analyze scripts if ($PSCmdlet.ParameterSetName -eq "Script" -or $AnalyzeScriptsInFile.IsPresent) { + # Read code map from file + $codeMap = Import-Csv $CodeMapPath # Read and analyze ".ps1" in \ScriptsByExample Write-Output "Analyzing file ..." - $analysisResultsTable += Get-ScriptAnalyzerResult (Get-Item -Path $ScriptPaths) $RulePaths -IncludeDefaultRules:$IncludeDefaultRules.IsPresent -ErrorAction Continue + $analysisResultsTable += Get-ScriptAnalyzerResult (Get-Item -Path $ScriptPaths) $RulePaths -IncludeDefaultRules:$IncludeDefaultRules.IsPresent $codeMap -ErrorAction Continue # Summarize analysis results, output in Result.csv if($analysisResultsTable){ diff --git a/tools/StaticAnalysis/ExampleAnalyzer/utils.ps1 b/tools/StaticAnalysis/ExampleAnalyzer/utils.ps1 index 17b66d7eb9a7..338da2637670 100644 --- a/tools/StaticAnalysis/ExampleAnalyzer/utils.ps1 +++ b/tools/StaticAnalysis/ExampleAnalyzer/utils.ps1 @@ -12,6 +12,8 @@ Get-RecordsNotInAllowList Measure-SectionMissingAndOutputScript Get-ScriptAnalyzerResult + Set-AnalysisOutput + Set-ExampleProperties #> $SYNOPSIS_HEADING = "## SYNOPSIS" @@ -19,39 +21,12 @@ $SYNTAX_HEADING = "## SYNTAX" $DESCRIPTION_HEADING = "## DESCRIPTION" $EXAMPLES_HEADING = "## EXAMPLES" $PARAMETERS_HEADING = "## PARAMETERS" -$SINGLE_EXAMPLE_HEADING_REGEX = "\n###\s*" -$SINGLE_EXAMPLE_TITLE_HEADING_REGEX = "$SINGLE_EXAMPLE_HEADING_REGEX.+" -$CODE_BLOCK_REGEX = "``````(powershell)?\s*\n(.*\n)*?\s*``````" -$OUTPUT_BLOCK_REGEX = "``````output\s*\n(.*\n)*?\s*``````" - -class Scale { - [string]$Module - [string]$Cmdlet - [int]$Examples -} - -class Missing { - [string]$Module - [string]$Cmdlet - [int]$MissingSynopsis - [int]$MissingDescription - [int]$MissingExampleTitle - [int]$MissingExampleCode - [int]$MissingExampleOutput - [int]$MissingExampleDescription -} - -class DeletePromptAndSeparateOutput { - [string]$Module - [string]$Cmdlet - [int]$NeedDeleting - [int]$NeedSplitting -} class AnalysisOutput{ [string]$Module [string]$Cmdlet [int]$Example + [string]$Line [string]$RuleName [int]$ProblemID [int]$Severity @@ -68,138 +43,185 @@ class AnalysisOutput{ #> function Get-ExamplesDetailsFromMd { param ( + [string]$Module, + [string]$Cmdlet, [string]$MarkdownPath ) - $fileContent = Get-Content $MarkdownPath -Raw + $errors = @() + $missingSeverity = 1 + $Extent = $MarkdownPath + $blockTypeList = ("yaml","json") + $fileContent = Get-Content $MarkdownPath $indexOfExamples = $fileContent.IndexOf($EXAMPLES_HEADING) $indexOfParameters = $fileContent.IndexOf($PARAMETERS_HEADING) + if($indexOfExamples -eq -1){ + $RuleName = "MissingExamplesHeading" + $ProblemID = 5060 + $Description = "'## EXAMPLES' is missing." + $Remediation = "Mark '## EXAMPLES' for your reference docs." + $errors += Set-AnalysisOutput $Module $Cmdlet 0 0 $RuleName $ProblemID $missingSeverity $Description $Extent $Remediation + return + } + if($indexOfParameters -eq -1){ + $RuleName = "MissingParametersHeading" + $ProblemID = 5061 + $Description = "'## PARAMETERS' is missing." + $Remediation = "Mark '## PARAMETERS' for your reference docs." + $errors += Set-AnalysisOutput $Module $Cmdlet 0 0 $RuleName $ProblemID $missingSeverity $Description $Extent $Remediation + return + } - $exampleNumber = -1 + $exampleNumber = 0 $examplesProperties = @() - $examplesContent = $fileContent.Substring($indexOfExamples, $indexOfParameters - $indexOfExamples) - $examplesTitles = ($examplesContent | Select-String -Pattern $SINGLE_EXAMPLE_TITLE_HEADING_REGEX -AllMatches).Matches - $examplesContentWithoutTitle = $examplesContent -split $SINGLE_EXAMPLE_TITLE_HEADING_REGEX | Select-Object -Skip 1 - foreach ($exampleContent in $examplesContentWithoutTitle) { - $exampleNumber++ - # Skip the autogenerated example - if($exampleContent -match "\(autogenerated\)"){ - continue + $examplesContent = $fileContent[$indexOfExamples..$($indexOfParameters - 1)] + $firstExample = $true + $needRecord = $false + foreach ($exampleLine in $examplesContent) { + if($exampleLine.StartsWith("###")){ + if(!$firstExample -and $needRecord){ + if(!$codeBlockComplete -or !$outputBlockComplete -or !$otherTypeBlockComplete){ + $RuleName = "BlockIdentifiersNotMatched" + $ProblemID = 5064 + $Description = "The start and end identifiers of the code block do not match." + $Remediation = "Please check whether the start or end identifier (triple backticks) of the code block is missing." + $errors += Set-AnalysisOutput $Module $Cmdlet $exampleNumber 0 $RuleName $ProblemID $missingSeverity $Description $Extent $Remediation + if(!$codeBlockComplete){ + $exampleCodes = "#BlockIdentifiersNotMatched" + } + } + $examplesProperties += Set-ExampleProperties $exampleNumber $exampleTitle $exampleCodes $exampleOutputs $hasOutputBlock $exampleDescriptions + } + # Initialize states + $exampleNumber++ + $exampleTitle = ($exampleLine -split "###")[1] + $exampleCodes = @() + $exampleOutputs = @() + $exampleDescriptions = @() + $needRecord = $true + $firstCodeBlock = $true + $codeBlockComplete = $true + $outputBlockComplete = $true + $otherTypeBlockComplete = $true + $hasOutputBlock = $false + $firstExample = $false } - # Skip the example whose output can not be splitted from code - if($exampleContent -match ""){ + elseif($exampleLine -eq "" -or $exampleLine -match ""){ + # Skip the autogenerated example and the example whose output can not be splitted from code + $needRecord = $false continue } - $exampleTitle = ($examplesTitles[$exampleNumber].Value -split $SINGLE_EXAMPLE_HEADING_REGEX)[1].Trim() - $exampleCodes = @() - $exampleOutputs = @() - $exampleDescriptions = @() - - $exampleCodeBlocks = ($exampleContent | Select-String -Pattern $CODE_BLOCK_REGEX -AllMatches).Matches - $exampleOutputBlocks = ($exampleContent | Select-String -Pattern $OUTPUT_BLOCK_REGEX -AllMatches).Matches - if ($exampleCodeBlocks.Count -eq 0) { - $description = $exampleContent.Trim() - if ($description -ne "") { - $exampleDescriptions += $description + elseif($needRecord){ + if($exampleLine.StartsWith("``````powershell","CurrentCultureIgnoreCase")){ + # Find the head of code block with tag: powershell + $codeBlockComplete = $false } - } - else { - # From the start to the start of the first codeblock is example description. - $description = $exampleContent.SubString(0, $exampleCodeBlocks[0].Index).Trim() - if ($description -ne "") { - $exampleDescriptions += $description + elseif($exampleLine.StartsWith("``````output","CurrentCultureIgnoreCase")){ + # Find the head of output block with tag: output + $outputBlockComplete = $false + $hasOutputBlock = $true } - # Extract code from the first "\n" to the last "\n" - foreach ($exampleCodeBlock in $exampleCodeBlocks) { - $code = $exampleCodeBlock.Value.Substring($exampleCodeBlock.Value.IndexOf("`n"), $exampleCodeBlock.Value.LastIndexOf("`n") - $exampleCodeBlock.Value.IndexOf("`n")) - if ($code -ne ""-and $code -notmatch "{{ Add code here }}") { - $exampleCodes += $code + elseif($exampleLine -match "^``````\w+"){ + $blockType = ($exampleLine -split "``````")[1] + if($blockType -notin $blockTypeList){ + $RuleName = "BlockTypeUnsupported" + $ProblemID = 5062 + $Description = "The language identifier $blockType is not supported." + $Remediation = "Please check the spelling or contact Azure PowerShell team." + $errors += Set-AnalysisOutput $Module $Cmdlet $exampleNumber 0 $RuleName $ProblemID $missingSeverity $Description $Extent $Remediation } + $otherTypeBlockComplete = $false } - # Extract output from the first "\n" to the last "\n" - foreach ($exampleOutputBlock in $exampleOutputBlocks) { - $output = $exampleOutputBlock.Value.Substring($exampleOutputBlock.Value.IndexOf("`n"), $exampleOutputBlock.Value.LastIndexOf("`n") - $exampleOutputBlock.Value.IndexOf("`n")).Trim() - if ($output -ne "") { - $exampleOutputs += $output + elseif($exampleLine -match "\A\s*``````\s*\Z"){ + if($codeBlockComplete -and $outputBlockComplete -and $otherTypeBlockComplete){ + if($firstCodeBlock){ + # Find the first head of code block without tag: powershell + $codeBlockComplete = $false + $firstCodeBlock = $false + } + else{ + $RuleName = "UnclearBlockType" + $ProblemID = 5063 + $Description = "The code blocks are missing language identifiers." + $Remediation = "The type of the code block needs to be clearly indicated with 'powershell' or 'output'." + $errors += Set-AnalysisOutput $Module $Cmdlet $exampleNumber 0 $RuleName $ProblemID $missingSeverity $Description $Extent $Remediation + $otherTypeBlockComplete = $false + } + } + elseif(!$codeBlockComplete -and $outputBlockComplete -and $otherTypeBlockComplete){ + # Find the tail of code block + $codeBlockComplete = $true + } + elseif($codeBlockComplete -and !$outputBlockComplete -and $otherTypeBlockComplete){ + # Find the tail of output block + $outputBlockComplete = $true + } + elseif($codeBlockComplete -and $outputBlockComplete -and !$otherTypeBlockComplete){ + # Find the tail of other-type block + $otherTypeBlockComplete = $true } } - # From the end of the last codeblock to the end is example description. - if($null -ne $exampleOutputBlocks){ - $description = $exampleContent.SubString($exampleOutputBlocks[-1].Index + $exampleOutputBlocks[-1].Length).Trim() + elseif(($codeBlockComplete + $outputBlockComplete + $otherTypeBlockComplete)-le 1){ + # Report BlockPromptsNotMatched later } - else{ - $description = $exampleContent.SubString($exampleCodeBlocks[-1].Index + $exampleCodeBlocks[-1].Length).Trim() + elseif(!$codeBlockComplete){ + # Find codes in code block + $exampleCodes += $exampleLine } - if ($description -ne "") { - $exampleDescriptions += $description + elseif(!$outputBlockComplete){ + # Find outputs in output block + $exampleOutputs += $exampleLine } - } - - $examplesProperties += [PSCustomObject]@{ - Num = $exampleNumber + 1 - Title = $exampleTitle - Codes = $exampleCodes - Outputs = $exampleOutputs - OutputBlocks = $exampleOutputBlocks - Description = ([string]$exampleDescriptions).Trim() - } - } - - return $examplesProperties -} -<# - .SYNOPSIS - Except the suppressed records. It is independent of ExampleIssues.cs. -#> -function Get-NonExceptionRecord{ - param( - [AnalysisOutput[]]$records - ) - $exceptionPaths = "$PSScriptRoot\..\..\..\tools\StaticAnalysis\Exceptions" - $results = @() - foreach($record in $records){ - $needAdd = $true - $exceptionPath = Join-Path -Path $exceptionPaths -ChildPath "Az.$($record.Module)" -AdditionalChildPath "ExampleIssues.csv" - if(Test-Path -Path $exceptionPath){ - $exceptionContents = Import-Csv -Path $exceptionPath - foreach($exceptionContent in $exceptionContents) { - if($exceptionContent.Module -eq $record.Module -and $exceptionContent.Cmdlet -eq $record.Cmdlet -and $exceptionContent.Example -eq $record.Example -and $exceptionContent.Description -eq $record.Description){ - $needAdd = $false - break + elseif(!$otherTypeBlockComplete){ + # Find outputs in other-type block, skip + } + else{ + # Find descriptions + if($exampleLine.Trim() -ne ""){ + $exampleDescriptions += $exampleLine } } } - if($needAdd){ - $results += $record + } + if($needRecord){ + if(!$codeBlockComplete -or !$outputBlockComplete -or !$otherTypeBlockComplete){ + $RuleName = "BlockIdentifiersNotMatched" + $ProblemID = 5064 + $Description = "The start and end identifiers of the code block do not match." + $Remediation = "Please check whether the start or end identifier (triple backticks) of the code block is missing." + $errors += Set-AnalysisOutput $Module $Cmdlet $exampleNumber 0 $RuleName $ProblemID $missingSeverity $Description $Extent $Remediation + if(!$codeBlockComplete){ + $exampleCodes = "#BlockIdentifiersNotMatched" + } } + $examplesProperties += Set-ExampleProperties $exampleNumber $exampleTitle $exampleCodes $exampleOutputs $hasOutputBlock $exampleDescriptions } - return $results + return ($examplesProperties, $errors) } <# .SYNOPSIS - Get AnalysisOutput entries not in the allow list. + Set example properties. #> -function Get-RecordsNotInAllowList{ - param ( - [AnalysisOutput[]]$records +function Set-ExampleProperties{ + param( + [int] $exampleNumber, + [string] $exampleTitle, + [string[]] $exampleCodes, + [string[]] $exampleOutputs, + [Boolean] $hasOutputBlock, + [string[]] $exampleDescriptions ) - return $records | Where-Object { - # Skip the unexpected error caused by using to assign parameters - if($_.RuleName -eq "RedirectionNotSupported"){ - return $false - } - # Skip the invaild cmdlet "<" - $CommandName = ($_.Description -split " ")[0] - if($CommandName -eq "<"){ - return $false - } - # Skip NeedDeleting in Storage - if($_.RuleName -eq "NeedDeleting" -and $_.Module -eq "Storage"){ - return $false - } - return $true + $examplesProperty = [PSCustomObject]@{ + Num = $exampleNumber + Title = $exampleTitle + Codes = $exampleCodes + Outputs = $exampleOutputs + OutputBlock = $hasOutputBlock + Description = $exampleDescriptions + } + return $examplesProperty } <# @@ -213,9 +235,9 @@ function Measure-SectionMissingAndOutputScript { [string]$Cmdlet, [string]$MarkdownPath, [switch]$OutputScriptsInFile, - [string]$OutputFolder + [string]$OutputFolder, + [int]$TotalLine ) - $results = @() $missingSeverity = 1 $fileContent = Get-Content $MarkdownPath -Raw @@ -225,19 +247,20 @@ function Measure-SectionMissingAndOutputScript { $indexOfDescription = $fileContent.IndexOf($DESCRIPTION_HEADING) $indexOfExamples = $fileContent.IndexOf($EXAMPLES_HEADING) - $exampleNumber = 0 $missingSynopsis = 0 $missingDescription = 0 - $missingExampleTitle = 0 - $missingExampleCode = 0 - $missingExampleOutput = 0 - $missingExampleDescription = 0 - $needDeleting = 0 + $exampleNum = 0 + $exampleLine = 0 + $Extent = $MarkdownPath + + $codeMap = @() + + ($examplesDetails, $errors) = Get-ExamplesDetailsFromMd $Module $Cmdlet $MarkdownPath # If Synopsis section exists if ($indexOfSynopsis -ne -1) { $synopsisContent = $fileContent.Substring($indexOfSynopsis + $SYNOPSIS_HEADING.Length, $indexOfSyntax - ($indexOfSynopsis + $SYNOPSIS_HEADING.Length)) - if ($synopsisContent.Trim() -eq "") { + if ($synopsisContent.Trim().Length -eq 0) { $missingSynopsis = 1 } else { @@ -248,18 +271,11 @@ function Measure-SectionMissingAndOutputScript { $missingSynopsis = 1 } if($missingSynopsis -ne 0){ - $result = [AnalysisOutput]@{ - Module = $Module - Cmdlet = $Cmdlet - Example = "" - Description = "Synopsis is missing." - RuleName = "MissingSynopsis" - Severity = $missingSeverity - Extent = "$Module\help\$Cmdlet.md" - ProblemID = 5040 - Remediation = "Add Synopsis. Remove any placeholders." - } - $results += $result + $Description = "Synopsis is missing." + $RuleName = "MissingSynopsis" + $ProblemID = 5040 + $Remediation = "Add Synopsis. Remove any placeholders." + $errors += Set-AnalysisOutput $Module $Cmdlet $exampleNum $exampleLine $RuleName $ProblemID $missingSeverity $Description $Extent $Remediation } # If Description section exists @@ -276,161 +292,141 @@ function Measure-SectionMissingAndOutputScript { $missingDescription = 1 } if($missingDescription -ne 0){ - $result = [AnalysisOutput]@{ - Module = $Module - Cmdlet = $Cmdlet - Example = "" - Description = "Description is missing." - RuleName = "MissingDescription" - Severity = $missingSeverity - Extent = "$Module\help\$Cmdlet.md" - ProblemID = 5041 - Remediation = "Add Description. Remove any placeholders." - } - $results += $result + $Description = "Description is missing." + $RuleName = "MissingDescription" + $ProblemID = 5041 + $Remediation = "Add Description. Remove any placeholders." + $errors += Set-AnalysisOutput $Module $Cmdlet $exampleNum $exampleLine $RuleName $ProblemID $missingSeverity $Description $Extent $Remediation } - $examplesDetails = Get-ExamplesDetailsFromMd $MarkdownPath # If no examples if ($examplesDetails.Count -eq 0) { - if($fileContent -notmatch "\(autogenerated\)" -and $fileContent -notmatch ""){ - $missingExampleTitle++ - $missingExampleCode++ - $missingExampleOutput++ - $missingExampleDescription++ - $result = [AnalysisOutput]@{ - Module = $Module - Cmdlet = $Cmdlet - Example = "" - Description = "Example is missing." - RuleName = "MissingExample" - Severity = $missingSeverity - Extent = "$Module\help\$Cmdlet.md" - ProblemID = 5042 - Remediation = "Add Example. Remove any placeholders." - } - $results += $result + if($fileContent -notmatch "" -and $fileContent -notmatch ""){ + $Description = "Example is missing." + $RuleName = "MissingExample" + $ProblemID = 5042 + $Remediation = "Add Example. Remove any placeholders." + $errors += Set-AnalysisOutput $Module $Cmdlet $exampleNum $exampleLine $RuleName $ProblemID $missingSeverity $Description $Extent $Remediation } } else { foreach ($exampleDetails in $examplesDetails) { $exampleNumber = $exampleDetails.Num $exampleCodes = $exampleDetails.Codes - $_missingExampleTitle = ($exampleDetails.Title | Select-String -Pattern "{{[A-Za-z ]*}}").Count - $_missingExampleCode = ($exampleDetails.Codes | Select-String -Pattern "{{[A-Za-z ]*}}").Count - $_missingExampleOutput = ($exampleDetails.Outputs | Select-String -Pattern "{{[A-Za-z ]*}}").Count - $_missingExampleDescription = ($exampleDetails.Description | Select-String -Pattern "{{[A-Za-z ]*}}").Count - $_needDeleting = ($exampleDetails.Codes | Select-String -Pattern "`n([A-Za-z \t\\:>])*(PS|[A-Za-z]:)(\w|[\\/\[\].\- ])*(>|>)+( PS)*[ \t]*" -CaseSensitive).Count + - ($exampleDetails.Codes | Select-String -Pattern "(?<=[A-Za-z]\w+-[A-Za-z]\w+)\.ps1" -CaseSensitive).Count + $missingExampleTitle = ($exampleDetails.Title | Where-Object{$_ -match "{{[A-Za-z ]*}}"}).Count + $missingExampleCode = ($exampleDetails.Codes | Where-Object{$_ -match "{{[A-Za-z ]*}}"}).Count + $missingExampleOutput = ($exampleDetails.Outputs | Where-Object{$_ -match "{{[A-Za-z ]*}}"}).Count + $missingExampleDescription = ($exampleDetails.Description | Where-Object{$_ -match "{{[A-Za-z ]*}}"}).Count + $needDeleting = ($exampleDetails.Codes | Where-Object{$_ -cmatch "^([A-Za-z \t\\:>])*(PS|[A-Za-z]:)(\w|[\\/\[\].\- ])*(>|>)+( PS)*[ \t]*"}).Count switch ($exampleDetails) { - {$exampleDetails.Title -eq "" -or $_missingExampleTitle -ne 0} { - $missingExampleTitle ++ - $result = [AnalysisOutput]@{ - Module = $Module - Cmdlet = $Cmdlet - Example = $exampleDetails.Num - Description = "Title of the example is missing." - RuleName = "MissingExampleTitle" - Severity = $missingSeverity - Extent = "$Module\help\$Cmdlet.md" - ProblemID = 5043 - Remediation = "Add title for the example. Remove any placeholders." - } - $results += $result + {$exampleDetails.Title -eq "" -or $missingExampleTitle -ne 0} { + $Description = "Title of the example is missing." + $RuleName = "MissingExampleTitle" + $ProblemID = 5043 + $Remediation = "Add title for the example. Remove any placeholders." + $errors += Set-AnalysisOutput $Module $Cmdlet $exampleDetails.Num $exampleLine $RuleName $ProblemID $missingSeverity $Description $Extent $Remediation } - {$exampleDetails.Codes.Count -eq 0 -or $_missingExampleCode -ne 0} { - $missingExampleCode++ - $result = [AnalysisOutput]@{ - Module = $Module - Cmdlet = $Cmdlet - Example = $exampleDetails.Num - Description = "Code of the example is missing." - RuleName = "MissingExampleCode" - Severity = $missingSeverity - Extent = "$Module\help\$Cmdlet.md" - ProblemID = 5044 - Remediation = "Add code for the example. Remove any placeholders." - } - $results += $result + {$exampleDetails.Codes.Count -eq 0 -or $exampleDetails.Codes.Trim().Length -eq 0 -or $missingExampleCode -ne 0} { + $missingExampleCode = 1 + $Description = "Code of the example is missing." + $RuleName = "MissingExampleCode" + $ProblemID = 5044 + $Remediation = "Add code for the example. Remove any placeholders." + $errors += Set-AnalysisOutput $Module $Cmdlet $exampleDetails.Num $exampleLine $RuleName $ProblemID $missingSeverity $Description $Extent $Remediation } - {($exampleDetails.OutputBlocks.Count -ne 0 -and $exampleDetails.Outputs.Count -eq 0) -or $_missingExampleOutput -ne 0} { - $missingExampleOutput++ - $result = [AnalysisOutput]@{ - Module = $Module - Cmdlet = $Cmdlet - Example = $exampleDetails.Num - Description = "Output of the example is missing." - RuleName = "MissingExampleOutput" - Severity = $missingSeverity - Extent = "$Module\help\$Cmdlet.md" - ProblemID = 5045 - Remediation = "Add output for the example. Remove any placeholders." - } - $results += $result + {($exampleDetails.OutputBlock -and ($exampleDetails.Outputs.Count -eq 0 -or $exampleDetails.Outputs.Trim().Length -eq 0)) -or $missingExampleOutput -ne 0} { + $Description = "Output of the example is missing." + $RuleName = "MissingExampleOutput" + $ProblemID = 5045 + $Remediation = "Add output for the example. Remove any placeholders." + $errors += Set-AnalysisOutput $Module $Cmdlet $exampleDetails.Num $exampleLine $RuleName $ProblemID $missingSeverity $Description $Extent $Remediation } - {$exampleDetails.Description -eq "" -or $_missingExampleDescription -ne 0} { - $missingExampleDescription++ - $result = [AnalysisOutput]@{ - Module = $Module - Cmdlet = $Cmdlet - Example = $exampleDetails.Num - Description = "Description of the example is missing." - RuleName = "MissingExampleDescription" - Severity = $missingSeverity - Extent = "$Module\help\$Cmdlet.md" - ProblemID = 5046 - Remediation = "Add description for the example. Remove any placeholders." - } - $results += $result + {$exampleDetails.Description -eq "" -or $missingExampleDescription -ne 0} { + $Description = "Description of the example is missing." + $RuleName = "MissingExampleDescription" + $ProblemID = 5046 + $Remediation = "Add description for the example. Remove any placeholders." + $errors += Set-AnalysisOutput $Module $Cmdlet $exampleDetails.Num $exampleLine $RuleName $ProblemID $missingSeverity $Description $Extent $Remediation } - {$_needDeleting -ne 0}{ - $needDeleting++ - $result = [AnalysisOutput]@{ - Module = $Module - Cmdlet = $Cmdlet - Example = $exampleDetails.Num - Description = "The prompt of example need to be deleted." - RuleName = "NeedDeleting" - Severity = $missingSeverity - Extent = "$Module\help\$Cmdlet.md" - ProblemID = 5051 - Remediation = "Delete the prompt of example." - } - $results += $result - $newCode = $exampleCodes -replace "`n([A-Za-z \t\\:>])*(PS|[A-Za-z]:)(\w|[\\/\[\].\- ])*(>|>)+( PS)*[ \t]*", "`n" - $newCode = $newCode -replace "(?<=[A-Za-z]\w+-[A-Za-z]\w+)\.ps1" - $exampleCodes = $newCode + {$needDeleting -ne 0}{ + $Description = "The prompt of example need to be deleted." + $RuleName = "NeedDeleting" + $ProblemID = 5050 + $Remediation = "Delete the prompt of example." + $errors += Set-AnalysisOutput $Module $Cmdlet $exampleDetails.Num $exampleLine $RuleName $ProblemID $missingSeverity $Description $Extent $Remediation + $exampleCodes = $exampleCodes | ForEach-Object{ $_ -replace "^([A-Za-z \t\\:>])*(PS|[A-Za-z]:)(\w|[\\/\[\].\- ])*(>|>)+( PS)*[ \t]*"} } } # Output example codes to "TempScript.ps1" - if ($OutputScriptsInFile.IsPresent) { + if ($OutputScriptsInFile.IsPresent -and $missingExampleCode -eq 0) { $cmdletExamplesScriptPath = "$OutputFolder\TempScript.ps1" - if($null -ne $exampleCodes -and $exampleCodes -ne ""){ - $exampleCodes = $exampleCodes.Trim() + $line = $exampleCodes.Count + if($line -ne 0){ $functionHead = "function $Module-$Cmdlet-$exampleNumber{" Add-Content -Path (Get-Item $cmdletExamplesScriptPath).FullName -Value $functionHead Add-Content -Path (Get-Item $cmdletExamplesScriptPath).FullName -Value $exampleCodes - $functionTail = "}`n" + $functionTail = "}" Add-Content -Path (Get-Item $cmdletExamplesScriptPath).FullName -Value $functionTail + for($i = 0; $i -le $line + 1; $i++){ + $codeMap += @{ + TotalLine = $TotalLine + $i + Module = $Module + Cmdlet = $Cmdlet + Example = $exampleNumber + Line = $i + } + } + $TotalLine = $TotalLine + $line + 2 } } } } # Except records in allow list - $results = Get-RecordsNotInAllowList $results + $errors = Get-RecordsNotInAllowList $errors # Except the suppressed records - $results = Get-NonExceptionRecord $results + $errors = Get-NonExceptionRecord $errors return @{ - Scale = $scale - Missing = $missing - DeletePromptAndSeparateOutput = $deletePromptAndSeparateOutput - Errors = $results + Errors = $errors + CodeMap = $codeMap + TotalLine = $TotalLine } } +<# + .SYNOPSIS + Set properties for an AnalysisOutput object. +#> +function Set-AnalysisOutput { + param( + [string]$Module, + [string]$Cmdlet, + [int]$Example, + [string]$Line, + [string]$RuleName, + [int]$ProblemID, + [int]$Severity, + [string]$Description, + [string]$Extent, + [String]$Remediation + ) + $result = [AnalysisOutput]@{ + Module = $Module + Cmdlet = $Cmdlet + Example = $Example + Line = $Line + RuleName = $RuleName + ProblemID = $ProblemID + Severity = $Severity + Description = $Description + Extent = $Extent + Remediation = $Remediation + } + return $result +} + <# .SYNOPSIS Invoke PSScriptAnalyzer with custom rules, return the error set. @@ -440,9 +436,9 @@ function Get-ScriptAnalyzerResult { [string]$ScriptPath, [Parameter(Mandatory, HelpMessage = "PSScriptAnalyzer custom rules path. Supports wildcard.")] [string[]]$RulePath, - [switch]$IncludeDefaultRules + [switch]$IncludeDefaultRules, + [Object[]]$CodeMap ) - # Validate script file exists. if (!(Test-Path $ScriptPath -PathType Leaf)) { throw "Cannot find cached script file '$ScriptPath'." @@ -455,7 +451,7 @@ function Get-ScriptAnalyzerResult { else { $analysisResults = Invoke-ScriptAnalyzer -Path $ScriptPath -CustomRulePath $RulePath -IncludeDefaultRules:$IncludeDefaultRules.IsPresent } - $results = @() + $errors = @() foreach($analysisResult in $analysisResults){ if($analysisResult.Severity -eq "ParseError"){ $Severity = 1 @@ -469,38 +465,96 @@ function Get-ScriptAnalyzerResult { elseif($analysisResult.Severity -eq "Information"){ $Severity = 3 } + $locationMessage = $CodeMap[$analysisResult.Line - 1] if($analysisResult.RuleSuppressionID -ge 5000 -and $analysisResult.RuleSuppressionID -le 5199){ $result = [AnalysisOutput]@{ - Module = ($analysisResult.Message -split "-")[0] - Cmdlet = ($analysisResult.Message -split "-")[1] + "-" + ($analysisResult.Message -split "-")[2] - Example = ($analysisResult.Message -split "-")[3] + Module = $locationMessage.Module + Cmdlet = $locationMessage.Cmdlet + Example = $locationMessage.Example + Line = $locationMessage.Line RuleName = $analysisResult.RuleName - Description = ($analysisResult.Message -split "#@#")[1] -replace "`"","`'" -replace "`n"," " -replace "`r"," " + Description = ($analysisResult.Message -split "#@#")[0] -replace "`"","`'" -replace "`n"," " -replace "`r"," " Severity = $Severity Extent = $analysisResult.Extent.ToString().Trim() -replace "`"","`'" -replace "`n"," " -replace "`r"," " ProblemID = $analysisResult.RuleSuppressionID - Remediation = ($analysisResult.Message -split "#@#")[2] -replace "`"","`'" -replace "`n"," " -replace "`r"," " + Remediation = ($analysisResult.Message -split "#@#")[1] -replace "`"","`'" -replace "`n"," " -replace "`r"," " } } else{ $result = [AnalysisOutput]@{ - Module = "" - Cmdlet = "" - Example = 0 + Module = $locationMessage.Module + Cmdlet = $locationMessage.Cmdlet + Example = $locationMessage.Example + Line = $locationMessage.Line RuleName = $analysisResult.RuleName Description = $analysisResult.Message -replace "`"","`'" -replace "`n"," " -replace "`r"," " Severity = $Severity Extent = $analysisResult.Extent.ToString().Trim() -replace "`"","`'" -replace "`n"," " -replace "`r"," " ProblemID = 5200 - Remediation = "Unexpected Error! Please check your example or contact the Azure Powershell Team. (Appeared in Line $($analysisResult.Line))" + Remediation = "Unexpected Error. Please check [Trouble Shotting for Unexpected Errors in Example Issues](https://github.com/Azure/azure-powershell/blob/main/documentation/Debugging-StaticAnalysis-Errors.md#Troubleshotting-Example-Issues) for more details." } } - $results += $result + $errors += $result } # Except records in allow list - $results = Get-RecordsNotInAllowList $results + $errors = Get-RecordsNotInAllowList $errors # Except the suppressed records - $results = Get-NonExceptionRecord $results + $errors = Get-NonExceptionRecord $errors - return $results + return $errors } + +<# + .SYNOPSIS + Except the suppressed records. It is independent of ExampleIssues.cs. +#> +function Get-NonExceptionRecord{ + param( + [AnalysisOutput[]]$records + ) + $exceptionPaths = "$PSScriptRoot\..\..\..\tools\StaticAnalysis\Exceptions" + $errors = @() + foreach($record in $records){ + $needAdd = $true + $exceptionPath = Join-Path -Path $exceptionPaths -ChildPath "Az.$($record.Module)" -AdditionalChildPath "ExampleIssues.csv" + if(Test-Path -Path $exceptionPath){ + $exceptionContents = Import-Csv -Path $exceptionPath + foreach($exceptionContent in $exceptionContents) { + if($exceptionContent.Module -eq $record.Module -and $exceptionContent.Cmdlet -eq $record.Cmdlet -and $exceptionContent.Example -eq $record.Example -and $exceptionContent.Line -eq $record.Line -and $exceptionContent.Description -eq $record.Description){ + $needAdd = $false + break + } + } + } + if($needAdd){ + $errors += $record + } + } + return $errors +} + +<# + .SYNOPSIS + Get AnalysisOutput entries not in the allow list. +#> +function Get-RecordsNotInAllowList{ + param ( + [AnalysisOutput[]]$records + ) + return $records | Where-Object { + # Skip the unexpected error caused by using to assign parameters + if($_.RuleName -eq "RedirectionNotSupported"){ + return $false + } + # Skip the invaild cmdlet "<" + $CommandName = ($_.Description -split " ")[0] + if($CommandName -eq "<"){ + return $false + } + # Skip NeedDeleting in Storage + if($_.RuleName -eq "NeedDeleting" -and $_.Module -eq "Storage"){ + return $false + } + return $true + } +} \ No newline at end of file diff --git a/tools/StaticAnalysis/Exceptions/Az.Accounts/ExampleIssues.csv b/tools/StaticAnalysis/Exceptions/Az.Accounts/ExampleIssues.csv index 67c001ce7aca..51d3a134929f 100644 --- a/tools/StaticAnalysis/Exceptions/Az.Accounts/ExampleIssues.csv +++ b/tools/StaticAnalysis/Exceptions/Az.Accounts/ExampleIssues.csv @@ -1,8 +1,8 @@ -"Module","Cmdlet","Example","RuleName","ProblemID","Severity","Description","Extent","Remediation" -"Accounts","Disconnect-AzAccount","2","Unbinded_Expression","5014","2","Get-AzContext 'Work' is not explicitly assigned to a parameter.","'Work'","Assign 'Work' explicitly to the parameter." -"Accounts","Remove-AzContext","1","Invalid_Parameter_Name","5011","2","Remove-AzContext -Name is not a valid parameter name.","-Name","Check validity of the parameter Name." -"Accounts","Rename-AzContext","1","Invalid_Parameter_Name","5011","2","Rename-AzContext -SourceName is not a valid parameter name.","-SourceName","Check validity of the parameter SourceName." -"Accounts","Rename-AzContext","1","Invalid_Parameter_Name","5011","2","Rename-AzContext -TargetName is not a valid parameter name.","-TargetName","Check validity of the parameter TargetName." -"Accounts","Rename-AzContext","2","Unbinded_Expression","5014","2","Rename-AzContext 'My context' is not explicitly assigned to a parameter.","'My context'","Assign 'My context' explicitly to the parameter." -"Accounts","Rename-AzContext","2","Unbinded_Expression","5014","2","Rename-AzContext 'Work' is not explicitly assigned to a parameter.","'Work'","Assign 'Work' explicitly to the parameter." -"Accounts","Select-AzContext","1","Unbinded_Expression","5014","2","Select-AzContext 'Work' is not explicitly assigned to a parameter.","'Work'","Assign 'Work' explicitly to the parameter." \ No newline at end of file +"Module","Cmdlet","Example","Line","RuleName","ProblemID","Severity","Description","Extent","Remediation" +"Accounts","Disconnect-AzAccount","2","1","Unbinded_Expression","5014","1","Get-AzContext 'Work' is not explicitly assigned to a parameter.","'Work'","Assign 'Work' explicitly to the parameter." +"Accounts","Remove-AzContext","1","1","Invalid_Parameter_Name","5011","1","Remove-AzContext -Name is not a valid parameter name.","-Name","Check validity of the parameter -Name." +"Accounts","Rename-AzContext","1","1","Invalid_Parameter_Name","5011","1","Rename-AzContext -SourceName is not a valid parameter name.","-SourceName","Check validity of the parameter -SourceName." +"Accounts","Rename-AzContext","1","1","Invalid_Parameter_Name","5011","1","Rename-AzContext -TargetName is not a valid parameter name.","-TargetName","Check validity of the parameter -TargetName." +"Accounts","Rename-AzContext","2","1","Unbinded_Expression","5014","1","Rename-AzContext 'My context' is not explicitly assigned to a parameter.","'My context'","Assign 'My context' explicitly to the parameter." +"Accounts","Rename-AzContext","2","1","Unbinded_Expression","5014","1","Rename-AzContext 'Work' is not explicitly assigned to a parameter.","'Work'","Assign 'Work' explicitly to the parameter." +"Accounts","Select-AzContext","1","1","Unbinded_Expression","5014","1","Select-AzContext 'Work' is not explicitly assigned to a parameter.","'Work'","Assign 'Work' explicitly to the parameter." \ No newline at end of file