]> git.ipfire.org Git - thirdparty/git.git/blame_incremental - t/chainlint.sed
Merge branch 'gc/branch-recurse-submodules-fix'
[thirdparty/git.git] / t / chainlint.sed
... / ...
CommitLineData
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#
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.
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
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.
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#
64# Swallowing here-docs with arbitrary tags requires a bit of finesse. When a
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".
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
71# target line, it becomes "<EOF>cat <<EOF\nsome data", and a match is attempted
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
74# encountered, it is appended to the target line giving "<EOF>cat <<EOF\nEOF",
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
78# the target line "cat <<EOF".
79#------------------------------------------------------------------------------
80
81# incomplete line -- slurp up next line
82:squash
83/\\$/ {
84 N
85 s/\\\n//
86 bsquash
87}
88
89# here-doc -- swallow it to avoid false hits within its body (but keep the
90# command to which it was attached)
91/<<-*[ ]*[\\'"]*[A-Za-z0-9_]/ {
92 /"[^"]*<<[^"]*"/bnotdoc
93 s/^\(.*<<-*[ ]*\)[\\'"]*\([A-Za-z0-9_][A-Za-z0-9_]*\)['"]*/<\2>\1\2/
94 :hered
95 N
96 /^<\([^>]*\)>.*\n[ ]*\1[ ]*$/!{
97 s/\n.*$//
98 bhered
99 }
100 s/^<[^>]*>//
101 s/\n.*$//
102}
103:notdoc
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...)"
115/^[ ]*(/bsubsh
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/;/{
124 /"[^"]*;[^"]*"/!s/;/; ?!AMP?!/
125}
126b
127
128:subsh
129# bare "(" line? -- stash for later printing
130/^[ ]*([ ]*$/ {
131 h
132 bnextln
133}
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
139x
140s/.*//
141x
142s/(/^/
143bslurp
144
145:nextln
146N
147s/.*\n//
148
149:slurp
150# incomplete line "...\"
151/\\$/bicmplte
152# multi-line quoted string "...\n..."?
153/"/bdqstr
154# multi-line quoted string '...\n...'? (but not contraction in string "it's")
155/'/{
156 /"[^'"]*'[^'"]*"/!bsqstr
157}
158:folded
159# here-doc -- swallow it (but not "<<" in a string)
160/<<-*[ ]*[\\'"]*[A-Za-z0-9_]/{
161 /"[^"]*<<[^"]*"/!bheredoc
162}
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
167/^[ ]*#/bnextln
168/^[ ]*$/bnextln
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"
175/^[ ^]*case[ ]*..*esac/bchkchn
176# multi-line "case ... esac"
177/^[ ^]*case[ ]..*[ ]in/bcase
178# multi-line "for ... done" or "while ... done"
179/^[ ^]*for[ ]..*[ ]in/bcont
180/^[ ^]*while[ ]/bcont
181/^[ ]*do[ ]/bcont
182/^[ ]*do[ ]*$/bcont
183/;[ ]*do/bcont
184/^[ ]*done[ ]*&&[ ]*$/bdone
185/^[ ]*done[ ]*$/bdone
186/^[ ]*done[ ]*[<>|]/bdone
187/^[ ]*done[ ]*)/bdone
188/||[ ]*exit[ ]/bcont
189/||[ ]*exit[ ]*$/bcont
190# multi-line "if...elsif...else...fi"
191/^[ ^]*if[ ]/bcont
192/^[ ]*then[ ]/bcont
193/^[ ]*then[ ]*$/bcont
194/;[ ]*then/bcont
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 "(...) &&"
204/^[ ^]*(.*)[ ]*&&[ ]*$/bchkchn
205# nested one-liner "(...)"
206/^[ ^]*(.*)[ ]*$/bchkchn
207# nested one-liner "(...) >x" (or "2>x" or "<x" or "|x")
208/^[ ^]*(.*)[ ]*[0-9]*[<>|]/bchkchn
209# nested multi-line "(...\n...)"
210/^[ ^]*(/bnest
211# multi-line "{...\n...}"
212/^[ ^]*{/bblock
213# closing ")" on own line -- exit subshell
214/^[ ]*)/bclssolo
215# "$((...))" -- arithmetic expansion; not closing ")"
216/\$(([^)][^)]*))[^)]*$/bchkchn
217# "$(...)" -- command substitution; not closing ")"
218/\$([^)][^)]*)[^)]*$/bchkchn
219# multi-line "$(...\n...)" -- command substitution; treat as nested subshell
220/\$([^)]*$/bnest
221# "=(...)" -- Bash array assignment; not closing ")"
222/=(/bchkchn
223# closing "...) &&"
224/)[ ]*&&[ ]*$/bclose
225# closing "...)"
226/)[ ]*$/bclose
227# closing "...) >x" (or "2>x" or "<x" or "|x")
228/)[ ]*[<>|]/bclose
229:chkchn
230# mark suspect if line uses ";" internally rather than "&&" (but not ";" in a
231# string and not ";;" in one-liner "case...esac")
232/;/{
233 /;;/!{
234 /"[^"]*;[^"]*"/!s/;/; ?!AMP?!/
235 }
236}
237# line ends with pipe "...|" -- valid; not missing "&&"
238/|[ ]*$/bcont
239# missing end-of-line "&&" -- mark suspect
240/&&[ ]*$/!s/$/ ?!AMP?!/
241:cont
242# retrieve and print previous line
243x
244s/^\([ ]*\)^/\1(/
245s/?!HERE?!/<</g
246n
247bslurp
248
249# found incomplete line "...\" -- slurp up next line
250:icmplte
251N
252s/\\\n//
253bslurp
254
255# check for multi-line double-quoted string "...\n..." -- fold to one line
256:dqstr
257# remove all quote pairs
258s/"\([^"]*\)"/@!\1@!/g
259# done if no dangling quote
260/"/!bdqdone
261# otherwise, slurp next line and try again
262N
263s/\n//
264bdqstr
265:dqdone
266s/@!/"/g
267bfolded
268
269# check for multi-line single-quoted string '...\n...' -- fold to one line
270:sqstr
271# remove all quote pairs
272s/'\([^']*\)'/@!\1@!/g
273# done if no dangling quote
274/'/!bsqdone
275# otherwise, slurp next line and try again
276N
277s/\n//
278bsqstr
279:sqdone
280s/@!/'/g
281bfolded
282
283# found here-doc -- swallow it to avoid false hits within its body (but keep
284# the command to which it was attached)
285:heredoc
286s/^\(.*\)<<\(-*[ ]*\)[\\'"]*\([A-Za-z0-9_][A-Za-z0-9_]*\)['"]*/<\3>\1?!HERE?!\2\3/
287:hdocsub
288N
289/^<\([^>]*\)>.*\n[ ]*\1[ ]*$/!{
290 s/\n.*$//
291 bhdocsub
292}
293s/^<[^>]*>//
294s/\n.*$//
295bfolded
296
297# found "case ... in" -- pass through untouched
298:case
299x
300s/^\([ ]*\)^/\1(/
301s/?!HERE?!/<</g
302n
303:cascom
304/^[ ]*#/{
305 N
306 s/.*\n//
307 bcascom
308}
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
316s/\( ?!AMP?!\)* ?!AMP?!$//
317x
318bcont
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
324s/\( ?!AMP?!\)* ?!AMP?!$//
325x
326# is 'done' or 'fi' cuddled with ")" to close subshell?
327/done.*)/bclose
328/fi.*)/bclose
329bchkchn
330
331# found nested multi-line "(...\n...)" -- pass through untouched
332:nest
333x
334:nstslrp
335s/^\([ ]*\)^/\1(/
336s/?!HERE?!/<</g
337n
338:nstcom
339# comment -- not closing ")" if in comment
340/^[ ]*#/{
341 N
342 s/.*\n//
343 bnstcom
344}
345# closing ")" on own line -- stop nested slurp
346/^[ ]*)/bnstcl
347# "$((...))" -- arithmetic expansion; not closing ")"
348/\$(([^)][^)]*))[^)]*$/bnstcnt
349# "$(...)" -- command substitution; not closing ")"
350/\$([^)][^)]*)[^)]*$/bnstcnt
351# closing "...)" -- stop nested slurp
352/)/bnstcl
353:nstcnt
354x
355bnstslrp
356:nstcl
357# is it "))" which closes nested and parent subshells?
358/)[ ]*)/bslurp
359bchkchn
360
361# found multi-line "{...\n...}" block -- pass through untouched
362:block
363x
364s/^\([ ]*\)^/\1(/
365s/?!HERE?!/<</g
366n
367:blkcom
368/^[ ]*#/{
369 N
370 s/.*\n//
371 bblkcom
372}
373# closing "}" -- stop block slurp
374/}/bchkchn
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
379:clssolo
380x
381s/\( ?!AMP?!\)* ?!AMP?!$//
382s/^\([ ]*\)^/\1(/
383s/?!HERE?!/<</g
384p
385x
386s/^\([ ]*\)^/\1(/
387s/?!HERE?!/<</g
388b
389
390# found closing "...)" -- exit subshell loop
391:close
392x
393s/^\([ ]*\)^/\1(/
394s/?!HERE?!/<</g
395p
396x
397s/^\([ ]*\)^/\1(/
398s/?!HERE?!/<</g
399b