rust_code_analysis_code_split/
spaces.rs1use 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::abc::{self, Abc};
11use crate::cognitive::{self, Cognitive};
12use crate::cyclomatic::{self, Cyclomatic};
13use crate::exit::{self, Exit};
14use crate::getter::Getter;
15use crate::halstead::{self, Halstead, HalsteadMaps};
16use crate::loc::{self, Loc};
17use crate::mi::{self, Mi};
18use crate::nargs::{self, NArgs};
19use crate::nom::{self, Nom};
20use crate::npa::{self, Npa};
21use crate::npm::{self, Npm};
22use crate::wmc::{self, Wmc};
23
24use crate::dump_metrics::*;
25use crate::traits::*;
26
27#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize)]
29#[serde(rename_all = "lowercase")]
30pub enum SpaceKind {
31 #[default]
33 Unknown,
34 Function,
36 Class,
38 Struct,
40 Trait,
42 Impl,
44 Unit,
46 Namespace,
48 Interface,
50}
51
52impl fmt::Display for SpaceKind {
53 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
54 let s = match self {
55 SpaceKind::Unknown => "unknown",
56 SpaceKind::Function => "function",
57 SpaceKind::Class => "class",
58 SpaceKind::Struct => "struct",
59 SpaceKind::Trait => "trait",
60 SpaceKind::Impl => "impl",
61 SpaceKind::Unit => "unit",
62 SpaceKind::Namespace => "namespace",
63 SpaceKind::Interface => "interface",
64 };
65 write!(f, "{s}")
66 }
67}
68
69#[derive(Default, Debug, Clone, Serialize)]
71pub struct CodeMetrics {
72 pub nargs: nargs::Stats,
74 pub nexits: exit::Stats,
76 pub cognitive: cognitive::Stats,
77 pub cyclomatic: cyclomatic::Stats,
79 pub halstead: halstead::Stats,
81 pub loc: loc::Stats,
83 pub nom: nom::Stats,
85 pub mi: mi::Stats,
87 pub abc: abc::Stats,
89 #[serde(skip_serializing_if = "wmc::Stats::is_disabled")]
91 pub wmc: wmc::Stats,
92 #[serde(skip_serializing_if = "npm::Stats::is_disabled")]
94 pub npm: npm::Stats,
95 #[serde(skip_serializing_if = "npa::Stats::is_disabled")]
97 pub npa: npa::Stats,
98}
99
100impl fmt::Display for CodeMetrics {
101 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
102 writeln!(f, "{}", self.nargs)?;
103 writeln!(f, "{}", self.nexits)?;
104 writeln!(f, "{}", self.cognitive)?;
105 writeln!(f, "{}", self.cyclomatic)?;
106 writeln!(f, "{}", self.halstead)?;
107 writeln!(f, "{}", self.loc)?;
108 writeln!(f, "{}", self.nom)?;
109 write!(f, "{}", self.mi)
110 }
111}
112
113impl CodeMetrics {
114 pub fn merge(&mut self, other: &CodeMetrics) {
115 self.cognitive.merge(&other.cognitive);
116 self.cyclomatic.merge(&other.cyclomatic);
117 self.halstead.merge(&other.halstead);
118 self.loc.merge(&other.loc);
119 self.nom.merge(&other.nom);
120 self.mi.merge(&other.mi);
121 self.nargs.merge(&other.nargs);
122 self.nexits.merge(&other.nexits);
123 self.abc.merge(&other.abc);
124 self.wmc.merge(&other.wmc);
125 self.npm.merge(&other.npm);
126 self.npa.merge(&other.npa);
127 }
128}
129
130#[derive(Debug, Clone, Serialize)]
132pub struct FuncSpace {
133 pub name: Option<String>,
138 pub start_line: usize,
140 pub end_line: usize,
142 pub kind: SpaceKind,
144 pub spaces: Vec<FuncSpace>,
146 pub metrics: CodeMetrics,
148}
149
150impl FuncSpace {
151 fn new<T: Getter>(node: &Node, code: &[u8], kind: SpaceKind) -> Self {
152 let (start_position, end_position) = match kind {
153 SpaceKind::Unit => {
154 if node.child_count() == 0 {
155 (0, 0)
156 } else {
157 (node.start_row() + 1, node.end_row())
158 }
159 }
160 _ => (node.start_row() + 1, node.end_row() + 1),
161 };
162
163 Self {
164 name: T::get_func_space_name(node, code)
165 .map(|name| name.split_whitespace().collect::<Vec<_>>().join(" ")),
166 spaces: Vec::new(),
167 metrics: CodeMetrics::default(),
168 kind,
169 start_line: start_position,
170 end_line: end_position,
171 }
172 }
173}
174
175#[inline(always)]
176fn compute_halstead_mi_and_wmc<T: ParserTrait>(state: &mut State) {
177 state
178 .halstead_maps
179 .finalize(&mut state.space.metrics.halstead);
180 T::Mi::compute(
181 &state.space.metrics.loc,
182 &state.space.metrics.cyclomatic,
183 &state.space.metrics.halstead,
184 &mut state.space.metrics.mi,
185 );
186 T::Wmc::compute(
187 state.space.kind,
188 &state.space.metrics.cyclomatic,
189 &mut state.space.metrics.wmc,
190 );
191}
192
193#[inline(always)]
194fn compute_averages(state: &mut State) {
195 let nom_functions = state.space.metrics.nom.functions_sum() as usize;
196 let nom_closures = state.space.metrics.nom.closures_sum() as usize;
197 let nom_total = state.space.metrics.nom.total() as usize;
198 state.space.metrics.cognitive.finalize(nom_total);
200 state.space.metrics.nexits.finalize(nom_total);
202 state
204 .space
205 .metrics
206 .nargs
207 .finalize(nom_functions, nom_closures);
208}
209
210#[inline(always)]
211fn compute_minmax(state: &mut State) {
212 state.space.metrics.cyclomatic.compute_minmax();
213 state.space.metrics.nexits.compute_minmax();
214 state.space.metrics.cognitive.compute_minmax();
215 state.space.metrics.nargs.compute_minmax();
216 state.space.metrics.nom.compute_minmax();
217 state.space.metrics.loc.compute_minmax();
218 state.space.metrics.abc.compute_minmax();
219}
220
221#[inline(always)]
222fn compute_sum(state: &mut State) {
223 state.space.metrics.wmc.compute_sum();
224 state.space.metrics.npm.compute_sum();
225 state.space.metrics.npa.compute_sum();
226}
227
228fn finalize<T: ParserTrait>(state_stack: &mut Vec<State>, diff_level: usize) {
229 if state_stack.is_empty() {
230 return;
231 }
232 for _ in 0..diff_level {
233 if state_stack.len() == 1 {
234 let last_state = state_stack.last_mut().unwrap();
235 compute_minmax(last_state);
236 compute_sum(last_state);
237 compute_halstead_mi_and_wmc::<T>(last_state);
238 compute_averages(last_state);
239 break;
240 } else {
241 let mut state = state_stack.pop().unwrap();
242 compute_minmax(&mut state);
243 compute_sum(&mut state);
244 compute_halstead_mi_and_wmc::<T>(&mut state);
245 compute_averages(&mut state);
246
247 let last_state = state_stack.last_mut().unwrap();
248 last_state.halstead_maps.merge(&state.halstead_maps);
249 compute_halstead_mi_and_wmc::<T>(last_state);
250
251 last_state.space.metrics.merge(&state.space.metrics);
253 last_state.space.spaces.push(state.space);
254 }
255 }
256}
257
258#[derive(Debug, Clone)]
259struct State<'a> {
260 space: FuncSpace,
261 halstead_maps: HalsteadMaps<'a>,
262}
263
264pub fn metrics<'a, T: ParserTrait>(parser: &'a T, path: &'a Path) -> Option<FuncSpace> {
287 let code = parser.get_code();
288 let node = parser.get_root();
289 let mut cursor = node.cursor();
290 let mut stack = Vec::new();
291 let mut children = Vec::new();
292 let mut state_stack: Vec<State> = Vec::new();
293 let mut last_level = 0;
294 let mut nesting_map = HashMap::<usize, (usize, usize, usize)>::default();
297 nesting_map.insert(node.id(), (0, 0, 0));
298 stack.push((node, 0));
299
300 while let Some((node, level)) = stack.pop() {
301 if level < last_level {
302 finalize::<T>(&mut state_stack, last_level - level);
303 last_level = level;
304 }
305
306 let kind = T::Getter::get_space_kind(&node);
307
308 let func_space = T::Checker::is_func(&node) || T::Checker::is_func_space(&node);
309 let unit = kind == SpaceKind::Unit;
310
311 let new_level = if func_space {
312 let state = State {
313 space: FuncSpace::new::<T::Getter>(&node, code, kind),
314 halstead_maps: HalsteadMaps::new(),
315 };
316 state_stack.push(state);
317 last_level = level + 1;
318 last_level
319 } else {
320 level
321 };
322
323 if let Some(state) = state_stack.last_mut() {
324 let last = &mut state.space;
325 T::Cognitive::compute(&node, &mut last.metrics.cognitive, &mut nesting_map);
326 T::Cyclomatic::compute(&node, &mut last.metrics.cyclomatic);
327 T::Halstead::compute(&node, code, &mut state.halstead_maps);
328 T::Loc::compute(&node, &mut last.metrics.loc, func_space, unit);
329 T::Nom::compute(&node, &mut last.metrics.nom);
330 T::NArgs::compute(&node, &mut last.metrics.nargs);
331 T::Exit::compute(&node, &mut last.metrics.nexits);
332 T::Abc::compute(&node, &mut last.metrics.abc);
333 T::Npm::compute(&node, &mut last.metrics.npm);
334 T::Npa::compute(&node, &mut last.metrics.npa);
335 }
336
337 cursor.reset(&node);
338 if cursor.goto_first_child() {
339 loop {
340 children.push((cursor.node(), new_level));
341 if !cursor.goto_next_sibling() {
342 break;
343 }
344 }
345 for child in children.drain(..).rev() {
346 stack.push(child);
347 }
348 }
349 }
350
351 finalize::<T>(&mut state_stack, usize::MAX);
352
353 state_stack.pop().map(|mut state| {
354 state.space.name = path.to_str().map(|name| name.to_string());
355 state.space
356 })
357}
358
359#[derive(Debug)]
362pub struct MetricsCfg {
363 pub path: PathBuf,
365}
366
367pub struct Metrics {
368 _guard: (),
369}
370
371impl Callback for Metrics {
372 type Res = std::io::Result<()>;
373 type Cfg = MetricsCfg;
374
375 fn call<T: ParserTrait>(cfg: Self::Cfg, parser: &T) -> Self::Res {
376 match metrics(parser, &cfg.path) {
377 Some(space) => dump_root(&space),
378 _ => Ok(()),
379 }
380 }
381}
382
383#[cfg(test)]
384mod tests {
385 use crate::{CppParser, check_func_space};
386
387 #[test]
388 fn c_scope_resolution_operator() {
389 check_func_space::<CppParser, _>(
390 "void Foo::bar(){
391 return;
392 }",
393 "foo.c",
394 |func_space| {
395 insta::assert_json_snapshot!(
396 func_space.spaces[0].name,
397 @r###""Foo::bar""###
398 );
399 },
400 );
401 }
402}