Dependency Analysis
The dependency analysis system tracks relationships between types, methods, and other symbols in C# code to identify coupling, circular dependencies, and architectural issues.
Overview
Location: src/bsharp_analysis/src/artifacts/dependencies.rs
The dependency analysis builds a directed graph of symbol relationships, where:
- Nodes represent symbols (classes, interfaces, methods, etc.)
- Edges represent dependencies (inheritance, method calls, field types, etc.)
Dependency Types
Type Dependencies
Inheritance:
public class Derived : Base { } // Derived depends on Base
Interface Implementation:
public class MyClass : IInterface { } // MyClass depends on IInterface
Field Types:
public class Container {
private Helper helper; // Container depends on Helper
}
Method Parameters and Return Types:
public Response Process(Request req) { } // Process depends on Request and Response
Member Dependencies
Method Calls:
public void Caller() {
Helper.DoSomething(); // Caller depends on Helper.DoSomething
}
Property Access:
var value = obj.Property; // Depends on Property
Constructor Calls:
var instance = new MyClass(); // Depends on MyClass constructor
Dependency Graph Structure
DependencyGraph
#![allow(unused)] fn main() { pub struct DependencyGraph { // Symbol ID -> list of symbols it depends on dependencies: HashMap<SymbolId, Vec<SymbolId>>, } }
Operations
Adding Dependencies:
#![allow(unused)] fn main() { graph.add_dependency(from_symbol, to_symbol); }
Querying Dependencies:
#![allow(unused)] fn main() { // Direct dependencies let deps = graph.get_dependencies(symbol_id); // Transitive dependencies let all_deps = graph.get_transitive_dependencies(symbol_id); // Reverse dependencies (who depends on this symbol) let dependents = graph.get_dependents(symbol_id); }
Circular Dependency Detection
Algorithm
The analysis uses depth-first search to detect cycles in the dependency graph:
- Start from each symbol
- Traverse dependencies depth-first
- Track visited nodes in current path
- If we revisit a node in current path, cycle detected
Example
public class A {
private B b; // A depends on B
}
public class B {
private C c; // B depends on C
}
public class C {
private A a; // C depends on A -> CYCLE: A -> B -> C -> A
}
Detection:
#![allow(unused)] fn main() { let cycles = graph.find_cycles(); for cycle in cycles { // Report diagnostic for circular dependency session.diagnostics.add( DiagnosticCode::CircularDependency, format!("Circular dependency detected: {:?}", cycle) ); } }
Coupling Metrics
Afferent Coupling (Ca)
Definition: Number of types that depend on this type (incoming dependencies)
Interpretation:
- High Ca = Many types depend on this type (responsibility)
- Type is stable and hard to change
Efferent Coupling (Ce)
Definition: Number of types this type depends on (outgoing dependencies)
Interpretation:
- High Ce = This type depends on many others
- Type is unstable and sensitive to changes
Instability (I)
Formula: I = Ce / (Ca + Ce)
Range: 0.0 to 1.0
- 0.0 = Maximally stable (only incoming dependencies)
- 1.0 = Maximally unstable (only outgoing dependencies)
Example:
#![allow(unused)] fn main() { let ca = graph.afferent_coupling(symbol_id); let ce = graph.efferent_coupling(symbol_id); let instability = ce as f64 / (ca + ce) as f64; if instability > 0.8 { // Highly unstable type - consider refactoring } }
Dependency Summary
DependencySummary
#![allow(unused)] fn main() { pub struct DependencySummary { pub total_nodes: usize, pub total_edges: usize, pub circular_dependencies: usize, pub max_depth: usize, } }
Generated by: DependencyGraph::summarize()
Included in: AnalysisReport
Usage in Analysis Pipeline
Phase: Global
Dependency analysis runs in the Global phase after symbol indexing:
#![allow(unused)] fn main() { // In AnalyzerPipeline Phase::Index -> Build SymbolIndex Phase::Global -> Build DependencyGraph Phase::Semantic -> Use dependencies for semantic analysis }
Integration with Passes
DependencyPass (if implemented):
#![allow(unused)] fn main() { impl AnalyzerPass for DependencyPass { fn id(&self) -> &'static str { "dependencies" } fn phase(&self) -> Phase { Phase::Global } fn run(&self, cu: &CompilationUnit, session: &mut AnalysisSession) { let graph = build_dependency_graph(cu, &session.artifacts.symbols); session.artifacts.dependencies = Some(graph); } } }
Building Dependency Graph
From CompilationUnit
#![allow(unused)] fn main() { pub fn build_dependency_graph( cu: &CompilationUnit, symbols: &SymbolIndex ) -> DependencyGraph { let mut graph = DependencyGraph::new(); // Visit all declarations for decl in &cu.declarations { match decl { TopLevelDeclaration::Class(class) => { analyze_class_dependencies(class, symbols, &mut graph); } // ... other declaration types } } graph } }
From Class Declaration
#![allow(unused)] fn main() { fn analyze_class_dependencies( class: &ClassDeclaration, symbols: &SymbolIndex, graph: &mut DependencyGraph ) { let class_symbol = symbols.lookup(&class.identifier.name); // Base types for base_type in &class.base_types { if let Some(base_symbol) = resolve_type(base_type, symbols) { graph.add_dependency(class_symbol, base_symbol); } } // Members for member in &class.body_declarations { analyze_member_dependencies(member, class_symbol, symbols, graph); } } }
Dependency Visualization
Dependency Matrix
Generate a matrix showing which types depend on which:
A B C D
A - X - X
B - - X -
C X - - -
D - - - -
- Row A, Column B = X means A depends on B
Dependency Tree
MyApp
├── Services
│ ├── UserService
│ │ ├── IUserRepository
│ │ └── IEmailService
│ └── OrderService
│ ├── IOrderRepository
│ └── IPaymentService
└── Models
├── User
└── Order
Diagnostics
Circular Dependency Warning
warning[DEP001]: Circular dependency detected
--> src/ClassA.cs:3:5
|
3 | private ClassB b;
| ^^^^^^ ClassA depends on ClassB
|
= note: Dependency cycle: ClassA -> ClassB -> ClassC -> ClassA
High Coupling Warning
warning[DEP002]: High efferent coupling detected
--> src/GodClass.cs:1:14
|
1 | public class GodClass {
| ^^^^^^^^ depends on 25 other types
|
= help: Consider breaking this class into smaller, focused classes
Unstable Dependency Warning
warning[DEP003]: Stable type depends on unstable type
--> src/StableClass.cs:5:5
|
5 | private UnstableClass helper;
| ^^^^^^^^^^^^^ instability = 0.95
|
= note: Stable types (instability < 0.2) should not depend on unstable types (instability > 0.8)
Configuration
Thresholds
[analysis.dependencies]
max_efferent_coupling = 20
max_afferent_coupling = 10
max_instability = 0.8
warn_circular_dependencies = true
CLI Usage
# Analyze dependencies
bsharp analyze MyProject.csproj --enable-pass dependencies
# Generate dependency report
bsharp analyze MyProject.sln --out deps.json --format pretty-json
Future Enhancements
Planned Features
-
Package-Level Dependencies
- Track dependencies between namespaces/assemblies
- Identify layering violations
-
Dependency Metrics Dashboard
- Visual dependency graphs
- Coupling heatmaps
- Trend analysis over time
-
Architectural Rules
- Define allowed/forbidden dependencies
- Enforce layered architecture
- Prevent specific coupling patterns
-
Dependency Injection Analysis
- Track DI container registrations
- Verify dependency lifetimes
- Detect missing registrations
Implementation Status
Current State:
- Basic dependency graph structure defined
- Integration with analysis pipeline planned
- Circular dependency detection algorithm ready
TODO:
- Implement full dependency extraction from AST
- Add coupling metrics calculation
- Create dependency visualization tools
- Add comprehensive tests
Related Documentation
- Analysis Pipeline - How dependency analysis fits in the pipeline
- Control Flow Analysis - Related analysis type
- Metrics Collection - Coupling metrics
- Architecture Decisions - Design rationale
References
- Implementation:
src/bsharp_analysis/src/artifacts/dependencies.rs - Tests:
src/bsharp_tests/src/analysis/dependencies/(planned) - Related Passes:
src/bsharp_analysis/src/passes/(when implemented)