]> git.ipfire.org Git - thirdparty/git.git/blame - t/chainlint.sed
path.c: don't call the match function without value in trie_find()
[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#
27# Lines missing a final "&&" are flagged with "?!AMP?!", and lines which chain
28# commands with ";" internally rather than "&&" are flagged "?!SEMI?!". A line
29# may be flagged 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 "&&", so "?!SEMI?!" is never removed
51# from a line (even though "?!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#
c2c29cc0
ES
64# Swallowing here-docs with arbitrary tags requires a bit of finesse. When a
65# line such as "cat <<EOF >out" is seen, the here-doc tag is moved to the front
66# of the line enclosed in angle brackets as a sentinel, giving "<EOF>cat >out".
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 >out\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 >out\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 >out".
79#
878f9883
ES
80# To facilitate regression testing (and manual debugging), a ">" annotation is
81# applied to the line containing ")" which closes a subshell, ">>" to a line
82# closing a nested subshell, and ">>>" to a line closing both at once. This
83# makes it easy to detect whether the heuristics correctly identify
84# end-of-subshell.
85#------------------------------------------------------------------------------
86
87# incomplete line -- slurp up next line
88:squash
89/\\$/ {
ace64e56
ES
90 N
91 s/\\\n//
92 bsquash
878f9883
ES
93}
94
95# here-doc -- swallow it to avoid false hits within its body (but keep the
96# command to which it was attached)
3042b6bb
ES
97/<<[ ]*[-\\'"]*[A-Za-z0-9_]/ {
98 s/^\(.*\)<<[ ]*[-\\'"]*\([A-Za-z0-9_][A-Za-z0-9_]*\)['"]*/<\2>\1<</
c2c29cc0 99 s/[ ]*<<//
a3c4c884 100 :hered
878f9883 101 N
c2c29cc0
ES
102 /^<\([^>]*\)>.*\n[ ]*\1[ ]*$/!{
103 s/\n.*$//
a3c4c884 104 bhered
c2c29cc0
ES
105 }
106 s/^<[^>]*>//
107 s/\n.*$//
878f9883
ES
108}
109
110# one-liner "(...) &&"
111/^[ ]*!*[ ]*(..*)[ ]*&&[ ]*$/boneline
112
113# same as above but without trailing "&&"
114/^[ ]*!*[ ]*(..*)[ ]*$/boneline
115
116# one-liner "(...) >x" (or "2>x" or "<x" or "|x" or "&"
117/^[ ]*!*[ ]*(..*)[ ]*[0-9]*[<>|&]/boneline
118
119# multi-line "(...\n...)"
120/^[ ]*(/bsubshell
121
122# innocuous line -- print it and advance to next line
123b
124
125# found one-liner "(...)" -- mark suspect if it uses ";" internally rather than
126# "&&" (but not ";" in a string)
127:oneline
128/;/{
129 /"[^"]*;[^"]*"/!s/^/?!SEMI?!/
130}
131b
132
133:subshell
2d9ded8a 134# bare "(" line? -- stash for later printing
878f9883 135/^[ ]*([ ]*$/ {
878f9883
ES
136 h
137 bnextline
138}
139# "(..." line -- split off and stash "(", then process "..." as its own line
140x
141s/.*/(/
142x
143s/(//
144bslurp
145
146:nextline
147N
148s/.*\n//
149
150:slurp
151# incomplete line "...\"
a3c4c884 152/\\$/bicmplte
22e3e024
ES
153# multi-line quoted string "...\n..."?
154/"/bdqstring
155# multi-line quoted string '...\n...'? (but not contraction in string "it's")
156/'/{
878f9883
ES
157 /"[^'"]*'[^'"]*"/!bsqstring
158}
d9387114 159:folded
878f9883 160# here-doc -- swallow it
3042b6bb 161/<<[ ]*[-\\'"]*[A-Za-z0-9_]/bheredoc
878f9883
ES
162# comment or empty line -- discard since final non-comment, non-empty line
163# before closing ")", "done", "elsif", "else", or "fi" will need to be
164# re-visited to drop "suspect" marking since final line of those constructs
165# legitimately lacks "&&", so "suspect" mark must be removed
166/^[ ]*#/bnextline
167/^[ ]*$/bnextline
168# in-line comment -- strip it (but not "#" in a string, Bash ${#...} array
169# length, or Perforce "//depot/path#42" revision in filespec)
170/[ ]#/{
171 /"[^"]*#[^"]*"/!s/[ ]#.*$//
172}
173# one-liner "case ... esac"
a3c4c884 174/^[ ]*case[ ]*..*esac/bchkchn
878f9883
ES
175# multi-line "case ... esac"
176/^[ ]*case[ ]..*[ ]in/bcase
177# multi-line "for ... done" or "while ... done"
178/^[ ]*for[ ]..*[ ]in/bcontinue
179/^[ ]*while[ ]/bcontinue
180/^[ ]*do[ ]/bcontinue
181/^[ ]*do[ ]*$/bcontinue
182/;[ ]*do/bcontinue
183/^[ ]*done[ ]*&&[ ]*$/bdone
184/^[ ]*done[ ]*$/bdone
185/^[ ]*done[ ]*[<>|]/bdone
186/^[ ]*done[ ]*)/bdone
187/||[ ]*exit[ ]/bcontinue
188/||[ ]*exit[ ]*$/bcontinue
189# multi-line "if...elsif...else...fi"
190/^[ ]*if[ ]/bcontinue
191/^[ ]*then[ ]/bcontinue
192/^[ ]*then[ ]*$/bcontinue
193/;[ ]*then/bcontinue
194/^[ ]*elif[ ]/belse
195/^[ ]*elif[ ]*$/belse
196/^[ ]*else[ ]/belse
197/^[ ]*else[ ]*$/belse
198/^[ ]*fi[ ]*&&[ ]*$/bdone
199/^[ ]*fi[ ]*$/bdone
200/^[ ]*fi[ ]*[<>|]/bdone
201/^[ ]*fi[ ]*)/bdone
202# nested one-liner "(...) &&"
a3c4c884 203/^[ ]*(.*)[ ]*&&[ ]*$/bchkchn
878f9883 204# nested one-liner "(...)"
a3c4c884 205/^[ ]*(.*)[ ]*$/bchkchn
878f9883 206# nested one-liner "(...) >x" (or "2>x" or "<x" or "|x")
a3c4c884 207/^[ ]*(.*)[ ]*[0-9]*[<>|]/bchkchn
878f9883
ES
208# nested multi-line "(...\n...)"
209/^[ ]*(/bnest
210# multi-line "{...\n...}"
211/^[ ]*{/bblock
212# closing ")" on own line -- exit subshell
a3c4c884 213/^[ ]*)/bclssolo
878f9883 214# "$((...))" -- arithmetic expansion; not closing ")"
a3c4c884 215/\$(([^)][^)]*))[^)]*$/bchkchn
878f9883 216# "$(...)" -- command substitution; not closing ")"
a3c4c884 217/\$([^)][^)]*)[^)]*$/bchkchn
878f9883 218# multi-line "$(...\n...)" -- command substitution; treat as nested subshell
06fc5c9f 219/\$([^)]*$/bnest
878f9883 220# "=(...)" -- Bash array assignment; not closing ")"
a3c4c884 221/=(/bchkchn
878f9883
ES
222# closing "...) &&"
223/)[ ]*&&[ ]*$/bclose
224# closing "...)"
225/)[ ]*$/bclose
226# closing "...) >x" (or "2>x" or "<x" or "|x")
227/)[ ]*[<>|]/bclose
a3c4c884 228:chkchn
878f9883
ES
229# mark suspect if line uses ";" internally rather than "&&" (but not ";" in a
230# string and not ";;" in one-liner "case...esac")
231/;/{
232 /;;/!{
233 /"[^"]*;[^"]*"/!s/^/?!SEMI?!/
234 }
235}
236# line ends with pipe "...|" -- valid; not missing "&&"
237/|[ ]*$/bcontinue
238# missing end-of-line "&&" -- mark suspect
239/&&[ ]*$/!s/^/?!AMP?!/
240:continue
241# retrieve and print previous line
242x
243n
244bslurp
245
246# found incomplete line "...\" -- slurp up next line
a3c4c884 247:icmplte
878f9883
ES
248N
249s/\\\n//
250bslurp
251
22e3e024 252# check for multi-line double-quoted string "...\n..." -- fold to one line
878f9883 253:dqstring
22e3e024
ES
254# remove all quote pairs
255s/"\([^"]*\)"/@!\1@!/g
256# done if no dangling quote
257/"/!bdqdone
258# otherwise, slurp next line and try again
878f9883
ES
259N
260s/\n//
22e3e024
ES
261bdqstring
262:dqdone
263s/@!/"/g
d9387114 264bfolded
878f9883 265
22e3e024 266# check for multi-line single-quoted string '...\n...' -- fold to one line
878f9883 267:sqstring
22e3e024
ES
268# remove all quote pairs
269s/'\([^']*\)'/@!\1@!/g
270# done if no dangling quote
271/'/!bsqdone
272# otherwise, slurp next line and try again
878f9883
ES
273N
274s/\n//
22e3e024
ES
275bsqstring
276:sqdone
277s/@!/'/g
d9387114 278bfolded
878f9883
ES
279
280# found here-doc -- swallow it to avoid false hits within its body (but keep
c2c29cc0 281# the command to which it was attached)
878f9883 282:heredoc
3042b6bb 283s/^\(.*\)<<[ ]*[-\\'"]*\([A-Za-z0-9_][A-Za-z0-9_]*\)['"]*/<\2>\1<</
c2c29cc0 284s/[ ]*<<//
a3c4c884 285:heredsub
878f9883 286N
c2c29cc0
ES
287/^<\([^>]*\)>.*\n[ ]*\1[ ]*$/!{
288 s/\n.*$//
a3c4c884 289 bheredsub
c2c29cc0
ES
290}
291s/^<[^>]*>//
878f9883 292s/\n.*$//
d9387114 293bfolded
878f9883
ES
294
295# found "case ... in" -- pass through untouched
296:case
297x
298n
299/^[ ]*esac/bslurp
300bcase
301
302# found "else" or "elif" -- drop "suspect" from final line before "else" since
303# that line legitimately lacks "&&"
304:else
305x
306s/?!AMP?!//
307x
308bcontinue
309
310# found "done" closing for-loop or while-loop, or "fi" closing if-then -- drop
311# "suspect" from final contained line since that line legitimately lacks "&&"
312:done
313x
314s/?!AMP?!//
315x
316# is 'done' or 'fi' cuddled with ")" to close subshell?
317/done.*)/bclose
318/fi.*)/bclose
a3c4c884 319bchkchn
878f9883
ES
320
321# found nested multi-line "(...\n...)" -- pass through untouched
322:nest
323x
a3c4c884 324:nstslurp
878f9883
ES
325n
326# closing ")" on own line -- stop nested slurp
a3c4c884 327/^[ ]*)/bnstclose
878f9883 328# comment -- not closing ")" if in comment
a3c4c884 329/^[ ]*#/bnstcnt
878f9883 330# "$((...))" -- arithmetic expansion; not closing ")"
a3c4c884 331/\$(([^)][^)]*))[^)]*$/bnstcnt
878f9883 332# "$(...)" -- command substitution; not closing ")"
a3c4c884 333/\$([^)][^)]*)[^)]*$/bnstcnt
878f9883 334# closing "...)" -- stop nested slurp
a3c4c884
ÆAB
335/)/bnstclose
336:nstcnt
878f9883 337x
a3c4c884
ÆAB
338bnstslurp
339:nstclose
878f9883
ES
340s/^/>>/
341# is it "))" which closes nested and parent subshells?
342/)[ ]*)/bslurp
a3c4c884 343bchkchn
878f9883
ES
344
345# found multi-line "{...\n...}" block -- pass through untouched
346:block
347x
348n
349# closing "}" -- stop block slurp
a3c4c884 350/}/bchkchn
878f9883
ES
351bblock
352
353# found closing ")" on own line -- drop "suspect" from final line of subshell
354# since that line legitimately lacks "&&" and exit subshell loop
a3c4c884 355:clssolo
878f9883
ES
356x
357s/?!AMP?!//
358p
359x
360s/^/>/
361b
362
363# found closing "...)" -- exit subshell loop
364:close
365x
366p
367x
368s/^/>/
369b