9
9
# Input arguments are pathnames of shell scripts containing test definitions,
10
10
# or globs referencing a collection of scripts. For each problem discovered,
11
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
15
16
16
use warnings;
17
17
use strict;
@@ -181,7 +181,7 @@ sub swallow_heredocs {
181
181
$self -> {lineno } += () = $body =~ / \n /sg ;
182
182
next ;
183
183
}
184
- push (@{$self -> {parser }-> {problems }}, [' UNCLOSED- HEREDOC' , $tag ]);
184
+ push (@{$self -> {parser }-> {problems }}, [' HEREDOC' , $tag ]);
185
185
$$b =~ / (?:\G |\n ).*\z /gc ; # consume rest of input
186
186
my $body = substr ($$b , $start , pos ($$b ) - $start );
187
187
$self -> {lineno } += () = $body =~ / \n /sg ;
@@ -238,6 +238,7 @@ sub new {
238
238
stop => [],
239
239
output => [],
240
240
heredocs => {},
241
+ insubshell => 0,
241
242
} => $class ;
242
243
$self -> {lexer } = Lexer-> new($self , $s );
243
244
return $self ;
@@ -296,8 +297,11 @@ sub parse_group {
296
297
297
298
sub parse_subshell {
298
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 ;
301
305
}
302
306
303
307
sub parse_case_pattern {
@@ -528,7 +532,7 @@ sub parse_loop_body {
528
532
return @tokens if ends_with(\@tokens , [qr / ^\|\| $ / , " \n " , qr / ^echo$ / , qr / ^.+$ / ]);
529
533
# flag missing "return/exit" handling explicit failure in loop body
530
534
my $n = find_non_nl(\@tokens );
531
- push (@{$self -> {problems }}, [' LOOP ' , $tokens [$n ]]);
535
+ push (@{$self -> {problems }}, [$self -> { insubshell } ? ' LOOPEXIT ' : ' LOOPRETURN ' , $tokens [$n ]]);
532
536
return @tokens ;
533
537
}
534
538
@@ -587,6 +591,7 @@ sub new {
587
591
my $class = shift @_ ;
588
592
my $self = $class -> SUPER::new(@_ );
589
593
$self -> {ntests } = 0;
594
+ $self -> {nerrs } = 0;
590
595
return $self ;
591
596
}
592
597
@@ -619,6 +624,15 @@ sub unwrap {
619
624
return $s
620
625
}
621
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
+
622
636
sub check_test {
623
637
my $self = shift @_ ;
624
638
my $title = unwrap(shift @_ );
@@ -634,22 +648,26 @@ sub check_test {
634
648
my $parser = TestParser-> new(\$body );
635
649
my @tokens = $parser -> parse();
636
650
my $problems = $parser -> {problems };
651
+ $self -> {nerrs } += @$problems ;
637
652
return unless $emit_all || @$problems ;
638
653
my $c = main::fd_colors(1);
654
+ my ($erropen , $errclose ) = -t 1 ? (" $c ->{rev}$c ->{red}" , $c -> {reset }) : (' ?!' , ' ?!' );
639
655
my $start = 0;
640
656
my $checked = ' ' ;
641
657
for (sort {$a -> [1]-> [2] <=> $b -> [1]-> [2]} @$problems ) {
642
658
my ($label , $token ) = @$_ ;
643
659
my $pos = $token -> [2];
644
- $checked .= substr ($body , $start , $pos - $start ) . " ?!$label ?! " ;
660
+ my $err = format_problem($label );
661
+ $checked .= substr ($body , $start , $pos - $start );
662
+ $checked .= ' ' unless $checked =~ / \s $ / ;
663
+ $checked .= " ${erropen} LINT: $err$errclose " ;
664
+ $checked .= ' ' unless $pos >= length ($body ) ||
665
+ substr ($body , $pos , 1) =~ / ^\s / ;
645
666
$start = $pos ;
646
667
}
647
668
$checked .= substr ($body , $start );
648
669
$checked =~ s / ^/ $lineno ++ . ' '/ mge ;
649
670
$checked =~ s / ^\d + \n // ;
650
- $checked =~ s / (\s ) \? !/ $1 ?!/ mg ;
651
- $checked =~ s /\? ! (\s )/ ?!$1 / mg ;
652
- $checked =~ s / (\? ![^?]+\? !)/ $c ->{rev}$c ->{red}$1 $c ->{reset}/ mg ;
653
671
$checked =~ s / ^\d +/ $c ->{dim}$& $c ->{reset}/ mg ;
654
672
$checked .= " \n " unless $checked =~ / \n $ / ;
655
673
push (@{$self -> {output }}, " $c ->{blue}# chainlint: $title$c ->{reset}\n $checked " );
@@ -791,9 +809,9 @@ sub check_script {
791
809
my $c = fd_colors(1);
792
810
my $s = join (' ' , @{$parser -> {output }});
793
811
$emit -> (" $c ->{bold}$c ->{blue}# chainlint: $path$c ->{reset}\n " . $s );
794
- $nerrs += () = $s =~ / \? ![^?]+\? !/g ;
795
812
}
796
813
$ntests += $parser -> {ntests };
814
+ $nerrs += $parser -> {nerrs };
797
815
}
798
816
return [$id , $nscripts , $ntests , $nerrs ];
799
817
}
0 commit comments