use std::cell::Cell;
use crate::test_support::check_func_space_only;
use crate::*;
#[cfg(feature = "perl")]
type Decisions = [u64; 3];
#[cfg(feature = "perl")]
type Halstead = [u64; 4];
#[cfg(feature = "perl")]
fn measure(body: &str) -> (Decisions, Halstead) {
let ([cyclomatic, _, conditions, cognitive], halstead) = measure_with_branches(body);
([cyclomatic, conditions, cognitive], halstead)
}
#[cfg(feature = "perl")]
fn measure_with_branches(body: &str) -> ([u64; 4], Halstead) {
let out = Cell::new(([0; 4], [0; 4]));
check_func_space_only::<PerlParser, _>(
&format!("sub f {{ {body} }}\n"),
"foo.pl",
&[
Metric::Cyclomatic,
Metric::Abc,
Metric::Cognitive,
Metric::Halstead,
],
|space| {
let m = &space.metrics;
out.set((
[
m.cyclomatic.cyclomatic_sum(),
m.abc.branches_sum(),
m.abc.conditions_sum(),
m.cognitive.cognitive_sum(),
],
[
m.halstead.unique_operators(),
m.halstead.total_operators(),
m.halstead.unique_operands(),
m.halstead.total_operands(),
],
));
},
);
out.get()
}
#[cfg(feature = "perl")]
#[test]
fn perl_and_key_scores_like_an_or_key() {
let rows: [(&str, &str, Decisions); 12] = [
("my %h; return $h{or};", "my %h; return $h{and};", [2, 0, 0]),
(
"my %h; return $h{ or };",
"my %h; return $h{ and };",
[2, 0, 0],
),
(
"my $r; return $r->{or};",
"my $r; return $r->{and};",
[2, 0, 0],
),
(
"my %h; if ($h{or}) { return 1; } return 0;",
"my %h; if ($h{and}) { return 1; } return 0;",
[3, 1, 1],
),
(
"my %h = (or => 1); return 1;",
"my %h = (and => 1); return 1;",
[2, 0, 0],
),
(
"my $h = { or => 1 }; return $h;",
"my $h = { and => 1 }; return $h;",
[2, 0, 0],
),
("f(x => 1, or => 2);", "f(x => 1, and => 2);", [2, 0, 0]),
("f(or => !$a);", "f(and => !$a);", [2, 1, 0]),
(
"my $y = $c ? 1 : (or => !$a);",
"my $y = $c ? 1 : (and => !$a);",
[3, 3, 1],
),
(
"if (or => $x > 1) { return 1; } return 0;",
"if (and => $x > 1) { return 1; } return 0;",
[3, 1, 1],
),
(
"my @a = (1 - or => 2);",
"my @a = (1 - and => 2);",
[2, 0, 0],
),
(
"my @a = ($x - or => 2, or => 3);",
"my @a = ($x - and => 2, and => 3);",
[2, 0, 0],
),
];
for (twin, key, decisions) in rows {
assert_eq!(
key.replace("and", "or"),
twin,
"`{key}` drifted from its twin"
);
let want = measure(twin);
assert_eq!(want.0, decisions, "`{twin}` moved; re-derive the row");
assert_eq!(measure(key), want, "`{key}` must score like `{twin}`");
}
}
#[cfg(feature = "perl")]
#[test]
fn perl_and_key_in_a_condition_slot_pays_the_slot() {
let twin = "my %h; if (%h = (or => 1)) { return 1; } return 0;";
let key = "my %h; if (%h = (and => 1)) { return 1; } return 0;";
let want = measure(twin);
assert_eq!(want.0, [3, 1, 1], "`{twin}` moved; re-derive the row");
assert_eq!(measure(key), want, "`{key}` must score like `{twin}`");
}
#[cfg(feature = "perl")]
#[test]
fn perl_and_operator_still_scores_like_ampamp() {
let rows: [(&str, &str, Decisions); 5] = [
("return ($a && $b);", "return ($a and $b);", [3, 2, 1]),
(
"return ($a && $b && $c);",
"return ($a and $b and $c);",
[4, 3, 1],
),
(
"if (!$a && $b) { return 1; } return 0;",
"if (not $a and $b) { return 1; } return 0;",
[4, 2, 2],
),
(
"return ($a > $b && $c);",
"return ($a > $b and $c);",
[3, 2, 1],
),
(
"return (($a > $b) && ($c ? 1 : 2));",
"return ($a > $b and $c ? 1 : 2);",
[4, 3, 2],
),
];
for (twin, operator, decisions) in rows {
assert!(operator.contains(" and "), "`{operator}` lost its `and`");
let want = measure(twin).0;
assert_eq!(want, decisions, "`{twin}` moved; re-derive the row");
assert_eq!(
measure(operator).0,
want,
"`{operator}` must score like `{twin}`"
);
}
}
#[cfg(feature = "perl")]
#[test]
fn perl_not_key_scores_like_an_or_key() {
let rows: [(&str, &str, Decisions, Halstead); 13] = [
(
"my %h; return $h{or};",
"my %h; return $h{not};",
[2, 0, 0],
[6, 8, 4, 4],
),
(
"my %h; return $h{ or };",
"my %h; return $h{ not };",
[2, 0, 0],
[6, 8, 4, 4],
),
(
"my $r; return $r->{or};",
"my $r; return $r->{not};",
[2, 0, 0],
[7, 10, 3, 4],
),
(
"my %h = (or => 1); return 1;",
"my %h = (not => 1); return 1;",
[2, 0, 0],
[8, 9, 4, 5],
),
(
"my $r = { or => 1 }; return 1;",
"my $r = { not => 1 }; return 1;",
[2, 0, 0],
[8, 10, 4, 5],
),
(
"f(x => 1, or => 2);",
"f(x => 1, not => 2);",
[2, 0, 0],
[6, 7, 5, 6],
),
("f(or => 1);", "f(not => 1);", [2, 0, 0], [5, 5, 3, 4]),
(
"return (or => 1);",
"return (not => 1);",
[2, 0, 0],
[6, 6, 3, 3],
),
("f(or => !$a);", "f(not => !$a);", [2, 1, 0], [7, 7, 3, 4]),
(
"my $y = $c ? 1 : (or => 2);",
"my $y = $c ? 1 : (not => 2);",
[3, 2, 1],
[10, 11, 6, 6],
),
(
"my @a = ($x - or => 2);",
"my @a = ($x - not => 2);",
[2, 0, 0],
[9, 9, 5, 5],
),
(
"my %h = (\n # -----\n or => 1,\n or => 2,\n);",
"my %h = (\n # -----\n not => 1,\n not => 2,\n);",
[2, 0, 0],
[8, 10, 5, 6],
),
(
"f(and => 1, or => 2);",
"f(and => 1, not => 2);",
[2, 0, 0],
[6, 7, 5, 6],
),
];
for (twin, key, decisions, halstead) in rows {
assert_eq!(
key.replace("not", "or"),
twin,
"`{key}` drifted from its twin"
);
let want = measure(twin);
assert_eq!(
want,
(decisions, halstead),
"`{twin}` moved; re-derive the row"
);
assert_eq!(measure(key), want, "`{key}` must score like `{twin}`");
}
}
#[cfg(feature = "perl")]
#[test]
fn perl_not_operator_still_scores_like_bang() {
let rows: [(&str, &str, Decisions, Halstead); 6] = [
("!$a;", "not $a;", [2, 0, 0], [5, 5, 2, 2]),
(
"if (!$a) { return 1; } return 0;",
"if (not $a) { return 1; } return 0;",
[3, 1, 1],
[8, 11, 4, 4],
),
("!($a);", "not($a);", [2, 0, 0], [6, 6, 2, 2]),
("f(!$a);", "f(not $a);", [2, 1, 0], [6, 6, 2, 3]),
(
"return (!$a && $b);",
"return (not $a and $b);",
[3, 2, 1],
[8, 9, 3, 3],
),
(
"my %h = (or => !$a); return 1;",
"my %h = (not => not $a); return 1;",
[2, 0, 0],
[10, 11, 5, 5],
),
];
for (twin, operator, decisions, halstead) in rows {
assert!(operator.contains("not"), "`{operator}` lost its `not`");
let want = measure(twin);
assert_eq!(
want,
(decisions, halstead),
"`{twin}` moved; re-derive the row"
);
assert_eq!(
measure(operator),
want,
"`{operator}` must score like `{twin}`"
);
}
}
#[cfg(feature = "perl")]
const DASH_KEY_ROWS: [(&str, &str, [u64; 4], Halstead); 20] = [
(
"my %h; return $h{'-foo'};",
"my %h; return $h{-foo};",
[2, 0, 0, 0],
[6, 8, 4, 4],
),
(
"my %h; return $h{'-foo' # why\n};",
"my %h; return $h{-foo # why\n};",
[2, 0, 0, 0],
[6, 8, 4, 4],
),
(
"my $r; return $r->{'-foo'};",
"my $r; return $r->{-foo};",
[2, 0, 0, 0],
[7, 10, 3, 4],
),
(
"my %h; return @h{'-foo'};",
"my %h; return @h{-foo};",
[2, 0, 0, 0],
[5, 7, 4, 4],
),
(
"my %h; if ($h{'-foo'}) { return 1; } return 0;",
"my %h; if ($h{-foo}) { return 1; } return 0;",
[3, 0, 1, 1],
[8, 13, 6, 6],
),
(
"my %h = ('-foo' => 1); return 1;",
"my %h = (-foo => 1); return 1;",
[2, 0, 0, 0],
[8, 9, 4, 5],
),
(
"my $r = { '-foo' => 1 }; return $r;",
"my $r = { -foo => 1 }; return $r;",
[2, 0, 0, 0],
[8, 11, 4, 5],
),
(
"f('-text' => 1);",
"f(-text => 1);",
[2, 1, 0, 0],
[5, 5, 3, 4],
),
(
"f('-text' => 1, 'ext' => 2);",
"f(-text => 1, ext => 2);",
[2, 1, 0, 0],
[6, 7, 5, 6],
),
(
"my %h = ('-x' => 1); return 1;",
"my %h = (-x => 1); return 1;",
[2, 0, 0, 0],
[8, 9, 4, 5],
),
(
"my %h = ('-x' => 1); my %g = ('-x' => 2);",
"my %h = (-x => 1); my %g = (-x => 2);",
[2, 0, 0, 0],
[7, 12, 6, 7],
),
(
"f('-x' => foo);",
"f(-x => foo);",
[2, 2, 0, 0],
[5, 5, 3, 4],
),
(
"my %h; return $h{'-not'};",
"my %h; return $h{-not};",
[2, 0, 0, 0],
[6, 8, 4, 4],
),
(
"my $r; return $r->{'-and'};",
"my $r; return $r->{-and};",
[2, 0, 0, 0],
[7, 10, 3, 4],
),
(
"my %h = ('-and' => 1); return 1;",
"my %h = (-and => 1); return 1;",
[2, 0, 0, 0],
[8, 9, 4, 5],
),
(
"my $r = { '-not' => 1 }; return $r;",
"my $r = { -not => 1 }; return $r;",
[2, 0, 0, 0],
[8, 11, 4, 5],
),
(
"f('-not' => 1, not => 2);",
"f(-not => 1, not => 2);",
[2, 1, 0, 0],
[6, 7, 5, 6],
),
(
"f('-and' => 1, and => 2);",
"f(-and => 1, and => 2);",
[2, 1, 0, 0],
[6, 7, 5, 6],
),
(
"f('-and' => !$a);",
"f(-and => !$a);",
[2, 1, 1, 0],
[7, 7, 3, 4],
),
(
"f('-not' => !$a);",
"f(-not => !$a);",
[2, 1, 1, 0],
[7, 7, 3, 4],
),
];
#[cfg(feature = "perl")]
#[test]
fn perl_dash_key_scores_like_its_quoted_twin() {
for (twin, key, decisions, halstead) in DASH_KEY_ROWS {
assert_eq!(twin.replace('\'', ""), key, "`{key}` drifted from its twin");
let want = measure_with_branches(twin);
assert_eq!(
want,
(decisions, halstead),
"`{twin}` moved; re-derive the row"
);
assert_eq!(
measure_with_branches(key),
want,
"`{key}` must score like `{twin}`"
);
}
}
#[cfg(feature = "perl")]
#[test]
fn perl_spaced_dash_key_keeps_its_sign() {
for (twin, key) in [
("f('-not' => 1, not => 2);", "f(- not => 1, not => 2);"),
("f('-and' => 1, and => 2);", "f(- and => 1, and => 2);"),
(
"f('-not' => 1, not => 2);",
"f(- # c\n not => 1, not => 2);",
),
(
"f('-name' => 1, 'name' => 2);",
"f(- name => 1, name => 2);",
),
(
"f('-name' => 1, 'name' => 2);",
"f(- # c\n name => 1, name => 2);",
),
] {
let want = measure_with_branches(twin);
assert_eq!(
want,
([2, 1, 0, 0], [6, 7, 5, 6]),
"`{twin}` moved; re-derive the row"
);
assert_eq!(
measure_with_branches(key),
want,
"`{key}` must score like `{twin}`"
);
}
}
#[cfg(feature = "perl")]
#[test]
fn perl_file_test_key_stops_at_a_line_break() {
let comma = ([2, 0, 0, 0], [8, 9, 3, 4]);
let quoted = ([2, 0, 0, 0], [8, 9, 4, 5]);
let word = ([2, 1, 0, 0], [5, 5, 3, 4]);
for (twin, want, key) in [
(
"my @p = (-f, 7); return @p;",
comma,
"my @p = (-f # c\n=> 7); return @p;",
),
(
"my @p = (-f, 7); return @p;",
comma,
"my @p = (-f\n=> 7); return @p;",
),
(
"my @p = ('-f' => 7); return @p;",
quoted,
"my @p = (-f \t => 7); return @p;",
),
("f('-foo' => 1);", word, "f(-foo # c\n=> 1);"),
("f('-foo' => 1);", word, "f(-foo\n=> 1);"),
] {
assert_eq!(
measure_with_branches(twin),
want,
"`{twin}` moved; re-derive the row"
);
assert_eq!(
measure_with_branches(key),
want,
"`{key}` must score like `{twin}`"
);
}
}
#[cfg(feature = "perl")]
#[test]
fn perl_file_test_outside_a_key_keeps_its_reading() {
let rows: [(&str, [u64; 4], Halstead); 11] = [
("my $z = -f $x;", [2, 0, 0, 0], [6, 7, 3, 3]),
(
"if (-e $x) { return 1; } return 0;",
[3, 0, 1, 1],
[7, 10, 4, 4],
),
("my $z = -d _;", [2, 1, 0, 0], [6, 6, 3, 3]),
("my $z = -f -w $x;", [2, 0, 0, 0], [6, 7, 3, 3]),
("my %h; return $h{-f $x};", [2, 0, 0, 0], [6, 9, 4, 4]),
("my %h; return $h{-f foo};", [2, 1, 0, 0], [6, 8, 4, 4]),
("return -foo;", [2, 1, 0, 0], [4, 4, 2, 2]),
("my %h; return $h{$a . -foo};", [2, 1, 0, 0], [7, 10, 5, 5]),
("f(-foo, 1);", [2, 2, 0, 0], [5, 5, 3, 4]),
("return -not $x;", [2, 0, 0, 0], [7, 7, 2, 2]),
("return $a - not $x;", [2, 0, 0, 0], [7, 8, 3, 3]),
];
for (body, decisions, halstead) in rows {
assert_eq!(
measure_with_branches(body),
(decisions, halstead),
"`{body}` changed its reading"
);
}
}
#[cfg(feature = "perl")]
#[test]
fn perl_dash_key_before_stray_comma_pins_the_grammar_misparse_1571() {
assert_eq!(
measure_with_branches("my @c = (-foo, => 1);"),
([2, 1, 0, 0], [8, 8, 4, 4]),
"the grammar's `oo` reading moved; re-derive against #1571"
);
}
#[cfg(feature = "perl")]
const BAREWORD_KEY_ROWS: [(&str, &str, [u64; 4], Halstead); 23] = [
(
"my %h = ('name' => 1); return 1;",
"my %h = (name => 1); return 1;",
[2, 0, 0, 0],
[8, 9, 4, 5],
),
(
"my %h = ('-name' => 1); return 1;",
"my %h = (-name => 1); return 1;",
[2, 0, 0, 0],
[8, 9, 4, 5],
),
(
"my %h; return $h{'-name'};",
"my %h; return $h{-name};",
[2, 0, 0, 0],
[6, 8, 4, 4],
),
(
"my %h; return $h{ '-name' };",
"my %h; return $h{ -name };",
[2, 0, 0, 0],
[6, 8, 4, 4],
),
(
"my $r; return $r->{'-name'};",
"my $r; return $r->{-name};",
[2, 0, 0, 0],
[7, 10, 3, 4],
),
(
"my %h; return @h{'-a'};",
"my %h; return @h{-a};",
[2, 0, 0, 0],
[5, 7, 4, 4],
),
(
"my %h; if ($h{'-name'}) { return 1; } return 0;",
"my %h; if ($h{-name}) { return 1; } return 0;",
[3, 0, 1, 1],
[8, 13, 6, 6],
),
(
"my $r = { 'a' => { 'b' => 1 } }; return $r;",
"my $r = { a => { b => 1 } }; return $r;",
[2, 0, 0, 0],
[8, 13, 5, 6],
),
(
"new('name' => $x, 'size' => 3);",
"new(name => $x, size => 3);",
[2, 1, 0, 0],
[7, 8, 6, 6],
),
(
"foo 'name' => 1;",
"foo name => 1;",
[2, 1, 0, 0],
[4, 4, 4, 4],
),
(
"$o->m('-name' => 1);",
"$o->m(-name => 1);",
[2, 1, 0, 0],
[7, 7, 5, 5],
),
(
"my %h = ('name' # c\n => 1); return 1;",
"my %h = (name # c\n => 1); return 1;",
[2, 0, 0, 0],
[8, 9, 4, 5],
),
(
"f('-name' => 1, 'name' => 2);",
"f(-name => 1, name => 2);",
[2, 1, 0, 0],
[6, 7, 5, 6],
),
(
"f('-name' => 1, '-name' => 2);",
"f(-name => 1, -name => 2);",
[2, 1, 0, 0],
[6, 7, 4, 6],
),
(
"my %h = ('shift' => 1, 'time' => 2); return 1;",
"my %h = (shift => 1, time => 2); return 1;",
[2, 0, 0, 0],
[9, 11, 6, 7],
),
(
"my @a = ('a' => 'b' => 1); return @a;",
"my @a = (a => b => 1); return @a;",
[2, 0, 0, 0],
[8, 10, 5, 6],
),
(
"my @a = ('a' => foo); return @a;",
"my @a = (a => foo); return @a;",
[2, 1, 0, 0],
[8, 9, 4, 5],
),
(
"my @a = (1 - 'name' => 2); return @a;",
"my @a = (1 - name => 2); return @a;",
[2, 0, 0, 0],
[9, 10, 5, 6],
),
(
"my @a = ($x . 'name' => 2); return @a;",
"my @a = ($x . name => 2); return @a;",
[2, 0, 0, 0],
[10, 11, 5, 6],
),
(
"my @a = (f(1), 'name' => 2); return @a;",
"my @a = (f(1), name => 2); return @a;",
[2, 1, 0, 0],
[9, 11, 5, 7],
),
(
"f(not => 1, 'name' => 2);",
"f(not => 1, name => 2);",
[2, 1, 0, 0],
[6, 7, 5, 6],
),
(
"f(and => 1, '-name' => 2);",
"f(and => 1, -name => 2);",
[2, 1, 0, 0],
[6, 7, 5, 6],
),
(
"f('-x' => 1, 'name' => 2);",
"f(-x => 1, name => 2);",
[2, 1, 0, 0],
[6, 7, 5, 6],
),
];
#[cfg(feature = "perl")]
#[test]
fn perl_bareword_key_scores_like_its_quoted_twin() {
for (twin, key, decisions, halstead) in BAREWORD_KEY_ROWS {
assert_eq!(twin.replace('\'', ""), key, "`{key}` drifted from its twin");
let want = measure_with_branches(twin);
assert_eq!(
want,
(decisions, halstead),
"`{twin}` moved; re-derive the row"
);
assert_eq!(
measure_with_branches(key),
want,
"`{key}` must score like `{twin}`"
);
}
}
#[cfg(feature = "perl")]
#[test]
fn perl_bareword_outside_a_key_keeps_its_call() {
let rows: [(&str, [u64; 4], Halstead); 11] = [
("foo(-name);", [2, 2, 0, 0], [5, 5, 3, 3]),
(
"my @a = (name, => 1); return @a;",
[2, 1, 0, 0],
[9, 10, 4, 5],
),
(
"my @a = (-name, => 1); return @a;",
[2, 1, 0, 0],
[10, 11, 4, 5],
),
("return -name;", [2, 1, 0, 0], [5, 5, 2, 2]),
("my @a = (name, 1); return @a;", [2, 1, 0, 0], [8, 9, 4, 5]),
(
"my @a = (Foo::Bar => 1); return @a;",
[2, 1, 0, 0],
[9, 10, 5, 6],
),
(
"my @a = (name() => 1); return @a;",
[2, 1, 0, 0],
[8, 10, 4, 5],
),
("my %h; return $h{foo()};", [2, 1, 0, 0], [7, 9, 4, 4]),
(
"my %h; return $h{-name . 'x'};",
[2, 1, 0, 0],
[8, 10, 5, 5],
),
("my %h; return $h{1 - name};", [2, 1, 0, 0], [7, 9, 5, 5]),
("my $y = -$x; return $y;", [2, 0, 0, 0], [8, 11, 3, 4]),
];
for (body, decisions, halstead) in rows {
assert_eq!(
measure_with_branches(body),
(decisions, halstead),
"`{body}` changed its reading"
);
}
}
#[cfg(feature = "perl")]
#[test]
fn perl_flat_error_reads_siblings_from_the_walk() {
use big_code_analysis_ast::node::WIDE_PARENT;
let body = "{\nlast,\n$a && $b,\n- and,\n-a => 1,\nname => 1,\n$c > $d && $e,\n";
let source = format!("my @r = ({}", body.repeat(12));
let ast = Ast::parse(Source::new(LANG::Perl, source.as_bytes())).expect("perl is enabled");
let root = ast.as_tree_sitter().root_node();
assert!(
root.is_error() && root.child_count() > WIDE_PARENT,
"the fixture must parse to a wide flat ERROR root"
);
let space = ast
.metrics(MetricsOptions::default())
.expect("metrics yields a root space");
let (abc, halstead) = (&space.metrics.abc, &space.metrics.halstead);
assert_eq!(
(
abc.branches_sum(),
abc.conditions_sum(),
abc.assignments_sum()
),
(12, 48, 1)
);
assert_eq!(
[
halstead.unique_operators(),
halstead.total_operators(),
halstead.unique_operands(),
halstead.total_operands(),
],
[9, 207, 11, 133]
);
let ops = ast.ops().expect("ops yields a root space");
assert_eq!(
ops.operands,
[
"$a", "$b", "$c", "$d", "$e", "-a", "-and", "1", "@r", "last", "name"
]
);
assert_eq!(
ops.operators,
["$", "&&", "()", ",", "=", "=>", ">", "my", "{}"]
);
assert_eq!(ast.count(&["call".to_owned()]).0, 12);
}
#[cfg(feature = "perl")]
fn perl_calls(source: &str) -> (Vec<(String, String)>, usize) {
let ast = Ast::parse(Source::new(LANG::Perl, source.as_bytes())).expect("perl is enabled");
let found = ast
.find(&["call".to_owned()])
.expect("find is infallible")
.iter()
.map(|node| {
(
node.kind().to_owned(),
source[node.start_byte()..node.end_byte()].to_owned(),
)
})
.collect::<Vec<_>>();
assert_eq!(
ast.count(&["call".to_owned()]).0,
found.len(),
"`count` and `find` disagree on `{source}`"
);
let branches = ast
.metrics(MetricsOptions::default())
.expect("metrics yields a root space")
.metrics
.abc
.branches_sum();
let branches = usize::try_from(branches).expect("a fixture's branch count fits a usize");
(found, branches)
}
#[cfg(feature = "perl")]
#[test]
fn perl_find_call_reports_one_node_per_call_1580() {
let source = "sub d { my %h = (name => 1, -size => 2); my $y = $h{-name}; return foo(1); }\n";
let (found, branches) = perl_calls(source);
assert_eq!(
found,
[(
"call_expression_with_args_with_brackets".to_owned(),
"foo(1)".to_owned()
)]
);
assert_eq!(branches, 1);
}
#[cfg(feature = "perl")]
#[test]
fn perl_each_call_spelling_is_one_call_1580() {
const BRACKETS: &str = "call_expression_with_args_with_brackets";
const SPACED: &str = "call_expression_with_spaced_args";
const BAREWORD: &str = "call_expression_with_bareword";
const METHOD: &str = "method_invocation";
let rows: [(&str, &[(&str, &str)]); 14] = [
("foo(1);", &[(BRACKETS, "foo(1)")]),
("foo();", &[(BRACKETS, "foo()")]),
("&foo(1);", &[(BRACKETS, "&foo(1)")]),
("Foo::bar(1);", &[(BRACKETS, "Foo::bar(1)")]),
("foo 1;", &[(SPACED, "foo 1")]),
("print \"x\";", &[(SPACED, "print \"x\"")]),
(
"sort { $a <=> $b } @x;",
&[(SPACED, "sort { $a <=> $b } @x")],
),
("$obj->m(1);", &[(METHOD, "$obj->m(1)")]),
(
"&$code(1);",
&[("call_expression_with_variable", "&$code(1)")],
),
(
"__SUB__->(1);",
&[("call_expression_recursive", "__SUB__->(1)")],
),
("foo;", &[(BAREWORD, "foo")]),
("&foo;", &[(BAREWORD, "&foo")]),
("foo(bar);", &[(BRACKETS, "foo(bar)"), (BAREWORD, "bar")]),
("foo(1", &[(BAREWORD, "foo")]),
];
for (source, expected) in rows {
let (found, branches) = perl_calls(&format!("{source}\n"));
let expected = expected
.iter()
.map(|&(kind, text)| (kind.to_owned(), text.to_owned()))
.collect::<Vec<_>>();
assert_eq!(found, expected, "`{source}`");
assert_eq!(branches, found.len(), "`{source}`: ABC disagrees");
}
}
#[cfg(feature = "perl")]
#[test]
fn perl_bareword_key_is_no_call_1580() {
let rows = [
("my %h = (name => 1);", "my %h = ('name' => 1);", 0),
("my %h = (-name => 1);", "my %h = ('-name' => 1);", 0),
("my %h = (-size => 1);", "my %h = ('-size' => 1);", 0),
("my $y = $h{-name};", "my $y = $h{'-name'};", 0),
("my $y = $h{-size};", "my $y = $h{'-size'};", 0),
("my %h = (1 - name => 2);", "my %h = (1 - 'name' => 2);", 0),
("new(name => $x);", "new('name' => $x);", 1),
("my %h = (Foo::Bar => 1);", "my %h = (Foo::Bar() => 1);", 1),
(
"my %h = (-foo::bar => 1);",
"my %h = (-foo::bar() => 1);",
1,
),
("my %h = (name() => 1);", "my %h = (name(1) => 1);", 1),
("my %h = (k => name);", "my %h = ('k' => name());", 1),
];
for (key, twin, calls) in rows {
let (found, branches) = perl_calls(&format!("{key}\n"));
let (twin_found, _) = perl_calls(&format!("{twin}\n"));
assert_eq!(found.len(), calls, "`{key}` reported {found:?}");
assert_eq!(found.len(), twin_found.len(), "`{key}` against `{twin}`");
assert_eq!(branches, calls, "`{key}`: ABC disagrees");
}
}
#[cfg(feature = "perl")]
#[test]
fn perl_negated_qualified_calls_keep_their_operand_names() {
let source = "my %h = (-foo::bar => 1, -foo::baz => 2);";
let ast = Ast::parse(Source::new(LANG::Perl, source.as_bytes())).expect("perl is enabled");
assert_eq!(ast.count(&["call".to_owned()]).0, 2);
let metrics = ast
.metrics(MetricsOptions::default())
.expect("metrics has a root");
assert_eq!(metrics.metrics.abc.branches_sum(), 2);
assert_eq!(metrics.metrics.halstead.unique_operands(), 6);
assert_eq!(
ast.ops().expect("ops has a root").operands,
["%h", "1", "2", "bar", "baz", "oo"]
);
}