Public/Export-PSModuleMarkDownReport.ps1

<#
.SYNOPSIS
Exports a comprehensive Markdown analysis report for a PowerShell module.

.DESCRIPTION
Export-PSModuleMarkdownReport collects summary, metrics, complexity, dependency,
and maintainability data for the specified module and writes the results to a
Markdown file.

The report includes:
- module information and core metrics
- full function inventory
- largest functions
- unused private functions
- refactoring candidates
- Mermaid charts for size distribution, complexity ranking, and dependencies

.PARAMETER ModuleName
Specifies the module to analyze. Use the module name as recognized by
PowerShell (for example, a loaded module or module discoverable in PSModulePath).

.PARAMETER Path
Specifies the output path of the Markdown report.
If omitted, the function creates a file name in the current directory using the
pattern ModuleAnalysis_<ModuleName>_<Version>.md.

.EXAMPLE
Export-PSModuleMarkdownReport -ModuleName PSModuleQuantityAnalyzer

Generates a Markdown report for PSModuleQuantityAnalyzer and writes it to the
default output file in the current directory.

.EXAMPLE
Export-PSModuleMarkdownReport -ModuleName PSModuleQuantityAnalyzer -Path .\reports\qa.md

Generates a Markdown report and saves it to a custom file path.

.OUTPUTS
None. This function writes a Markdown file to disk.

.NOTES
Use -Verbose to display the final export path.
#>

function Export-PSModuleMarkdownReport {

    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [string]$ModuleName,

        [string]$Path
    )

    $summary = Get-PSModuleSummary -ModuleName $ModuleName
    $metrics = Get-PSModuleMetrics -ModuleName $ModuleName
    $largest = Get-PSModuleLargestFunctions -ModuleName $ModuleName -Top 5
    $unused = Get-PSModuleUnusedPrivateFunctions -ModuleName $ModuleName
    $refactor = Get-PSModuleRefactoringCandidates -ModuleName $ModuleName

    $quantity = Get-PSModuleQuantity -ModuleName $ModuleName |
    Select-Object Function, HelpTopics, TotalLines, LinesOfCode, LinesOfComment, References, PartOfFile |
    Sort-Object Function

    if (-not $Path) {
        $Path = ".\ModuleAnalysis_{0}_{1}.md" -f $summary.ModuleName, $summary.Version
    }

    $collectionDate = (Get-Date).ToString("yyyy-MM-dd HH:mm:ss")

    $md = @()

    $md += "# PowerShell Module Quantity Analysis"
    $md += ([string][char]96 + ("Date of Analysis: $collectionDate") + [string][char]96)
    $md += "## Module Information"
    $md += "| Property | Value |"
    $md += "|---|---|"
    $md += "| Module | $($summary.ModuleName) |"
    $md += "| Version | $($summary.Version) |"
        $md += "| Last Update | $($summary.LastUpdate) |"
    $md += "| Path | $($summary.ModulePath) |"
    $md += ""

    $md += "## Module Metrics"
    $md += "| Metric | Value |"
    $md += "|---|---|"
    $md += "| Total Functions | $($metrics.Functions) |"
    $md += "| Public Functions | $($metrics.PublicFunctions) |"
    $md += "| Private Functions | $($metrics.PrivateFunctions) |"
    $md += "| Total Lines | $($metrics.TotalLines) |"
    $md += "| Lines of Code | $($metrics.LinesOfCode) |"
    $md += "| Comment Lines | $($metrics.LinesOfComment) |"
    $md += "| Comment Ratio | $($metrics.CommentRatioPercent) % |"
    $md += "| Average Function Size | $([math]::Round($metrics.AverageFunctionLines,2)) |"
    $md += "| Largest Function | $($metrics.LargestFunction) |"
    $md += "| Largest Function Lines | $($metrics.LargestFunctionLines) |"
    $md += "| Largest Function File | $($metrics.LargestFunctionFile) |"

    $md += ""
    $md += "## Function Inventory"
    $md += ""
    $md += "| Function | Help Topics | Total Lines | Code | Comments | References | File |"
    $md += "|---|---|---|---|---|---|---|"

    foreach ($f in $quantity) {
        $md += "| $($f.Function) | $($f.HelpTopics) | $($f.TotalLines) | $($f.LinesOfCode) | $($f.LinesOfComment) | $($f.References) | $($f.PartOfFile) |"
    }

    $md += ""
    $md += "## Largest Functions"
    $md += "| Function | Lines | File |"
    $md += "|---|---|---|"

    foreach ($f in $largest) {
        $md += "| $($f.Function) | $($f.TotalLines) | $($f.PartOfFile) |"
    }

    $md += ""
    $md += "## Unused Private Functions"
    $md += "| Function | File |"
    $md += "|---|---|"

    if ($unused) {
        foreach ($u in $unused) {
            $md += "| $($u.Function) | $($u.PartOfFile) |"
        }
    }
    else {
        $md += "| _None detected_ | - |"
    }

    $md += ""
    $md += "## Refactoring Candidates"
    $md += ""
    $md += "The following functions were identified as potential **refactoring candidates**."
    $md += ""
    $md += "- **LargeFunction** - Function exceeds the recommended size threshold."
    $md += '- **HighComplexity** - Function contains many control structures (`if`, `switch`, `foreach`, etc.).'
    $md += ""
    $md += "| Function | Issues |"
    $md += "|---|---|"

    if ($refactor) {
        foreach ($r in $refactor) {
            $md += "| $($r.Function) | $($r.Issues) |"
        }
    }
    else {
        $md += "| _None detected_ | - |"
    }


    # Function Size Distribution
    #$md += "```powershell"

    $sizeData = Get-PSModuleQuantity -ModuleName $ModuleName

    $sizeBuckets = @{
        "0-25 Lines"    = ($sizeData | Where-Object TotalLines -le 25).Count
        "26-50 Lines"   = ($sizeData | Where-Object { $_.TotalLines -gt 25 -and $_.TotalLines -le 50 }).Count
        "51-100 Lines"  = ($sizeData | Where-Object { $_.TotalLines -gt 50 -and $_.TotalLines -le 100 }).Count
        "101-200 Lines" = ($sizeData | Where-Object { $_.TotalLines -gt 100 -and $_.TotalLines -le 200 }).Count
        "201-500 Lines" = ($sizeData | Where-Object { $_.TotalLines -gt 200 -and $_.TotalLines -le 500 }).Count
        ">500 Lines"    = ($sizeData | Where-Object TotalLines -gt 500).Count
    }

    $md += ""
    $md += "## Function Size Distribution"
    $md += '```mermaid'
    $md += "pie"
    $md += " title Function Size Distribution"

    foreach ($bucket in $sizeBuckets.Keys) {
        $md += " ""$bucket"" : $($sizeBuckets[$bucket])"
    }

    $md += '```'


    $complexity = Get-PSModuleComplexity -ModuleName $ModuleName |
    Sort-Object Complexity -Descending |
    Select-Object -First 10

    $labels = ($complexity.Function | ForEach-Object {
            ($_ -replace '[^A-Za-z0-9_]', '_')
        }) -join ', '

    $values = ($complexity.Complexity) -join ', '

    $maxComplexity = ($complexity.Complexity | Measure-Object -Maximum).Maximum

    $md += ""
    $md += "## Complexity Ranking"
    $md += '```mermaid'
    $md += "xychart-beta"
    $md += " title ""Top Function Complexity"""
    $md += " x-axis [$labels]"
    $md += " y-axis ""Complexity"" 0 --> $maxComplexity"
    $md += " bar [$values]"
    $md += '```'

    $deps = Get-PSModuleDependencyGraph -ModuleName $ModuleName |
    Group-Object Function |
    Sort-Object Count -Descending |
    Select-Object -First 25 |
    ForEach-Object { $_.Group }

    $md += ""
    $md += "## Function Dependency Graph (Top 25)"
    $md += '```mermaid'
    $md += "graph TD"

    foreach ($d in $deps) {

        $from = ($d.Function -replace '[^A-Za-z0-9_]', '_')
        $to = ($d.Calls -replace '[^A-Za-z0-9_]', '_')

        $md += " $from[`"$($d.Function)`"] --> $to[`"$($d.Calls)`"]"
    }

    $md += '```'



    $md | Set-Content $Path -Encoding UTF8


    Invoke-Output -Type TextMaker -Message "Markdown report exported to: " -TextMaker $Path


}