Skip to content

Commit d52da62

Browse files
committed
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
2 parents 62a3a27 + d73f5cf commit d52da62

File tree

71 files changed

+339
-293
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

71 files changed

+339
-293
lines changed

t/Makefile

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -71,12 +71,10 @@ clean-chainlint:
7171

7272
check-chainlint:
7373
@mkdir -p '$(CHAINLINTTMP_SQ)' && \
74-
err=0 && \
75-
for i in $(CHAINLINTTESTS); do \
76-
$(CHAINLINT) <chainlint/$$i.test | \
77-
sed -e '/^# LINT: /d' >'$(CHAINLINTTMP_SQ)'/$$i.actual && \
78-
diff -u chainlint/$$i.expect '$(CHAINLINTTMP_SQ)'/$$i.actual || err=1; \
79-
done && exit $$err
74+
sed -e '/^# LINT: /d' $(patsubst %,chainlint/%.test,$(CHAINLINTTESTS)) >'$(CHAINLINTTMP_SQ)'/tests && \
75+
sed -e '/^[ ]*$$/d' $(patsubst %,chainlint/%.expect,$(CHAINLINTTESTS)) >'$(CHAINLINTTMP_SQ)'/expect && \
76+
$(CHAINLINT) '$(CHAINLINTTMP_SQ)'/tests | grep -v '^[ ]*$$' >'$(CHAINLINTTMP_SQ)'/actual && \
77+
diff -u '$(CHAINLINTTMP_SQ)'/expect '$(CHAINLINTTMP_SQ)'/actual
8078

8179
test-lint: test-lint-duplicates test-lint-executable test-lint-shell-syntax \
8280
test-lint-filenames

t/chainlint.sed

Lines changed: 77 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,9 @@
2424
# in order to avoid misinterpreting the ")" in constructs such as "x=$(...)"
2525
# and "case $x in *)" as ending the subshell.
2626
#
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.
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.
3030
#
3131
# Detection of a missing &&-link in a multi-line subshell is complicated by the
3232
# fact that the last statement before the closing ")" must not end with "&&".
@@ -47,8 +47,8 @@
4747
# "?!AMP?!" violation is removed from the "bar" line (retrieved from the "hold"
4848
# area) since the final statement of a subshell must not end with "&&". The
4949
# 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).
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.
5252
#
5353
# Care is taken to recognize the last _statement_ of a multi-line subshell, not
5454
# necessarily the last textual _line_ within the subshell, since &&-chaining
@@ -62,26 +62,20 @@
6262
# receives similar treatment.
6363
#
6464
# 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".
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".
6767
# As each subsequent line is read, it is appended to the target line and a
6868
# (whitespace-loose) back-reference match /^<(.*)>\n\1$/ is attempted to see if
6969
# the content inside "<...>" matches the entirety of the newly-read line. For
7070
# 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
71+
# target line, it becomes "<EOF>cat <<EOF\nsome data", and a match is attempted
7272
# to see if "EOF" matches "some data". Since it doesn't, the next line is
7373
# 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",
74+
# encountered, it is appended to the target line giving "<EOF>cat <<EOF\nEOF",
7575
# in which case the "EOF" inside "<...>" does match the text following the
7676
# newline, thus the closing here-doc tag has been found. The closing tag line
7777
# and the "<...>" prefix on the target line are then discarded, leaving just
78-
# the target line "cat >out".
79-
#
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.
78+
# the target line "cat <<EOF".
8579
#------------------------------------------------------------------------------
8680

8781
# incomplete line -- slurp up next line
@@ -94,9 +88,9 @@
9488

9589
# here-doc -- swallow it to avoid false hits within its body (but keep the
9690
# command to which it was attached)
97-
/<<[ ]*[-\\'"]*[A-Za-z0-9_]/ {
98-
s/^\(.*\)<<[ ]*[-\\'"]*\([A-Za-z0-9_][A-Za-z0-9_]*\)['"]*/<\2>\1<</
99-
s/[ ]*<<//
91+
/<<-*[ ]*[\\'"]*[A-Za-z0-9_]/ {
92+
/"[^"]*<<[^"]*"/bnotdoc
93+
s/^\(.*<<-*[ ]*\)[\\'"]*\([A-Za-z0-9_][A-Za-z0-9_]*\)['"]*/<\2>\1\2/
10094
:hered
10195
N
10296
/^<\([^>]*\)>.*\n[ ]*\1[ ]*$/!{
@@ -106,6 +100,7 @@
106100
s/^<[^>]*>//
107101
s/\n.*$//
108102
}
103+
:notdoc
109104

110105
# one-liner "(...) &&"
111106
/^[ ]*!*[ ]*(..*)[ ]*&&[ ]*$/boneline
@@ -126,7 +121,7 @@ b
126121
# "&&" (but not ";" in a string)
127122
:oneline
128123
/;/{
129-
/"[^"]*;[^"]*"/!s/^/?!SEMI?!/
124+
/"[^"]*;[^"]*"/!s/;/; ?!AMP?!/
130125
}
131126
b
132127

@@ -136,11 +131,15 @@ b
136131
h
137132
bnextln
138133
}
139-
# "(..." line -- split off and stash "(", then process "..." as its own line
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
140139
x
141-
s/.*/(/
140+
s/.*//
142141
x
143-
s/(//
142+
s/(/^/
144143
bslurp
145144

146145
:nextln
@@ -157,8 +156,10 @@ s/.*\n//
157156
/"[^'"]*'[^'"]*"/!bsqstr
158157
}
159158
:folded
160-
# here-doc -- swallow it
161-
/<<[ ]*[-\\'"]*[A-Za-z0-9_]/bheredoc
159+
# here-doc -- swallow it (but not "<<" in a string)
160+
/<<-*[ ]*[\\'"]*[A-Za-z0-9_]/{
161+
/"[^"]*<<[^"]*"/!bheredoc
162+
}
162163
# comment or empty line -- discard since final non-comment, non-empty line
163164
# before closing ")", "done", "elsif", "else", or "fi" will need to be
164165
# re-visited to drop "suspect" marking since final line of those constructs
@@ -171,12 +172,12 @@ s/.*\n//
171172
/"[^"]*#[^"]*"/!s/[ ]#.*$//
172173
}
173174
# one-liner "case ... esac"
174-
/^[ ]*case[ ]*..*esac/bchkchn
175+
/^[ ^]*case[ ]*..*esac/bchkchn
175176
# multi-line "case ... esac"
176-
/^[ ]*case[ ]..*[ ]in/bcase
177+
/^[ ^]*case[ ]..*[ ]in/bcase
177178
# multi-line "for ... done" or "while ... done"
178-
/^[ ]*for[ ]..*[ ]in/bcont
179-
/^[ ]*while[ ]/bcont
179+
/^[ ^]*for[ ]..*[ ]in/bcont
180+
/^[ ^]*while[ ]/bcont
180181
/^[ ]*do[ ]/bcont
181182
/^[ ]*do[ ]*$/bcont
182183
/;[ ]*do/bcont
@@ -187,7 +188,7 @@ s/.*\n//
187188
/||[ ]*exit[ ]/bcont
188189
/||[ ]*exit[ ]*$/bcont
189190
# multi-line "if...elsif...else...fi"
190-
/^[ ]*if[ ]/bcont
191+
/^[ ^]*if[ ]/bcont
191192
/^[ ]*then[ ]/bcont
192193
/^[ ]*then[ ]*$/bcont
193194
/;[ ]*then/bcont
@@ -200,15 +201,15 @@ s/.*\n//
200201
/^[ ]*fi[ ]*[<>|]/bdone
201202
/^[ ]*fi[ ]*)/bdone
202203
# nested one-liner "(...) &&"
203-
/^[ ]*(.*)[ ]*&&[ ]*$/bchkchn
204+
/^[ ^]*(.*)[ ]*&&[ ]*$/bchkchn
204205
# nested one-liner "(...)"
205-
/^[ ]*(.*)[ ]*$/bchkchn
206+
/^[ ^]*(.*)[ ]*$/bchkchn
206207
# nested one-liner "(...) >x" (or "2>x" or "<x" or "|x")
207-
/^[ ]*(.*)[ ]*[0-9]*[<>|]/bchkchn
208+
/^[ ^]*(.*)[ ]*[0-9]*[<>|]/bchkchn
208209
# nested multi-line "(...\n...)"
209-
/^[ ]*(/bnest
210+
/^[ ^]*(/bnest
210211
# multi-line "{...\n...}"
211-
/^[ ]*{/bblock
212+
/^[ ^]*{/bblock
212213
# closing ")" on own line -- exit subshell
213214
/^[ ]*)/bclssolo
214215
# "$((...))" -- arithmetic expansion; not closing ")"
@@ -230,16 +231,18 @@ s/.*\n//
230231
# string and not ";;" in one-liner "case...esac")
231232
/;/{
232233
/;;/!{
233-
/"[^"]*;[^"]*"/!s/^/?!SEMI?!/
234+
/"[^"]*;[^"]*"/!s/;/; ?!AMP?!/
234235
}
235236
}
236237
# line ends with pipe "...|" -- valid; not missing "&&"
237238
/|[ ]*$/bcont
238239
# missing end-of-line "&&" -- mark suspect
239-
/&&[ ]*$/!s/^/?!AMP?!/
240+
/&&[ ]*$/!s/$/ ?!AMP?!/
240241
:cont
241242
# retrieve and print previous line
242243
x
244+
s/^\([ ]*\)^/\1(/
245+
s/?!HERE?!/<</g
243246
n
244247
bslurp
245248

@@ -280,8 +283,7 @@ bfolded
280283
# found here-doc -- swallow it to avoid false hits within its body (but keep
281284
# the command to which it was attached)
282285
:heredoc
283-
s/^\(.*\)<<[ ]*[-\\'"]*\([A-Za-z0-9_][A-Za-z0-9_]*\)['"]*/<\2>\1<</
284-
s/[ ]*<<//
286+
s/^\(.*\)<<\(-*[ ]*\)[\\'"]*\([A-Za-z0-9_][A-Za-z0-9_]*\)['"]*/<\3>\1?!HERE?!\2\3/
285287
:hdocsub
286288
N
287289
/^<\([^>]*\)>.*\n[ ]*\1[ ]*$/!{
@@ -295,23 +297,31 @@ bfolded
295297
# found "case ... in" -- pass through untouched
296298
:case
297299
x
300+
s/^\([ ]*\)^/\1(/
301+
s/?!HERE?!/<</g
298302
n
303+
:cascom
304+
/^[ ]*#/{
305+
N
306+
s/.*\n//
307+
bcascom
308+
}
299309
/^[ ]*esac/bslurp
300310
bcase
301311

302312
# found "else" or "elif" -- drop "suspect" from final line before "else" since
303313
# that line legitimately lacks "&&"
304314
:else
305315
x
306-
s/?!AMP?!//
316+
s/\( ?!AMP?!\)* ?!AMP?!$//
307317
x
308318
bcont
309319

310320
# found "done" closing for-loop or while-loop, or "fi" closing if-then -- drop
311321
# "suspect" from final contained line since that line legitimately lacks "&&"
312322
:done
313323
x
314-
s/?!AMP?!//
324+
s/\( ?!AMP?!\)* ?!AMP?!$//
315325
x
316326
# is 'done' or 'fi' cuddled with ")" to close subshell?
317327
/done.*)/bclose
@@ -322,11 +332,18 @@ bchkchn
322332
:nest
323333
x
324334
:nstslrp
335+
s/^\([ ]*\)^/\1(/
336+
s/?!HERE?!/<</g
325337
n
338+
:nstcom
339+
# comment -- not closing ")" if in comment
340+
/^[ ]*#/{
341+
N
342+
s/.*\n//
343+
bnstcom
344+
}
326345
# closing ")" on own line -- stop nested slurp
327346
/^[ ]*)/bnstcl
328-
# comment -- not closing ")" if in comment
329-
/^[ ]*#/bnstcnt
330347
# "$((...))" -- arithmetic expansion; not closing ")"
331348
/\$(([^)][^)]*))[^)]*$/bnstcnt
332349
# "$(...)" -- command substitution; not closing ")"
@@ -337,15 +354,22 @@ n
337354
x
338355
bnstslrp
339356
:nstcl
340-
s/^/>>/
341357
# is it "))" which closes nested and parent subshells?
342358
/)[ ]*)/bslurp
343359
bchkchn
344360

345361
# found multi-line "{...\n...}" block -- pass through untouched
346362
:block
347363
x
364+
s/^\([ ]*\)^/\1(/
365+
s/?!HERE?!/<</g
348366
n
367+
:blkcom
368+
/^[ ]*#/{
369+
N
370+
s/.*\n//
371+
bblkcom
372+
}
349373
# closing "}" -- stop block slurp
350374
/}/bchkchn
351375
bblock
@@ -354,16 +378,22 @@ bblock
354378
# since that line legitimately lacks "&&" and exit subshell loop
355379
:clssolo
356380
x
357-
s/?!AMP?!//
381+
s/\( ?!AMP?!\)* ?!AMP?!$//
382+
s/^\([ ]*\)^/\1(/
383+
s/?!HERE?!/<</g
358384
p
359385
x
360-
s/^/>/
386+
s/^\([ ]*\)^/\1(/
387+
s/?!HERE?!/<</g
361388
b
362389

363390
# found closing "...)" -- exit subshell loop
364391
:close
365392
x
393+
s/^\([ ]*\)^/\1(/
394+
s/?!HERE?!/<</g
366395
p
367396
x
368-
s/^/>/
397+
s/^\([ ]*\)^/\1(/
398+
s/?!HERE?!/<</g
369399
b

t/chainlint/arithmetic-expansion.expect

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22
foo &&
33
bar=$((42 + 1)) &&
44
baz
5-
>) &&
5+
) &&
66
(
7-
?!AMP?! bar=$((42 + 1))
7+
bar=$((42 + 1)) ?!AMP?!
88
baz
9-
>)
9+
)

t/chainlint/bash-array.expect

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22
foo &&
33
bar=(gumbo stumbo wumbo) &&
44
baz
5-
>) &&
5+
) &&
66
(
77
foo &&
88
bar=${#bar[@]} &&
99
baz
10-
>)
10+
)

t/chainlint/blank-line.expect

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
(
22
nothing &&
33
something
4-
>)
4+
)

t/chainlint/blank-line.test

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
nothing &&
44

55
something
6-
# LINT: swallow blank lines since final _statement_ before subshell end is
6+
# LINT: ignore blank lines since final _statement_ before subshell end is
77
# LINT: significant to "&&"-check, not final _line_ (which might be blank)
88

99

t/chainlint/block-comment.expect

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
(
2+
{
3+
echo a &&
4+
echo b
5+
}
6+
)

t/chainlint/block-comment.test

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
(
2+
{
3+
# show a
4+
echo a &&
5+
# show b
6+
echo b
7+
}
8+
)

t/chainlint/block.expect

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,6 @@
77
bar &&
88
{
99
echo c
10-
?!AMP?! }
10+
} ?!AMP?!
1111
baz
12-
>)
12+
)

t/chainlint/block.test

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
(
2-
# LINT: missing "&&" in block not currently detected (for consistency with
3-
# LINT: --chain-lint at top level and to provide escape hatch if needed)
2+
# LINT: missing "&&" after first "echo"
43
foo &&
54
{
65
echo a

0 commit comments

Comments
 (0)