@eslint-sh/eslint-plugin 0.5.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,197 @@
1
+ /**
2
+ * Every gated feature, keyed by the name rules pass to
3
+ * {@link isFeatureAvailable}.
4
+ */
5
+ const FEATURE_GATES = Object.freeze({
6
+ /**
7
+ * The `&>>` redirection, appending both stdout and stderr: Bash 4.0 NEWS,
8
+ * "the new `&>>` redirection operator". Bash 3.2 rejects it as a syntax
9
+ * error, so the whole script fails to parse.
10
+ */
11
+ appendAllRedirect: Object.freeze({ bash: '4.0', posix: false }),
12
+ /** `declare -A` / `typeset -A`: Bash 4.0 NEWS, "associative arrays". */
13
+ associativeArrays: Object.freeze({ bash: '4.0', posix: false }),
14
+ /**
15
+ * The case-modifying expansions `${v^}`, `${v^^}`, `${v,}`, `${v,,}`: Bash
16
+ * 4.0 NEWS, "new case-modifying word expansions".
17
+ */
18
+ caseModification: Object.freeze({ bash: '4.0', posix: false }),
19
+ /**
20
+ * The case-converting attributes `declare -l` / `-u` (and through
21
+ * `typeset` and `local`): Bash 4.0 NEWS, "new `-l` and `-u` options to
22
+ * declare/typeset/local".
23
+ */
24
+ caseAttributes: Object.freeze({ bash: '4.0', posix: false }),
25
+ /**
26
+ * The `case` terminators `;&` (fall through) and `;;&` (test the next
27
+ * pattern): Bash 4.0 NEWS. POSIX.1-2024 adds `;&`, but dash 0.5.12 still
28
+ * rejects it, so a POSIX target does not have it.
29
+ */
30
+ caseFallthrough: Object.freeze({ bash: '4.0', posix: false }),
31
+ /** The `coproc` reserved word: Bash 4.0 NEWS, "a new `coproc` reserved word". */
32
+ coproc: Object.freeze({ bash: '4.0', posix: false }),
33
+ /**
34
+ * `declare -g` / `typeset -g`: Bash 4.2 NEWS, "declare/typeset has a new
35
+ * `-g` option".
36
+ */
37
+ declareGlobal: Object.freeze({ bash: '4.2', posix: false }),
38
+ /**
39
+ * The `[[ … ]]` conditional. POSIX section 2.4 lists `[[` and `]]` among the
40
+ * words whose results are unspecified; every Bash has it, and dash runs it
41
+ * as a command that does not exist.
42
+ */
43
+ doubleBracket: Object.freeze({ bash: null, posix: false }),
44
+ /** `$EPOCHSECONDS` and `$EPOCHREALTIME`: Bash 5.0 NEWS. */
45
+ epochVariables: Object.freeze({ bash: '5.0', posix: false }),
46
+ /**
47
+ * A `{varname}` redirection that allocates a descriptor into a variable
48
+ * (`exec {fd}>file`): Bash 4.1 NEWS. Bash 4.0 runs `{fd}` as a command.
49
+ */
50
+ fdVariable: Object.freeze({ bash: '4.1', posix: false }),
51
+ /** `shopt -s globstar` (recursive `**`): Bash 4.0 NEWS. */
52
+ globstar: Object.freeze({ bash: '4.0', posix: false }),
53
+ /** `shopt -s lastpipe`: Bash 4.2 NEWS, "a new `lastpipe` shell option". */
54
+ lastpipe: Object.freeze({ bash: '4.2', posix: false }),
55
+ /**
56
+ * The `let` builtin. POSIX section 2.9.1.4 lists `let` among the command
57
+ * names whose results are unspecified; every Bash has it, and dash has no
58
+ * such command.
59
+ */
60
+ letCommand: Object.freeze({ bash: null, posix: false }),
61
+ /** The `mapfile` builtin and its `readarray` synonym: Bash 4.0 NEWS. */
62
+ mapfile: Object.freeze({ bash: '4.0', posix: false }),
63
+ /** Namerefs, `declare -n` / `typeset -n` / `local -n`: Bash 4.3 NEWS. */
64
+ nameref: Object.freeze({ bash: '4.3', posix: false }),
65
+ /**
66
+ * Negative indexed-array subscripts (`${a[-1]}`, `a[-1]=x`): Bash 4.3 NEWS,
67
+ * "assigning, referencing, and unsetting elements of indexed arrays using
68
+ * negative subscripts". Bash 4.2 already reads `${a[-1]}`, but rejects the
69
+ * element length `${#a[-1]}`, assignment and unset, so 4.3 is the first
70
+ * release where every use works.
71
+ */
72
+ negativeArrayIndex: Object.freeze({ bash: '4.3', posix: false }),
73
+ /**
74
+ * Expanding an empty array (`"${a[@]}"`, `${a[*]}`) while `set -u` is on:
75
+ * the syntax is in every Bash, but through 4.3 it aborts with "unbound
76
+ * variable"; Bash 4.4 is the first release where it expands to nothing.
77
+ */
78
+ nounsetEmptyArray: Object.freeze({ bash: '4.4', posix: false }),
79
+ /**
80
+ * The parameter transformations `${v@Q}`, `${v@E}`, `${v@P}`, `${v@A}` and
81
+ * `${v@a}`: Bash 4.4 NEWS.
82
+ */
83
+ parameterTransformation: Object.freeze({ bash: '4.4', posix: false }),
84
+ /** The case transformations `${v@U}`, `${v@u}` and `${v@L}`: Bash 5.1 NEWS. */
85
+ parameterTransformationCase: Object.freeze({ bash: '5.1', posix: false }),
86
+ /** `${a[@]@k}`, key-value pairs as separate words: Bash 5.2 NEWS. */
87
+ parameterTransformationKeys: Object.freeze({ bash: '5.2', posix: false }),
88
+ /** `${a[@]@K}`, quoted key-value pairs: Bash 5.1 NEWS. */
89
+ parameterTransformationQuotedKeys: Object.freeze({ bash: '5.1', posix: false }),
90
+ /**
91
+ * The `|&` pipe, shorthand for `2>&1 |`: Bash 4.0 NEWS. Bash 3.2 rejects
92
+ * it as a syntax error.
93
+ */
94
+ pipeAll: Object.freeze({ bash: '4.0', posix: false }),
95
+ /** `printf`'s `%(fmt)T` date format: Bash 4.2 NEWS. */
96
+ printfTimeFormat: Object.freeze({ bash: '4.2', posix: false }),
97
+ /**
98
+ * The `source` builtin, Bash's name for `.`. POSIX section 2.9.1.4 lists
99
+ * `source` among the command names whose results are unspecified; every Bash
100
+ * has it, and dash has no such command.
101
+ */
102
+ sourceCommand: Object.freeze({ bash: null, posix: false }),
103
+ /**
104
+ * The `-v name` test (`[[ -v x ]]`, `test -v x`, `[ -v x ]`): Bash 4.2
105
+ * NEWS. In older Bash, `[[ -v x ]]` is a syntax error.
106
+ */
107
+ testVariableSet: Object.freeze({ bash: '4.2', posix: false }),
108
+ /**
109
+ * The `\u`/`\U` escapes in `$'…'` strings: Bash 4.2 NEWS. Earlier releases
110
+ * leave the escape as literal text. See `unicodeEscapeLatin1` for the one
111
+ * range the 4.2 release decodes wrongly.
112
+ */
113
+ unicodeEscape: Object.freeze({ bash: '4.2', posix: false }),
114
+ /**
115
+ * A `\u`/`\U` escape for a code point from U+0080 to U+00FF: the Bash 4.2
116
+ * release tarball writes these as a single Latin-1 byte even in a UTF-8
117
+ * locale (observed: `e9` for U+00E9, where UTF-8 is `c3 a9`), while every
118
+ * other code point is UTF-8. Bash 4.3 is the first release that writes all
119
+ * of them as UTF-8.
120
+ */
121
+ unicodeEscapeLatin1: Object.freeze({ bash: '4.3', posix: false }),
122
+ /** `wait -n`, waiting for the next job to finish: Bash 4.3 NEWS. */
123
+ waitNext: Object.freeze({ bash: '4.3', posix: false }),
124
+ });
125
+ /**
126
+ * Splits a `MAJOR[.MINOR]` string into numbers, so `"4"` is `[4, 0]` and
127
+ * `"3.10"` is `[3, 10]`. Returns `undefined` for any other shape; the
128
+ * language's option validation already rejects those, so the gate never
129
+ * sees one from a real config.
130
+ */
131
+ function parseVersion(version) {
132
+ if (!/^\d+(?:\.\d+)?$/u.test(version))
133
+ return undefined;
134
+ const [major, minor = '0'] = version.split('.');
135
+ return [Number(major), Number(minor)];
136
+ }
137
+ /**
138
+ * Compares two `MAJOR[.MINOR]` versions numerically, major first, then minor:
139
+ * negative when `a` is older, `0` when equal, positive when `a` is newer. So
140
+ * `"3.10"` is newer than `"3.2"`, and `"4"` equals `"4.0"`.
141
+ */
142
+ function compareVersions(a, b) {
143
+ return a[0] !== b[0] ? a[0] - b[0] : a[1] - b[1];
144
+ }
145
+ /**
146
+ * The dialects whose `shellVersion` is a Bash version. `bats` test files run
147
+ * under Bash, so they are gated exactly like `bash` files.
148
+ */
149
+ const BASH_DIALECTS = new Set(['bash', 'bats']);
150
+ /**
151
+ * Whether `feature` is available to a file linted with `languageOptions`.
152
+ *
153
+ * - `dialect: "posix"`: available only when the gate's `posix` is `true`,
154
+ * at any `shellVersion` (a Bash-only feature is never available).
155
+ * - `dialect: "bash"` (the default) or `"bats"`: an unset `shellVersion`
156
+ * means the newest Bash, so every Bash feature is available. A set
157
+ * `shellVersion` makes the feature available when it is the gate's `bash`
158
+ * minimum or newer, comparing major then minor numerically (`"4"` is
159
+ * `4.0`; `"3.10"` is newer than `"3.2"`). A `null` minimum is available at
160
+ * every version.
161
+ * - `dialect: "mksh"` or `"zsh"`: the table carries no data for these
162
+ * shells, and their `shellVersion` numbers their own releases, not Bash's,
163
+ * so no gate applies and every feature is available.
164
+ *
165
+ * @internal
166
+ */
167
+ export function isFeatureAvailable(feature, languageOptions) {
168
+ const gate = FEATURE_GATES[feature];
169
+ const dialect = languageOptions.dialect ?? 'bash';
170
+ if (dialect === 'posix')
171
+ return gate.posix;
172
+ if (!BASH_DIALECTS.has(dialect))
173
+ return true;
174
+ const { shellVersion } = languageOptions;
175
+ if (shellVersion === undefined || gate.bash === null)
176
+ return true;
177
+ const target = parseVersion(shellVersion);
178
+ const minimum = parseVersion(gate.bash);
179
+ if (target === undefined || minimum === undefined)
180
+ return true;
181
+ return compareVersions(target, minimum) >= 0;
182
+ }
183
+ /**
184
+ * Describes why `feature` is unavailable under `languageOptions`, for a
185
+ * report's message data. Call it only when {@link isFeatureAvailable} is
186
+ * `false`.
187
+ *
188
+ * @internal
189
+ */
190
+ export function describeGate(feature, languageOptions) {
191
+ const gate = FEATURE_GATES[feature];
192
+ const required = gate.bash === null ? 'bash' : `bash ${gate.bash} or newer`;
193
+ const target = (languageOptions.dialect ?? 'bash') === 'posix'
194
+ ? 'POSIX sh'
195
+ : `bash ${languageOptions.shellVersion ?? ''}`.trim();
196
+ return { required, target };
197
+ }
@@ -0,0 +1,91 @@
1
+ /**
2
+ * The factory for this plugin's built-in rules: the public
3
+ * {@link defineShellRule} plus the one piece of metadata that only built-in
4
+ * rules share.
5
+ *
6
+ * `meta.docs.url` is derived from the rule name and always points at the
7
+ * rule's committed docs page, so the link a user follows from a lint report
8
+ * can never be hand-mistyped or left stale after a rename. Everything else —
9
+ * `meta.languages: ["sh/sh"]` and the shell-typed context, nodes and visitor —
10
+ * comes from `defineShellRule`, so built-in and third-party rules share one
11
+ * implementation of that guarantee.
12
+ *
13
+ * This module is internal (not re-exported from the package entry point): the
14
+ * docs URL scheme is this repository's. Rule packs use `defineShellRule` and
15
+ * write their own `meta.docs.url`.
16
+ */
17
+ import type { RulesMetaDocs } from '@eslint/core';
18
+ import type { ShellRule, ShellRuleContext, ShellRuleMeta, ShellRuleVisitor } from './define-shell-rule.js';
19
+ /**
20
+ * The canonical documentation URL for the built-in rule `name`. This is the
21
+ * single place the URL scheme is spelled; the factory uses it to fill
22
+ * `meta.docs.url`, and the docs-page tests use it to check each registered
23
+ * rule points at its own committed page.
24
+ *
25
+ * @internal
26
+ */
27
+ export declare function ruleDocsUrl(name: string): string;
28
+ /**
29
+ * The documentation URL for `page`, a path relative to this package's `docs/`
30
+ * directory (`wrapper-rules.md` is the committed file
31
+ * `packages/eslint-plugin/docs/wrapper-rules.md`). Used for rules that are not
32
+ * in the registry, which have no `docs/rules/<name>.md` page of their own.
33
+ *
34
+ * @internal
35
+ */
36
+ export declare function packageDocsUrl(page: string): string;
37
+ /**
38
+ * The `meta.docs` an author writes: everything ESLint accepts except `url`,
39
+ * which the factory derives from the rule name. Typed `never` so supplying a
40
+ * URL by hand is a compile error rather than a silently-overwritten value.
41
+ *
42
+ * @internal
43
+ */
44
+ export type BuiltInRuleDocs = Omit<RulesMetaDocs, 'url'> & {
45
+ readonly url?: never;
46
+ };
47
+ /**
48
+ * The `meta` a built-in rule writes: {@link ShellRuleMeta} (so the language
49
+ * fields are already forbidden), with `docs` narrowed to
50
+ * {@link BuiltInRuleDocs} because the factory derives `docs.url`.
51
+ *
52
+ * @internal
53
+ */
54
+ export type BuiltInRuleMeta<RuleOptions extends unknown[], MessageIds extends string> = Omit<ShellRuleMeta<RuleOptions, MessageIds>, 'docs'> & {
55
+ readonly docs?: BuiltInRuleDocs;
56
+ };
57
+ /**
58
+ * What an author hands to {@link createRule}: the rule's registry name (used
59
+ * to derive its docs URL), its author-owned metadata, and its `create()`.
60
+ *
61
+ * @internal
62
+ */
63
+ export interface BuiltInRuleSpec<RuleOptions extends unknown[], MessageIds extends string> {
64
+ /**
65
+ * The rule's name without the plugin namespace (`require-shebang`, not
66
+ * `sh/require-shebang`). For a built-in rule it must equal the rule's key in
67
+ * the registry and the basename of its docs page.
68
+ */
69
+ readonly name: string;
70
+ /**
71
+ * The docs URL of a rule that is not in the registry. A rule made by one of
72
+ * this package's public factories (for example `createWrapperRule`) has no
73
+ * `docs/rules/<name>.md` page: it links its factory's page
74
+ * ({@link packageDocsUrl}), or the page its rule pack supplies. Omit it for
75
+ * a built-in rule, whose page is always `docs/rules/<name>.md`.
76
+ */
77
+ readonly docsUrl?: string;
78
+ readonly meta: BuiltInRuleMeta<RuleOptions, MessageIds>;
79
+ /** Builds the rule's listeners for one file; receives the shell-typed context. */
80
+ readonly create: (context: ShellRuleContext<RuleOptions, MessageIds>) => ShellRuleVisitor;
81
+ }
82
+ /**
83
+ * Builds a built-in shell rule from its spec with {@link defineShellRule},
84
+ * after setting `meta.docs.url` to the rule's docs page ({@link ruleDocsUrl},
85
+ * or the spec's `docsUrl` for a rule outside the registry). The URL is
86
+ * written after the author's `docs`, so even an untyped caller cannot
87
+ * override it; `defineShellRule` does the same for `meta.languages`.
88
+ *
89
+ * @internal
90
+ */
91
+ export declare function createRule<RuleOptions extends unknown[] = [], MessageIds extends string = string>(spec: BuiltInRuleSpec<RuleOptions, MessageIds>): ShellRule<RuleOptions, MessageIds>;
@@ -0,0 +1,51 @@
1
+ import { defineShellRule } from './define-shell-rule.js';
2
+ /**
3
+ * Where this package's shipped docs live in the source repository: the
4
+ * committed `packages/eslint-plugin/docs/` directory.
5
+ */
6
+ const PACKAGE_DOCS_BASE_URL = 'https://github.com/mike-north/eslint-sh/blob/main/packages/eslint-plugin/docs/';
7
+ /**
8
+ * Where built-in rule docs pages live in the source repository. A rule named
9
+ * `foo` is documented at `<this>/foo.md`, i.e. the committed file
10
+ * `packages/eslint-plugin/docs/rules/foo.md`.
11
+ */
12
+ const RULE_DOCS_BASE_URL = `${PACKAGE_DOCS_BASE_URL}rules/`;
13
+ /**
14
+ * The canonical documentation URL for the built-in rule `name`. This is the
15
+ * single place the URL scheme is spelled; the factory uses it to fill
16
+ * `meta.docs.url`, and the docs-page tests use it to check each registered
17
+ * rule points at its own committed page.
18
+ *
19
+ * @internal
20
+ */
21
+ export function ruleDocsUrl(name) {
22
+ return `${RULE_DOCS_BASE_URL}${name}.md`;
23
+ }
24
+ /**
25
+ * The documentation URL for `page`, a path relative to this package's `docs/`
26
+ * directory (`wrapper-rules.md` is the committed file
27
+ * `packages/eslint-plugin/docs/wrapper-rules.md`). Used for rules that are not
28
+ * in the registry, which have no `docs/rules/<name>.md` page of their own.
29
+ *
30
+ * @internal
31
+ */
32
+ export function packageDocsUrl(page) {
33
+ return `${PACKAGE_DOCS_BASE_URL}${page}`;
34
+ }
35
+ /**
36
+ * Builds a built-in shell rule from its spec with {@link defineShellRule},
37
+ * after setting `meta.docs.url` to the rule's docs page ({@link ruleDocsUrl},
38
+ * or the spec's `docsUrl` for a rule outside the registry). The URL is
39
+ * written after the author's `docs`, so even an untyped caller cannot
40
+ * override it; `defineShellRule` does the same for `meta.languages`.
41
+ *
42
+ * @internal
43
+ */
44
+ export function createRule(spec) {
45
+ const { name, docsUrl, meta, create } = spec;
46
+ const url = docsUrl ?? ruleDocsUrl(name);
47
+ return defineShellRule({
48
+ meta: { ...meta, docs: { ...meta.docs, url } },
49
+ create,
50
+ });
51
+ }
@@ -0,0 +1,166 @@
1
+ import type { WrapperSpec } from 'sh-ast/analyze';
2
+ import type { ShellRule } from './define-shell-rule.js';
3
+ /**
4
+ * Restricts a listed command to some of its subcommands, for commands such as
5
+ * `git` where only some subcommands are in scope (`git fetch` is a network
6
+ * call, `git status` is not).
7
+ *
8
+ * The flag fields are `WrapperSpec`'s own, picked from it so the two cannot
9
+ * drift, and describe the command's options that may appear before the
10
+ * subcommand (`git -C dir fetch`): `noArgFlags` take no operand (`--bare`),
11
+ * `argFlags` take one (`-C <dir>`), and `stopsChainFlags` mean the command
12
+ * runs no subcommand at all (`git --version fetch` prints the version, and is
13
+ * never reported). The subcommand is found the way a wrapped command is:
14
+ * declared options are skipped, and a flag the command does not declare
15
+ * leaves the subcommand unknown, so the invocation is not reported.
16
+ *
17
+ * @public
18
+ */
19
+ export interface WrapperRuleSubcommands extends Pick<WrapperSpec, 'noArgFlags' | 'argFlags' | 'stopsChainFlags'> {
20
+ /** The subcommands that make an invocation of the command count, such as `['fetch', 'push']`. */
21
+ readonly names: readonly string[];
22
+ }
23
+ /**
24
+ * Names the operand of a listed command whose value decides whether the
25
+ * invocation can leave the machine, such as the source of `git clone`.
26
+ *
27
+ * The flags describe the options that may precede the operand, with the same
28
+ * meaning as {@link WrapperRuleSubcommands}'s: `noArgFlags` take no value and
29
+ * `argFlags` take one (as `--depth 1`, or `--depth=1` in one word). The
30
+ * operand is the first word after them, or after a `--`. A flag that is not
31
+ * declared leaves the operand unknown, and an unknown operand is never taken
32
+ * as local.
33
+ *
34
+ * @public
35
+ */
36
+ export interface WrapperRuleLocalOperand extends Pick<WrapperSpec, 'noArgFlags' | 'argFlags'> {
37
+ /**
38
+ * The operand counts as local when it is a static word starting with one
39
+ * of these, such as `['./', '../', '/', 'file://']`.
40
+ */
41
+ readonly localPrefixes: readonly string[];
42
+ }
43
+ /**
44
+ * Ways to invoke one listed command that only act locally, so the policy has
45
+ * nothing to guard. An invocation matching either field is not reported.
46
+ *
47
+ * @public
48
+ */
49
+ export interface WrapperRuleLocalInvocation {
50
+ /**
51
+ * Complete argument lists, after the command (and its subcommand, if it has
52
+ * one), that make the invocation local. They match exactly: with
53
+ * `[['--version']]`, `curl --version` is local and
54
+ * `curl --version https://example.com` is not.
55
+ */
56
+ readonly argLists?: readonly (readonly string[])[];
57
+ /** Local when the command's source operand is a local path. */
58
+ readonly localOperand?: WrapperRuleLocalOperand;
59
+ }
60
+ /**
61
+ * What a "must be run through a wrapper" rule enforces. Passed to
62
+ * {@link createWrapperRule}.
63
+ *
64
+ * @public
65
+ */
66
+ export interface WrapperRuleConfig {
67
+ /**
68
+ * The command names that must be run through the wrapper. A static command
69
+ * word matches a name when it equals it, or when it contains a slash and
70
+ * its basename equals it (`/usr/bin/curl` and `./bin/curl` match `curl`).
71
+ */
72
+ readonly commands: readonly string[];
73
+ /**
74
+ * The wrapper the commands must be run through. A string names a wrapper
75
+ * that takes no options of its own (`retry curl x`), unless it names one of
76
+ * `sh-ast/analyze`'s default transparent wrappers (`timeout`, `nice`, …),
77
+ * whose known option shape is used. A `WrapperSpec` declares the wrapper's
78
+ * option shape, so that `retry -n 3 curl x` is recognized as `curl` run
79
+ * through `retry`; any of its `names` counts as the wrapper. Like
80
+ * `commands`, a wrapper run by path matches by basename
81
+ * (`/usr/local/bin/retry curl x`).
82
+ */
83
+ readonly wrapper: string | WrapperSpec;
84
+ /**
85
+ * Listed commands that count only with some subcommands, keyed by command
86
+ * name. A listed command with no entry here counts with any arguments.
87
+ */
88
+ readonly subcommands?: Readonly<Record<string, WrapperRuleSubcommands>>;
89
+ /**
90
+ * Invocations of a listed command that only act locally and so are not
91
+ * reported, keyed by the command as the rule's message names it: the listed
92
+ * name (`curl`), or the name with its subcommand (`git clone`).
93
+ */
94
+ readonly localInvocations?: Readonly<Record<string, WrapperRuleLocalInvocation>>;
95
+ /**
96
+ * The rule's `meta.docs` fields a rule pack owns. `url` defaults to this
97
+ * package's wrapper-rules docs page, and `description` to a sentence naming
98
+ * the commands and the wrapper. Set them to point users at the pack's own
99
+ * documentation.
100
+ */
101
+ readonly docs?: {
102
+ readonly url?: string;
103
+ readonly description?: string;
104
+ };
105
+ }
106
+ /**
107
+ * The message ids a wrapper rule reports with: `unwrappedCommand` names the
108
+ * command (with its subcommand, when one was matched) and the wrapper.
109
+ *
110
+ * @internal
111
+ */
112
+ export type WrapperRuleMessageId = 'unwrappedCommand';
113
+ /**
114
+ * The messages every wrapper rule reports with. Shared so that a rule which
115
+ * delegates to the factory (`sh/require-network-wrapper`) declares exactly the
116
+ * messages the factory's listeners report.
117
+ *
118
+ * @internal
119
+ */
120
+ export declare const WRAPPER_RULE_MESSAGES: Readonly<Record<WrapperRuleMessageId, string>>;
121
+ /**
122
+ * The wrapper-rule factory, typed for this package's rules: it is what
123
+ * {@link createWrapperRule} returns, with the shell-specific rule type and an
124
+ * options tuple chosen by the caller. A built-in rule whose config comes from
125
+ * its own options (`sh/require-network-wrapper`) builds the factory's rule
126
+ * per config and calls its `create` with its own context, so the options
127
+ * tuple is that rule's. The factory's rule itself reads no options.
128
+ *
129
+ * @internal
130
+ */
131
+ export declare function buildWrapperRule<RuleOptions extends unknown[] = []>(config: WrapperRuleConfig): ShellRule<RuleOptions, WrapperRuleMessageId>;
132
+ /**
133
+ * Creates a rule that reports every listed command that is not run through
134
+ * `config.wrapper`, for a rule pack's "must be wrapped by X" policies:
135
+ *
136
+ * ```js
137
+ * import { createWrapperRule } from '@eslint-sh/eslint-plugin';
138
+ *
139
+ * export const requireAudit = createWrapperRule({
140
+ * commands: ['kubectl', 'terraform'],
141
+ * wrapper: 'audit-run',
142
+ * });
143
+ * ```
144
+ *
145
+ * The rule declares `meta.languages: ["sh/sh"]`, takes no options (the
146
+ * config is bound here), and reports with message id `unwrappedCommand`,
147
+ * naming the command and the wrapper. Its `meta.docs.url` and
148
+ * `meta.docs.description` default to this package's wrapper-rules page and a
149
+ * sentence naming the policy; `config.docs` overrides either. A listed
150
+ * command is wrapped when the wrapper encloses it in `sh-ast/analyze`'s
151
+ * transparent-wrapper chain, so `audit-run sudo kubectl apply` is wrapped.
152
+ * Command and wrapper words written as paths match by basename
153
+ * (`/usr/bin/kubectl`). Commands inside `$(…)`, functions, and pipelines are
154
+ * checked; a command whose name or subcommand is not static is not reported.
155
+ *
156
+ * @param config - The commands, the wrapper they must be run through, any
157
+ * subcommand restrictions, and optional `meta.docs` overrides.
158
+ * @returns A {@link ShellRule}: an ESLint rule for the `sh/sh` language,
159
+ * typed so a pack's own declarations need no `@eslint/core` import to name it.
160
+ * @throws TypeError when `config` is malformed (no `commands` array, an empty
161
+ * wrapper name, a subcommand restriction without `names`, or an empty or
162
+ * non-string `docs.url` / `docs.description`).
163
+ *
164
+ * @public
165
+ */
166
+ export declare function createWrapperRule(config: WrapperRuleConfig): ShellRule<[], 'unwrappedCommand'>;