Merge branch 'es/chainlint'

The chainlint test script linter in the test suite has been updated.

* es/chainlint:
  chainlint.sed: stop splitting "(..." into separate lines "(" and "..."
  chainlint.sed: swallow comments consistently
  chainlint.sed: stop throwing away here-doc tags
  chainlint.sed: don't mistake `<< word` in string as here-doc operator
  chainlint.sed: make here-doc "<<-" operator recognition more POSIX-like
  chainlint.sed: drop subshell-closing ">" annotation
  chainlint.sed: drop unnecessary distinction between ?!AMP?! and ?!SEMI?!
  chainlint.sed: tolerate harmless ";" at end of last line in block
  chainlint.sed: improve ?!SEMI?! placement accuracy
  chainlint.sed: improve ?!AMP?! placement accuracy
  t/Makefile: optimize chainlint self-test
  t/chainlint/one-liner: avoid overly intimate chainlint.sed knowledge
  t/chainlint/*.test: generalize self-test commentary
  t/chainlint/*.test: fix invalid test cases due to mixing quote types
  t/chainlint/*.test: don't use invalid shell syntax
This commit is contained in:
Junio C Hamano 2021-12-22 22:48:11 -08:00
commit d52da62801
71 changed files with 339 additions and 293 deletions

@ -71,12 +71,10 @@ clean-chainlint:
check-chainlint: check-chainlint:
@mkdir -p '$(CHAINLINTTMP_SQ)' && \ @mkdir -p '$(CHAINLINTTMP_SQ)' && \
err=0 && \ sed -e '/^# LINT: /d' $(patsubst %,chainlint/%.test,$(CHAINLINTTESTS)) >'$(CHAINLINTTMP_SQ)'/tests && \
for i in $(CHAINLINTTESTS); do \ sed -e '/^[ ]*$$/d' $(patsubst %,chainlint/%.expect,$(CHAINLINTTESTS)) >'$(CHAINLINTTMP_SQ)'/expect && \
$(CHAINLINT) <chainlint/$$i.test | \ $(CHAINLINT) '$(CHAINLINTTMP_SQ)'/tests | grep -v '^[ ]*$$' >'$(CHAINLINTTMP_SQ)'/actual && \
sed -e '/^# LINT: /d' >'$(CHAINLINTTMP_SQ)'/$$i.actual && \ diff -u '$(CHAINLINTTMP_SQ)'/expect '$(CHAINLINTTMP_SQ)'/actual
diff -u chainlint/$$i.expect '$(CHAINLINTTMP_SQ)'/$$i.actual || err=1; \
done && exit $$err
test-lint: test-lint-duplicates test-lint-executable test-lint-shell-syntax \ test-lint: test-lint-duplicates test-lint-executable test-lint-shell-syntax \
test-lint-filenames test-lint-filenames

@ -24,9 +24,9 @@
# in order to avoid misinterpreting the ")" in constructs such as "x=$(...)" # in order to avoid misinterpreting the ")" in constructs such as "x=$(...)"
# and "case $x in *)" as ending the subshell. # and "case $x in *)" as ending the subshell.
# #
# Lines missing a final "&&" are flagged with "?!AMP?!", and lines which chain # Lines missing a final "&&" are flagged with "?!AMP?!", as are lines which
# commands with ";" internally rather than "&&" are flagged "?!SEMI?!". A line # chain commands with ";" internally rather than "&&". A line may be flagged
# may be flagged for both violations. # for both violations.
# #
# Detection of a missing &&-link in a multi-line subshell is complicated by the # Detection of a missing &&-link in a multi-line subshell is complicated by the
# fact that the last statement before the closing ")" must not end with "&&". # fact that the last statement before the closing ")" must not end with "&&".
@ -47,8 +47,8 @@
# "?!AMP?!" violation is removed from the "bar" line (retrieved from the "hold" # "?!AMP?!" violation is removed from the "bar" line (retrieved from the "hold"
# area) since the final statement of a subshell must not end with "&&". The # area) since the final statement of a subshell must not end with "&&". The
# final line of a subshell may still break the &&-chain by using ";" internally # final line of a subshell may still break the &&-chain by using ";" internally
# to chain commands together rather than "&&", so "?!SEMI?!" is never removed # to chain commands together rather than "&&", but an internal "?!AMP?!" is
# from a line (even though "?!AMP?!" might be). # never removed from a line even though a line-ending "?!AMP?!" might be.
# #
# Care is taken to recognize the last _statement_ of a multi-line subshell, not # Care is taken to recognize the last _statement_ of a multi-line subshell, not
# necessarily the last textual _line_ within the subshell, since &&-chaining # necessarily the last textual _line_ within the subshell, since &&-chaining
@ -62,26 +62,20 @@
# receives similar treatment. # receives similar treatment.
# #
# Swallowing here-docs with arbitrary tags requires a bit of finesse. When a # Swallowing here-docs with arbitrary tags requires a bit of finesse. When a
# line such as "cat <<EOF >out" is seen, the here-doc tag is moved to the front # line such as "cat <<EOF" is seen, the here-doc tag is copied to the front of
# of the line enclosed in angle brackets as a sentinel, giving "<EOF>cat >out". # the line enclosed in angle brackets as a sentinel, giving "<EOF>cat <<EOF".
# As each subsequent line is read, it is appended to the target line and a # As each subsequent line is read, it is appended to the target line and a
# (whitespace-loose) back-reference match /^<(.*)>\n\1$/ is attempted to see if # (whitespace-loose) back-reference match /^<(.*)>\n\1$/ is attempted to see if
# the content inside "<...>" matches the entirety of the newly-read line. For # the content inside "<...>" matches the entirety of the newly-read line. For
# instance, if the next line read is "some data", when concatenated with the # instance, if the next line read is "some data", when concatenated with the
# target line, it becomes "<EOF>cat >out\nsome data", and a match is attempted # target line, it becomes "<EOF>cat <<EOF\nsome data", and a match is attempted
# to see if "EOF" matches "some data". Since it doesn't, the next line is # to see if "EOF" matches "some data". Since it doesn't, the next line is
# attempted. When a line consisting of only "EOF" (and possible whitespace) is # attempted. When a line consisting of only "EOF" (and possible whitespace) is
# encountered, it is appended to the target line giving "<EOF>cat >out\nEOF", # encountered, it is appended to the target line giving "<EOF>cat <<EOF\nEOF",
# in which case the "EOF" inside "<...>" does match the text following the # in which case the "EOF" inside "<...>" does match the text following the
# newline, thus the closing here-doc tag has been found. The closing tag line # newline, thus the closing here-doc tag has been found. The closing tag line
# and the "<...>" prefix on the target line are then discarded, leaving just # and the "<...>" prefix on the target line are then discarded, leaving just
# the target line "cat >out". # the target line "cat <<EOF".
#
# To facilitate regression testing (and manual debugging), a ">" annotation is
# applied to the line containing ")" which closes a subshell, ">>" to a line
# closing a nested subshell, and ">>>" to a line closing both at once. This
# makes it easy to detect whether the heuristics correctly identify
# end-of-subshell.
#------------------------------------------------------------------------------ #------------------------------------------------------------------------------
# incomplete line -- slurp up next line # incomplete line -- slurp up next line
@ -94,9 +88,9 @@
# here-doc -- swallow it to avoid false hits within its body (but keep the # here-doc -- swallow it to avoid false hits within its body (but keep the
# command to which it was attached) # command to which it was attached)
/<<[ ]*[-\\'"]*[A-Za-z0-9_]/ { /<<-*[ ]*[\\'"]*[A-Za-z0-9_]/ {
s/^\(.*\)<<[ ]*[-\\'"]*\([A-Za-z0-9_][A-Za-z0-9_]*\)['"]*/<\2>\1<</ /"[^"]*<<[^"]*"/bnotdoc
s/[ ]*<<// s/^\(.*<<-*[ ]*\)[\\'"]*\([A-Za-z0-9_][A-Za-z0-9_]*\)['"]*/<\2>\1\2/
:hered :hered
N N
/^<\([^>]*\)>.*\n[ ]*\1[ ]*$/!{ /^<\([^>]*\)>.*\n[ ]*\1[ ]*$/!{
@ -106,6 +100,7 @@
s/^<[^>]*>// s/^<[^>]*>//
s/\n.*$// s/\n.*$//
} }
:notdoc
# one-liner "(...) &&" # one-liner "(...) &&"
/^[ ]*!*[ ]*(..*)[ ]*&&[ ]*$/boneline /^[ ]*!*[ ]*(..*)[ ]*&&[ ]*$/boneline
@ -126,7 +121,7 @@ b
# "&&" (but not ";" in a string) # "&&" (but not ";" in a string)
:oneline :oneline
/;/{ /;/{
/"[^"]*;[^"]*"/!s/^/?!SEMI?!/ /"[^"]*;[^"]*"/!s/;/; ?!AMP?!/
} }
b b
@ -136,11 +131,15 @@ b
h h
bnextln bnextln
} }
# "(..." line -- split off and stash "(", then process "..." as its own line # "(..." line -- "(" opening subshell cuddled with command; temporarily replace
# "(" with sentinel "^" and process the line as if "(" had been seen solo on
# the preceding line; this temporary replacement prevents several rules from
# accidentally thinking "(" introduces a nested subshell; "^" is changed back
# to "(" at output time
x x
s/.*/(/ s/.*//
x x
s/(// s/(/^/
bslurp bslurp
:nextln :nextln
@ -157,8 +156,10 @@ s/.*\n//
/"[^'"]*'[^'"]*"/!bsqstr /"[^'"]*'[^'"]*"/!bsqstr
} }
:folded :folded
# here-doc -- swallow it # here-doc -- swallow it (but not "<<" in a string)
/<<[ ]*[-\\'"]*[A-Za-z0-9_]/bheredoc /<<-*[ ]*[\\'"]*[A-Za-z0-9_]/{
/"[^"]*<<[^"]*"/!bheredoc
}
# comment or empty line -- discard since final non-comment, non-empty line # comment or empty line -- discard since final non-comment, non-empty line
# before closing ")", "done", "elsif", "else", or "fi" will need to be # before closing ")", "done", "elsif", "else", or "fi" will need to be
# re-visited to drop "suspect" marking since final line of those constructs # re-visited to drop "suspect" marking since final line of those constructs
@ -171,12 +172,12 @@ s/.*\n//
/"[^"]*#[^"]*"/!s/[ ]#.*$// /"[^"]*#[^"]*"/!s/[ ]#.*$//
} }
# one-liner "case ... esac" # one-liner "case ... esac"
/^[ ]*case[ ]*..*esac/bchkchn /^[ ^]*case[ ]*..*esac/bchkchn
# multi-line "case ... esac" # multi-line "case ... esac"
/^[ ]*case[ ]..*[ ]in/bcase /^[ ^]*case[ ]..*[ ]in/bcase
# multi-line "for ... done" or "while ... done" # multi-line "for ... done" or "while ... done"
/^[ ]*for[ ]..*[ ]in/bcont /^[ ^]*for[ ]..*[ ]in/bcont
/^[ ]*while[ ]/bcont /^[ ^]*while[ ]/bcont
/^[ ]*do[ ]/bcont /^[ ]*do[ ]/bcont
/^[ ]*do[ ]*$/bcont /^[ ]*do[ ]*$/bcont
/;[ ]*do/bcont /;[ ]*do/bcont
@ -187,7 +188,7 @@ s/.*\n//
/||[ ]*exit[ ]/bcont /||[ ]*exit[ ]/bcont
/||[ ]*exit[ ]*$/bcont /||[ ]*exit[ ]*$/bcont
# multi-line "if...elsif...else...fi" # multi-line "if...elsif...else...fi"
/^[ ]*if[ ]/bcont /^[ ^]*if[ ]/bcont
/^[ ]*then[ ]/bcont /^[ ]*then[ ]/bcont
/^[ ]*then[ ]*$/bcont /^[ ]*then[ ]*$/bcont
/;[ ]*then/bcont /;[ ]*then/bcont
@ -200,15 +201,15 @@ s/.*\n//
/^[ ]*fi[ ]*[<>|]/bdone /^[ ]*fi[ ]*[<>|]/bdone
/^[ ]*fi[ ]*)/bdone /^[ ]*fi[ ]*)/bdone
# nested one-liner "(...) &&" # nested one-liner "(...) &&"
/^[ ]*(.*)[ ]*&&[ ]*$/bchkchn /^[ ^]*(.*)[ ]*&&[ ]*$/bchkchn
# nested one-liner "(...)" # nested one-liner "(...)"
/^[ ]*(.*)[ ]*$/bchkchn /^[ ^]*(.*)[ ]*$/bchkchn
# nested one-liner "(...) >x" (or "2>x" or "<x" or "|x") # nested one-liner "(...) >x" (or "2>x" or "<x" or "|x")
/^[ ]*(.*)[ ]*[0-9]*[<>|]/bchkchn /^[ ^]*(.*)[ ]*[0-9]*[<>|]/bchkchn
# nested multi-line "(...\n...)" # nested multi-line "(...\n...)"
/^[ ]*(/bnest /^[ ^]*(/bnest
# multi-line "{...\n...}" # multi-line "{...\n...}"
/^[ ]*{/bblock /^[ ^]*{/bblock
# closing ")" on own line -- exit subshell # closing ")" on own line -- exit subshell
/^[ ]*)/bclssolo /^[ ]*)/bclssolo
# "$((...))" -- arithmetic expansion; not closing ")" # "$((...))" -- arithmetic expansion; not closing ")"
@ -230,16 +231,18 @@ s/.*\n//
# string and not ";;" in one-liner "case...esac") # string and not ";;" in one-liner "case...esac")
/;/{ /;/{
/;;/!{ /;;/!{
/"[^"]*;[^"]*"/!s/^/?!SEMI?!/ /"[^"]*;[^"]*"/!s/;/; ?!AMP?!/
} }
} }
# line ends with pipe "...|" -- valid; not missing "&&" # line ends with pipe "...|" -- valid; not missing "&&"
/|[ ]*$/bcont /|[ ]*$/bcont
# missing end-of-line "&&" -- mark suspect # missing end-of-line "&&" -- mark suspect
/&&[ ]*$/!s/^/?!AMP?!/ /&&[ ]*$/!s/$/ ?!AMP?!/
:cont :cont
# retrieve and print previous line # retrieve and print previous line
x x
s/^\([ ]*\)^/\1(/
s/?!HERE?!/<</g
n n
bslurp bslurp
@ -280,8 +283,7 @@ bfolded
# found here-doc -- swallow it to avoid false hits within its body (but keep # found here-doc -- swallow it to avoid false hits within its body (but keep
# the command to which it was attached) # the command to which it was attached)
:heredoc :heredoc
s/^\(.*\)<<[ ]*[-\\'"]*\([A-Za-z0-9_][A-Za-z0-9_]*\)['"]*/<\2>\1<</ s/^\(.*\)<<\(-*[ ]*\)[\\'"]*\([A-Za-z0-9_][A-Za-z0-9_]*\)['"]*/<\3>\1?!HERE?!\2\3/
s/[ ]*<<//
:hdocsub :hdocsub
N N
/^<\([^>]*\)>.*\n[ ]*\1[ ]*$/!{ /^<\([^>]*\)>.*\n[ ]*\1[ ]*$/!{
@ -295,7 +297,15 @@ bfolded
# found "case ... in" -- pass through untouched # found "case ... in" -- pass through untouched
:case :case
x x
s/^\([ ]*\)^/\1(/
s/?!HERE?!/<</g
n n
:cascom
/^[ ]*#/{
N
s/.*\n//
bcascom
}
/^[ ]*esac/bslurp /^[ ]*esac/bslurp
bcase bcase
@ -303,7 +313,7 @@ bcase
# that line legitimately lacks "&&" # that line legitimately lacks "&&"
:else :else
x x
s/?!AMP?!// s/\( ?!AMP?!\)* ?!AMP?!$//
x x
bcont bcont
@ -311,7 +321,7 @@ bcont
# "suspect" from final contained line since that line legitimately lacks "&&" # "suspect" from final contained line since that line legitimately lacks "&&"
:done :done
x x
s/?!AMP?!// s/\( ?!AMP?!\)* ?!AMP?!$//
x x
# is 'done' or 'fi' cuddled with ")" to close subshell? # is 'done' or 'fi' cuddled with ")" to close subshell?
/done.*)/bclose /done.*)/bclose
@ -322,11 +332,18 @@ bchkchn
:nest :nest
x x
:nstslrp :nstslrp
s/^\([ ]*\)^/\1(/
s/?!HERE?!/<</g
n n
:nstcom
# comment -- not closing ")" if in comment
/^[ ]*#/{
N
s/.*\n//
bnstcom
}
# closing ")" on own line -- stop nested slurp # closing ")" on own line -- stop nested slurp
/^[ ]*)/bnstcl /^[ ]*)/bnstcl
# comment -- not closing ")" if in comment
/^[ ]*#/bnstcnt
# "$((...))" -- arithmetic expansion; not closing ")" # "$((...))" -- arithmetic expansion; not closing ")"
/\$(([^)][^)]*))[^)]*$/bnstcnt /\$(([^)][^)]*))[^)]*$/bnstcnt
# "$(...)" -- command substitution; not closing ")" # "$(...)" -- command substitution; not closing ")"
@ -337,7 +354,6 @@ n
x x
bnstslrp bnstslrp
:nstcl :nstcl
s/^/>>/
# is it "))" which closes nested and parent subshells? # is it "))" which closes nested and parent subshells?
/)[ ]*)/bslurp /)[ ]*)/bslurp
bchkchn bchkchn
@ -345,7 +361,15 @@ bchkchn
# found multi-line "{...\n...}" block -- pass through untouched # found multi-line "{...\n...}" block -- pass through untouched
:block :block
x x
s/^\([ ]*\)^/\1(/
s/?!HERE?!/<</g
n n
:blkcom
/^[ ]*#/{
N
s/.*\n//
bblkcom
}
# closing "}" -- stop block slurp # closing "}" -- stop block slurp
/}/bchkchn /}/bchkchn
bblock bblock
@ -354,16 +378,22 @@ bblock
# since that line legitimately lacks "&&" and exit subshell loop # since that line legitimately lacks "&&" and exit subshell loop
:clssolo :clssolo
x x
s/?!AMP?!// s/\( ?!AMP?!\)* ?!AMP?!$//
s/^\([ ]*\)^/\1(/
s/?!HERE?!/<</g
p p
x x
s/^/>/ s/^\([ ]*\)^/\1(/
s/?!HERE?!/<</g
b b
# found closing "...)" -- exit subshell loop # found closing "...)" -- exit subshell loop
:close :close
x x
s/^\([ ]*\)^/\1(/
s/?!HERE?!/<</g
p p
x x
s/^/>/ s/^\([ ]*\)^/\1(/
s/?!HERE?!/<</g
b b

@ -2,8 +2,8 @@
foo && foo &&
bar=$((42 + 1)) && bar=$((42 + 1)) &&
baz baz
>) && ) &&
( (
?!AMP?! bar=$((42 + 1)) bar=$((42 + 1)) ?!AMP?!
baz baz
>) )

@ -2,9 +2,9 @@
foo && foo &&
bar=(gumbo stumbo wumbo) && bar=(gumbo stumbo wumbo) &&
baz baz
>) && ) &&
( (
foo && foo &&
bar=${#bar[@]} && bar=${#bar[@]} &&
baz baz
>) )

@ -1,4 +1,4 @@
( (
nothing && nothing &&
something something
>) )

@ -3,7 +3,7 @@
nothing && nothing &&
something something
# LINT: swallow blank lines since final _statement_ before subshell end is # LINT: ignore blank lines since final _statement_ before subshell end is
# LINT: significant to "&&"-check, not final _line_ (which might be blank) # LINT: significant to "&&"-check, not final _line_ (which might be blank)

@ -0,0 +1,6 @@
(
{
echo a &&
echo b
}
)

@ -0,0 +1,8 @@
(
{
# show a
echo a &&
# show b
echo b
}
)

@ -7,6 +7,6 @@
bar && bar &&
{ {
echo c echo c
?!AMP?! } } ?!AMP?!
baz baz
>) )

@ -1,6 +1,5 @@
( (
# LINT: missing "&&" in block not currently detected (for consistency with # LINT: missing "&&" after first "echo"
# LINT: --chain-lint at top level and to provide escape hatch if needed)
foo && foo &&
{ {
echo a echo a

@ -1,6 +1,6 @@
( (
foo && foo &&
?!AMP?! bar bar ?!AMP?!
baz && baz &&
wop wop
>) )

@ -1,6 +1,6 @@
( (
foo && foo &&
# LINT: missing "&&" from 'bar' # LINT: missing "&&" from "bar"
bar bar
baz && baz &&
# LINT: final statement before closing ")" legitimately lacks "&&" # LINT: final statement before closing ")" legitimately lacks "&&"

@ -0,0 +1,8 @@
(
case "$x" in
x) foo ;;
*)
bar
;;
esac
)

@ -0,0 +1,11 @@
(
case "$x" in
# found foo
x) foo ;;
# found other
*)
# treat it as bar
bar
;;
esac
)

@ -4,16 +4,16 @@
*) bar ;; *) bar ;;
esac && esac &&
foobar foobar
>) && ) &&
( (
case "$x" in case "$x" in
x) foo ;; x) foo ;;
*) bar ;; *) bar ;;
?!AMP?! esac esac ?!AMP?!
foobar foobar
>) && ) &&
( (
case "$x" in 1) true;; esac && case "$x" in 1) true;; esac &&
?!AMP?! case "$y" in 2) false;; esac case "$y" in 2) false;; esac ?!AMP?!
foobar foobar
>) )

@ -1,5 +1,5 @@
( (
# LINT: "...)" arms in 'case' not misinterpreted as subshell-closing ")" # LINT: "...)" arms in "case" not misinterpreted as subshell-closing ")"
case "$x" in case "$x" in
x) foo ;; x) foo ;;
*) bar ;; *) bar ;;
@ -7,7 +7,7 @@
foobar foobar
) && ) &&
( (
# LINT: missing "&&" on 'esac' # LINT: missing "&&" on "esac"
case "$x" in case "$x" in
x) foo ;; x) foo ;;
*) bar ;; *) bar ;;
@ -15,7 +15,7 @@
foobar foobar
) && ) &&
( (
# LINT: "...)" arm in one-liner 'case' not misinterpreted as closing ")" # LINT: "...)" arm in one-liner "case" not misinterpreted as closing ")"
case "$x" in 1) true;; esac && case "$x" in 1) true;; esac &&
# LINT: same but missing "&&" # LINT: same but missing "&&"
case "$y" in 2) false;; esac case "$y" in 2) false;; esac

@ -1,4 +1,3 @@
( (cd foo &&
cd foo &&
(bar && (bar &&
>>> baz)) baz))

@ -1,25 +1,25 @@
( (
foo foo
>) && ) &&
( (
bar bar
>) >out && ) >out &&
( (
baz baz
>) 2>err && ) 2>err &&
( (
boo boo
>) <input && ) <input &&
( (
bip bip
>) | wuzzle && ) | wuzzle &&
( (
bop bop
>) | fazz fozz && ) | fazz fozz &&
( (
bup bup
>) | ) |
fuzzle && fuzzle &&
( (
yop yop
>) )

@ -2,8 +2,8 @@
foo && foo &&
bar=$(gobble) && bar=$(gobble) &&
baz baz
>) && ) &&
( (
?!AMP?! bar=$(gobble blocks) bar=$(gobble blocks) ?!AMP?!
baz baz
>) )

@ -1,4 +1,4 @@
( (
nothing && nothing &&
something something
>) )

@ -1,10 +1,9 @@
( (for i in a b c; do
for i in a b c; do
if test "$(echo $(waffle bat))" = "eleventeen" && if test "$(echo $(waffle bat))" = "eleventeen" &&
test "$x" = "$y"; then test "$x" = "$y"; then
: :
else else
echo >file echo >file
fi fi
> done) && done) &&
test ! -f file test ! -f file

@ -1,4 +1,4 @@
# LINT: 'for' loop cuddled with "(" and ")" and nested 'if' with complex # LINT: "for" loop cuddled with "(" and ")" and nested "if" with complex
# LINT: multi-line condition; indented with spaces, not tabs # LINT: multi-line condition; indented with spaces, not tabs
(for i in a b c; do (for i in a b c; do
if test "$(echo $(waffle bat))" = "eleventeen" && if test "$(echo $(waffle bat))" = "eleventeen" &&

@ -1,7 +1,6 @@
( (if test -z ""; then
if test -z ""; then
echo empty echo empty
else else
echo bizzy echo bizzy
> fi) && fi) &&
echo foobar echo foobar

@ -1,4 +1,4 @@
# LINT: 'if' cuddled with "(" and ")"; indented with spaces, not tabs # LINT: "if" cuddled with "(" and ")"; indented with spaces, not tabs
(if test -z ""; then (if test -z ""; then
echo empty echo empty
else else

@ -1,5 +1,4 @@
( ( while read x
while read x
do foobar bop || exit 1 do foobar bop || exit 1
> done <file ) && done <file ) &&
outside subshell outside subshell

@ -1,4 +1,4 @@
# LINT: 'while' loop cuddled with "(" and ")", with embedded (allowed) # LINT: "while" loop cuddled with "(" and ")", with embedded (allowed)
# LINT: "|| exit {n}" to exit loop early, and using redirection "<" to feed # LINT: "|| exit {n}" to exit loop early, and using redirection "<" to feed
# LINT: loop; indented with spaces, not tabs # LINT: loop; indented with spaces, not tabs
( while read x ( while read x

@ -1,21 +1,17 @@
( (cd foo &&
cd foo &&
bar bar
>) && ) &&
( (cd foo ?!AMP?!
?!AMP?!cd foo
bar bar
>) && ) &&
( (
cd foo && cd foo &&
> bar) && bar) &&
( (cd foo &&
cd foo && bar) &&
> bar) &&
( (cd foo ?!AMP?!
?!AMP?!cd foo bar)
> bar)

@ -1,5 +1,4 @@
# LINT: first subshell statement cuddled with opening "("; for implementation # LINT: first subshell statement cuddled with opening "("
# LINT: simplicity, "(..." is split into two lines, "(" and "..."
(cd foo && (cd foo &&
bar bar
) && ) &&

@ -5,7 +5,7 @@
bar && bar &&
baz baz
done done
>) && ) &&
( (
while true while true
do do
@ -13,7 +13,7 @@
bar && bar &&
baz baz
done done
>) && ) &&
( (
i=0 && i=0 &&
while test $i -lt 10 while test $i -lt 10
@ -21,4 +21,4 @@
echo $i || exit echo $i || exit
i=$(($i + 1)) i=$(($i + 1))
done done
>) )

@ -2,4 +2,4 @@
foo || exit 1 foo || exit 1
bar && bar &&
baz baz
>) )

@ -1,11 +1,11 @@
( (
for i in a b c for i in a b c
do do
?!AMP?! echo $i echo $i ?!AMP?!
cat cat <<-EOF
?!AMP?! done done ?!AMP?!
for i in a b c; do for i in a b c; do
echo $i && echo $i &&
cat $i cat $i
done done
>) )

@ -1,17 +1,17 @@
( (
# LINT: 'for', 'do', 'done' do not need "&&" # LINT: "for", "do", "done" do not need "&&"
for i in a b c for i in a b c
do do
# LINT: missing "&&" on 'echo' # LINT: missing "&&" on "echo"
echo $i echo $i
# LINT: last statement of while does not need "&&" # LINT: last statement of while does not need "&&"
cat <<-\EOF cat <<-\EOF
bar bar
EOF EOF
# LINT: missing "&&" on 'done' # LINT: missing "&&" on "done"
done done
# LINT: 'do' on same line as 'for' # LINT: "do" on same line as "for"
for i in a b c; do for i in a b c; do
echo $i && echo $i &&
cat $i cat $i

@ -1,2 +1,2 @@
( (
> cat) cat <<-INPUT)

@ -1,5 +1,5 @@
( (
x=$(bobble && x=$(bobble <<-END &&
?!AMP?!>> wiffle) wiffle) ?!AMP?!
echo $x echo $x
>) )

@ -1,4 +1,4 @@
( (
?!AMP?! cat && echo "multi-line string" cat <<-TXT && echo "multi-line string" ?!AMP?!
bap bap
>) )

@ -1,9 +1,7 @@
boodle wobba gorgo snoot wafta snurb && boodle wobba gorgo snoot wafta snurb <<EOF &&
cat >foo && cat <<-Arbitrary_Tag_42 >foo &&
cat >bar && cat <<zump >boo &&
cat >boo && horticulture <<EOF
horticulture

@ -14,13 +14,6 @@ boz
woz woz
Arbitrary_Tag_42 Arbitrary_Tag_42
# LINT: swallow 'quoted' here-doc
cat <<'FUMP' >bar &&
snoz
boz
woz
FUMP
# LINT: swallow "quoted" here-doc # LINT: swallow "quoted" here-doc
cat <<"zump" >boo && cat <<"zump" >boo &&
snoz snoz

@ -3,10 +3,10 @@
do do
if false if false
then then
?!AMP?! echo "err" echo "err" ?!AMP?!
exit 1 exit 1
?!AMP?! fi fi ?!AMP?!
foo foo
?!AMP?! done done ?!AMP?!
bar bar
>) )

@ -3,13 +3,13 @@
do do
if false if false
then then
# LINT: missing "&&" on 'echo' # LINT: missing "&&" on "echo"
echo "err" echo "err"
exit 1 exit 1
# LINT: missing "&&" on 'fi' # LINT: missing "&&" on "fi"
fi fi
foo foo
# LINT: missing "&&" on 'done' # LINT: missing "&&" on "done"
done done
bar bar
) )

@ -1,19 +1,20 @@
( (
if test -n "" if test -n ""
then then
?!AMP?! echo very echo very ?!AMP?!
echo empty echo empty
elif test -z "" elif test -z ""
then
echo foo echo foo
else else
echo foo && echo foo &&
cat cat <<-EOF
?!AMP?! fi fi ?!AMP?!
echo poodle echo poodle
>) && ) &&
( (
if test -n ""; then if test -n ""; then
echo very && echo very &&
?!AMP?! echo empty echo empty
if fi
>) )

@ -1,28 +1,29 @@
( (
# LINT: 'if', 'then', 'elif', 'else', 'fi' do not need "&&" # LINT: "if", "then", "elif", "else", "fi" do not need "&&"
if test -n "" if test -n ""
then then
# LINT: missing "&&" on 'echo' # LINT: missing "&&" on "echo"
echo very echo very
# LINT: last statement before 'elif' does not need "&&" # LINT: last statement before "elif" does not need "&&"
echo empty echo empty
elif test -z "" elif test -z ""
# LINT: last statement before 'else' does not need "&&" then
# LINT: last statement before "else" does not need "&&"
echo foo echo foo
else else
echo foo && echo foo &&
# LINT: last statement before 'fi' does not need "&&" # LINT: last statement before "fi" does not need "&&"
cat <<-\EOF cat <<-\EOF
bar bar
EOF EOF
# LINT: missing "&&" on 'fi' # LINT: missing "&&" on "fi"
fi fi
echo poodle echo poodle
) && ) &&
( (
# LINT: 'then' on same line as 'if' # LINT: "then" on same line as "if"
if test -n ""; then if test -n ""; then
echo very && echo very &&
echo empty echo empty
if fi
) )

@ -1,4 +1,4 @@
line 1 line 2 line 3 line 4 && line 1 line 2 line 3 line 4 &&
( (
line 5 line 6 line 7 line 8 line 5 line 6 line 7 line 8
>) )

@ -1,9 +1,8 @@
( (
foobar && foobar &&
?!AMP?! barfoo barfoo ?!AMP?!
flibble "not a # comment" flibble "not a # comment"
>) && ) &&
( (cd foo &&
cd foo && flibble "not a # comment")
> flibble "not a # comment")

@ -3,10 +3,10 @@
then then
while true while true
do do
?!AMP?! echo "pop" echo "pop" ?!AMP?!
echo "glup" echo "glup"
?!AMP?! done done ?!AMP?!
foo foo
?!AMP?! fi fi ?!AMP?!
bar bar
>) )

@ -3,13 +3,13 @@
then then
while true while true
do do
# LINT: missing "&&" on 'echo' # LINT: missing "&&" on "echo"
echo "pop" echo "pop"
echo "glup" echo "glup"
# LINT: missing "&&" on 'done' # LINT: missing "&&" on "done"
done done
foo foo
# LINT: missing "&&" on 'fi' # LINT: missing "&&" on "fi"
fi fi
bar bar
) )

@ -3,16 +3,16 @@
x=$( x=$(
echo bar | echo bar |
cat cat
>> ) && ) &&
echo ok echo ok
>) | ) |
sort && sort &&
( (
bar && bar &&
x=$(echo bar | x=$(echo bar |
cat cat
>> ) && ) &&
y=$(echo baz | y=$(echo baz |
>> fip) && fip) &&
echo fail echo fail
>) )

@ -1,15 +1,9 @@
( (
x="line 1 line 2 line 3" && x="line 1 line 2 line 3" &&
?!AMP?! y='line 1 line2' y="line 1 line2" ?!AMP?!
foobar foobar
>) && ) &&
(
echo "there's nothing to see here" &&
exit
>) &&
( (
echo "xyz" "abc def ghi" && echo "xyz" "abc def ghi" &&
echo 'xyz' 'abc def ghi' &&
echo 'xyz' "abc def ghi" &&
barfoo barfoo
>) )

@ -3,25 +3,13 @@
line 2 line 2
line 3" && line 3" &&
# LINT: missing "&&" on assignment # LINT: missing "&&" on assignment
y='line 1 y="line 1
line2' line2"
foobar foobar
) && ) &&
(
# LINT: apostrophe (in a contraction) within string not misinterpreted as
# LINT: starting multi-line single-quoted string
echo "there's nothing to see here" &&
exit
) &&
( (
echo "xyz" "abc echo "xyz" "abc
def def
ghi" && ghi" &&
echo 'xyz' 'abc
def
ghi' &&
echo 'xyz' "abc
def
ghi" &&
barfoo barfoo
) )

@ -1,5 +1,5 @@
! (foo && bar) && ! (foo && bar) &&
! (foo && bar) >baz && ! (foo && bar) >baz &&
?!SEMI?!! (foo; bar) && ! (foo; ?!AMP?! bar) &&
?!SEMI?!! (foo; bar) >baz ! (foo; ?!AMP?! bar) >baz

@ -1,19 +1,19 @@
( (
(cd foo && (cd foo &&
bar bar
>> ) && ) &&
(cd foo && (cd foo &&
bar bar
?!AMP?!>> ) ) ?!AMP?!
( (
cd foo && cd foo &&
>> bar) && bar) &&
( (
cd foo && cd foo &&
?!AMP?!>> bar) bar) ?!AMP?!
(cd foo && (cd foo &&
>> bar) && bar) &&
(cd foo && (cd foo &&
?!AMP?!>> bar) bar) ?!AMP?!
foobar foobar
>) )

@ -1,7 +1,7 @@
cat >foop && cat <<ARBITRARY >foop &&
( (
cat && cat <<-INPUT_END &&
?!AMP?! cat cat <<-EOT ?!AMP?!
foobar foobar
>) )

@ -2,10 +2,8 @@
foo && foo &&
( (
bar && bar &&
# bottles wobble while fiddles gobble
# minor numbers of cows (or do they?)
baz && baz &&
snaff snaff
?!AMP?!>> ) ) ?!AMP?!
fuzzy fuzzy
>) )

@ -7,7 +7,7 @@
# minor numbers of cows (or do they?) # minor numbers of cows (or do they?)
baz && baz &&
snaff snaff
# LINT: missing "&&" on ')' # LINT: missing "&&" on ")"
) )
fuzzy fuzzy
) )

@ -3,10 +3,10 @@
( (
echo a && echo a &&
echo b echo b
>> ) >file && ) >file &&
cd foo && cd foo &&
( (
echo a echo a
echo b echo b
>> ) >file ) >file
>) )

@ -7,7 +7,6 @@
cd foo && cd foo &&
( (
# LINT: nested multi-line subshell not presently checked for missing "&&"
echo a echo a
echo b echo b
) >file ) >file

@ -0,0 +1,14 @@
echo "<<<<<<< ours" &&
echo ourside &&
echo "=======" &&
echo theirside &&
echo ">>>>>>> theirs" &&
(
echo "<<<<<<< ours" &&
echo ourside &&
echo "=======" &&
echo theirside &&
echo ">>>>>>> theirs" ?!AMP?!
poodle
) >merged

@ -0,0 +1,16 @@
# LINT: "<< ours" inside string is not here-doc
echo "<<<<<<< ours" &&
echo ourside &&
echo "=======" &&
echo theirside &&
echo ">>>>>>> theirs" &&
(
# LINT: "<< ours" inside string is not here-doc
echo "<<<<<<< ours" &&
echo ourside &&
echo "=======" &&
echo theirside &&
echo ">>>>>>> theirs"
poodle
) >merged

@ -2,8 +2,8 @@
(foo && bar) | (foo && bar) |
(foo && bar) >baz && (foo && bar) >baz &&
?!SEMI?!(foo; bar) && (foo; ?!AMP?! bar) &&
?!SEMI?!(foo; bar) | (foo; ?!AMP?! bar) |
?!SEMI?!(foo; bar) >baz (foo; ?!AMP?! bar) >baz &&
(foo "bar; baz") (foo "bar; baz")

@ -3,10 +3,10 @@
(foo && bar) | (foo && bar) |
(foo && bar) >baz && (foo && bar) >baz &&
# LINT: top-level one-liner subshell missing internal "&&" # LINT: top-level one-liner subshell missing internal "&&" and broken &&-chain
(foo; bar) && (foo; bar) &&
(foo; bar) | (foo; bar) |
(foo; bar) >baz (foo; bar) >baz &&
# LINT: ";" in string not misinterpreted as broken &&-chain # LINT: ";" in string not misinterpreted as broken &&-chain
(foo "bar; baz") (foo "bar; baz")

@ -1,4 +1,4 @@
( (
p4 print -1 //depot/fiddle#42 >file && p4 print -1 //depot/fiddle#42 >file &&
foobar foobar
>) )

@ -3,6 +3,6 @@
bar | bar |
baz && baz &&
fish | fish |
?!AMP?! cow cow ?!AMP?!
sunder sunder
>) )

@ -4,7 +4,7 @@
bar | bar |
baz && baz &&
# LINT: final line of pipe sequence ('cow') lacking "&&" # LINT: final line of pipe sequence ("cow") lacking "&&"
fish | fish |
cow cow

@ -1,20 +1,19 @@
( (
?!AMP?!?!SEMI?! cat foo ; echo bar cat foo ; ?!AMP?! echo bar ?!AMP?!
?!SEMI?! cat foo ; echo bar cat foo ; ?!AMP?! echo bar
>) && ) &&
( (
?!SEMI?! cat foo ; echo bar && cat foo ; ?!AMP?! echo bar &&
?!SEMI?! cat foo ; echo bar cat foo ; ?!AMP?! echo bar
>) && ) &&
( (
echo "foo; bar" && echo "foo; bar" &&
?!SEMI?! cat foo; echo bar cat foo; ?!AMP?! echo bar
>) && ) &&
( (
?!SEMI?! foo; foo;
>) && ) &&
( (cd foo &&
cd foo &&
for i in a b c; do for i in a b c; do
?!SEMI?! echo; echo;
> done) done)

@ -15,11 +15,11 @@
cat foo; echo bar cat foo; echo bar
) && ) &&
( (
# LINT: unnecessary terminating semicolon # LINT: semicolon unnecessary but legitimate
foo; foo;
) && ) &&
(cd foo && (cd foo &&
for i in a b c; do for i in a b c; do
# LINT: unnecessary terminating semicolon # LINT: semicolon unnecessary but legitimate
echo; echo;
done) done)

@ -1,11 +1,10 @@
( (
echo wobba gorgo snoot wafta snurb && echo wobba gorgo snoot wafta snurb <<-EOF &&
?!AMP?! cat >bip cat <<EOF >bip ?!AMP?!
echo >bop echo <<-EOF >bop
>) && ) &&
( (
cat >bup && cat <<-ARBITRARY >bup &&
cat >bup2 && cat <<-ARBITRARY3 >bup3 &&
cat >bup3 &&
meep meep
>) )

@ -8,7 +8,7 @@
nevermore... nevermore...
EOF EOF
# LINT: missing "&&" on 'cat' # LINT: missing "&&" on "cat"
cat <<EOF >bip cat <<EOF >bip
fish fly high fish fly high
EOF EOF
@ -27,10 +27,6 @@
glink glink
FIZZ FIZZ
ARBITRARY ARBITRARY
cat <<-'ARBITRARY2' >bup2 &&
glink
FIZZ
ARBITRARY2
cat <<-"ARBITRARY3" >bup3 && cat <<-"ARBITRARY3" >bup3 &&
glink glink
FIZZ FIZZ

@ -2,13 +2,13 @@
(foo && bar) && (foo && bar) &&
(foo && bar) | (foo && bar) |
(foo && bar) >baz && (foo && bar) >baz &&
?!SEMI?! (foo; bar) && (foo; ?!AMP?! bar) &&
?!SEMI?! (foo; bar) | (foo; ?!AMP?! bar) |
?!SEMI?! (foo; bar) >baz && (foo; ?!AMP?! bar) >baz &&
(foo || exit 1) && (foo || exit 1) &&
(foo || exit 1) | (foo || exit 1) |
(foo || exit 1) >baz && (foo || exit 1) >baz &&
?!AMP?! (foo && bar) (foo && bar) ?!AMP?!
?!AMP?!?!SEMI?! (foo && bar; baz) (foo && bar; ?!AMP?! baz) ?!AMP?!
foobar foobar
>) )

@ -1,10 +1,10 @@
( (
chks="sub1sub2sub3sub4" && chks="sub1sub2sub3sub4" &&
chks_sub=$(cat | sed 's,^,sub dir/,' chks_sub=$(cat <<TXT | sed "s,^,sub dir/,"
>>) && ) &&
chkms="main-sub1main-sub2main-sub3main-sub4" && chkms="main-sub1main-sub2main-sub3main-sub4" &&
chkms_sub=$(cat | sed 's,^,sub dir/,' chkms_sub=$(cat <<TXT | sed "s,^,sub dir/,"
>>) && ) &&
subfiles=$(git ls-files) && subfiles=$(git ls-files) &&
check_equal "$subfiles" "$chkms$chks" check_equal "$subfiles" "$chkms$chks"
>) )

@ -3,7 +3,7 @@
sub2 sub2
sub3 sub3
sub4" && sub4" &&
chks_sub=$(cat <<TXT | sed 's,^,sub dir/,' chks_sub=$(cat <<TXT | sed "s,^,sub dir/,"
$chks $chks
TXT TXT
) && ) &&
@ -11,7 +11,7 @@ TXT
main-sub2 main-sub2
main-sub3 main-sub3
main-sub4" && main-sub4" &&
chkms_sub=$(cat <<TXT | sed 's,^,sub dir/,' chkms_sub=$(cat <<TXT | sed "s,^,sub dir/,"
$chkms $chkms
TXT TXT
) && ) &&

@ -1,11 +1,11 @@
( (
while true while true
do do
?!AMP?! echo foo echo foo ?!AMP?!
cat cat <<-EOF
?!AMP?! done done ?!AMP?!
while true; do while true; do
echo foo && echo foo &&
cat bar cat bar
done done
>) )

@ -1,17 +1,17 @@
( (
# LINT: 'while, 'do', 'done' do not need "&&" # LINT: "while", "do", "done" do not need "&&"
while true while true
do do
# LINT: missing "&&" on 'echo' # LINT: missing "&&" on "echo"
echo foo echo foo
# LINT: last statement of while does not need "&&" # LINT: last statement of while does not need "&&"
cat <<-\EOF cat <<-\EOF
bar bar
EOF EOF
# LINT: missing "&&" on 'done' # LINT: missing "&&" on "done"
done done
# LINT: 'do' on same line as 'while' # LINT: "do" on same line as "while"
while true; do while true; do
echo foo && echo foo &&
cat bar cat bar