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
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
use ;
use crate::*;
// Ruby ABC rules follow the Fitzpatrick paper's spirit, adapted to
// tree-sitter-ruby:
// - Assignments: `assignment` (plain `=`) and `operator_assignment`
// (`+=`, `-=`, `||=`, `&&=`, …). Tree-sitter wraps both forms in a
// dedicated node, so we count one assignment per node and avoid
// double-counting the inner `=` / augmented token.
// - Branches: every Ruby method invocation kind (`Call` / `Call2` /
// `Call3` / `Call4`) plus `super` and `yield`. `yield` is grammar-
// level a "block invocation" but ABC's branch bucket is "message
// pass / function call", so it belongs here. `attr_*` macros are
// `Call3` nodes and are counted as branches like any other call.
// - Conditions: comparison and equality operator tokens emitted inside
// `binary` (`==`, `!=`, `===`, `<`, `>`, `<=`, `>=`, `<=>`,
// `=~`, `!~`) — the `binary` parent is a gate, not a description, since
// every one of those tokens also names an operator method in a `def`
// and `<` also spells a superclass clause (#1280) — plus the
// control-flow arms that the Fitzpatrick rules
// list — the named clause nodes `Else` / `Elsif` / `When` (see
// `ruby_count_when` for a subject-less `case`) and the
// `?` ternary marker, plus `Rescue` (the rescue clause) and rescue
// modifiers. An `if` / `unless` / `while` / `until` predicate pays
// one condition unless another arm already charged it — see
// `ruby_count_condition`; the `Then` clause is an implicit grammar
// wrapper around every `if` / `elsif` body and is NOT counted as a
// separate arm.
// One step of the value peel: the operand a wrapper evaluates to,
// and whether the wrapper itself proves that operand boolean. `None`
// for anything that is not a wrapper this peel descends. The chain
// walker and the slot both descend through this one function, so what
// the slot decides is already paid for and what the walker actually
// counts cannot disagree (#1470; the Kotlin, Groovy
// and C# instances of that disagreement were #1459, #1466 and #1463).
//
// Both spellings of the same negation. `not` and `!` differ in
// precedence but not in meaning, and ABC counts the negation, not the
// parse — testing `BANG` alone scored `if not b` as 0 where `if !b`
// scores 1, and a `not` ternary as 2 where the `!` form scores 4
// (#1182). The operator is read through the grammar's `operator` field
// rather than child(0), matching the ternary slots (#1181). The other
// `unary` operators (`-b`, `~b`, `defined?`) are arithmetic or
// introspection, never a boolean slot's operand, so the peel declines.
//
// A negation names its `operand`; `parenthesized_statements` wraps a
// statement sequence and evaluates to its *last* statement (`(x; y)` is
// `y`), so the peel reads its `last_operand`, as Perl's peel does for
// a list. Both are read by role because a comment may sit before the
// operand — `(# c` or `! # c` — and a positional or first-named-child
// read handed the comment to the slot (#1455).
//
// `begin … end` evaluates to its last statement exactly as `(…)` does,
// and an assignment to its `right` value (#1520). Both are peeled so
// `ruby_condition_scores_itself` sees the comparison inside
// `if begin a > 1 end` / `if (y = a > 1)` — without that, the slot
// would pay a second time for a decision the comparison token already
// counted. A `begin` whose last child is a `rescue` / `else` / `ensure`
// clause peels to that clause, which is no wrapper, so the peel stops.
// Scores one boolean slot — the predicate of every `if` / `unless` /
// `while` / `until` form (block and modifier), a `case … in` guard, a
// ternary condition, each pattern of a subject-less `when`, and each
// operand of an `&&` / `||` chain — as Fitzpatrick's "unary conditional
// expression" (Rule 6 / 7 / 9): one condition, unless another arm
// already charged the same decision (see `count_boolean_slot`).
//
// The slot once paid only for a predicate that peeled down to a fixed
// list of terminal kinds, so `if Foo::Bar`, `if self`, `if -x`,
// `if defined?(x)`, `if (y = x)`, `if x + 1` and `g if begin b end` each
// scored 0 against a cyclomatic decision of 1, while the same expression
// as a subject-less `when` scored 1 (#1520); the chain walker kept that
// list until #1529, so `a && self` scored one below `a && b`. #1526
// moved the rule into `count_boolean_slot` and ported it to every
// sibling.
// The comparison / equality tokens that are one condition each inside a
// `binary`. Shared by the token arm in `compute` and by
// `ruby_condition_scores_itself`, which must agree with that arm on
// exactly which operators it charges.
// Whether another arm of `compute` already charges `expr` (already
// peeled through the wrappers `ruby_wrapper_operand` descends, which add
// no decision of their own) as a condition. True for a comparison (the
// token arm), a
// one-line pattern test (the `TestPattern` arm), a ternary (the `?` arm
// plus `ruby_walk_ternary`) and an `&&` / `||` / `and` / `or` chain:
// the chain walker scores each operand through `ruby_count_condition`,
// so every operand either pays there or is one of these, and a chain
// always carries at least one condition of its own.
// Scores one `when` clause (#1453, transferring #1421's Kotlin rule).
// Both `case` shapes contribute the single decision cyclomatic counts per
// clause, but they pay for it in different places.
//
// A subject-ful clause (`case x; when 1`) lists a *pattern* matched with
// `pattern === x`: that comparison is written nowhere in the source, so
// the clause itself is the condition.
//
// A subject-less clause (`case; when x > 5`) lists an ordinary boolean
// expression, evaluated exactly as an `if` predicate. When that
// expression is a comparison, chain, pattern test or ternary, its own arm
// has already counted it, and the clause's former blanket +1 counted the
// same decision twice: `case; when x > 5 then 1; else 0; end` scored 3
// against its `if` analogue's 2. Otherwise — a bare `when b`, `(b)`,
// `!b`, `x.even?` — nothing else sees it and the clause still pays.
//
// A clause may list several patterns (`when a, b`): an implicit `||`, so
// each pattern is one operand of that chain and is scored as Rule 9
// scores an `||` operand — once, unless its own arm already counted it.
// `when a, b` / `when a, x > 1` / `when x > 1, x < -1` therefore each
// read 2, exactly their `if a || b` analogues, and the order the patterns
// are written in cannot matter. Cyclomatic scores the clause as one
// decision, so these sit above `conditions == cyclomatic - 1`, as the
// `||` analogues do.
//
// A `when` whose parent is not a `case` occurs only under error
// recovery; it keeps the per-clause count rather than guessing.
// Phase-2B (issues #403 / #1102 / #1161): a Ruby ternary's condition and
// its two branch operands are each a Fitzpatrick Rule 9 unary condition,
// exactly as `cpp_walk_ternary` counts them for the C family. Without
// this, Ruby scored `a ? !b : !c` as 1 — the `?` token alone — against
// Java's 4.
//
// Slots are addressed by grammar field, per
// `.claude/rules/grammar-dispatch.md` item 3.
//
// The condition slot reuses `ruby_count_condition`, so a ternary's
// predicate is classified by exactly the code that classifies an
// `if` / `unless` / `while` / `until` predicate, and the two cannot
// drift.
//
// A branch slot is type-free: it is a boolean slot only when the peel
// proves it one — a `!` / `not` layer. Keying on the `unary` kind instead
// would also take `-b` (the same kind, a different operator), moving the
// control case `(a > 0) ? b : -b` from 2 to 3.