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
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
//! `Getter` implementation for PHP.
#![allow(clippy::wildcard_imports, clippy::enum_glob_use)]
use super::*;
impl Getter for PhpCode {
fn get_space_kind(node: &Node) -> SpaceKind {
match node.kind_id().into() {
// PHP traits are class-like mixins whose method
// implementations roll up into the consuming class's WMC; we
// map them to `SpaceKind::Class` so the per-class metrics
// (NPA, NPM, WMC) treat them uniformly. The output may label
// them "class" — that is intentional for metric coherence.
// LOAD-BEARING: `Wmc::compute` for PhpCode does not match
// `SpaceKind::Trait`. If you remap `TraitDeclaration` here,
// also update `src/metrics/wmc.rs`.
Php::ClassDeclaration
| Php::AnonymousClass
| Php::EnumDeclaration
| Php::TraitDeclaration => SpaceKind::Class,
Php::InterfaceDeclaration => SpaceKind::Interface,
Php::FunctionDefinition
| Php::MethodDeclaration
| Php::AnonymousFunction
| Php::ArrowFunction => SpaceKind::Function,
Php::Program => SpaceKind::Unit,
_ => SpaceKind::Unknown,
}
}
fn get_op_type(node: &Node) -> HalsteadType {
use Php::*;
match node.kind_id().into() {
// Operator: control-flow keywords
If | Else | Elseif | Endif
| Switch | Case | Default | Endswitch
| For | Endfor | Foreach | Endforeach
| While | Endwhile | Do
| Break | Continue
| Return | Throw | Try | Catch | Finally
| Match | Yield | Yieldfrom | Goto
| Echo | Exit | Print
| Include | IncludeOnce | Require | RequireOnce
// Operator: declaration keywords
| Function | Class | Interface | Trait | Enum | Namespace
| Use | Const | Global | Static | VarModifier
| Public | Protected | Private
| Final | Abstract | Readonly
| New | Clone | Instanceof | As | Insteadof | Extends | Implements
| Fn | Declare | Enddeclare | Unset | List
| Zelf | Parent
// Operator: structural punctuation. Only the *opening*
// delimiter is an operator (the pair folds to one glyph in
// `get_operator_id_as_str`); the former closing arms
// (`RBRACE`/`RPAREN`/`RPAREN2`/`RBRACK`) double-counted every
// balanced pair, inflating n1/N1 (#695). `LPAREN2` is defensive —
// the runtime collapses it to `LPAREN` before `kind_id()`, so it
// never fires (#768; see the Cpp note).
| LBRACE | LPAREN | LPAREN2
| LBRACK
| COMMA | SEMI | COLON | COLONCOLON
| DASHGT | QMARKDASHGT | EQGT | BSLASH | DOTDOTDOT | QMARK | AT
| HASHLBRACK
// Operator: arithmetic
| PLUS | DASH | STAR | SLASH | PERCENT | STARSTAR
| PLUSPLUS | DASHDASH
// Operator: comparison
| EQEQ | EQEQEQ | BANGEQ | BANGEQEQ | LTGT
| LT | GT | LTEQ | GTEQ | LTEQGT
// Operator: logical
| AMPAMP | PIPEPIPE | BANG
| And | Or | Xor | QMARKQMARK
// Operator: bitwise
| AMP | PIPE | CARET | TILDE | LTLT | GTGT
// Operator: assignment
| EQ
| PLUSEQ | DASHEQ | STAREQ | SLASHEQ | PERCENTEQ | STARSTAREQ
| DOTEQ | QMARKQMARKEQ
| AMPEQ | PIPEEQ | CARETEQ | LTLTEQ | GTGTEQ
// Operator: string concat
| DOT
=> HalsteadType::Operator,
// Operands: identifiers and literals.
// `String`/`String2`/`String3` (single-quoted) and
// `Nowdoc` never interpolate and are always counted as
// one operand each.
Name | Name2 | VariableName | DynamicVariableName
| Integer | Float | Float2
| String | String2 | String3
| Nowdoc
| Boolean | Null | Null2
| NamedType | OptionalType | UnionType | IntersectionType
| DisjunctiveNormalFormType | BottomType
| PrimitiveType | CastType
| QualifiedName | RelativeName | NamespaceName
| Int | Bool | Array | Object
=> HalsteadType::Operand,
// `EncapsedString` (double-quoted), `Heredoc`, and
// `ShellCommandExpression` (backticks) count as one
// operand when inert. When they carry a `$var`,
// `${name}`, or `{$expr}` interpolation child, those
// inner expressions are already walked and classified as
// operands in their own right; counting the wrapping
// literal too would double-count their contribution to
// `N2` (issue #184, same pattern as #180 for Elixir/Bash
// and #183 for C#). `ShellCommandExpression` was previously
// omitted entirely (issue #288), so backtick literals
// contributed no Halstead operand at all even when inert.
EncapsedString | Heredoc | ShellCommandExpression => {
// PHP's interpolation children appear directly on the
// wrapping literal, except `Heredoc`, which holds them
// one level down under a single `heredoc_body` child —
// so the descend below mirrors the original
// `php_string_has_interpolation` two-level walk.
const PHP_INTERP_KINDS: &[u16] = &[
// `"$name"` → direct `variable_name` child.
VariableName as u16,
// `"${name}"` → direct `dynamic_variable_name` child.
DynamicVariableName as u16,
// `"$arr[0]"` → direct `subscript_expression` child.
// The grammar gives this kind three numeric aliases.
SubscriptExpression as u16,
SubscriptExpression2 as u16,
SubscriptExpression3 as u16,
// `"$obj->prop"` → direct `member_access_expression`
// child. PHP's bare-interpolation syntax does not
// support `?->` (nullsafe) or `::` (scope), so only
// member-access aliases need handling here; nullsafe /
// scope forms always go through the `{ … }` wrapper.
MemberAccessExpression as u16,
MemberAccessExpression2 as u16,
MemberAccessExpression3 as u16,
// `"{$expr}"` → anonymous `{` (LBRACE) opens the
// complex-interpolation wrapper whose body is an
// arbitrary expression; the brace appears as a direct
// child.
LBRACE as u16,
];
// Single pass over the direct children: an interpolation
// child on the literal itself (EncapsedString /
// ShellCommandExpression) OR, for Heredoc, one nested under
// its `heredoc_body` child. Folding both checks into one walk
// avoids re-scanning the children a second time through
// `string_operand_type` for an inert heredoc.
let has_interp = node.children().any(|c| {
let kind = c.kind_id();
PHP_INTERP_KINDS.contains(&kind)
|| (kind == HeredocBody as u16 && c.wraps_any(PHP_INTERP_KINDS))
});
if has_interp {
HalsteadType::Unknown
} else {
HalsteadType::Operand
}
}
_ => HalsteadType::Unknown,
}
}
get_operator!(Php);
}