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
//! `Cognitive` implementation for PHP.
#![allow(
clippy::enum_glob_use,
clippy::match_same_arms,
clippy::needless_pass_by_value,
clippy::wildcard_imports
)]
#![allow(
clippy::cast_precision_loss,
clippy::cast_possible_truncation,
clippy::cast_sign_loss
)]
use super::*;
impl Cognitive for PhpCode {
fn compute<'a>(
node: &Node<'a>,
_code: &'a [u8],
stats: &mut Stats,
nesting_map: &mut HashMap<usize, (usize, usize, usize)>,
) {
use Php::*;
let (mut nesting, mut depth, mut lambda) = get_nesting_from_map(node, nesting_map);
match node.kind_id().into() {
// The two-word `else if` form parses as `else_clause →
// if_statement`; `Self::is_else_if` flags that nested
// `IfStatement` so it is not counted again against the wrapping
// `else_clause`'s branch extension and does not inflate nesting
// for later arms (#529). The one-word `elseif` keyword is a
// dedicated `ElseIfClause` node handled by the branch-extension
// arm below.
IfStatement if !Self::is_else_if(node) => {
increase_nesting(stats, &mut nesting, depth, lambda);
}
ForStatement
| ForeachStatement
| WhileStatement
| DoStatement
| SwitchStatement
| MatchExpression
| CatchClause
| ConditionalExpression => {
increase_nesting(stats, &mut nesting, depth, lambda);
}
ElseClause | ElseClause2 | ElseIfClause | ElseIfClause2 => {
increment_branch_extension(stats);
}
// Per SonarSource Cognitive Complexity §B2, `goto label;` is an
// unstructured jump and adds +1 (matching C++/C#/Go/Perl/Lua
// goto). PHP has no *labeled* `break`/`continue`; its only
// non-default jump argument is the numeric level form
// `break N;` / `continue N;`, which breaks out of N enclosing
// loops. Those enclosing loops are already counted via nesting,
// so the numeric form is a structured loop-level exit and adds
// +0 — only `goto` is genuinely unstructured here.
GotoStatement => {
increment_by_one(stats);
}
BinaryExpression => {
// PHP's null-coalescing `??` short-circuits like `&&` /
// `||` and the word-form `and` / `or` / `xor`, so it
// forms boolean sequences alongside them. Mirrors the
// PHP cyclomatic operator set minus the assignment
// form `??=`, which is not a `BinaryExpression`.
compute_booleans_with(node, stats, |id| {
matches!(id.into(), AMPAMP | PIPEPIPE | And | Or | Xor | QMARKQMARK)
});
}
AugmentedAssignmentExpression => {
// PHP's `??=` is `x = x ?? y` and carries one boolean-
// sequence decision, parallel to the cyclomatic fix
// from #231. The token sits inside the augmented-
// assignment container rather than a `BinaryExpression`,
// so it needs its own arm (#236). PHP grammar has no
// `&&=` / `||=`.
compute_booleans_with(node, stats, |id| matches!(id.into(), QMARKQMARKEQ));
}
AnonymousFunction | ArrowFunction => {
lambda += 1;
}
// At a (possibly nested) named-function / method boundary, reset
// structural nesting to zero and bump the function-depth
// surcharge when this definition is itself nested inside another
// — matching Java and the sibling families. Without this, a PHP
// function/method declared inside a control construct inherited
// the enclosing nesting and every nested definition missed the
// SonarSource B-nesting amplification (#775, the #696 gap).
// Closures (`AnonymousFunction` / `ArrowFunction`) are handled by
// the lambda arm above and intentionally do *not* reset nesting,
// mirroring how the siblings treat lambda vs named-function arms.
FunctionDefinition | MethodDeclaration => {
nesting = 0;
increment_function_depth(
&mut depth,
node,
&[FunctionDefinition, MethodDeclaration],
);
}
_ => {}
}
nesting_map.insert(node.id(), (nesting, depth, lambda));
}
}