@eslint-sh/eslint-plugin 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (256) hide show
  1. package/README.md +223 -17
  2. package/dist/command-harvest/builtins/bash.d.ts +36 -0
  3. package/dist/command-harvest/builtins/bash.js +98 -0
  4. package/dist/command-harvest/builtins/bats.d.ts +45 -0
  5. package/dist/command-harvest/builtins/bats.js +33 -0
  6. package/dist/command-harvest/builtins/index.d.ts +18 -0
  7. package/dist/command-harvest/builtins/index.js +39 -0
  8. package/dist/command-harvest/builtins/posix.d.ts +51 -0
  9. package/dist/command-harvest/builtins/posix.js +93 -0
  10. package/dist/command-harvest/harvest-commands.d.ts +66 -0
  11. package/dist/command-harvest/harvest-commands.js +97 -0
  12. package/dist/command-harvest/wrapper-table.d.ts +24 -0
  13. package/dist/command-harvest/wrapper-table.js +49 -0
  14. package/dist/configs/agent-authored.d.ts +41 -0
  15. package/dist/configs/agent-authored.js +33 -0
  16. package/dist/configs/bash-3-2.d.ts +28 -0
  17. package/dist/configs/bash-3-2.js +50 -0
  18. package/dist/configs/posix-portable.d.ts +40 -0
  19. package/dist/configs/posix-portable.js +52 -0
  20. package/dist/configs/recommended.d.ts +52 -0
  21. package/dist/configs/recommended.js +34 -0
  22. package/dist/declaration-conventions/comment-pragma.d.ts +33 -0
  23. package/dist/declaration-conventions/comment-pragma.js +153 -0
  24. package/dist/declaration-conventions/declaration-convention.d.ts +189 -0
  25. package/dist/declaration-conventions/declaration-convention.js +1 -0
  26. package/dist/declaration-conventions/read-declared-commands.d.ts +70 -0
  27. package/dist/declaration-conventions/read-declared-commands.js +128 -0
  28. package/dist/eslint-plugin-syntax-analyze.d.ts +23 -0
  29. package/dist/eslint-plugin-syntax.d.ts +27 -0
  30. package/dist/eslint-plugin.d.ts +1211 -52
  31. package/dist/index.d.ts +56 -8
  32. package/dist/index.js +53 -6
  33. package/dist/language/command-resolution.d.ts +124 -0
  34. package/dist/language/command-resolution.js +120 -0
  35. package/dist/language/end-of-input-marker.d.ts +29 -0
  36. package/dist/language/end-of-input-marker.js +88 -0
  37. package/dist/language/line-breaks.d.ts +18 -0
  38. package/dist/language/line-breaks.js +22 -0
  39. package/dist/language/lint-view-types.d.ts +104 -0
  40. package/dist/language/lint-view-types.js +1 -0
  41. package/dist/language/non-shell-file-guard.d.ts +35 -0
  42. package/dist/language/non-shell-file-guard.js +59 -0
  43. package/dist/language/selector-classes.d.ts +0 -1
  44. package/dist/language/selector-classes.js +0 -1
  45. package/dist/language/shell-language-options.d.ts +7 -1
  46. package/dist/language/shell-language-options.js +4 -4
  47. package/dist/language/shell-language.d.ts +10 -2
  48. package/dist/language/shell-language.js +59 -8
  49. package/dist/language/shell-source-code.d.ts +342 -5
  50. package/dist/language/shell-source-code.js +559 -30
  51. package/dist/language/source-coordinates.d.ts +13 -2
  52. package/dist/language/source-coordinates.js +68 -14
  53. package/dist/language/syntax-view.d.ts +60 -39
  54. package/dist/language/syntax-view.js +200 -45
  55. package/dist/language/word-resolution.d.ts +4 -3
  56. package/dist/language/word-resolution.js +4 -3
  57. package/dist/rules/compat/create-compat-rule.d.ts +59 -0
  58. package/dist/rules/compat/create-compat-rule.js +65 -0
  59. package/dist/rules/compat/declaration-options.d.ts +19 -0
  60. package/dist/rules/compat/declaration-options.js +110 -0
  61. package/dist/rules/compat/no-append-all-redirect.d.ts +13 -0
  62. package/dist/rules/compat/no-append-all-redirect.js +73 -0
  63. package/dist/rules/compat/no-associative-arrays.d.ts +12 -0
  64. package/dist/rules/compat/no-associative-arrays.js +39 -0
  65. package/dist/rules/compat/no-case-attributes.d.ts +12 -0
  66. package/dist/rules/compat/no-case-attributes.js +63 -0
  67. package/dist/rules/compat/no-case-fallthrough.d.ts +12 -0
  68. package/dist/rules/compat/no-case-fallthrough.js +67 -0
  69. package/dist/rules/compat/no-case-modification.d.ts +35 -0
  70. package/dist/rules/compat/no-case-modification.js +86 -0
  71. package/dist/rules/compat/no-coproc.d.ts +12 -0
  72. package/dist/rules/compat/no-coproc.js +47 -0
  73. package/dist/rules/compat/no-declare-g.d.ts +12 -0
  74. package/dist/rules/compat/no-declare-g.js +59 -0
  75. package/dist/rules/compat/no-double-bracket.d.ts +12 -0
  76. package/dist/rules/compat/no-double-bracket.js +52 -0
  77. package/dist/rules/compat/no-epoch-variables.d.ts +12 -0
  78. package/dist/rules/compat/no-epoch-variables.js +57 -0
  79. package/dist/rules/compat/no-fd-variable.d.ts +12 -0
  80. package/dist/rules/compat/no-fd-variable.js +48 -0
  81. package/dist/rules/compat/no-globstar.d.ts +12 -0
  82. package/dist/rules/compat/no-globstar.js +39 -0
  83. package/dist/rules/compat/no-lastpipe-semantics.d.ts +12 -0
  84. package/dist/rules/compat/no-lastpipe-semantics.js +53 -0
  85. package/dist/rules/compat/no-let.d.ts +12 -0
  86. package/dist/rules/compat/no-let.js +62 -0
  87. package/dist/rules/compat/no-mapfile.d.ts +13 -0
  88. package/dist/rules/compat/no-mapfile.js +122 -0
  89. package/dist/rules/compat/no-nameref.d.ts +12 -0
  90. package/dist/rules/compat/no-nameref.js +44 -0
  91. package/dist/rules/compat/no-negative-array-index.d.ts +13 -0
  92. package/dist/rules/compat/no-negative-array-index.js +123 -0
  93. package/dist/rules/compat/no-nounset-empty-array.d.ts +13 -0
  94. package/dist/rules/compat/no-nounset-empty-array.js +158 -0
  95. package/dist/rules/compat/no-parameter-transformation.d.ts +13 -0
  96. package/dist/rules/compat/no-parameter-transformation.js +123 -0
  97. package/dist/rules/compat/no-pipe-all.d.ts +13 -0
  98. package/dist/rules/compat/no-pipe-all.js +68 -0
  99. package/dist/rules/compat/no-printf-time-format.d.ts +12 -0
  100. package/dist/rules/compat/no-printf-time-format.js +99 -0
  101. package/dist/rules/compat/no-source.d.ts +13 -0
  102. package/dist/rules/compat/no-source.js +103 -0
  103. package/dist/rules/compat/no-test-v.d.ts +12 -0
  104. package/dist/rules/compat/no-test-v.js +280 -0
  105. package/dist/rules/compat/no-unicode-escape.d.ts +14 -0
  106. package/dist/rules/compat/no-unicode-escape.js +120 -0
  107. package/dist/rules/compat/no-wait-n.d.ts +12 -0
  108. package/dist/rules/compat/no-wait-n.js +73 -0
  109. package/dist/rules/compat/operator-range.d.ts +10 -0
  110. package/dist/rules/compat/operator-range.js +15 -0
  111. package/dist/rules/compat/posix-absent-command.d.ts +12 -0
  112. package/dist/rules/compat/posix-absent-command.js +134 -0
  113. package/dist/rules/compat/shopt-options.d.ts +9 -0
  114. package/dist/rules/compat/shopt-options.js +63 -0
  115. package/dist/rules/compat/version-gates.d.ts +76 -0
  116. package/dist/rules/compat/version-gates.js +197 -0
  117. package/dist/rules/create-rule.d.ts +91 -0
  118. package/dist/rules/create-rule.js +51 -0
  119. package/dist/rules/create-wrapper-rule.d.ts +166 -0
  120. package/dist/rules/create-wrapper-rule.js +407 -0
  121. package/dist/rules/define-shell-rule.d.ts +184 -0
  122. package/dist/rules/define-shell-rule.js +56 -0
  123. package/dist/rules/index.d.ts +26 -0
  124. package/dist/rules/index.js +88 -0
  125. package/dist/rules/max-script-complexity.d.ts +28 -0
  126. package/dist/rules/max-script-complexity.js +130 -0
  127. package/dist/rules/no-dynamic-invocation.d.ts +35 -0
  128. package/dist/rules/no-dynamic-invocation.js +117 -0
  129. package/dist/rules/no-eval.d.ts +13 -0
  130. package/dist/rules/no-eval.js +89 -0
  131. package/dist/rules/no-masked-exit-status.d.ts +21 -0
  132. package/dist/rules/no-masked-exit-status.js +667 -0
  133. package/dist/rules/no-secret-env-reads.d.ts +24 -0
  134. package/dist/rules/no-secret-env-reads.js +133 -0
  135. package/dist/rules/no-secret-flags.d.ts +32 -0
  136. package/dist/rules/no-secret-flags.js +162 -0
  137. package/dist/rules/no-unchecked-cd.d.ts +44 -0
  138. package/dist/rules/no-unchecked-cd.js +250 -0
  139. package/dist/rules/no-undeclared-commands.d.ts +20 -0
  140. package/dist/rules/no-undeclared-commands.js +115 -0
  141. package/dist/rules/no-unused-declarations.d.ts +23 -0
  142. package/dist/rules/no-unused-declarations.js +259 -0
  143. package/dist/rules/prefer-local.d.ts +16 -0
  144. package/dist/rules/prefer-local.js +249 -0
  145. package/dist/rules/quote-expansions.d.ts +13 -0
  146. package/dist/rules/quote-expansions.js +229 -0
  147. package/dist/rules/require-network-wrapper.d.ts +40 -0
  148. package/dist/rules/require-network-wrapper.js +244 -0
  149. package/dist/rules/require-shebang.d.ts +29 -0
  150. package/dist/rules/require-shebang.js +101 -0
  151. package/dist/rules/require-strict-mode.d.ts +52 -0
  152. package/dist/rules/require-strict-mode.js +177 -0
  153. package/dist/rules/require-tty-guard.d.ts +13 -0
  154. package/dist/rules/require-tty-guard.js +289 -0
  155. package/dist/rules/resolve-declaration-convention.d.ts +96 -0
  156. package/dist/rules/resolve-declaration-convention.js +109 -0
  157. package/dist/rules/resolve-network-wrapper.d.ts +19 -0
  158. package/dist/rules/resolve-network-wrapper.js +116 -0
  159. package/dist/rules/set-options.d.ts +109 -0
  160. package/dist/rules/set-options.js +201 -0
  161. package/dist/rules/shebang-interpreter.d.ts +18 -0
  162. package/dist/rules/shebang-interpreter.js +81 -0
  163. package/dist/rules/shebang-matches-dialect.d.ts +12 -0
  164. package/dist/rules/shebang-matches-dialect.js +69 -0
  165. package/dist/rules/transparent-wrappers.d.ts +52 -0
  166. package/dist/rules/transparent-wrappers.js +99 -0
  167. package/dist/rules/user-pattern.d.ts +63 -0
  168. package/dist/rules/user-pattern.js +184 -0
  169. package/dist/syntax/analyze.d.ts +19 -0
  170. package/dist/syntax/analyze.js +21 -0
  171. package/dist/syntax/index.d.ts +23 -0
  172. package/dist/syntax/index.js +25 -0
  173. package/dist/version.d.ts +0 -1
  174. package/dist/version.js +0 -1
  175. package/docs/compat.md +347 -0
  176. package/docs/custom-rule-tutorial.md +780 -34
  177. package/docs/declaration-conventions.md +478 -0
  178. package/docs/examples/audit-wrapper-pack.mjs +15 -0
  179. package/docs/examples/dep-line-convention.mjs +22 -0
  180. package/docs/examples/eslint.config.mjs +3 -2
  181. package/docs/examples/no-and-or-as-if-rule.mjs +61 -0
  182. package/docs/examples/no-backticks-rule-tester.mjs +106 -0
  183. package/docs/examples/no-backticks-rule.mjs +60 -0
  184. package/docs/examples/no-curl-in-functions-rule.mjs +53 -0
  185. package/docs/examples/no-function-keyword-rule.mjs +59 -0
  186. package/docs/examples/no-listed-commands-rule.ts +63 -0
  187. package/docs/examples/no-rm-rf-rule.mjs +26 -15
  188. package/docs/examples/no-unquoted-rm-variable-rule.ts +54 -0
  189. package/docs/examples/sidecar-convention.mjs +38 -0
  190. package/docs/examples/sidecar-snapshot-convention.mjs +26 -0
  191. package/docs/examples/sorted-declarations-rule.mjs +52 -0
  192. package/docs/reading-nodes.md +159 -0
  193. package/docs/rules/README.md +78 -0
  194. package/docs/rules/_template.md +83 -0
  195. package/docs/rules/compat/no-append-all-redirect.md +87 -0
  196. package/docs/rules/compat/no-associative-arrays.md +95 -0
  197. package/docs/rules/compat/no-case-attributes.md +82 -0
  198. package/docs/rules/compat/no-case-fallthrough.md +90 -0
  199. package/docs/rules/compat/no-case-modification.md +74 -0
  200. package/docs/rules/compat/no-coproc.md +76 -0
  201. package/docs/rules/compat/no-declare-g.md +80 -0
  202. package/docs/rules/compat/no-double-bracket.md +72 -0
  203. package/docs/rules/compat/no-epoch-variables.md +79 -0
  204. package/docs/rules/compat/no-fd-variable.md +78 -0
  205. package/docs/rules/compat/no-globstar.md +74 -0
  206. package/docs/rules/compat/no-lastpipe-semantics.md +73 -0
  207. package/docs/rules/compat/no-let.md +78 -0
  208. package/docs/rules/compat/no-mapfile.md +118 -0
  209. package/docs/rules/compat/no-nameref.md +81 -0
  210. package/docs/rules/compat/no-negative-array-index.md +87 -0
  211. package/docs/rules/compat/no-nounset-empty-array.md +103 -0
  212. package/docs/rules/compat/no-parameter-transformation.md +94 -0
  213. package/docs/rules/compat/no-pipe-all.md +85 -0
  214. package/docs/rules/compat/no-printf-time-format.md +75 -0
  215. package/docs/rules/compat/no-source.md +98 -0
  216. package/docs/rules/compat/no-test-v.md +82 -0
  217. package/docs/rules/compat/no-unicode-escape.md +91 -0
  218. package/docs/rules/compat/no-wait-n.md +85 -0
  219. package/docs/rules/max-script-complexity.md +109 -0
  220. package/docs/rules/no-dynamic-invocation.md +139 -0
  221. package/docs/rules/no-eval.md +106 -0
  222. package/docs/rules/no-masked-exit-status.md +239 -0
  223. package/docs/rules/no-secret-env-reads.md +173 -0
  224. package/docs/rules/no-secret-flags.md +169 -0
  225. package/docs/rules/no-unchecked-cd.md +146 -0
  226. package/docs/rules/no-undeclared-commands.md +141 -0
  227. package/docs/rules/no-unused-declarations.md +216 -0
  228. package/docs/rules/prefer-local.md +221 -0
  229. package/docs/rules/quote-expansions.md +157 -0
  230. package/docs/rules/require-network-wrapper.md +171 -0
  231. package/docs/rules/require-shebang.md +157 -0
  232. package/docs/rules/require-strict-mode.md +253 -0
  233. package/docs/rules/require-tty-guard.md +128 -0
  234. package/docs/rules/shebang-matches-dialect.md +120 -0
  235. package/docs/selectors.md +2 -2
  236. package/docs/shellcheck.md +86 -0
  237. package/docs/wrapper-rules.md +206 -0
  238. package/package.json +26 -20
  239. package/dist/index.d.ts.map +0 -1
  240. package/dist/index.js.map +0 -1
  241. package/dist/language/selector-classes.d.ts.map +0 -1
  242. package/dist/language/selector-classes.js.map +0 -1
  243. package/dist/language/shell-language-options.d.ts.map +0 -1
  244. package/dist/language/shell-language-options.js.map +0 -1
  245. package/dist/language/shell-language.d.ts.map +0 -1
  246. package/dist/language/shell-language.js.map +0 -1
  247. package/dist/language/shell-source-code.d.ts.map +0 -1
  248. package/dist/language/shell-source-code.js.map +0 -1
  249. package/dist/language/source-coordinates.d.ts.map +0 -1
  250. package/dist/language/source-coordinates.js.map +0 -1
  251. package/dist/language/syntax-view.d.ts.map +0 -1
  252. package/dist/language/syntax-view.js.map +0 -1
  253. package/dist/language/word-resolution.d.ts.map +0 -1
  254. package/dist/language/word-resolution.js.map +0 -1
  255. package/dist/version.d.ts.map +0 -1
  256. package/dist/version.js.map +0 -1
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,61 @@ 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-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.
24
39
 
40
+ ## CommonJS config
41
+
42
+ In a package without `"type": "module"` (the ESLint default), `eslint.config.js` is CommonJS, as
43
+ is `eslint.config.cjs` in any package. `@eslint-sh/eslint-plugin` is ESM-only, so
44
+ `require('@eslint-sh/eslint-plugin')` returns the module namespace and the plugin object is its
45
+ `.default`. Named exports such as `commentPragma` are ordinary properties of the `require` result.
46
+ Without `.default`, `sh.configs` is `undefined` and ESLint fails with
47
+ `Cannot read properties of undefined (reading 'recommended')`.
48
+
49
+ ```js
50
+ // eslint.config.js (CommonJS package) or eslint.config.cjs
51
+ const { defineConfig } = require('eslint/config');
52
+ const sh = require('@eslint-sh/eslint-plugin').default;
53
+ // Named exports need no `.default`.
54
+ const { commentPragma } = require('@eslint-sh/eslint-plugin');
55
+
56
+ module.exports = defineConfig([
57
+ {
58
+ files: ['**/*.sh'],
59
+ extends: [sh.configs.recommended],
60
+ settings: { sh: { declarationConvention: commentPragma } },
61
+ rules: { 'sh/no-undeclared-commands': 'error' },
62
+ },
63
+ ]);
64
+ ```
65
+
25
66
  ## Public API
26
67
 
27
68
  - `plugin` (default export) — `{ meta: { name, version, namespace: "sh" }, languages: { sh:
28
- ShellLanguage }, rules: {} }`. `meta.version` always matches this package's `package.json`.
69
+ ShellLanguage }, rules, configs: { recommended, 'agent-authored', 'bash-3.2', 'posix-portable' } }`. `meta.version` always
70
+ matches this package's `package.json`. Each config is a single config object with no `files`
71
+ key (`bash-3.2` and `posix-portable` also set `languageOptions`); extend it under your own `files`:
72
+ `defineConfig({ files: ['**/*.sh'], extends: [sh.configs.recommended] })`.
29
73
  - `ShellLanguage` — the `@eslint/core` `Language` implementation: `fileType: "text"`,
30
74
  `lineStart`/`columnStart: 1`, `nodeTypeKey: "type"`, the consumer's schema-derived `visitorKeys`,
31
75
  `validateLanguageOptions`/`normalizeLanguageOptions` for the
@@ -36,7 +80,13 @@ ShellLanguage }, rules: {} }`. `meta.version` always matches this package's `pac
36
80
  - `ShellSourceCode` — extends `@eslint/plugin-kit`'s `TextSourceCodeBase`; implements `traverse()`
37
81
  (driven by the consumer's `visitorKeys`, yielding two-phase `VisitNodeStep`s), `getParent()`, and
38
82
  the disable-directive surface (`getDisableDirectives()`, `getInlineConfigNodes()`,
39
- `applyInlineConfig()`).
83
+ `applyInlineConfig()`). Rule authors get helpers that delegate to `sh-ast/analyze`, so every rule
84
+ gets the same answer: `resolveWordText(word, options?)` (a word's literal text, or `null` if
85
+ `resolveWord` does not report it static), `getCommandName(callExpr)` (the static command word,
86
+ without following wrappers), and `resolveInvokedCommand(callExpr, { transparentWrappers? })` (the
87
+ full `Argv0Resolution` once wrappers such as `env`, `sudo`, and `exec` are followed, including the
88
+ wrapper `chain` and the `effective` command or the reason it is unknowable). The `dialect` and
89
+ `shellVersion` accessors return the file's normalized language options.
40
90
  - `ShellLanguageOptions` / `NormalizedShellLanguageOptions` / `ShellLanguageOptionsJSON` — the
41
91
  `languageOptions` types (`dialect`, `shellVersion`, `directivePrefix`). `directivePrefix` is the
42
92
  prefix directive/inline-config comments must start with — e.g. the default `"eslint"` recognizes
@@ -45,20 +95,176 @@ ShellLanguage }, rules: {} }`. `meta.version` always matches this package's `pac
45
95
  - `ShellNode` / `ShellFile` / `ShellComment` / `ShellPosition` — the plugin's lint view: lowercase
46
96
  fields, UTF-16 ranges, and one-based UTF-16 locations, independent of the raw parser syntax.
47
97
  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.
98
+ projecting the view does not run live bounds operations on every syntax subtree. `ShellNode` is
99
+ the union of every node type, so narrowing on `node.type` gives that node's fields, typed, with
100
+ no cast. See [Reading nodes](./docs/reading-nodes.md).
101
+ - `ShellNodeOf` / `ShellNodeType` / `ShellNodeMap` / `ShellNodeField` — the types behind the union.
102
+ `ShellNodeOf<'Stmt'>` is one node type by name, `ShellNodeType` is every name, and
103
+ `ShellNodeMap` maps one to the other. They are computed from `sh-ast`'s generated typings, so
104
+ they follow the pinned mvdan/sh version. `ShellNodeField<T>` is the type of one node field
105
+ (`ShellNodeField<ShellNodeOf<'Stmt'>['cmd']>`), for a helper that takes or returns field values.
106
+ - `ShellCommandSite` / `CommandContext` / `ResolveArgv0Options` — one command in a file as `getCommandSites()` reports it:
107
+ its `CallExpr` node (`node`), the `context` path of enclosing constructs from the file root
108
+ (each frame a `CommandContext`, discriminated by `kind`: `and`, `or`, `pipeline`, `subshell`,
109
+ `cmdSubst`, …), its `argv0` and `argv` as `WordResolution`s, and `resolveInvokedCommand()`,
110
+ which follows wrappers. `ResolveArgv0Options` is that method's option bag (and
111
+ `ShellSourceCode.resolveInvokedCommand`'s): `transparentWrappers` replaces the wrapper table
112
+ followed.
113
+ - `WordResolution` — what `resolveWord` and a site's `argv` words report: `{ static: true, text }`
114
+ when the word's value is known without running the script, or `{ static: false, reason }` when
115
+ it is not (`reason` is `'expansion'`, `'tilde'`, `'glob'`, `'brace'`, `'locale'` or
116
+ `'unsupported'`; a new reason can appear in a minor release, so keep a `default` branch). Check
117
+ `static` before reading `text`.
118
+ - `ShellDialect` — the `dialect` language option's values: `'bash'`, `'posix'`, `'mksh'`,
119
+ `'bats'` and `'zsh'`.
120
+ - `Argv0Resolution` — the type `ShellSourceCode.resolveInvokedCommand` returns, re-exported from
121
+ `sh-ast/analyze`.
49
122
  - `resolveWord` — adapts a parsed lint Word to `sh-ast`'s word-resolution helper, so a project that installs only
50
123
  this package can write custom rules that resolve static shell words (e.g. the `no-rm-rf` example
51
124
  in the [custom-rule tutorial](./docs/custom-rule-tutorial.md)) without a separate `sh-ast`
52
125
  install. Fabricated nodes are rejected because they have no original syntax association.
126
+ - `DeclarationConvention` / `DeclaredCommand` / `commentPragma` — the extension point for how a
127
+ project declares the external commands its scripts use, bound with
128
+ `settings: { sh: { declarationConvention } }`. `commentPragma` is the reference convention,
129
+ reading `# uses: jq curl` header comments. See [declaration
130
+ conventions](./docs/declaration-conventions.md).
131
+ - `ScriptDeclaredCommand` / `FileDeclaredCommand` / `DescribedDeclaredCommand` / `LintedFile` — the
132
+ three kinds of `DeclaredCommand`, by where a declaration lives (in the script, in another file
133
+ such as a sidecar, or in a source described in words, such as a config list or a registry
134
+ snapshot), and the
135
+ linted file a convention is told about so it can find a per-script sidecar. See [declarations
136
+ outside the script](./docs/declaration-conventions.md#declarations-outside-the-script).
137
+ - `readDeclaredCommands` / `DeclarationReadContext` — reads the bound convention's declarations
138
+ for the file a rule is linting, checked exactly as the built-in rules check them. See [reading
139
+ the convention in your own
140
+ rule](./docs/declaration-conventions.md#reading-the-convention-in-your-own-rule).
141
+ - `defineShellRule` / `ShellRule` / `ShellRuleSpec` / `ShellRuleMeta` / `ShellRuleContext` /
142
+ `ShellRuleVisitor` / `ShellListener` / `ShellTypedListeners` — the rule-authoring helper and its types. `defineShellRule({ meta, create })`
143
+ returns an ordinary ESLint rule with `meta.languages: ['sh/sh']` set, so ESLint refuses to run it
144
+ on another language; `context.sourceCode` is typed `ShellSourceCode`, a listener keyed by a node
145
+ type (`CallExpr`, `Word:exit`) receives that node type, and any other selector's listener receives
146
+ `ShellNode`. Writing `meta.languages` yourself is a type error. The built-in rules are made with
147
+ it too. `ShellListener<Node>` is the type of one listener, so a helper that builds listeners for
148
+ several rules can name its return type; `ShellTypedListeners` is the node-type-keyed half of
149
+ `ShellRuleVisitor`. See [Writing your own rules](#writing-your-own-rules).
150
+ - `resolveDeclarationConvention` / `DeclarationConventionBindingContext` — resolves the declaration
151
+ convention bound for a rule (its `declarationConvention` option, else
152
+ `settings.sh.declarationConvention`, else `null`) with the validation and error messages the
153
+ built-in consumers use, so a rule of your own can read the convention. See [declaration
154
+ conventions](./docs/declaration-conventions.md#reading-the-convention-in-your-own-rule).
155
+ - `createWrapperRule` / `WrapperRuleConfig` / `WrapperRuleSubcommands` / `WrapperSpec` — the
156
+ factory for "command X must be run through wrapper Y" rules a rule pack
157
+ binds to its own commands and wrapper. `sh/require-network-wrapper` is the built-in rule made
158
+ with it. See [wrapper rules](./docs/wrapper-rules.md).
159
+ - `WrapperRuleLocalInvocation` / `WrapperRuleLocalOperand` — the `localInvocations` entry of a
160
+ `createWrapperRule` config: invocations of a listed command that only act locally and are not
161
+ reported, such as `curl --version` or `git clone ./x`. See
162
+ [wrapper rules](./docs/wrapper-rules.md#local-only-invocations).
163
+
164
+ ## Faithful syntax: `@eslint-sh/eslint-plugin/syntax`
165
+
166
+ The root exports work on the plugin's lint view (`ShellNode`). A rule that edits or analyzes the
167
+ parser's own records needs `sh-ast` itself: `walk`, `parseSync`, `printSync` and `PrintOptions`,
168
+ the record types (`Node`, `Word`, `CallExpr`, `SyntaxPos`, `GoString`, …), and the analysis
169
+ helpers. Import them from this package's subpaths, not from `sh-ast`:
170
+
171
+ ```js
172
+ // View-aware helpers: take lint-view nodes.
173
+ import { resolveWord } from '@eslint-sh/eslint-plugin';
174
+ // sh-ast's root entry point: parse, walk, and print faithful records.
175
+ import { parseSync, printSync, walk } from '@eslint-sh/eslint-plugin/syntax';
176
+ // sh-ast/analyze: command enumeration and wrapper resolution over faithful records.
177
+ import {
178
+ DEFAULT_TRANSPARENT_WRAPPERS,
179
+ enumerateCommands,
180
+ } from '@eslint-sh/eslint-plugin/syntax/analyze';
181
+ ```
182
+
183
+ - `@eslint-sh/eslint-plugin/syntax` re-exports the complete public surface of `sh-ast`, and
184
+ `@eslint-sh/eslint-plugin/syntax/analyze` re-exports `sh-ast/analyze`. Both come from the
185
+ `sh-ast` this plugin depends on, the same module instance its language parses with, so you
186
+ don't install `sh-ast` yourself and records always match the parser that made them. Nothing is
187
+ wrapped or renamed; each name keeps `sh-ast`'s own contract and docs.
188
+ - The two `resolveWord`s differ. The root's takes a lint-view `ShellNode`; the one in
189
+ `…/syntax/analyze` takes a faithful `Node`. That is why the subpaths are kept apart from the
190
+ root.
191
+ - **Versioning.** `…/syntax` and `…/syntax/analyze` track this plugin's pinned `sh-ast` minor
192
+ (currently `sh-ast@^0.7.0`). Under 0.x, a different `sh-ast` minor is a breaking change, so
193
+ an `sh-ast` 0.x minor bump is a minor bump of this plugin, and the changelog entry names the
194
+ new `sh-ast` version.
53
195
 
54
196
  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.
197
+ the source's UTF-16 location. Failures without a valid syntax position use a 1:1 fallback. A
198
+ failure of the parser runtime itself (an abort, or a failure of the bridge to it) is not a
199
+ syntax error, and the file may be perfectly valid. It is reported at 1:1 as
200
+ `Parsing error: eslint-sh parser runtime failure (not a syntax error in this file): <detail>`.
201
+ ESLint adds the `Parsing error: ` prefix to every message a language returns from `parse`, and a
202
+ language cannot remove it, so look for the marker text `eslint-sh parser runtime failure` to tell
203
+ this apart from a real syntax error, which has no marker and sits at the offending position. Linting
204
+ the file again, or in a fresh process, usually gets past a runtime failure.
205
+
206
+ ### Known parser gaps
207
+
208
+ The parser is the upstream `sh-ast`, and it rejects three valid Bash forms. Each is reported as a
209
+ syntax error. Until the parser accepts them, write the workaround in the right-hand column.
210
+
211
+ | Valid Bash form | Workaround |
212
+ | ------------------------ | -------------------------- |
213
+ | `x=$((a \|\| b) \| c)` | `x=$( (a \|\| b) \| c )` |
214
+ | `x=(${y}[0])` | `word=${y}[0]; x=($word)` |
215
+ | `declare -A m=([a.b]=1)` | `declare -A m=(["a.b"]=1)` |
216
+
217
+ - `x=$((a || b) | c)`: the parser reads `$((` as the start of arithmetic. A space after `$(` makes
218
+ it a command substitution containing a subshell.
219
+ - `x=(${y}[0])`: inside an array literal, any element word that ends in an unquoted `[…]` is read
220
+ as an indexed element (`[0]=value`) and must be followed by `=`. It is not limited to a word
221
+ that starts with an expansion: `x=(a[0])`, `x=(a${y}[0])` and `x=("${y}"[0])` are rejected too
222
+ (a `[0]` that is meant as a glob character class). Building the word in its own statement keeps
223
+ the same splitting and globbing.
224
+ - An associative-array subscript containing a `.` that is not quoted: the parser reads the
225
+ subscript as arithmetic and rejects the dot ("floating point arithmetic is a zsh feature"). The
226
+ gap is wider than the `declare` form above. It also rejects `m[a.b]=1` and `${m[a.b]}`, which
227
+ parse once quoted: `m["a.b"]=1` and `${m["a.b"]}`.
56
228
 
57
229
  ## Writing your own rules
58
230
 
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
231
+ Build a rule with `defineShellRule`. It takes the `{ meta, create(context) }` object you would
232
+ write for any ESLint rule and returns an ESLint rule you register in a plugin's `rules`:
233
+
234
+ ```js
235
+ import { defineShellRule } from '@eslint-sh/eslint-plugin';
236
+
237
+ export default defineShellRule({
238
+ meta: {
239
+ type: 'problem',
240
+ schema: [],
241
+ messages: { noEval: 'Avoid `eval`; it runs a string as code.' },
242
+ },
243
+ create(context) {
244
+ return {
245
+ CallExpr(node) {
246
+ if (context.sourceCode.getCommandName(node) === 'eval') {
247
+ context.report({ node, messageId: 'noEval' });
248
+ }
249
+ },
250
+ };
251
+ },
252
+ });
253
+ ```
254
+
255
+ `defineShellRule` sets `meta.languages: ['sh/sh']`, the declaration that makes ESLint refuse to
256
+ run the rule on a file of another language (usually because a config entry is missing
257
+ `language: 'sh/sh'`) instead of letting it silently match nothing. In TypeScript,
258
+ `context.sourceCode` is `ShellSourceCode`, a listener keyed by a node type receives that node type
259
+ (`CallExpr(node)` reads `node.args` with no check), and `context.report`
260
+ accepts only the message ids in `meta.messages`, with no `@eslint/core` import of your own.
261
+
262
+ [Reading nodes](./docs/reading-nodes.md) is the field reference for the nodes your listeners
263
+ receive: narrow a `ShellNode` on `type` and read its fields, typed, with one worked rule.
264
+
265
+ The [custom-rule tutorial](./docs/custom-rule-tutorial.md) is a complete, CI-tested walkthrough: a
266
+ selector from the cookbook, a full rule with `context.report`, the flat-config wiring to run it,
267
+ fixes (and when a printed fix reflows code, so a position-based text edit is the better choice),
268
+ and a TypeScript rule. `docs/` ships with this package (see `package.json#files`), so the
63
269
  tutorial and its example files (`docs/examples/`) are available in every installed copy — not just
64
270
  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[];