1use fxhash::FxHashMap;
2use serde::Serialize;
3use std::fmt;
4use std::path::{Path, PathBuf};
5
6use crate::checker::Checker;
7use crate::node::Node;
8
9use crate::abc::{self, Abc};
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};
19use crate::npa::{self, Npa};
20use crate::npm::{self, Npm};
21use crate::wmc::{self, Wmc};
22
23use crate::dump_metrics::*;
24use crate::traits::*;
25
26#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize)]
28#[serde(rename_all = "lowercase")]
29pub enum SpaceKind {
30 #[default]
32 Unknown,
33 Function,
35 Class,
37 Struct,
39 Trait,
41 Impl,
43 Unit,
45 Namespace,
47 Interface,
49}
50
51impl fmt::Display for SpaceKind {
52 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
53 let s = match self {
54 SpaceKind::Unknown => "unknown",
55 SpaceKind::Function => "function",
56 SpaceKind::Class => "class",
57 SpaceKind::Struct => "struct",
58 SpaceKind::Trait => "trait",
59 SpaceKind::Impl => "impl",
60 SpaceKind::Unit => "unit",
61 SpaceKind::Namespace => "namespace",
62 SpaceKind::Interface => "interface",
63 };
64 write!(f, "{}", s)
65 }
66}
67
68#[derive(Default, Debug, Clone, Serialize)]
70pub struct CodeMetrics {
71 pub nargs: nargs::Stats,
73 pub nexits: exit::Stats,
75 pub cognitive: cognitive::Stats,
76 pub cyclomatic: cyclomatic::Stats,
78 pub halstead: halstead::Stats,
80 pub loc: loc::Stats,
82 pub nom: nom::Stats,
84 pub mi: mi::Stats,
86 pub abc: abc::Stats,
88 #[serde(skip_serializing_if = "wmc::Stats::is_disabled")]
90 pub wmc: wmc::Stats,
91 #[serde(skip_serializing_if = "npm::Stats::is_disabled")]
93 pub npm: npm::Stats,
94 #[serde(skip_serializing_if = "npa::Stats::is_disabled")]
96 pub npa: npa::Stats,
97}
98
99impl fmt::Display for CodeMetrics {
100 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
101 writeln!(f, "{}", self.nargs)?;
102 writeln!(f, "{}", self.nexits)?;
103 writeln!(f, "{}", self.cognitive)?;
104 writeln!(f, "{}", self.cyclomatic)?;
105 writeln!(f, "{}", self.halstead)?;
106 writeln!(f, "{}", self.loc)?;
107 writeln!(f, "{}", self.nom)?;
108 write!(f, "{}", self.mi)
109 }
110}
111
112impl CodeMetrics {
113 pub fn merge(&mut self, other: &CodeMetrics) {
114 self.cognitive.merge(&other.cognitive);
115 self.cyclomatic.merge(&other.cyclomatic);
116 self.halstead.merge(&other.halstead);
117 self.loc.merge(&other.loc);
118 self.nom.merge(&other.nom);
119 self.mi.merge(&other.mi);
120 self.nargs.merge(&other.nargs);
121 self.nexits.merge(&other.nexits);
122 self.abc.merge(&other.abc);
123 self.wmc.merge(&other.wmc);
124 self.npm.merge(&other.npm);
125 self.npa.merge(&other.npa);
126 }
127}
128
129#[derive(Debug, Clone, Serialize)]
131pub struct FuncSpace {
132 pub name: Option<String>,
137 pub start_line: usize,
139 pub end_line: usize,
141 pub kind: SpaceKind,
143 pub spaces: Vec<FuncSpace>,
145 pub metrics: CodeMetrics,
147}
148
149impl FuncSpace {
150 fn new<T: Getter>(node: &Node, code: &[u8], kind: SpaceKind) -> Self {
151 let (start_position, end_position) = match kind {
152 SpaceKind::Unit => {
153 if node.object().child_count() == 0 {
154 (0, 0)
155 } else {
156 (
157 node.object().start_position().row + 1,
158 node.object().end_position().row,
159 )
160 }
161 }
162 _ => (
163 node.object().start_position().row + 1,
164 node.object().end_position().row + 1,
165 ),
166 };
167
168 Self {
169 name: T::get_func_space_name(node, code)
170 .map(|name| name.split_whitespace().collect::<Vec<_>>().join(" ")),
171 spaces: Vec::new(),
172 metrics: CodeMetrics::default(),
173 kind,
174 start_line: start_position,
175 end_line: end_position,
176 }
177 }
178}
179
180#[inline(always)]
181fn compute_halstead_mi_and_wmc<T: ParserTrait>(state: &mut State) {
182 state
183 .halstead_maps
184 .finalize(&mut state.space.metrics.halstead);
185 T::Mi::compute(
186 &state.space.metrics.loc,
187 &state.space.metrics.cyclomatic,
188 &state.space.metrics.halstead,
189 &mut state.space.metrics.mi,
190 );
191 T::Wmc::compute(
192 state.space.kind,
193 &state.space.metrics.cyclomatic,
194 &mut state.space.metrics.wmc,
195 );
196}
197
198#[inline(always)]
199fn compute_averages(state: &mut State) {
200 let nom_functions = state.space.metrics.nom.functions_sum() as usize;
201 let nom_closures = state.space.metrics.nom.closures_sum() as usize;
202 let nom_total = state.space.metrics.nom.total() as usize;
203 state.space.metrics.cognitive.finalize(nom_total);
205 state.space.metrics.nexits.finalize(nom_total);
207 state
209 .space
210 .metrics
211 .nargs
212 .finalize(nom_functions, nom_closures);
213}
214
215#[inline(always)]
216fn compute_minmax(state: &mut State) {
217 state.space.metrics.cyclomatic.compute_minmax();
218 state.space.metrics.nexits.compute_minmax();
219 state.space.metrics.cognitive.compute_minmax();
220 state.space.metrics.nargs.compute_minmax();
221 state.space.metrics.nom.compute_minmax();
222 state.space.metrics.loc.compute_minmax();
223 state.space.metrics.abc.compute_minmax();
224}
225
226#[inline(always)]
227fn compute_sum(state: &mut State) {
228 state.space.metrics.wmc.compute_sum();
229 state.space.metrics.npm.compute_sum();
230 state.space.metrics.npa.compute_sum();
231}
232
233fn finalize<T: ParserTrait>(state_stack: &mut Vec<State>, diff_level: usize) {
234 if state_stack.is_empty() {
235 return;
236 }
237 for _ in 0..diff_level {
238 if state_stack.len() == 1 {
239 let last_state = state_stack.last_mut().unwrap();
240 compute_minmax(last_state);
241 compute_sum(last_state);
242 compute_halstead_mi_and_wmc::<T>(last_state);
243 compute_averages(last_state);
244 break;
245 } else {
246 let mut state = state_stack.pop().unwrap();
247 compute_minmax(&mut state);
248 compute_sum(&mut state);
249 compute_halstead_mi_and_wmc::<T>(&mut state);
250 compute_averages(&mut state);
251
252 let last_state = state_stack.last_mut().unwrap();
253 last_state.halstead_maps.merge(&state.halstead_maps);
254 compute_halstead_mi_and_wmc::<T>(last_state);
255
256 last_state.space.metrics.merge(&state.space.metrics);
258 last_state.space.spaces.push(state.space);
259 }
260 }
261}
262
263#[derive(Debug, Clone)]
264struct State<'a> {
265 space: FuncSpace,
266 halstead_maps: HalsteadMaps<'a>,
267}
268
269pub fn metrics<'a, T: ParserTrait>(parser: &'a T, path: &'a Path) -> Option<FuncSpace> {
292 let code = parser.get_code();
293 let node = parser.get_root();
294 let mut cursor = node.object().walk();
295 let mut stack = Vec::new();
296 let mut children = Vec::new();
297 let mut state_stack: Vec<State> = Vec::new();
298 let mut last_level = 0;
299 let mut nesting_map = FxHashMap::<usize, (usize, usize, usize)>::default();
302 nesting_map.insert(node.object().id(), (0, 0, 0));
303 stack.push((node, 0));
304
305 while let Some((node, level)) = stack.pop() {
306 if level < last_level {
307 finalize::<T>(&mut state_stack, last_level - level);
308 last_level = level;
309 }
310
311 let kind = T::Getter::get_space_kind(&node);
312
313 let func_space = T::Checker::is_func(&node) || T::Checker::is_func_space(&node);
314 let unit = kind == SpaceKind::Unit;
315
316 let new_level = if func_space {
317 let state = State {
318 space: FuncSpace::new::<T::Getter>(&node, code, kind),
319 halstead_maps: HalsteadMaps::new(),
320 };
321 state_stack.push(state);
322 last_level = level + 1;
323 last_level
324 } else {
325 level
326 };
327
328 if let Some(state) = state_stack.last_mut() {
329 let last = &mut state.space;
330 T::Cognitive::compute(&node, &mut last.metrics.cognitive, &mut nesting_map);
331 T::Cyclomatic::compute(&node, &mut last.metrics.cyclomatic);
332 T::Halstead::compute(&node, code, &mut state.halstead_maps);
333 T::Loc::compute(&node, &mut last.metrics.loc, func_space, unit);
334 T::Nom::compute(&node, &mut last.metrics.nom);
335 T::NArgs::compute(&node, &mut last.metrics.nargs);
336 T::Exit::compute(&node, &mut last.metrics.nexits);
337 T::Abc::compute(&node, &mut last.metrics.abc);
338 T::Npm::compute(&node, &mut last.metrics.npm);
339 T::Npa::compute(&node, &mut last.metrics.npa);
340 }
341
342 cursor.reset(node.object());
343 if cursor.goto_first_child() {
344 loop {
345 children.push((Node::new(cursor.node()), new_level));
346 if !cursor.goto_next_sibling() {
347 break;
348 }
349 }
350 for child in children.drain(..).rev() {
351 stack.push(child);
352 }
353 }
354 }
355
356 finalize::<T>(&mut state_stack, std::usize::MAX);
357
358 state_stack.pop().map(|mut state| {
359 state.space.name = path.to_str().map(|name| name.to_string());
360 state.space
361 })
362}
363
364pub struct MetricsCfg {
367 pub path: PathBuf,
369}
370
371pub struct Metrics {
372 _guard: (),
373}
374
375impl Callback for Metrics {
376 type Res = std::io::Result<()>;
377 type Cfg = MetricsCfg;
378
379 fn call<T: ParserTrait>(cfg: Self::Cfg, parser: &T) -> Self::Res {
380 if let Some(space) = metrics(parser, &cfg.path) {
381 dump_root(&space)
382 } else {
383 Ok(())
384 }
385 }
386}