Get-CyclomaticComplexity.ps1
<#PSScriptInfo
.VERSION 1.0.1 .DESCRIPTION Calculates the cyclomatic complexity of functions in a given PowerShell script or code block. .GUID 74b3a6a0-ec1b-459e-869a-5cafe8f744e3 .AUTHOR https://github.com/voytas75 .TAGS Cyclomatic, Complexity, Script, Function .PROJECTURI https://github.com/voytas75/VoytasCodeLab#get-cyclomaticcomplexity .RELEASENOTES 1.0.1: initializing. #> function Get-CyclomaticComplexity { <# .SYNOPSIS Calculates the cyclomatic complexity of functions in a given PowerShell script or code block. .DESCRIPTION This function analyzes the provided PowerShell script file or code block to calculate the cyclomatic complexity of each function defined within it. The cyclomatic complexity score is interpreted as follows: 1: The function has a single execution path with no control flow statements (e.g., if, else, while, etc.). This typically means the function is simple and straightforward. 2 or 3: Functions with moderate complexity, having a few conditional paths or loops. 4-7: These functions are more complex, with multiple decision points and/or nested control structures. Above 7: Indicates higher complexity, which can make the function harder to test and maintain. .PARAMETER FilePath The path to the PowerShell script file to be analyzed. .PARAMETER CodeBlock A string containing the PowerShell code block to be analyzed. .EXAMPLE Get-CyclomaticComplexity -FilePath "C:\Scripts\MyScript.ps1" .EXAMPLE $code = @" function Test { if ($true) { Write-Output "True" } else { Write-Output "False" } } "@ Get-CyclomaticComplexity -CodeBlock $code .NOTES Author: https://github.com/voytas75 Date: 2024-06-17 #> param( [Parameter(Mandatory = $false)] [string]$FilePath, [Parameter(Mandatory = $false)] [string]$CodeBlock ) # Initialize tokens array $tokens = @() if ($FilePath) { if (Test-Path $FilePath -PathType Leaf) { # Parse the script file $ast = [System.Management.Automation.Language.Parser]::ParseInput((Get-Content -Path $FilePath -Raw), [ref]$tokens, [ref]$null) } else { Write-Error "File '$FilePath' does not exist." return } } elseif ($CodeBlock) { # Parse the code block $ast = [System.Management.Automation.Language.Parser]::ParseInput($CodeBlock, [ref]$tokens, [ref]$null) } else { Write-Error "No FilePath or CodeBlock provided for analysis." return } # Identify and loop through all function definitions $functions = $ast.FindAll({ $args[0] -is [System.Management.Automation.Language.FunctionDefinitionAst] }, $true) if ($functions.Count -eq 0) { Write-Information "-- No functions found for cyclomatic complexity analysis." -InformationAction Continue return $false } foreach ($function in $functions) { $cyclomaticComplexity = 1 # Filter tokens that belong to the current function $functionTokens = $tokens | Where-Object { $_.Extent.StartOffset -ge $function.Extent.StartOffset -and $_.Extent.EndOffset -le $function.Extent.EndOffset } $observedBlocks = @() foreach ($token in $functionTokens) { if ($token.Kind -in 'If', 'ElseIf', 'Catch') { $cyclomaticComplexity++ } elseif ($token.Kind -in 'While', 'For', 'Switch') { $cyclomaticComplexity++ $observedBlocks += $token } elseif ($token.Kind -in 'EndWhile', 'EndFor', 'EndSwitch') { $observedBlocks = $observedBlocks | Where-Object { $_ -ne $token } } } # Determine the complexity description $description = switch ($cyclomaticComplexity) { 1 { "The function has a single execution path with no control flow statements. This typically means the function is simple and straightforward." } { $_ -in 2..3 } { "Functions with moderate complexity, having a few conditional paths or loops." } { $_ -in 4..7 } { "These functions are more complex, with multiple decision points and/or nested control structures." } default { "Indicates higher complexity, which can make the function harder to test and maintain." } } Write-Output "$($function.Name) : $cyclomaticComplexity - $description" } } |