]> git.ipfire.org Git - thirdparty/git.git/blame - t/chainlint.sed
Merge branch 'gc/branch-recurse-submodules-fix'
[thirdparty/git.git] / t / chainlint.sed
CommitLineData
878f9883
ES
1#------------------------------------------------------------------------------
2# Detect broken &&-chains in tests.
3#
4# At present, only &&-chains in subshells are examined by this linter;
5# top-level &&-chains are instead checked directly by the test framework. Like
6# the top-level &&-chain linter, the subshell linter (intentionally) does not
7# check &&-chains within {...} blocks.
8#
9# Checking for &&-chain breakage is done line-by-line by pure textual
10# inspection.
11#
12# Incomplete lines (those ending with "\") are stitched together with following
13# lines to simplify processing, particularly of "one-liner" statements.
14# Top-level here-docs are swallowed to avoid false positives within the
15# here-doc body, although the statement to which the here-doc is attached is
16# retained.
17#
18# Heuristics are used to detect end-of-subshell when the closing ")" is cuddled
19# with the final subshell statement on the same line:
20#
21# (cd foo &&
22# bar)
23#
24# in order to avoid misinterpreting the ")" in constructs such as "x=$(...)"
25# and "case $x in *)" as ending the subshell.
26#
0d713176
ES
27# Lines missing a final "&&" are flagged with "?!AMP?!", as are lines which
28# chain commands with ";" internally rather than "&&". A line may be flagged
29# for both violations.
878f9883
ES
30#
31# Detection of a missing &&-link in a multi-line subshell is complicated by the
32# fact that the last statement before the closing ")" must not end with "&&".
33# Since processing is line-by-line, it is not known whether a missing "&&" is
34# legitimate or not until the _next_ line is seen. To accommodate this, within
35# multi-line subshells, each line is stored in sed's "hold" area until after
36# the next line is seen and processed. If the next line is a stand-alone ")",
37# then a missing "&&" on the previous line is legitimate; otherwise a missing
38# "&&" is a break in the &&-chain.
39#
40# (
41# cd foo &&
42# bar
43# )
44#
45# In practical terms, when "bar" is encountered, it is flagged with "?!AMP?!",
46# but when the stand-alone ")" line is seen which closes the subshell, the
47# "?!AMP?!" violation is removed from the "bar" line (retrieved from the "hold"
48# area) since the final statement of a subshell must not end with "&&". The
49# final line of a subshell may still break the &&-chain by using ";" internally
0d713176
ES
50# to chain commands together rather than "&&", but an internal "?!AMP?!" is
51# never removed from a line even though a line-ending "?!AMP?!" might be.
878f9883
ES
52#
53# Care is taken to recognize the last _statement_ of a multi-line subshell, not
54# necessarily the last textual _line_ within the subshell, since &&-chaining
55# applies to statements, not to lines. Consequently, blank lines, comment
56# lines, and here-docs are swallowed (but not the command to which the here-doc
57# is attached), leaving the last statement in the "hold" area, not the last
58# line, thus simplifying &&-link checking.
59#
60# The final statement before "done" in for- and while-loops, and before "elif",
61# "else", and "fi" in if-then-else likewise must not end with "&&", thus
62# receives similar treatment.
63#
c2c29cc0 64# Swallowing here-docs with arbitrary tags requires a bit of finesse. When a
34ba05c2
ES
65# line such as "cat <<EOF" is seen, the here-doc tag is copied to the front of
66# the line enclosed in angle brackets as a sentinel, giving "<EOF>cat <<EOF".
c2c29cc0
ES
67# As each subsequent line is read, it is appended to the target line and a
68# (whitespace-loose) back-reference match /^<(.*)>\n\1$/ is attempted to see if
69# the content inside "<...>" matches the entirety of the newly-read line. For
70# instance, if the next line read is "some data", when concatenated with the
34ba05c2 71# target line, it becomes "<EOF>cat <<EOF\nsome data", and a match is attempted
c2c29cc0
ES
72# to see if "EOF" matches "some data". Since it doesn't, the next line is
73# attempted. When a line consisting of only "EOF" (and possible whitespace) is
34ba05c2 74# encountered, it is appended to the target line giving "<EOF>cat <<EOF\nEOF",
c2c29cc0
ES
75# in which case the "EOF" inside "<...>" does match the text following the
76# newline, thus the closing here-doc tag has been found. The closing tag line
77# and the "<...>" prefix on the target line are then discarded, leaving just
34ba05c2 78# the target line "cat <<EOF".
878f9883
ES
79#------------------------------------------------------------------------------
80
81# incomplete line -- slurp up next line
82:squash
83/\\$/ {
ace64e56
ES
84 N
85 s/\\\n//
86 bsquash
878f9883
ES
87}
88
89# here-doc -- swallow it to avoid false hits within its body (but keep the
90# command to which it was attached)
2d536142 91/<<-*[ ]*[\\'"]*[A-Za-z0-9_]/ {
22597af9 92 /"[^"]*<<[^"]*"/bnotdoc
34ba05c2 93 s/^\(.*<<-*[ ]*\)[\\'"]*\([A-Za-z0-9_][A-Za-z0-9_]*\)['"]*/<\2>\1\2/
a3c4c884 94 :hered
878f9883 95 N
c2c29cc0
ES
96 /^<\([^>]*\)>.*\n[ ]*\1[ ]*$/!{
97 s/\n.*$//
a3c4c884 98 bhered
c2c29cc0
ES
99 }
100 s/^<[^>]*>//
101 s/\n.*$//
878f9883 102}
22597af9 103:notdoc
878f9883
ES
104
105# one-liner "(...) &&"
106/^[ ]*!*[ ]*(..*)[ ]*&&[ ]*$/boneline
107
108# same as above but without trailing "&&"
109/^[ ]*!*[ ]*(..*)[ ]*$/boneline
110
111# one-liner "(...) >x" (or "2>x" or "<x" or "|x" or "&"
112/^[ ]*!*[ ]*(..*)[ ]*[0-9]*[<>|&]/boneline
113
114# multi-line "(...\n...)"
b3b753b1 115/^[ ]*(/bsubsh
878f9883
ES
116
117# innocuous line -- print it and advance to next line
118b
119
120# found one-liner "(...)" -- mark suspect if it uses ";" internally rather than
121# "&&" (but not ";" in a string)
122:oneline
123/;/{
0d713176 124 /"[^"]*;[^"]*"/!s/;/; ?!AMP?!/
878f9883
ES
125}
126b
127
b3b753b1 128:subsh
2d9ded8a 129# bare "(" line? -- stash for later printing
878f9883 130/^[ ]*([ ]*$/ {
878f9883 131 h
b3b753b1 132 bnextln
878f9883 133}
d73f5cfa
ES
134# "(..." line -- "(" opening subshell cuddled with command; temporarily replace
135# "(" with sentinel "^" and process the line as if "(" had been seen solo on
136# the preceding line; this temporary replacement prevents several rules from
137# accidentally thinking "(" introduces a nested subshell; "^" is changed back
138# to "(" at output time
878f9883 139x
d73f5cfa 140s/.*//
878f9883 141x
d73f5cfa 142s/(/^/
878f9883
ES
143bslurp
144
b3b753b1 145:nextln
878f9883
ES
146N
147s/.*\n//
148
149:slurp
150# incomplete line "...\"
a3c4c884 151/\\$/bicmplte
22e3e024 152# multi-line quoted string "...\n..."?
b3b753b1 153/"/bdqstr
22e3e024
ES
154# multi-line quoted string '...\n...'? (but not contraction in string "it's")
155/'/{
b3b753b1 156 /"[^'"]*'[^'"]*"/!bsqstr
878f9883 157}
d9387114 158:folded
22597af9
ES
159# here-doc -- swallow it (but not "<<" in a string)
160/<<-*[ ]*[\\'"]*[A-Za-z0-9_]/{
161 /"[^"]*<<[^"]*"/!bheredoc
162}
878f9883
ES
163# comment or empty line -- discard since final non-comment, non-empty line
164# before closing ")", "done", "elsif", "else", or "fi" will need to be
165# re-visited to drop "suspect" marking since final line of those constructs
166# legitimately lacks "&&", so "suspect" mark must be removed
b3b753b1
KK
167/^[ ]*#/bnextln
168/^[ ]*$/bnextln
878f9883
ES
169# in-line comment -- strip it (but not "#" in a string, Bash ${#...} array
170# length, or Perforce "//depot/path#42" revision in filespec)
171/[ ]#/{
172 /"[^"]*#[^"]*"/!s/[ ]#.*$//
173}
174# one-liner "case ... esac"
d73f5cfa 175/^[ ^]*case[ ]*..*esac/bchkchn
878f9883 176# multi-line "case ... esac"
d73f5cfa 177/^[ ^]*case[ ]..*[ ]in/bcase
878f9883 178# multi-line "for ... done" or "while ... done"
d73f5cfa
ES
179/^[ ^]*for[ ]..*[ ]in/bcont
180/^[ ^]*while[ ]/bcont
b3b753b1
KK
181/^[ ]*do[ ]/bcont
182/^[ ]*do[ ]*$/bcont
183/;[ ]*do/bcont
878f9883
ES
184/^[ ]*done[ ]*&&[ ]*$/bdone
185/^[ ]*done[ ]*$/bdone
186/^[ ]*done[ ]*[<>|]/bdone
187/^[ ]*done[ ]*)/bdone
b3b753b1
KK
188/||[ ]*exit[ ]/bcont
189/||[ ]*exit[ ]*$/bcont
878f9883 190# multi-line "if...elsif...else...fi"
d73f5cfa 191/^[ ^]*if[ ]/bcont
b3b753b1
KK
192/^[ ]*then[ ]/bcont
193/^[ ]*then[ ]*$/bcont
194/;[ ]*then/bcont
878f9883
ES
195/^[ ]*elif[ ]/belse
196/^[ ]*elif[ ]*$/belse
197/^[ ]*else[ ]/belse
198/^[ ]*else[ ]*$/belse
199/^[ ]*fi[ ]*&&[ ]*$/bdone
200/^[ ]*fi[ ]*$/bdone
201/^[ ]*fi[ ]*[<>|]/bdone
202/^[ ]*fi[ ]*)/bdone
203# nested one-liner "(...) &&"
d73f5cfa 204/^[ ^]*(.*)[ ]*&&[ ]*$/bchkchn
878f9883 205# nested one-liner "(...)"
d73f5cfa 206/^[ ^]*(.*)[ ]*$/bchkchn
878f9883 207# nested one-liner "(...) >x" (or "2>x" or "<x" or "|x")
d73f5cfa 208/^[ ^]*(.*)[ ]*[0-9]*[<>|]/bchkchn
878f9883 209# nested multi-line "(...\n...)"
d73f5cfa 210/^[ ^]*(/bnest
878f9883 211# multi-line "{...\n...}"
d73f5cfa 212/^[ ^]*{/bblock
878f9883 213# closing ")" on own line -- exit subshell
a3c4c884 214/^[ ]*)/bclssolo
878f9883 215# "$((...))" -- arithmetic expansion; not closing ")"
a3c4c884 216/\$(([^)][^)]*))[^)]*$/bchkchn
878f9883 217# "$(...)" -- command substitution; not closing ")"
a3c4c884 218/\$([^)][^)]*)[^)]*$/bchkchn
878f9883 219# multi-line "$(...\n...)" -- command substitution; treat as nested subshell
06fc5c9f 220/\$([^)]*$/bnest
878f9883 221# "=(...)" -- Bash array assignment; not closing ")"
a3c4c884 222/=(/bchkchn
878f9883
ES
223# closing "...) &&"
224/)[ ]*&&[ ]*$/bclose
225# closing "...)"
226/)[ ]*$/bclose
227# closing "...) >x" (or "2>x" or "<x" or "|x")
228/)[ ]*[<>|]/bclose
a3c4c884 229:chkchn
878f9883
ES
230# mark suspect if line uses ";" internally rather than "&&" (but not ";" in a
231# string and not ";;" in one-liner "case...esac")
232/;/{
233 /;;/!{
0d713176 234 /"[^"]*;[^"]*"/!s/;/; ?!AMP?!/
878f9883
ES
235 }
236}
237# line ends with pipe "...|" -- valid; not missing "&&"
b3b753b1 238/|[ ]*$/bcont
878f9883 239# missing end-of-line "&&" -- mark suspect
db8c7a1c 240/&&[ ]*$/!s/$/ ?!AMP?!/
b3b753b1 241:cont
878f9883
ES
242# retrieve and print previous line
243x
d73f5cfa 244s/^\([ ]*\)^/\1(/
34ba05c2 245s/?!HERE?!/<</g
878f9883
ES
246n
247bslurp
248
249# found incomplete line "...\" -- slurp up next line
a3c4c884 250:icmplte
878f9883
ES
251N
252s/\\\n//
253bslurp
254
22e3e024 255# check for multi-line double-quoted string "...\n..." -- fold to one line
b3b753b1 256:dqstr
22e3e024
ES
257# remove all quote pairs
258s/"\([^"]*\)"/@!\1@!/g
259# done if no dangling quote
260/"/!bdqdone
261# otherwise, slurp next line and try again
878f9883
ES
262N
263s/\n//
b3b753b1 264bdqstr
22e3e024
ES
265:dqdone
266s/@!/"/g
d9387114 267bfolded
878f9883 268
22e3e024 269# check for multi-line single-quoted string '...\n...' -- fold to one line
b3b753b1 270:sqstr
22e3e024
ES
271# remove all quote pairs
272s/'\([^']*\)'/@!\1@!/g
273# done if no dangling quote
274/'/!bsqdone
275# otherwise, slurp next line and try again
878f9883
ES
276N
277s/\n//
b3b753b1 278bsqstr
22e3e024
ES
279:sqdone
280s/@!/'/g
d9387114 281bfolded
878f9883
ES
282
283# found here-doc -- swallow it to avoid false hits within its body (but keep
c2c29cc0 284# the command to which it was attached)
878f9883 285:heredoc
34ba05c2 286s/^\(.*\)<<\(-*[ ]*\)[\\'"]*\([A-Za-z0-9_][A-Za-z0-9_]*\)['"]*/<\3>\1?!HERE?!\2\3/
b3b753b1 287:hdocsub
878f9883 288N
c2c29cc0
ES
289/^<\([^>]*\)>.*\n[ ]*\1[ ]*$/!{
290 s/\n.*$//
b3b753b1 291 bhdocsub
c2c29cc0
ES
292}
293s/^<[^>]*>//
878f9883 294s/\n.*$//
d9387114 295bfolded
878f9883
ES
296
297# found "case ... in" -- pass through untouched
298:case
299x
d73f5cfa 300s/^\([ ]*\)^/\1(/
34ba05c2 301s/?!HERE?!/<</g
878f9883 302n
31da22d1
ES
303:cascom
304/^[ ]*#/{
305 N
306 s/.*\n//
307 bcascom
308}
878f9883
ES
309/^[ ]*esac/bslurp
310bcase
311
312# found "else" or "elif" -- drop "suspect" from final line before "else" since
313# that line legitimately lacks "&&"
314:else
315x
0d713176 316s/\( ?!AMP?!\)* ?!AMP?!$//
878f9883 317x
b3b753b1 318bcont
878f9883
ES
319
320# found "done" closing for-loop or while-loop, or "fi" closing if-then -- drop
321# "suspect" from final contained line since that line legitimately lacks "&&"
322:done
323x
0d713176 324s/\( ?!AMP?!\)* ?!AMP?!$//
878f9883
ES
325x
326# is 'done' or 'fi' cuddled with ")" to close subshell?
327/done.*)/bclose
328/fi.*)/bclose
a3c4c884 329bchkchn
878f9883
ES
330
331# found nested multi-line "(...\n...)" -- pass through untouched
332:nest
333x
b3b753b1 334:nstslrp
d73f5cfa 335s/^\([ ]*\)^/\1(/
34ba05c2 336s/?!HERE?!/<</g
878f9883 337n
31da22d1
ES
338:nstcom
339# comment -- not closing ")" if in comment
340/^[ ]*#/{
341 N
342 s/.*\n//
343 bnstcom
344}
878f9883 345# closing ")" on own line -- stop nested slurp
b3b753b1 346/^[ ]*)/bnstcl
878f9883 347# "$((...))" -- arithmetic expansion; not closing ")"
a3c4c884 348/\$(([^)][^)]*))[^)]*$/bnstcnt
878f9883 349# "$(...)" -- command substitution; not closing ")"
a3c4c884 350/\$([^)][^)]*)[^)]*$/bnstcnt
878f9883 351# closing "...)" -- stop nested slurp
b3b753b1 352/)/bnstcl
a3c4c884 353:nstcnt
878f9883 354x
b3b753b1
KK
355bnstslrp
356:nstcl
878f9883
ES
357# is it "))" which closes nested and parent subshells?
358/)[ ]*)/bslurp
a3c4c884 359bchkchn
878f9883
ES
360
361# found multi-line "{...\n...}" block -- pass through untouched
362:block
363x
d73f5cfa 364s/^\([ ]*\)^/\1(/
34ba05c2 365s/?!HERE?!/<</g
878f9883 366n
31da22d1
ES
367:blkcom
368/^[ ]*#/{
369 N
370 s/.*\n//
371 bblkcom
372}
878f9883 373# closing "}" -- stop block slurp
a3c4c884 374/}/bchkchn
878f9883
ES
375bblock
376
377# found closing ")" on own line -- drop "suspect" from final line of subshell
378# since that line legitimately lacks "&&" and exit subshell loop
a3c4c884 379:clssolo
878f9883 380x
0d713176 381s/\( ?!AMP?!\)* ?!AMP?!$//
d73f5cfa 382s/^\([ ]*\)^/\1(/
34ba05c2 383s/?!HERE?!/<</g
878f9883
ES
384p
385x
d73f5cfa 386s/^\([ ]*\)^/\1(/
34ba05c2 387s/?!HERE?!/<</g
878f9883
ES
388b
389
390# found closing "...)" -- exit subshell loop
391:close
392x
d73f5cfa 393s/^\([ ]*\)^/\1(/
34ba05c2 394s/?!HERE?!/<</g
878f9883
ES
395p
396x
d73f5cfa 397s/^\([ ]*\)^/\1(/
34ba05c2 398s/?!HERE?!/<</g
878f9883 399b