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)
- Edge —
from → tomeans:fromdepends onto(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.
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:
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.
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:
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 = $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:
$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.