@eslint-sh/eslint-plugin 0.4.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (256) hide show
  1. package/README.md +223 -17
  2. package/dist/command-harvest/builtins/bash.d.ts +36 -0
  3. package/dist/command-harvest/builtins/bash.js +98 -0
  4. package/dist/command-harvest/builtins/bats.d.ts +45 -0
  5. package/dist/command-harvest/builtins/bats.js +33 -0
  6. package/dist/command-harvest/builtins/index.d.ts +18 -0
  7. package/dist/command-harvest/builtins/index.js +39 -0
  8. package/dist/command-harvest/builtins/posix.d.ts +51 -0
  9. package/dist/command-harvest/builtins/posix.js +93 -0
  10. package/dist/command-harvest/harvest-commands.d.ts +66 -0
  11. package/dist/command-harvest/harvest-commands.js +97 -0
  12. package/dist/command-harvest/wrapper-table.d.ts +24 -0
  13. package/dist/command-harvest/wrapper-table.js +49 -0
  14. package/dist/configs/agent-authored.d.ts +41 -0
  15. package/dist/configs/agent-authored.js +33 -0
  16. package/dist/configs/bash-3-2.d.ts +28 -0
  17. package/dist/configs/bash-3-2.js +50 -0
  18. package/dist/configs/posix-portable.d.ts +40 -0
  19. package/dist/configs/posix-portable.js +52 -0
  20. package/dist/configs/recommended.d.ts +52 -0
  21. package/dist/configs/recommended.js +34 -0
  22. package/dist/declaration-conventions/comment-pragma.d.ts +33 -0
  23. package/dist/declaration-conventions/comment-pragma.js +153 -0
  24. package/dist/declaration-conventions/declaration-convention.d.ts +189 -0
  25. package/dist/declaration-conventions/declaration-convention.js +1 -0
  26. package/dist/declaration-conventions/read-declared-commands.d.ts +70 -0
  27. package/dist/declaration-conventions/read-declared-commands.js +128 -0
  28. package/dist/eslint-plugin-syntax-analyze.d.ts +23 -0
  29. package/dist/eslint-plugin-syntax.d.ts +27 -0
  30. package/dist/eslint-plugin.d.ts +1211 -52
  31. package/dist/index.d.ts +56 -8
  32. package/dist/index.js +53 -6
  33. package/dist/language/command-resolution.d.ts +124 -0
  34. package/dist/language/command-resolution.js +120 -0
  35. package/dist/language/end-of-input-marker.d.ts +29 -0
  36. package/dist/language/end-of-input-marker.js +88 -0
  37. package/dist/language/line-breaks.d.ts +18 -0
  38. package/dist/language/line-breaks.js +22 -0
  39. package/dist/language/lint-view-types.d.ts +104 -0
  40. package/dist/language/lint-view-types.js +1 -0
  41. package/dist/language/non-shell-file-guard.d.ts +35 -0
  42. package/dist/language/non-shell-file-guard.js +59 -0
  43. package/dist/language/selector-classes.d.ts +0 -1
  44. package/dist/language/selector-classes.js +0 -1
  45. package/dist/language/shell-language-options.d.ts +7 -1
  46. package/dist/language/shell-language-options.js +4 -4
  47. package/dist/language/shell-language.d.ts +10 -2
  48. package/dist/language/shell-language.js +59 -8
  49. package/dist/language/shell-source-code.d.ts +342 -5
  50. package/dist/language/shell-source-code.js +559 -30
  51. package/dist/language/source-coordinates.d.ts +13 -2
  52. package/dist/language/source-coordinates.js +68 -14
  53. package/dist/language/syntax-view.d.ts +60 -39
  54. package/dist/language/syntax-view.js +200 -45
  55. package/dist/language/word-resolution.d.ts +4 -3
  56. package/dist/language/word-resolution.js +4 -3
  57. package/dist/rules/compat/create-compat-rule.d.ts +59 -0
  58. package/dist/rules/compat/create-compat-rule.js +65 -0
  59. package/dist/rules/compat/declaration-options.d.ts +19 -0
  60. package/dist/rules/compat/declaration-options.js +110 -0
  61. package/dist/rules/compat/no-append-all-redirect.d.ts +13 -0
  62. package/dist/rules/compat/no-append-all-redirect.js +73 -0
  63. package/dist/rules/compat/no-associative-arrays.d.ts +12 -0
  64. package/dist/rules/compat/no-associative-arrays.js +39 -0
  65. package/dist/rules/compat/no-case-attributes.d.ts +12 -0
  66. package/dist/rules/compat/no-case-attributes.js +63 -0
  67. package/dist/rules/compat/no-case-fallthrough.d.ts +12 -0
  68. package/dist/rules/compat/no-case-fallthrough.js +67 -0
  69. package/dist/rules/compat/no-case-modification.d.ts +35 -0
  70. package/dist/rules/compat/no-case-modification.js +86 -0
  71. package/dist/rules/compat/no-coproc.d.ts +12 -0
  72. package/dist/rules/compat/no-coproc.js +47 -0
  73. package/dist/rules/compat/no-declare-g.d.ts +12 -0
  74. package/dist/rules/compat/no-declare-g.js +59 -0
  75. package/dist/rules/compat/no-double-bracket.d.ts +12 -0
  76. package/dist/rules/compat/no-double-bracket.js +52 -0
  77. package/dist/rules/compat/no-epoch-variables.d.ts +12 -0
  78. package/dist/rules/compat/no-epoch-variables.js +57 -0
  79. package/dist/rules/compat/no-fd-variable.d.ts +12 -0
  80. package/dist/rules/compat/no-fd-variable.js +48 -0
  81. package/dist/rules/compat/no-globstar.d.ts +12 -0
  82. package/dist/rules/compat/no-globstar.js +39 -0
  83. package/dist/rules/compat/no-lastpipe-semantics.d.ts +12 -0
  84. package/dist/rules/compat/no-lastpipe-semantics.js +53 -0
  85. package/dist/rules/compat/no-let.d.ts +12 -0
  86. package/dist/rules/compat/no-let.js +62 -0
  87. package/dist/rules/compat/no-mapfile.d.ts +13 -0
  88. package/dist/rules/compat/no-mapfile.js +122 -0
  89. package/dist/rules/compat/no-nameref.d.ts +12 -0
  90. package/dist/rules/compat/no-nameref.js +44 -0
  91. package/dist/rules/compat/no-negative-array-index.d.ts +13 -0
  92. package/dist/rules/compat/no-negative-array-index.js +123 -0
  93. package/dist/rules/compat/no-nounset-empty-array.d.ts +13 -0
  94. package/dist/rules/compat/no-nounset-empty-array.js +158 -0
  95. package/dist/rules/compat/no-parameter-transformation.d.ts +13 -0
  96. package/dist/rules/compat/no-parameter-transformation.js +123 -0
  97. package/dist/rules/compat/no-pipe-all.d.ts +13 -0
  98. package/dist/rules/compat/no-pipe-all.js +68 -0
  99. package/dist/rules/compat/no-printf-time-format.d.ts +12 -0
  100. package/dist/rules/compat/no-printf-time-format.js +99 -0
  101. package/dist/rules/compat/no-source.d.ts +13 -0
  102. package/dist/rules/compat/no-source.js +103 -0
  103. package/dist/rules/compat/no-test-v.d.ts +12 -0
  104. package/dist/rules/compat/no-test-v.js +280 -0
  105. package/dist/rules/compat/no-unicode-escape.d.ts +14 -0
  106. package/dist/rules/compat/no-unicode-escape.js +120 -0
  107. package/dist/rules/compat/no-wait-n.d.ts +12 -0
  108. package/dist/rules/compat/no-wait-n.js +73 -0
  109. package/dist/rules/compat/operator-range.d.ts +10 -0
  110. package/dist/rules/compat/operator-range.js +15 -0
  111. package/dist/rules/compat/posix-absent-command.d.ts +12 -0
  112. package/dist/rules/compat/posix-absent-command.js +134 -0
  113. package/dist/rules/compat/shopt-options.d.ts +9 -0
  114. package/dist/rules/compat/shopt-options.js +63 -0
  115. package/dist/rules/compat/version-gates.d.ts +76 -0
  116. package/dist/rules/compat/version-gates.js +197 -0
  117. package/dist/rules/create-rule.d.ts +91 -0
  118. package/dist/rules/create-rule.js +51 -0
  119. package/dist/rules/create-wrapper-rule.d.ts +166 -0
  120. package/dist/rules/create-wrapper-rule.js +407 -0
  121. package/dist/rules/define-shell-rule.d.ts +184 -0
  122. package/dist/rules/define-shell-rule.js +56 -0
  123. package/dist/rules/index.d.ts +26 -0
  124. package/dist/rules/index.js +88 -0
  125. package/dist/rules/max-script-complexity.d.ts +28 -0
  126. package/dist/rules/max-script-complexity.js +130 -0
  127. package/dist/rules/no-dynamic-invocation.d.ts +35 -0
  128. package/dist/rules/no-dynamic-invocation.js +117 -0
  129. package/dist/rules/no-eval.d.ts +13 -0
  130. package/dist/rules/no-eval.js +89 -0
  131. package/dist/rules/no-masked-exit-status.d.ts +21 -0
  132. package/dist/rules/no-masked-exit-status.js +667 -0
  133. package/dist/rules/no-secret-env-reads.d.ts +24 -0
  134. package/dist/rules/no-secret-env-reads.js +133 -0
  135. package/dist/rules/no-secret-flags.d.ts +32 -0
  136. package/dist/rules/no-secret-flags.js +162 -0
  137. package/dist/rules/no-unchecked-cd.d.ts +44 -0
  138. package/dist/rules/no-unchecked-cd.js +250 -0
  139. package/dist/rules/no-undeclared-commands.d.ts +20 -0
  140. package/dist/rules/no-undeclared-commands.js +115 -0
  141. package/dist/rules/no-unused-declarations.d.ts +23 -0
  142. package/dist/rules/no-unused-declarations.js +259 -0
  143. package/dist/rules/prefer-local.d.ts +16 -0
  144. package/dist/rules/prefer-local.js +249 -0
  145. package/dist/rules/quote-expansions.d.ts +13 -0
  146. package/dist/rules/quote-expansions.js +229 -0
  147. package/dist/rules/require-network-wrapper.d.ts +40 -0
  148. package/dist/rules/require-network-wrapper.js +244 -0
  149. package/dist/rules/require-shebang.d.ts +29 -0
  150. package/dist/rules/require-shebang.js +101 -0
  151. package/dist/rules/require-strict-mode.d.ts +52 -0
  152. package/dist/rules/require-strict-mode.js +177 -0
  153. package/dist/rules/require-tty-guard.d.ts +13 -0
  154. package/dist/rules/require-tty-guard.js +289 -0
  155. package/dist/rules/resolve-declaration-convention.d.ts +96 -0
  156. package/dist/rules/resolve-declaration-convention.js +109 -0
  157. package/dist/rules/resolve-network-wrapper.d.ts +19 -0
  158. package/dist/rules/resolve-network-wrapper.js +116 -0
  159. package/dist/rules/set-options.d.ts +109 -0
  160. package/dist/rules/set-options.js +201 -0
  161. package/dist/rules/shebang-interpreter.d.ts +18 -0
  162. package/dist/rules/shebang-interpreter.js +81 -0
  163. package/dist/rules/shebang-matches-dialect.d.ts +12 -0
  164. package/dist/rules/shebang-matches-dialect.js +69 -0
  165. package/dist/rules/transparent-wrappers.d.ts +52 -0
  166. package/dist/rules/transparent-wrappers.js +99 -0
  167. package/dist/rules/user-pattern.d.ts +63 -0
  168. package/dist/rules/user-pattern.js +184 -0
  169. package/dist/syntax/analyze.d.ts +19 -0
  170. package/dist/syntax/analyze.js +21 -0
  171. package/dist/syntax/index.d.ts +23 -0
  172. package/dist/syntax/index.js +25 -0
  173. package/dist/version.d.ts +0 -1
  174. package/dist/version.js +0 -1
  175. package/docs/compat.md +347 -0
  176. package/docs/custom-rule-tutorial.md +780 -34
  177. package/docs/declaration-conventions.md +478 -0
  178. package/docs/examples/audit-wrapper-pack.mjs +15 -0
  179. package/docs/examples/dep-line-convention.mjs +22 -0
  180. package/docs/examples/eslint.config.mjs +3 -2
  181. package/docs/examples/no-and-or-as-if-rule.mjs +61 -0
  182. package/docs/examples/no-backticks-rule-tester.mjs +106 -0
  183. package/docs/examples/no-backticks-rule.mjs +60 -0
  184. package/docs/examples/no-curl-in-functions-rule.mjs +53 -0
  185. package/docs/examples/no-function-keyword-rule.mjs +59 -0
  186. package/docs/examples/no-listed-commands-rule.ts +63 -0
  187. package/docs/examples/no-rm-rf-rule.mjs +26 -15
  188. package/docs/examples/no-unquoted-rm-variable-rule.ts +54 -0
  189. package/docs/examples/sidecar-convention.mjs +38 -0
  190. package/docs/examples/sidecar-snapshot-convention.mjs +26 -0
  191. package/docs/examples/sorted-declarations-rule.mjs +52 -0
  192. package/docs/reading-nodes.md +159 -0
  193. package/docs/rules/README.md +78 -0
  194. package/docs/rules/_template.md +83 -0
  195. package/docs/rules/compat/no-append-all-redirect.md +87 -0
  196. package/docs/rules/compat/no-associative-arrays.md +95 -0
  197. package/docs/rules/compat/no-case-attributes.md +82 -0
  198. package/docs/rules/compat/no-case-fallthrough.md +90 -0
  199. package/docs/rules/compat/no-case-modification.md +74 -0
  200. package/docs/rules/compat/no-coproc.md +76 -0
  201. package/docs/rules/compat/no-declare-g.md +80 -0
  202. package/docs/rules/compat/no-double-bracket.md +72 -0
  203. package/docs/rules/compat/no-epoch-variables.md +79 -0
  204. package/docs/rules/compat/no-fd-variable.md +78 -0
  205. package/docs/rules/compat/no-globstar.md +74 -0
  206. package/docs/rules/compat/no-lastpipe-semantics.md +73 -0
  207. package/docs/rules/compat/no-let.md +78 -0
  208. package/docs/rules/compat/no-mapfile.md +118 -0
  209. package/docs/rules/compat/no-nameref.md +81 -0
  210. package/docs/rules/compat/no-negative-array-index.md +87 -0
  211. package/docs/rules/compat/no-nounset-empty-array.md +103 -0
  212. package/docs/rules/compat/no-parameter-transformation.md +94 -0
  213. package/docs/rules/compat/no-pipe-all.md +85 -0
  214. package/docs/rules/compat/no-printf-time-format.md +75 -0
  215. package/docs/rules/compat/no-source.md +98 -0
  216. package/docs/rules/compat/no-test-v.md +82 -0
  217. package/docs/rules/compat/no-unicode-escape.md +91 -0
  218. package/docs/rules/compat/no-wait-n.md +85 -0
  219. package/docs/rules/max-script-complexity.md +109 -0
  220. package/docs/rules/no-dynamic-invocation.md +139 -0
  221. package/docs/rules/no-eval.md +106 -0
  222. package/docs/rules/no-masked-exit-status.md +239 -0
  223. package/docs/rules/no-secret-env-reads.md +173 -0
  224. package/docs/rules/no-secret-flags.md +169 -0
  225. package/docs/rules/no-unchecked-cd.md +146 -0
  226. package/docs/rules/no-undeclared-commands.md +141 -0
  227. package/docs/rules/no-unused-declarations.md +216 -0
  228. package/docs/rules/prefer-local.md +221 -0
  229. package/docs/rules/quote-expansions.md +157 -0
  230. package/docs/rules/require-network-wrapper.md +171 -0
  231. package/docs/rules/require-shebang.md +157 -0
  232. package/docs/rules/require-strict-mode.md +253 -0
  233. package/docs/rules/require-tty-guard.md +128 -0
  234. package/docs/rules/shebang-matches-dialect.md +120 -0
  235. package/docs/selectors.md +2 -2
  236. package/docs/shellcheck.md +86 -0
  237. package/docs/wrapper-rules.md +206 -0
  238. package/package.json +26 -20
  239. package/dist/index.d.ts.map +0 -1
  240. package/dist/index.js.map +0 -1
  241. package/dist/language/selector-classes.d.ts.map +0 -1
  242. package/dist/language/selector-classes.js.map +0 -1
  243. package/dist/language/shell-language-options.d.ts.map +0 -1
  244. package/dist/language/shell-language-options.js.map +0 -1
  245. package/dist/language/shell-language.d.ts.map +0 -1
  246. package/dist/language/shell-language.js.map +0 -1
  247. package/dist/language/shell-source-code.d.ts.map +0 -1
  248. package/dist/language/shell-source-code.js.map +0 -1
  249. package/dist/language/source-coordinates.d.ts.map +0 -1
  250. package/dist/language/source-coordinates.js.map +0 -1
  251. package/dist/language/syntax-view.d.ts.map +0 -1
  252. package/dist/language/syntax-view.js.map +0 -1
  253. package/dist/language/word-resolution.d.ts.map +0 -1
  254. package/dist/language/word-resolution.js.map +0 -1
  255. package/dist/version.d.ts.map +0 -1
  256. package/dist/version.js.map +0 -1
@@ -0,0 +1,90 @@
1
+ # `sh/compat/no-case-fallthrough`
2
+
3
+ Disallow the `case` terminators `;&` (run the next branch without testing its pattern) and `;;&`
4
+ (go on testing the next patterns) in scripts whose target shell lacks them: Bash older than 4.0,
5
+ including the Bash 3.2 that macOS ships as `/bin/bash`, and POSIX sh.
6
+
7
+ | Configs | Fixable | Languages |
8
+ | ------- | ------- | --------- |
9
+ | `bash-3.2`, `posix-portable` | not fixable | `sh/sh` |
10
+
11
+ ## Rule details
12
+
13
+ Both terminators arrived in Bash 4.0 (Bash NEWS for 4.0). Bash 3.2 rejects them as a syntax error (``syntax error near unexpected token `&'``), so the whole script fails to run. POSIX.1-2024 adds `;&`, but dash 0.5.12 still rejects it (`Syntax error: "&" unexpected`), so the rule treats a POSIX target as lacking it until the `sh` binaries people deploy to catch up.
14
+
15
+ The rule reports a `case` branch whose terminator is `;&` or `;;&`; the report is on the terminator. The usual `;;`, a branch with no terminator, and these characters in quotes or in a pattern are not reported.
16
+
17
+ There is no mechanical rewrite, so the rule offers no suggestion. The message names the usual restructuring: for `;&`, repeat the next branch's commands at the end of this branch and end it with `;;`; for `;;&`, end this branch with `;;` and test the patterns that follow it in a second `case` statement.
18
+
19
+ Under `dialect: "posix"` the parser itself rejects both terminators, so a POSIX file that uses them fails to parse and this rule does not run on it.
20
+
21
+ This is a version-compat rule. Whether it reports depends only on `languageOptions`, as
22
+ described in [version compatibility](../../compat.md):
23
+
24
+ - `shellVersion` unset: never reports. An unset version means the newest Bash.
25
+ - `dialect: "bash"` (the default) or `"bats"`: reports when `shellVersion` is older than `4.0`.
26
+ - `dialect: "mksh"` or `"zsh"`: never reports.
27
+
28
+ ## Options
29
+
30
+ This rule has no options. Set the target through `languageOptions`:
31
+
32
+ ```js
33
+ // eslint.config.js
34
+ {
35
+ languageOptions: { dialect: 'bash', shellVersion: '3.2' },
36
+ rules: {
37
+ 'sh/compat/no-case-fallthrough': 'error',
38
+ },
39
+ }
40
+ ```
41
+
42
+ ## Examples
43
+
44
+ ### Incorrect
45
+
46
+ With `shellVersion: "3.2"`:
47
+
48
+ <!-- example: {"dialect": "bash", "shellVersion": "3.2", "findings": 2} -->
49
+ ```sh
50
+ case $level in
51
+ debug) verbose=1 ;&
52
+ info) log=1 ;;
53
+ esac
54
+ case $file in
55
+ *.tar.gz) compressed=1 ;;&
56
+ *.tar*) archive=1 ;;
57
+ esac
58
+ ```
59
+
60
+ ### Correct
61
+
62
+ With `shellVersion: "3.2"`:
63
+
64
+ <!-- example: {"dialect": "bash", "shellVersion": "3.2"} -->
65
+ ```sh
66
+ case $level in
67
+ debug) verbose=1; log=1 ;;
68
+ info) log=1 ;;
69
+ esac
70
+ case $file in
71
+ *.tar.gz) compressed=1 ;;
72
+ esac
73
+ case $file in
74
+ *.tar*) archive=1 ;;
75
+ esac
76
+ ```
77
+
78
+ With `shellVersion: "4.0"` or newer, or with no `shellVersion`, both terminators are fine.
79
+
80
+ ## When not to use it
81
+
82
+ Turn the rule off when your scripts never run on Bash older than 4.0, or leave `shellVersion`
83
+ unset, which makes the rule report nothing.
84
+
85
+ ## Relationship to shellcheck
86
+
87
+ eslint-sh is a convention and contract linter: a policy layer, where shellcheck is the gotcha
88
+ layer. Run shellcheck beside it.
89
+
90
+ Under `-s sh`, shellcheck 0.11.0 reports `;&` and `;;&` as an error, SC2127 ("To use cases with ;&, specify #!/usr/bin/env bash or ksh", or the `;;&` equivalent naming bash), not as one of its SC3xxx portability checks. It reports the same SC2127 under `-s dash`, reports `;;&` (but not `;&`) under `-s ksh`, and reports nothing under `-s bash`. It does not know which Bash release a script must run on, so it reports nothing for either operator in a Bash 3.2 target. This rule exists for that target.
@@ -0,0 +1,74 @@
1
+ # `sh/compat/no-case-modification`
2
+
3
+ Disallow the case-modifying parameter expansions `${v^}`, `${v^^}`, `${v,}` and `${v,,}` in scripts whose
4
+ target shell lacks them: Bash older than 4.0, including the Bash 3.2 that macOS ships as `/bin/bash`.
5
+
6
+ | Configs | Fixable | Languages |
7
+ | ------- | ------- | --------- |
8
+ | `bash-3.2`, `posix-portable` | not fixable | `sh/sh` |
9
+
10
+ ## Rule details
11
+
12
+ The four case-modifying expansions arrived in Bash 4.0 (Bash NEWS for 4.0: "new case-modifying word
13
+ expansions"). Bash 3.2 fails the command with `bad substitution` when the word is expanded.
14
+
15
+ The report is on the whole expansion, with or without a trailing pattern (`${v^^[aeiou]}`). The message shows the `tr` replacement for the operator written: `tr` over the whole value for `^^` and `,,`, and `tr` over the first character (`${v%"${v#?}"}`) followed by the rest (`${v#?}`) for `^` and `,`. A pattern form such as `${w^^a}` changes only the characters the pattern matches, which a plain case-converting `tr` does not reproduce (`banana` becomes `bAnAnA`, not `BANANA`), so for those forms the message offers no `tr` equivalent and tells you to spell out the matching characters.
16
+
17
+ This is a version-compat rule. Whether it reports depends only on `languageOptions`, as
18
+ described in [version compatibility](../../compat.md):
19
+
20
+ - `shellVersion` unset: never reports. An unset version means the newest Bash.
21
+ - `dialect: "bash"` (the default) or `"bats"`: reports when `shellVersion` is older than `4.0`.
22
+ - `dialect: "posix"`: reports nothing in practice for this syntax, because the POSIX parser rejects it with a parse error before any rule runs.
23
+ - `dialect: "mksh"` or `"zsh"`: never reports.
24
+
25
+ ## Options
26
+
27
+ This rule has no options. Set the target through `languageOptions`:
28
+
29
+ ```js
30
+ // eslint.config.js
31
+ {
32
+ languageOptions: { dialect: 'bash', shellVersion: '3.2' },
33
+ rules: {
34
+ 'sh/compat/no-case-modification': 'error',
35
+ },
36
+ }
37
+ ```
38
+
39
+ ## Examples
40
+
41
+ ### Incorrect
42
+
43
+ With `shellVersion: "3.2"`:
44
+
45
+ <!-- example: {"dialect": "bash", "shellVersion": "3.2", "findings": 3} -->
46
+ ```sh
47
+ name=${input,,} # lowercase every character
48
+ title=${word^} # uppercase the first character
49
+ flag=${arg^^[a-z]} # with a pattern
50
+ ```
51
+
52
+ ### Correct
53
+
54
+ With `shellVersion: "3.2"`:
55
+
56
+ <!-- example: {"dialect": "bash", "shellVersion": "3.2"} -->
57
+ ```sh
58
+ name=$(printf '%s' "$input" | tr '[:upper:]' '[:lower:]')
59
+ title=$(printf '%s' "${word%"${word#?}"}" | tr '[:lower:]' '[:upper:]')"${word#?}"
60
+ ```
61
+
62
+ With `shellVersion: "4.0"` or newer, or with no `shellVersion`, the original forms are fine.
63
+
64
+ ## When not to use it
65
+
66
+ Turn the rule off when your scripts never run on Bash older than 4.0, or leave
67
+ `shellVersion` unset, which makes the rule report nothing.
68
+
69
+ ## Relationship to shellcheck
70
+
71
+ eslint-sh is a convention and contract linter: a policy layer, where shellcheck is the gotcha
72
+ layer. Run shellcheck beside it.
73
+
74
+ shellcheck reports case-modifying expansions in scripts it treats as POSIX sh (SC3059), but it cannot target a Bash version, so it cannot report `${v,,}` in a script that must run on Bash 3.2. This rule exists for that target.
@@ -0,0 +1,76 @@
1
+ # `sh/compat/no-coproc`
2
+
3
+ Disallow the `coproc` reserved word in scripts whose target shell lacks it: Bash older than 4.0,
4
+ including the Bash 3.2 that macOS ships as `/bin/bash`, and POSIX sh.
5
+
6
+ | Configs | Fixable | Languages |
7
+ | ------- | ------- | --------- |
8
+ | `bash-3.2`, `posix-portable` | not fixable | `sh/sh` |
9
+
10
+ ## Rule details
11
+
12
+ `coproc` arrived in Bash 4.0 (Bash NEWS for 4.0: "a new `coproc` reserved word"). On Bash 3.2 the word is an ordinary command name, so `coproc cat` fails with `command not found` and the `COPROC` array is never set.
13
+
14
+ The rule reports every `coproc` clause, named or not, on the whole clause. Under `dialect: "posix"` the parser reads `coproc cat` as an ordinary command, and the rule reports a command named `coproc` too.
15
+
16
+ The portable alternative is a named pipe: create it with `mkfifo`, start the coprocess in the background reading or writing the pipe, and open the pipe from the script.
17
+
18
+ This is a version-compat rule. Whether it reports depends only on `languageOptions`, as
19
+ described in [version compatibility](../../compat.md):
20
+
21
+ - `shellVersion` unset: never reports. An unset version means the newest Bash.
22
+ - `dialect: "bash"` (the default) or `"bats"`: reports when `shellVersion` is older than `4.0`.
23
+ - `dialect: "posix"`: reports at any `shellVersion`.
24
+ - `dialect: "mksh"` or `"zsh"`: never reports.
25
+
26
+ ## Options
27
+
28
+ This rule has no options. Set the target through `languageOptions`:
29
+
30
+ ```js
31
+ // eslint.config.js
32
+ {
33
+ languageOptions: { dialect: 'bash', shellVersion: '3.2' },
34
+ rules: {
35
+ 'sh/compat/no-coproc': 'error',
36
+ },
37
+ }
38
+ ```
39
+
40
+ ## Examples
41
+
42
+ ### Incorrect
43
+
44
+ With `shellVersion: "3.2"`:
45
+
46
+ <!-- example: {"dialect": "bash", "shellVersion": "3.2", "findings": 2} -->
47
+ ```sh
48
+ coproc worker { cat; }
49
+ coproc sort
50
+ ```
51
+
52
+ ### Correct
53
+
54
+ With `shellVersion: "3.2"`:
55
+
56
+ <!-- example: {"dialect": "bash", "shellVersion": "3.2"} -->
57
+ ```sh
58
+ fifo=$(mktemp -u) && mkfifo "$fifo"
59
+ cat < "$fifo" &
60
+ exec 3> "$fifo"
61
+ printf '%s\n' hello >&3
62
+ ```
63
+
64
+ With `shellVersion: "4.0"` or newer, or with no `shellVersion`, the original forms are fine.
65
+
66
+ ## When not to use it
67
+
68
+ Turn the rule off when your scripts never run on Bash older than 4.0, or leave
69
+ `shellVersion` unset, which makes the rule report nothing.
70
+
71
+ ## Relationship to shellcheck
72
+
73
+ eslint-sh is a convention and contract linter: a policy layer, where shellcheck is the gotcha
74
+ layer. Run shellcheck beside it.
75
+
76
+ shellcheck has no check for `coproc` in a script that must run on an older Bash; it cannot target a Bash version. This rule exists for that target.
@@ -0,0 +1,80 @@
1
+ # `sh/compat/no-declare-g`
2
+
3
+ Disallow `declare -g` and `typeset -g` in scripts whose target shell lacks the `-g` option: Bash
4
+ older than 4.2, including the Bash 3.2 that macOS ships as `/bin/bash`, and POSIX sh.
5
+
6
+ | Configs | Fixable | Languages |
7
+ | ------- | ------- | --------- |
8
+ | `bash-3.2`, `posix-portable` | not fixable | `sh/sh` |
9
+
10
+ ## Rule details
11
+
12
+ `declare -g` arrived in Bash 4.2 (Bash NEWS for 4.2: "declare/typeset has a new `-g` option"). Bash 3.2 rejects it as an invalid option, so the declaration fails and the variable is never created.
13
+
14
+ The rule reads the options of every `declare` and `typeset` command, up to the first operand, as the builtin does. An option word reports when it is a `-` cluster containing `g` (`-g`, `-gx`, `-Ag`); the report is on that word. `local`, `readonly` and `export` are ignored, and so is a `-g` after the first operand or after `--`.
15
+
16
+ The portable alternative: in Bash before 4.2 a plain `name=value` inside a function sets the global, as long as the function has not declared the name `local`. The two are not identical: a plain `name=value` assigns to a `local` of the same name declared by a calling function (dynamic scope) instead of creating a global, and it does not carry the attributes `declare -g` can set, such as `-i`, `-A` and `-r`, so a script using those must restructure rather than drop the flag.
17
+
18
+ The message says so. For a plain `-g` (and `-gx`, which `export name=value` covers) it suggests the assignment. For any other option (`declare -Ag m`, `typeset -gi n=1`, or as separate words `declare -g -A m`, `typeset -g -r c`) it does not suggest `name=value`, which would drop the attribute; it points at declaring the variable outside any function, or raising the target.
19
+
20
+ This is a version-compat rule. Whether it reports depends only on `languageOptions`, as
21
+ described in [version compatibility](../../compat.md):
22
+
23
+ - `shellVersion` unset: never reports. An unset version means the newest Bash.
24
+ - `dialect: "bash"` (the default) or `"bats"`: reports when `shellVersion` is older than `4.2`.
25
+ - `dialect: "posix"`: reports at any `shellVersion`, because POSIX sh has no `declare`.
26
+ - `dialect: "mksh"` or `"zsh"`: never reports.
27
+
28
+ ## Options
29
+
30
+ This rule has no options. Set the target through `languageOptions`:
31
+
32
+ ```js
33
+ // eslint.config.js
34
+ {
35
+ languageOptions: { dialect: 'bash', shellVersion: '3.2' },
36
+ rules: {
37
+ 'sh/compat/no-declare-g': 'error',
38
+ },
39
+ }
40
+ ```
41
+
42
+ ## Examples
43
+
44
+ ### Incorrect
45
+
46
+ With `shellVersion: "3.2"`:
47
+
48
+ <!-- example: {"dialect": "bash", "shellVersion": "3.2", "findings": 2} -->
49
+ ```sh
50
+ f() {
51
+ declare -g counter=0
52
+ typeset -gx exported=1
53
+ }
54
+ ```
55
+
56
+ ### Correct
57
+
58
+ With `shellVersion: "3.2"`:
59
+
60
+ <!-- example: {"dialect": "bash", "shellVersion": "3.2"} -->
61
+ ```sh
62
+ f() {
63
+ counter=0
64
+ export exported=1
65
+ }
66
+ ```
67
+
68
+ With `shellVersion: "4.2"` or newer, or with no `shellVersion`, the original forms are fine.
69
+
70
+ ## When not to use it
71
+
72
+ Turn the rule off when your scripts never run on Bash older than 4.2, or leave
73
+ `shellVersion` unset, which makes the rule report nothing.
74
+
75
+ ## Relationship to shellcheck
76
+
77
+ eslint-sh is a convention and contract linter: a policy layer, where shellcheck is the gotcha
78
+ layer. Run shellcheck beside it.
79
+
80
+ shellcheck does not know which Bash a script must run on, so it cannot report `declare -g` for a Bash 3.2 target. This rule exists for that target.
@@ -0,0 +1,72 @@
1
+ # `sh/compat/no-double-bracket`
2
+
3
+ Disallow `[[ … ]]` in scripts that target POSIX sh.
4
+
5
+ | Configs | Fixable | Languages |
6
+ | ------- | ------- | --------- |
7
+ | `posix-portable` | not fixable | `sh/sh` |
8
+
9
+ ## Rule details
10
+
11
+ POSIX lists `[[` and `]]` among the words whose results are unspecified ([Shell Command Language, section 2.4](https://pubs.opengroup.org/onlinepubs/9799919799/utilities/V3_chap02.html#tag_19_04)). dash, the `/bin/sh` of Debian and Ubuntu, looks up a command named `[[` and fails with `[[: not found` and status 127. Under `set -e` a bare test stops the script. In an `if` condition the script carries on down the wrong branch: `if [[ a == a ]]; then echo yes; else echo no; fi` prints `no`.
12
+
13
+ `bash --posix` and BusyBox ash both accept `[[`, so a script can pass on them and still fail on dash.
14
+
15
+ Under `dialect: "posix"` the parser reads `[[` as an ordinary command, and the rule reports a command whose effective name is `[[`, after following wrappers such as `command`. It reports the `[[` word, because the parser ends that command at the first `&&` or `||`. `command -v [[` only asks about the word and is not reported. A use only a shell with a version variable can reach is not reported either, such as the body of `if [ -n "${BASH_VERSION-}" ]`, because dash never runs it; [version compatibility](../../compat.md#a-branch-for-one-shell) lists the shapes recognized. A `[[` containing a parenthesis, such as `[[ $a =~ ^(x|y)$ ]]`, is a parse error under `dialect: "posix"` and never reaches this rule.
16
+
17
+ The portable alternatives are `[ … ]` for tests and `case` for pattern matching. The rule offers no suggestion, because `[` splits and globs its unquoted operands and has no spelling for `==` patterns or `=~`.
18
+
19
+ This is a POSIX-only gate. Every Bash has `[[`, so whether the rule reports depends only on `dialect`, as described in [version compatibility](../../compat.md):
20
+
21
+ - `dialect: "posix"`: reports at any `shellVersion`.
22
+ - `dialect: "bash"` (the default), `"bats"`, `"mksh"` or `"zsh"`: never reports.
23
+
24
+ ## Options
25
+
26
+ This rule has no options. Set the target through `languageOptions`:
27
+
28
+ ```js
29
+ // eslint.config.js
30
+ {
31
+ languageOptions: { dialect: 'posix' },
32
+ rules: {
33
+ 'sh/compat/no-double-bracket': 'error',
34
+ },
35
+ }
36
+ ```
37
+
38
+ ## Examples
39
+
40
+ ### Incorrect
41
+
42
+ With `dialect: "posix"`:
43
+
44
+ <!-- example: {"dialect": "posix", "findings": 2} -->
45
+ ```sh
46
+ if [[ "$1" == "x" ]]; then echo hi; fi
47
+ [[ -n "$a" && -n "$b" ]]
48
+ ```
49
+
50
+ ### Correct
51
+
52
+ With `dialect: "posix"`:
53
+
54
+ <!-- example: {"dialect": "posix"} -->
55
+ ```sh
56
+ if [ "$1" = "x" ]; then echo hi; fi
57
+ [ -n "$a" ] && [ -n "$b" ]
58
+ case "$1" in
59
+ x*) echo starts-with-x ;;
60
+ esac
61
+ ```
62
+
63
+ ## When not to use it
64
+
65
+ Turn the rule off when your scripts only ever run where `[[` exists, such as under Bash or BusyBox ash, and set `dialect` to match if you can.
66
+
67
+ ## Relationship to shellcheck
68
+
69
+ eslint-sh is a convention and contract linter: a policy layer, where shellcheck is the gotcha
70
+ layer. Run shellcheck beside it.
71
+
72
+ shellcheck reports this construct as SC3010 for a script whose shebang says `sh`. This rule reports it for the dialect you configure, whatever the shebang says, and at the severity you choose. shellcheck keeps the misuse of `[[` and `[`, which this rule does not look at.
@@ -0,0 +1,79 @@
1
+ # `sh/compat/no-epoch-variables`
2
+
3
+ Disallow `$EPOCHSECONDS` and `$EPOCHREALTIME` in scripts whose target shell lacks them: Bash older
4
+ than 5.0, including the Bash 3.2 that macOS ships as `/bin/bash`, and POSIX sh.
5
+
6
+ | Configs | Fixable | Languages |
7
+ | ------- | ------- | --------- |
8
+ | `bash-3.2`, `posix-portable` | not fixable | `sh/sh` |
9
+
10
+ ## Rule details
11
+
12
+ Both variables arrived in Bash 5.0 (Bash NEWS for 5.0). On an older Bash they are simply unset: nothing fails, and the script quietly gets an empty string where it expected a timestamp. Under `set -u` it aborts instead.
13
+
14
+ The rule reports every parameter expansion of either name (`$EPOCHSECONDS`, `${EPOCHSECONDS:-0}`), unquoted, in double quotes, or in an unquoted here-document, and either name written bare as an operand in arithmetic (`$(( EPOCHSECONDS - start ))`, `(( t = EPOCHREALTIME ))`). Text in single quotes or in a quoted here-document is not an expansion and is not reported, and neither is a longer name such as `$EPOCHSECONDSX`. The report is on the expansion or the bare name.
15
+
16
+ A read inside a feature-detect that already falls back (`case "${EPOCHREALTIME:-}" in [0-9]*) … ;; *) fallback ;; esac`, or `${EPOCHSECONDS:-$(date +%s)}`) still reports: the rule cannot tell that the fallback makes the read safe, so suppress those lines or use the portable form.
17
+
18
+ The usual alternative is `$(date +%s)`. It is not POSIX: `%s` is an extension that GNU, BSD and BusyBox `date` all have. It gives whole seconds, so `$EPOCHREALTIME`'s fraction has no portable equivalent; its message says so.
19
+
20
+ This is a version-compat rule. Whether it reports depends only on `languageOptions`, as
21
+ described in [version compatibility](../../compat.md):
22
+
23
+ - `shellVersion` unset: never reports. An unset version means the newest Bash.
24
+ - `dialect: "bash"` (the default) or `"bats"`: reports when `shellVersion` is older than `5.0`.
25
+ - `dialect: "posix"`: reports at any `shellVersion`, because POSIX sh does not set either variable.
26
+ - `dialect: "mksh"` or `"zsh"`: never reports.
27
+
28
+ ## Options
29
+
30
+ This rule has no options. Set the target through `languageOptions`:
31
+
32
+ ```js
33
+ // eslint.config.js
34
+ {
35
+ languageOptions: { dialect: 'bash', shellVersion: '3.2' },
36
+ rules: {
37
+ 'sh/compat/no-epoch-variables': 'error',
38
+ },
39
+ }
40
+ ```
41
+
42
+ ## Examples
43
+
44
+ ### Incorrect
45
+
46
+ With `shellVersion: "3.2"`:
47
+
48
+ <!-- example: {"dialect": "bash", "shellVersion": "3.2", "findings": 2} -->
49
+ ```sh
50
+ start=$EPOCHSECONDS
51
+ run_tests
52
+ echo "took $(( EPOCHSECONDS - start ))s"
53
+ ```
54
+
55
+ ### Correct
56
+
57
+ With `shellVersion: "3.2"`:
58
+
59
+ <!-- example: {"dialect": "bash", "shellVersion": "3.2"} -->
60
+ ```sh
61
+ start=$(date +%s)
62
+ run_tests
63
+ echo "took $(( $(date +%s) - start ))s"
64
+ echo 'the variable is spelled $EPOCHSECONDS'
65
+ ```
66
+
67
+ With `shellVersion: "5.0"` or newer, or with no `shellVersion`, both variables are fine.
68
+
69
+ ## When not to use it
70
+
71
+ Turn the rule off when your scripts never run on Bash older than 5.0, or leave `shellVersion`
72
+ unset, which makes the rule report nothing.
73
+
74
+ ## Relationship to shellcheck
75
+
76
+ eslint-sh is a convention and contract linter: a policy layer, where shellcheck is the gotcha
77
+ layer. Run shellcheck beside it.
78
+
79
+ shellcheck 0.11.0 reports `$EPOCHSECONDS` and `$EPOCHREALTIME` as undefined in POSIX sh (SC3028) under `-s sh`, and nothing under `-s bash`. It does not know which Bash release a script must run on, so it reports nothing for either variable in a Bash 4.4 target. This rule exists for that target.
@@ -0,0 +1,78 @@
1
+ # `sh/compat/no-fd-variable`
2
+
3
+ Disallow `{varname}` redirections, which have the shell pick a free file descriptor and store its
4
+ number in a variable (`exec {fd}>file`), in scripts whose target shell lacks them: Bash older than
5
+ 4.1, including the Bash 3.2 that macOS ships as `/bin/bash`, and POSIX sh.
6
+
7
+ | Configs | Fixable | Languages |
8
+ | ------- | ------- | --------- |
9
+ | `bash-3.2`, `posix-portable` | not fixable | `sh/sh` |
10
+
11
+ ## Rule details
12
+
13
+ Descriptor allocation arrived in Bash 4.1 (Bash NEWS for 4.1). Bash 4.0 and older treat `{fd}` as an ordinary word, so `exec {fd}>out` tries to run a command named `{fd}` and fails with `exec: {fd}: not found`.
14
+
15
+ The rule reports a redirection whose descriptor is written `{name}`, which the parser records separately from the redirection's target; the report is on the whole redirection, including when it closes the descriptor (`exec {fd}>&-`). A numbered descriptor (`3>file`), a `{…}` word separated from the redirection by a space (`echo {fd} >out`), a brace group, and the same text in quotes are not reported.
16
+
17
+ The portable alternative is a fixed descriptor number: `exec 3>file`, write with `>&3`, then close it with `exec 3>&-`. Pick a number the script does not use for anything else.
18
+
19
+ Under `dialect: "posix"` the parser itself rejects `{varname}` redirections, so a POSIX file that uses one fails to parse and this rule does not run on it.
20
+
21
+ This is a version-compat rule. Whether it reports depends only on `languageOptions`, as
22
+ described in [version compatibility](../../compat.md):
23
+
24
+ - `shellVersion` unset: never reports. An unset version means the newest Bash.
25
+ - `dialect: "bash"` (the default) or `"bats"`: reports when `shellVersion` is older than `4.1`.
26
+ - `dialect: "mksh"` or `"zsh"`: never reports.
27
+
28
+ ## Options
29
+
30
+ This rule has no options. Set the target through `languageOptions`:
31
+
32
+ ```js
33
+ // eslint.config.js
34
+ {
35
+ languageOptions: { dialect: 'bash', shellVersion: '3.2' },
36
+ rules: {
37
+ 'sh/compat/no-fd-variable': 'error',
38
+ },
39
+ }
40
+ ```
41
+
42
+ ## Examples
43
+
44
+ ### Incorrect
45
+
46
+ With `shellVersion: "3.2"`:
47
+
48
+ <!-- example: {"dialect": "bash", "shellVersion": "3.2", "findings": 2} -->
49
+ ```sh
50
+ exec {log}>>"$logfile"
51
+ echo started >&"$log"
52
+ exec {log}>&-
53
+ ```
54
+
55
+ ### Correct
56
+
57
+ With `shellVersion: "3.2"`:
58
+
59
+ <!-- example: {"dialect": "bash", "shellVersion": "3.2"} -->
60
+ ```sh
61
+ exec 3>>"$logfile"
62
+ echo started >&3
63
+ exec 3>&-
64
+ ```
65
+
66
+ With `shellVersion: "4.1"` or newer, or with no `shellVersion`, `{varname}` redirections are fine.
67
+
68
+ ## When not to use it
69
+
70
+ Turn the rule off when your scripts never run on Bash older than 4.1, or leave `shellVersion`
71
+ unset, which makes the rule report nothing.
72
+
73
+ ## Relationship to shellcheck
74
+
75
+ eslint-sh is a convention and contract linter: a policy layer, where shellcheck is the gotcha
76
+ layer. Run shellcheck beside it.
77
+
78
+ shellcheck reports `{varname}` redirections only for a script it treats as POSIX `sh` (its SC3xxx portability checks). It does not know which Bash release a script must run on, so it cannot report them for a Bash 3.2 target. This rule exists for that target.
@@ -0,0 +1,74 @@
1
+ # `sh/compat/no-globstar`
2
+
3
+ Disallow `shopt -s globstar` in scripts whose target shell lacks the option: Bash older than 4.0,
4
+ including the Bash 3.2 that macOS ships as `/bin/bash`, and POSIX sh.
5
+
6
+ | Configs | Fixable | Languages |
7
+ | ------- | ------- | --------- |
8
+ | `bash-3.2`, `posix-portable` | not fixable | `sh/sh` |
9
+
10
+ ## Rule details
11
+
12
+ The `globstar` option, which makes `**` match across directories, arrived in Bash 4.0 (Bash NEWS for 4.0). Bash 3.2 has no such option: `shopt` prints `globstar: invalid shell option name`, the script carries on, and `**` then matches like `*`. A recursive glob therefore matches fewer files, often none, with no failure.
13
+
14
+ The rule reads `shopt` commands (also through `builtin` or `command`) whose options, up to the first operand, include `-s`, and reports a `globstar` operand; the report is on that word. `shopt -u globstar` turns the option off, a `-u` or `-o` beside the `-s` changes what the command does, a query (`shopt globstar`, `shopt -q globstar`) turns nothing on, and an operand that is not a static word cannot be read, so none of those reports.
15
+
16
+ The portable alternative is `find`, for example `find dir -name '*.txt'`.
17
+
18
+ This is a version-compat rule. Whether it reports depends only on `languageOptions`, as
19
+ described in [version compatibility](../../compat.md):
20
+
21
+ - `shellVersion` unset: never reports. An unset version means the newest Bash.
22
+ - `dialect: "bash"` (the default) or `"bats"`: reports when `shellVersion` is older than `4.0`.
23
+ - `dialect: "posix"`: reports at any `shellVersion`, because POSIX sh has no `shopt`.
24
+ - `dialect: "mksh"` or `"zsh"`: never reports.
25
+
26
+ ## Options
27
+
28
+ This rule has no options. Set the target through `languageOptions`:
29
+
30
+ ```js
31
+ // eslint.config.js
32
+ {
33
+ languageOptions: { dialect: 'bash', shellVersion: '3.2' },
34
+ rules: {
35
+ 'sh/compat/no-globstar': 'error',
36
+ },
37
+ }
38
+ ```
39
+
40
+ ## Examples
41
+
42
+ ### Incorrect
43
+
44
+ With `shellVersion: "3.2"`:
45
+
46
+ <!-- example: {"dialect": "bash", "shellVersion": "3.2", "findings": 1} -->
47
+ ```sh
48
+ shopt -s nullglob globstar
49
+ for f in src/**/*.sh; do lint "$f"; done
50
+ ```
51
+
52
+ ### Correct
53
+
54
+ With `shellVersion: "3.2"`:
55
+
56
+ <!-- example: {"dialect": "bash", "shellVersion": "3.2"} -->
57
+ ```sh
58
+ find src -name '*.sh' -exec lint {} \;
59
+ shopt -s nullglob
60
+ ```
61
+
62
+ With `shellVersion: "4.0"` or newer, or with no `shellVersion`, `globstar` is fine.
63
+
64
+ ## When not to use it
65
+
66
+ Turn the rule off when your scripts never run on Bash older than 4.0, or leave `shellVersion`
67
+ unset, which makes the rule report nothing.
68
+
69
+ ## Relationship to shellcheck
70
+
71
+ eslint-sh is a convention and contract linter: a policy layer, where shellcheck is the gotcha
72
+ layer. Run shellcheck beside it.
73
+
74
+ shellcheck reports `shopt` only for a script it treats as POSIX `sh` (its SC3xxx portability checks). It does not know which Bash release a script must run on, so it cannot report `globstar` for a Bash 3.2 target. This rule exists for that target.