@eslint-sh/eslint-plugin 0.5.0 → 0.7.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 (260) hide show
  1. package/README.md +228 -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 +51 -0
  18. package/dist/configs/posix-portable.d.ts +40 -0
  19. package/dist/configs/posix-portable.js +53 -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-brace-sequence-padding-step.d.ts +15 -0
  66. package/dist/rules/compat/no-brace-sequence-padding-step.js +287 -0
  67. package/dist/rules/compat/no-case-attributes.d.ts +12 -0
  68. package/dist/rules/compat/no-case-attributes.js +63 -0
  69. package/dist/rules/compat/no-case-fallthrough.d.ts +12 -0
  70. package/dist/rules/compat/no-case-fallthrough.js +67 -0
  71. package/dist/rules/compat/no-case-modification.d.ts +35 -0
  72. package/dist/rules/compat/no-case-modification.js +86 -0
  73. package/dist/rules/compat/no-coproc.d.ts +12 -0
  74. package/dist/rules/compat/no-coproc.js +47 -0
  75. package/dist/rules/compat/no-declare-g.d.ts +12 -0
  76. package/dist/rules/compat/no-declare-g.js +59 -0
  77. package/dist/rules/compat/no-double-bracket.d.ts +12 -0
  78. package/dist/rules/compat/no-double-bracket.js +52 -0
  79. package/dist/rules/compat/no-epoch-variables.d.ts +12 -0
  80. package/dist/rules/compat/no-epoch-variables.js +57 -0
  81. package/dist/rules/compat/no-fd-variable.d.ts +12 -0
  82. package/dist/rules/compat/no-fd-variable.js +48 -0
  83. package/dist/rules/compat/no-globstar.d.ts +12 -0
  84. package/dist/rules/compat/no-globstar.js +39 -0
  85. package/dist/rules/compat/no-lastpipe-semantics.d.ts +12 -0
  86. package/dist/rules/compat/no-lastpipe-semantics.js +53 -0
  87. package/dist/rules/compat/no-let.d.ts +12 -0
  88. package/dist/rules/compat/no-let.js +62 -0
  89. package/dist/rules/compat/no-mapfile.d.ts +13 -0
  90. package/dist/rules/compat/no-mapfile.js +122 -0
  91. package/dist/rules/compat/no-nameref.d.ts +12 -0
  92. package/dist/rules/compat/no-nameref.js +44 -0
  93. package/dist/rules/compat/no-negative-array-index.d.ts +13 -0
  94. package/dist/rules/compat/no-negative-array-index.js +123 -0
  95. package/dist/rules/compat/no-nounset-empty-array.d.ts +13 -0
  96. package/dist/rules/compat/no-nounset-empty-array.js +158 -0
  97. package/dist/rules/compat/no-parameter-transformation.d.ts +13 -0
  98. package/dist/rules/compat/no-parameter-transformation.js +123 -0
  99. package/dist/rules/compat/no-pipe-all.d.ts +13 -0
  100. package/dist/rules/compat/no-pipe-all.js +68 -0
  101. package/dist/rules/compat/no-printf-time-format.d.ts +12 -0
  102. package/dist/rules/compat/no-printf-time-format.js +99 -0
  103. package/dist/rules/compat/no-source.d.ts +13 -0
  104. package/dist/rules/compat/no-source.js +103 -0
  105. package/dist/rules/compat/no-test-v.d.ts +12 -0
  106. package/dist/rules/compat/no-test-v.js +280 -0
  107. package/dist/rules/compat/no-unicode-escape.d.ts +14 -0
  108. package/dist/rules/compat/no-unicode-escape.js +120 -0
  109. package/dist/rules/compat/no-wait-n.d.ts +12 -0
  110. package/dist/rules/compat/no-wait-n.js +73 -0
  111. package/dist/rules/compat/operator-range.d.ts +10 -0
  112. package/dist/rules/compat/operator-range.js +15 -0
  113. package/dist/rules/compat/posix-absent-command.d.ts +12 -0
  114. package/dist/rules/compat/posix-absent-command.js +134 -0
  115. package/dist/rules/compat/shopt-options.d.ts +9 -0
  116. package/dist/rules/compat/shopt-options.js +63 -0
  117. package/dist/rules/compat/version-gates.d.ts +76 -0
  118. package/dist/rules/compat/version-gates.js +205 -0
  119. package/dist/rules/create-rule.d.ts +91 -0
  120. package/dist/rules/create-rule.js +51 -0
  121. package/dist/rules/create-wrapper-rule.d.ts +166 -0
  122. package/dist/rules/create-wrapper-rule.js +407 -0
  123. package/dist/rules/define-shell-rule.d.ts +184 -0
  124. package/dist/rules/define-shell-rule.js +56 -0
  125. package/dist/rules/index.d.ts +26 -0
  126. package/dist/rules/index.js +90 -0
  127. package/dist/rules/max-script-complexity.d.ts +28 -0
  128. package/dist/rules/max-script-complexity.js +130 -0
  129. package/dist/rules/no-dynamic-invocation.d.ts +35 -0
  130. package/dist/rules/no-dynamic-invocation.js +117 -0
  131. package/dist/rules/no-eval.d.ts +13 -0
  132. package/dist/rules/no-eval.js +89 -0
  133. package/dist/rules/no-masked-exit-status.d.ts +21 -0
  134. package/dist/rules/no-masked-exit-status.js +667 -0
  135. package/dist/rules/no-secret-env-reads.d.ts +24 -0
  136. package/dist/rules/no-secret-env-reads.js +133 -0
  137. package/dist/rules/no-secret-flags.d.ts +32 -0
  138. package/dist/rules/no-secret-flags.js +162 -0
  139. package/dist/rules/no-unchecked-cd.d.ts +44 -0
  140. package/dist/rules/no-unchecked-cd.js +250 -0
  141. package/dist/rules/no-undeclared-commands.d.ts +20 -0
  142. package/dist/rules/no-undeclared-commands.js +115 -0
  143. package/dist/rules/no-unused-declarations.d.ts +23 -0
  144. package/dist/rules/no-unused-declarations.js +259 -0
  145. package/dist/rules/prefer-local.d.ts +16 -0
  146. package/dist/rules/prefer-local.js +249 -0
  147. package/dist/rules/quote-expansions.d.ts +13 -0
  148. package/dist/rules/quote-expansions.js +229 -0
  149. package/dist/rules/require-network-wrapper.d.ts +40 -0
  150. package/dist/rules/require-network-wrapper.js +244 -0
  151. package/dist/rules/require-shebang.d.ts +29 -0
  152. package/dist/rules/require-shebang.js +101 -0
  153. package/dist/rules/require-strict-mode.d.ts +52 -0
  154. package/dist/rules/require-strict-mode.js +177 -0
  155. package/dist/rules/require-tty-guard.d.ts +13 -0
  156. package/dist/rules/require-tty-guard.js +318 -0
  157. package/dist/rules/resolve-declaration-convention.d.ts +96 -0
  158. package/dist/rules/resolve-declaration-convention.js +109 -0
  159. package/dist/rules/resolve-network-wrapper.d.ts +19 -0
  160. package/dist/rules/resolve-network-wrapper.js +116 -0
  161. package/dist/rules/set-options.d.ts +109 -0
  162. package/dist/rules/set-options.js +201 -0
  163. package/dist/rules/shebang-interpreter.d.ts +18 -0
  164. package/dist/rules/shebang-interpreter.js +81 -0
  165. package/dist/rules/shebang-matches-dialect.d.ts +12 -0
  166. package/dist/rules/shebang-matches-dialect.js +69 -0
  167. package/dist/rules/transparent-wrappers.d.ts +52 -0
  168. package/dist/rules/transparent-wrappers.js +99 -0
  169. package/dist/rules/user-pattern.d.ts +63 -0
  170. package/dist/rules/user-pattern.js +184 -0
  171. package/dist/syntax/analyze.d.ts +19 -0
  172. package/dist/syntax/analyze.js +21 -0
  173. package/dist/syntax/index.d.ts +23 -0
  174. package/dist/syntax/index.js +25 -0
  175. package/dist/version.d.ts +0 -1
  176. package/dist/version.js +0 -1
  177. package/docs/compat.md +354 -0
  178. package/docs/custom-rule-tutorial.md +780 -34
  179. package/docs/declaration-conventions.md +478 -0
  180. package/docs/examples/audit-wrapper-pack.mjs +15 -0
  181. package/docs/examples/dep-line-convention.mjs +22 -0
  182. package/docs/examples/eslint.config.mjs +3 -2
  183. package/docs/examples/no-and-or-as-if-rule.mjs +61 -0
  184. package/docs/examples/no-backticks-rule-tester.mjs +106 -0
  185. package/docs/examples/no-backticks-rule.mjs +60 -0
  186. package/docs/examples/no-curl-in-functions-rule.mjs +53 -0
  187. package/docs/examples/no-function-keyword-rule.mjs +59 -0
  188. package/docs/examples/no-listed-commands-rule.ts +63 -0
  189. package/docs/examples/no-rm-rf-rule.mjs +26 -15
  190. package/docs/examples/no-unquoted-rm-variable-rule.ts +54 -0
  191. package/docs/examples/sidecar-convention.mjs +38 -0
  192. package/docs/examples/sidecar-snapshot-convention.mjs +26 -0
  193. package/docs/examples/sorted-declarations-rule.mjs +52 -0
  194. package/docs/reading-nodes.md +159 -0
  195. package/docs/rules/README.md +79 -0
  196. package/docs/rules/_template.md +83 -0
  197. package/docs/rules/compat/no-append-all-redirect.md +87 -0
  198. package/docs/rules/compat/no-associative-arrays.md +95 -0
  199. package/docs/rules/compat/no-brace-sequence-padding-step.md +90 -0
  200. package/docs/rules/compat/no-case-attributes.md +82 -0
  201. package/docs/rules/compat/no-case-fallthrough.md +90 -0
  202. package/docs/rules/compat/no-case-modification.md +74 -0
  203. package/docs/rules/compat/no-coproc.md +76 -0
  204. package/docs/rules/compat/no-declare-g.md +80 -0
  205. package/docs/rules/compat/no-double-bracket.md +72 -0
  206. package/docs/rules/compat/no-epoch-variables.md +79 -0
  207. package/docs/rules/compat/no-fd-variable.md +78 -0
  208. package/docs/rules/compat/no-globstar.md +74 -0
  209. package/docs/rules/compat/no-lastpipe-semantics.md +73 -0
  210. package/docs/rules/compat/no-let.md +78 -0
  211. package/docs/rules/compat/no-mapfile.md +118 -0
  212. package/docs/rules/compat/no-nameref.md +81 -0
  213. package/docs/rules/compat/no-negative-array-index.md +87 -0
  214. package/docs/rules/compat/no-nounset-empty-array.md +103 -0
  215. package/docs/rules/compat/no-parameter-transformation.md +94 -0
  216. package/docs/rules/compat/no-pipe-all.md +85 -0
  217. package/docs/rules/compat/no-printf-time-format.md +75 -0
  218. package/docs/rules/compat/no-source.md +98 -0
  219. package/docs/rules/compat/no-test-v.md +82 -0
  220. package/docs/rules/compat/no-unicode-escape.md +91 -0
  221. package/docs/rules/compat/no-wait-n.md +85 -0
  222. package/docs/rules/max-script-complexity.md +109 -0
  223. package/docs/rules/no-dynamic-invocation.md +139 -0
  224. package/docs/rules/no-eval.md +106 -0
  225. package/docs/rules/no-masked-exit-status.md +239 -0
  226. package/docs/rules/no-secret-env-reads.md +173 -0
  227. package/docs/rules/no-secret-flags.md +169 -0
  228. package/docs/rules/no-unchecked-cd.md +146 -0
  229. package/docs/rules/no-undeclared-commands.md +141 -0
  230. package/docs/rules/no-unused-declarations.md +216 -0
  231. package/docs/rules/prefer-local.md +221 -0
  232. package/docs/rules/quote-expansions.md +157 -0
  233. package/docs/rules/require-network-wrapper.md +171 -0
  234. package/docs/rules/require-shebang.md +157 -0
  235. package/docs/rules/require-strict-mode.md +253 -0
  236. package/docs/rules/require-tty-guard.md +131 -0
  237. package/docs/rules/shebang-matches-dialect.md +120 -0
  238. package/docs/selectors.md +2 -2
  239. package/docs/shellcheck.md +87 -0
  240. package/docs/untrusted-code.md +440 -0
  241. package/docs/wrapper-rules.md +206 -0
  242. package/package.json +26 -20
  243. package/dist/index.d.ts.map +0 -1
  244. package/dist/index.js.map +0 -1
  245. package/dist/language/selector-classes.d.ts.map +0 -1
  246. package/dist/language/selector-classes.js.map +0 -1
  247. package/dist/language/shell-language-options.d.ts.map +0 -1
  248. package/dist/language/shell-language-options.js.map +0 -1
  249. package/dist/language/shell-language.d.ts.map +0 -1
  250. package/dist/language/shell-language.js.map +0 -1
  251. package/dist/language/shell-source-code.d.ts.map +0 -1
  252. package/dist/language/shell-source-code.js.map +0 -1
  253. package/dist/language/source-coordinates.d.ts.map +0 -1
  254. package/dist/language/source-coordinates.js.map +0 -1
  255. package/dist/language/syntax-view.d.ts.map +0 -1
  256. package/dist/language/syntax-view.js.map +0 -1
  257. package/dist/language/word-resolution.d.ts.map +0 -1
  258. package/dist/language/word-resolution.js.map +0 -1
  259. package/dist/version.d.ts.map +0 -1
  260. package/dist/version.js.map +0 -1
package/README.md CHANGED
@@ -1,12 +1,13 @@
1
1
  # @eslint-sh/eslint-plugin
2
2
 
3
- The underlying ESLint plugin behind the `eslint-sh` umbrella package: shell script parsing and
4
- syntax-error reporting for ESLint, with custom-rule authoring over a typed shell AST. Provides the
3
+ Shell script linting for ESLint: enforce project conventions and contracts with built-in rules,
4
+ shared presets, and custom rules over a typed shell AST. This is the underlying plugin behind
5
+ the `eslint-sh` umbrella package. It provides the
5
6
  `ShellLanguage` / `ShellSourceCode` core that lets ESLint parse and traverse `.sh`/`.bash` files,
6
7
  backed by `sh-ast`.
7
8
 
8
- For installation, the config example, the `languageOptions` reference table, the capability
9
- caveat, and troubleshooting, see the [`eslint-sh` package's README](https://github.com/mike-north/eslint-sh/blob/main/packages/eslint-sh/README.md) —
9
+ For installation, the config example, the `languageOptions` reference table, the shellcheck
10
+ positioning, and troubleshooting, see the [`eslint-sh` package's README](https://github.com/mike-north/eslint-sh/blob/main/packages/eslint-sh/README.md) —
10
11
  that's the canonical quickstart for this project, and applies whether you install `eslint-sh` or
11
12
  this package directly.
12
13
 
@@ -14,18 +15,66 @@ this package directly.
14
15
 
15
16
  `ShellLanguage`/`ShellSourceCode` core, plus the six launch esquery selector classes — see the
16
17
  [selector cookbook](./docs/selectors.md) (shipped with this package) for ready-made selector
17
- recipes. Rules and shared `configs` are not implemented yet — `plugin.rules` is intentionally
18
- empty here. Disable directives / inline config are implemented and live: all four
18
+ recipes. Four shared configs ship, each supplying `plugins: { sh }` and `language: 'sh/sh'`:
19
+ `configs.recommended` enables the convention-free rules (`sh/require-strict-mode`,
20
+ `sh/require-shebang`, `sh/no-eval`, `sh/no-unchecked-cd`, `sh/shebang-matches-dialect` at `error`; `sh/no-secret-flags`, `sh/quote-expansions` at `warn`), and
21
+ `configs['agent-authored']` adds the contract rules (`sh/no-dynamic-invocation`,
22
+ `sh/no-secret-env-reads`, `sh/no-undeclared-commands`, `sh/no-unused-declarations`,
23
+ `sh/require-network-wrapper`, `sh/require-tty-guard`, `sh/prefer-local`, `sh/no-masked-exit-status`) and
24
+ `sh/max-script-complexity`, with every rule at `error`. Its convention and wrapper rules report nothing until you bind a declaration
25
+ convention or a wrapper. The [rules index](./docs/rules/README.md) lists all forty built-in rules,
26
+ their presets, and
27
+ autofix availability. The version-gated rules `sh/compat/no-append-all-redirect`, `sh/compat/no-associative-arrays`, `sh/compat/no-brace-sequence-padding-step`, `sh/compat/no-case-attributes`, `sh/compat/no-case-fallthrough`, `sh/compat/no-case-modification`, `sh/compat/no-coproc`, `sh/compat/no-declare-g`, `sh/compat/no-epoch-variables`, `sh/compat/no-fd-variable`, `sh/compat/no-globstar`, `sh/compat/no-lastpipe-semantics`, `sh/compat/no-mapfile`, `sh/compat/no-nameref`, `sh/compat/no-negative-array-index`, `sh/compat/no-nounset-empty-array`, `sh/compat/no-parameter-transformation`, `sh/compat/no-pipe-all`, `sh/compat/no-printf-time-format`, `sh/compat/no-test-v`, `sh/compat/no-unicode-escape` and `sh/compat/no-wait-n`, report features your target shell version
28
+ lacks; `configs['bash-3.2']` (`shellVersion: '3.2'`) and `configs['posix-portable']` (`dialect: 'posix'`, with
29
+ `sh/require-strict-mode` as `set -eu`) enable them for those targets. `configs['posix-portable']` also reports `[[`, `let` and `source`, which fail on dash (`sh/compat/no-double-bracket`, `sh/compat/no-let`, `sh/compat/no-source`), but not `local`, `echo -e` or the other constructs it leaves to shellcheck, so a clean run does not prove a script runs under every POSIX `sh`; see [version compatibility](./docs/compat.md). Run shellcheck alongside eslint-sh: shellcheck catches shell correctness
30
+ problems; eslint-sh enforces project policy. [Running eslint-sh beside shellcheck](./docs/shellcheck.md)
31
+ covers who owns what and the rules that overlap it deliberately (see also the
32
+ [design rationale](https://github.com/mike-north/eslint-sh/blob/main/design/README.md#eslint-sh-and-shellcheck)).
33
+
34
+ Disable directives / inline config are implemented and live: all four
19
35
  `# eslint-disable*` comment forms, unused-directive detection, and `linterOptions.noInlineConfig`
20
- interplay work through ESLint's native machinery. No lint rules ship with this package yet, so the
21
- rule id below is illustrative — but once you register a custom rule of your own, the directives
22
- suppress it: for a rule you've configured as `sh/your-rule`,
36
+ interplay work through ESLint's native machinery. The rule id below is illustrative; the
37
+ directives suppress built-in rules and your own custom rules alike: for a rule you've configured as `sh/your-rule`,
23
38
  `# eslint-disable-next-line sh/your-rule -- justification` suppresses it on the following line.
39
+ A script can therefore switch any rule off for itself. When the scripts you lint come from someone
40
+ who doesn't own the policy, see [linting code you don't control](./docs/untrusted-code.md): a
41
+ config that ignores those comments, and how to run ESLint from outside the checkout under review,
42
+ so that it can't supply the config, ESLint, the plugin, or anything else ESLint looks for in the
43
+ directory it runs in.
44
+
45
+ ## CommonJS config
46
+
47
+ In a package without `"type": "module"` (the ESLint default), `eslint.config.js` is CommonJS, as
48
+ is `eslint.config.cjs` in any package. `@eslint-sh/eslint-plugin` is ESM-only, so
49
+ `require('@eslint-sh/eslint-plugin')` returns the module namespace and the plugin object is its
50
+ `.default`. Named exports such as `commentPragma` are ordinary properties of the `require` result.
51
+ Without `.default`, `sh.configs` is `undefined` and ESLint fails with
52
+ `Cannot read properties of undefined (reading 'recommended')`.
53
+
54
+ ```js
55
+ // eslint.config.js (CommonJS package) or eslint.config.cjs
56
+ const { defineConfig } = require('eslint/config');
57
+ const sh = require('@eslint-sh/eslint-plugin').default;
58
+ // Named exports need no `.default`.
59
+ const { commentPragma } = require('@eslint-sh/eslint-plugin');
60
+
61
+ module.exports = defineConfig([
62
+ {
63
+ files: ['**/*.sh'],
64
+ extends: [sh.configs.recommended],
65
+ settings: { sh: { declarationConvention: commentPragma } },
66
+ rules: { 'sh/no-undeclared-commands': 'error' },
67
+ },
68
+ ]);
69
+ ```
24
70
 
25
71
  ## Public API
26
72
 
27
73
  - `plugin` (default export) — `{ meta: { name, version, namespace: "sh" }, languages: { sh:
28
- ShellLanguage }, rules: {} }`. `meta.version` always matches this package's `package.json`.
74
+ ShellLanguage }, rules, configs: { recommended, 'agent-authored', 'bash-3.2', 'posix-portable' } }`. `meta.version` always
75
+ matches this package's `package.json`. Each config is a single config object with no `files`
76
+ key (`bash-3.2` and `posix-portable` also set `languageOptions`); extend it under your own `files`:
77
+ `defineConfig({ files: ['**/*.sh'], extends: [sh.configs.recommended] })`.
29
78
  - `ShellLanguage` — the `@eslint/core` `Language` implementation: `fileType: "text"`,
30
79
  `lineStart`/`columnStart: 1`, `nodeTypeKey: "type"`, the consumer's schema-derived `visitorKeys`,
31
80
  `validateLanguageOptions`/`normalizeLanguageOptions` for the
@@ -36,7 +85,13 @@ ShellLanguage }, rules: {} }`. `meta.version` always matches this package's `pac
36
85
  - `ShellSourceCode` — extends `@eslint/plugin-kit`'s `TextSourceCodeBase`; implements `traverse()`
37
86
  (driven by the consumer's `visitorKeys`, yielding two-phase `VisitNodeStep`s), `getParent()`, and
38
87
  the disable-directive surface (`getDisableDirectives()`, `getInlineConfigNodes()`,
39
- `applyInlineConfig()`).
88
+ `applyInlineConfig()`). Rule authors get helpers that delegate to `sh-ast/analyze`, so every rule
89
+ gets the same answer: `resolveWordText(word, options?)` (a word's literal text, or `null` if
90
+ `resolveWord` does not report it static), `getCommandName(callExpr)` (the static command word,
91
+ without following wrappers), and `resolveInvokedCommand(callExpr, { transparentWrappers? })` (the
92
+ full `Argv0Resolution` once wrappers such as `env`, `sudo`, and `exec` are followed, including the
93
+ wrapper `chain` and the `effective` command or the reason it is unknowable). The `dialect` and
94
+ `shellVersion` accessors return the file's normalized language options.
40
95
  - `ShellLanguageOptions` / `NormalizedShellLanguageOptions` / `ShellLanguageOptionsJSON` — the
41
96
  `languageOptions` types (`dialect`, `shellVersion`, `directivePrefix`). `directivePrefix` is the
42
97
  prefix directive/inline-config comments must start with — e.g. the default `"eslint"` recognizes
@@ -45,20 +100,176 @@ ShellLanguage }, rules: {} }`. `meta.version` always matches this package's `pac
45
100
  - `ShellNode` / `ShellFile` / `ShellComment` / `ShellPosition` — the plugin's lint view: lowercase
46
101
  fields, UTF-16 ranges, and one-based UTF-16 locations, independent of the raw parser syntax.
47
102
  Locations come from one parse-time position snapshot and remain bound to that original source;
48
- projecting the view does not run live bounds operations on every syntax subtree.
103
+ projecting the view does not run live bounds operations on every syntax subtree. `ShellNode` is
104
+ the union of every node type, so narrowing on `node.type` gives that node's fields, typed, with
105
+ no cast. See [Reading nodes](./docs/reading-nodes.md).
106
+ - `ShellNodeOf` / `ShellNodeType` / `ShellNodeMap` / `ShellNodeField` — the types behind the union.
107
+ `ShellNodeOf<'Stmt'>` is one node type by name, `ShellNodeType` is every name, and
108
+ `ShellNodeMap` maps one to the other. They are computed from `sh-ast`'s generated typings, so
109
+ they follow the pinned mvdan/sh version. `ShellNodeField<T>` is the type of one node field
110
+ (`ShellNodeField<ShellNodeOf<'Stmt'>['cmd']>`), for a helper that takes or returns field values.
111
+ - `ShellCommandSite` / `CommandContext` / `ResolveArgv0Options` — one command in a file as `getCommandSites()` reports it:
112
+ its `CallExpr` node (`node`), the `context` path of enclosing constructs from the file root
113
+ (each frame a `CommandContext`, discriminated by `kind`: `and`, `or`, `pipeline`, `subshell`,
114
+ `cmdSubst`, …), its `argv0` and `argv` as `WordResolution`s, and `resolveInvokedCommand()`,
115
+ which follows wrappers. `ResolveArgv0Options` is that method's option bag (and
116
+ `ShellSourceCode.resolveInvokedCommand`'s): `transparentWrappers` replaces the wrapper table
117
+ followed.
118
+ - `WordResolution` — what `resolveWord` and a site's `argv` words report: `{ static: true, text }`
119
+ when the word's value is known without running the script, or `{ static: false, reason }` when
120
+ it is not (`reason` is `'expansion'`, `'tilde'`, `'glob'`, `'brace'`, `'locale'` or
121
+ `'unsupported'`; a new reason can appear in a minor release, so keep a `default` branch). Check
122
+ `static` before reading `text`.
123
+ - `ShellDialect` — the `dialect` language option's values: `'bash'`, `'posix'`, `'mksh'`,
124
+ `'bats'` and `'zsh'`.
125
+ - `Argv0Resolution` — the type `ShellSourceCode.resolveInvokedCommand` returns, re-exported from
126
+ `sh-ast/analyze`.
49
127
  - `resolveWord` — adapts a parsed lint Word to `sh-ast`'s word-resolution helper, so a project that installs only
50
128
  this package can write custom rules that resolve static shell words (e.g. the `no-rm-rf` example
51
129
  in the [custom-rule tutorial](./docs/custom-rule-tutorial.md)) without a separate `sh-ast`
52
130
  install. Fabricated nodes are rejected because they have no original syntax association.
131
+ - `DeclarationConvention` / `DeclaredCommand` / `commentPragma` — the extension point for how a
132
+ project declares the external commands its scripts use, bound with
133
+ `settings: { sh: { declarationConvention } }`. `commentPragma` is the reference convention,
134
+ reading `# uses: jq curl` header comments. See [declaration
135
+ conventions](./docs/declaration-conventions.md).
136
+ - `ScriptDeclaredCommand` / `FileDeclaredCommand` / `DescribedDeclaredCommand` / `LintedFile` — the
137
+ three kinds of `DeclaredCommand`, by where a declaration lives (in the script, in another file
138
+ such as a sidecar, or in a source described in words, such as a config list or a registry
139
+ snapshot), and the
140
+ linted file a convention is told about so it can find a per-script sidecar. See [declarations
141
+ outside the script](./docs/declaration-conventions.md#declarations-outside-the-script).
142
+ - `readDeclaredCommands` / `DeclarationReadContext` — reads the bound convention's declarations
143
+ for the file a rule is linting, checked exactly as the built-in rules check them. See [reading
144
+ the convention in your own
145
+ rule](./docs/declaration-conventions.md#reading-the-convention-in-your-own-rule).
146
+ - `defineShellRule` / `ShellRule` / `ShellRuleSpec` / `ShellRuleMeta` / `ShellRuleContext` /
147
+ `ShellRuleVisitor` / `ShellListener` / `ShellTypedListeners` — the rule-authoring helper and its types. `defineShellRule({ meta, create })`
148
+ returns an ordinary ESLint rule with `meta.languages: ['sh/sh']` set, so ESLint refuses to run it
149
+ on another language; `context.sourceCode` is typed `ShellSourceCode`, a listener keyed by a node
150
+ type (`CallExpr`, `Word:exit`) receives that node type, and any other selector's listener receives
151
+ `ShellNode`. Writing `meta.languages` yourself is a type error. The built-in rules are made with
152
+ it too. `ShellListener<Node>` is the type of one listener, so a helper that builds listeners for
153
+ several rules can name its return type; `ShellTypedListeners` is the node-type-keyed half of
154
+ `ShellRuleVisitor`. See [Writing your own rules](#writing-your-own-rules).
155
+ - `resolveDeclarationConvention` / `DeclarationConventionBindingContext` — resolves the declaration
156
+ convention bound for a rule (its `declarationConvention` option, else
157
+ `settings.sh.declarationConvention`, else `null`) with the validation and error messages the
158
+ built-in consumers use, so a rule of your own can read the convention. See [declaration
159
+ conventions](./docs/declaration-conventions.md#reading-the-convention-in-your-own-rule).
160
+ - `createWrapperRule` / `WrapperRuleConfig` / `WrapperRuleSubcommands` / `WrapperSpec` — the
161
+ factory for "command X must be run through wrapper Y" rules a rule pack
162
+ binds to its own commands and wrapper. `sh/require-network-wrapper` is the built-in rule made
163
+ with it. See [wrapper rules](./docs/wrapper-rules.md).
164
+ - `WrapperRuleLocalInvocation` / `WrapperRuleLocalOperand` — the `localInvocations` entry of a
165
+ `createWrapperRule` config: invocations of a listed command that only act locally and are not
166
+ reported, such as `curl --version` or `git clone ./x`. See
167
+ [wrapper rules](./docs/wrapper-rules.md#local-only-invocations).
168
+
169
+ ## Faithful syntax: `@eslint-sh/eslint-plugin/syntax`
170
+
171
+ The root exports work on the plugin's lint view (`ShellNode`). A rule that edits or analyzes the
172
+ parser's own records needs `sh-ast` itself: `walk`, `parseSync`, `printSync` and `PrintOptions`,
173
+ the record types (`Node`, `Word`, `CallExpr`, `SyntaxPos`, `GoString`, …), and the analysis
174
+ helpers. Import them from this package's subpaths, not from `sh-ast`:
175
+
176
+ ```js
177
+ // View-aware helpers: take lint-view nodes.
178
+ import { resolveWord } from '@eslint-sh/eslint-plugin';
179
+ // sh-ast's root entry point: parse, walk, and print faithful records.
180
+ import { parseSync, printSync, walk } from '@eslint-sh/eslint-plugin/syntax';
181
+ // sh-ast/analyze: command enumeration and wrapper resolution over faithful records.
182
+ import {
183
+ DEFAULT_TRANSPARENT_WRAPPERS,
184
+ enumerateCommands,
185
+ } from '@eslint-sh/eslint-plugin/syntax/analyze';
186
+ ```
187
+
188
+ - `@eslint-sh/eslint-plugin/syntax` re-exports the complete public surface of `sh-ast`, and
189
+ `@eslint-sh/eslint-plugin/syntax/analyze` re-exports `sh-ast/analyze`. Both come from the
190
+ `sh-ast` this plugin depends on, the same module instance its language parses with, so you
191
+ don't install `sh-ast` yourself and records always match the parser that made them. Nothing is
192
+ wrapped or renamed; each name keeps `sh-ast`'s own contract and docs.
193
+ - The two `resolveWord`s differ. The root's takes a lint-view `ShellNode`; the one in
194
+ `…/syntax/analyze` takes a faithful `Node`. That is why the subpaths are kept apart from the
195
+ root.
196
+ - **Versioning.** `…/syntax` and `…/syntax/analyze` track this plugin's pinned `sh-ast` minor
197
+ (currently `sh-ast@^0.7.0`). Under 0.x, a different `sh-ast` minor is a breaking change, so
198
+ an `sh-ast` 0.x minor bump is a minor bump of this plugin, and the changelog entry names the
199
+ new `sh-ast` version.
53
200
 
54
201
  Syntax failures surface as fatal lint messages at mvdan/sh's reported byte offset, converted to
55
- the source's UTF-16 location. Failures without a valid syntax position use a 1:1 fallback.
202
+ the source's UTF-16 location. Failures without a valid syntax position use a 1:1 fallback. A
203
+ failure of the parser runtime itself (an abort, or a failure of the bridge to it) is not a
204
+ syntax error, and the file may be perfectly valid. It is reported at 1:1 as
205
+ `Parsing error: eslint-sh parser runtime failure (not a syntax error in this file): <detail>`.
206
+ ESLint adds the `Parsing error: ` prefix to every message a language returns from `parse`, and a
207
+ language cannot remove it, so look for the marker text `eslint-sh parser runtime failure` to tell
208
+ this apart from a real syntax error, which has no marker and sits at the offending position. Linting
209
+ the file again, or in a fresh process, usually gets past a runtime failure.
210
+
211
+ ### Known parser gaps
212
+
213
+ The parser is the upstream `sh-ast`, and it rejects three valid Bash forms. Each is reported as a
214
+ syntax error. Until the parser accepts them, write the workaround in the right-hand column.
215
+
216
+ | Valid Bash form | Workaround |
217
+ | ------------------------ | -------------------------- |
218
+ | `x=$((a \|\| b) \| c)` | `x=$( (a \|\| b) \| c )` |
219
+ | `x=(${y}[0])` | `word=${y}[0]; x=($word)` |
220
+ | `declare -A m=([a.b]=1)` | `declare -A m=(["a.b"]=1)` |
221
+
222
+ - `x=$((a || b) | c)`: the parser reads `$((` as the start of arithmetic. A space after `$(` makes
223
+ it a command substitution containing a subshell.
224
+ - `x=(${y}[0])`: inside an array literal, any element word that ends in an unquoted `[…]` is read
225
+ as an indexed element (`[0]=value`) and must be followed by `=`. It is not limited to a word
226
+ that starts with an expansion: `x=(a[0])`, `x=(a${y}[0])` and `x=("${y}"[0])` are rejected too
227
+ (a `[0]` that is meant as a glob character class). Building the word in its own statement keeps
228
+ the same splitting and globbing.
229
+ - An associative-array subscript containing a `.` that is not quoted: the parser reads the
230
+ subscript as arithmetic and rejects the dot ("floating point arithmetic is a zsh feature"). The
231
+ gap is wider than the `declare` form above. It also rejects `m[a.b]=1` and `${m[a.b]}`, which
232
+ parse once quoted: `m["a.b"]=1` and `${m["a.b"]}`.
56
233
 
57
234
  ## Writing your own rules
58
235
 
59
- Want to write a custom rule against the shell AST? See the [custom-rule
60
- tutorial](./docs/custom-rule-tutorial.md) for a complete, CI-tested walkthrough: a selector from
61
- the cookbook, a full `{ meta, create(context) }` rule module with `context.report`, and the
62
- flat-config wiring to run it. `docs/` ships with this package (see `package.json#files`), so the
236
+ Build a rule with `defineShellRule`. It takes the `{ meta, create(context) }` object you would
237
+ write for any ESLint rule and returns an ESLint rule you register in a plugin's `rules`:
238
+
239
+ ```js
240
+ import { defineShellRule } from '@eslint-sh/eslint-plugin';
241
+
242
+ export default defineShellRule({
243
+ meta: {
244
+ type: 'problem',
245
+ schema: [],
246
+ messages: { noEval: 'Avoid `eval`; it runs a string as code.' },
247
+ },
248
+ create(context) {
249
+ return {
250
+ CallExpr(node) {
251
+ if (context.sourceCode.getCommandName(node) === 'eval') {
252
+ context.report({ node, messageId: 'noEval' });
253
+ }
254
+ },
255
+ };
256
+ },
257
+ });
258
+ ```
259
+
260
+ `defineShellRule` sets `meta.languages: ['sh/sh']`, the declaration that makes ESLint refuse to
261
+ run the rule on a file of another language (usually because a config entry is missing
262
+ `language: 'sh/sh'`) instead of letting it silently match nothing. In TypeScript,
263
+ `context.sourceCode` is `ShellSourceCode`, a listener keyed by a node type receives that node type
264
+ (`CallExpr(node)` reads `node.args` with no check), and `context.report`
265
+ accepts only the message ids in `meta.messages`, with no `@eslint/core` import of your own.
266
+
267
+ [Reading nodes](./docs/reading-nodes.md) is the field reference for the nodes your listeners
268
+ receive: narrow a `ShellNode` on `type` and read its fields, typed, with one worked rule.
269
+
270
+ The [custom-rule tutorial](./docs/custom-rule-tutorial.md) is a complete, CI-tested walkthrough: a
271
+ selector from the cookbook, a full rule with `context.report`, the flat-config wiring to run it,
272
+ fixes (and when a printed fix reflows code, so a position-based text edit is the better choice),
273
+ and a TypeScript rule. `docs/` ships with this package (see `package.json#files`), so the
63
274
  tutorial and its example files (`docs/examples/`) are available in every installed copy — not just
64
275
  on GitHub.
@@ -0,0 +1,36 @@
1
+ /**
2
+ * The commands bash runs as builtins: data for the command harvest, never
3
+ * rule logic.
4
+ *
5
+ * Source: the Bash Reference Manual for **Bash 5.3**, chapter 4 "Shell
6
+ * Builtin Commands" (§4.1 Bourne Shell Builtins, §4.2 Bash Builtin Commands,
7
+ * §4.3 Modifying Shell Behavior: `set`, `shopt`), plus the builtins that
8
+ * chapter defers to other chapters: §6.8.1 Directory Stack Builtins
9
+ * (`dirs`, `popd`, `pushd`), §7.2 Job Control Builtins (`bg`, `fg`, `jobs`,
10
+ * `kill`, `wait`, `disown`, `suspend`), §8.7 Programmable Completion
11
+ * Builtins (`compgen`, `complete`, `compopt`) and §9.2 Bash History Builtins
12
+ * (`fc`, `history`). `true` and `false` are builtins in every bash release
13
+ * (`help true`) although the manual describes them only as utilities.
14
+ * Cross-checked against `compgen -b` from bash 5.3.9, which prints exactly
15
+ * this list.
16
+ *
17
+ * Reserved words (`if`, `time`, `[[`, `function`, …) are not here: the
18
+ * parser never produces them as command words.
19
+ *
20
+ * The list is the latest bash's, and is not narrowed by `shellVersion`:
21
+ * `mapfile`, `readarray` and `compopt` (added in bash 4.0) are builtins here
22
+ * even for a file configured as bash 3.2. Whether a script may use them on
23
+ * an old bash is a version-compatibility question, not a dependency one.
24
+ *
25
+ * @see https://www.gnu.org/software/bash/manual/bash.html#Shell-Builtin-Commands
26
+ * @see https://www.gnu.org/software/bash/manual/bash.html#Directory-Stack-Builtins
27
+ * @see https://www.gnu.org/software/bash/manual/bash.html#Job-Control-Builtins
28
+ * @see https://www.gnu.org/software/bash/manual/bash.html#Programmable-Completion-Builtins
29
+ * @see https://www.gnu.org/software/bash/manual/bash.html#Bash-History-Builtins
30
+ */
31
+ /**
32
+ * Every bash 5.3 builtin command name, in `compgen -b` order.
33
+ *
34
+ * @internal
35
+ */
36
+ export declare const BASH_BUILTINS: readonly string[];
@@ -0,0 +1,98 @@
1
+ /**
2
+ * The commands bash runs as builtins: data for the command harvest, never
3
+ * rule logic.
4
+ *
5
+ * Source: the Bash Reference Manual for **Bash 5.3**, chapter 4 "Shell
6
+ * Builtin Commands" (§4.1 Bourne Shell Builtins, §4.2 Bash Builtin Commands,
7
+ * §4.3 Modifying Shell Behavior: `set`, `shopt`), plus the builtins that
8
+ * chapter defers to other chapters: §6.8.1 Directory Stack Builtins
9
+ * (`dirs`, `popd`, `pushd`), §7.2 Job Control Builtins (`bg`, `fg`, `jobs`,
10
+ * `kill`, `wait`, `disown`, `suspend`), §8.7 Programmable Completion
11
+ * Builtins (`compgen`, `complete`, `compopt`) and §9.2 Bash History Builtins
12
+ * (`fc`, `history`). `true` and `false` are builtins in every bash release
13
+ * (`help true`) although the manual describes them only as utilities.
14
+ * Cross-checked against `compgen -b` from bash 5.3.9, which prints exactly
15
+ * this list.
16
+ *
17
+ * Reserved words (`if`, `time`, `[[`, `function`, …) are not here: the
18
+ * parser never produces them as command words.
19
+ *
20
+ * The list is the latest bash's, and is not narrowed by `shellVersion`:
21
+ * `mapfile`, `readarray` and `compopt` (added in bash 4.0) are builtins here
22
+ * even for a file configured as bash 3.2. Whether a script may use them on
23
+ * an old bash is a version-compatibility question, not a dependency one.
24
+ *
25
+ * @see https://www.gnu.org/software/bash/manual/bash.html#Shell-Builtin-Commands
26
+ * @see https://www.gnu.org/software/bash/manual/bash.html#Directory-Stack-Builtins
27
+ * @see https://www.gnu.org/software/bash/manual/bash.html#Job-Control-Builtins
28
+ * @see https://www.gnu.org/software/bash/manual/bash.html#Programmable-Completion-Builtins
29
+ * @see https://www.gnu.org/software/bash/manual/bash.html#Bash-History-Builtins
30
+ */
31
+ /**
32
+ * Every bash 5.3 builtin command name, in `compgen -b` order.
33
+ *
34
+ * @internal
35
+ */
36
+ export const BASH_BUILTINS = Object.freeze([
37
+ '.',
38
+ ':',
39
+ '[',
40
+ 'alias',
41
+ 'bg',
42
+ 'bind',
43
+ 'break',
44
+ 'builtin',
45
+ 'caller',
46
+ 'cd',
47
+ 'command',
48
+ 'compgen',
49
+ 'complete',
50
+ 'compopt',
51
+ 'continue',
52
+ 'declare',
53
+ 'dirs',
54
+ 'disown',
55
+ 'echo',
56
+ 'enable',
57
+ 'eval',
58
+ 'exec',
59
+ 'exit',
60
+ 'export',
61
+ 'false',
62
+ 'fc',
63
+ 'fg',
64
+ 'getopts',
65
+ 'hash',
66
+ 'help',
67
+ 'history',
68
+ 'jobs',
69
+ 'kill',
70
+ 'let',
71
+ 'local',
72
+ 'logout',
73
+ 'mapfile',
74
+ 'popd',
75
+ 'printf',
76
+ 'pushd',
77
+ 'pwd',
78
+ 'read',
79
+ 'readarray',
80
+ 'readonly',
81
+ 'return',
82
+ 'set',
83
+ 'shift',
84
+ 'shopt',
85
+ 'source',
86
+ 'suspend',
87
+ 'test',
88
+ 'times',
89
+ 'trap',
90
+ 'true',
91
+ 'type',
92
+ 'typeset',
93
+ 'ulimit',
94
+ 'umask',
95
+ 'unalias',
96
+ 'unset',
97
+ 'wait',
98
+ ]);
@@ -0,0 +1,45 @@
1
+ /**
2
+ * The helpers bats-core defines for every test file: data for the command
3
+ * harvest under the `bats` dialect, never rule logic.
4
+ *
5
+ * A `.bats` file runs inside bats, which defines these as shell functions
6
+ * before any test runs. They are not external commands, so a test file never
7
+ * declares them. They are not defined in the file either, so the harvest's
8
+ * in-file function check cannot see them. Hence this list.
9
+ *
10
+ * Source: the bats-core manual, "Writing tests": `run` ("Test other
11
+ * commands"), `bats_pipe` ("Run commands with pipes"), `load` and
12
+ * `bats_load_safe` ("Share common code"), `bats_load_library` ("Load system
13
+ * wide libraries"), `skip` ("Easily skip tests"),
14
+ * `bats_require_minimum_version`, and `bats_test_function` ("Dynamic test
15
+ * registration").
16
+ *
17
+ * Absent by design: the hooks a test file defines itself (`setup`,
18
+ * `teardown`, `setup_file`, `teardown_file`, `bats::on_failure`), which the
19
+ * in-file function check already covers, and helpers from separately
20
+ * installed libraries such as bats-assert (`assert_success`), which are not
21
+ * part of bats-core.
22
+ *
23
+ * @see https://bats-core.readthedocs.io/en/stable/writing-tests.html
24
+ */
25
+ import type { WrapperSpec } from 'sh-ast/analyze';
26
+ /**
27
+ * Every helper function bats-core provides to test files.
28
+ *
29
+ * @internal
30
+ */
31
+ export declare const BATS_HELPERS: readonly string[];
32
+ /**
33
+ * bats' `run`, as a transparent wrapper: it runs the command after its own
34
+ * options and captures the result, so `run curl x` uses `curl`.
35
+ *
36
+ * Its options, all before the command (bats-core manual, "`run`: Test other
37
+ * commands"): `-N` (expect exit status N, 0–255), `!` (expect a nonzero
38
+ * status), `--keep-empty-lines`, `--separate-stderr`, and `--` to end option
39
+ * parsing. `!` is not dash-prefixed, but `noArgFlags` matches it as an exact
40
+ * word all the same.
41
+ *
42
+ * @see https://bats-core.readthedocs.io/en/stable/writing-tests.html#run-test-other-commands
43
+ * @internal
44
+ */
45
+ export declare const BATS_RUN_WRAPPER: WrapperSpec;
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Every helper function bats-core provides to test files.
3
+ *
4
+ * @internal
5
+ */
6
+ export const BATS_HELPERS = Object.freeze([
7
+ 'run',
8
+ 'bats_pipe',
9
+ 'load',
10
+ 'bats_load_safe',
11
+ 'bats_load_library',
12
+ 'skip',
13
+ 'bats_require_minimum_version',
14
+ 'bats_test_function',
15
+ ]);
16
+ /**
17
+ * bats' `run`, as a transparent wrapper: it runs the command after its own
18
+ * options and captures the result, so `run curl x` uses `curl`.
19
+ *
20
+ * Its options, all before the command (bats-core manual, "`run`: Test other
21
+ * commands"): `-N` (expect exit status N, 0–255), `!` (expect a nonzero
22
+ * status), `--keep-empty-lines`, `--separate-stderr`, and `--` to end option
23
+ * parsing. `!` is not dash-prefixed, but `noArgFlags` matches it as an exact
24
+ * word all the same.
25
+ *
26
+ * @see https://bats-core.readthedocs.io/en/stable/writing-tests.html#run-test-other-commands
27
+ * @internal
28
+ */
29
+ export const BATS_RUN_WRAPPER = Object.freeze({
30
+ names: Object.freeze(['run']),
31
+ noArgFlags: Object.freeze(['!', '--keep-empty-lines', '--separate-stderr']),
32
+ noArgFlagPattern: /^-\d+$/u,
33
+ });
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Which command names the shell itself provides for a given dialect.
3
+ *
4
+ * A builtin runs inside the shell, so a script never depends on it being
5
+ * installed, and the command harvest drops it. Under `bats`, the helper
6
+ * functions bats-core defines for every test file (`run`, `load`, `skip`, …)
7
+ * are provided the same way and are dropped too. The lists themselves are
8
+ * data, each citing its source, in `./bash.ts`, `./posix.ts` and `./bats.ts`;
9
+ * this module only maps each supported dialect to a combination of them.
10
+ */
11
+ import type { ShellDialect } from '../../language/shell-language-options.js';
12
+ /**
13
+ * Returns the set of command names that `dialect` provides without an
14
+ * external command: its builtins, plus bats-core's helpers for `bats`.
15
+ *
16
+ * @internal
17
+ */
18
+ export declare function builtinsForDialect(dialect: ShellDialect): ReadonlySet<string>;
@@ -0,0 +1,39 @@
1
+ import { BASH_BUILTINS } from './bash.js';
2
+ import { BATS_HELPERS } from './bats.js';
3
+ import { POSIX_CONVENTIONAL_BUILTINS, POSIX_REGULAR_BUILTINS, POSIX_SPECIAL_BUILTINS, } from './posix.js';
4
+ /** Every bash builtin. */
5
+ const BASH = new Set(BASH_BUILTINS);
6
+ /** Every bash builtin, plus the helpers bats-core defines for test files. */
7
+ const BATS = new Set([...BASH_BUILTINS, ...BATS_HELPERS]);
8
+ /** Every POSIX builtin: special, regular, and conventionally built in. */
9
+ const POSIX = new Set([
10
+ ...POSIX_SPECIAL_BUILTINS,
11
+ ...POSIX_REGULAR_BUILTINS,
12
+ ...POSIX_CONVENTIONAL_BUILTINS,
13
+ ]);
14
+ /**
15
+ * The builtin set for each dialect.
16
+ *
17
+ * - `bats` files are bash scripts run by bats, so they use the bash list
18
+ * plus bats-core's helpers.
19
+ * - `mksh` and `zsh` have no list of their own yet. Both implement every
20
+ * POSIX builtin, so the POSIX list is a safe floor: it never counts a real
21
+ * external command as a builtin. Their own extra builtins (`print`,
22
+ * `whence`, `setopt`, …) are reported as commands until they get lists.
23
+ */
24
+ const BUILTINS_BY_DIALECT = {
25
+ bash: BASH,
26
+ bats: BATS,
27
+ posix: POSIX,
28
+ mksh: POSIX,
29
+ zsh: POSIX,
30
+ };
31
+ /**
32
+ * Returns the set of command names that `dialect` provides without an
33
+ * external command: its builtins, plus bats-core's helpers for `bats`.
34
+ *
35
+ * @internal
36
+ */
37
+ export function builtinsForDialect(dialect) {
38
+ return BUILTINS_BY_DIALECT[dialect];
39
+ }
@@ -0,0 +1,51 @@
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 declare const POSIX_SPECIAL_BUILTINS: readonly string[];
39
+ /**
40
+ * POSIX regular built-in utilities (XCU §2.9.1.1 step 1.d, including the
41
+ * XSI `type` and `ulimit`).
42
+ *
43
+ * @internal
44
+ */
45
+ export declare const POSIX_REGULAR_BUILTINS: readonly string[];
46
+ /**
47
+ * Utilities POSIX lets a shell build in, and every POSIX shell does.
48
+ *
49
+ * @internal
50
+ */
51
+ export declare const POSIX_CONVENTIONAL_BUILTINS: readonly string[];