chainlint: make error messages self-explanatory

The annotations emitted by chainlint to indicate detected problems are overly terse, so much so that developers new to the project -- those who should most benefit from the linting -- may find them baffling. For instance, although the author of chainlint and seasoned Git developers may understand that "?!AMP?!" is an abbreviation of "ampersand" and indicates a break in the &&-chain, this may not be obvious to newcomers. The "?!LOOP?!" case is particularly serious because that terse single word does nothing to convey that the loop body should end with "|| return 1" (or "|| exit 1" in a subshell) to ensure that a failing command in the body aborts the loop immediately. Moreover, unlike &&-chaining which is ubiquitous in Git tests, the "|| return 1" idiom is relatively infrequent, thus may be harder for a newcomer to discover by consulting nearby code. Address these shortcomings by emitting human-readable messages which both explain the problem and give a strong hint about how to correct it. Signed-off-by: Eric Sunshine <sunshine@sunshineco.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Eric Sunshine committed Sep 10, 2024 at 00:10 UTC e44f15ba3ee873b5df5e8e5d8cc018df288472ef
44 files changed +104 -90
t/chainlint.pl
+22 -8
@@ -9,9 +9,9 @@
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,7 +181,7 @@ sub swallow_heredocs {
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,6 +238,7 @@ sub new {
238 stop => [],
239 output => [],
240 heredocs => {},
241 + insubshell => 0,
242 } => $class;
243 $self->{lexer} = Lexer->new($self, $s);
244 return $self;
@@ -296,8 +297,11 @@ sub parse_group {
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 {
@@ -528,7 +532,7 @@ sub parse_loop_body {
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
@@ -620,6 +624,15 @@ sub unwrap {
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 @_);
@@ -643,9 +656,10 @@ sub check_test {
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;
t/chainlint/arithmetic-expansion.expect
+1 -1
@@ -4,6 +4,6 @@
4 5 baz
5 6 ) &&
6 7 (
7 -8 bar=$((42 + 1)) ?!AMP?!
7 +8 bar=$((42 + 1)) ?!LINT: missing '&&'?!
8 9 baz
9 10 )
t/chainlint/block.expect
+4 -4
@@ -1,20 +1,20 @@
1 2 (
2 3 foo &&
3 4 {
4 -5 echo a ?!AMP?!
4 +5 echo a ?!LINT: missing '&&'?!
5 6 echo b
6 7 } &&
7 8 bar &&
8 9 {
9 10 echo c
10 -11 } ?!AMP?!
10 +11 } ?!LINT: missing '&&'?!
11 12 baz
12 13 ) &&
13 14
14 15 {
15 -16 echo a; ?!AMP?! echo b
15 +16 echo a; ?!LINT: missing '&&'?! echo b
16 17 } &&
17 -18 { echo a; ?!AMP?! echo b; } &&
17 +18 { echo a; ?!LINT: missing '&&'?! echo b; } &&
18 19
19 20 {
20 21 echo "${var}9" &&
t/chainlint/broken-chain.expect
+1 -1
@@ -1,6 +1,6 @@
1 2 (
2 3 foo &&
3 -4 bar ?!AMP?!
3 +4 bar ?!LINT: missing '&&'?!
4 5 baz &&
5 6 wop
6 7 )
t/chainlint/case.expect
+2 -2
@@ -9,11 +9,11 @@
9 10 case "$x" in
10 11 x) foo ;;
11 12 *) bar ;;
12 -13 esac ?!AMP?!
12 +13 esac ?!LINT: missing '&&'?!
13 14 foobar
14 15 ) &&
15 16 (
16 17 case "$x" in 1) true;; esac &&
17 -18 case "$y" in 2) false;; esac ?!AMP?!
17 +18 case "$y" in 2) false;; esac ?!LINT: missing '&&'?!
18 19 foobar
19 20 )
t/chainlint/chain-break-false.expect
+1 -1
@@ -4,6 +4,6 @@
4 5 echo failed!
5 6 false
6 7 else
7 -8 echo it went okay ?!AMP?!
7 +8 echo it went okay ?!LINT: missing '&&'?!
8 9 congratulate user
9 10 fi
t/chainlint/chained-block.expect
+1 -1
@@ -1,5 +1,5 @@
1 2 echo nobody home && {
2 -3 test the doohicky ?!AMP?!
2 +3 test the doohicky ?!LINT: missing '&&'?!
3 4 right now
4 5 } &&
5 6
t/chainlint/chained-subshell.expect
+2 -2
@@ -1,10 +1,10 @@
1 2 mkdir sub && (
2 3 cd sub &&
3 -4 foo the bar ?!AMP?!
3 +4 foo the bar ?!LINT: missing '&&'?!
4 5 nuff said
5 6 ) &&
6 7
7 8 cut "-d " -f actual | (read s1 s2 s3 &&
8 -9 test -f $s1 ?!AMP?!
8 +9 test -f $s1 ?!LINT: missing '&&'?!
9 10 test $(cat $s2) = tree2path1 &&
10 11 test $(cat $s3) = tree3path1)
t/chainlint/command-substitution.expect
+1 -1
@@ -4,6 +4,6 @@
4 5 baz
5 6 ) &&
6 7 (
7 -8 bar=$(gobble blocks) ?!AMP?!
7 +8 bar=$(gobble blocks) ?!LINT: missing '&&'?!
8 9 baz
9 10 )
t/chainlint/complex-if-in-cuddled-loop.expect
+1 -1
@@ -4,6 +4,6 @@
4 5 :
5 6 else
6 7 echo >file
7 -8 fi ?!LOOP?!
7 +8 fi ?!LINT: missing '|| exit 1'?!
8 9 done) &&
9 10 test ! -f file
t/chainlint/cuddled.expect
+2 -2
@@ -2,7 +2,7 @@
2 3 bar
3 4 ) &&
4 5
5 -6 (cd foo ?!AMP?!
5 +6 (cd foo ?!LINT: missing '&&'?!
6 7 bar
7 8 ) &&
8 9
@@ -13,5 +13,5 @@
13 14 (cd foo &&
14 15 bar) &&
15 16
16 -17 (cd foo ?!AMP?!
16 +17 (cd foo ?!LINT: missing '&&'?!
17 18 bar)
t/chainlint/for-loop.expect
+4 -4
@@ -1,14 +1,14 @@
1 2 (
2 3 for i in a b c
3 4 do
4 -5 echo $i ?!AMP?!
5 -6 cat <<-\EOF ?!LOOP?!
4 +5 echo $i ?!LINT: missing '&&'?!
5 +6 cat <<-\EOF ?!LINT: missing '|| exit 1'?!
6 7 bar
7 8 EOF
8 -9 done ?!AMP?!
8 +9 done ?!LINT: missing '&&'?!
9 10
10 11 for i in a b c; do
11 12 echo $i &&
12 -13 cat $i ?!LOOP?!
12 +13 cat $i ?!LINT: missing '|| exit 1'?!
13 14 done
14 15 )
t/chainlint/function.expect
+2 -2
@@ -4,8 +4,8 @@
4 5
5 6 remove_object() {
6 7 file=$(sha1_file "$*") &&
7 -8 test -e "$file" ?!AMP?!
7 +8 test -e "$file" ?!LINT: missing '&&'?!
8 9 rm -f "$file"
9 -10 } ?!AMP?!
9 +10 } ?!LINT: missing '&&'?!
10 11
11 12 sha1_file arg && remove_object arg
t/chainlint/here-doc-body-indent.expect
+1 -1
@@ -1,2 +1,2 @@
1 -2 echo "we should find this" ?!AMP?!
1 +2 echo "we should find this" ?!LINT: missing '&&'?!
2 3 echo "even though our heredoc has its indent stripped"
t/chainlint/here-doc-body-pathological.expect
+2 -2
@@ -1,7 +1,7 @@
1 -2 echo "outer here-doc does not allow indented end-tag" ?!AMP?!
1 +2 echo "outer here-doc does not allow indented end-tag" ?!LINT: missing '&&'?!
2 3 cat >file <<-\EOF &&
3 4 but this inner here-doc
4 5 does allow indented EOF
5 6 EOF
6 -7 echo "missing chain after" ?!AMP?!
6 +7 echo "missing chain after" ?!LINT: missing '&&'?!
7 8 echo "but this line is OK because it's the end"
t/chainlint/here-doc-body.expect
+2 -2
@@ -1,7 +1,7 @@
1 -2 echo "missing chain before" ?!AMP?!
1 +2 echo "missing chain before" ?!LINT: missing '&&'?!
2 3 cat >file <<-\EOF &&
3 4 inside inner here-doc
4 5 these are not shell commands
5 6 EOF
6 -7 echo "missing chain after" ?!AMP?!
6 +7 echo "missing chain after" ?!LINT: missing '&&'?!
7 8 echo "but this line is OK because it's the end"
t/chainlint/here-doc-double.expect
+1 -1
@@ -1,2 +1,2 @@
1 -8 echo "actual test commands" ?!AMP?!
1 +8 echo "actual test commands" ?!LINT: missing '&&'?!
2 9 echo "that should be checked"
t/chainlint/here-doc-indent-operator.expect
+1 -1
@@ -4,7 +4,7 @@
4 5 chunks: oid_fanout oid_lookup commit_metadata generation_data bloom_indexes bloom_data
5 6 EOF
6 7
7 -8 cat >expect << -EOF ?!AMP?!
7 +8 cat >expect << -EOF ?!LINT: missing '&&'?!
8 9 this is not indented
9 10 -EOF
10 11
t/chainlint/here-doc-multi-line-command-subst.expect
+1 -1
@@ -3,6 +3,6 @@
3 4 fossil
4 5 vegetable
5 6 END
6 -7 wiffle) ?!AMP?!
6 +7 wiffle) ?!LINT: missing '&&'?!
7 8 echo $x
8 9 )
t/chainlint/here-doc-multi-line-string.expect
+1 -1
@@ -1,6 +1,6 @@
1 2 (
2 3 cat <<-\TXT && echo "multi-line
3 -4 string" ?!AMP?!
3 +4 string" ?!LINT: missing '&&'?!
4 5 fizzle
5 6 TXT
6 7 bap
t/chainlint/if-condition-split.expect
+1 -1
@@ -2,6 +2,6 @@
2 3 marcia ||
3 4 kevin
4 5 then
5 -6 echo "nomads" ?!AMP?!
5 +6 echo "nomads" ?!LINT: missing '&&'?!
6 7 echo "for sure"
7 8 fi
t/chainlint/if-in-loop.expect
+2 -2
@@ -5,8 +5,8 @@
5 6 then
6 7 echo "err"
7 8 exit 1
8 -9 fi ?!AMP?!
8 +9 fi ?!LINT: missing '&&'?!
9 10 foo
10 -11 done ?!AMP?!
10 +11 done ?!LINT: missing '&&'?!
11 12 bar
12 13 )
t/chainlint/if-then-else.expect
+2 -2
@@ -1,7 +1,7 @@
1 2 (
2 3 if test -n ""
3 4 then
4 -5 echo very ?!AMP?!
4 +5 echo very ?!LINT: missing '&&'?!
5 6 echo empty
6 7 elif test -z ""
7 8 then
@@ -11,7 +11,7 @@
11 12 cat <<-\EOF
12 13 bar
13 14 EOF
14 -15 fi ?!AMP?!
14 +15 fi ?!LINT: missing '&&'?!
15 16 echo poodle
16 17 ) &&
17 18 (
t/chainlint/inline-comment.expect
+1 -1
@@ -1,6 +1,6 @@
1 2 (
2 3 foobar && # comment 1
3 -4 barfoo ?!AMP?! # wrong position for &&
3 +4 barfoo ?!LINT: missing '&&'?! # wrong position for &&
4 5 flibble "not a # comment"
5 6 ) &&
6 7
t/chainlint/loop-detect-failure.expect
+1 -1
@@ -11,5 +11,5 @@
11 12 do
12 13 printf "%"$n"s" X > r2/large.$n &&
13 14 git -C r2 add large.$n &&
14 -15 git -C r2 commit -m "$n" ?!LOOP?!
14 +15 git -C r2 commit -m "$n" ?!LINT: missing '|| return 1'?!
15 16 done
t/chainlint/loop-in-if.expect
+4 -4
@@ -3,10 +3,10 @@
3 4 then
4 5 while true
5 6 do
6 -7 echo "pop" ?!AMP?!
7 -8 echo "glup" ?!LOOP?!
8 -9 done ?!AMP?!
6 +7 echo "pop" ?!LINT: missing '&&'?!
7 +8 echo "glup" ?!LINT: missing '|| exit 1'?!
8 +9 done ?!LINT: missing '&&'?!
9 10 foo
10 -11 fi ?!AMP?!
10 +11 fi ?!LINT: missing '&&'?!
11 12 bar
12 13 )
t/chainlint/multi-line-string.expect
+1 -1
@@ -3,7 +3,7 @@
3 4 line 2
4 5 line 3" &&
5 6 y="line 1
6 -7 line2" ?!AMP?!
6 +7 line2" ?!LINT: missing '&&'?!
7 8 foobar
8 9 ) &&
9 10 (
t/chainlint/negated-one-liner.expect
+2 -2
@@ -1,5 +1,5 @@
1 2 ! (foo && bar) &&
2 3 ! (foo && bar) >baz &&
3 4
4 -5 ! (foo; ?!AMP?! bar) &&
5 -6 ! (foo; ?!AMP?! bar) >baz
4 +5 ! (foo; ?!LINT: missing '&&'?! bar) &&
5 +6 ! (foo; ?!LINT: missing '&&'?! bar) >baz
t/chainlint/nested-cuddled-subshell.expect
+3 -3
@@ -5,7 +5,7 @@
5 6
6 7 (cd foo &&
7 8 bar
8 -9 ) ?!AMP?!
8 +9 ) ?!LINT: missing '&&'?!
9 10
10 11 (
11 12 cd foo &&
@@ -13,13 +13,13 @@
13 14
14 15 (
15 16 cd foo &&
16 -17 bar) ?!AMP?!
16 +17 bar) ?!LINT: missing '&&'?!
17 18
18 19 (cd foo &&
19 20 bar) &&
20 21
21 22 (cd foo &&
22 -23 bar) ?!AMP?!
22 +23 bar) ?!LINT: missing '&&'?!
23 24
24 25 foobar
25 26 )
t/chainlint/nested-here-doc.expect
+1 -1
@@ -18,7 +18,7 @@
18 19 toink
19 20 INPUT_END
20 21
21 -22 cat <<-\EOT ?!AMP?!
21 +22 cat <<-\EOT ?!LINT: missing '&&'?!
22 23 text goes here
23 24 data <<EOF
24 25 data goes here
t/chainlint/nested-loop-detect-failure.expect
+3 -3
@@ -2,8 +2,8 @@
2 3 do
3 4 for j in 0 1 2 3 4 5 6 7 8 9;
4 5 do
5 -6 echo "$i$j" >"path$i$j" ?!LOOP?!
6 -7 done ?!LOOP?!
5 +6 echo "$i$j" >"path$i$j" ?!LINT: missing '|| return 1'?!
6 +7 done ?!LINT: missing '|| return 1'?!
7 8 done &&
8 9
9 10 for i in 0 1 2 3 4 5 6 7 8 9;
@@ -18,7 +18,7 @@
18 19 do
19 20 for j in 0 1 2 3 4 5 6 7 8 9;
20 21 do
21 -22 echo "$i$j" >"path$i$j" ?!LOOP?!
21 +22 echo "$i$j" >"path$i$j" ?!LINT: missing '|| return 1'?!
22 23 done || return 1
23 24 done &&
24 25
t/chainlint/nested-subshell-comment.expect
+1 -1
@@ -6,6 +6,6 @@
6 7 # minor numbers of cows (or do they?)
7 8 baz &&
8 9 snaff
9 -10 ) ?!AMP?!
9 +10 ) ?!LINT: missing '&&'?!
10 11 fuzzy
11 12 )
t/chainlint/nested-subshell.expect
+1 -1
@@ -7,7 +7,7 @@
7 8
8 9 cd foo &&
9 10 (
10 -11 echo a ?!AMP?!
10 +11 echo a ?!LINT: missing '&&'?!
11 12 echo b
12 13 ) >file
13 14 )
t/chainlint/not-heredoc.expect
+1 -1
@@ -9,6 +9,6 @@
9 10 echo ourside &&
10 11 echo "=======" &&
11 12 echo theirside &&
12 -13 echo ">>>>>>> theirs" ?!AMP?!
12 +13 echo ">>>>>>> theirs" ?!LINT: missing '&&'?!
13 14 poodle
14 15 ) >merged
t/chainlint/one-liner-for-loop.expect
+1 -1
@@ -3,7 +3,7 @@
3 4 cd dir-rename-and-content &&
4 5 test_write_lines 1 2 3 4 5 >foo &&
5 6 mkdir olddir &&
6 -7 for i in a b c; do echo $i >olddir/$i; ?!LOOP?! done ?!AMP?!
6 +7 for i in a b c; do echo $i >olddir/$i; ?!LINT: missing '|| exit 1'?! done ?!LINT: missing '&&'?!
7 8 git add foo olddir &&
8 9 git commit -m "original" &&
9 10 )
t/chainlint/one-liner.expect
+3 -3
@@ -2,8 +2,8 @@
2 3 (foo && bar) |
3 4 (foo && bar) >baz &&
4 5
5 -6 (foo; ?!AMP?! bar) &&
6 -7 (foo; ?!AMP?! bar) |
7 -8 (foo; ?!AMP?! bar) >baz &&
5 +6 (foo; ?!LINT: missing '&&'?! bar) &&
6 +7 (foo; ?!LINT: missing '&&'?! bar) |
7 +8 (foo; ?!LINT: missing '&&'?! bar) >baz &&
8 9
9 10 (foo "bar; baz")
t/chainlint/pipe.expect
+1 -1
@@ -4,7 +4,7 @@
4 5 baz &&
5 6
6 7 fish |
7 -8 cow ?!AMP?!
7 +8 cow ?!LINT: missing '&&'?!
8 9
9 10 sunder
10 11 )
t/chainlint/semicolon.expect
+6 -6
@@ -1,19 +1,19 @@
1 2 (
2 -3 cat foo ; ?!AMP?! echo bar ?!AMP?!
3 -4 cat foo ; ?!AMP?! echo bar
2 +3 cat foo ; ?!LINT: missing '&&'?! echo bar ?!LINT: missing '&&'?!
3 +4 cat foo ; ?!LINT: missing '&&'?! echo bar
4 5 ) &&
5 6 (
6 -7 cat foo ; ?!AMP?! echo bar &&
7 -8 cat foo ; ?!AMP?! echo bar
6 +7 cat foo ; ?!LINT: missing '&&'?! echo bar &&
7 +8 cat foo ; ?!LINT: missing '&&'?! echo bar
8 9 ) &&
9 10 (
10 11 echo "foo; bar" &&
11 -12 cat foo; ?!AMP?! echo bar
11 +12 cat foo; ?!LINT: missing '&&'?! echo bar
12 13 ) &&
13 14 (
14 15 foo;
15 16 ) &&
16 17 (cd foo &&
17 18 for i in a b c; do
18 -19 echo; ?!LOOP?!
18 +19 echo; ?!LINT: missing '|| exit 1'?!
19 20 done)
t/chainlint/subshell-here-doc.expect
+1 -1
@@ -6,7 +6,7 @@
6 7 nevermore...
7 8 EOF
8 9
9 -10 cat <<EOF >bip ?!AMP?!
9 +10 cat <<EOF >bip ?!LINT: missing '&&'?!
10 11 fish fly high
11 12 EOF
12 13
t/chainlint/subshell-one-liner.expect
+5 -5
@@ -3,17 +3,17 @@
3 4 (foo && bar) |
4 5 (foo && bar) >baz &&
5 6
6 -7 (foo; ?!AMP?! bar) &&
7 -8 (foo; ?!AMP?! bar) |
8 -9 (foo; ?!AMP?! bar) >baz &&
6 +7 (foo; ?!LINT: missing '&&'?! bar) &&
7 +8 (foo; ?!LINT: missing '&&'?! bar) |
8 +9 (foo; ?!LINT: missing '&&'?! bar) >baz &&
9 10
10 11 (foo || exit 1) &&
11 12 (foo || exit 1) |
12 13 (foo || exit 1) >baz &&
13 14
14 -15 (foo && bar) ?!AMP?!
14 +15 (foo && bar) ?!LINT: missing '&&'?!
15 16
16 -17 (foo && bar; ?!AMP?! baz) ?!AMP?!
16 +17 (foo && bar; ?!LINT: missing '&&'?! baz) ?!LINT: missing '&&'?!
17 18
18 19 foobar
19 20 )
t/chainlint/token-pasting.expect
+4 -4
@@ -2,13 +2,13 @@
2 3 git config filter.rot13.clean ./rot13.sh &&
3 4
4 5 {
5 -6 echo "*.t filter=rot13" ?!AMP?!
5 +6 echo "*.t filter=rot13" ?!LINT: missing '&&'?!
6 7 echo "*.i ident"
7 8 } >.gitattributes &&
8 9
9 10 {
10 -11 echo a b c d e f g h i j k l m ?!AMP?!
11 -12 echo n o p q r s t u v w x y z ?!AMP?!
10 +11 echo a b c d e f g h i j k l m ?!LINT: missing '&&'?!
11 +12 echo n o p q r s t u v w x y z ?!LINT: missing '&&'?!
12 13 echo '$Id$'
13 14 } >test &&
14 15 cat test >test.t &&
@@ -19,7 +19,7 @@
19 20 git checkout -- test test.t test.i &&
20 21
21 22 echo "content-test2" >test2.o &&
22 -23 echo "content-test3 - filename with special characters" >"test3 'sq',$x=.o" ?!AMP?!
22 +23 echo "content-test3 - filename with special characters" >"test3 'sq',$x=.o" ?!LINT: missing '&&'?!
23 24
24 25 downstream_url_for_sed=$(
25 26 printf "%sn" "$downstream_url" |
t/chainlint/unclosed-here-doc-indent.expect
+1 -1
@@ -1,4 +1,4 @@
1 2 command_which_is_run &&
2 -3 cat >expect <<-\EOF ?!UNCLOSED-HEREDOC?! &&
2 +3 cat >expect <<-\EOF ?!LINT: unclosed heredoc?! &&
3 4 we forget to end the here-doc
4 5 command_which_is_gobbled
t/chainlint/unclosed-here-doc.expect
+1 -1
@@ -1,5 +1,5 @@
1 2 command_which_is_run &&
2 -3 cat >expect <<\EOF ?!UNCLOSED-HEREDOC?! &&
2 +3 cat >expect <<\EOF ?!LINT: unclosed heredoc?! &&
3 4 we try to end the here-doc below,
4 5 but the indentation throws us off
5 6 since the operator is not "<<-".
t/chainlint/while-loop.expect
+4 -4
@@ -1,14 +1,14 @@
1 2 (
2 3 while true
3 4 do
4 -5 echo foo ?!AMP?!
5 -6 cat <<-\EOF ?!LOOP?!
4 +5 echo foo ?!LINT: missing '&&'?!
5 +6 cat <<-\EOF ?!LINT: missing '|| exit 1'?!
6 7 bar
7 8 EOF
8 -9 done ?!AMP?!
8 +9 done ?!LINT: missing '&&'?!
9 10
10 11 while true; do
11 12 echo foo &&
12 -13 cat bar ?!LOOP?!
12 +13 cat bar ?!LINT: missing '|| exit 1'?!
13 14 done
14 15 )