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
//! `Getter` implementation for C#.
#![allow(clippy::wildcard_imports, clippy::enum_glob_use)]
use super::*;
impl Getter for CsharpCode {
fn get_space_kind(node: &Node) -> SpaceKind {
use Csharp::*;
// `EnumDeclaration` maps to `SpaceKind::Class` for cross-language
// parity with Java/PHP/Groovy (issue #429): a C# enum opens a
// FuncSpace via `is_func_space`, so it must classify here too or
// it falls through to `_ => SpaceKind::Unknown`.
// A bodied indexer (#464) or property (#472) defers to its
// `accessor_declaration` children; only the accessor-less
// expression-bodied form opens a Function space directly. Keep this
// gate in lockstep with `CsharpCode::is_func` / `is_func_space` so the
// walker and the space-kind classifier agree on which nodes promote.
if matches!(
node.kind_id().into(),
IndexerDeclaration | PropertyDeclaration
) {
return if crate::checker::csharp_member_has_accessors(node) {
SpaceKind::Unknown
} else {
SpaceKind::Function
};
}
match node.kind_id().into() {
ClassDeclaration | StructDeclaration | RecordDeclaration | EnumDeclaration => {
SpaceKind::Class
}
InterfaceDeclaration => SpaceKind::Interface,
MethodDeclaration
| ConstructorDeclaration
| DestructorDeclaration
| LocalFunctionStatement
| LambdaExpression
| AnonymousMethodExpression
| AccessorDeclaration
| OperatorDeclaration
| ConversionOperatorDeclaration => SpaceKind::Function,
CompilationUnit => SpaceKind::Unit,
_ => SpaceKind::Unknown,
}
}
fn get_op_type<'a>(node: &Node<'a>, ancestors: Ancestors<'a, '_>) -> TokenRole {
use Csharp::*;
match node.kind_id().into() {
// Control-flow keywords
If | Else | Switch | Case | Default | Try | Catch | Finally | Throw
| Return | Yield | Break | Continue | Goto | For | Foreach | While | Do
// Declaration / namespace keywords
| Class | Struct | Interface | Enum | Record | Delegate | Namespace | Using
// Modifiers
| Public | Private | Protected | Internal | Static | Abstract | Virtual
| Override | Sealed | Partial | Readonly | Const | Extern | Unsafe
| Volatile | Async | Required | File | New | Fixed | Implicit | Explicit
// Expression-keyword operators
| Await | Is | As | Typeof | Sizeof | Checked | Unchecked | Ref | Out | In
| Params | Lock | Stackalloc | Where | With | When | Operator
| Scoped | Not | And | Or
// Property/event accessor keywords
| Get | Set | Init | Add | Remove
// Structural punctuation
| LBRACE | LBRACK | LPAREN | COMMA | SEMI | COLON | COLONCOLON | DOT
| DOTDOT | EQGT | DASHGT | QMARK
// Arithmetic / comparison / logical / bitwise / assignment operators
| EQ | EQEQ | BANGEQ | LT | GT | LTEQ | GTEQ
| PLUS | DASH | STAR | SLASH | PERCENT
| AMP | PIPE | CARET | TILDE | BANG
| AMPAMP | PIPEPIPE | QMARKQMARK
| LTLT | GTGT | GTGTGT
| PLUSPLUS | DASHDASH
| PLUSEQ | DASHEQ | STAREQ | SLASHEQ | PERCENTEQ
| AMPEQ | PIPEEQ | CARETEQ | LTLTEQ | GTGTEQ | GTGTGTEQ | QMARKQMARKEQ
// Predefined / primitive types
| PredefinedType
=> TokenRole::Operator,
// `boolean_literal: choice('true', 'false')` wraps the
// keyword leaf, so a literal reaches the walker twice;
// listing both kinds inflated `N2` by one per literal while
// the text-keyed `n2` hid it (#1253). The leaf cannot simply
// be dropped: the overloadable-operator list emits a bare
// `true` / `false` under `operator_declaration` — the
// grammar's only unwrapped position — and suppressing that
// would leave two such declarations with identical Halstead
// vocabularies whenever their bodies match. That position
// keeps its pre-#1253 `Operand` classification here; whether
// an overloaded operator's *name* is better counted as an
// operator, as `operator +` already is, is #1296.
True | False => match ancestors.parent(node).map(|p| p.kind_id().into()) {
Some(BooleanLiteral) => TokenRole::Unknown,
_ => TokenRole::Operand,
},
// `this` is a self-reference everywhere (`this.x`,
// `this[i]`, `: this(1)`, `f(this)`) except directly under
// an `indexer_declaration`, where the keyword *names* the
// member being declared rather than denoting a value —
// `public int this[int i] { … }`. That position keeps its
// operator classification alongside the `operator` keyword
// of an overload declaration, which this match already
// bills as an operator (#1380). The extension-method
// receiver (`static void M(this Foo f)`) is a childless
// `modifier` node, kind 249, not this kind at all; it is
// classified — as an operand, so the two positions agree —
// by `get_op_type_with_code` below (#1418).
//
// Java's receiver parameter (`void m(J J.this)`) is the
// other declarator use of the keyword in this workspace and
// `java.rs` calls it an operand — a parameter *name* is an
// operand in every language here, where a member named by a
// keyword belongs with `operator +`.
This => match ancestors.parent(node).map(|p| p.kind_id().into()) {
Some(IndexerDeclaration) => TokenRole::Operator,
_ => TokenRole::Operand,
},
// Operands: identifiers and literals. `NullLiteral` is a
// childless leaf, so it needs no such guard.
//
// `QualifiedName` (`System.Text`), `GenericName`
// (`List<int>`) and `AliasQualifiedName` (`global::Foo`)
// were listed here until #1263. Each is a container whose
// every part the walker already reaches and classifies —
// the identifier leaves as operands, `.` / `::` / `<` / `>`
// and any `predefined_type` argument as operators — so
// listing the container too billed one occurrence twice
// (grammar-dispatch section 5). Probed with `bca ops`:
// dropping them leaves `System`+`Text`+`.`,
// `List`+`<`+`int`+`>`, and `global`+`::`+`Foo` intact.
//
// `Base` joined this list in #1380 for the same structural
// reason `This` did: it is the receiver of `base.M()` /
// `base[i]` / `: base(x)`. The `base_list` container that
// spells inheritance (`class D : B`) holds the base type's
// identifier and no `base` keyword, so nothing bills this
// text twice.
Identifier | Base
| IntegerLiteral | RealLiteral | BooleanLiteral | NullLiteral
| CharacterLiteral | StringLiteral | VerbatimStringLiteral | RawStringLiteral
=> TokenRole::Operand,
// `$"..."` counts as one operand when inert. When it carries
// any `Interpolation` child the inner expressions are
// already walked and classified as operands; counting the
// wrapping literal too would double-count the inner
// identifiers' contribution to `N2` (issue #183, same
// pattern as #180 for Elixir/Bash).
InterpolatedStringExpression => {
Self::string_operand_type(node, &[Interpolation as u16])
}
_ => TokenRole::Unknown,
}
}
// The parameter-position spelling of six keywords (#1418). The
// grammar's `_parameter_type_with_modifiers` rule aliases the bare
// tokens `this scoped ref out in readonly` to `$.modifier`, so in
// that one position each is a **childless** `modifier` node rather
// than the kind its own arm above lists. Nothing billed them, so an
// extension receiver, a `ref` parameter and an `out` parameter each
// contributed to neither Halstead half.
//
// A blanket `Modifier` arm is not available: the `modifier` *rule*
// is a wrapper around a `public` / `static` / `async` /
// field-`readonly` keyword leaf this match already classifies, so
// listing the wrapper too would bill those twice (grammar-dispatch
// section 5). The gate is child-presence, which section 6 prescribes
// over deleting either side, and which
// `csharp_is_aliased_modifier` reads as "aliased bare token" rather
// than "parameter modifier" — see its doc for the second alias site.
//
// `Checker::is_bare_param` deliberately refuses to infer from
// `child_count() == 0`, and this is the case that refusal excludes
// rather than a contradiction of it: there, childlessness would be
// a *guess* that a MISSING or zero-width ERROR node is a real
// parameter. Here the grammar aliases a bare token onto this named
// kind, so having no children is the shape the alias produces, not
// evidence about a broken parse.
//
// The role follows the text, never the position: each keyword keeps
// the role its own kind carries elsewhere in this match, so
// `M(ref Foo f)` and `f(ref x)` agree, and `this` stays the operand
// #1380 made it. That decision's `IndexerDeclaration` carve-out
// cannot arise here — an indexer declarator is not a parameter, and
// this arm is reached only from `_parameter_type_with_modifiers`.
//
// Reading the bytes needs this spelling, which is the one the walk
// calls (`compute_halstead` -> `get_op_type_with_code`), so the
// classification reaches every count; `get_op_type` above stays the
// byte-less answer for the callers that have no source to offer
// (grammar-dispatch section 7).
//
// `CsharpCode::is_primitive` routes the operator half through the
// lexeme-keyed map, because these five keywords now have two kind
// spellings for one operator; its comment carries that argument.
fn get_op_type_with_code<'a>(
node: &Node<'a>,
code: &[u8],
ancestors: Ancestors<'a, '_>,
) -> TokenRole {
if !crate::checker::csharp_is_aliased_modifier(node) {
return Self::get_op_type(node, ancestors);
}
match node_text(code, node) {
Some("this") => TokenRole::Operand,
Some("ref" | "out" | "in" | "scoped" | "readonly") => TokenRole::Operator,
// Live, not merely defensive: `_lambda_expression_init` and
// `anonymous_method_expression` alias a bare `static` /
// `async` onto `modifier` too, so `static (int x) => x`
// arrives here. Leaving those unclassified is what they
// already were before #1418, and closing that gap is a
// decision about lambdas rather than about parameters — the
// *declaration* spelling of both keywords is billed through
// its leaf, so they are unbilled only in this one position.
// A grammar bump that adds a seventh parameter spelling
// lands here as well, unclassified rather than billed as
// whichever arm happened to be last.
_ => TokenRole::Unknown,
}
}
get_operator!(Csharp);
}