Skip to content

Dependency Analysis

PSScriptBuilder uses PowerShell AST analysis to build a dependency graph of all components and determine the correct load order for the generated output script. This ensures that base classes always appear before derived classes, and that interdependent components are placed correctly.

How Dependency Detection Works

When a collector processes a source file, the AST engine extracts dependency information for each component:

Component Dependencies extracted
Class BaseClass, TypeReferences (properties, method parameters), CalledFunctions
Function CalledFunctions, TypeReferences
Enum None (no outgoing dependencies)

Built-in types (string, int, bool, System.*, etc.) are automatically filtered out — only project-defined types create graph edges.

The Dependency Graph

Internally, all dependencies are stored as a directed graph:

  • Node — a component name (class, function, or enum)
  • Edgefrom → to means: from depends on to (prerequisite)

Example: if Dog inherits from Animal, the graph contains an edge Dog → Animal. Topological sort then guarantees Animal appears before Dog in the output.

Running a Dependency Analysis

Use Get-PSScriptBuilderDependencyAnalysis to inspect the dependency structure without performing a build:

$cc = New-PSScriptBuilderContentCollector |
    Add-PSScriptBuilderCollector -Type Class    -IncludePath "src\Classes" |
    Add-PSScriptBuilderCollector -Type Function -IncludePath "src\Public"

$analysis = Get-PSScriptBuilderDependencyAnalysis -ContentCollector $cc

Write-Host "Total components:    $($analysis.TotalComponents)"
Write-Host "Total nodes:         $($analysis.TotalNodes)"
Write-Host "Total edges:         $($analysis.TotalEdges)"
Write-Host "Has cycles:          $($analysis.HasCycles)"
Write-Host "Has cross-deps:      $($analysis.HasCrossDependencies)"
Write-Host "Ordered components:  $($analysis.OrderedComponents -join ' -> ')"

Analysis Result Properties

Get-PSScriptBuilderDependencyAnalysis returns a PSScriptBuilderDependencyAnalysisResult:

Property Type Description
HasCycles bool Whether a circular dependency was detected
CyclePath string[] The components forming the cycle, e.g. A → B → C → A. Empty if no cycle.
HasCrossDependencies bool Whether classes and functions are interleaved in the sorted order (see below). Always false if HasCycles is true.
OrderedComponents string[] Topologically sorted component names, enums first. Empty if HasCycles is true.
ComponentCounts PSScriptBuilderBuildComponentCounts Per-type counts: UsingStatements, EnumDefinitions, ClassDefinitions, FunctionDefinitions, FileContents
TotalComponents int Sum of all ComponentCounts fields
TotalNodes int Number of nodes in the dependency graph
TotalEdges int Number of edges (dependencies) in the graph
DependencyGraph PSScriptBuilderDependencyGraph The full graph object for advanced queries

Circular Dependencies

If a circular dependency exists (e.g. ClassA inherits from ClassB which inherits from ClassA), topological sorting is not possible. The build fails immediately with a clear error.

Check for circular dependencies
if ($analysis.HasCycles) {
    Write-Error "Circular dependency detected:"
    Write-Host ($analysis.CyclePath -join ' -> ')
}

The CyclePath array contains the names of all components involved in the cycle, with the first component repeated at the end to show the closed loop:

Example cycle path
ServiceA -> ServiceB -> ServiceC -> ServiceA

Resolve cycles before building

Invoke-PSScriptBuilderBuild will throw an error if cycles exist. Always check HasCycles when diagnosing a failed build.

Cross-Dependencies

Cross-dependencies occur when a class must appear after a function in the topological order — meaning a class has a transitive dependency on a function. This is detected by scanning the sorted component list for a class that follows a function.

Check for cross-dependencies
if ($analysis.HasCrossDependencies) {
    Write-Host "Cross-dependencies detected — use {{ORDERED_COMPONENTS}} in template"
} else {
    Write-Host "Free mode — separate per-type placeholders work fine"
}

When HasCrossDependencies is true, the template must use {{ORDERED_COMPONENTS}} instead of separate {{ClassDefinitions}} / {{FunctionDefinitions}} placeholders. See the Templates Guide for details.

Topological Sort — Load Order Guarantee

The topological sort uses Kahn's algorithm and guarantees:

  • Every prerequisite appears before the components that depend on it
  • Enums are always placed first in the order (they have no dependencies)
  • Dependency order is guaranteed — every prerequisite always appears before its dependents
  • The relative order of independent components may vary between runs

Example for an inheritance chain Animal ← Mammal ← Dog:

Example: ordered components
OrderedComponents: Animal, Mammal, Dog

Animal loads first, Mammal can then inherit from it, Dog can then inherit from Mammal.

Impact Analysis with the Dependency Graph

The DependencyGraph property on the result object exposes two directional queries:

Graph queries
$graph = $analysis.DependencyGraph

# Forward: what does "ClassA" depend on?
$prerequisites = $graph.GetDependencies("ClassA")
Write-Host "ClassA depends on: $($prerequisites -join ', ')"

# Reverse (impact): what would break if "BaseClass" changes?
$dependents = $graph.GetDependents("BaseClass")
Write-Host "Changing BaseClass affects: $($dependents -join ', ')"
Method Direction Description
GetDependencies(name) Forward What name depends on (its prerequisites)
GetDependents(name) Reverse / Impact What depends on name (would be affected by changes)
GetAllNodes() All component names in the graph
HasNode(name) Whether a component is in the graph
GetNodeCount() Total number of nodes
GetEdgeCount() Total number of dependency edges

Component Statistics

$counts = $analysis.ComponentCounts

Write-Host "Using statements: $($counts.UsingStatements)"
Write-Host "Enums:            $($counts.EnumDefinitions)"
Write-Host "Classes:          $($counts.ClassDefinitions)"
Write-Host "Functions:        $($counts.FunctionDefinitions)"
Write-Host "Files:            $($counts.FileContents)"
Write-Host "Total:            $($analysis.TotalComponents)"

Pre-Build Validation Pattern

Use dependency analysis as a pre-flight check before building:

Pre-build validation
$cc = New-PSScriptBuilderContentCollector |
    Add-PSScriptBuilderCollector -Type Class    -IncludePath "src\Classes" |
    Add-PSScriptBuilderCollector -Type Function -IncludePath "src\Public"

$analysis = Get-PSScriptBuilderDependencyAnalysis -ContentCollector $cc

if ($analysis.HasCycles) {
    Write-Error "Build aborted — circular dependency: $($analysis.CyclePath -join ' -> ')"
    return
}

if ($analysis.HasCrossDependencies) {
    Write-Warning "Cross-dependencies detected — ensure template uses {{ORDERED_COMPONENTS}}"
}

Invoke-PSScriptBuilderBuild -ContentCollector $cc `
    -TemplatePath "template.psm1" `
    -OutputPath   "output.psm1"

How External Dependencies Are Handled

References to types that are not defined in the project (e.g. [System.IO.FileInfo], [hashtable]) are automatically excluded from the graph. Only components whose names match a collected component in the project are treated as internal dependencies. External type references are silently skipped with a verbose log entry.

See Also