rust_code_analysis/
spaces.rs

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/// The list of supported space kinds.
27#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize)]
28#[serde(rename_all = "lowercase")]
29pub enum SpaceKind {
30    /// An unknown space
31    #[default]
32    Unknown,
33    /// A function space
34    Function,
35    /// A class space
36    Class,
37    /// A struct space
38    Struct,
39    /// A `Rust` trait space
40    Trait,
41    /// A `Rust` implementation space
42    Impl,
43    /// A general space
44    Unit,
45    /// A `C/C++` namespace
46    Namespace,
47    /// An interface
48    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/// All metrics data.
69#[derive(Default, Debug, Clone, Serialize)]
70pub struct CodeMetrics {
71    /// `NArgs` data
72    pub nargs: nargs::Stats,
73    /// `NExits` data
74    pub nexits: exit::Stats,
75    pub cognitive: cognitive::Stats,
76    /// `Cyclomatic` data
77    pub cyclomatic: cyclomatic::Stats,
78    /// `Halstead` data
79    pub halstead: halstead::Stats,
80    /// `Loc` data
81    pub loc: loc::Stats,
82    /// `Nom` data
83    pub nom: nom::Stats,
84    /// `Mi` data
85    pub mi: mi::Stats,
86    /// `Abc` data
87    pub abc: abc::Stats,
88    /// `Wmc` data
89    #[serde(skip_serializing_if = "wmc::Stats::is_disabled")]
90    pub wmc: wmc::Stats,
91    /// `Npm` data
92    #[serde(skip_serializing_if = "npm::Stats::is_disabled")]
93    pub npm: npm::Stats,
94    /// `Npa` data
95    #[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/// Function space data.
130#[derive(Debug, Clone, Serialize)]
131pub struct FuncSpace {
132    /// The name of a function space
133    ///
134    /// If `None`, an error is occurred in parsing
135    /// the name of a function space
136    pub name: Option<String>,
137    /// The first line of a function space
138    pub start_line: usize,
139    /// The last line of a function space
140    pub end_line: usize,
141    /// The space kind
142    pub kind: SpaceKind,
143    /// All subspaces contained in a function space
144    pub spaces: Vec<FuncSpace>,
145    /// All metrics of a function space
146    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    // Cognitive average
204    state.space.metrics.cognitive.finalize(nom_total);
205    // Nexit average
206    state.space.metrics.nexits.finalize(nom_total);
207    // Nargs average
208    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            // Merge function spaces
257            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
269/// Returns all function spaces data of a code. This function needs a parser to
270/// be created a priori in order to work.
271///
272/// # Examples
273///
274/// ```
275/// use std::path::Path;
276///
277/// use rust_code_analysis::{CppParser, metrics, ParserTrait};
278///
279/// let source_code = "int a = 42;";
280///
281/// // The path to a dummy file used to contain the source code
282/// let path = Path::new("foo.c");
283/// let source_as_vec = source_code.as_bytes().to_vec();
284///
285/// // The parser of the code, in this case a CPP parser
286/// let parser = CppParser::new(source_as_vec, &path, None);
287///
288/// // Gets all function spaces data of the code contained in foo.c
289/// metrics(&parser, &path).unwrap();
290/// ```
291pub 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    // Initialize nesting_map used for storing nesting information for cognitive
300    // Three type of nesting info: conditionals, functions and lambdas
301    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
364/// Configuration options for computing
365/// the metrics of a code.
366pub struct MetricsCfg {
367    /// Path to the file containing the code
368    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}