cha_core/model.rs
1/// Where a referenced type is defined, from the perspective of the file that
2/// uses it. Used by abstraction-boundary analyses to distinguish "own domain"
3/// types from "pulled in from a library" types.
4#[derive(Debug, Clone, PartialEq, Eq, Default, serde::Serialize, serde::Deserialize)]
5#[serde(rename_all = "snake_case", tag = "kind", content = "module")]
6pub enum TypeOrigin {
7 /// Declared inside the project (resolved via project-wide type registry
8 /// or an import pointing at a project-local path).
9 Local,
10 /// Imported from an external module / crate / package. Carries the module
11 /// name if known (Rust crate path root, Go module path, npm package name,
12 /// C header filename without extension). May be empty if only structure
13 /// says "external" (e.g. `#include <...>` without the header name).
14 External(String),
15 /// Built-in primitive / standard library scalar (int, bool, &str, char…).
16 Primitive,
17 /// Could not be resolved. Detection treats this as potentially external
18 /// but with lower confidence.
19 #[default]
20 Unknown,
21}
22
23/// A function parameter's (or return value's) type, with resolved origin.
24/// Produced by parsers after combining AST type text with the file's imports.
25#[derive(Debug, Clone, PartialEq, Eq, Default, serde::Serialize, serde::Deserialize)]
26pub struct TypeRef {
27 /// Innermost identifier after stripping references, generics, containers.
28 /// e.g. `&mut Vec<tree_sitter::Node>` → `"Node"`.
29 pub name: String,
30 /// Original source text as written, for messages and debugging.
31 /// e.g. `"&mut Vec<tree_sitter::Node>"`.
32 pub raw: String,
33 /// Where the type is declared.
34 pub origin: TypeOrigin,
35}
36
37/// Extracted function info from AST.
38#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
39pub struct FunctionInfo {
40 pub name: String,
41 pub start_line: usize,
42 pub end_line: usize,
43 /// 0-based column of the function name identifier.
44 pub name_col: usize,
45 /// 0-based end column of the function name identifier.
46 pub name_end_col: usize,
47 pub line_count: usize,
48 /// Cyclomatic complexity (1 + number of branch points).
49 pub complexity: usize,
50 /// Hash of the function body AST structure for duplicate detection.
51 pub body_hash: Option<u64>,
52 /// Whether this function is exported (pub/export).
53 pub is_exported: bool,
54 /// Number of parameters.
55 pub parameter_count: usize,
56 /// Names of external identifiers referenced in the body (for Feature Envy).
57 pub external_refs: Vec<String>,
58 /// Max method chain depth in the body (for Message Chains).
59 pub chain_depth: usize,
60 /// Number of switch/match arms (for Switch Statements).
61 pub switch_arms: usize,
62 /// Whether this function only delegates to another object's method (for Middle Man).
63 pub is_delegating: bool,
64 /// Parameter types **in declaration order**, each resolved to a TypeRef.
65 /// Preserves position (first param = index 0) so positional analyses work.
66 pub parameter_types: Vec<TypeRef>,
67 /// Number of comment lines in the function body.
68 pub comment_lines: usize,
69 /// Field names referenced in this function body (for Temporary Field).
70 pub referenced_fields: Vec<String>,
71 /// Field names checked for null/None in this function (for Null Object pattern).
72 pub null_check_fields: Vec<String>,
73 /// The field/variable name being dispatched on in switch/match (for Strategy/State).
74 pub switch_dispatch_target: Option<String>,
75 /// Number of optional parameters (for Builder pattern).
76 pub optional_param_count: usize,
77 /// Names of functions/methods called in this function body (for call graph).
78 pub called_functions: Vec<String>,
79 /// Cognitive complexity score [SonarSource 2017] — nesting-aware understandability metric.
80 pub cognitive_complexity: usize,
81 /// Declared return type (None if not annotated or inferred), resolved the
82 /// same way as parameter types. Drives return_type_leak detection.
83 pub return_type: Option<TypeRef>,
84}
85
86/// Extracted class/struct info from AST.
87#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
88pub struct ClassInfo {
89 pub name: String,
90 pub start_line: usize,
91 pub end_line: usize,
92 /// 0-based column of the class/struct name identifier.
93 pub name_col: usize,
94 /// 0-based end column of the class/struct name identifier.
95 pub name_end_col: usize,
96 pub method_count: usize,
97 pub line_count: usize,
98 /// Whether this class is exported.
99 pub is_exported: bool,
100 /// Number of methods that only delegate to another object.
101 pub delegating_method_count: usize,
102 /// Number of fields/properties.
103 pub field_count: usize,
104 /// Field names declared in this class.
105 pub field_names: Vec<String>,
106 /// Field types (parallel to field_names).
107 pub field_types: Vec<String>,
108 /// Whether the class has non-accessor methods (business logic).
109 pub has_behavior: bool,
110 /// Whether this is an interface or abstract class.
111 pub is_interface: bool,
112 /// Parent class/trait name (for Refused Bequest).
113 pub parent_name: Option<String>,
114 /// Number of overridden methods (for Refused Bequest).
115 pub override_count: usize,
116 /// Number of self-method calls in the longest method (for Template Method).
117 pub self_call_count: usize,
118 /// Whether the class has a listener/callback collection field.
119 pub has_listener_field: bool,
120 /// Whether the class has a notify/emit method.
121 pub has_notify_method: bool,
122}
123
124/// Extracted import info.
125#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
126pub struct ImportInfo {
127 pub source: String,
128 pub line: usize,
129 /// 0-based column of the import statement.
130 pub col: usize,
131 /// True for module declarations (e.g. Rust `mod foo;`).
132 pub is_module_decl: bool,
133}
134
135/// A comment extracted from source code by the language parser.
136#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
137pub struct CommentInfo {
138 pub text: String,
139 pub line: usize,
140}
141
142/// Unified source model produced by parsing.
143#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
144pub struct SourceModel {
145 pub language: String,
146 pub total_lines: usize,
147 pub functions: Vec<FunctionInfo>,
148 pub classes: Vec<ClassInfo>,
149 pub imports: Vec<ImportInfo>,
150 pub comments: Vec<CommentInfo>,
151 /// Type aliases: (alias, original). e.g. typedef, using, type =
152 pub type_aliases: Vec<(String, String)>,
153}
154
155/// Compact structural summary of a file — symbol-level view without the
156/// per-function-body detail that analyze plugins need. Serves `cha deps`,
157/// future LSP workspace-symbols, and anywhere a reader needs "what
158/// classes/functions live here and how are they related" without caring
159/// about complexity metrics or TypeRef origin resolution.
160///
161/// One-way derivable from `SourceModel`; cached separately so light
162/// consumers don't pay `SourceModel`'s deserialise cost.
163#[derive(Debug, Default, Clone, serde::Serialize, serde::Deserialize)]
164pub struct SymbolIndex {
165 pub language: String,
166 pub total_lines: usize,
167 pub imports: Vec<ImportInfo>,
168 pub classes: Vec<ClassSymbol>,
169 pub functions: Vec<FunctionSymbol>,
170 /// `(alias, original)`. Mirrors `SourceModel.type_aliases`.
171 pub type_aliases: Vec<(String, String)>,
172}
173
174/// Symbol-level view of a class — everything deps/LSP/hotspot need to
175/// reason about a class without parsing method bodies. Fields intentionally
176/// track the subset of `ClassInfo` that survives cross-file consumption.
177#[derive(Debug, Default, Clone, serde::Serialize, serde::Deserialize)]
178pub struct ClassSymbol {
179 pub name: String,
180 pub parent_name: Option<String>,
181 pub is_interface: bool,
182 pub is_exported: bool,
183 pub method_count: usize,
184 pub has_behavior: bool,
185 pub field_names: Vec<String>,
186 pub field_types: Vec<String>,
187 pub start_line: usize,
188 pub end_line: usize,
189 pub name_col: usize,
190 pub name_end_col: usize,
191}
192
193/// Symbol-level view of a function — name + signature + call-graph input.
194/// Omits body_hash, complexity, cognitive, external_refs, chain_depth,
195/// parameter_types (TypeRef), return_type — those live in `FunctionInfo`
196/// for analyze plugins.
197#[derive(Debug, Default, Clone, serde::Serialize, serde::Deserialize)]
198pub struct FunctionSymbol {
199 pub name: String,
200 pub is_exported: bool,
201 pub parameter_count: usize,
202 pub called_functions: Vec<String>,
203 pub start_line: usize,
204 pub end_line: usize,
205 pub name_col: usize,
206 pub name_end_col: usize,
207 /// Bare type names (no module prefix, no origin info) for each
208 /// parameter in declaration order. Sufficient for signature-based
209 /// clustering (C OOP attribution, call-graph refinement) without
210 /// pulling in TypeRef's origin resolution, which is analyze-only.
211 pub parameter_type_names: Vec<String>,
212 /// Bare return type name (same conventions as parameter_type_names);
213 /// `None` if the function has no declared return type.
214 pub return_type_name: Option<String>,
215}
216
217impl SymbolIndex {
218 /// Project a `SourceModel` onto the symbol-level view. Cheap —
219 /// clones strings but no heavy structures.
220 pub fn from_source_model(m: &SourceModel) -> Self {
221 Self {
222 language: m.language.clone(),
223 total_lines: m.total_lines,
224 imports: m.imports.clone(),
225 classes: m.classes.iter().map(ClassSymbol::from_class_info).collect(),
226 functions: m
227 .functions
228 .iter()
229 .map(FunctionSymbol::from_function_info)
230 .collect(),
231 type_aliases: m.type_aliases.clone(),
232 }
233 }
234}
235
236impl ClassSymbol {
237 pub fn from_class_info(c: &ClassInfo) -> Self {
238 Self {
239 name: c.name.clone(),
240 parent_name: c.parent_name.clone(),
241 is_interface: c.is_interface,
242 is_exported: c.is_exported,
243 method_count: c.method_count,
244 has_behavior: c.has_behavior,
245 field_names: c.field_names.clone(),
246 field_types: c.field_types.clone(),
247 start_line: c.start_line,
248 end_line: c.end_line,
249 name_col: c.name_col,
250 name_end_col: c.name_end_col,
251 }
252 }
253}
254
255impl FunctionSymbol {
256 pub fn from_function_info(f: &FunctionInfo) -> Self {
257 Self {
258 name: f.name.clone(),
259 is_exported: f.is_exported,
260 parameter_count: f.parameter_count,
261 called_functions: f.called_functions.clone(),
262 start_line: f.start_line,
263 end_line: f.end_line,
264 name_col: f.name_col,
265 name_end_col: f.name_end_col,
266 parameter_type_names: f.parameter_types.iter().map(|t| t.raw.clone()).collect(),
267 return_type_name: f.return_type.as_ref().map(|t| t.raw.clone()),
268 }
269 }
270}