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
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
//! `Getter` implementation for C++.
#![allow(clippy::wildcard_imports, clippy::enum_glob_use)]
use super::*;
use crate::c_declarator::declarator_name;
// `GT2` is the template closer's own symbol, which the parser folds
// onto `GT` (`cpp_template_closer_alias_never_reaches_kind_id`); it is
// listed defensively.
const GENERIC_ANGLES: GenericAngleKinds = GenericAngleKinds {
lists: &[
Cpp::TemplateArgumentList as u16,
Cpp::TemplateParameterList as u16,
],
openers: &[Cpp::LT as u16],
closers: &[Cpp::GT as u16, Cpp::GT2 as u16],
is_misparse: never_misparsed,
};
impl Getter for CppCode {
fn get_func_space_name<'a, 'tree>(
node: &Node<'tree>,
code: &'a [u8],
_ancestors: Ancestors<'tree, '_>,
) -> Option<&'a str> {
// Issue #285 contract: every `Cpp::FunctionDefinition*` alias
// must be enumerated here AND in `get_space_kind` below AND
// in `is_func` / `is_func_space` (see `src/checker.rs`).
// The aliased kind_ids 489/491/494 are not emitted by the
// currently pinned `tree-sitter-cpp` parse tables, so a
// dropped variant would silently fall through to the
// `_ => name-field` arm and yield the wrong name (or `None`).
match node.kind_id().into() {
Cpp::FunctionDefinition
| Cpp::FunctionDefinition2
| Cpp::FunctionDefinition3
| Cpp::FunctionDefinition4 => {
if let Some(op_cast) = node.first_child(|id| Cpp::OperatorCast == id) {
return node_text(code, &op_cast);
}
// The name is not a child of the function node — see
// `crate::c_declarator` (#1208).
if let Some(name) = declarator_name::<Self>(node)
&& matches!(
name.kind_id().into(),
Cpp::TypeIdentifier
| Cpp::Identifier
| Cpp::FieldIdentifier
| Cpp::DestructorName
| Cpp::OperatorName
| Cpp::QualifiedIdentifier
| Cpp::QualifiedIdentifier2
| Cpp::QualifiedIdentifier3
| Cpp::QualifiedIdentifier4
| Cpp::TemplateFunction
| Cpp::TemplateMethod
)
{
return node_text(code, &name);
}
}
_ => {
if let Some(name) = node.child_by_field_name("name") {
return node_text(code, &name);
}
}
}
None
}
fn get_space_kind(node: &Node) -> SpaceKind {
use Cpp::*;
// Issue #285 contract: keep every `FunctionDefinition*` alias
// listed here — see the comment above `get_func_space_name`.
match node.kind_id().into() {
FunctionDefinition | FunctionDefinition2 | FunctionDefinition3
| FunctionDefinition4 => SpaceKind::Function,
// Bodiless specifiers open no space (#1555); see
// `cpp_specifier_has_body`.
StructSpecifier if crate::checker::cpp_specifier_has_body(node) => SpaceKind::Struct,
ClassSpecifier if crate::checker::cpp_specifier_has_body(node) => SpaceKind::Class,
NamespaceDefinition => SpaceKind::Namespace,
TranslationUnit => SpaceKind::Unit,
_ => SpaceKind::Unknown,
}
}
fn get_op_type<'a>(node: &Node<'a>, ancestors: Ancestors<'a, '_>) -> TokenRole {
use Cpp::*;
if GENERIC_ANGLES.is_closer(node, ancestors) {
return TokenRole::Unknown;
}
// `LPAREN2` here (and the `LBRACK2`/`LBRACK3` aliases in the
// Elixir/Ruby impls) is a defensive arm, not an active one: every
// grammar's `public_symbol_map` collapses the second-alias opener
// to its base before `Node::kind_id()` (`ts_node_symbol`) returns,
// so `kind_id()` never yields the alias id and the arm cannot fire
// for real source. It guards against a future grammar bump that
// drops that collapse — at which point the alias would also need
// folding to its pair glyph in `get_operator_id_as_str`. The
// invariant is pinned by `second_alias_opener_collapses_to_base_kind_id`
// in `metrics/halstead.rs` (issue #768).
match node.kind_id().into() {
// Raw-string delimiter punctuation. A `raw_string_literal`
// carries its `R"(` opener as a bare `LPAREN` child — the
// kind id a call or a grouping uses — so `R"(raw)"`
// reported a `()` operator with no call in the source
// (#1314, the C++ sibling of Elixir #1256 and Ruby/Perl
// #1312). The literal is already an operand (below), so the
// delimiter is suppressed exactly when its parent is that
// node — the compound-leaf guard of grammar-dispatch
// section 5. Verified across `R"(x)"`, the custom-delimiter
// `R"tag(x)tag"` (which adds a `raw_string_delimiter` but
// keeps the same `(`), and the `LR` / `u8R` prefixed forms;
// the closing `)` needs no arm because #695 dropped every
// closer from the operator set.
//
// Parent, not ancestor. That distinction is unobservable
// here — `raw_string_content` is a leaf, so no `LPAREN` is
// ever a deeper descendant of a raw string — but parent
// scoping is correct by construction and keeps this arm the
// same shape as its siblings. `mozcpp` carries the twin.
LPAREN
if ancestors.parent_has_kind(node, RawStringLiteral as u16) =>
{
TokenRole::Unknown
}
DOT | DOTSTAR | LPAREN | LPAREN2 | COMMA | STAR | GTGT | COLON | SEMI | Return
| Break | Continue | If | Else | Switch | Case | Default | For | While | Goto | Do
| Delete | New | Try | Try2 | Catch | Throw | EQ | AMPAMP | PIPEPIPE | DASH
| DASHDASH | DASHGT | DASHGTSTAR | PLUS | PLUSPLUS | SLASH | PERCENT | PIPE | AMP
| LTLT | TILDE | LT | LTEQ | EQEQ | BANGEQ | GTEQ | GT | GT2 | LTEQGT | PLUSEQ
| DASHEQ | BANG | STAREQ | SLASHEQ | PERCENTEQ | GTGTEQ | LTLTEQ | AMPEQ | CARET
| CARETEQ | PIPEEQ | LBRACK | LBRACE | QMARK | COLONCOLON | PrimitiveType
| TypeSpecifier | Sizeof
// The ISO alternative tokens ([lex.digraph]): `not`, `compl`,
// `and`, `or`, `bitand`, `bitor`, `xor`, `not_eq`, `and_eq`,
// `or_eq`, `xor_eq` spell `!`, `~`, `&&`, `||`, `&`, `|`, `^`,
// `!=`, `&=`, `|=`, `^=`. The grammar gives each a kind of its
// own, so without these they fell into `Unknown` and `b and c`
// billed no operator at all (#1522). Each kind occurs only in
// productions its symbol also occurs in, so it needs no gate
// the symbol lacks. Keyed by `kind_id` like every operator, so
// `and` and `&&` are two distinct operators in n1: the `ops`
// vocabulary names operators by kind, and folding the two in
// the metric alone would make the two stores disagree.
| Not | Compl | And | Or | Bitand | Bitor | Xor | NotEq | AndEq | OrEq | XorEq
// A `sized_type_specifier` carries its `unsigned`/`signed`/`long`/
// `short` modifiers as bare keyword tokens, not as `primitive_type`
// children (`unsigned int` is `unsigned` + `primitive_type int`;
// `signed long` and `long long` have no `primitive_type` at all).
// Without these arms the modifiers fell into `Unknown` and were
// dropped, so `unsigned int` collapsed to just `int` and a standalone
// `signed long` contributed nothing to n1/N1 (issue #466). Each
// modifier has a distinct kind_id, so keying by kind_id (the default
// `operators` store) keeps them distinct in n1 while `long long`'s
// two `long` tokens correctly fold to one n1 entry but two N1 hits.
| Signed | Unsigned | Long | Short
// An overloaded operator's name (#1296). `operator +` already
// billed its `+` through the punctuation kind, but the grammar
// spells four overloadable names as whole tokens of their own
// — `[]`, `()`, the `""` of a user-defined literal and
// `co_await` — and none was in either arm, so
// `operator[]` and `operator()` billed nothing for the name
// that tells them apart. `Operator` is the `operator` keyword
// itself, which C# bills the same way. `CoAwait` is also the
// keyword of `co_await expr`, an operator there too. Each
// token occurs only under `operator_name`, `operator_cast` or
// `co_await_expression`, none of which is classified, so
// nothing bills one twice (grammar-dispatch section 5).
//
// `LPARENRPAREN` / `LBRACKRBRACK` are kinds of their own, not
// the `LPAREN` / `LBRACK` of a call or subscript, so declaring
// `operator[]` and applying `s[1]` are two `n1` entries, which
// `bca ops` renders `operator[]` and `[]` (see
// `get_operator_id_as_str`).
| LPARENRPAREN | LBRACKRBRACK | DQUOTEDQUOTE | CoAwait | Operator
// `co_return` and `co_yield` are the coroutine spellings of
// `return` and of handing a value back, so they bill as
// `return` does (#1547). Each is the keyword leaf of a
// `co_return_statement` / `co_yield_statement`, which no arm
// classifies, so nothing bills one twice.
| CoReturn | CoYield
// The type-declaration keywords, billed as Rust bills
// `struct` and C# its `class` / `struct` / `enum` (#1552).
// Each is the keyword leaf of a `class_specifier` /
// `struct_specifier` / `union_specifier` / `enum_specifier`,
// none of which is classified, so a specifier bills its
// keyword once — and `enum class` bills both of its two.
// The same leaf heads an elaborated type (`struct S s;`),
// where it is billed too, as `int` is in `int s;`.
//
// #1557 adds the remaining declaration keywords: `namespace`,
// `template`, `typename`, `typedef` and `using`, each the
// leaf of an unclassified wrapper (`namespace_definition`,
// `template_declaration`, `type_parameter_declaration`,
// `type_definition`, `alias_declaration` /
// `using_declaration`). A template-parameter `class` bills
// with them: it is the synonym of `typename`, so the two
// spellings of `template <class T>` still score alike, now
// both billed.
| Class | Struct | Union | Enum | Namespace | Template | Typename | Typedef | Using
=> TokenRole::Operator,
// `CharLiteral` — the full derivation lives on the same arm
// in `src/getter/c.rs` (#1316): the wrapper is the only
// classified node in a character literal, so it bills one
// operand per literal, keyed by text, and `Checker::is_string`
// deliberately stays without a `CharLiteral` arm.
//
// `This` was in *neither* arm until #1361, so a C++ `this`
// contributed nothing at all: not an operator and not an
// operand. Same shape as #1316, opposite of the #1351-#1355
// wrapper/leaf over-counts — the fix adds an arm rather than
// deleting one.
//
// Operand, not operator, on three grounds. **Structure**:
// `field_expression` is `<receiver> -> <field>`, and `p->x`
// already bills operands `p` / `x` around the `->` operator.
// Calling `this` an operator would make `this->x` a binary
// operator with one operand, and would score the identical
// AST differently for `this->x` than for `p->x`.
// **Role**: `this` is a pointer *rvalue* and stands exactly
// where a variable stands — `return this;`, `*this`,
// `f(this)`, which would otherwise pass an operator as a
// call argument — while `->` / `.` / `*` are the operators
// already counted acting on it. **Precedent**: eleven of the
// fourteen languages here bill their self-reference as an
// operand (Rust `Zelf`, Ruby `Zelf`, PHP's `$this`
// `variable_name`, the four JS-family `This` arms, and
// Python / ObjC / Groovy / Lua, whose grammars spell it a
// plain `identifier`), and the JS-family arm in
// `src/getter.rs` is the only *reasoned* one among them —
// its `MetaProperty` note calls `this` "one atomic
// operand". Java / C# / Kotlin disagree, but each
// swept `This` in as one entry in a run of keywords — under
// `// Operator: keywords`, `// Operator: other keywords` and
// `// Expression-keyword operators` respectively — grouping
// it by lexical class rather than by Halstead role; that
// three-way split is #1380, deliberately not settled here.
// Keeping C++ an operand also keeps it agreeing with ObjC,
// whose `self` is an operand and whose `.mm` files route to
// this very impl.
//
// Safe against a grammar-dispatch section 5 double count:
// `this` is a childless leaf in every position the grammar
// admits it — `this->x`, `(*this)`, `return this`, `f(this)`,
// the `[this]` and `[=, this]` lambda captures, a
// `decltype(this->x)` trailing return type, and a
// constructor body after a member-initialiser list — and no
// node that *contains* it (`field_expression`,
// `pointer_expression`, `lambda_capture_specifier`,
// `argument_list`) is classified, so it can double-count in
// neither direction. Neither generated enum aliases the rule
// (`This = 215` is the sole variant spelling `"this"` in
// `language_cpp.rs` and `language_mozcpp.rs`), so there is no
// `This2` for a single-variant arm to miss;
// `cpp_this_is_a_childless_unaliased_leaf` pins both facts.
//
// C++23's explicit object parameter (`int g(this S&& self)`)
// does *not* reach this arm: the pinned grammar cannot parse
// it and emits `type_identifier "this"` plus an `ERROR`. That
// spelling is already an operand via `TypeIdentifier`, so the
// construct is unaffected either way.
Identifier | TypeIdentifier | FieldIdentifier | RawStringLiteral | StringLiteral
| CharLiteral | NumberLiteral | True | False | Null | This | DOTDOTDOT => {
TokenRole::Operand
}
// A namespace identifier is an operand only where it
// *names* a namespace; the same kind also spells the
// qualifier in `ns::thing`, which the final arm leaves
// `Unknown` (#1096).
NamespaceIdentifier if ancestors.parent_has_kind(node, NamespaceDefinition as u16) => {
TokenRole::Operand
}
_ => TokenRole::Unknown,
}
}
fn get_operator_spelling<'a>(
node: &Node<'a>,
ancestors: Ancestors<'a, '_>,
) -> Option<&'static str> {
GENERIC_ANGLES.opener_spelling(node, ancestors)
}
// `get_operator!`'s three bracket arms, plus the two overloaded
// operator names (#1296). The grammar names `LPARENRPAREN` /
// `LBRACKRBRACK` `()` / `[]`, the same text a call's and a
// subscript's opener render as, so `bca ops` would list one spelling
// for two `n1` entries. Rendering each as the operator it names keeps
// every listed operator distinct (lesson 4).
fn get_operator_id_as_str(id: u16) -> &'static str {
let typ = id.into();
match typ {
Cpp::LPAREN => "()",
Cpp::LBRACK => "[]",
Cpp::LBRACE => "{}",
Cpp::LPARENRPAREN => "operator()",
Cpp::LBRACKRBRACK => "operator[]",
_ => typ.into(),
}
}
}