1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
//! Inheritance command - Extract and visualize class hierarchies
//!
//! Analyzes class inheritance relationships across a codebase:
//! - Python: class inheritance, ABC, Protocol, metaclasses
//! - TypeScript: class extends, implements, interfaces
//! - Go: struct embedding (modeled as inheritance)
//! - Rust: trait implementations
//!
//! # Output Formats
//!
//! - JSON: Full structured output (default)
//! - DOT: Graphviz format for visualization
//! - text: Human-readable tree format
//!
//! # Mitigations Addressed
//!
//! - A2: Diamond detection uses BFS + set intersection (O(|ancestors|))
//! - A12: Python metaclass extraction
//! - A14: Go struct embedding as Embeds relationships
//! - A16: Rust trait impl blocks
//! - A17: --depth requires --class validation
//! - A19: DOT output properly escapes special characters
use std::path::PathBuf;
use anyhow::Result;
use clap::Args;
use tldr_core::inheritance::{extract_inheritance, format_dot, format_text, InheritanceOptions};
use tldr_core::Language;
use crate::output::{OutputFormat, OutputWriter};
/// Extract class inheritance hierarchies
#[derive(Debug, Args)]
pub struct InheritanceArgs {
/// Path to file or directory to analyze (default: current directory)
#[arg(default_value = ".")]
pub path: PathBuf,
/// Programming language (auto-detect if not specified)
#[arg(long, short = 'l')]
pub lang: Option<Language>,
/// Focus on specific class (shows ancestors + descendants)
#[arg(long, short = 'c')]
pub class: Option<String>,
/// Limit traversal depth (requires --class)
#[arg(long, short = 'd')]
pub depth: Option<usize>,
/// Skip ABC/Protocol/mixin/diamond detection
#[arg(long)]
pub no_patterns: bool,
/// Skip external base resolution
#[arg(long)]
pub no_external: bool,
/// Output format override (backwards compatibility, prefer global --format/-f)
#[arg(long = "output", short = 'o', hide = true, value_parser = parse_inheritance_format)]
pub output: Option<InheritanceFormat>,
}
/// Inheritance-specific output formats (includes DOT)
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum InheritanceFormat {
Json,
Text,
Dot,
}
fn parse_inheritance_format(s: &str) -> Result<InheritanceFormat, String> {
match s.to_lowercase().as_str() {
"json" => Ok(InheritanceFormat::Json),
"text" => Ok(InheritanceFormat::Text),
"dot" | "graphviz" => Ok(InheritanceFormat::Dot),
_ => Err(format!(
"Invalid format '{}'. Expected: json, text, or dot",
s
)),
}
}
impl InheritanceArgs {
/// Run the inheritance command
pub fn run(&self, format: OutputFormat, quiet: bool) -> Result<()> {
let writer = OutputWriter::new(format, quiet);
writer.progress(&format!(
"Analyzing inheritance in {}...",
self.path.display()
));
// Build options
let options = InheritanceOptions {
class_filter: self.class.clone(),
depth: self.depth,
no_patterns: self.no_patterns,
no_external: self.no_external,
..Default::default()
};
// Run analysis
let report = extract_inheritance(&self.path, self.lang, &options)?;
// Determine output format
let inh_format = self.output.unwrap_or_else(|| {
if writer.is_text() {
InheritanceFormat::Text
} else {
InheritanceFormat::Json
}
});
// Output based on format
match inh_format {
InheritanceFormat::Json => {
writer.write(&report)?;
}
InheritanceFormat::Text => {
let text = format_text(&report);
writer.write_text(&text)?;
}
InheritanceFormat::Dot => {
let dot = format_dot(&report);
writer.write_text(&dot)?;
}
}
// Summary if not quiet
if !quiet {
eprintln!(
"Found {} classes in {}ms",
report.count, report.scan_time_ms
);
if !report.diamonds.is_empty() {
eprintln!(
"Warning: {} diamond inheritance pattern(s) detected",
report.diamonds.len()
);
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_inheritance_format() {
assert_eq!(
parse_inheritance_format("json").unwrap(),
InheritanceFormat::Json
);
assert_eq!(
parse_inheritance_format("text").unwrap(),
InheritanceFormat::Text
);
assert_eq!(
parse_inheritance_format("dot").unwrap(),
InheritanceFormat::Dot
);
assert_eq!(
parse_inheritance_format("graphviz").unwrap(),
InheritanceFormat::Dot
);
assert_eq!(
parse_inheritance_format("DOT").unwrap(),
InheritanceFormat::Dot
);
assert!(parse_inheritance_format("invalid").is_err());
}
}