Public/Get-PSModuleNestedDependencies.ps1

<#
.SYNOPSIS
Finds function calls that cross the boundary between a module and its nested modules.

.DESCRIPTION
Get-PSModuleNestedDependencies parses module source files with PowerShell AST and
returns every call from a function in one (nested) module to a function that is
defined in a different (nested) module of the same module.

The result shows how tightly the main module and its nested modules are coupled
and helps to decide whether a nested module could be extracted or published
separately.

.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)
or the path to the module manifest.

.EXAMPLE
Get-PSModuleNestedDependencies -ModuleName .\SSAwBH.psd1 | Group-Object FromModule, ToModule

Counts the calls between the main module and each nested module.

.OUTPUTS
PSCustomObject. The output includes Function, FromModule, Calls, ToModule, and File.

.NOTES
Only calls that match function names defined in the module sources are returned.
If a function name exists in several nested modules, the first match is used.
#>

function Get-PSModuleNestedDependencies {

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

    $data = Get-PSModuleQuantity -ModuleName $ModuleName

    $moduleOfFunction = @{}
    $moduleOfFile = @{}

    foreach ($item in $data) {
        if (-not $moduleOfFunction.ContainsKey($item.Function)) {
            $moduleOfFunction[$item.Function] = $item.NestedModule
        }
        $moduleOfFile[$item.FullName] = $item.NestedModule
    }

    foreach ($file in $moduleOfFile.Keys | Sort-Object) {

        $ast = [System.Management.Automation.Language.Parser]::ParseFile(
            $file,
            [ref]$null,
            [ref]$null
        )

        $functions = $ast.FindAll({
                param($node)
                $node -is [System.Management.Automation.Language.FunctionDefinitionAst]
            }, $true)

        foreach ($func in $functions) {

            $commands = $func.FindAll({
                    param($node)
                    $node -is [System.Management.Automation.Language.CommandAst]
                }, $true)

            foreach ($cmd in $commands) {

                $name = $cmd.GetCommandName()

                if ($name -and $moduleOfFunction.ContainsKey($name) -and $moduleOfFunction[$name] -ne $moduleOfFile[$file]) {

                    [PSCustomObject]@{
                        Function   = $func.Name
                        FromModule = $moduleOfFile[$file]
                        Calls      = $name
                        ToModule   = $moduleOfFunction[$name]
                        File       = $file
                    }

                }

            }

        }

    }
}