diff --git a/.azure-pipelines/util/analyze-steps.yml b/.azure-pipelines/util/analyze-steps.yml index 0241121d5af0..ab0472348c86 100644 --- a/.azure-pipelines/util/analyze-steps.yml +++ b/.azure-pipelines/util/analyze-steps.yml @@ -31,6 +31,17 @@ steps: - pwsh: 'Install-Module "platyPS", "PSScriptAnalyzer" -Force -Confirm:$false -Scope CurrentUser' displayName: 'Install PowerShell Dependencies' + +- task: PowerShell@2 + displayName: 'Install latest modules' + inputs: + targetType: 'inline' + script: | + New-Item -ItemType Directory -Path "Az-Cmdlets-latest" + Invoke-WebRequest -Uri "https://azpspackage.blob.core.windows.net/release/Az-Cmdlets-latest.tar.gz" -OutFile "Az-Cmdlets-latest/Az-Cmdlets-latest.tar.gz" -MaximumRetryCount 2 -RetryIntervalSec 1 + tar -xvzf "Az-Cmdlets-latest/Az-Cmdlets-latest.tar.gz" -C "Az-Cmdlets-latest" + . Az-Cmdlets-latest/InstallModule.ps1 + pwsh: true - task: DotNetCoreCLI@2 displayName: 'Generate Help' diff --git a/.ci-config.json b/.ci-config.json index 5ac915199931..c3ea25f1fd8d 100644 --- a/.ci-config.json +++ b/.ci-config.json @@ -87,7 +87,7 @@ "src/{ModuleName}/**/*.md$" ], "phases": [ - "build:related-module", + "build:module", "help:module" ] }, diff --git a/tools/StaticAnalysis/ExampleAnalyzer/AnalyzeRules/CommandName.psm1 b/tools/StaticAnalysis/ExampleAnalyzer/AnalyzeRules/CommandName.psm1 index eff223ac635e..e2ba7e672b67 100644 --- a/tools/StaticAnalysis/ExampleAnalyzer/AnalyzeRules/CommandName.psm1 +++ b/tools/StaticAnalysis/ExampleAnalyzer/AnalyzeRules/CommandName.psm1 @@ -4,6 +4,7 @@ .NOTES File: CommandName.psm1 #> +. $PSScriptRoot\..\utils.ps1 enum RuleNames { Invalid_Cmdlet @@ -53,12 +54,15 @@ function Measure-CommandName { $GetCommand = Get-Command $CommandName -ErrorAction SilentlyContinue if ($null -eq $GetCommand) { # CommandName is not valid. - $global:CommandParameterPair += @{ - CommandName = $CommandName - ParameterName = "" - ModuleCmdletExNum = $ModuleCmdletExNum + # Redo import-module + if(!(Redo-ImportModule $CommandName)){ + $global:CommandParameterPair += @{ + CommandName = $CommandName + ParameterName = "" + ModuleCmdletExNum = $ModuleCmdletExNum + } + return $true } - return $true } else { if ($GetCommand.CommandType -eq "Alias") { diff --git a/tools/StaticAnalysis/ExampleAnalyzer/AnalyzeRules/ParameterNameAndValue.psm1 b/tools/StaticAnalysis/ExampleAnalyzer/AnalyzeRules/ParameterNameAndValue.psm1 index 81e544ae3c41..3933d737b0f3 100644 --- a/tools/StaticAnalysis/ExampleAnalyzer/AnalyzeRules/ParameterNameAndValue.psm1 +++ b/tools/StaticAnalysis/ExampleAnalyzer/AnalyzeRules/ParameterNameAndValue.psm1 @@ -4,6 +4,7 @@ .NOTES File: ParameterNameAndValue.psm1 #> +. $PSScriptRoot\..\utils.ps1 enum RuleNames { Unknown_Parameter_Set @@ -381,6 +382,12 @@ function Measure-ParameterNameAndValue { [System.Management.Automation.Language.CommandElementAst]$CommandElementAst = $Ast [System.Management.Automation.Language.CommandAst]$CommandAst = $CommandElementAst.Parent + # Skip all the statements with -ParameterName + if($Ast.Parent.Extent.Text -match "-\w+\s*<.*?>"){ + Write-Debug "Skip $($Ast.Parent.Extent.Text)" + return $false + } + if ($global:SkipNextCommandElementAst) { $global:SkipNextCommandElementAst = $false return $false @@ -398,7 +405,10 @@ function Measure-ParameterNameAndValue { # Skip parameters for invaild cmdlet if ($null -eq $GetCommand) { - return $false + # Redo import-module + if(!(Redo-ImportModule $CommandName)){ + return $false + } } # Get command from alias if ($GetCommand.CommandType -eq "Alias") { diff --git a/tools/StaticAnalysis/ExampleAnalyzer/utils.ps1 b/tools/StaticAnalysis/ExampleAnalyzer/utils.ps1 index 663cfb1c9eab..fded751a10ae 100644 --- a/tools/StaticAnalysis/ExampleAnalyzer/utils.ps1 +++ b/tools/StaticAnalysis/ExampleAnalyzer/utils.ps1 @@ -9,9 +9,10 @@ AnalysisOutput Functions: Get-ExamplesDetailsFromMd Get-NonExceptionRecord + Get-RecordsNotInAllowList Measure-SectionMissingAndOutputScript - Measure-NotInWhiteList Get-ScriptAnalyzerResult + Redo-ImportModule #> $SYNOPSIS_HEADING = "## SYNOPSIS" @@ -176,6 +177,32 @@ function Get-NonExceptionRecord{ return $results } +<# + .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.Management"){ + return $false + } + return $true + } +} + <# .SYNOPSIS Tests whether the script is integral, outputs examples in ".md" to "TempScript.ps1" @@ -370,7 +397,7 @@ function Measure-SectionMissingAndOutputScript { ProblemID = 5051 Remediation = "Delete the prompt of example." } - $results += $result + $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 @@ -392,7 +419,8 @@ function Measure-SectionMissingAndOutputScript { } } } - + # Except records in allow list + $results = Get-RecordsNotInAllowList $results # Except the suppressed records $results = Get-NonExceptionRecord $results @@ -404,26 +432,6 @@ function Measure-SectionMissingAndOutputScript { } } -<# - .SYNOPSIS - Measure whether the AnalysisOutput entry is in white list. -#> -function Measure-InAllowList{ - param ( - [AnalysisOutput]$result - ) - # Skip the unexpected error caused by using to assign parameters - if($result.RuleName -eq "RedirectionNotSupported"){ - return $false - } - # Skip the invaild cmdlet "<" - $CommandName = ($result.Description -split " ")[0] - if($CommandName -eq "<"){ - return $false - } - return $true -} - <# .SYNOPSIS Invoke PSScriptAnalyzer with custom rules, return the error set. @@ -486,15 +494,34 @@ function Get-ScriptAnalyzerResult { 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))" - } } - # Measure whether the result is in the white list - if(Measure-InAllowList $result){ - $results += $result } + $results += $result } - #Except the suppressed records + # Except records in allow list + $results = Get-RecordsNotInAllowList $results + # Except the suppressed records $results = Get-NonExceptionRecord $results return $results +} + +<# + .SYNOPSIS + Retry import-module +#> +function Redo-ImportModule { + param ( + [string]$CommandName + ) + $modulePath = "$PSScriptRoot\..\..\..\..\artifacts\Debug\Az.*\Az.*.psd1" + Get-Item $modulePath | Import-Module -Global + $GetCommand = Get-Command $CommandName -ErrorAction SilentlyContinue + if ($null -eq $GetCommand) { + return $false + } + else{ + Write-Debug "Succeed by retrying import-module" + return $true + } } \ No newline at end of file diff --git a/tools/StaticAnalysis/IssueChecker/IssueChecker.cs b/tools/StaticAnalysis/IssueChecker/IssueChecker.cs index ca6bf02b6709..9c431ba92836 100644 --- a/tools/StaticAnalysis/IssueChecker/IssueChecker.cs +++ b/tools/StaticAnalysis/IssueChecker/IssueChecker.cs @@ -108,6 +108,7 @@ private bool IsSingleExceptionFileHasCriticalIssue(string exceptionFilePath, str } var errorText = new StringBuilder(); errorText.AppendLine(recordList.First().PrintHeaders()); + var warningText = new StringBuilder(); foreach (IReportRecord record in recordList) { if (record.Severity < 2) @@ -117,13 +118,17 @@ private bool IsSingleExceptionFileHasCriticalIssue(string exceptionFilePath, str } else if (record.Severity == 2 && outputWarning) { - errorText.AppendLine(record.FormatRecord()); + warningText.AppendLine(record.FormatRecord()); } } if (hasError) { Console.WriteLine("{0} Errors", exceptionFilePath); Console.WriteLine(errorText.ToString()); + if(outputWarning && !String.IsNullOrEmpty(warningText.ToString())){ + Console.WriteLine("Following are warning issues. It is recommended to correct them as well."); + Console.WriteLine(warningText.ToString()); + } } } return hasError;