9
# Input arguments are pathnames of shell scripts containing test definitions,
10
# or globs referencing a collection of scripts. For each problem discovered,
11
# the pathname of the script containing the test is printed along with the test
12
-# name and the test body with a `?!FOO?!` annotation at the location of each
13
-# detected problem, where "FOO" is a tag such as "AMP" which indicates a broken
14
-# &&-chain. Returns zero if no problems are discovered, otherwise non-zero.
12
+# name and the test body with a `?!LINT: ...?!` annotation at the location of
13
+# each detected problem, where "..." is an explanation of the problem. Returns
14
+# zero if no problems are discovered, otherwise non-zero.
15
16
use warnings;
17
use strict;
181
$self->{lineno} += () = $body =~ /\n/sg;
182
next;
183
}
184
- push(@{$self->{parser}->{problems}}, ['UNCLOSED-HEREDOC', $tag]);
184
+ push(@{$self->{parser}->{problems}}, ['HEREDOC', $tag]);
185
$$b =~ /(?:\G|\n).*\z/gc; # consume rest of input
186
my $body = substr($$b, $start, pos($$b) - $start);
187
$self->{lineno} += () = $body =~ /\n/sg;
238
stop => [],
239
output => [],
240
heredocs => {},
241
+ insubshell => 0,
242
} => $class;
243
$self->{lexer} = Lexer->new($self, $s);
244
return $self;
297
298
sub parse_subshell {
299
my $self = shift @_;
299
- return ($self->parse(qr/^\)$/),
300
- $self->expect(')'));
300
+ $self->{insubshell}++;
301
+ my @tokens = ($self->parse(qr/^\)$/),
302
+ $self->expect(')'));
303
+ $self->{insubshell}--;
304
+ return @tokens;
305
}
306
307
sub parse_case_pattern {
532
return @tokens if ends_with(\@tokens, [qr/^\|\|$/, "\n", qr/^echo$/, qr/^.+$/]);
533
# flag missing "return/exit" handling explicit failure in loop body
534
my $n = find_non_nl(\@tokens);
531
- push(@{$self->{problems}}, ['LOOP', $tokens[$n]]);
535
+ push(@{$self->{problems}}, [$self->{insubshell} ? 'LOOPEXIT' : 'LOOPRETURN', $tokens[$n]]);
536
return @tokens;
537
}
538
624
return $s
625
}
626
627
+sub format_problem {
628
+ local $_ = shift;
629
+ /^AMP$/ && return "missing '&&'";
630
+ /^LOOPRETURN$/ && return "missing '|| return 1'";
631
+ /^LOOPEXIT$/ && return "missing '|| exit 1'";
632
+ /^HEREDOC$/ && return 'unclosed heredoc';
633
+ die("unrecognized problem type '$_'\n");
634
+}
635
+
636
sub check_test {
637
my $self = shift @_;
638
my $title = unwrap(shift @_);
656
for (sort {$a->[1]->[2] <=> $b->[1]->[2]} @$problems) {
657
my ($label, $token) = @$_;
658
my $pos = $token->[2];
659
+ my $err = format_problem($label);
660
$checked .= substr($body, $start, $pos - $start);
661
$checked .= ' ' unless $checked =~ /\s$/;
648
- $checked .= "$c->{rev}$c->{red}?!$label?!$c->{reset}";
662
+ $checked .= "$c->{rev}$c->{red}?!LINT: $err?!$c->{reset}";
663
$checked .= ' ' unless $pos >= length($body) ||
664
substr($body, $pos, 1) =~ /^\s/;
665
$start = $pos;