Merge branch 'es/chain-lint-more'
authorJunio C Hamano <gitster@pobox.com>
Mon, 20 Aug 2018 18:33:53 +0000 (11:33 -0700)
committerJunio C Hamano <gitster@pobox.com>
Mon, 20 Aug 2018 18:33:53 +0000 (11:33 -0700)
Improve built-in facility to catch broken &&-chain in the tests.

* es/chain-lint-more:
  chainlint: add test of pathological case which triggered false positive
  chainlint: recognize multi-line quoted strings more robustly
  chainlint: let here-doc and multi-line string commence on same line
  chainlint: recognize multi-line $(...) when command cuddled with "$("
  chainlint: match 'quoted' here-doc tags
  chainlint: match arbitrary here-docs tags rather than hard-coded names

19 files changed:
t/chainlint.sed
t/chainlint/here-doc-close-subshell.expect [new file with mode: 0644]
t/chainlint/here-doc-close-subshell.test [new file with mode: 0644]
t/chainlint/here-doc-multi-line-command-subst.expect [new file with mode: 0644]
t/chainlint/here-doc-multi-line-command-subst.test [new file with mode: 0644]
t/chainlint/here-doc-multi-line-string.expect [new file with mode: 0644]
t/chainlint/here-doc-multi-line-string.test [new file with mode: 0644]
t/chainlint/here-doc.expect
t/chainlint/here-doc.test
t/chainlint/multi-line-nested-command-substitution.expect
t/chainlint/multi-line-nested-command-substitution.test
t/chainlint/multi-line-string.expect
t/chainlint/multi-line-string.test
t/chainlint/nested-here-doc.expect
t/chainlint/nested-here-doc.test
t/chainlint/subshell-here-doc.expect
t/chainlint/subshell-here-doc.test
t/chainlint/t7900-subtree.expect [new file with mode: 0644]
t/chainlint/t7900-subtree.test [new file with mode: 0644]

index 5f0882c..8544df3 100644 (file)
 # "else", and "fi" in if-then-else likewise must not end with "&&", thus
 # 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".
+# 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
+# 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",
+# 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
 
 # here-doc -- swallow it to avoid false hits within its body (but keep the
 # command to which it was attached)
-/<<[   ]*[-\\]*EOF[    ]*/ {
-       s/[     ]*<<[   ]*[-\\]*EOF//
-       h
+/<<[   ]*[-\\']*[A-Za-z0-9_]/ {
+       s/^\(.*\)<<[    ]*[-\\']*\([A-Za-z0-9_][A-Za-z0-9_]*\)'*/<\2>\1<</
+       s/[     ]*<<//
        :hereslurp
        N
-       s/.*\n//
-       /^[     ]*EOF[  ]*$/!bhereslurp
-       x
+       /^<\([^>]*\)>.*\n[      ]*\1[   ]*$/!{
+               s/\n.*$//
+               bhereslurp
+       }
+       s/^<[^>]*>//
+       s/\n.*$//
 }
 
 # one-liner "(...) &&"
@@ -132,16 +151,15 @@ s/.*\n//
 :slurp
 # incomplete line "...\"
 /\\$/bincomplete
-# multi-line quoted string "...\n..."
-/^[^"]*"[^"]*$/bdqstring
-# multi-line quoted string '...\n...' (but not contraction in string "it's so")
-/^[^']*'[^']*$/{
+# multi-line quoted string "...\n..."?
+/"/bdqstring
+# multi-line quoted string '...\n...'? (but not contraction in string "it's")
+/'/{
        /"[^'"]*'[^'"]*"/!bsqstring
 }
+:folded
 # here-doc -- swallow it
-/<<[   ]*[-\\]*EOF/bheredoc
-/<<[   ]*[-\\]*EOT/bheredoc
-/<<[   ]*[-\\]*INPUT_END/bheredoc
+/<<[   ]*[-\\']*[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
@@ -199,7 +217,7 @@ s/.*\n//
 # "$(...)" -- command substitution; not closing ")"
 /\$([^)][^)]*)[^)]*$/bcheckchain
 # multi-line "$(...\n...)" -- command substitution; treat as nested subshell
-/\$([  ]*$/bnest
+/\$([^)]*$/bnest
 # "=(...)" -- Bash array assignment; not closing ")"
 /=(/bcheckchain
 # closing "...) &&"
@@ -232,42 +250,48 @@ N
 s/\\\n//
 bslurp
 
-# found multi-line double-quoted string "...\n..." -- slurp until end of string
+# check for multi-line double-quoted string "...\n..." -- fold to one line
 :dqstring
-s/"//g
+# remove all quote pairs
+s/"\([^"]*\)"/@!\1@!/g
+# done if no dangling quote
+/"/!bdqdone
+# otherwise, slurp next line and try again
 N
 s/\n//
-/"/!bdqstring
-bcheckchain
+bdqstring
+:dqdone
+s/@!/"/g
+bfolded
 
-# found multi-line single-quoted string '...\n...' -- slurp until end of string
+# check for multi-line single-quoted string '...\n...' -- fold to one line
 :sqstring
-s/'//g
+# remove all quote pairs
+s/'\([^']*\)'/@!\1@!/g
+# done if no dangling quote
+/'/!bsqdone
+# otherwise, slurp next line and try again
 N
 s/\n//
-/'/!bsqstring
-bcheckchain
+bsqstring
+:sqdone
+s/@!/'/g
+bfolded
 
 # found here-doc -- swallow it to avoid false hits within its body (but keep
-# the command to which it was attached); take care to handle here-docs nested
-# within here-docs by only recognizing closing tag matching outer here-doc
-# opening tag
+# the command to which it was attached)
 :heredoc
-/EOF/{ s/[     ]*<<[   ]*[-\\]*EOF//; s/^/EOF/; }
-/EOT/{ s/[     ]*<<[   ]*[-\\]*EOT//; s/^/EOT/; }
-/INPUT_END/{ s/[       ]*<<[   ]*[-\\]*INPUT_END//; s/^/INPUT_END/; }
+s/^\(.*\)<<[   ]*[-\\']*\([A-Za-z0-9_][A-Za-z0-9_]*\)'*/<\2>\1<</
+s/[    ]*<<//
 :hereslurpsub
 N
-/^EOF.*\n[     ]*EOF[  ]*$/bhereclose
-/^EOT.*\n[     ]*EOT[  ]*$/bhereclose
-/^INPUT_END.*\n[       ]*INPUT_END[    ]*$/bhereclose
-bhereslurpsub
-:hereclose
-s/^EOF//
-s/^EOT//
-s/^INPUT_END//
+/^<\([^>]*\)>.*\n[     ]*\1[   ]*$/!{
+       s/\n.*$//
+       bhereslurpsub
+}
+s/^<[^>]*>//
 s/\n.*$//
-bcheckchain
+bfolded
 
 # found "case ... in" -- pass through untouched
 :case
diff --git a/t/chainlint/here-doc-close-subshell.expect b/t/chainlint/here-doc-close-subshell.expect
new file mode 100644 (file)
index 0000000..f011e33
--- /dev/null
@@ -0,0 +1,2 @@
+(
+>      cat)
diff --git a/t/chainlint/here-doc-close-subshell.test b/t/chainlint/here-doc-close-subshell.test
new file mode 100644 (file)
index 0000000..b857ff5
--- /dev/null
@@ -0,0 +1,5 @@
+(
+# LINT: line contains here-doc and closes nested subshell
+       cat <<-\INPUT)
+       fizz
+       INPUT
diff --git a/t/chainlint/here-doc-multi-line-command-subst.expect b/t/chainlint/here-doc-multi-line-command-subst.expect
new file mode 100644 (file)
index 0000000..e5fb752
--- /dev/null
@@ -0,0 +1,5 @@
+(
+       x=$(bobble &&
+?!AMP?!>>              wiffle)
+       echo $x
+>)
diff --git a/t/chainlint/here-doc-multi-line-command-subst.test b/t/chainlint/here-doc-multi-line-command-subst.test
new file mode 100644 (file)
index 0000000..899bc5d
--- /dev/null
@@ -0,0 +1,9 @@
+(
+# LINT: line contains here-doc and opens multi-line $(...)
+       x=$(bobble <<-\END &&
+               fossil
+               vegetable
+               END
+               wiffle)
+       echo $x
+)
diff --git a/t/chainlint/here-doc-multi-line-string.expect b/t/chainlint/here-doc-multi-line-string.expect
new file mode 100644 (file)
index 0000000..32038a0
--- /dev/null
@@ -0,0 +1,4 @@
+(
+?!AMP?!        cat && echo "multi-line string"
+       bap
+>)
diff --git a/t/chainlint/here-doc-multi-line-string.test b/t/chainlint/here-doc-multi-line-string.test
new file mode 100644 (file)
index 0000000..a53edbc
--- /dev/null
@@ -0,0 +1,8 @@
+(
+# LINT: line contains here-doc and opens multi-line string
+       cat <<-\TXT && echo "multi-line
+       string"
+       fizzle
+       TXT
+       bap
+)
index 2328fe7..aff6568 100644 (file)
@@ -1,3 +1,7 @@
 boodle wobba        gorgo snoot        wafta snurb &&
 
+cat >foo &&
+
+cat >bar &&
+
 horticulture
index bd36f6e..f2bb14b 100644 (file)
@@ -7,6 +7,20 @@ quoth the raven,
 nevermore...
 EOF
 
+# LINT: swallow here-doc with arbitrary tag
+cat <<-Arbitrary_Tag_42 >foo &&
+snoz
+boz
+woz
+Arbitrary_Tag_42
+
+# LINT: swallow 'quoted' here-doc
+cat <<'FUMP' >bar &&
+snoz
+boz
+woz
+FUMP
+
 # LINT: swallow here-doc (EOF is last line of test)
 horticulture <<\EOF
 gomez
index 19c023b..59b6c8b 100644 (file)
@@ -6,4 +6,13 @@
 >>     ) &&
        echo ok
 >) |
-sort
+sort &&
+(
+       bar &&
+       x=$(echo bar |
+               cat
+>>     ) &&
+       y=$(echo baz |
+>>             fip) &&
+       echo fail
+>)
index ca0620a..3000583 100644 (file)
@@ -6,4 +6,13 @@
        ) &&
        echo ok
 ) |
-sort
+sort &&
+(
+       bar &&
+       x=$(echo bar |
+               cat
+       ) &&
+       y=$(echo baz |
+               fip) &&
+       echo fail
+)
index 8334c4c..170cb59 100644 (file)
@@ -1,9 +1,15 @@
 (
-       x=line 1                line 2          line 3" &&
-?!AMP?!        y=line 1                line2'
+       x="line 1               line 2          line 3" &&
+?!AMP?!        y='line 1               line2'
        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
 >)
index 14cb44d..287ab89 100644 (file)
 # 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
 )
index 559301e..0c9ef1c 100644 (file)
@@ -1,3 +1,5 @@
+cat >foop &&
+
 (
        cat &&
 ?!AMP?!        cat
index 027e0bb..f35404b 100644 (file)
@@ -1,3 +1,13 @@
+# LINT: inner "EOF" not misintrepreted as closing ARBITRARY here-doc
+cat <<ARBITRARY >foop &&
+naddle
+fub <<EOF
+       nozzle
+       noodle
+EOF
+formp
+ARBITRARY
+
 (
 # LINT: inner "EOF" not misintrepreted as closing INPUT_END here-doc
        cat <<-\INPUT_END &&
index 19d5aff..7663ea7 100644 (file)
@@ -2,4 +2,9 @@
        echo wobba             gorgo snoot             wafta snurb &&
 ?!AMP?!        cat >bip
        echo >bop
+>) &&
+(
+       cat >bup &&
+       cat >bup2 &&
+       meep
 >)
index 9c3564c..b6b5a9b 100644 (file)
        wednesday
        pugsly
        EOF
+) &&
+(
+# LINT: swallow here-doc with arbitrary tag
+       cat <<-\ARBITRARY >bup &&
+       glink
+       FIZZ
+       ARBITRARY
+       cat <<-'ARBITRARY2' >bup2 &&
+       glink
+       FIZZ
+       ARBITRARY2
+       meep
 )
diff --git a/t/chainlint/t7900-subtree.expect b/t/chainlint/t7900-subtree.expect
new file mode 100644 (file)
index 0000000..c991342
--- /dev/null
@@ -0,0 +1,10 @@
+(
+       chks="sub1sub2sub3sub4" &&
+       chks_sub=$(cat | sed 's,^,sub dir/,'
+>>) &&
+       chkms="main-sub1main-sub2main-sub3main-sub4" &&
+       chkms_sub=$(cat | sed 's,^,sub dir/,'
+>>) &&
+       subfiles=$(git ls-files) &&
+       check_equal "$subfiles" "$chkms$chks"
+>)
diff --git a/t/chainlint/t7900-subtree.test b/t/chainlint/t7900-subtree.test
new file mode 100644 (file)
index 0000000..277d835
--- /dev/null
@@ -0,0 +1,22 @@
+(
+       chks="sub1
+sub2
+sub3
+sub4" &&
+       chks_sub=$(cat <<TXT | sed 's,^,sub dir/,'
+$chks
+TXT
+) &&
+       chkms="main-sub1
+main-sub2
+main-sub3
+main-sub4" &&
+       chkms_sub=$(cat <<TXT | sed 's,^,sub dir/,'
+$chkms
+TXT
+) &&
+
+       subfiles=$(git ls-files) &&
+       check_equal "$subfiles" "$chkms
+$chks"
+)