rust_code_analysis/metrics/
abc.rs

1use serde::ser::{SerializeStruct, Serializer};
2use serde::Serialize;
3use std::fmt;
4
5use crate::checker::Checker;
6use crate::node::Node;
7use crate::*;
8
9/// The `ABC` metric.
10///
11/// The `ABC` metric measures the size of a source code by counting
12/// the number of Assignments (`A`), Branches (`B`) and Conditions (`C`).
13/// The metric defines an ABC score as a vector of three elements (`<A,B,C>`).
14/// The ABC score can be represented by its individual components (`A`, `B` and `C`)
15/// or by the magnitude of the vector (`|<A,B,C>| = sqrt(A^2 + B^2 + C^2)`).
16///
17/// Official paper and definition:
18///
19/// Fitzpatrick, Jerry (1997). "Applying the ABC metric to C, C++ and Java". C++ Report.
20/// https://www.softwarerenovation.com/Articles.aspx
21#[derive(Debug, Clone)]
22pub struct Stats {
23    assignments: f64,
24    assignments_sum: f64,
25    assignments_min: f64,
26    assignments_max: f64,
27    branches: f64,
28    branches_sum: f64,
29    branches_min: f64,
30    branches_max: f64,
31    conditions: f64,
32    conditions_sum: f64,
33    conditions_min: f64,
34    conditions_max: f64,
35    space_count: usize,
36    declaration: Vec<DeclKind>,
37}
38
39#[derive(Debug, Clone)]
40pub enum DeclKind {
41    Var,
42    Const,
43}
44
45impl Default for Stats {
46    fn default() -> Self {
47        Self {
48            assignments: 0.,
49            assignments_sum: 0.,
50            assignments_min: f64::MAX,
51            assignments_max: 0.,
52            branches: 0.,
53            branches_sum: 0.,
54            branches_min: f64::MAX,
55            branches_max: 0.,
56            conditions: 0.,
57            conditions_sum: 0.,
58            conditions_min: f64::MAX,
59            conditions_max: 0.,
60            space_count: 1,
61            declaration: Vec::new(),
62        }
63    }
64}
65
66impl Serialize for Stats {
67    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
68    where
69        S: Serializer,
70    {
71        let mut st = serializer.serialize_struct("abc", 13)?;
72        st.serialize_field("assignments", &self.assignments_sum())?;
73        st.serialize_field("branches", &self.branches_sum())?;
74        st.serialize_field("conditions", &self.conditions_sum())?;
75        st.serialize_field("magnitude", &self.magnitude_sum())?;
76        st.serialize_field("assignments_average", &self.assignments_average())?;
77        st.serialize_field("branches_average", &self.branches_average())?;
78        st.serialize_field("conditions_average", &self.conditions_average())?;
79        st.serialize_field("assignments_min", &self.assignments_min())?;
80        st.serialize_field("assignments_max", &self.assignments_max())?;
81        st.serialize_field("branches_min", &self.branches_min())?;
82        st.serialize_field("branches_max", &self.branches_max())?;
83        st.serialize_field("conditions_min", &self.conditions_min())?;
84        st.serialize_field("conditions_max", &self.conditions_max())?;
85        st.end()
86    }
87}
88
89impl fmt::Display for Stats {
90    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
91        write!(
92            f,
93            "assignments: {}, branches: {}, conditions: {}, magnitude: {}, \
94            assignments_average: {}, branches_average: {}, conditions_average: {}, \
95            assignments_min: {}, assignments_max: {}, \
96            branches_min: {}, branches_max: {}, \
97            conditions_min: {}, conditions_max: {}",
98            self.assignments_sum(),
99            self.branches_sum(),
100            self.conditions_sum(),
101            self.magnitude_sum(),
102            self.assignments_average(),
103            self.branches_average(),
104            self.conditions_average(),
105            self.assignments_min(),
106            self.assignments_max(),
107            self.branches_min(),
108            self.branches_max(),
109            self.conditions_min(),
110            self.conditions_max()
111        )
112    }
113}
114
115impl Stats {
116    /// Merges a second `Abc` metric into the first one.
117    pub fn merge(&mut self, other: &Stats) {
118        // Calculates minimum and maximum values
119        self.assignments_min = self.assignments_min.min(other.assignments_min);
120        self.assignments_max = self.assignments_max.max(other.assignments_max);
121        self.branches_min = self.branches_min.min(other.branches_min);
122        self.branches_max = self.branches_max.max(other.branches_max);
123        self.conditions_min = self.conditions_min.min(other.conditions_min);
124        self.conditions_max = self.conditions_max.max(other.conditions_max);
125
126        self.assignments_sum += other.assignments_sum;
127        self.branches_sum += other.branches_sum;
128        self.conditions_sum += other.conditions_sum;
129
130        self.space_count += other.space_count;
131    }
132
133    /// Returns the `Abc` assignments metric value.
134    pub fn assignments(&self) -> f64 {
135        self.assignments
136    }
137
138    /// Returns the `Abc` assignments sum metric value.
139    pub fn assignments_sum(&self) -> f64 {
140        self.assignments_sum
141    }
142
143    /// Returns the `Abc` assignments average value.
144    ///
145    /// This value is computed dividing the `Abc`
146    /// assignments value for the number of spaces.
147    pub fn assignments_average(&self) -> f64 {
148        self.assignments_sum() / self.space_count as f64
149    }
150
151    /// Returns the `Abc` assignments minimum value.
152    pub fn assignments_min(&self) -> f64 {
153        self.assignments_min
154    }
155
156    /// Returns the `Abc` assignments maximum value.
157    pub fn assignments_max(&self) -> f64 {
158        self.assignments_max
159    }
160
161    /// Returns the `Abc` branches metric value.
162    pub fn branches(&self) -> f64 {
163        self.branches
164    }
165
166    /// Returns the `Abc` branches sum metric value.
167    pub fn branches_sum(&self) -> f64 {
168        self.branches_sum
169    }
170
171    /// Returns the `Abc` branches average value.
172    ///
173    /// This value is computed dividing the `Abc`
174    /// branches value for the number of spaces.
175    pub fn branches_average(&self) -> f64 {
176        self.branches_sum() / self.space_count as f64
177    }
178
179    /// Returns the `Abc` branches minimum value.
180    pub fn branches_min(&self) -> f64 {
181        self.branches_min
182    }
183
184    /// Returns the `Abc` branches maximum value.
185    pub fn branches_max(&self) -> f64 {
186        self.branches_max
187    }
188
189    /// Returns the `Abc` conditions metric value.
190    pub fn conditions(&self) -> f64 {
191        self.conditions
192    }
193
194    /// Returns the `Abc` conditions sum metric value.
195    pub fn conditions_sum(&self) -> f64 {
196        self.conditions_sum
197    }
198
199    /// Returns the `Abc` conditions average value.
200    ///
201    /// This value is computed dividing the `Abc`
202    /// conditions value for the number of spaces.
203    pub fn conditions_average(&self) -> f64 {
204        self.conditions_sum() / self.space_count as f64
205    }
206
207    /// Returns the `Abc` conditions minimum value.
208    pub fn conditions_min(&self) -> f64 {
209        self.conditions_min
210    }
211
212    /// Returns the `Abc` conditions maximum value.
213    pub fn conditions_max(&self) -> f64 {
214        self.conditions_max
215    }
216
217    /// Returns the `Abc` magnitude metric value.
218    pub fn magnitude(&self) -> f64 {
219        (self.assignments.powi(2) + self.branches.powi(2) + self.conditions.powi(2)).sqrt()
220    }
221
222    /// Returns the `Abc` magnitude sum metric value.
223    pub fn magnitude_sum(&self) -> f64 {
224        (self.assignments_sum.powi(2) + self.branches_sum.powi(2) + self.conditions_sum.powi(2))
225            .sqrt()
226    }
227
228    #[inline(always)]
229    pub(crate) fn compute_sum(&mut self) {
230        self.assignments_sum += self.assignments;
231        self.branches_sum += self.branches;
232        self.conditions_sum += self.conditions;
233    }
234
235    #[inline(always)]
236    pub(crate) fn compute_minmax(&mut self) {
237        self.assignments_min = self.assignments_min.min(self.assignments);
238        self.assignments_max = self.assignments_max.max(self.assignments);
239        self.branches_min = self.branches_min.min(self.branches);
240        self.branches_max = self.branches_max.max(self.branches);
241        self.conditions_min = self.conditions_min.min(self.conditions);
242        self.conditions_max = self.conditions_max.max(self.conditions);
243        self.compute_sum();
244    }
245}
246
247#[doc(hidden)]
248pub trait Abc
249where
250    Self: Checker,
251{
252    fn compute(_node: &Node, _stats: &mut Stats) {}
253}
254
255// Inspects the content of Java parenthesized expressions
256// and `Not` operators to find unary conditional expressions
257fn java_inspect_container(container_node: &Node, conditions: &mut f64) {
258    use Java::*;
259
260    let mut node = container_node.object();
261    let mut node_kind = node.kind_id().into();
262
263    // Initializes the flag to true if the container is known to contain a boolean value
264    let mut has_boolean_content = match node.parent().unwrap().kind_id().into() {
265        BinaryExpression | IfStatement | WhileStatement | DoStatement | ForStatement => true,
266        TernaryExpression => node.prev_sibling().map_or(true, |prev_node| {
267            !matches!(prev_node.kind_id().into(), QMARK | COLON)
268        }),
269        _ => false,
270    };
271
272    // Looks inside parenthesized expressions and `Not` operators to find what they contain
273    loop {
274        // Checks if the node is a parenthesized expression or a `Not` operator
275        // The child node of index 0 contains the unary expression operator (we look for the `!` operator)
276        let is_parenthesised_exp = matches!(node_kind, ParenthesizedExpression);
277        let is_not_operator = matches!(node_kind, UnaryExpression)
278            && matches!(node.child(0).unwrap().kind_id().into(), BANG);
279
280        // Stops the exploration if the node is neither
281        // a parenthesized expression nor a `Not` operator
282        if !is_parenthesised_exp && !is_not_operator {
283            break;
284        }
285
286        // Sets the flag to true if a `Not` operator is found
287        // This is used to prove if a variable or a value returned by a method is actually boolean
288        // e.g. `return (!x);`
289        if !has_boolean_content && is_not_operator {
290            has_boolean_content = true;
291        }
292
293        // Parenthesized expressions and `Not` operators nodes
294        // always store their expressions in the children nodes of index one
295        // https://github.com/tree-sitter/tree-sitter-java/blob/master/src/grammar.json#L2472
296        // https://github.com/tree-sitter/tree-sitter-java/blob/master/src/grammar.json#L2150
297        node = node.child(1).unwrap();
298        node_kind = node.kind_id().into();
299
300        // Stops the exploration when the content is found
301        if matches!(node_kind, MethodInvocation | Identifier | True | False) {
302            if has_boolean_content {
303                *conditions += 1.;
304            }
305            break;
306        }
307    }
308}
309
310// Inspects a list of elements and counts any unary conditional expression found
311fn java_count_unary_conditions(list_node: &Node, conditions: &mut f64) {
312    use Java::*;
313
314    let list_kind = list_node.object().kind_id().into();
315    let mut cursor = list_node.object().walk();
316
317    // Scans the immediate children nodes of the argument node
318    if cursor.goto_first_child() {
319        loop {
320            // Gets the current child node and its kind
321            let node = cursor.node();
322            let node_kind = node.kind_id().into();
323
324            // Checks if the node is a unary condition
325            if matches!(node_kind, MethodInvocation | Identifier | True | False)
326                && matches!(list_kind, BinaryExpression)
327                && !matches!(list_kind, ArgumentList)
328            {
329                *conditions += 1.;
330            } else {
331                // Checks if the node is a unary condition container
332                java_inspect_container(&Node::new(node), conditions);
333            }
334
335            // Moves the cursor to the next sibling node of the current node
336            // Exits the scan if there is no next sibling node
337            if !cursor.goto_next_sibling() {
338                break;
339            }
340        }
341    }
342}
343
344impl Abc for PythonCode {}
345impl Abc for MozjsCode {}
346impl Abc for JavascriptCode {}
347impl Abc for TypescriptCode {}
348impl Abc for TsxCode {}
349impl Abc for RustCode {}
350impl Abc for CppCode {}
351impl Abc for PreprocCode {}
352impl Abc for CcommentCode {}
353
354// Fitzpatrick, Jerry (1997). "Applying the ABC metric to C, C++ and Java". C++ Report.
355// Source: https://www.softwarerenovation.com/Articles.aspx
356// ABC Java rules: (page 8, figure 4)
357// ABC Java example: (page 15, listing 4)
358impl Abc for JavaCode {
359    fn compute(node: &Node, stats: &mut Stats) {
360        use Java::*;
361
362        match node.object().kind_id().into() {
363            STAREQ | SLASHEQ | PERCENTEQ | DASHEQ | PLUSEQ | LTLTEQ | GTGTEQ | AMPEQ | PIPEEQ
364            | CARETEQ | GTGTGTEQ | PLUSPLUS | DASHDASH => {
365                stats.assignments += 1.;
366            }
367            FieldDeclaration | LocalVariableDeclaration => {
368                stats.declaration.push(DeclKind::Var);
369            }
370            Final => {
371                if let Some(DeclKind::Var) = stats.declaration.last() {
372                    stats.declaration.push(DeclKind::Const);
373                }
374            }
375            SEMI => {
376                if let Some(DeclKind::Const | DeclKind::Var) = stats.declaration.last() {
377                    stats.declaration.clear();
378                }
379            }
380            EQ => {
381                // Excludes constant declarations
382                stats
383                    .declaration
384                    .last()
385                    .map(|decl| {
386                        if matches!(decl, DeclKind::Var) {
387                            stats.assignments += 1.;
388                        }
389                    })
390                    .unwrap_or_else(|| {
391                        stats.assignments += 1.;
392                    });
393            }
394            MethodInvocation | New => {
395                stats.branches += 1.;
396            }
397            GTEQ | LTEQ | EQEQ | BANGEQ | Else | Case | Default | QMARK | Try | Catch => {
398                stats.conditions += 1.;
399            }
400            GT | LT => {
401                // Excludes `<` and `>` used for generic types
402                if let Some(parent) = node.object().parent() {
403                    if !matches!(parent.kind_id().into(), TypeArguments) {
404                        stats.conditions += 1.;
405                    }
406                }
407            }
408            // Counts unary conditions in elements separated by `&&` or `||` boolean operators
409            AMPAMP | PIPEPIPE => {
410                if let Some(parent) = node.object().parent() {
411                    java_count_unary_conditions(&Node::new(parent), &mut stats.conditions);
412                }
413            }
414            // Counts unary conditions among method arguments
415            ArgumentList => {
416                java_count_unary_conditions(node, &mut stats.conditions);
417            }
418            // Counts unary conditions inside assignments
419            VariableDeclarator | AssignmentExpression => {
420                // The child node of index 2 contains the right operand of an assignment operation
421                if let Some(right_operand) = node.object().child(2) {
422                    if matches!(
423                        right_operand.kind_id().into(),
424                        ParenthesizedExpression | UnaryExpression
425                    ) {
426                        java_inspect_container(&Node::new(right_operand), &mut stats.conditions);
427                    }
428                }
429            }
430            // Counts unary conditions inside if and while statements
431            IfStatement | WhileStatement => {
432                // The child node of index 1 contains the condition
433                if let Some(condition) = node.object().child(1) {
434                    if matches!(condition.kind_id().into(), ParenthesizedExpression) {
435                        java_inspect_container(&Node::new(condition), &mut stats.conditions);
436                    }
437                }
438            }
439            // Counts unary conditions do-while statements
440            DoStatement => {
441                // The child node of index 3 contains the condition
442                if let Some(condition) = node.object().child(3) {
443                    if matches!(condition.kind_id().into(), ParenthesizedExpression) {
444                        java_inspect_container(&Node::new(condition), &mut stats.conditions);
445                    }
446                }
447            }
448            // Counts unary conditions inside for statements
449            ForStatement => {
450                // The child node of index 3 contains the `condition` when
451                // the initialization expression is a variable declaration
452                // e.g. `for ( int i=0; `condition`; ... ) {}`
453                if let Some(condition) = node.object().child(3) {
454                    match condition.kind_id().into() {
455                        SEMI => {
456                            // The child node of index 4 contains the `condition` when
457                            // the initialization expression is not a variable declaration
458                            // e.g. `for ( i=0; `condition`; ... ) {}`
459                            if let Some(cond) = node.object().child(4) {
460                                match cond.kind_id().into() {
461                                    MethodInvocation | Identifier | True | False | SEMI
462                                    | RPAREN => {
463                                        stats.conditions += 1.;
464                                    }
465                                    ParenthesizedExpression | UnaryExpression => {
466                                        java_inspect_container(
467                                            &Node::new(cond),
468                                            &mut stats.conditions,
469                                        );
470                                    }
471                                    _ => {}
472                                }
473                            }
474                        }
475                        MethodInvocation | Identifier | True | False => {
476                            stats.conditions += 1.;
477                        }
478                        ParenthesizedExpression | UnaryExpression => {
479                            java_inspect_container(&Node::new(condition), &mut stats.conditions);
480                        }
481                        _ => {}
482                    }
483                }
484            }
485            // Counts unary conditions inside return statements
486            ReturnStatement => {
487                // The child node of index 1 contains the return value
488                if let Some(value) = node.object().child(1) {
489                    if matches!(
490                        value.kind_id().into(),
491                        ParenthesizedExpression | UnaryExpression
492                    ) {
493                        java_inspect_container(&Node::new(value), &mut stats.conditions)
494                    }
495                }
496            }
497            // Counts unary conditions inside implicit return statements in lambda expressions
498            LambdaExpression => {
499                // The child node of index 2 contains the return value
500                if let Some(value) = node.object().child(2) {
501                    if matches!(
502                        value.kind_id().into(),
503                        ParenthesizedExpression | UnaryExpression
504                    ) {
505                        java_inspect_container(&Node::new(value), &mut stats.conditions)
506                    }
507                }
508            }
509            // Counts unary conditions inside ternary expressions
510            TernaryExpression => {
511                // The child node of index 0 contains the condition
512                if let Some(condition) = node.object().child(0) {
513                    match condition.kind_id().into() {
514                        MethodInvocation | Identifier | True | False => {
515                            stats.conditions += 1.;
516                        }
517                        ParenthesizedExpression | UnaryExpression => {
518                            java_inspect_container(&Node::new(condition), &mut stats.conditions);
519                        }
520                        _ => {}
521                    }
522                }
523                // The child node of index 2 contains the first expression
524                if let Some(expression) = node.object().child(2) {
525                    if matches!(
526                        expression.kind_id().into(),
527                        ParenthesizedExpression | UnaryExpression
528                    ) {
529                        java_inspect_container(&Node::new(expression), &mut stats.conditions);
530                    }
531                }
532                // The child node of index 4 contains the second expression
533                if let Some(expression) = node.object().child(4) {
534                    if matches!(
535                        expression.kind_id().into(),
536                        ParenthesizedExpression | UnaryExpression
537                    ) {
538                        java_inspect_container(&Node::new(expression), &mut stats.conditions);
539                    }
540                }
541            }
542            _ => {}
543        }
544    }
545}
546
547#[cfg(test)]
548mod tests {
549    use std::path::PathBuf;
550
551    use super::*;
552
553    // Constant declarations are not counted as assignments
554    #[test]
555    fn java_constant_declarations() {
556        check_metrics!(
557            "class A {
558                private final int X1 = 0, Y1 = 0;
559                public final float PI = 3.14f;
560                final static String HELLO = \"Hello,\";
561                protected String world = \" world!\";   // +1a
562                public float e = 2.718f;                // +1a
563                private int x2 = 1, y2 = 2;             // +2a
564            
565                void m() {
566                    final int Z1 = 0, Z2 = 0, Z3 = 0;
567                    final float T = 0.0f;
568                    int z1 = 1, z2 = 2, z3 = 3;         // +3a
569                    float t = 60.0f;                    // +1a
570                }
571            }",
572            "foo.java",
573            JavaParser,
574            abc,
575            [
576                (assignments_sum, 8.0),
577                (branches_sum, 0.0),
578                (conditions_sum, 0.0),
579                (magnitude_sum, 8.0), // sqrt(64 + 0 + 0) = sqrt(64)
580                (assignments_average, 2.666_666_666_666_666_6), // space count = 3 (1 unit, 1 class and 1 method)
581                (branches_average, 0.0),
582                (conditions_average, 0.0),
583                (assignments_min, 0.0),
584                (branches_min, 0.0),
585                (conditions_min, 0.0),
586                (assignments_max, 4.0),
587                (branches_max, 0.0),
588                (conditions_max, 0.0)
589            ]
590        );
591    }
592
593    // "In computer science, conditionals (that is, conditional statements, conditional expressions
594    // and conditional constructs,) are programming language commands for handling decisions."
595    // Source: https://en.wikipedia.org/wiki/Conditional_(computer_programming)
596    // According to this definition, boolean expressions that are evaluated to make a decision are considered as conditions
597    // Variables, method invocations and true or false values used inside
598    // variable declarations and assignment expressions are not counted as conditions
599    #[test]
600    fn java_declarations_with_conditions() {
601        check_metrics!(
602            "
603            boolean a = (1 > 2);            // +1a +1c
604            boolean b = 3 > 4;              // +1a +1c
605            boolean c = (1 > 2) && 3 > 4;   // +1a +2c
606            boolean d = b && (x > 5) || c;  // +1a +3c
607            boolean e = !d;                 // +1a +1c
608            boolean f = ((!false));         // +1a +1c
609            boolean g = !(!(true));         // +1a +1c
610            boolean h = true;               // +1a
611            boolean i = (false);            // +1a
612            boolean j = (((((true)))));     // +1a
613            boolean k = (((((m())))));      // +1a +1b
614            boolean l = (((((!m())))));     // +1a +1b +1c
615            boolean m = (!(!((m()))));      // +1a +1b +1c
616            List<String> n = null;          // +1a (< and > used for generic types are not counted as conditions)
617            ",
618            "foo.java",
619            JavaParser,
620            abc,
621            [
622                (assignments_sum, 14.0),
623                (branches_sum, 3.0),
624                (conditions_sum, 12.0),
625                (magnitude_sum, 18.681_541_692_269_406), // sqrt(196 + 9 + 144) = sqrt(349)
626                (assignments_average, 14.0),             // space count = 1 (1 unit)
627                (branches_average, 3.0),
628                (conditions_average, 12.0),
629                (assignments_min, 14.0),
630                (branches_min, 3.0),
631                (conditions_min, 12.0),
632                (assignments_max, 14.0),
633                (branches_max, 3.0),
634                (conditions_max, 12.0)
635            ]
636        );
637    }
638
639    // Conditions can be found in assignment expressions
640    #[test]
641    fn java_assignments_with_conditions() {
642        check_metrics!(
643            "
644            a = 2 < 1;                  // +1a +1c
645            b = (4 >= 3) && 2 <= 1;     // +1a +2c
646            c = a || (x != 10) && b;    // +1a +3c
647            d = !false;                 // +1a +1c
648            e = (!false);               // +1a +1c
649            f = !(false);               // +1a +1c
650            g = (!(((true))));          // +1a +1c
651            h = ((true));               // +1a
652            i = !m();                   // +1a +1b +1c
653            j = !((m()));               // +1a +1b +1c
654            k = (!(m()));               // +1a +1b +1c
655            l = ((!(m())));             // +1a +1b +1c
656            m = !B.<Integer>m(2);       // +1a +1b +1c
657            n = !((B.<Integer>m(4)));   // +1a +1b +1c
658            ",
659            "foo.java",
660            JavaParser,
661            abc,
662            [
663                (assignments_sum, 14.0),
664                (branches_sum, 6.0),
665                (conditions_sum, 16.0),
666                (magnitude_sum, 22.090_722_034_374_522), // sqrt(196 + 36 + 256) = sqrt(488)
667                (assignments_average, 14.0),             // space count = 1 (1 unit)
668                (branches_average, 6.0),
669                (conditions_average, 16.0),
670                (assignments_min, 14.0),
671                (branches_min, 6.0),
672                (conditions_min, 16.0),
673                (assignments_max, 14.0),
674                (branches_max, 6.0),
675                (conditions_max, 16.0)
676            ]
677        );
678    }
679
680    // Conditions can be found in method arguments
681    #[test]
682    fn java_methods_arguments_with_conditions() {
683        check_metrics!(
684            "
685            m1(a);                                  // +1b
686            m2(a, b);                               // +1b
687            m3(true, (false), (((true))));          // +1b
688            m3(m1(false), m1(true), m1(false));     // +4b
689            m1(!a);                                 // +1b +1c
690            m2((((a))), (!b));                      // +1b +1c
691            m3(!(a), b, !!!c);                      // +1b +2c
692            m3(a, !b, m2(!a, !m2(!b, !m1(!c))));    // +4b +6c
693            ",
694            "foo.java",
695            JavaParser,
696            abc,
697            [
698                (assignments_sum, 0.0),
699                (branches_sum, 14.0),
700                (conditions_sum, 10.0),
701                (magnitude_sum, 17.204_650_534_085_253), // sqrt(0 + 196 + 100) = sqrt(296)
702                (assignments_average, 0.0),              // space count = 1 (1 unit)
703                (branches_average, 14.0),
704                (conditions_average, 10.0),
705                (assignments_min, 0.0),
706                (branches_min, 14.0),
707                (conditions_min, 10.0),
708                (assignments_max, 0.0),
709                (branches_max, 14.0),
710                (conditions_max, 10.0)
711            ]
712        );
713    }
714
715    // "A unary conditional expression is an implicit condition that uses no relational operators."
716    // Source: Fitzpatrick, Jerry (1997). "Applying the ABC metric to C, C++ and Java". C++ Report.
717    // https://www.softwarerenovation.com/Articles.aspx (page 5)
718    #[test]
719    fn java_if_single_conditions() {
720        check_metrics!(
721            "          
722            if ( a < 0 ) {}             // +1c
723            if ( ((a != 0)) ) {}        // +1c
724            if ( !(a > 0) ) {}          // +1c
725            if ( !(((a == 0))) ) {}     // +1c
726            if ( b.m1() ) {}            // +1b +1c
727            if ( !b.m1() ) {}           // +1b +1c
728            if ( !!b.m2() ) {}          // +1b +1c
729            if ( (!(b.m1())) ) {}       // +1b +1c
730            if ( (!(!b.m1())) ) {}      // +1b +1c
731            if ( ((b.m2())) ) {}        // +1b +1c
732            if ( ((b.m().m1())) ) {}    // +2b +1c
733            if ( c ) {}                 // +1c
734            if ( !c ) {}                // +1c
735            if ( !!!!!!!!!!c ) {}       // +1c
736            if ( (((c))) ) {}           // +1c
737            if ( (((!c))) ) {}          // +1c
738            if ( ((!(c))) ) {}          // +1c
739            if ( true ) {}              // +1c
740            if ( !true ) {}             // +1c
741            if ( ((false)) ) {}         // +1c
742            if ( !(!(false)) ) {}       // +1c
743            if ( !!!false ) {}          // +1c
744            ",
745            "foo.java",
746            JavaParser,
747            abc,
748            [
749                (assignments_sum, 0.0),
750                (branches_sum, 8.0),
751                (conditions_sum, 22.0),
752                (magnitude_sum, 23.409_399_821_439_25), // sqrt(0 + 64 + 484) = sqrt(548)
753                (assignments_average, 0.0),             // space count = 1 (1 unit)
754                (branches_average, 8.0),
755                (conditions_average, 22.0),
756                (assignments_min, 0.0),
757                (branches_min, 8.0),
758                (conditions_min, 22.0),
759                (assignments_max, 0.0),
760                (branches_max, 8.0),
761                (conditions_max, 22.0)
762            ]
763        );
764    }
765
766    #[test]
767    fn java_if_multiple_conditions() {
768        check_metrics!(
769            "
770            if ( a || b || c || d ) {}              // +4c
771            if ( a || b && c && d ) {}              // +4c
772            if ( x < y && a == b ) {}               // +2c
773            if ( ((z < (x + y))) ) {}               // +1c
774            if ( a || ((((b))) && c) ) {}           // +3c
775            if ( a && ((((a == b))) && c) ) {}      // +3c
776            if ( a || ((((a == b))) || ((c))) ) {}  // +3c
777            if ( x < y && B.m() ) {}                // +1b +2c
778            if ( x < y && !(((B.m()))) ) {}         // +1b +2c
779            if ( !(x < y) && !B.m() ) {}            // +1b +2c
780            if ( !!!(!!!(a)) && B.m() ||            // +1b +2c
781                 !B.m() && (((x > 4))) ) {}         // +1b +2c
782            ",
783            "foo.java",
784            JavaParser,
785            abc,
786            [
787                (assignments_sum, 0.0),
788                (branches_sum, 5.0),
789                (conditions_sum, 30.0),
790                (magnitude_sum, 30.413_812_651_491_1), // sqrt(0 + 25 + 900) = sqrt(925)
791                (assignments_average, 0.0),            // space count = 1 (1 unit)
792                (branches_average, 5.0),
793                (conditions_average, 30.0),
794                (assignments_min, 0.0),
795                (branches_min, 5.0),
796                (conditions_min, 30.0),
797                (assignments_max, 0.0),
798                (branches_max, 5.0),
799                (conditions_max, 30.0)
800            ]
801        );
802    }
803
804    #[test]
805    fn java_while_and_do_while_conditions() {
806        check_metrics!(
807            "
808            while ( (!(!(!(a)))) ) {}                   // +1c
809            while ( b || 1 > 2 ) {}                     // +2c
810            while ( x.m() && (((c))) ) {}               // +1b +2c
811            do {} while ( !!!(((!!!a))) );              // +1c
812            do {} while ( a || (b && c) );              // +3c
813            do {} while ( !x.m() && 1 > 2 || !true );   // +1b +3c
814            ",
815            "foo.java",
816            JavaParser,
817            abc,
818            [
819                (assignments_sum, 0.0),
820                (branches_sum, 2.0),
821                (conditions_sum, 12.0),
822                (magnitude_sum, 12.165_525_060_596_439), // sqrt(0 + 4 + 144) = sqrt(148)
823                (assignments_average, 0.0),              // space count = 1 (1 unit)
824                (branches_average, 2.0),
825                (conditions_average, 12.0),
826                (assignments_min, 0.0),
827                (branches_min, 2.0),
828                (conditions_min, 12.0),
829                (assignments_max, 0.0),
830                (branches_max, 2.0),
831                (conditions_max, 12.0)
832            ]
833        );
834    }
835
836    // GMetrics, a Groovy source code analyzer, provides the following definition of unary conditional expression:
837    // "These are cases where a single variable/field/value is treated as a boolean value.
838    // Examples include `if (x)` and `return !ready`."
839    // According to this definition, unary conditional expressions are counted also in function return values.
840    // Source: https://dx42.github.io/gmetrics/metrics/AbcMetric.html
841    // Examples: https://github.com/dx42/gmetrics/blob/master/src/test/groovy/org/gmetrics/metric/abc/AbcMetric_MethodTest.groovy
842    #[test]
843    fn java_return_with_conditions() {
844        check_metrics!(
845            "class A {
846                boolean m1() {
847                    return !(z >= 0);       // +1c
848                }
849                boolean m2() {
850                    return (((!x)));        // +1c
851                }
852                boolean m3() {
853                    return x && y;          // +2c
854                }
855                boolean m4() {
856                    return y || (z < 0);    // +2c
857                }
858                boolean m5() {
859                    return x || y ?         // +3c (two unary conditions and one ?)
860                        true : false;
861                }
862            }",
863            "foo.java",
864            JavaParser,
865            abc,
866            [
867                (assignments_sum, 0.0),
868                (branches_sum, 0.0),
869                (conditions_sum, 9.0),
870                (magnitude_sum, 9.0),       // sqrt(0 + 0 + 81) = sqrt(81)
871                (assignments_average, 0.0), // space count = 7 (1 unit, 1 class and 5 methods)
872                (branches_average, 0.0),
873                (conditions_average, 1.285_714_285_714_285_8),
874                (assignments_min, 0.0),
875                (branches_min, 0.0),
876                (conditions_min, 0.0),
877                (assignments_max, 0.0),
878                (branches_max, 0.0),
879                (conditions_max, 3.0)
880            ]
881        );
882    }
883
884    // Variables, method invocations, and true or false values
885    // inside return statements are not counted as conditions
886    #[test]
887    fn java_return_without_conditions() {
888        check_metrics!(
889            "class A {
890                boolean m1() {
891                    return x;
892                }
893                boolean m2() {
894                    return (x);
895                }
896                boolean m3() {
897                    return y.m();   // +1b
898                }
899                boolean m4() {
900                    return false;
901                }
902                void m5() {
903                    return;
904                }
905            }",
906            "foo.java",
907            JavaParser,
908            abc,
909            [
910                (assignments_sum, 0.0),
911                (branches_sum, 1.0),
912                (conditions_sum, 0.0),
913                (magnitude_sum, 1.0),       // sqrt(0 + 1 + 0) = sqrt(1)
914                (assignments_average, 0.0), // space count = 7 (1 unit, 1 class and 5 methods)
915                (branches_average, 0.142_857_142_857_142_85),
916                (conditions_average, 0.0),
917                (assignments_min, 0.0),
918                (branches_min, 0.0),
919                (conditions_min, 0.0),
920                (assignments_max, 0.0),
921                (branches_max, 1.0),
922                (conditions_max, 0.0)
923            ]
924        );
925    }
926
927    // Variables, method invocations, and true or false values
928    // in lambda expression return values are not counted as conditions
929    #[test]
930    fn java_lambda_expressions_return_with_conditions() {
931        check_metrics!(
932            "
933            Predicate<Boolean> p1 = a -> a;                         // +1a
934            Predicate<Boolean> p2 = b -> true;                      // +1a
935            Predicate<Boolean> p3 = c -> m();                       // +1a
936            Predicate<Integer> p4 = d -> d > 10;                    // +1a +1c
937            Predicate<Boolean> p5 = (e) -> !e;                      // +1a +1c
938            Predicate<Boolean> p6 = (f) -> !((!f));                 // +1a +1c
939            Predicate<Boolean> p7 = (g) -> !g && true;              // +1a +2c
940            BiPredicate<Boolean, Boolean> bp1 = (h, i) -> !h && !i; // +1a +2c
941            BiPredicate<Boolean, Boolean> bp2 = (j, k) -> {
942                return j || k;                                      // +1a +2c
943            };
944            ",
945            "foo.java",
946            JavaParser,
947            abc,
948            [
949                (assignments_sum, 9.0),
950                (branches_sum, 1.0),
951                (conditions_sum, 9.0),
952                (magnitude_sum, 12.767_145_334_803_704), // sqrt(81 + 1 + 81) = sqrt(163)
953                (assignments_average, 9.0),              // space count = 1 (1 unit)
954                (branches_average, 1.0),
955                (conditions_average, 9.0),
956                (assignments_min, 9.0),
957                (branches_min, 1.0),
958                (conditions_min, 9.0),
959                (assignments_max, 9.0),
960                (branches_max, 1.0),
961                (conditions_max, 9.0)
962            ]
963        );
964    }
965
966    #[test]
967    fn java_for_with_variable_declaration() {
968        check_metrics!(
969            "
970            for ( int i1 = 0; !(!(!(!a))); i1++ ) {}                // +2a +1c
971            for ( int i2 = 0; !B.m(); i2++ ) {}                     // +2a +1b +1c
972            for ( int i3 = 0; a || false; i3++ ) {}                 // +2a +2c
973            for ( int i4 = 0; a && B.m() ? true : false; i4++ ) {}  // +2a +1b +3c
974            for ( int i5 = 0; true; i5++ ) {}                       // +2a +1c
975            ",
976            "foo.java",
977            JavaParser,
978            abc,
979            [
980                (assignments_sum, 10.0),
981                (branches_sum, 2.0),
982                (conditions_sum, 8.0),
983                (magnitude_sum, 12.961_481_396_815_72), // sqrt(100 + 4 + 64) = sqrt(168)
984                (assignments_average, 10.0),            // space count = 1 (1 unit)
985                (branches_average, 2.0),
986                (conditions_average, 8.0),
987                (assignments_min, 10.0),
988                (branches_min, 2.0),
989                (conditions_min, 8.0),
990                (assignments_max, 10.0),
991                (branches_max, 2.0),
992                (conditions_max, 8.0)
993            ]
994        );
995    }
996
997    #[test]
998    fn java_for_without_variable_declaration() {
999        check_metrics!(
1000            "class A{
1001                void m1() {
1002                    for (i = 0; x < y; i++) {}          // +2a +1c
1003                    for (i = 0; ((x < y)); i++) {}      // +2a +1c
1004                    for (i = 0; !(!(x < y)); i++) {}    // +2a +1c
1005                    for (i = 0; true; i++) {}           // +2a +1c
1006                }
1007                void m2() {
1008                    for ( ; true; ) {}  // +1c
1009                }
1010                void m3() {
1011                    for ( ; ; ) {}      // +1c (one implicit unary condition set to true)
1012                }
1013            }",
1014            "foo.java",
1015            JavaParser,
1016            abc,
1017            [
1018                (assignments_sum, 8.0),
1019                (branches_sum, 0.0),
1020                (conditions_sum, 6.0),
1021                (magnitude_sum, 10.0),      // sqrt(64 + 0 + 36) = sqrt(100)
1022                (assignments_average, 1.6), // space count = 5 (1 unit, 1 class and 3 methods)
1023                (branches_average, 0.0),
1024                (conditions_average, 1.2),
1025                (assignments_min, 0.0),
1026                (branches_min, 0.0),
1027                (conditions_min, 0.0),
1028                (assignments_max, 8.0),
1029                (branches_max, 0.0),
1030                (conditions_max, 4.0)
1031            ]
1032        );
1033    }
1034
1035    // Variables, method invocations, and true or false values
1036    // in ternary expression return values are not counted as conditions
1037    #[test]
1038    fn java_ternary_conditions() {
1039        check_metrics!(
1040            "
1041            a = true;                                   // +1a
1042            b = a ? true : false;                       // +1a +2c
1043            c = ((((a)))) ? !false : !b;                // +1a +4c
1044            d = !this.m() ? !!a : (false);              // +1a +1b +3c
1045            e = !(a) && b ? ((c)) : !d;                 // +1a +4c
1046            if ( this.m() ? a : !this.m() ) {}          // +2b +3c
1047            if ( x > 0 ? !(false) : this.m() ) {}       // +1b +3c
1048            if ( x > 0 && x != 3 ? !(a) : (!(b)) ) {}   // +5c
1049            ",
1050            "foo.java",
1051            JavaParser,
1052            abc,
1053            [
1054                (assignments_sum, 5.0),
1055                (branches_sum, 4.0),
1056                (conditions_sum, 24.0),
1057                (magnitude_sum, 24.839_484_696_748_443), // sqrt(25 + 16 + 576) = sqrt(617)
1058                (assignments_average, 5.0),              // space count = 1 (1 unit)
1059                (branches_average, 4.0),
1060                (conditions_average, 24.0),
1061                (assignments_min, 5.0),
1062                (branches_min, 4.0),
1063                (conditions_min, 24.0),
1064                (assignments_max, 5.0),
1065                (branches_max, 4.0),
1066                (conditions_max, 24.0)
1067            ]
1068        );
1069    }
1070}