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

View File

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

View File

@ -24,9 +24,9 @@
# in order to avoid misinterpreting the ")" in constructs such as "x=$(...)"
# and "case $x in *)" as ending the subshell.
#
# Lines missing a final "&&" are flagged with "?!AMP?!", and lines which chain
# commands with ";" internally rather than "&&" are flagged "?!SEMI?!". A line
# may be flagged for both violations.
# Lines missing a final "&&" are flagged with "?!AMP?!", as are lines which
# chain commands with ";" internally rather than "&&". A line may be flagged
# for both violations.
#
# 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 "&&".
@ -47,8 +47,8 @@
# "?!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
# final line of a subshell may still break the &&-chain by using ";" internally
# to chain commands together rather than "&&", so "?!SEMI?!" is never removed
# from a line (even though "?!AMP?!" might be).
# to chain commands together rather than "&&", but an internal "?!AMP?!" is
# 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
# necessarily the last textual _line_ within the subshell, since &&-chaining
@ -62,26 +62,20 @@
# receives similar treatment.
#
# 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
# of the line enclosed in angle brackets as a sentinel, giving "<EOF>cat >out".
# line such as "cat <<EOF" is seen, the here-doc tag is copied to the front of
# 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
# (whitespace-loose) back-reference match /^<(.*)>\n\1$/ is attempted to see if
# 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
# 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
# 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
# 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
# the target line "cat >out".
#
# 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.
# the target line "cat <<EOF".
#------------------------------------------------------------------------------
# incomplete line -- slurp up next line
@ -94,9 +88,9 @@
# here-doc -- swallow it to avoid false hits within its body (but keep the
# command to which it was attached)
/<<[ ]*[-\\'"]*[A-Za-z0-9_]/ {
s/^\(.*\)<<[ ]*[-\\'"]*\([A-Za-z0-9_][A-Za-z0-9_]*\)['"]*/<\2>\1<</
s/[ ]*<<//
/<<-*[ ]*[\\'"]*[A-Za-z0-9_]/ {
/"[^"]*<<[^"]*"/bnotdoc
s/^\(.*<<-*[ ]*\)[\\'"]*\([A-Za-z0-9_][A-Za-z0-9_]*\)['"]*/<\2>\1\2/
:hered
N
/^<\([^>]*\)>.*\n[ ]*\1[ ]*$/!{
@ -106,6 +100,7 @@
s/^<[^>]*>//
s/\n.*$//
}
:notdoc
# one-liner "(...) &&"
/^[ ]*!*[ ]*(..*)[ ]*&&[ ]*$/boneline
@ -126,7 +121,7 @@ b
# "&&" (but not ";" in a string)
:oneline
/;/{
/"[^"]*;[^"]*"/!s/^/?!SEMI?!/
/"[^"]*;[^"]*"/!s/;/; ?!AMP?!/
}
b
@ -136,11 +131,15 @@ b
h
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
s/.*/(/
s/.*//
x
s/(//
s/(/^/
bslurp
:nextln
@ -157,8 +156,10 @@ s/.*\n//
/"[^'"]*'[^'"]*"/!bsqstr
}
:folded
# here-doc -- swallow it
/<<[ ]*[-\\'"]*[A-Za-z0-9_]/bheredoc
# here-doc -- swallow it (but not "<<" in a string)
/<<-*[ ]*[\\'"]*[A-Za-z0-9_]/{
/"[^"]*<<[^"]*"/!bheredoc
}
# comment or empty line -- discard since final non-comment, non-empty line
# before closing ")", "done", "elsif", "else", or "fi" will need to be
# re-visited to drop "suspect" marking since final line of those constructs
@ -171,12 +172,12 @@ s/.*\n//
/"[^"]*#[^"]*"/!s/[ ]#.*$//
}
# one-liner "case ... esac"
/^[ ]*case[ ]*..*esac/bchkchn
/^[ ^]*case[ ]*..*esac/bchkchn
# multi-line "case ... esac"
/^[ ]*case[ ]..*[ ]in/bcase
/^[ ^]*case[ ]..*[ ]in/bcase
# multi-line "for ... done" or "while ... done"
/^[ ]*for[ ]..*[ ]in/bcont
/^[ ]*while[ ]/bcont
/^[ ^]*for[ ]..*[ ]in/bcont
/^[ ^]*while[ ]/bcont
/^[ ]*do[ ]/bcont
/^[ ]*do[ ]*$/bcont
/;[ ]*do/bcont
@ -187,7 +188,7 @@ s/.*\n//
/||[ ]*exit[ ]/bcont
/||[ ]*exit[ ]*$/bcont
# multi-line "if...elsif...else...fi"
/^[ ]*if[ ]/bcont
/^[ ^]*if[ ]/bcont
/^[ ]*then[ ]/bcont
/^[ ]*then[ ]*$/bcont
/;[ ]*then/bcont
@ -200,15 +201,15 @@ s/.*\n//
/^[ ]*fi[ ]*[<>|]/bdone
/^[ ]*fi[ ]*)/bdone
# nested one-liner "(...) &&"
/^[ ]*(.*)[ ]*&&[ ]*$/bchkchn
/^[ ^]*(.*)[ ]*&&[ ]*$/bchkchn
# nested one-liner "(...)"
/^[ ]*(.*)[ ]*$/bchkchn
/^[ ^]*(.*)[ ]*$/bchkchn
# nested one-liner "(...) >x" (or "2>x" or "<x" or "|x")
/^[ ]*(.*)[ ]*[0-9]*[<>|]/bchkchn
/^[ ^]*(.*)[ ]*[0-9]*[<>|]/bchkchn
# nested multi-line "(...\n...)"
/^[ ]*(/bnest
/^[ ^]*(/bnest
# multi-line "{...\n...}"
/^[ ]*{/bblock
/^[ ^]*{/bblock
# closing ")" on own line -- exit subshell
/^[ ]*)/bclssolo
# "$((...))" -- arithmetic expansion; not closing ")"
@ -230,16 +231,18 @@ s/.*\n//
# string and not ";;" in one-liner "case...esac")
/;/{
/;;/!{
/"[^"]*;[^"]*"/!s/^/?!SEMI?!/
/"[^"]*;[^"]*"/!s/;/; ?!AMP?!/
}
}
# line ends with pipe "...|" -- valid; not missing "&&"
/|[ ]*$/bcont
# missing end-of-line "&&" -- mark suspect
/&&[ ]*$/!s/^/?!AMP?!/
/&&[ ]*$/!s/$/ ?!AMP?!/
:cont
# retrieve and print previous line
x
s/^\([ ]*\)^/\1(/
s/?!HERE?!/<</g
n
bslurp
@ -280,8 +283,7 @@ bfolded
# found here-doc -- swallow it to avoid false hits within its body (but keep
# the command to which it was attached)
:heredoc
s/^\(.*\)<<[ ]*[-\\'"]*\([A-Za-z0-9_][A-Za-z0-9_]*\)['"]*/<\2>\1<</
s/[ ]*<<//
s/^\(.*\)<<\(-*[ ]*\)[\\'"]*\([A-Za-z0-9_][A-Za-z0-9_]*\)['"]*/<\3>\1?!HERE?!\2\3/
:hdocsub
N
/^<\([^>]*\)>.*\n[ ]*\1[ ]*$/!{
@ -295,7 +297,15 @@ bfolded
# found "case ... in" -- pass through untouched
:case
x
s/^\([ ]*\)^/\1(/
s/?!HERE?!/<</g
n
:cascom
/^[ ]*#/{
N
s/.*\n//
bcascom
}
/^[ ]*esac/bslurp
bcase
@ -303,7 +313,7 @@ bcase
# that line legitimately lacks "&&"
:else
x
s/?!AMP?!//
s/\( ?!AMP?!\)* ?!AMP?!$//
x
bcont
@ -311,7 +321,7 @@ bcont
# "suspect" from final contained line since that line legitimately lacks "&&"
:done
x
s/?!AMP?!//
s/\( ?!AMP?!\)* ?!AMP?!$//
x
# is 'done' or 'fi' cuddled with ")" to close subshell?
/done.*)/bclose
@ -322,11 +332,18 @@ bchkchn
:nest
x
:nstslrp
s/^\([ ]*\)^/\1(/
s/?!HERE?!/<</g
n
:nstcom
# comment -- not closing ")" if in comment
/^[ ]*#/{
N
s/.*\n//
bnstcom
}
# closing ")" on own line -- stop nested slurp
/^[ ]*)/bnstcl
# comment -- not closing ")" if in comment
/^[ ]*#/bnstcnt
# "$((...))" -- arithmetic expansion; not closing ")"
/\$(([^)][^)]*))[^)]*$/bnstcnt
# "$(...)" -- command substitution; not closing ")"
@ -337,7 +354,6 @@ n
x
bnstslrp
:nstcl
s/^/>>/
# is it "))" which closes nested and parent subshells?
/)[ ]*)/bslurp
bchkchn
@ -345,7 +361,15 @@ bchkchn
# found multi-line "{...\n...}" block -- pass through untouched
:block
x
s/^\([ ]*\)^/\1(/
s/?!HERE?!/<</g
n
:blkcom
/^[ ]*#/{
N
s/.*\n//
bblkcom
}
# closing "}" -- stop block slurp
/}/bchkchn
bblock
@ -354,16 +378,22 @@ bblock
# since that line legitimately lacks "&&" and exit subshell loop
:clssolo
x
s/?!AMP?!//
s/\( ?!AMP?!\)* ?!AMP?!$//
s/^\([ ]*\)^/\1(/
s/?!HERE?!/<</g
p
x
s/^/>/
s/^\([ ]*\)^/\1(/
s/?!HERE?!/<</g
b
# found closing "...)" -- exit subshell loop
:close
x
s/^\([ ]*\)^/\1(/
s/?!HERE?!/<</g
p
x
s/^/>/
s/^\([ ]*\)^/\1(/
s/?!HERE?!/<</g
b

View File

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

View File

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

View File

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

View File

@ -3,7 +3,7 @@
nothing &&
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)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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
x) foo ;;
*) bar ;;
@ -7,7 +7,7 @@
foobar
) &&
(
# LINT: missing "&&" on 'esac'
# LINT: missing "&&" on "esac"
case "$x" in
x) foo ;;
*) bar ;;
@ -15,7 +15,7 @@
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 &&
# LINT: same but missing "&&"
case "$y" in 2) false;; esac

View File

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

View File

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

View File

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

View File

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

View File

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

View 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
(for i in a b c; do
if test "$(echo $(waffle bat))" = "eleventeen" &&

View File

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

View File

@ -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
echo empty
else

View File

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

View File

@ -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: loop; indented with spaces, not tabs
( while read x

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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
horticulture <<EOF

View File

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

View File

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

View File

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

View File

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

View File

@ -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 ""
then
# LINT: missing "&&" on 'echo'
# LINT: missing "&&" on "echo"
echo very
# LINT: last statement before 'elif' does not need "&&"
# LINT: last statement before "elif" does not need "&&"
echo empty
elif test -z ""
# LINT: last statement before 'else' does not need "&&"
then
# LINT: last statement before "else" does not need "&&"
echo foo
else
echo foo &&
# LINT: last statement before 'fi' does not need "&&"
# LINT: last statement before "fi" does not need "&&"
cat <<-\EOF
bar
EOF
# LINT: missing "&&" on 'fi'
# LINT: missing "&&" on "fi"
fi
echo poodle
) &&
(
# LINT: 'then' on same line as 'if'
# LINT: "then" on same line as "if"
if test -n ""; then
echo very &&
echo empty
if
fi
)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -3,25 +3,13 @@
line 2
line 3" &&
# LINT: missing "&&" on assignment
y='line 1
line2'
y="line 1
line2"
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
def
ghi" &&
echo 'xyz' 'abc
def
ghi' &&
echo 'xyz' "abc
def
ghi" &&
barfoo
)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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

View File

@ -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

View File

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

View File

@ -3,10 +3,10 @@
(foo && bar) |
(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) >baz
(foo; bar) >baz &&
# LINT: ";" in string not misinterpreted as broken &&-chain
(foo "bar; baz")

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -8,10 +8,10 @@
nevermore...
EOF
# LINT: missing "&&" on 'cat'
# LINT: missing "&&" on "cat"
cat <<EOF >bip
fish fly high
EOF
EOF
# LINT: swallow here-doc (EOF is last line of subshell)
echo <<-\EOF >bop
@ -27,10 +27,6 @@
glink
FIZZ
ARBITRARY
cat <<-'ARBITRARY2' >bup2 &&
glink
FIZZ
ARBITRARY2
cat <<-"ARBITRARY3" >bup3 &&
glink
FIZZ

View File

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

View File

@ -1,10 +1,10 @@
(
chks="sub1sub2sub3sub4" &&
chks_sub=$(cat | sed 's,^,sub dir/,'
>>) &&
chks_sub=$(cat <<TXT | sed "s,^,sub dir/,"
) &&
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) &&
check_equal "$subfiles" "$chkms$chks"
>)
)

View File

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

View File

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

View File

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