big-code-analysis 2.4.0

Tool to compute and export code metrics
Documentation
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
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
//! Two spellings of one Elixir program score alike (#1574).
//!
//! Elixir writes a construct's sections three ways: a `do … end` block, a
//! trailing keyword list (`if c, do: 1, else: 2`), and the same list in
//! brackets (`if(c, [do: 1, else: 2])`), which the compiler reads as the
//! identical last argument. And it applies a short-circuit operator either
//! as an operator (`a || b`) or as a `Kernel` remote call
//! (`Kernel.||(a, b)`), which expands the same macro.
//!
//! Every group pairs the spellings with a twin whose score predates the
//! fix — the block form, or the operator form — and that is the oracle:
//! the twin's numbers are asserted outright, so a group cannot pass by
//! every spelling drifting together. The one twin that moved is the
//! `with … else` block form's cognitive score, decided in
//! [`elixir_with_else_scores_like_case`] against `case`.

use crate::test_support::metrics_verbatim;
use crate::*;

/// `[cyclomatic, cyclomatic_modified, abc conditions, cognitive]` sums of
/// the root space; both cyclomatic tiers carry a base of 1.
#[cfg(feature = "elixir")]
type Scores = [u64; 4];

#[cfg(feature = "elixir")]
fn measure(source: &str) -> Scores {
    let m = metrics_verbatim(
        LANG::Elixir,
        format!("{source}\n").as_bytes(),
        MetricsOptions::default().with_only(&[Metric::Cyclomatic, Metric::Abc, Metric::Cognitive]),
    );
    [
        m.cyclomatic.cyclomatic_sum(),
        m.cyclomatic.cyclomatic_modified_sum(),
        m.abc.conditions_sum(),
        m.cognitive.cognitive_sum(),
    ]
}

/// Asserts `twin` scores `want`, then that every spelling scores the same.
#[cfg(feature = "elixir")]
fn assert_group(twin: &str, want: Scores, spellings: &[&str]) {
    assert_eq!(
        measure(twin),
        want,
        "twin `{twin}` moved; re-derive the group"
    );
    for spelling in spellings {
        assert_eq!(
            measure(spelling),
            want,
            "`{spelling}` must score like `{twin}`"
        );
    }
}

/// A `Kernel` remote call naming `&&` / `||` is the operator: one
/// decision, one ABC slot per operand, one cognitive sequence. Before
/// #1574 only cyclomatic counted it; ABC and cognitive scored 0, and a
/// nested call read as a plain operand, so `a || Kernel.||(b, c)` paid a
/// slot for the inner call that `a || (b || c)` does not.
#[cfg(feature = "elixir")]
#[test]
fn elixir_kernel_remote_call_scores_like_the_operator() {
    assert_group(
        "a || b",
        [2, 2, 2, 1],
        &[
            "Kernel.||(a, b)",
            "Elixir.Kernel.||(a, b)",
            // The pipe passes the piped value as the first operand.
            "a |> Kernel.||(b)",
        ],
    );
    assert_group("a && b", [2, 2, 2, 1], &["Kernel.&&(a, b)"]);
    // A nested chain pays its own operands and no slot as an operand.
    assert_group(
        "a || (b || c)",
        [3, 3, 3, 1],
        &[
            "Kernel.||(a, Kernel.||(b, c))",
            "a || Kernel.||(b, c)",
            "Kernel.||(a, b || c)",
        ],
    );
    // A change of operator opens a second cognitive sequence.
    assert_group(
        "a && (b || c)",
        [3, 3, 3, 2],
        &["Kernel.&&(a, Kernel.||(b, c))"],
    );
    // Piping the call's *result* passes no operand to it.
    assert_group("(a || b) |> f()", [2, 2, 2, 1], &["Kernel.||(a, b) |> f()"]);
    // As an `if` predicate the call scores itself, as the chain does.
    assert_group(
        "if a || b, do: 1",
        [3, 3, 2, 2],
        &["if Kernel.||(a, b), do: 1"],
    );
}

/// `Kernel.and` / `Kernel.or` expand the same short-circuit macros as
/// `a and b` / `a or b`, but the grammar names them with a plain
/// `identifier` rather than an operator token, so before #1582 they
/// scored nothing in any metric. Each word form keys the cognitive
/// sequence its symbol does, so a mixed run of `or` and `||` is one
/// sequence and a change of operation opens a second.
#[cfg(feature = "elixir")]
#[test]
fn elixir_kernel_word_operator_scores_like_the_operator() {
    assert_group(
        "a and b",
        [2, 2, 2, 1],
        &[
            "Kernel.and(a, b)",
            "Elixir.Kernel.and(a, b)",
            "a |> Kernel.and(b)",
        ],
    );
    assert_group("a or b", [2, 2, 2, 1], &["Kernel.or(a, b)"]);
    assert_group(
        "a or (b or c)",
        [3, 3, 3, 1],
        &["Kernel.or(a, Kernel.or(b, c))", "a or Kernel.||(b, c)"],
    );
    assert_group(
        "a and (b or c)",
        [3, 3, 3, 2],
        &["Kernel.and(a, Kernel.or(b, c))"],
    );
    assert_group(
        "if a and b, do: 1",
        [3, 3, 2, 2],
        &["if Kernel.and(a, b), do: 1"],
    );
}

/// A negation decides nothing in any spelling, applied or called on
/// `Kernel`; a user module's `and/2` is an ordinary function; and a
/// capture or a bare reference only names `Kernel`'s.
#[cfg(feature = "elixir")]
#[test]
fn elixir_kernel_negation_and_foreign_word_operator_decide_nothing() {
    assert_group(
        "not x",
        [1, 1, 0, 0],
        &["!x", "Kernel.not(x)", "Kernel.!(x)"],
    );
    assert_group(
        "Foo.foo(a, b)",
        [1, 1, 0, 0],
        &[
            "Foo.and(a, b)",
            "Foo.or(a, b)",
            "&Kernel.and/2",
            "Kernel.and",
        ],
    );
}

/// A user module's `||/2` is an ordinary function, and a capture only
/// names `Kernel`'s: neither decides anything in any metric.
#[cfg(feature = "elixir")]
#[test]
fn elixir_non_kernel_remote_call_decides_nothing() {
    assert_group(
        "Foo.foo(a, b)",
        [1, 1, 0, 0],
        &["Foo.||(a, b)", "a |> Foo.||(b)", "&Kernel.||/2"],
    );
    // In a slot, neither a user module's `||` nor a `Kernel` function
    // that is no operator scores itself: each pays the slot as a call.
    assert_group(
        "if foo(a), do: 1",
        [2, 2, 1, 1],
        &["if Kernel.foo(a), do: 1", "if Foo.||(a, b), do: 1"],
    );
}

/// `if` / `unless` with an `else`, in every spelling of the keyword list,
/// including a trailing comma and quoted keys (`"do":` is the atom
/// `:do`). Before #1574 the keyword forms lost cognitive's `else` +1,
/// and the bracketed and quoted forms lost ABC's `else` condition too.
#[cfg(feature = "elixir")]
#[test]
fn elixir_if_else_scores_alike_in_every_spelling() {
    assert_group(
        "if c do 1 else 2 end",
        [2, 2, 2, 2],
        &[
            "if(c, do: 1, else: 2)",
            "if(c, [do: 1, else: 2])",
            "if(c, [do: 1, else: 2,])",
            "if(c, \"do\": 1, \"else\": 2)",
            "if(c, [\"do\": 1, \"else\": 2])",
        ],
    );
    assert_group(
        "unless c do 1 else 2 end",
        [2, 2, 2, 2],
        &["unless(c, do: 1, else: 2)", "unless(c, [do: 1, else: 2])"],
    );
    // Each `else` belongs to its own `if`, however the two nest.
    assert_group(
        "if a do (if b do 1 else 2 end) else 3 end",
        [3, 3, 4, 5],
        &[
            "if(a, do: if(b, do: 1, else: 2), else: 3)",
            "if(a, [do: if(b, [do: 1, else: 2]), else: 3])",
        ],
    );
}

/// Only the *last* argument, and only a list holding nothing but pairs,
/// is a keyword list: `[do: 1, else: 2]` in first position, `[1, do: …]`
/// and `[]` are ordinary list arguments, so the `if` has no `else`. And
/// `else:` is an ordinary option name to any other function.
#[cfg(feature = "elixir")]
#[test]
fn elixir_list_that_is_not_a_keyword_list_has_no_sections() {
    assert_group("[1]", [1, 1, 0, 0], &["[else: 1]", "[do: 1, else: 2]"]);
    assert_group(
        "if(c, x)",
        [2, 2, 1, 1],
        &[
            "if([do: 1, else: 2], c)",
            "if(c, [1, do: 1, else: 2])",
            "if(c, [])",
        ],
    );
    assert_group(
        "f(x, y)",
        [1, 1, 0, 0],
        &["f(x, else: 1)", "f(x, [else: 1])"],
    );
    assert_group(
        "Foo.bar(x, y)",
        [1, 1, 0, 0],
        &["Foo.bar(x, else: 1)", "Foo.bar(x, [else: 1])"],
    );
    // An interpolated key has no static name. Only an ordinary call
    // takes one validly, and no ordinary call has sections, so this row
    // exercises the reading rather than discriminating it.
    assert_group("f(x, y)", [1, 1, 0, 0], &["f(x, \"else#{y}\": 1)"]);
}

/// Clause constructs: the bracketed `cond` lost its free `true ->`
/// default, because the `cond` lookup climbed a fixed number of parents.
#[cfg(feature = "elixir")]
#[test]
fn elixir_clause_constructs_score_alike_in_every_spelling() {
    assert_group(
        "case x do 1 -> :a; 2 -> :b end",
        [3, 2, 2, 1],
        &[
            "case(x, do: (1 -> :a; 2 -> :b))",
            "case(x, [do: (1 -> :a; 2 -> :b)])",
        ],
    );
    assert_group(
        "cond do x > 1 -> :a; true -> :b end",
        [2, 2, 1, 1],
        &[
            "cond(do: (x > 1 -> :a; true -> :b))",
            "cond([do: (x > 1 -> :a; true -> :b)])",
        ],
    );
}

/// `try`'s `rescue:` / `catch:` pay +nesting and its `else:` +1 in the
/// keyword forms, as their blocks do; before #1574 the keyword `try`
/// scored cognitive 0. A `def` body is an implicit `try` and takes the
/// same sections.
#[cfg(feature = "elixir")]
#[test]
fn elixir_try_sections_score_alike_in_every_spelling() {
    assert_group(
        "try do f() rescue e in RuntimeError -> e catch :x -> 1 end",
        [3, 2, 2, 2],
        &[
            "try(do: f(), rescue: (e in RuntimeError -> e), catch: (:x -> 1))",
            "try([do: f(), rescue: (e in RuntimeError -> e), catch: (:x -> 1)])",
        ],
    );
    assert_group(
        "try do f() rescue _ -> 1 else x -> x end",
        [2, 2, 1, 2],
        &[
            "try(do: f(), rescue: (_ -> 1), else: (x -> x))",
            "try([do: f(), rescue: (_ -> 1), else: (x -> x)])",
        ],
    );
    // A handler raises nesting, not just +1: the `if` inside it costs 2.
    assert_group(
        "try do f() rescue _ -> (if a do 1 end) end",
        [2, 3, 1, 3],
        &[
            "try(do: f(), rescue: (_ -> if(a, do: 1)))",
            "try([do: f(), rescue: (_ -> if(a, do: 1))])",
        ],
    );
    assert_group(
        "try do f() catch _ -> (if a do 1 end) end",
        [2, 3, 1, 3],
        &["try(do: f(), catch: (_ -> if(a, do: 1)))"],
    );
    assert_group(
        "def f() do\n  g()\nrescue\n  e -> e\nend",
        [3, 2, 1, 1],
        &["def f(), do: g(), rescue: (e -> e)"],
    );
}

/// A `with`'s `else` holds the clauses that dispatch over the value a
/// `<-` failed to match: the arms of a `case` in all but spelling. A
/// `case`'s arms add no cognitive cost past the +nesting its container
/// pays, and Ruby draws the same line for the `else` of `case/when`
/// (#451), so the block form's `else` token adds nothing either; the
/// keyword form already scored that (#1574). An `if`'s `else` is a
/// conditional's alternative and keeps its +1 (see the nested row).
///
/// The bracketed form also lost its modified tier: the `else` lookup
/// read only a bare keyword list, so the dispatching `else` looked absent.
#[cfg(feature = "elixir")]
#[test]
fn elixir_with_else_scores_like_case() {
    // The oracle: the same dispatch as one `case`, cognitive 1.
    assert_eq!(
        measure("case g(x) do {:ok, a} -> a; {:error, e} -> e; :z -> 0 end")[3],
        1
    );
    assert_group(
        "with({:ok, a} <- g(x), do: a, else: ({:error, e} -> e; :z -> 0))",
        [4, 3, 3, 1],
        &[
            "with {:ok, a} <- g(x) do a else {:error, e} -> e; :z -> 0 end",
            "with({:ok, a} <- g(x), [do: a, else: ({:error, e} -> e; :z -> 0)])",
        ],
    );
    // A non-dispatching `else _ -> 0` is free in the modified tier too.
    assert_group(
        "with({:ok, a} <- g(x), do: a, else: (_ -> 0))",
        [2, 2, 1, 1],
        &[
            "with {:ok, a} <- g(x) do a else _ -> 0 end",
            "with({:ok, a} <- g(x), [do: a, else: (_ -> 0)])",
        ],
    );
    // The `if` nested in the body keeps its own `else` +1 at nesting 1.
    assert_group(
        "with({:ok, a} <- g(x), do: if(a, do: 1, else: 2), else: (_ -> 0))",
        [3, 3, 3, 4],
        &["with {:ok, a} <- g(x) do (if a do 1 else 2 end) else _ -> 0 end"],
    );
}

/// A `for … reduce:`'s first accumulator clause is its head, free in
/// every spelling (#1535). The bracketed form missed the `for` behind
/// the list and scored the head as a decision.
#[cfg(feature = "elixir")]
#[test]
fn elixir_for_reduce_head_is_free_in_every_spelling() {
    assert_group(
        "for x <- xs, reduce: 0 do acc -> acc + x end",
        [2, 2, 0, 1],
        &[
            "for(x <- xs, reduce: 0, do: (acc -> acc + x))",
            "for(x <- xs, [reduce: 0, do: (acc -> acc + x)])",
        ],
    );
    assert_group(
        "for x <- xs, reduce: 0 do 0 -> x; acc -> acc + x end",
        [3, 2, 1, 1],
        &[
            "for(x <- xs, reduce: 0, do: (0 -> x; acc -> acc + x))",
            "for(x <- xs, [reduce: 0, do: (0 -> x; acc -> acc + x)])",
        ],
    );
}

/// A `receive` is a `case` over the mailbox and scores as one in every
/// spelling (#1581): before, modified cyclomatic and cognitive did not
/// know the construct, so it sat at the base of both, and its `after`
/// timeout counted as one more clause. The timeout is the path taken
/// when no clause matched in time — `receive`'s default arm, which
/// `case`'s `_ ->` twin shows is free — so with it the `receive` still
/// scores what the two-clause `case` does. A `receive` has one free arm,
/// though, so beside a bare `_ ->` the timeout counts.
#[cfg(feature = "elixir")]
#[test]
fn elixir_receive_scores_like_case() {
    assert_group(
        "case x do {:a, y} -> y; :b -> 0 end",
        [3, 2, 2, 1],
        &[
            "receive do {:a, y} -> y; :b -> 0 end",
            "receive do {:a, y} -> y; :b -> 0 after 10 -> :t end",
            "receive(do: ({:a, y} -> y; :b -> 0), after: (10 -> :t))",
            "receive([do: ({:a, y} -> y; :b -> 0), after: (10 -> :t)])",
        ],
    );
    // The container raises nesting for what its clauses hold.
    assert_group(
        "case x do {:a, y} -> (if y do 1 end) end",
        [3, 3, 2, 3],
        &[
            "receive do {:a, y} -> (if y do 1 end) after 10 -> :t end",
            "receive(do: ({:a, y} -> if(y, do: 1)), after: (10 -> :t))",
        ],
    );
    // A `receive` gets one free arm. With a bare `_ ->` in its `do`
    // section the timeout is a path of its own: a matched `{:a, y}`, any
    // other message, and an empty mailbox timing out make three paths,
    // so cyclomatic is 3 (two decisions: `{:a, y}` and `after`, the `_`
    // free), modified 2 (base + the container), C 2 (one per decision),
    // cognitive 1 (+nesting on the container, arms free) — exactly the
    // three-clause `case` beside it, whose `_ ->` is its free arm.
    assert_group(
        "case x do {:a, y} -> y; :b -> 0; _ -> 1 end",
        [3, 2, 2, 1],
        &[
            "receive do {:a, y} -> y; _ -> 0 after 10 -> :t end",
            "receive(do: ({:a, y} -> y; _ -> 0), after: (10 -> :t))",
            "receive([do: ({:a, y} -> y; _ -> 0), after: (10 -> :t)])",
        ],
    );
    // A lone catch-all is free in standard and still pays the container.
    assert_group(
        "case x do _ -> 0 end",
        [1, 2, 0, 1],
        &["receive do _ -> 0 end", "receive(do: (_ -> 0))"],
    );
    // With no message clause it is a timed wait, the sleep it spells.
    assert_group(
        "Process.sleep(10)",
        [1, 1, 0, 0],
        &[
            "receive do after 10 -> :t end",
            "receive(after: (10 -> :t))",
            "receive([after: (10 -> :t)])",
        ],
    );
}