1use std::collections::HashMap;
2
3use serde::Serialize;
4use std::fmt;
5use std::path::{Path, PathBuf};
6
7use crate::checker::Checker;
8use crate::node::Node;
9
10use crate::cognitive::{self, Cognitive};
11use crate::cyclomatic::{self, Cyclomatic};
12use crate::exit::{self, Exit};
13use crate::getter::Getter;
14use crate::halstead::{self, Halstead, HalsteadMaps};
15use crate::loc::{self, Loc};
16use crate::mi::{self, Mi};
17use crate::nargs::{self, NArgs};
18use crate::nom::{self, Nom};
19
20use crate::dump_metrics::*;
21use crate::traits::*;
22
23#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize)]
25#[serde(rename_all = "lowercase")]
26pub enum SpaceKind {
27 #[default]
29 Unknown,
30 Function,
32 Class,
34 Struct,
36 Trait,
38 Impl,
40 Unit,
42 Namespace,
44 Interface,
46}
47
48impl fmt::Display for SpaceKind {
49 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
50 let s = match self {
51 SpaceKind::Unknown => "unknown",
52 SpaceKind::Function => "function",
53 SpaceKind::Class => "class",
54 SpaceKind::Struct => "struct",
55 SpaceKind::Trait => "trait",
56 SpaceKind::Impl => "impl",
57 SpaceKind::Unit => "unit",
58 SpaceKind::Namespace => "namespace",
59 SpaceKind::Interface => "interface",
60 };
61 write!(f, "{s}")
62 }
63}
64
65#[derive(Default, Debug, Clone, Serialize)]
67pub struct CodeMetrics {
68 pub nargs: nargs::Stats,
70 pub nexits: exit::Stats,
72 pub cognitive: cognitive::Stats,
73 pub cyclomatic: cyclomatic::Stats,
75 pub halstead: halstead::Stats,
77 pub loc: loc::Stats,
79 pub nom: nom::Stats,
81 pub mi: mi::Stats,
83}
84
85impl fmt::Display for CodeMetrics {
86 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
87 writeln!(f, "{}", self.nargs)?;
88 writeln!(f, "{}", self.nexits)?;
89 writeln!(f, "{}", self.cognitive)?;
90 writeln!(f, "{}", self.cyclomatic)?;
91 writeln!(f, "{}", self.halstead)?;
92 writeln!(f, "{}", self.loc)?;
93 writeln!(f, "{}", self.nom)?;
94 write!(f, "{}", self.mi)
95 }
96}
97
98impl CodeMetrics {
99 pub fn merge(&mut self, other: &CodeMetrics) {
100 self.cognitive.merge(&other.cognitive);
101 self.cyclomatic.merge(&other.cyclomatic);
102 self.halstead.merge(&other.halstead);
103 self.loc.merge(&other.loc);
104 self.nom.merge(&other.nom);
105 self.mi.merge(&other.mi);
106 self.nargs.merge(&other.nargs);
107 self.nexits.merge(&other.nexits);
108 }
109}
110
111#[derive(Debug, Clone, Serialize)]
113pub struct FuncSpace {
114 pub name: Option<String>,
119 pub start_line: usize,
121 pub end_line: usize,
123 pub kind: SpaceKind,
125 pub spaces: Vec<FuncSpace>,
127 pub metrics: CodeMetrics,
129}
130
131impl FuncSpace {
132 fn new<T: Getter>(node: &Node, code: &[u8], kind: SpaceKind) -> Self {
133 let (start_position, end_position) = match kind {
134 SpaceKind::Unit => {
135 if node.child_count() == 0 {
136 (0, 0)
137 } else {
138 (node.start_row() + 1, node.end_row())
139 }
140 }
141 _ => (node.start_row() + 1, node.end_row() + 1),
142 };
143
144 Self {
145 name: T::get_func_space_name(node, code)
146 .map(|name| name.split_whitespace().collect::<Vec<_>>().join(" ")),
147 spaces: Vec::new(),
148 metrics: CodeMetrics::default(),
149 kind,
150 start_line: start_position,
151 end_line: end_position,
152 }
153 }
154}
155
156#[inline(always)]
157fn compute_halstead_and_mi<T: ParserTrait>(state: &mut State) {
158 state
159 .halstead_maps
160 .finalize(&mut state.space.metrics.halstead);
161 T::Mi::compute(
162 &state.space.metrics.loc,
163 &state.space.metrics.cyclomatic,
164 &state.space.metrics.halstead,
165 &mut state.space.metrics.mi,
166 );
167}
168
169#[inline(always)]
170fn compute_averages(state: &mut State) {
171 let nom_functions = state.space.metrics.nom.functions_sum() as usize;
172 let nom_closures = state.space.metrics.nom.closures_sum() as usize;
173 let nom_total = state.space.metrics.nom.total() as usize;
174 state.space.metrics.cognitive.finalize(nom_total);
176 state.space.metrics.nexits.finalize(nom_total);
178 state
180 .space
181 .metrics
182 .nargs
183 .finalize(nom_functions, nom_closures);
184}
185
186#[inline(always)]
187fn compute_minmax(state: &mut State) {
188 state.space.metrics.cyclomatic.compute_minmax();
189 state.space.metrics.nexits.compute_minmax();
190 state.space.metrics.cognitive.compute_minmax();
191 state.space.metrics.nargs.compute_minmax();
192 state.space.metrics.nom.compute_minmax();
193 state.space.metrics.loc.compute_minmax();
194}
195
196fn finalize<T: ParserTrait>(state_stack: &mut Vec<State>, diff_level: usize) {
197 if state_stack.is_empty() {
198 return;
199 }
200 for _ in 0..diff_level {
201 if state_stack.len() == 1 {
202 let last_state = state_stack.last_mut().unwrap();
203 compute_minmax(last_state);
204 compute_halstead_and_mi::<T>(last_state);
205 compute_averages(last_state);
206 break;
207 } else {
208 let mut state = state_stack.pop().unwrap();
209 compute_minmax(&mut state);
210 compute_halstead_and_mi::<T>(&mut state);
211 compute_averages(&mut state);
212
213 let last_state = state_stack.last_mut().unwrap();
214 last_state.halstead_maps.merge(&state.halstead_maps);
215 compute_halstead_and_mi::<T>(last_state);
216
217 last_state.space.metrics.merge(&state.space.metrics);
219 last_state.space.spaces.push(state.space);
220 }
221 }
222}
223
224#[derive(Debug, Clone)]
225struct State<'a> {
226 space: FuncSpace,
227 halstead_maps: HalsteadMaps<'a>,
228}
229
230pub fn metrics<'a, T: ParserTrait>(parser: &'a T, path: &'a Path) -> Option<FuncSpace> {
253 let code = parser.get_code();
254 let node = parser.get_root();
255 let mut cursor = node.cursor();
256 let mut stack = Vec::new();
257 let mut children = Vec::new();
258 let mut state_stack: Vec<State> = Vec::new();
259 let mut last_level = 0;
260 let mut nesting_map = HashMap::<usize, (usize, usize, usize)>::default();
263 nesting_map.insert(node.id(), (0, 0, 0));
264 stack.push((node, 0));
265
266 while let Some((node, level)) = stack.pop() {
267 if level < last_level {
268 finalize::<T>(&mut state_stack, last_level - level);
269 last_level = level;
270 }
271
272 let kind = T::Getter::get_space_kind(&node);
273
274 let func_space = T::Checker::is_func(&node) || T::Checker::is_func_space(&node);
275 let unit = kind == SpaceKind::Unit;
276
277 let new_level = if func_space {
278 let state = State {
279 space: FuncSpace::new::<T::Getter>(&node, code, kind),
280 halstead_maps: HalsteadMaps::new(),
281 };
282 state_stack.push(state);
283 last_level = level + 1;
284 last_level
285 } else {
286 level
287 };
288
289 if let Some(state) = state_stack.last_mut() {
290 let last = &mut state.space;
291 T::Cognitive::compute(&node, &mut last.metrics.cognitive, &mut nesting_map);
292 T::Cyclomatic::compute(&node, &mut last.metrics.cyclomatic);
293 T::Halstead::compute(&node, code, &mut state.halstead_maps);
294 T::Loc::compute(&node, &mut last.metrics.loc, func_space, unit);
295 T::Nom::compute(&node, &mut last.metrics.nom);
296 T::NArgs::compute(&node, &mut last.metrics.nargs);
297 T::Exit::compute(&node, &mut last.metrics.nexits);
298 }
299
300 cursor.reset(&node);
301 if cursor.goto_first_child() {
302 loop {
303 children.push((cursor.node(), new_level));
304 if !cursor.goto_next_sibling() {
305 break;
306 }
307 }
308 for child in children.drain(..).rev() {
309 stack.push(child);
310 }
311 }
312 }
313
314 finalize::<T>(&mut state_stack, usize::MAX);
315
316 state_stack.pop().map(|mut state| {
317 state.space.name = path.to_str().map(|name| name.to_string());
318 state.space
319 })
320}
321
322#[derive(Debug)]
325pub struct MetricsCfg {
326 pub path: PathBuf,
328}
329
330pub struct Metrics {
331 _guard: (),
332}
333
334impl Callback for Metrics {
335 type Res = std::io::Result<()>;
336 type Cfg = MetricsCfg;
337
338 fn call<T: ParserTrait>(cfg: Self::Cfg, parser: &T) -> Self::Res {
339 match metrics(parser, &cfg.path) {
340 Some(space) => dump_root(&space),
341 _ => Ok(()),
342 }
343 }
344}
345
346#[cfg(test)]
347mod tests {
348 use crate::{CppParser, check_func_space};
349
350 #[test]
351 fn c_scope_resolution_operator() {
352 check_func_space::<CppParser, _>(
353 "void Foo::bar(){
354 return;
355 }",
356 "foo.c",
357 |func_space| {
358 insta::assert_json_snapshot!(
359 func_space.spaces[0].name,
360 @r###""Foo::bar""###
361 );
362 },
363 );
364 }
365}