Skip to main content

rust_code_analysis_code_split/
spaces.rs

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