diff --git a/tools/StaticAnalysis/ExampleAnalyzer/Measure-MarkdownOrScript.ps1 b/tools/StaticAnalysis/ExampleAnalyzer/Measure-MarkdownOrScript.ps1 index 726c3f2edf9a..7e372d44e605 100644 --- a/tools/StaticAnalysis/ExampleAnalyzer/Measure-MarkdownOrScript.ps1 +++ b/tools/StaticAnalysis/ExampleAnalyzer/Measure-MarkdownOrScript.ps1 @@ -4,21 +4,19 @@ .PARAMETER MarkdownPaths Markdown searching paths. Empty for current path. Supports wildcard. .PARAMETER ScriptPath - PowerShell script searching path. + PowerShell script searching paths. Empty for current path. Supports wildcard. .PARAMETER RulePaths PSScriptAnalyzer custom rules paths. Empty for current path. Supports wildcard. - .PARAMETER CodeMapPath - Code map path bound with the PowerShell script. .PARAMETER Recurse - To search markdowns recursively in the folders. + To search files recursively in the folders. .PARAMETER IncludeDefaultRules To analyze default rules provided by PSScriptAnalyzer. .PARAMETER OutputFolder Folder path storing output files. .PARAMETER SkipAnalyzing To skip analyzing step. Only extracting example codes from markdowns to the temp script. - .PARAMETER CleanScripts - To clean the temp script. + .PARAMETER NotCleanScripts + Do not clean the temp script. .NOTES File Name: Measure-MarkdownOrScript.ps1 #> @@ -32,17 +30,14 @@ param ( [string[]]$MarkdownPaths, [Parameter(Mandatory, ParameterSetName = "Script")] [AllowEmptyString()] - [string[]]$ScriptPath, + [string[]]$ScriptPaths, [string[]]$RulePaths, - [Parameter(Mandatory, ParameterSetName = "Script")] - [string]$CodeMapPath, - [Parameter(ParameterSetName = "Markdown")] [switch]$Recurse, [switch]$IncludeDefaultRules, [string]$OutputFolder = "$PSScriptRoot\..\..\..\artifacts\StaticAnalysisResults\ExampleAnalysis", [Parameter(ParameterSetName = "Markdown")] [switch]$SkipAnalyzing, - [switch]$CleanScripts + [switch]$NotCleanScripts ) . $PSScriptRoot\utils.ps1 @@ -51,17 +46,24 @@ $analysisResultsTable = @() $codeMap = @() $totalLine = 1 +$tempScript = "TempScript.ps1" +$tempScriptMap = "TempScript.Map.csv" +$TempScriptPath = "$OutputFolder\$tempScript" +$TempScriptMapPath = "$OutputFolder\$tempScriptMap" + +# Clean caches, remove files in "output" folder +Remove-Item $TempScriptPath -ErrorAction SilentlyContinue +Remove-Item $TempScriptMapPath -ErrorAction SilentlyContinue +Remove-Item $PSScriptRoot\..\..\..\artifacts\StaticAnalysisResults\ExampleIssues.csv -ErrorAction SilentlyContinue +Remove-Item $OutputFolder -ErrorAction SilentlyContinue +# Create output folder and temp script +$null = New-Item -ItemType Directory -Path $OutputFolder -ErrorAction SilentlyContinue +$null = New-Item -ItemType File $TempScriptPath + # Find examples in "help\*.md", output ".ps1" if ($PSCmdlet.ParameterSetName -eq "Markdown") { - # Clean caches, remove files in "output" folder - 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 - if ($MarkdownPaths -cmatch ".*\.txt") { + # When the input $MarkdownPaths is the path of txt file contained markdown paths + if ((Test-Path $MarkdownPaths -PathType Leaf) -and $MarkdownPaths.EndsWith(".txt")) { $MarkdownPath = Get-Content $MarkdownPaths } # When the input $MarkdownPaths is the path of a folder @@ -83,27 +85,29 @@ if ($PSCmdlet.ParameterSetName -eq "Markdown") { } $cmdlet = $_.BaseName $result = Measure-SectionMissingAndOutputScript $module $cmdlet $_.FullName ` - -OutputFolder $OutputFolder ` + -TempScriptPath $TempScriptPath ` -TotalLine $totalLine $analysisResultsTable += $result.Errors $codeMap += $result.CodeMap $totalLine = $result.TotalLine } } - $codeMap| Export-Csv "$OutputFolder\TempCodeMap.csv" -NoTypeInformation - if (!$SkipAnalyzing.IsPresent) { - $ScriptPath = "$OutputFolder\TempScript.ps1" - $CodeMapPath = "$OutputFolder\TempCodeMap.csv" + if(!$NotCleanScripts.IsPresent){ + $codeMap| Export-Csv $TempScriptMapPath -NoTypeInformation } } # Analyze scripts if ($PSCmdlet.ParameterSetName -eq "Script" -or !$SkipAnalyzing.IsPresent) { - # Read code map from file - $codeMap = Import-Csv $CodeMapPath + if ($PSCmdlet.ParameterSetName -eq "Script"){ + $codeMap = Merge-Scripts -ScriptPaths $ScriptPaths -Recurse:$Recurse.IsPresent -TempScriptPath $TempScriptPath + if(!$NotCleanScripts.IsPresent){ + $codeMap| Export-Csv $TempScriptMapPath -NoTypeInformation + } + } # Read and analyze ".ps1" in \ScriptsByExample Write-Output "Analyzing file ..." - $analysisResultsTable += Get-ScriptAnalyzerResult (Get-Item -Path $ScriptPath) $RulePaths -IncludeDefaultRules:$IncludeDefaultRules.IsPresent $codeMap -ErrorAction Continue + $analysisResultsTable += Get-ScriptAnalyzerResult -ScriptPath $TempScriptPath -RulePaths $RulePaths -IncludeDefaultRules:$IncludeDefaultRules.IsPresent -CodeMap $codeMap -ErrorAction Continue # Summarize analysis results, output in Result.csv if($analysisResultsTable){ @@ -112,6 +116,7 @@ if ($PSCmdlet.ParameterSetName -eq "Script" -or !$SkipAnalyzing.IsPresent) { } # Clean caches -if ($CleanScripts.IsPresent) { - Remove-Item $ScriptPath -Exclude *.csv -Recurse -ErrorAction Continue +if (!$NotCleanScripts.IsPresent) { + Remove-Item $TempScriptPath -ErrorAction Continue + Remove-Item $OutputFolder -ErrorAction SilentlyContinue } \ No newline at end of file diff --git a/tools/StaticAnalysis/ExampleAnalyzer/utils.ps1 b/tools/StaticAnalysis/ExampleAnalyzer/utils.ps1 index e260c8ce732a..605918066f34 100644 --- a/tools/StaticAnalysis/ExampleAnalyzer/utils.ps1 +++ b/tools/StaticAnalysis/ExampleAnalyzer/utils.ps1 @@ -11,6 +11,8 @@ Get-NonExceptionRecord Get-RecordsNotInAllowList Measure-SectionMissingAndOutputScript + Merge-Contents + Merge-Scripts Get-ScriptAnalyzerResult Set-AnalysisOutput Set-ExampleProperties @@ -234,7 +236,7 @@ function Measure-SectionMissingAndOutputScript { [string]$Module, [string]$Cmdlet, [string]$MarkdownPath, - [string]$OutputFolder, + [string]$TempScriptPath, [int]$TotalLine ) $missingSeverity = 1 @@ -357,27 +359,11 @@ function Measure-SectionMissingAndOutputScript { } } - # Output example codes to "TempScript.ps1" if ($missingExampleCode -eq 0) { - $cmdletExamplesScriptPath = "$OutputFolder\TempScript.ps1" - $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 = "}" - 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 + ($tempCodeMap, $TotalLine) = Merge-Contents -Content $exampleCodes -Module $Module -Cmdlet $Cmdlet -Example $exampleNumber -TotalLine $TotalLine -TempScriptPath $TempScriptPath + $codeMap += $tempCodeMap } } } @@ -394,6 +380,68 @@ function Measure-SectionMissingAndOutputScript { } } +<# + .SYNOPSIS + Merge the example codes or scripts into one PowerShell script and generate the code map. +#> +function Merge-Contents { + param( + [string[]]$Contents, + [string]$Module, + [string]$Cmdlet, + [int]$Example, + [int]$TotalLine, + [string]$TempScriptPath + ) + $codeMap =@() + $line = $Contents.Count + $functionHead = "function $Module" + if($null -ne $Cmdlet){ + $functionHead += "-$Cmdlet" + } + if($null -ne $exampleNumber){ + $functionHead += "-$exampleNumber" + } + $functionHead += "{" + Add-Content -Path (Get-Item $TempScriptPath).FullName -Value $functionHead + Add-Content -Path (Get-Item $TempScriptPath).FullName -Value $Contents + Add-Content -Path (Get-Item $TempScriptPath).FullName -Value "}" + for($i = 0; $i -le $line + 1; $i++){ + $codeMap += @{ + TotalLine = $TotalLine + $i + Module = $Module + Cmdlet = $Cmdlet + Example = $Example + Line = $i + } + } + $TotalLine = $TotalLine + $line + 2 + return ($codeMap, $TotalLine) +} + +<# + .SYNOPSIS + Merge PowerShell scripts into one and generate the code map. +#> +function Merge-Scripts { + param( + [string]$ScriptPaths, + [switch]$Recurse, + [string]$TempScriptPath + ) + $TotalLine = 1 + $codeMap = @() + foreach($_ in Get-ChildItem $ScriptPaths -Recurse:$Recurse.IsPresent){ + if((Test-Path $_ -PathType Leaf) -and $_.FullName.EndsWith(".ps1")){ + $fileName = (Get-Item -Path $_.FullName).Name + $scriptContent = Get-Content $_ + ($tempCodeMap, $TotalLine) = Merge-Contents -Content $scriptContent -Module $fileName -TotalLine $TotalLine -TempScriptPath $TempScriptPath + $codeMap += $tempCodeMap + } + } + return $codeMap +} + <# .SYNOPSIS Set properties for an AnalysisOutput object. @@ -429,12 +477,14 @@ function Set-AnalysisOutput { <# .SYNOPSIS Invoke PSScriptAnalyzer with custom rules, return the error set. + .PARAMETER RulePath + PSScriptAnalyzer custom rules path. Supports wildcard. #> function Get-ScriptAnalyzerResult { param ( [string]$ScriptPath, - [Parameter(Mandatory, HelpMessage = "PSScriptAnalyzer custom rules path. Supports wildcard.")] - [string[]]$RulePath, + [Parameter(Mandatory)] + [string[]]$RulePaths, [switch]$IncludeDefaultRules, [Object[]]$CodeMap ) @@ -444,11 +494,11 @@ function Get-ScriptAnalyzerResult { } # Invoke PSScriptAnalyzer : input scriptblock, output error set in $result with property: RuleName, Message, Extent - if ($null -eq $RulePath) { + if ($null -eq $RulePaths) { $analysisResults = Invoke-ScriptAnalyzer -Path $ScriptPath -IncludeDefaultRules:$IncludeDefaultRules.IsPresent } else { - $analysisResults = Invoke-ScriptAnalyzer -Path $ScriptPath -CustomRulePath $RulePath -IncludeDefaultRules:$IncludeDefaultRules.IsPresent + $analysisResults = Invoke-ScriptAnalyzer -Path $ScriptPath -CustomRulePath $RulePaths -IncludeDefaultRules:$IncludeDefaultRules.IsPresent } $errors = @() foreach($analysisResult in $analysisResults){