@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,93 @@
1
+ /**
2
+ * The commands a POSIX `sh` runs as builtins: data for the command harvest,
3
+ * never rule logic.
4
+ *
5
+ * Three groups, each from its own source (POSIX.1-2017, XCU Issue 7, whose
6
+ * section numbers are used here):
7
+ *
8
+ * - **Special built-ins**, XCU §2.14 "Special Built-In Utilities": `break`,
9
+ * `:`, `continue`, `.`, `eval`, `exec`, `exit`, `export`, `readonly`,
10
+ * `return`, `set`, `shift`, `times`, `trap`, `unset`.
11
+ * - **Regular built-ins**, XCU §2.9.1.1 "Command Search and Execution",
12
+ * step 1.d: the utilities a shell must invoke before searching `PATH`
13
+ * (`alias`, `bg`, `cd`, `command`, `false`, `fc`, `fg`, `getopts`, `hash`,
14
+ * `jobs`, `kill`, `newgrp`, `pwd`, `read`, `true`, `umask`, `unalias`,
15
+ * `wait`), plus `type` and `ulimit`, which the same step lists under the
16
+ * XSI option. POSIX.1-2024 (Issue 8) calls most of these "intrinsic
17
+ * utilities" (XCU §1.7).
18
+ * - **Built in by every POSIX shell in practice**: `echo`, `printf`, `test`
19
+ * and `[`. POSIX specifies them as ordinary utilities that a shell may
20
+ * build in (XCU §1.6), and dash, ash, ksh, mksh and bash all do (see the
21
+ * "Builtins" section of dash(1)). Counting them as external would make
22
+ * nearly every script declare `echo`.
23
+ *
24
+ * Bash-only builtins such as `local`, `source` and `mapfile` are
25
+ * deliberately absent: in a `posix` file they are commands that a POSIX
26
+ * shell need not provide.
27
+ *
28
+ * @see https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_14 — XCU §2.14 Special Built-In Utilities
29
+ * @see https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_09_01_01 — XCU §2.9.1.1 Command Search and Execution
30
+ * @see https://pubs.opengroup.org/onlinepubs/9799919799/utilities/V3_chap01.html — POSIX.1-2024 XCU §1.7 Intrinsic Utilities
31
+ * @see https://man7.org/linux/man-pages/man1/dash.1.html — dash(1), "Builtins"
32
+ */
33
+ /**
34
+ * POSIX special built-in utilities (XCU §2.14).
35
+ *
36
+ * @internal
37
+ */
38
+ export const POSIX_SPECIAL_BUILTINS = Object.freeze([
39
+ 'break',
40
+ ':',
41
+ 'continue',
42
+ '.',
43
+ 'eval',
44
+ 'exec',
45
+ 'exit',
46
+ 'export',
47
+ 'readonly',
48
+ 'return',
49
+ 'set',
50
+ 'shift',
51
+ 'times',
52
+ 'trap',
53
+ 'unset',
54
+ ]);
55
+ /**
56
+ * POSIX regular built-in utilities (XCU §2.9.1.1 step 1.d, including the
57
+ * XSI `type` and `ulimit`).
58
+ *
59
+ * @internal
60
+ */
61
+ export const POSIX_REGULAR_BUILTINS = Object.freeze([
62
+ 'alias',
63
+ 'bg',
64
+ 'cd',
65
+ 'command',
66
+ 'false',
67
+ 'fc',
68
+ 'fg',
69
+ 'getopts',
70
+ 'hash',
71
+ 'jobs',
72
+ 'kill',
73
+ 'newgrp',
74
+ 'pwd',
75
+ 'read',
76
+ 'true',
77
+ 'type',
78
+ 'ulimit',
79
+ 'umask',
80
+ 'unalias',
81
+ 'wait',
82
+ ]);
83
+ /**
84
+ * Utilities POSIX lets a shell build in, and every POSIX shell does.
85
+ *
86
+ * @internal
87
+ */
88
+ export const POSIX_CONVENTIONAL_BUILTINS = Object.freeze([
89
+ 'echo',
90
+ 'printf',
91
+ 'test',
92
+ '[',
93
+ ]);
@@ -0,0 +1,66 @@
1
+ import type { WrapperSpec } from 'sh-ast/analyze';
2
+ import type { ShellCommandSite } from '../language/command-resolution.js';
3
+ import type { ShellSourceCode } from '../language/shell-source-code.js';
4
+ import type { ShellNode } from '../language/syntax-view.js';
5
+ /**
6
+ * Options for {@link harvestCommands}.
7
+ *
8
+ * @internal
9
+ */
10
+ export interface HarvestCommandsOptions {
11
+ /**
12
+ * The transparent-wrapper table to follow, forwarded to
13
+ * `resolveInvokedCommand` unchanged. It **replaces** `sh-ast/analyze`'s
14
+ * `DEFAULT_TRANSPARENT_WRAPPERS` (spread the defaults in to extend them).
15
+ * Defaults to `DEFAULT_TRANSPARENT_WRAPPERS`.
16
+ */
17
+ readonly transparentWrappers?: readonly WrapperSpec[];
18
+ }
19
+ /**
20
+ * The first use of one external command in a file.
21
+ *
22
+ * @internal
23
+ */
24
+ export interface HarvestedCommand {
25
+ /** The command name, exactly as the shell would look it up (`jq`, `./build.sh`). */
26
+ readonly name: string;
27
+ /**
28
+ * The `Word` node that names the command at its first use: the command
29
+ * word itself, or for a wrapped command the word after the wrapper
30
+ * (`curl` in `sudo curl x`). Falls back to the whole `CallExpr` in the
31
+ * unexpected case that the word cannot be located.
32
+ */
33
+ readonly node: ShellNode;
34
+ /** The command site of that first use. */
35
+ readonly site: ShellCommandSite;
36
+ }
37
+ /**
38
+ * The names of every function the file defines, anywhere in it (top level,
39
+ * nested in another function, inside a conditional, …). Position is not read,
40
+ * so this answers "could this name be a function", not "is it one at this
41
+ * call": a call in a function body usually runs after a later definition, but a
42
+ * top-level call before the definition does not run it. zsh's
43
+ * `function a b { … }` defines every listed name.
44
+ *
45
+ * @internal
46
+ */
47
+ export declare function definedFunctionNames(sourceCode: ShellSourceCode): ReadonlySet<string>;
48
+ /**
49
+ * Returns every external command `sourceCode` runs, keyed by name, each with
50
+ * its first use. The map iterates in first-use (source) order.
51
+ *
52
+ * A command is every static word in an invocation's wrapper chain (the
53
+ * wrappers and the effective command), excluding:
54
+ *
55
+ * - dynamic words, which name no knowable command;
56
+ * - builtins of the file's dialect (`sourceCode.dialect`);
57
+ * - functions the file defines anywhere.
58
+ *
59
+ * @throws `ShAnalyzeMaxDepthError` (from `sh-ast/analyze`) when the file's
60
+ * nesting exceeds analysis's recursion guard: a partial harvest would miss
61
+ * real commands.
62
+ * @throws `ShAnalyzeInvalidWrapperSpecError` (from `sh-ast/analyze`) when
63
+ * `options.transparentWrappers` contains a malformed entry.
64
+ * @internal
65
+ */
66
+ export declare function harvestCommands(sourceCode: ShellSourceCode, options?: HarvestCommandsOptions): ReadonlyMap<string, HarvestedCommand>;
@@ -0,0 +1,97 @@
1
+ /**
2
+ * The command harvest: which external commands a shell file depends on.
3
+ *
4
+ * "Usage" here is the definition the declaration-convention rules compare
5
+ * declarations against: every command name the file statically runs, minus
6
+ * the shell's builtins and minus functions the file defines itself, following
7
+ * transparent wrappers so that `sudo curl …` uses both `sudo` and `curl`.
8
+ *
9
+ * Every fact comes from `sh-ast/analyze` through the `ShellSourceCode`
10
+ * helpers. `getCommandSites` supplies every command in the file, wherever it
11
+ * is nested (function bodies, `$(…)` in arguments, assignment values,
12
+ * redirect targets, loop lists, case subjects, …), and each site's
13
+ * `resolveInvokedCommand` supplies the wrapper chain. Whether a word is
14
+ * static is the analysis answer (`WordResolution.static`), never decided
15
+ * here.
16
+ *
17
+ * Out of scope, by design:
18
+ *
19
+ * - Dynamic command words (`"$cmd" x`, `sudo "$prog"`, `$(pick) x`). They
20
+ * name no knowable command; `no-dynamic-invocation` reports them. Static
21
+ * wrappers in front of a dynamic word (`sudo` in `sudo "$prog"`) are still
22
+ * used.
23
+ * - Commands inside strings that another program runs (`bash -c '…'`,
24
+ * `env -S '…'`). Those are text, not syntax.
25
+ *
26
+ * This module is internal. It is shaped to move, unchanged, into a public
27
+ * analysis-services package later; until then rules in this package use it.
28
+ */
29
+ import { DEFAULT_TRANSPARENT_WRAPPERS } from 'sh-ast/analyze';
30
+ import { walkShell } from '../language/syntax-view.js';
31
+ import { builtinsForDialect } from './builtins/index.js';
32
+ /**
33
+ * The names of every function the file defines, anywhere in it (top level,
34
+ * nested in another function, inside a conditional, …). Position is not read,
35
+ * so this answers "could this name be a function", not "is it one at this
36
+ * call": a call in a function body usually runs after a later definition, but a
37
+ * top-level call before the definition does not run it. zsh's
38
+ * `function a b { … }` defines every listed name.
39
+ *
40
+ * @internal
41
+ */
42
+ export function definedFunctionNames(sourceCode) {
43
+ const names = new Set();
44
+ walkShell(sourceCode.ast, (node) => {
45
+ if (node.type !== 'FuncDecl')
46
+ return;
47
+ if (node.name)
48
+ names.add(node.name.value);
49
+ for (const lit of node.names)
50
+ names.add(lit.value);
51
+ });
52
+ return names;
53
+ }
54
+ /**
55
+ * The `Word` node in `site`'s command at argv position `index`. argv and the
56
+ * `CallExpr`'s `args` list the same words in the same order, so a position
57
+ * from `chainIndices` selects the syntax word directly.
58
+ */
59
+ function wordNodeAt(site, index) {
60
+ return site.node.args[index] ?? site.node;
61
+ }
62
+ /**
63
+ * Returns every external command `sourceCode` runs, keyed by name, each with
64
+ * its first use. The map iterates in first-use (source) order.
65
+ *
66
+ * A command is every static word in an invocation's wrapper chain (the
67
+ * wrappers and the effective command), excluding:
68
+ *
69
+ * - dynamic words, which name no knowable command;
70
+ * - builtins of the file's dialect (`sourceCode.dialect`);
71
+ * - functions the file defines anywhere.
72
+ *
73
+ * @throws `ShAnalyzeMaxDepthError` (from `sh-ast/analyze`) when the file's
74
+ * nesting exceeds analysis's recursion guard: a partial harvest would miss
75
+ * real commands.
76
+ * @throws `ShAnalyzeInvalidWrapperSpecError` (from `sh-ast/analyze`) when
77
+ * `options.transparentWrappers` contains a malformed entry.
78
+ * @internal
79
+ */
80
+ export function harvestCommands(sourceCode, options = {}) {
81
+ const transparentWrappers = options.transparentWrappers ?? DEFAULT_TRANSPARENT_WRAPPERS;
82
+ const builtins = builtinsForDialect(sourceCode.dialect);
83
+ const functions = definedFunctionNames(sourceCode);
84
+ const harvested = new Map();
85
+ for (const site of sourceCode.getCommandSites()) {
86
+ const { chain, chainIndices } = site.resolveInvokedCommand({ transparentWrappers });
87
+ for (const [position, word] of chain.entries()) {
88
+ if (!word.static)
89
+ continue;
90
+ const name = word.text;
91
+ if (harvested.has(name) || builtins.has(name) || functions.has(name))
92
+ continue;
93
+ harvested.set(name, { name, node: wordNodeAt(site, chainIndices[position]), site });
94
+ }
95
+ }
96
+ return harvested;
97
+ }
@@ -0,0 +1,24 @@
1
+ import type { WrapperSpec } from 'sh-ast/analyze';
2
+ import type { ShellDialect } from '../language/shell-language-options.js';
3
+ /**
4
+ * Returns the transparent-wrapper table for harvesting a `dialect` file
5
+ * with the given configured wrapper names, in this order:
6
+ *
7
+ * 1. `sh-ast/analyze`'s `DEFAULT_TRANSPARENT_WRAPPERS` (`sudo`, `env`, …);
8
+ * 2. the dialect's own wrappers (`run` under `bats`);
9
+ * 3. the project's network wrapper (`settings.sh.networkWrapper`), when set,
10
+ * with the options its spec declares: a wrapper that network commands must
11
+ * run through runs the command after it;
12
+ * 4. one name-only entry per configured name. A name-only entry recognizes
13
+ * no flags, so `with-retry -n 3 curl` stops at `-n` rather than guessing
14
+ * past it.
15
+ *
16
+ * The first entry whose names match is the one used, so a network wrapper
17
+ * spec with options wins over a name-only entry for the same name.
18
+ *
19
+ * @param dialect - The linted file's `sourceCode.dialect`.
20
+ * @param wrapperNames - The rule's `transparentWrappers` option names.
21
+ * @param networkWrapper - The spec from `settings.sh.networkWrapper`, or `null`.
22
+ * @internal
23
+ */
24
+ export declare function harvestWrapperTable(dialect: ShellDialect, wrapperNames: readonly string[], networkWrapper?: WrapperSpec | null): readonly WrapperSpec[];
@@ -0,0 +1,49 @@
1
+ /**
2
+ * The one wrapper table that rules comparing declarations against usage
3
+ * pass to `harvestCommands` (`./harvest-commands.ts`).
4
+ *
5
+ * `no-undeclared-commands` and `no-unused-declarations` must agree exactly on
6
+ * which commands a file uses, or one would report a command the other calls
7
+ * unused. Usage depends on the wrapper table, so both build it here from the
8
+ * same inputs: the file's dialect, the network wrapper from
9
+ * `settings.sh.networkWrapper`, and the rule's `transparentWrappers` option
10
+ * names.
11
+ */
12
+ import { DEFAULT_TRANSPARENT_WRAPPERS } from 'sh-ast/analyze';
13
+ import { BATS_RUN_WRAPPER } from './builtins/bats.js';
14
+ /**
15
+ * Wrappers a dialect's runtime provides beyond sh-ast's defaults: bats' `run`
16
+ * for `bats` files.
17
+ */
18
+ const DIALECT_WRAPPERS = {
19
+ bats: [BATS_RUN_WRAPPER],
20
+ };
21
+ /**
22
+ * Returns the transparent-wrapper table for harvesting a `dialect` file
23
+ * with the given configured wrapper names, in this order:
24
+ *
25
+ * 1. `sh-ast/analyze`'s `DEFAULT_TRANSPARENT_WRAPPERS` (`sudo`, `env`, …);
26
+ * 2. the dialect's own wrappers (`run` under `bats`);
27
+ * 3. the project's network wrapper (`settings.sh.networkWrapper`), when set,
28
+ * with the options its spec declares: a wrapper that network commands must
29
+ * run through runs the command after it;
30
+ * 4. one name-only entry per configured name. A name-only entry recognizes
31
+ * no flags, so `with-retry -n 3 curl` stops at `-n` rather than guessing
32
+ * past it.
33
+ *
34
+ * The first entry whose names match is the one used, so a network wrapper
35
+ * spec with options wins over a name-only entry for the same name.
36
+ *
37
+ * @param dialect - The linted file's `sourceCode.dialect`.
38
+ * @param wrapperNames - The rule's `transparentWrappers` option names.
39
+ * @param networkWrapper - The spec from `settings.sh.networkWrapper`, or `null`.
40
+ * @internal
41
+ */
42
+ export function harvestWrapperTable(dialect, wrapperNames, networkWrapper = null) {
43
+ return [
44
+ ...DEFAULT_TRANSPARENT_WRAPPERS,
45
+ ...(DIALECT_WRAPPERS[dialect] ?? []),
46
+ ...(networkWrapper === null ? [] : [networkWrapper]),
47
+ ...wrapperNames.map((name) => ({ names: [name] })),
48
+ ];
49
+ }
@@ -0,0 +1,41 @@
1
+ /**
2
+ * The `sh/agent-authored` shared config: the strict preset for scripts that
3
+ * are machine-written and human-reviewed.
4
+ *
5
+ * Such a script is only as trustworthy as a reviewer's ability to vouch for
6
+ * it mechanically, so this preset adds the contract rules on top of
7
+ * `sh/recommended`: every command a script runs must be statically
8
+ * enumerable, declared, and (for network access) routed through the
9
+ * project's wrapper, and no script may stop to prompt a terminal that may not
10
+ * exist (`require-tty-guard`). Each contract rule reports at `error`, and so
11
+ * do `no-secret-flags` and `quote-expansions`, which `recommended` keeps at
12
+ * `warn`.
13
+ *
14
+ * It follows the same shape contract as `sh/recommended` (see
15
+ * `./recommended.ts`): one files-less config object that registers this
16
+ * plugin under `sh` and selects `language: 'sh/sh'`, so the two presets are
17
+ * interchangeable inside a user's `extends`.
18
+ *
19
+ * `max-script-complexity` keeps a machine-written script small enough for a
20
+ * reviewer to read in one sitting.
21
+ *
22
+ * Three members are inert until configured: `no-undeclared-commands` and
23
+ * `no-unused-declarations` need a declaration convention bound in
24
+ * `settings.sh.declarationConvention`, and `require-network-wrapper` needs a
25
+ * wrapper, set once in `settings.sh.networkWrapper` (which the declaration
26
+ * rules also read) or as the rule's `wrapper` option. They are enabled here so that binding the convention or
27
+ * wrapper is the only step a project takes; shipping a default convention or
28
+ * wrapper would impose one project's policy on every project.
29
+ *
30
+ * Rule membership follows the **Configs** column of the rule catalog
31
+ * (rules marked `A`), which a test checks against this
32
+ * module.
33
+ */
34
+ import type { ConfigObject, Plugin } from '@eslint/core';
35
+ /**
36
+ * Builds the `sh/agent-authored` config bound to `plugin`, which must be this
37
+ * package's default export (the preset registers it under `plugins.sh`).
38
+ *
39
+ * @internal
40
+ */
41
+ export declare function createAgentAuthoredConfig(plugin: Plugin): ConfigObject;
@@ -0,0 +1,33 @@
1
+ import { recommendedRules } from './recommended.js';
2
+ /**
3
+ * The rules `sh/agent-authored` enables: every `sh/recommended` rule, then
4
+ * the contract rules, all at `error`.
5
+ */
6
+ const agentAuthoredRules = {
7
+ ...recommendedRules,
8
+ 'sh/no-secret-flags': 'error',
9
+ 'sh/quote-expansions': 'error',
10
+ 'sh/no-dynamic-invocation': 'error',
11
+ 'sh/no-masked-exit-status': 'error',
12
+ 'sh/max-script-complexity': 'error',
13
+ 'sh/no-secret-env-reads': 'error',
14
+ 'sh/no-undeclared-commands': 'error',
15
+ 'sh/no-unused-declarations': 'error',
16
+ 'sh/prefer-local': 'error',
17
+ 'sh/require-network-wrapper': 'error',
18
+ 'sh/require-tty-guard': 'error',
19
+ };
20
+ /**
21
+ * Builds the `sh/agent-authored` config bound to `plugin`, which must be this
22
+ * package's default export (the preset registers it under `plugins.sh`).
23
+ *
24
+ * @internal
25
+ */
26
+ export function createAgentAuthoredConfig(plugin) {
27
+ return {
28
+ name: 'sh/agent-authored',
29
+ plugins: { sh: plugin },
30
+ language: 'sh/sh',
31
+ rules: { ...agentAuthoredRules },
32
+ };
33
+ }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * The `sh/bash-3.2` shared config: the preset for scripts that must run on the
3
+ * Bash 3.2 that macOS ships as `/bin/bash`.
4
+ *
5
+ * The parser accepts every construct its grammar knows whatever target is
6
+ * configured, so a Bash 3.2 target is enforced only by the `compat/*` rules.
7
+ * This preset names that target once (`languageOptions.shellVersion: '3.2'`)
8
+ * and turns on every version-gated rule that reads it, so a project states its
9
+ * deployment target as a config choice instead of enumerating rules.
10
+ *
11
+ * It follows the shape contract in `./recommended.ts`, with one deliberate
12
+ * extension: it carries `languageOptions`, because the target version is the
13
+ * preset's whole meaning and a pack that left it to the user would enable
14
+ * rules that report nothing. It still carries no `files` or `ignores`, so it
15
+ * inherits the user's globs, and it composes with the other presets through
16
+ * `extends: [sh.configs.recommended, sh.configs['bash-3.2']]`.
17
+ *
18
+ * Rule membership follows the **Configs** column of the rule catalog (rows
19
+ * marked `3.2`), which a test checks against this module.
20
+ */
21
+ import type { ConfigObject, Plugin } from '@eslint/core';
22
+ /**
23
+ * Builds the `sh/bash-3.2` config bound to `plugin`, which must be this
24
+ * package's default export (the preset registers it under `plugins.sh`).
25
+ *
26
+ * @internal
27
+ */
28
+ export declare function createBash32Config(plugin: Plugin): ConfigObject;
@@ -0,0 +1,50 @@
1
+ /**
2
+ * The rules `sh/bash-3.2` enables: every version-gated `compat/*` rule, all at
3
+ * `error`, keyed as a user writes them (`sh/<rule-name>`). Each reports a
4
+ * feature that Bash 3.2 lacks and names the portable alternative.
5
+ * `shebang-matches-dialect` is not a version gate and belongs to the
6
+ * presets whose catalog row names it. The POSIX-only gates
7
+ * (`compat/no-double-bracket`, `compat/no-let`, `compat/no-source`) are not
8
+ * version gates either: every Bash has those features, so they could never
9
+ * report here.
10
+ *
11
+ * @internal
12
+ */
13
+ const bash32Rules = {
14
+ 'sh/compat/no-append-all-redirect': 'error',
15
+ 'sh/compat/no-associative-arrays': 'error',
16
+ 'sh/compat/no-case-attributes': 'error',
17
+ 'sh/compat/no-case-fallthrough': 'error',
18
+ 'sh/compat/no-case-modification': 'error',
19
+ 'sh/compat/no-coproc': 'error',
20
+ 'sh/compat/no-declare-g': 'error',
21
+ 'sh/compat/no-epoch-variables': 'error',
22
+ 'sh/compat/no-fd-variable': 'error',
23
+ 'sh/compat/no-globstar': 'error',
24
+ 'sh/compat/no-lastpipe-semantics': 'error',
25
+ 'sh/compat/no-mapfile': 'error',
26
+ 'sh/compat/no-nameref': 'error',
27
+ 'sh/compat/no-negative-array-index': 'error',
28
+ 'sh/compat/no-nounset-empty-array': 'error',
29
+ 'sh/compat/no-parameter-transformation': 'error',
30
+ 'sh/compat/no-pipe-all': 'error',
31
+ 'sh/compat/no-printf-time-format': 'error',
32
+ 'sh/compat/no-test-v': 'error',
33
+ 'sh/compat/no-unicode-escape': 'error',
34
+ 'sh/compat/no-wait-n': 'error',
35
+ };
36
+ /**
37
+ * Builds the `sh/bash-3.2` config bound to `plugin`, which must be this
38
+ * package's default export (the preset registers it under `plugins.sh`).
39
+ *
40
+ * @internal
41
+ */
42
+ export function createBash32Config(plugin) {
43
+ return {
44
+ name: 'sh/bash-3.2',
45
+ plugins: { sh: plugin },
46
+ language: 'sh/sh',
47
+ languageOptions: { dialect: 'bash', shellVersion: '3.2' },
48
+ rules: { ...bash32Rules },
49
+ };
50
+ }
@@ -0,0 +1,40 @@
1
+ /**
2
+ * The `sh/posix-portable` shared config: the preset for scripts meant for a
3
+ * POSIX `sh` rather than for Bash.
4
+ *
5
+ * It sets `languageOptions.dialect: 'posix'`, which makes every Bash-only
6
+ * feature unavailable at any `shellVersion`, and enables the `compat/*` rules
7
+ * that report those features. The dialect is also what every dialect-aware
8
+ * rule reads, so the preset is one switch rather than a list of settings that
9
+ * could drift apart.
10
+ *
11
+ * The preset is not a proof of portability. Its rules report named constructs
12
+ * that really fail on dash, and the parser rejects some others; it deliberately
13
+ * leaves `local`, `echo -e` and the rest of shellcheck's SC3xxx catalog to
14
+ * shellcheck, which is why it does not promise "any POSIX `sh`". The scope and
15
+ * the shell behaviour behind it are stated for users in `docs/compat.md`.
16
+ *
17
+ * `require-strict-mode` is configured, not merely enabled. Its default under
18
+ * `posix` is already `set -eu`, but the preset states `mode: 'set -eu'`
19
+ * explicitly so that neither a later edit to that default nor an inherited
20
+ * `recommended` severity-only entry can make the pack demand `pipefail`,
21
+ * which POSIX gained only in POSIX.1-2024 and which dash and older BusyBox
22
+ * ash reject. A pack that asked for `pipefail` would make its own autofix
23
+ * break a working `#!/bin/sh` script.
24
+ *
25
+ * It follows the shape contract in `./recommended.ts`, extended with
26
+ * `languageOptions` for the reason given in `./bash-3-2.ts`. It composes
27
+ * through `extends: [sh.configs.recommended, sh.configs['posix-portable']]`;
28
+ * the later entry's `require-strict-mode` options win.
29
+ *
30
+ * Rule membership follows the **Configs** column of the rule catalog (rows
31
+ * marked `P`), which a test checks against this module.
32
+ */
33
+ import type { ConfigObject, Plugin } from '@eslint/core';
34
+ /**
35
+ * Builds the `sh/posix-portable` config bound to `plugin`, which must be this
36
+ * package's default export (the preset registers it under `plugins.sh`).
37
+ *
38
+ * @internal
39
+ */
40
+ export declare function createPosixPortableConfig(plugin: Plugin): ConfigObject;
@@ -0,0 +1,52 @@
1
+ /**
2
+ * The rules `sh/posix-portable` enables, all at `error`, keyed as a user
3
+ * writes them (`sh/<rule-name>`): every `compat/*` rule (each reports under
4
+ * `dialect: 'posix'` whatever the version), including the POSIX-only gates
5
+ * `bash-3.2` has no use for, the shebang/dialect check, and strict mode in its
6
+ * POSIX spelling.
7
+ *
8
+ * @internal
9
+ */
10
+ const posixPortableRules = {
11
+ 'sh/require-strict-mode': ['error', { mode: 'set -eu' }],
12
+ 'sh/shebang-matches-dialect': 'error',
13
+ 'sh/compat/no-append-all-redirect': 'error',
14
+ 'sh/compat/no-associative-arrays': 'error',
15
+ 'sh/compat/no-case-attributes': 'error',
16
+ 'sh/compat/no-case-fallthrough': 'error',
17
+ 'sh/compat/no-case-modification': 'error',
18
+ 'sh/compat/no-coproc': 'error',
19
+ 'sh/compat/no-declare-g': 'error',
20
+ 'sh/compat/no-double-bracket': 'error',
21
+ 'sh/compat/no-epoch-variables': 'error',
22
+ 'sh/compat/no-fd-variable': 'error',
23
+ 'sh/compat/no-globstar': 'error',
24
+ 'sh/compat/no-lastpipe-semantics': 'error',
25
+ 'sh/compat/no-let': 'error',
26
+ 'sh/compat/no-mapfile': 'error',
27
+ 'sh/compat/no-nameref': 'error',
28
+ 'sh/compat/no-negative-array-index': 'error',
29
+ 'sh/compat/no-nounset-empty-array': 'error',
30
+ 'sh/compat/no-parameter-transformation': 'error',
31
+ 'sh/compat/no-pipe-all': 'error',
32
+ 'sh/compat/no-printf-time-format': 'error',
33
+ 'sh/compat/no-source': 'error',
34
+ 'sh/compat/no-test-v': 'error',
35
+ 'sh/compat/no-unicode-escape': 'error',
36
+ 'sh/compat/no-wait-n': 'error',
37
+ };
38
+ /**
39
+ * Builds the `sh/posix-portable` config bound to `plugin`, which must be this
40
+ * package's default export (the preset registers it under `plugins.sh`).
41
+ *
42
+ * @internal
43
+ */
44
+ export function createPosixPortableConfig(plugin) {
45
+ return {
46
+ name: 'sh/posix-portable',
47
+ plugins: { sh: plugin },
48
+ language: 'sh/sh',
49
+ languageOptions: { dialect: 'posix' },
50
+ rules: { ...posixPortableRules },
51
+ };
52
+ }
@@ -0,0 +1,52 @@
1
+ /**
2
+ * The `sh/recommended` shared config: the preset most users extend to adopt
3
+ * eslint-sh.
4
+ *
5
+ * Its job is to take `language: 'sh/sh'` out of the user's hands. A config
6
+ * that forgets that key makes ESLint parse shell scripts as JavaScript, and
7
+ * none of this plugin's code runs to explain why. Extending this preset
8
+ * supplies the key, together with the plugin registration it resolves
9
+ * against.
10
+ *
11
+ * Shape contract (binding for every config this plugin ships):
12
+ *
13
+ * - A single config object, not an array, so it composes through
14
+ * `extends: [sh.configs.recommended, ...]` and through object spread.
15
+ * - No `files`, `ignores` or `languageOptions`. ESLint intersects an extended
16
+ * config's `files` with the extending entry's `files`, so a preset that
17
+ * carried its own globs would silently drop any path the user matched
18
+ * outside them (extensionless scripts such as `scripts/deploy` in
19
+ * particular). Without `files`, the preset inherits exactly the globs the
20
+ * user wrote. Shell paths are the user's knowledge; the preset never
21
+ * guesses them.
22
+ * - The plugin is registered under the key `sh`, because `language: 'sh/sh'`
23
+ * resolves against the config's plugin key, not `meta.namespace`. The
24
+ * registered value is the plugin's own default export, so a user who also
25
+ * writes `plugins: { sh }` registers the identical object and ESLint
26
+ * accepts it.
27
+ *
28
+ * Rule membership follows the **Configs** column of the rule catalog
29
+ * (rules marked `R`): safe defaults that assume no project
30
+ * conventions. A rule joins this preset only by appearing in that column; a
31
+ * test parses the catalog and fails when the two drift apart.
32
+ */
33
+ import type { ConfigObject, Plugin, RulesConfig } from '@eslint/core';
34
+ /**
35
+ * The rules `sh/recommended` enables, with their severities, keyed as a user
36
+ * writes them (`sh/<rule-name>`).
37
+ *
38
+ * `no-secret-flags` is a heuristic screen with documented false positives, and
39
+ * `quote-expansions` duplicates shellcheck's SC2086 for teams that also run
40
+ * shellcheck, so both report at `warn` here; every other member is `error`. `agent-authored`
41
+ * extends this record, so a rule added here is enabled there too.
42
+ *
43
+ * @internal
44
+ */
45
+ export declare const recommendedRules: Readonly<RulesConfig>;
46
+ /**
47
+ * Builds the `sh/recommended` config bound to `plugin`, which must be this
48
+ * package's default export (the preset registers it under `plugins.sh`).
49
+ *
50
+ * @internal
51
+ */
52
+ export declare function createRecommendedConfig(plugin: Plugin): ConfigObject;
@@ -0,0 +1,34 @@
1
+ /**
2
+ * The rules `sh/recommended` enables, with their severities, keyed as a user
3
+ * writes them (`sh/<rule-name>`).
4
+ *
5
+ * `no-secret-flags` is a heuristic screen with documented false positives, and
6
+ * `quote-expansions` duplicates shellcheck's SC2086 for teams that also run
7
+ * shellcheck, so both report at `warn` here; every other member is `error`. `agent-authored`
8
+ * extends this record, so a rule added here is enabled there too.
9
+ *
10
+ * @internal
11
+ */
12
+ export const recommendedRules = {
13
+ 'sh/require-strict-mode': 'error',
14
+ 'sh/require-shebang': 'error',
15
+ 'sh/no-eval': 'error',
16
+ 'sh/no-secret-flags': 'warn',
17
+ 'sh/no-unchecked-cd': 'error',
18
+ 'sh/shebang-matches-dialect': 'error',
19
+ 'sh/quote-expansions': 'warn',
20
+ };
21
+ /**
22
+ * Builds the `sh/recommended` config bound to `plugin`, which must be this
23
+ * package's default export (the preset registers it under `plugins.sh`).
24
+ *
25
+ * @internal
26
+ */
27
+ export function createRecommendedConfig(plugin) {
28
+ return {
29
+ name: 'sh/recommended',
30
+ plugins: { sh: plugin },
31
+ language: 'sh/sh',
32
+ rules: { ...recommendedRules },
33
+ };
34
+ }