1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
#![allow(
clippy::enum_glob_use,
clippy::too_many_lines,
clippy::wildcard_imports
)]
#![allow(
clippy::cast_precision_loss,
clippy::cast_possible_truncation,
clippy::cast_sign_loss
)]
use super::cpp::{
cpp_count_arguments, cpp_count_chain_operands, cpp_count_condition_slot, cpp_count_return,
cpp_walk_for_statement, cpp_walk_ternary,
};
use super::{Abc, Stats};
use crate::metrics::cyclomatic::cpp_operator_is_applied;
use crate::*;
impl Abc for MozcppCode {
fn compute<'a>(
node: &Node<'a>,
_code: &'a [u8],
ancestors: Ancestors<'a, '_>,
stats: &mut Stats,
) {
// bca: suppress(cyclomatic)
// Exhaustive one-arm-per-grammar-kind dispatch table; see the
// rationale on `CppCode::compute`, of which this is the
// Mozilla-fork clone.
use Mozcpp::*;
// A requires clause pays no condition — see the Cpp twin (#1533).
let opens = matches!(node.kind_id().into(), RequiresClause | RequiresExpression);
let in_constraint = stats.constraint.covers(node, opens);
match node.kind_id().into() {
// `assignment_expression` covers both plain `=` and every
// compound form (`+=`, `-=`, `*=`, `/=`, `%=`, `&=`, `|=`,
// `^=`, `<<=`, `>>=`); the grammar lifts them all into a
// single named node so we count once per
// `assignment_expression`. `update_expression` covers both
// prefix and postfix `++` / `--`.
AssignmentExpression | AssignmentExpression2 | UpdateExpression => {
stats.assignments += 1.;
}
// `int x = expr;` parses as a `declaration` carrying an
// `init_declarator` of the form `declarator = value`. Per
// Fitzpatrick (1997), every `=` operator increments A; the
// JS impl already counts `let x = 5;` (and excludes
// `const`). We follow the literal reading for C++ too and
// count every `init_declarator` whose body contains an
// explicit `=` token. `const int x = 5;` is counted along
// with `int x = 5;` — distinguishing them would diverge
// from the JS rule's "let counted, const not" mapping
// because C++ `const` semantics are unlike JS `const` (a
// C++ `const int x` binding is the canonical "one
// assignment to initialise" — closer to Rust's
// non-`mut` `let` than to JS's hoisted reference binding).
// `int x;` parses as a plain declarator inside the
// `declaration`, not an `init_declarator`, so this arm
// never fires for un-initialised declarations. The second
// `init_declarator` grammar form `int x(5);` / `int x{5};`
// (paren / brace init) carries no `=` token and stays out
// — only the `=` operator counts.
InitDeclarator if node.first_child(|id| id == EQ as u16).is_some() => {
stats.assignments += 1.;
}
// Every call counts (method calls fold in as
// `call_expression` with a `field_expression` callee). The
// C++ grammar exposes two aliased `call_expression` ids.
// `new T(...)` allocations count as a branch — they invoke
// a constructor, mirroring Java's `New` and C#'s
// `ObjectCreationExpression` rule.
CallExpression | CallExpression2 | NewExpression => {
stats.branches += 1.;
}
// Only the A and B arms above apply in a constraint.
_ if in_constraint => {}
// `else` opens an alternative branch path; `case`
// (non-default) adds one per switch arm; `?` opens a
// ternary; `try` / `catch` count per Fitzpatrick (and
// Java's rule). `Try2` is the second token-id alias the
// C++ grammar emits for `try` (it appears under
// structured-exception forms).
//
// `&&` / `||` are deliberately NOT counted (Fitzpatrick
// Rule 7 in Figure 3 for C++; the unary-conditional
// counterpart is Rule 9). See the module-level `Stats`
// doc-comment for the cross-language policy (issue
// #395, walker tracked in #403).
Else | Case | QMARK | Try | Try2 | Catch => {
stats.conditions += 1.;
}
// The seven comparison tokens count only when applied. The
// vendored grammar has the same productions for them as
// tree-sitter-cpp; the `grammar.json` sweep, the fold and
// `#if` decisions, and the operator-overload defect (#1448)
// are recorded on the matching arm of `CppCode::compute`.
LT | GT | LTEQ | GTEQ | EQEQ | BANGEQ | NotEq | LTEQGT
if cpp_operator_is_applied(node, ancestors) =>
{
stats.conditions += 1.;
}
// Fitzpatrick Rule 9 (C++ in Figure 3): each operand of a
// `&&` / `||` chain is one condition (issue #403). `and` /
// `or` are the same operators spelled as ISO alternative
// tokens, which the grammar gives kinds of their own.
//
// Gated like the comparisons, so a `constraint_conjunction`
// and a binary fold's second
// operator spelling score nothing; the derivation is on the
// Cpp twin.
AMPAMP | PIPEPIPE | And | Or if cpp_operator_is_applied(node, ancestors) => {
if let Some(parent) = ancestors.parent(node) {
cpp_count_chain_operands(&parent, &mut stats.conditions);
}
}
// Phase-2B (issue #403): condition slots, read by grammar field
// in `cpp_count_condition_slot`. The slot pays for any
// predicate no other arm counts (#1526), so `if (true)`,
// `if (-x)` and `return !x` each count one condition; bare
// `return x` reports zero.
IfStatement | WhileStatement | DoStatement => {
cpp_count_condition_slot(node, &mut stats.conditions);
}
// `co_return` reads its value as `return` does; the
// derivation is on the Cpp twin (#1547).
ReturnStatement | CoReturnStatement => {
cpp_count_return(node, &mut stats.conditions);
}
// `f(!a, !b)` — argument list walker. Two aliases —
// `argument_list` is emitted as ArgumentList or
// ArgumentList2 depending on production rule path.
ArgumentList | ArgumentList2 => {
cpp_count_arguments(node, &mut stats.conditions);
}
// `a ? !b : !c` — the ternary's own `?` token is already
// counted by the condition arm above; this walks the three
// operand slots (issue #1102).
ConditionalExpression => {
cpp_walk_ternary(node, &mut stats.conditions);
}
// `for (init; cond; update)` — the condition slot, read by
// grammar field (issue #1276). `for (;;)` has no condition
// field and counts nothing.
ForStatement => {
cpp_walk_for_statement(node, &mut stats.conditions);
}
_ => {}
}
}
}