src/Ast.ps1
|
<# .SYNOPSIS Shared AST helpers for PSComplexity: unit discovery, nearest-function attribution, and nesting-depth computation. .DESCRIPTION A "unit" is a function/filter body, plus one synthetic '<script-body>' per file for top-level code. Every increment is attributed to the nearest enclosing function, so nested functions are measured independently. Nesting depth (used by cognitive complexity) counts the flow-structuring ancestors between a node and its enclosing function -- if/loops/switch/catch/trap/ternary AND nested script-block lambdas (e.g. a ForEach-Object body). That mirrors SonarSource's B3 nesting-level rule, where nested functions/lambdas raise the nesting level. #> # Ancestor types that raise the cognitive nesting level (B3). $script:PSCxNestingTypes = @( 'IfStatementAst', 'ForEachStatementAst', 'ForStatementAst', 'WhileStatementAst', 'DoWhileStatementAst', 'DoUntilStatementAst', 'SwitchStatementAst', 'CatchClauseAst', 'TrapStatementAst', 'TernaryExpressionAst', 'ScriptBlockExpressionAst' ) function Get-PSCxUnitKey { # Nearest enclosing function 'name@line', else '<script-body>'. [OutputType([string])] [CmdletBinding()] param([Parameter(Mandatory)] $Node) $p = $Node.Parent while ($p) { if ($p -is [System.Management.Automation.Language.FunctionDefinitionAst]) { return '{0}@{1}' -f $p.Name, $p.Extent.StartLineNumber } $p = $p.Parent } return '<script-body>' } function Get-PSCxNesting { # Count of nesting-raising ancestors up to (not crossing) the enclosing function. [OutputType([int])] [CmdletBinding()] param([Parameter(Mandatory)] $Node) $depth = 0 $p = $Node.Parent while ($p -and $p -isnot [System.Management.Automation.Language.FunctionDefinitionAst]) { if ($p.GetType().Name -in $script:PSCxNestingTypes) { $depth++ } $p = $p.Parent } return $depth } function Get-PSCxUnitTable { # Baseline unit -> start-line map: every function + the script body, so a # decision-free unit still reports (cyclomatic 1 / cognitive 0). [OutputType([hashtable])] [CmdletBinding()] param([Parameter(Mandatory)] $Ast) $units = @{ '<script-body>' = 1 } foreach ($fn in $Ast.FindAll({ param($x) $x -is [System.Management.Automation.Language.FunctionDefinitionAst] }, $true)) { $units['{0}@{1}' -f $fn.Name, $fn.Extent.StartLineNumber] = $fn.Extent.StartLineNumber } return $units } function Get-PSCxEnclosingFunctionName { # Name of the nearest enclosing function (for recursion detection), else $null. [OutputType([string])] [CmdletBinding()] param([Parameter(Mandatory)] $Node) $p = $Node.Parent while ($p) { if ($p -is [System.Management.Automation.Language.FunctionDefinitionAst]) { return $p.Name } $p = $p.Parent } return $null } |