@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
@@ -1,31 +1,384 @@
1
+ import { Argv0Resolution } from 'sh-ast/analyze';
2
+ import { CommandContext } from 'sh-ast/analyze';
3
+ import type { ConfigObject } from '@eslint/core';
1
4
  import type { Directive } from '@eslint/core';
5
+ import type { Expansion } from 'sh-ast';
2
6
  import type { File as File_2 } from '@eslint/core';
3
7
  import type { FileProblem } from '@eslint/core';
4
8
  import type { Language } from '@eslint/core';
9
+ import type { Node } from 'sh-ast';
5
10
  import type { OkParseResult } from '@eslint/core';
6
11
  import type { ParseResult } from '@eslint/core';
12
+ import type { PrintOptions } from 'sh-ast';
13
+ import type { Replace } from 'sh-ast';
14
+ import { ResolveArgv0Options } from 'sh-ast/analyze';
7
15
  import type { ResolveWordOptions } from 'sh-ast/analyze';
16
+ import type { RuleContext } from '@eslint/core';
17
+ import { RuleDefinition } from '@eslint/core';
18
+ import { RuleDefinitionTypeOptions } from '@eslint/core';
8
19
  import type { RulesConfig } from '@eslint/core';
20
+ import type { RulesMeta } from '@eslint/core';
21
+ import type { RuleTextEdit } from '@eslint/core';
22
+ import type { RuleTextEditor } from '@eslint/core';
23
+ import type { SettingsConfig } from '@eslint/core';
24
+ import type { Slice } from 'sh-ast';
9
25
  import type { SourceLocation } from '@eslint/core';
26
+ import type { SyntaxPos } from 'sh-ast';
10
27
  import { TextSourceCodeBase } from '@eslint/plugin-kit';
11
28
  import type { TraversalStep } from '@eslint/core';
12
- import type { WordResolution } from 'sh-ast/analyze';
29
+ import { WordResolution } from 'sh-ast/analyze';
30
+ import { WrapperSpec } from 'sh-ast/analyze';
31
+
32
+ export { Argv0Resolution }
33
+
34
+ export { CommandContext }
35
+
36
+ /**
37
+ * The reference {@link DeclarationConvention}: a script declares the external
38
+ * commands it uses in `# uses:` comments in its header.
39
+ *
40
+ * ```sh
41
+ * #!/usr/bin/env bash
42
+ * # uses: jq curl
43
+ * # uses: git
44
+ * set -euo pipefail
45
+ * ```
46
+ *
47
+ * declares `jq`, `curl`, and `git`. The header is the run of blank and
48
+ * comment-only lines at the top of the file; a `# uses:` comment after the
49
+ * first command is not a declaration. Several pragma lines accumulate, names
50
+ * are whitespace-separated, and each declaration is `kind: 'script'` with a
51
+ * `range` that covers exactly that name. A word starting with `#` ends the list and begins a trailing
52
+ * remark: `# uses: jq # for json` declares only `jq`.
53
+ *
54
+ * Its `removeDeclaration` hook deletes one name and the whitespace that
55
+ * separated it, or the whole pragma line (with its `\n` or `\r\n`) when that
56
+ * name was the line's last.
57
+ *
58
+ * Its `declarationHint` hook tells a person exactly what to add: the line
59
+ * `# uses: curl`, on a line below the shebang, or as the first line of the
60
+ * file when there is no shebang. A name a pragma cannot express (one with
61
+ * whitespace, or starting with `#`) gets no hint.
62
+ *
63
+ * Bind it with `settings: { sh: { declarationConvention: commentPragma } }`.
64
+ *
65
+ * @public
66
+ */
67
+ export declare const commentPragma: Readonly<Required<DeclarationConvention>>;
68
+
69
+ /**
70
+ * Creates a rule that reports every listed command that is not run through
71
+ * `config.wrapper`, for a rule pack's "must be wrapped by X" policies:
72
+ *
73
+ * ```js
74
+ * import { createWrapperRule } from '@eslint-sh/eslint-plugin';
75
+ *
76
+ * export const requireAudit = createWrapperRule({
77
+ * commands: ['kubectl', 'terraform'],
78
+ * wrapper: 'audit-run',
79
+ * });
80
+ * ```
81
+ *
82
+ * The rule declares `meta.languages: ["sh/sh"]`, takes no options (the
83
+ * config is bound here), and reports with message id `unwrappedCommand`,
84
+ * naming the command and the wrapper. Its `meta.docs.url` and
85
+ * `meta.docs.description` default to this package's wrapper-rules page and a
86
+ * sentence naming the policy; `config.docs` overrides either. A listed
87
+ * command is wrapped when the wrapper encloses it in `sh-ast/analyze`'s
88
+ * transparent-wrapper chain, so `audit-run sudo kubectl apply` is wrapped.
89
+ * Command and wrapper words written as paths match by basename
90
+ * (`/usr/bin/kubectl`). Commands inside `$(…)`, functions, and pipelines are
91
+ * checked; a command whose name or subcommand is not static is not reported.
92
+ *
93
+ * @param config - The commands, the wrapper they must be run through, any
94
+ * subcommand restrictions, and optional `meta.docs` overrides.
95
+ * @returns A {@link ShellRule}: an ESLint rule for the `sh/sh` language,
96
+ * typed so a pack's own declarations need no `@eslint/core` import to name it.
97
+ * @throws TypeError when `config` is malformed (no `commands` array, an empty
98
+ * wrapper name, a subcommand restriction without `names`, or an empty or
99
+ * non-string `docs.url` / `docs.description`).
100
+ *
101
+ * @public
102
+ */
103
+ export declare function createWrapperRule(config: WrapperRuleConfig): ShellRule<[], 'unwrappedCommand'>;
104
+
105
+ /**
106
+ * How a project declares the external commands its shell scripts depend on.
107
+ *
108
+ * Implement this to teach the plugin a project's own declaration syntax, then
109
+ * bind it with `settings: { sh: { declarationConvention: myConvention } }`.
110
+ * Until a convention is bound, rules that consume one report nothing.
111
+ *
112
+ * Bind it through `settings`, not through a rule's options: ESLint copies
113
+ * rule options with `structuredClone`, which cannot copy functions. Define it
114
+ * as a plain object whose methods are its own properties, and bind it in one
115
+ * config entry: when two matching config entries both set it, ESLint merges
116
+ * the two objects property by property, which drops methods inherited from a
117
+ * class prototype and can mix methods from two different conventions.
118
+ *
119
+ * One convention object serves every file its config entry matches, so its
120
+ * answer must depend only on its arguments and on data it holds, never on
121
+ * which file it saw before. ESLint's `--cache` sees a convention's own
122
+ * enumerable data properties (they are part of the config it hashes) but not
123
+ * files the convention reads while linting.
124
+ *
125
+ * {@link commentPragma} is the reference implementation.
126
+ *
127
+ * @public
128
+ */
129
+ export declare interface DeclarationConvention {
130
+ /**
131
+ * Returns every command the linted file declares, in source order. A name
132
+ * declared twice appears twice, each with its own source. `file` names the
133
+ * file being linted, for a convention whose declarations live beside it;
134
+ * a convention that reads only `sourceCode` can leave it out.
135
+ */
136
+ declaredCommands(sourceCode: ShellSourceCode, file: LintedFile): DeclaredCommand[];
137
+ /**
138
+ * Optional edit hook: returns the fix that deletes the single stale
139
+ * declaration `declared` (an in-script entry previously returned by
140
+ * {@link DeclarationConvention.declaredCommands} for the same
141
+ * `sourceCode`), built with the rule's `fixer`. Returns `null` when that
142
+ * declaration cannot be removed safely.
143
+ *
144
+ * It is only ever called with a {@link ScriptDeclaredCommand}: a fix edits
145
+ * only the linted script, and no edit to the script removes a declaration
146
+ * kept anywhere else.
147
+ *
148
+ * A convention without this hook still reports stale declarations; those
149
+ * reports simply carry no autofix.
150
+ */
151
+ removeDeclaration?(sourceCode: ShellSourceCode, declared: ScriptDeclaredCommand, fixer: RuleTextEditor): RuleTextEdit | RuleTextEdit[] | null;
152
+ /**
153
+ * Optional hint hook: returns one short sentence that tells a person how to
154
+ * declare `command`, with text they can copy. {@link commentPragma}'s hint says to
155
+ * add `# uses: curl` on a line below the shebang.
156
+ * `sh/no-undeclared-commands` appends it to its report. Following the
157
+ * sentence literally must make that report go away.
158
+ *
159
+ * Return `null` when this convention cannot declare `command` as written
160
+ * (for example, a name its syntax cannot express). The report then falls
161
+ * back to a generic sentence, as it does for a convention without this
162
+ * hook. Anything other than `null` must be a non-empty single-line string.
163
+ */
164
+ declarationHint?(command: string, sourceCode: ShellSourceCode, file: LintedFile): string | null;
165
+ }
166
+
167
+ /**
168
+ * The slice of an ESLint rule context that binding resolution reads: the rule
169
+ * id (for error messages), its configured options, and the shared settings.
170
+ * Any rule `context` satisfies it, so a rule passes its own `context`.
171
+ *
172
+ * @public
173
+ */
174
+ export declare interface DeclarationConventionBindingContext {
175
+ /** The rule id as configured (`pack/my-rule`), named in an option-binding error. */
176
+ readonly id: string;
177
+ /** The rule's configured options; the first may carry `declarationConvention`. */
178
+ readonly options: readonly unknown[];
179
+ /** The shared settings; `settings.sh.declarationConvention` is the usual binding. */
180
+ readonly settings: SettingsConfig;
181
+ }
182
+
183
+ /**
184
+ * What {@link readDeclaredCommands} reads from a rule's context: the linted
185
+ * file and its source code. Any shell rule's `context` satisfies it.
186
+ *
187
+ * @public
188
+ */
189
+ export declare interface DeclarationReadContext extends LintedFile {
190
+ /** The source code of the file being linted. */
191
+ readonly sourceCode: ShellSourceCode;
192
+ }
193
+
194
+ /**
195
+ * One command a script declares it depends on, as reported by a
196
+ * {@link DeclarationConvention}. Where the declaration lives is part of what
197
+ * it is, so it is one of three kinds, told apart by the required `kind`:
198
+ * written in the script ({@link ScriptDeclaredCommand}), in another file
199
+ * ({@link FileDeclaredCommand}), or in a source that can only be described in
200
+ * words ({@link DescribedDeclaredCommand}). Only the script kind has a `range`, so
201
+ * narrow on `kind` before reading it.
202
+ *
203
+ * @public
204
+ */
205
+ export declare type DeclaredCommand = ScriptDeclaredCommand | FileDeclaredCommand | DescribedDeclaredCommand;
206
+
207
+ /**
208
+ * Builds a shell rule from its spec, setting `meta.languages` to
209
+ * exactly `["sh/sh"]` so ESLint refuses to run the rule on any other language.
210
+ * Everything else in `meta`, and `create`, is kept as written; message ids
211
+ * and option types are inferred from `meta` or may be given explicitly
212
+ * (`defineShellRule<[], 'found'>(…)`).
213
+ *
214
+ * The language fields are written last, so a JavaScript caller that bypasses
215
+ * the types and supplies its own `languages` or `language` still gets
216
+ * exactly `["sh/sh"]` (and no `language`). A JavaScript caller that omits
217
+ * `meta`, which ESLint allows for a rule, is treated as having written
218
+ * `meta: {}`.
219
+ *
220
+ * @example
221
+ * ```ts
222
+ * import { defineShellRule } from '@eslint-sh/eslint-plugin';
223
+ *
224
+ * export const noEval = defineShellRule({
225
+ * meta: { type: 'problem', schema: [], messages: { evalUsed: 'Avoid eval.' } },
226
+ * create(context) {
227
+ * return {
228
+ * CallExpr(node) {
229
+ * if (context.sourceCode.getCommandName(node) === 'eval') {
230
+ * context.report({ node, messageId: 'evalUsed' });
231
+ * }
232
+ * },
233
+ * };
234
+ * },
235
+ * });
236
+ * ```
237
+ *
238
+ * @public
239
+ */
240
+ export declare function defineShellRule<RuleOptions extends unknown[] = [], MessageIds extends string = string>(spec: ShellRuleSpec<RuleOptions, MessageIds>): ShellRule<RuleOptions, MessageIds>;
241
+
242
+ /**
243
+ * A declaration whose source can only be described in words: there is no
244
+ * range in the script and no path to point at, only a phrase that tells a
245
+ * person where it comes from, such as a list written in the ESLint config, a
246
+ * convention's own constant, or a snapshot of a tool registry. A report about
247
+ * it names the source by `description` and carries no fix.
248
+ *
249
+ * @public
250
+ */
251
+ export declare interface DescribedDeclaredCommand {
252
+ /** Says the declaration's source is described in words, by `description`. */
253
+ readonly kind: 'described';
254
+ /** The declared command name, exactly as it would be invoked. */
255
+ readonly name: string;
256
+ /**
257
+ * Names the source for a person who wants to change the declaration, as a
258
+ * phrase that reads after "declared in": "the web image list in
259
+ * eslint.config.js", "the tool registry snapshot of 2026-11-01". It must be a
260
+ * non-empty single line.
261
+ */
262
+ readonly description: string;
263
+ }
264
+
265
+ /**
266
+ * A declaration written in a file other than the linted script: a per-script
267
+ * sidecar, or a manifest several scripts share. A report about it names the
268
+ * file, and carries no fix, since a fix can only edit the linted script.
269
+ *
270
+ * @public
271
+ */
272
+ export declare interface FileDeclaredCommand {
273
+ /** Says the declaration is in another file. */
274
+ readonly kind: 'file';
275
+ /** The declared command name, exactly as it would be invoked. */
276
+ readonly name: string;
277
+ /**
278
+ * Where a person or a tool should look for the declaration: a path
279
+ * relative to {@link LintedFile.cwd}, or an absolute path. It must be a
280
+ * non-empty single line. Rules name it in their reports as given; they never
281
+ * read it.
282
+ */
283
+ readonly path: string;
284
+ /** The 1-based line of `path` that declares the command, when the convention knows it. */
285
+ readonly line?: number;
286
+ }
287
+
288
+ /**
289
+ * The file ESLint is linting, as its rule context names it. A
290
+ * {@link DeclarationConvention} receives it so it can find declarations kept
291
+ * outside the script, such as a per-script sidecar file next to it.
292
+ *
293
+ * A rule's own `context` is a `LintedFile`, so a rule that calls
294
+ * `declaredCommands` passes `context`.
295
+ *
296
+ * @public
297
+ */
298
+ export declare interface LintedFile {
299
+ /**
300
+ * The name ESLint lints the code under (`context.filename`). For a code
301
+ * block a processor extracts from another file it is virtual
302
+ * (`README.md/0_0.sh`).
303
+ */
304
+ readonly filename: string;
305
+ /**
306
+ * The path on disk ESLint associates with the code
307
+ * (`context.physicalFilename`): the file it read, or the file a
308
+ * processor's code block came from. With `--stdin --stdin-filename`, it is
309
+ * the name given on the command line; the code came from standard input,
310
+ * and that file may not exist. Resolve sidecar paths from this one, and
311
+ * treat a missing sidecar as a normal case.
312
+ */
313
+ readonly physicalFilename: string;
314
+ /**
315
+ * ESLint's working directory (`context.cwd`). A relative
316
+ * {@link FileDeclaredCommand.path} is relative to it.
317
+ */
318
+ readonly cwd: string;
319
+ }
320
+
321
+ /**
322
+ * The result of {@link ShellLanguage.normalizeLanguageOptions}: `dialect` and
323
+ * `directivePrefix` are always resolved to concrete values (defaulted when
324
+ * the caller didn't specify them), and `toJSON()` provides the "custom
325
+ * serialization" ESLint's languages guide recommends
326
+ * (https://eslint.org/docs/latest/extend/languages, "normalizeLanguageOptions")
327
+ * so that config caching keyed off `JSON.stringify(languageOptions)` is
328
+ * stable irrespective of the input object's own key order — `JSON.stringify`
329
+ * calls `toJSON()` automatically when present.
330
+ *
331
+ * @public
332
+ */
333
+ export declare interface NormalizedShellLanguageOptions extends Record<string, unknown> {
334
+ readonly dialect: ShellDialect;
335
+ readonly directivePrefix: string;
336
+ readonly shellVersion?: string;
337
+ toJSON(): ShellLanguageOptionsJSON;
338
+ }
13
339
 
14
340
  /**
15
- * The `@eslint-sh/eslint-plugin` plugin object: `languages.sh` provides
341
+ * The `@eslint-sh/eslint-plugin` plugin object, this package's default
342
+ * export (`import sh from '@eslint-sh/eslint-plugin'`). `languages.sh` provides
16
343
  * `ShellLanguage` / `ShellSourceCode`, selected in a flat config via
17
344
  * `language: "sh/sh"` (`namespace: "sh"`). `meta.version` is read from this
18
345
  * package's own `package.json` at runtime — never hardcoded (see
19
346
  * `version.ts`).
20
347
  *
21
- * The empty `rules` registry lets consumers supply their own rules while
22
- * sharing parsing, selectors, source coordinates, and directive semantics.
348
+ * `rules` holds the plugin's built-in rules, each built with
349
+ * `defineShellRule` (so each declares `meta.languages: ["sh/sh"]`) and
350
+ * linking its docs page from `meta.docs.url`. Consumers can also supply their
351
+ * own rules, built with the same `defineShellRule`, while sharing parsing,
352
+ * selectors, source coordinates, and directive semantics.
353
+ *
354
+ * `configs.recommended` is the `sh/recommended` preset: a single config
355
+ * object carrying `plugins: { sh }` and `language: "sh/sh"` but no `files`,
356
+ * so it inherits the globs of the entry that extends it:
357
+ * `{ files: [...], extends: [sh.configs.recommended] }`. It enables the
358
+ * convention-free policy rules. The rules index at
359
+ * https://github.com/mike-north/eslint-sh/blob/main/packages/eslint-plugin/docs/rules/README.md
360
+ * describes preset membership and available autofixes.
361
+ * `configs['agent-authored']` is the strict `sh/agent-authored` preset for
362
+ * machine-written, human-reviewed scripts:
363
+ * every `recommended` rule plus the contract rules, all at `error`.
364
+ * `configs['bash-3.2']` and `configs['posix-portable']` are deployment-target
365
+ * packs: the first sets `languageOptions.shellVersion: "3.2"` and enables
366
+ * every version-gated `compat/*` rule; the second sets
367
+ * `languageOptions.dialect: "posix"`, enables the `compat/*` rules and the
368
+ * shebang/dialect check, and requires `set -eu`; it reports named constructs
369
+ * that fail on dash, not every non-POSIX construct, and leaves `local`,
370
+ * `echo -e` and the rest of shellcheck's SC3xxx catalog to shellcheck. Every
371
+ * shipped config is a
372
+ * single object with no `files`; the two packs also carry `languageOptions`,
373
+ * because the target they name is their meaning.
23
374
  *
24
375
  * Re-exported, unchanged, as the default export of the `eslint-sh` umbrella
25
376
  * package: importing this package or the umbrella package both yield the
26
377
  * same plugin object.
378
+ *
379
+ * @public
27
380
  */
28
- declare const _default: {
381
+ declare const plugin: {
29
382
  meta: {
30
383
  name: string;
31
384
  version: string;
@@ -34,57 +387,204 @@ declare const _default: {
34
387
  languages: {
35
388
  sh: ShellLanguage;
36
389
  };
37
- rules: {};
390
+ rules: Record<string, RuleDefinition<RuleDefinitionTypeOptions>>;
391
+ configs: {
392
+ recommended: ConfigObject;
393
+ "agent-authored": ConfigObject;
394
+ "bash-3.2": ConfigObject;
395
+ "posix-portable": ConfigObject;
396
+ };
38
397
  };
39
- export default _default;
398
+ export default plugin;
40
399
 
41
400
  /**
42
- * The result of {@link ShellLanguage.normalizeLanguageOptions}: `dialect` and
43
- * `directivePrefix` are always resolved to concrete values (defaulted when
44
- * the caller didn't specify them), and `toJSON()` provides the "custom
45
- * serialization" ESLint's languages guide recommends
46
- * (https://eslint.org/docs/latest/extend/languages, "normalizeLanguageOptions")
47
- * so that config caching keyed off `JSON.stringify(languageOptions)` is
48
- * stable irrespective of the input object's own key order — `JSON.stringify`
49
- * calls `toJSON()` automatically when present.
401
+ * Returns the declarations `convention` makes for the file `context` is
402
+ * linting, after checking each one against its kind. Call it from a rule's
403
+ * listener with the convention `resolveDeclarationConvention` returned and
404
+ * the rule's own `context`.
405
+ *
406
+ * The convention receives a frozen {@link LintedFile} holding only
407
+ * `filename`, `physicalFilename` and `cwd`, never the whole context, so it
408
+ * sees which file it is reading and nothing it could report through.
409
+ *
410
+ * Throws an `Error` that names the setting, starting
411
+ * `Invalid settings.sh.declarationConvention:`, and shows the offending value when
412
+ * `declaredCommands` does not return an array, or returns a declaration with
413
+ * a missing or unknown `kind`, without its kind's fields, or with a `range`
414
+ * outside the linted text.
415
+ *
416
+ * @example
417
+ * ```ts
418
+ * const convention = resolveDeclarationConvention(context);
419
+ * if (convention === null) return {};
420
+ * return {
421
+ * File() {
422
+ * for (const declared of readDeclaredCommands(convention, context)) {
423
+ * if (declared.kind === 'script') {
424
+ * // declared.range is an offset pair into context.sourceCode.text
425
+ * }
426
+ * }
427
+ * },
428
+ * };
429
+ * ```
50
430
  *
51
431
  * @public
52
432
  */
53
- export declare interface NormalizedShellLanguageOptions extends Record<string, unknown> {
54
- readonly dialect: ShellDialect;
55
- readonly directivePrefix: string;
56
- readonly shellVersion?: string;
57
- toJSON(): ShellLanguageOptionsJSON;
58
- }
433
+ export declare function readDeclaredCommands(convention: DeclarationConvention, context: DeclarationReadContext): DeclaredCommand[];
434
+
435
+ export { ResolveArgv0Options }
436
+
437
+ /**
438
+ * Resolves the declaration convention bound for the rule running in
439
+ * `context`: the rule's `declarationConvention` option, else
440
+ * `settings.sh.declarationConvention`, else `null` (the rule is inert).
441
+ *
442
+ * Throws an `Error` naming the offending setting when the configured value is
443
+ * present but does not implement `declaredCommands` (or has a non-function
444
+ * `removeDeclaration`). The message names where the value came from and what
445
+ * it was, for example one beginning
446
+ * `Invalid settings.sh.declarationConvention: expected a declaration convention`
447
+ * and ending `but got string "jq".` Call it from `create()`, so the error
448
+ * surfaces as ESLint's
449
+ * configuration message for the file being linted.
450
+ *
451
+ * A rule that receives `null` should register no listeners: with no
452
+ * convention bound, a convention consumer has nothing to check.
453
+ *
454
+ * @example
455
+ * ```ts
456
+ * import {
457
+ * defineShellRule,
458
+ * readDeclaredCommands,
459
+ * resolveDeclarationConvention,
460
+ * } from '@eslint-sh/eslint-plugin';
461
+ *
462
+ * export const maxDeclaredCommands = defineShellRule({
463
+ * meta: { schema: [], messages: { tooMany: 'Declare at most 5 commands.' } },
464
+ * create(context) {
465
+ * const convention = resolveDeclarationConvention(context);
466
+ * if (convention === null) return {};
467
+ * return {
468
+ * File(node) {
469
+ * // Validated declarations for the file `context` is linting.
470
+ * if (readDeclaredCommands(convention, context).length > 5) {
471
+ * context.report({ node, messageId: 'tooMany' });
472
+ * }
473
+ * },
474
+ * };
475
+ * },
476
+ * });
477
+ * ```
478
+ *
479
+ * @public
480
+ */
481
+ export declare function resolveDeclarationConvention(context: DeclarationConventionBindingContext): DeclarationConvention | null;
59
482
 
60
483
  /**
61
484
  * Resolves a parsed lint Word using the upstream-shaped syntax associated with it.
62
485
  * The view supplies locations and selector fields; sh-ast/analyze owns the word
63
486
  * interpretation. Fabricated or synthetic view nodes have no source syntax and
64
487
  * are rejected instead of being treated as known or unknown shell values.
488
+ * A static word that ends a line of a CRLF file resolves without the line
489
+ * terminator's carriage return, as it would on LF input.
65
490
  * @public
66
491
  */
67
- export declare function resolveWord(word: ShellNode, options?: ResolveWordOptions): WordResolution;
492
+ export declare function resolveWord(word: ShellNodeOf<'Word'>, options?: ResolveWordOptions): WordResolution;
68
493
 
69
- /** Directive processing uses the comment text without its leading shell hash. @public */
70
- export declare interface ShellComment extends ShellNode {
71
- /** Comment discriminator used by directive collection. */
72
- readonly type: 'Comment';
73
- /** Decoded comment content after the leading hash, preserving its whitespace. */
74
- readonly text: string;
494
+ /**
495
+ * A declaration written in the linted script's own text: the only kind an
496
+ * ESLint fix can edit, because a fix edits only the file being linted.
497
+ *
498
+ * @public
499
+ */
500
+ export declare interface ScriptDeclaredCommand {
501
+ /** Says the declaration is in the script. */
502
+ readonly kind: 'script';
503
+ /** The declared command name, exactly as it would be invoked (`jq`, `docker-compose`). */
504
+ readonly name: string;
505
+ /**
506
+ * The `[start, end)` source range of this declaration, as UTF-16 offsets
507
+ * into `sourceCode.text` (the same coordinates as every node's `range`),
508
+ * with `0 <= start <= end <= sourceCode.text.length`. Rules report a
509
+ * declaration at this range, so it should cover the text that declares
510
+ * this one name and nothing else.
511
+ */
512
+ readonly range: readonly [number, number];
75
513
  }
76
514
 
515
+ /**
516
+ * One command invocation in a linted shell file, in lint-view terms: an
517
+ * `sh-ast/analyze` `CommandSite` whose `node` is the `CallExpr` view node
518
+ * ESLint passes to rule visitors, so a rule can `context.report()` on it.
519
+ *
520
+ * The data comes from `sh-ast/analyze`'s `enumerateCommands` and is facts
521
+ * only: how the command is reached and what its words resolve to, never a
522
+ * verdict on whether running it is safe. A dynamic (`static: false`) `argv0`
523
+ * is a normal result.
524
+ *
525
+ * Read one through `ShellSourceCode.getCommandSites()`.
526
+ *
527
+ * Every rule linting a file sees the same sites, so each one is deeply
528
+ * frozen: the entry, its `context` array and every frame in it, its `argv`
529
+ * array, and every `WordResolution` in `argv` (`argv0` included). Writing to
530
+ * any of them throws in strict-mode code and is ignored otherwise, so one rule
531
+ * cannot change what another rule reads. The `node` is the shared lint view
532
+ * node, which, like every node ESLint passes to rules, is not frozen and must
533
+ * not be modified.
534
+ *
535
+ * @public
536
+ */
537
+ export declare interface ShellCommandSite {
538
+ /**
539
+ * The `CallExpr` view node for this command: the same object (`===`)
540
+ * ESLint passes to a `CallExpr` visitor, with `range` and `loc` in this
541
+ * file's UTF-16 coordinates.
542
+ */
543
+ readonly node: ShellNodeOf<'CallExpr'>;
544
+ /**
545
+ * The path from the file root to this command, outermost frame first,
546
+ * exactly as `sh-ast/analyze` reports it. For example, `date` in
547
+ * `f() { echo "$(date)"; }` has
548
+ * `[{ kind: 'function', name: 'f' }, { kind: 'cmdSubst' }]`.
549
+ */
550
+ readonly context: readonly CommandContext[];
551
+ /** The command word (`argv[0]`) resolved with `{ context: 'command-argument' }`. */
552
+ readonly argv0: WordResolution;
553
+ /**
554
+ * Every word of the command, `argv0` included, in argument order, each
555
+ * resolved with `{ context: 'command-argument' }`. Leading `VAR=val`
556
+ * assignments are not part of argv.
557
+ */
558
+ readonly argv: readonly WordResolution[];
559
+ /**
560
+ * Follows this command's argv0 through transparent wrappers (`env`, `sudo`,
561
+ * `nohup`, `exec`, …) and returns `sh-ast/analyze`'s `resolveArgv0`
562
+ * result. Same answer as
563
+ * `ShellSourceCode.resolveInvokedCommand(node, options)`; both read the
564
+ * same per-file index.
565
+ *
566
+ * @param options - Forwarded to `resolveArgv0` unchanged.
567
+ * `transparentWrappers` replaces `sh-ast/analyze`'s
568
+ * `DEFAULT_TRANSPARENT_WRAPPERS`; spread the defaults into the array to
569
+ * extend them instead.
570
+ * @returns A new result on every call. Its `chain` and `chainIndices`
571
+ * arrays belong to the caller. `chainIndices` gives each chain entry's
572
+ * position in this site's `argv`.
573
+ * @throws `ShAnalyzeInvalidWrapperSpecError` (from `sh-ast/analyze`) when
574
+ * `options.transparentWrappers` contains a malformed entry.
575
+ */
576
+ resolveInvokedCommand(options?: ResolveArgv0Options): Argv0Resolution;
577
+ }
578
+
579
+ /** Directive processing uses the comment text without its leading shell hash. @public */
580
+ export declare type ShellComment = ShellNodeOf<'Comment'>;
581
+
77
582
  /** Owns ESLint configuration labels and validates their consumer-facing meaning before parsing. */
78
583
  /** The dialect labels understood by ESLint configuration, mapped to upstream numeric variants when parsing. @public */
79
584
  export declare type ShellDialect = 'bash' | 'posix' | 'mksh' | 'bats' | 'zsh';
80
585
 
81
586
  /** The lint root has a statement collection even when upstream represents it as nil. @public */
82
- export declare interface ShellFile extends ShellNode {
83
- /** Root discriminator retained for selectors. */
84
- readonly type: 'File';
85
- /** Top-level statements in source order; nil upstream slices become an empty view. */
86
- readonly stmts: readonly ShellNode[];
87
- }
587
+ export declare type ShellFile = ShellNodeOf<'File'>;
88
588
 
89
589
  /**
90
590
  * `ShellLanguage` — the `@eslint/core` `Language` implementation for shell
@@ -144,6 +644,13 @@ export declare class ShellLanguage implements Language {
144
644
  * the `{ok: true, ast}` / `{ok: false, errors}` shape `Language.parse` is
145
645
  * required to return. Never throws — parse failures are reported as
146
646
  * fatal lint messages, per the `Language` contract.
647
+ *
648
+ * A file whose extension marks it as a known non-shell source type (for
649
+ * example `.js` or `.json`) is never shell-parsed: it fails with a single
650
+ * diagnostic explaining that a `language: 'sh/sh'` config entry without
651
+ * `files` reached it, and how to scope that entry. A source in which
652
+ * the parser silently drops text at a U+0080 fails at that character; a
653
+ * U+0080 the parser reads as an ordinary character does not.
147
654
  */
148
655
  parse(file: File_2, context: {
149
656
  languageOptions: ShellLanguageOptions;
@@ -157,7 +664,9 @@ export declare class ShellLanguage implements Language {
157
664
  * `createSourceCode` is called) — `directivePrefix` is therefore always
158
665
  * present, but `?? 'eslint'` stays as an explicit, honest fallback for
159
666
  * direct construction (e.g. tests that skip the `Language` normalization
160
- * step), mirroring `parse()`'s `dialect` default above.
667
+ * step), mirroring `parse()`'s `dialect` default above. `dialect` and
668
+ * `shellVersion` are forwarded so rules read the same normalized values
669
+ * through `sourceCode.dialect` / `sourceCode.shellVersion`.
161
670
  */
162
671
  createSourceCode(file: File_2, parseResult: OkParseResult<ShellFile>, context: {
163
672
  languageOptions: ShellLanguageOptions;
@@ -181,6 +690,13 @@ export declare class ShellLanguage implements Language {
181
690
  */
182
691
  export declare interface ShellLanguageOptions extends Record<string, unknown> {
183
692
  readonly dialect?: ShellDialect;
693
+ /**
694
+ * The Bash version (`MAJOR[.MINOR]`, such as `"3.2"`) the scripts must run
695
+ * on. The parser ignores it. The `compat/*` rules read it: unset means the
696
+ * newest version, versions compare numerically by major then minor, and
697
+ * under `dialect: "posix"` Bash-only features are unavailable at any
698
+ * version.
699
+ */
184
700
  readonly shellVersion?: string;
185
701
  /**
186
702
  * The prefix directive/inline-config comments must start with — e.g. the
@@ -217,24 +733,107 @@ export declare interface ShellLanguageOptionsJSON {
217
733
  }
218
734
 
219
735
  /**
220
- * A node in the consumer's lint view. Scalar/child fields use lowercase upstream
221
- * spellings; syntax fidelity belongs to the associated upstream tree. Auxiliary
222
- * view nodes may have spans derived from children and are not upstream Nodes.
736
+ * A listener for a node of type `Node`. It is declared with method syntax on
737
+ * purpose: method parameters are checked bivariantly, which lets a listener
738
+ * for one node type sit alongside the union-typed listener every other
739
+ * selector key gets (see {@link ShellRuleVisitor}) without a type error.
740
+ *
741
+ * Exported so a helper that builds listeners for several rules can name the
742
+ * type it returns; `node` is the matched node and `parent` its parent
743
+ * (`undefined` for the root `File`).
744
+ *
223
745
  * @public
224
746
  */
225
- export declare interface ShellNode {
226
- /** Original syntax kind retained as the discriminator for lint selectors. */
227
- readonly type: string;
228
- /** Half-open UTF-16 span used to slice source and place fixes. */
229
- readonly range: readonly [number, number];
230
- /** Source-derived endpoints for ESLint diagnostics. */
231
- readonly loc: {
232
- readonly start: ShellPosition;
233
- readonly end: ShellPosition;
747
+ export declare type ShellListener<Node extends ShellNode = ShellNode> = {
748
+ listener(node: Node, parent: ShellNode | undefined): void;
749
+ }['listener'];
750
+
751
+ /**
752
+ * A node in the consumer's lint view: the union of every node type, so
753
+ * narrowing on `type` gives that node's fields with no cast. Scalar/child fields
754
+ * use lowercase upstream spellings; syntax fidelity belongs to the associated
755
+ * upstream tree. Auxiliary view nodes may have spans derived from children and
756
+ * are not upstream Nodes.
757
+ * @public
758
+ */
759
+ export declare type ShellNode = ShellNodeMap[ShellNodeType];
760
+
761
+ /**
762
+ * The lint-view type of one upstream field value `Value`: a record becomes the
763
+ * node type of that record (a union of records, a union of node types), a slice
764
+ * becomes a read-only array of such nodes with its nil elements removed, a Go
765
+ * string becomes a JavaScript `string`, and booleans and operator values stay
766
+ * as they are. Upstream nil (`null`) and typed nils never reach a view, so
767
+ * neither appears in the result.
768
+ *
769
+ * It is the type of every field of every {@link ShellNodeMap} entry, exported
770
+ * so a helper that takes or returns a node's field value can name it.
771
+ *
772
+ * @public
773
+ */
774
+ export declare type ShellNodeField<Value> = Exclude<Value, null | {
775
+ readonly Type: 'TypedNil';
776
+ }> extends infer Present ? Present extends readonly (infer Element)[] ? readonly ShellNodeField<Element>[] : Present extends {
777
+ readonly Type: infer Type extends ShellNodeType;
778
+ } ? ShellNodeMap[Type] : Present extends string | Uint8Array ? string : Present : never;
779
+
780
+ /**
781
+ * Lint-view node types by their `type` discriminator, one entry per upstream
782
+ * syntax record. It is computed from `sh-ast`'s generated interfaces, so it
783
+ * follows the pinned mvdan/sh version.
784
+ *
785
+ * A node has `type`, `range` and `loc`, plus a lowercase field for each
786
+ * non-position upstream field. A field that is a single child that may be nil
787
+ * upstream is optional, because the view omits the key when the child is
788
+ * absent; a slice is always present.
789
+ *
790
+ * `range` is a half-open UTF-16 span of the source text and `loc` its ESLint
791
+ * line/column form; both describe the view, not the upstream byte positions. A
792
+ * node whose line ends in CRLF ends before the `\r` (and a text field such as
793
+ * `value` loses that one trailing `\r`), so a CRLF file reads like its LF
794
+ * twin. Auxiliary records (`Expansion`, `Replace`, `Slice`) take their span
795
+ * from their children.
796
+ *
797
+ * @public
798
+ */
799
+ export declare type ShellNodeMap = {
800
+ [Record in Exclude<Node, {
801
+ readonly Type: 'TypedNil';
802
+ }> | Expansion | Replace | Slice as Record['Type']]: {
803
+ /** Syntax kind of this node; the discriminator to narrow a ShellNode by. */
804
+ readonly type: Record['Type'];
805
+ /**
806
+ * Half-open UTF-16 span of the node in the source text. Its type is the
807
+ * mutable tuple ESLint's `fixer` methods take, so `node.range` can be passed
808
+ * to `fixer.insertTextBeforeRange` and the others without a cast; treat it
809
+ * as read-only.
810
+ */
811
+ range: [number, number];
812
+ /** ESLint line and column of the span's two ends. */
813
+ readonly loc: {
814
+ readonly start: ShellPosition;
815
+ readonly end: ShellPosition;
816
+ };
817
+ } & {
818
+ readonly [Name in keyof Record & string as Name extends 'Type' ? never : [Record[Name]] extends [SyntaxPos] ? never : null extends Record[Name] ? Exclude<Record[Name], null | {
819
+ readonly Type: 'TypedNil';
820
+ }> extends {
821
+ readonly Type: string;
822
+ } ? never : Lowercase<Name> : Lowercase<Name>]: ShellNodeField<Record[Name]>;
823
+ } & {
824
+ readonly [Name in keyof Record & string as null extends Record[Name] ? Exclude<Record[Name], null | {
825
+ readonly Type: 'TypedNil';
826
+ }> extends {
827
+ readonly Type: string;
828
+ } ? Lowercase<Name> : never : never]?: ShellNodeField<Record[Name]>;
234
829
  };
235
- /** Lowercase syntax fields containing consumer children or decoded scalar values. */
236
- readonly [field: string]: unknown;
237
- }
830
+ };
831
+
832
+ /** The lint-view node whose `type` is `Type`, for example `ShellNodeOf<'Stmt'>`. @public */
833
+ export declare type ShellNodeOf<Type extends ShellNodeType> = ShellNodeMap[Type];
834
+
835
+ /** Every value a lint-view node's `type` can hold. @public */
836
+ export declare type ShellNodeType = keyof ShellNodeMap;
238
837
 
239
838
  /** An ESLint location counts UTF-16 units, with both line and column starting at one. @public */
240
839
  export declare interface ShellPosition {
@@ -244,6 +843,105 @@ export declare interface ShellPosition {
244
843
  readonly column: number;
245
844
  }
246
845
 
846
+ /**
847
+ * A finished shell rule, as {@link defineShellRule} returns it: an ordinary
848
+ * ESLint rule definition (it goes in a plugin's `rules` like any other)
849
+ * specialized to the shell language, whose `meta.languages` is always exactly
850
+ * `["sh/sh"]`. The `meta.languages` requirement is part of the type, so a
851
+ * hand-written object that omits it, or names another language, is not a
852
+ * `ShellRule`.
853
+ *
854
+ * @public
855
+ */
856
+ export declare type ShellRule<RuleOptions extends unknown[] = [], MessageIds extends string = string> = Omit<RuleDefinition<{
857
+ LangOptions: NormalizedShellLanguageOptions;
858
+ Code: ShellSourceCode;
859
+ RuleOptions: RuleOptions;
860
+ Visitor: ShellRuleVisitor;
861
+ Node: ShellNode;
862
+ MessageIds: MessageIds;
863
+ ExtRuleDocs: unknown;
864
+ }>, 'meta'> & {
865
+ readonly meta: Omit<RulesMeta<MessageIds, RuleOptions>, 'languages'> & {
866
+ languages: ['sh/sh'];
867
+ };
868
+ };
869
+
870
+ /**
871
+ * The `context` a shell rule's `create()` receives: ESLint's rule context
872
+ * specialized to the shell language, so `context.sourceCode` is
873
+ * {@link ShellSourceCode}, `context.languageOptions` is
874
+ * {@link NormalizedShellLanguageOptions}, `context.options` is `RuleOptions`,
875
+ * and `context.report` takes {@link ShellNode}s and only the `MessageIds` the
876
+ * rule declares. Name it when a helper outside `create()` needs the context.
877
+ *
878
+ * `context.languageOptions` is the object `ShellLanguage.normalizeLanguageOptions`
879
+ * returned, not the user's config input, so `dialect` is always set (`"bash"`
880
+ * by default) and needs no fallback. `context.sourceCode.dialect` carries that
881
+ * same normalized value; read whichever is closer to hand.
882
+ *
883
+ * @public
884
+ */
885
+ export declare type ShellRuleContext<RuleOptions extends unknown[] = [], MessageIds extends string = string> = RuleContext<{
886
+ LangOptions: NormalizedShellLanguageOptions;
887
+ Code: ShellSourceCode;
888
+ RuleOptions: RuleOptions;
889
+ Node: ShellNode;
890
+ MessageIds: MessageIds;
891
+ }>;
892
+
893
+ /**
894
+ * The `meta` a rule author writes: every field ESLint's rule `meta` accepts
895
+ * (`type`, `docs`, `schema`, `messages`, `fixable`, `defaultOptions`, …)
896
+ * except the language fields, which {@link defineShellRule} owns. `languages`
897
+ * and its deprecated singular predecessor `language` are typed `never`, so
898
+ * writing either — in particular a language other than `sh/sh` — is a
899
+ * compile error rather than a value the helper silently replaces.
900
+ *
901
+ * @public
902
+ */
903
+ export declare type ShellRuleMeta<RuleOptions extends unknown[] = [], MessageIds extends string = string> = Omit<RulesMeta<MessageIds, RuleOptions>, 'languages' | 'language'> & {
904
+ readonly languages?: never;
905
+ readonly language?: never;
906
+ };
907
+
908
+ /**
909
+ * What a rule author hands to {@link defineShellRule}: the rule's `meta` and
910
+ * its `create()`, exactly as in any ESLint rule, minus `meta.languages`. It is
911
+ * the rule before the helper finishes it; {@link ShellRule} is the finished
912
+ * rule (ESLint's `RuleDefinition`), so the input is named a spec, not a
913
+ * definition.
914
+ *
915
+ * @public
916
+ */
917
+ export declare interface ShellRuleSpec<RuleOptions extends unknown[] = [], MessageIds extends string = string> {
918
+ /** The rule's metadata; see {@link ShellRuleMeta}. */
919
+ readonly meta: ShellRuleMeta<RuleOptions, MessageIds>;
920
+ /** Builds the rule's listeners for one file from the shell-typed context. */
921
+ readonly create: (context: ShellRuleContext<RuleOptions, MessageIds>) => ShellRuleVisitor;
922
+ }
923
+
924
+ /**
925
+ * The listener map a shell rule's `create()` returns. Keys are node types or
926
+ * esquery selectors written against the lint view's spelling (for example
927
+ * `CallExpr[args.0.parts.0.value='jq']` or the `:command` selector class),
928
+ * optionally suffixed with `:exit`. ESLint calls each listener with the
929
+ * matched node and its parent (the parent is `undefined` for the root
930
+ * `File`).
931
+ *
932
+ * A key that is exactly a node type, with or without `:exit` (`CallExpr`,
933
+ * `Word:exit`), types its listener's node as that node type, so
934
+ * `CallExpr(node) { node.args }` reads `args` with no `type` check. Any other
935
+ * selector string (`*`, `CallExpr[…]`, `:command`, `Stmt > CallExpr`) matches
936
+ * nodes of more than one type, or of one type the key does not state, so its
937
+ * listener receives the whole {@link ShellNode} union: narrow it on `type`.
938
+ *
939
+ * @public
940
+ */
941
+ export declare interface ShellRuleVisitor extends ShellTypedListeners {
942
+ [selector: string]: ShellListener | undefined;
943
+ }
944
+
247
945
  /**
248
946
  * `ShellSourceCode` — the `SourceCode` object ESLint uses to work with a
249
947
  * parsed shell file. Extends `@eslint/plugin-kit`'s `TextSourceCodeBase`, which
@@ -260,8 +958,8 @@ export declare interface ShellPosition {
260
958
  * `applyInlineConfig`) recognizes all four disable-comment forms
261
959
  * (`<prefix>-disable`, `<prefix>-enable`, `<prefix>-disable-line`,
262
960
  * `<prefix>-disable-next-line`) plus whole-file inline rule config
263
- * (bare `<prefix> <rule-config>`), sourced from `Comment` nodes gathered via
264
- * the consumer's `walkShell()` and parsed with
961
+ * (bare `<prefix> <rule-config>`), sourced from the tree's `Comment` nodes
962
+ * and parsed with
265
963
  * `@eslint/plugin-kit`'s `ConfigCommentParser` — the same building blocks
266
964
  * `@eslint/json`'s `JSONSourceCode` uses, generalized so the `<prefix>` is
267
965
  * configurable (default `"eslint"`; see `ShellLanguageOptions.directivePrefix`)
@@ -271,6 +969,24 @@ export declare interface ShellPosition {
271
969
  * classes) is implemented on `ShellLanguage` — see `shell-language.ts` and
272
970
  * `selector-classes.ts`.
273
971
  *
972
+ * Rule-author helpers (`resolveWordText`, `getCommandName`,
973
+ * `resolveInvokedCommand`, `getCommandSites`) answer "is this word statically
974
+ * the string `jq`?", "which command does this invocation run?", and "which
975
+ * commands does this file run, and where?" by delegating to
976
+ * `sh-ast/analyze`. They never reinterpret words themselves, so every rule
977
+ * gets the same answer for the same syntax. `dialect` and `shellVersion`
978
+ * expose the file's normalized language options.
979
+ *
980
+ * Syntax access (`getSyntax`, `getViewNode`, `getIndexFromSyntaxPos`,
981
+ * `getLocFromSyntaxPos`) reaches behind the lint view to the sh-ast records
982
+ * this file was parsed into: from a view node to its record and back, and
983
+ * from a record's byte positions (such as `BinaryCmd.OpPos`, which the view
984
+ * leaves out) to ESLint coordinates in this file's text.
985
+ *
986
+ * Printed fixes (`getEditableSyntax`, `getPrintedReplacement`) edit an
987
+ * independent copy of a view node's record and print it with sh-ast's
988
+ * printer, returning the source range and text for `fixer.replaceTextRange`.
989
+ *
274
990
  * @public
275
991
  */
276
992
  export declare class ShellSourceCode extends TextSourceCodeBase<{
@@ -284,7 +1000,324 @@ export declare class ShellSourceCode extends TextSourceCodeBase<{
284
1000
  text: string;
285
1001
  ast: ShellFile;
286
1002
  directivePrefix?: string;
1003
+ dialect?: ShellDialect;
1004
+ shellVersion?: string;
287
1005
  });
1006
+ /**
1007
+ * The shell dialect this file was parsed as: the normalized
1008
+ * `languageOptions.dialect`, which defaults to `"bash"` when the config
1009
+ * leaves it unset. Rules use it to gate dialect-specific checks.
1010
+ */
1011
+ get dialect(): ShellDialect;
1012
+ /**
1013
+ * The configured `languageOptions.shellVersion` (a `MAJOR[.MINOR]` string
1014
+ * such as `"3.2"`), or `undefined` when the config does not set one. The
1015
+ * parser never reads it; it exists for rules that gate on shell version.
1016
+ */
1017
+ get shellVersion(): string | undefined;
1018
+ /**
1019
+ * The one-based location of a UTF-16 `index` in this file's text, where
1020
+ * `index` may be anywhere from 0 to `text.length`. CRLF counts as one line
1021
+ * break.
1022
+ *
1023
+ * Overrides the base class, which measures every index from the root
1024
+ * node's own start and so assumes the root spans the whole text. A shell
1025
+ * file's root starts at its first statement or comment, so the base answer
1026
+ * was shifted by any leading blank lines or indentation.
1027
+ *
1028
+ * @throws TypeError when `index` is not a number.
1029
+ * @throws RangeError when `index` is not an integer from 0 to `text.length`.
1030
+ */
1031
+ getLocFromIndex(index: number): {
1032
+ line: number;
1033
+ column: number;
1034
+ };
1035
+ /**
1036
+ * The UTF-16 index of a one-based `loc` in this file's text: the inverse of
1037
+ * {@link ShellSourceCode.getLocFromIndex}. The column may name any position
1038
+ * on its line up to and including the line break.
1039
+ *
1040
+ * Overrides the base class for the same reason as `getLocFromIndex`.
1041
+ *
1042
+ * @throws TypeError when `loc` is not an object with numeric `line` and `column`.
1043
+ * @throws RangeError when `loc` is not inside the text.
1044
+ */
1045
+ getIndexFromLoc(loc: {
1046
+ line: number;
1047
+ column: number;
1048
+ }): number;
1049
+ /**
1050
+ * Returns the literal text of `word` when `sh-ast/analyze`'s `resolveWord`
1051
+ * reports it static, or `null` otherwise.
1052
+ *
1053
+ * Delegates to `resolveWord` (through this package's `resolveWord` export),
1054
+ * which alone defines what "static" means: a word is static iff
1055
+ * `resolveWord` reports `static: true`. Its non-static reasons are
1056
+ * `expansion`, `tilde`, `glob`, `brace`, `locale`, and `unsupported`.
1057
+ * Call the root `resolveWord` export instead when the reason matters.
1058
+ *
1059
+ * `options` is forwarded to `resolveWord` unchanged. When it is omitted, the
1060
+ * word's grammar position picks the tilde context: a word in a `CallExpr`'s
1061
+ * `args` uses `{ context: 'command-argument' }`, the context
1062
+ * `enumerateCommands` uses for argv, so `resolveWordText(callExpr.args[0])`
1063
+ * equals `getCommandName(callExpr)`. Any other word uses `resolveWord`'s
1064
+ * conservative default, which also treats a `~` after an unquoted `:` as a
1065
+ * tilde expansion, as assignment values do.
1066
+ *
1067
+ * @param word - A parsed `Word` node from this file's lint tree.
1068
+ * @param options - Forwarded to `resolveWord` unchanged when supplied.
1069
+ * @returns The resolved text, or `null` when the word is not static.
1070
+ * @throws TypeError when `word` is not a parsed shell `Word`.
1071
+ */
1072
+ resolveWordText(word: ShellNodeOf<'Word'>, options?: ResolveWordOptions): string | null;
1073
+ /**
1074
+ * Returns the static text of `callExpr`'s command word (`argv[0]`) as
1075
+ * written, or `null` when that word is dynamic or absent. Transparent
1076
+ * wrappers are **not** followed: `sudo rm x` yields `"sudo"`. Use
1077
+ * {@link ShellSourceCode.resolveInvokedCommand} for the command a wrapper
1078
+ * chain actually runs.
1079
+ *
1080
+ * Delegates to `sh-ast/analyze`: the answer is the `argv0` resolution of the
1081
+ * `CommandSite` that `enumerateCommands` reports for this `CallExpr` in this
1082
+ * file. Leading `VAR=val` assignments are not part of argv, so
1083
+ * `FOO=1 jq .` yields `"jq"`. An assignment-only statement (`FOO=1`) has no
1084
+ * argv0 and no `CommandSite`, and yields `null`.
1085
+ *
1086
+ * @param callExpr - A parsed `CallExpr` node from this file's lint tree.
1087
+ * @returns The command word's static text, or `null`.
1088
+ * @throws TypeError when `callExpr` is not a parsed shell `CallExpr`.
1089
+ * @throws `ShAnalyzeMaxDepthError` (from `sh-ast/analyze`) when this file's
1090
+ * nesting exceeds analysis's recursion guard; see
1091
+ * {@link ShellSourceCode.resolveInvokedCommand}.
1092
+ */
1093
+ getCommandName(callExpr: ShellNodeOf<'CallExpr'>): string | null;
1094
+ /**
1095
+ * Follows `callExpr`'s argv0 through transparent wrappers (`env`, `sudo`,
1096
+ * `nohup`, `exec`, …) and returns `sh-ast/analyze`'s full `resolveArgv0`
1097
+ * result, or `null` for a `CallExpr` that has no `CommandSite` (an
1098
+ * assignment-only statement such as `FOO=1`).
1099
+ *
1100
+ * Delegates to `resolveArgv0` with the `CommandSite` that `enumerateCommands`
1101
+ * reports for this `CallExpr` in this file, so it inherits that function's
1102
+ * guarantee that an unknowable word is never guessed through. Read the
1103
+ * effective command's name as
1104
+ * `res.effective.static ? res.effective.text : null`.
1105
+ * `sudo -u x "$prog"` and `exec "$@"` have a non-static `effective` with
1106
+ * reason `expansion`; `env -S '…'` has reason `embedded-command`; `chain`
1107
+ * lists every word resolved along the way, outermost wrapper first, and
1108
+ * `chainIndices` gives each entry's position in the command's `argv` (as
1109
+ * {@link ShellSourceCode.getCommandSites} reports it). When `effective` is
1110
+ * static, the effective command's arguments are
1111
+ * `argv.slice(chainIndices.at(-1)! + 1)`: every entry after the effective
1112
+ * command's own index.
1113
+ *
1114
+ * Enumeration runs once per file, the first time any command helper is
1115
+ * called. If the file's nesting exceeds `enumerateCommands`'s recursion
1116
+ * guard, its `ShAnalyzeMaxDepthError` propagates unchanged and ESLint
1117
+ * reports the rule as crashed for that file. That fails closed: a partial
1118
+ * index would silently omit real commands.
1119
+ *
1120
+ * @param callExpr - A parsed `CallExpr` node from this file's lint tree.
1121
+ * @param options - Forwarded to `resolveArgv0` unchanged.
1122
+ * `transparentWrappers` replaces `sh-ast/analyze`'s
1123
+ * `DEFAULT_TRANSPARENT_WRAPPERS`; spread the defaults into the array to
1124
+ * extend them instead.
1125
+ * @returns The resolution, or `null` when `callExpr` invokes no command.
1126
+ * @throws TypeError when `callExpr` is not a parsed shell `CallExpr`.
1127
+ * @throws `ShAnalyzeMaxDepthError` (from `sh-ast/analyze`) when this file's
1128
+ * nesting exceeds analysis's recursion guard.
1129
+ * @throws `ShAnalyzeInvalidWrapperSpecError` (from `sh-ast/analyze`) when
1130
+ * `options.transparentWrappers` contains a malformed entry.
1131
+ */
1132
+ resolveInvokedCommand(callExpr: ShellNodeOf<'CallExpr'>, options?: ResolveArgv0Options): Argv0Resolution | null;
1133
+ /**
1134
+ * Returns every command invocation in this file, one entry per
1135
+ * `sh-ast/analyze` `CommandSite`, in source order. Use it when a rule needs
1136
+ * the whole file's commands with their structural context, such as every
1137
+ * command inside a function or inside `$(…)`, instead of visiting
1138
+ * `CallExpr` nodes one by one.
1139
+ *
1140
+ * The data comes from `sh-ast/analyze`'s `enumerateCommands`: facts about
1141
+ * how each command is reached and what its words resolve to, never a
1142
+ * verdict. Each entry's `context`, `argv0`, and `argv` are analysis's own
1143
+ * values, unchanged; `context` lists frames outermost first, as in sh-ast.
1144
+ * Each entry's `node` is the `CallExpr` view node (`===` the node ESLint
1145
+ * passes to a `CallExpr` visitor), so it can be passed to
1146
+ * `context.report()`.
1147
+ *
1148
+ * An assignment-only statement (`FOO=1`) runs no command and has no entry,
1149
+ * although a command substitution in its value (`FOO=$(date)`) does.
1150
+ *
1151
+ * Enumeration runs at most once per file, shared with
1152
+ * {@link ShellSourceCode.getCommandName} and
1153
+ * {@link ShellSourceCode.resolveInvokedCommand}, and every call returns the
1154
+ * same array. Because every rule shares it, the array and everything in each
1155
+ * entry except `node` are deeply frozen (see `ShellCommandSite`): no rule can
1156
+ * change what another rule reads. If the file's nesting exceeds `enumerateCommands`'s
1157
+ * recursion guard, its `ShAnalyzeMaxDepthError` propagates unchanged, as it
1158
+ * does for those helpers: a partial list would silently omit real commands.
1159
+ *
1160
+ * @returns This file's command sites, in source order.
1161
+ * @throws `ShAnalyzeMaxDepthError` (from `sh-ast/analyze`) when this file's
1162
+ * nesting exceeds analysis's recursion guard.
1163
+ */
1164
+ getCommandSites(): readonly ShellCommandSite[];
1165
+ /**
1166
+ * Returns the sh-ast record `node` was projected from: this file's original
1167
+ * parse of `node`, with every field the lint view leaves out, including
1168
+ * named token positions such as `BinaryCmd.OpPos`, `Stmt.Semicolon` and
1169
+ * `Redirect.OpPos`. Pass those positions to
1170
+ * {@link ShellSourceCode.getIndexFromSyntaxPos} or
1171
+ * {@link ShellSourceCode.getLocFromSyntaxPos} to report on them, or pass the
1172
+ * record to an `sh-ast/analyze` function.
1173
+ *
1174
+ * The returned record is read-only by contract. It is the language's
1175
+ * original parse, shared by every rule and helper linting this file, and
1176
+ * mutating it is unsupported: helpers such as
1177
+ * {@link ShellSourceCode.resolveWordText} read the same record and would see
1178
+ * the edit. It is not frozen, so a write does not throw. Make edits on an
1179
+ * editable copy from {@link ShellSourceCode.getEditableSyntax} instead.
1180
+ *
1181
+ * @param node - A view node from this file's lint tree.
1182
+ * @returns The original sh-ast record, or `undefined` when `node` has none
1183
+ * in this file: an auxiliary view (a record whose type is not an sh-ast
1184
+ * `Node`, such as the `Slice` of `${x:1:2}`), a view node from another
1185
+ * file's tree, or an
1186
+ * object that did not come from the parser.
1187
+ */
1188
+ getSyntax(node: ShellNode): Node | undefined;
1189
+ /**
1190
+ * Returns the lint view node projected from `syntax`: the inverse of
1191
+ * {@link ShellSourceCode.getSyntax}. The result is the same object (`===`)
1192
+ * ESLint passes to rule visitors, so it can be passed to
1193
+ * `context.report()`. Use it to come back to the view after navigating the
1194
+ * original records, for example from a `Stmt` record's `Cmd`.
1195
+ *
1196
+ * Lookup is by object identity, so only the parser's own records match.
1197
+ * sh-ast's `walk` passes its callback a fresh copy of each comment stored
1198
+ * on a `Stmt`, `CaseItem` or `ArrayElem`, as Go's `syntax.Walk` does, so
1199
+ * those `Comment` copies have no view node. Read the stored comment from
1200
+ * the record's `Comments` array instead.
1201
+ *
1202
+ * @param syntax - An sh-ast `Node` record from this file's original parse.
1203
+ * @returns The view node, or `undefined` for any other record, including a
1204
+ * copy of one of this file's records (such as a `structuredClone`) and a
1205
+ * record from another parse.
1206
+ */
1207
+ getViewNode(syntax: Node): ShellNode | undefined;
1208
+ /**
1209
+ * Returns an independent, editable deep copy of the sh-ast record `node`
1210
+ * was parsed from: the first half of a printed fix. Edit the copy, then pass
1211
+ * it to {@link ShellSourceCode.getPrintedReplacement}.
1212
+ *
1213
+ * The copy is `structuredClone` of the record
1214
+ * {@link ShellSourceCode.getSyntax} returns. sh-ast documents and tests
1215
+ * ordinary `structuredClone` as the supported way to make an independent
1216
+ * editable copy of a syntax subtree (sh-ast 0.7.0 README, "Editing and
1217
+ * printing"), so no second parse is involved. The copy owns its records,
1218
+ * arrays, positions and byte buffers. Editing it leaves this file's original
1219
+ * syntax, its lint view, its cached traversal and the answers of view-aware
1220
+ * helpers such as `resolveWord` and
1221
+ * {@link ShellSourceCode.resolveWordText} unchanged. Each call returns a new
1222
+ * copy.
1223
+ *
1224
+ * The copy's position fields keep their original values, and the copy has
1225
+ * no view node: {@link ShellSourceCode.getViewNode},
1226
+ * {@link ShellSourceCode.getIndexFromSyntaxPos} and the parse's position
1227
+ * snapshot describe the original source, not the edit. Keep `node` to know
1228
+ * which source range the fix replaces.
1229
+ *
1230
+ * @param node - A view node from this file's lint tree.
1231
+ * @returns A deep copy of `node`'s original sh-ast record.
1232
+ * @throws TypeError when `node` has no original record in this file: an
1233
+ * auxiliary view (such as the `Slice` of `${x:1:2}`), a view node of another
1234
+ * file, or an object that did not come from the parser.
1235
+ */
1236
+ getEditableSyntax(node: ShellNode): Node;
1237
+ /**
1238
+ * Prints `edited` with sh-ast's `printSync` and pairs the text with the
1239
+ * source range it replaces, so a printed fix is
1240
+ * `fix: (fixer) => fixer.replaceTextRange(r.range, r.text)`. `edited` is
1241
+ * usually a copy from {@link ShellSourceCode.getEditableSyntax} of `node`
1242
+ * or of one of its ancestors.
1243
+ *
1244
+ * The range is `node.range`, except that a `File` root replaces the whole
1245
+ * text (`[0, text.length]`) whatever `node` is, which allows whole-file
1246
+ * regeneration. `options` is passed to `printSync` unchanged.
1247
+ *
1248
+ * The printer formats; it does not replay the source. Its output starts at
1249
+ * column 0 and indents nested lines with upstream's indentation (tabs by
1250
+ * default, `Indent` spaces when `options.Indent` is set). Replacing a
1251
+ * multi-line root nested inside other code, such as an `if` statement in a
1252
+ * function body, therefore keeps the shell meaning but may change the
1253
+ * formatting of its lines. Use a `Word` or `WordPart` root for a surgical
1254
+ * fix that leaves the surrounding source as written.
1255
+ *
1256
+ * A `Stmt` root is adjusted so its text matches `node.range`. A statement's
1257
+ * own leading and trailing comments lie outside its range, so they are not
1258
+ * printed (the copy passed in is not modified). When `node` is a statement
1259
+ * whose range ends in its `;` or `&` terminator, upstream prints the
1260
+ * statement without a `;`, so a `;` is appended unless the edited statement
1261
+ * prints its own `&`, `|&` or disown operator. Without this, replacing `a`
1262
+ * in `a; b` would produce `a b`.
1263
+ *
1264
+ * @param node - A view node from this file's lint tree: the node the fix
1265
+ * replaces.
1266
+ * @param edited - The record to print: a `File`, `Stmt`, `Word`, `Assign`,
1267
+ * `Command` or `WordPart`.
1268
+ * @param options - Forwarded to `printSync` unchanged.
1269
+ * @returns The range to replace and the printed text to put there.
1270
+ * @throws TypeError when `node` has no original record in this file (see
1271
+ * {@link ShellSourceCode.getEditableSyntax}), when `edited` is not a
1272
+ * printable root (the message names its type), or when the printer's output
1273
+ * is not valid UTF-8. Non-UTF-8 output comes back from `printSync` as a
1274
+ * `Uint8Array` and is never decoded lossily into a fix.
1275
+ * @throws `PrintError` or `PrintPanicError` (from sh-ast) when upstream's
1276
+ * printer fails for `edited` or `options`.
1277
+ */
1278
+ getPrintedReplacement(node: ShellNode, edited: Node, options?: PrintOptions): {
1279
+ range: [number, number];
1280
+ text: string;
1281
+ };
1282
+ /**
1283
+ * Converts a byte position from this file's original parse into the
1284
+ * UTF-16 index ESLint uses for `range` and `text.slice`. sh-ast positions
1285
+ * count UTF-8 bytes, so a raw `Offset` is wrong for any source with
1286
+ * non-ASCII text before it. For example, in `echo 🎉 && echo 中文`,
1287
+ * `getIndexFromSyntaxPos(binaryCmd.OpPos)` is the index where `&&` starts.
1288
+ *
1289
+ * The conversion is only correct for positions of this file's original
1290
+ * parse, as read from {@link ShellSourceCode.getSyntax}'s records. The live
1291
+ * positions of an edited or copied tree describe that edit, not this
1292
+ * source.
1293
+ *
1294
+ * @param pos - A `SyntaxPos` from one of this file's original records.
1295
+ * @returns The UTF-16 index of `pos` in this file's text.
1296
+ * @throws RangeError when `pos` is invalid (`IsValid === false`), recovered
1297
+ * (`IsRecovered === true`), or overflowed (`OffsetOverflow === true`), or
1298
+ * when its offset is not a UTF-8 character boundary of this source. Such a
1299
+ * position is never rounded to a nearby boundary.
1300
+ * @throws TypeError when `pos` is not a `SyntaxPos`, or when this source
1301
+ * code's tree was not built by this language's parser.
1302
+ */
1303
+ getIndexFromSyntaxPos(pos: SyntaxPos): number;
1304
+ /**
1305
+ * Converts a byte position from this file's original parse into a one-based
1306
+ * ESLint location, the shape `context.report({ loc })` takes. The column
1307
+ * counts UTF-16 code units, not bytes, and CRLF counts as one line break.
1308
+ * Same contract as {@link ShellSourceCode.getIndexFromSyntaxPos}.
1309
+ *
1310
+ * @param pos - A `SyntaxPos` from one of this file's original records.
1311
+ * @returns The one-based line and UTF-16 column of `pos`.
1312
+ * @throws RangeError when `pos` is invalid, recovered, or overflowed, or
1313
+ * when its offset is not a UTF-8 character boundary of this source.
1314
+ * @throws TypeError when `pos` is not a `SyntaxPos`, or when this source
1315
+ * code's tree was not built by this language's parser.
1316
+ */
1317
+ getLocFromSyntaxPos(pos: SyntaxPos): {
1318
+ line: number;
1319
+ column: number;
1320
+ };
288
1321
  /**
289
1322
  * Returns the parent of `node`, or `undefined` for the root `File` node
290
1323
  * (or any node visited before `traverse()` has run once). ESLint always
@@ -352,4 +1385,130 @@ export declare class ShellSourceCode extends TextSourceCodeBase<{
352
1385
  };
353
1386
  }
354
1387
 
1388
+ /**
1389
+ * The listeners whose key names one node type, or that node type with the
1390
+ * `:exit` suffix, each receiving exactly that node type. The key is
1391
+ * unambiguous in both forms (`CallExpr`, `CallExpr:exit`), so a listener
1392
+ * needs no `node.type` guard. Wildcard (`*`, `*:exit`) and attribute or
1393
+ * class selectors name no single type and fall under the index signature.
1394
+ *
1395
+ * Exported because {@link ShellRuleVisitor} extends it.
1396
+ *
1397
+ * @public
1398
+ */
1399
+ export declare type ShellTypedListeners = {
1400
+ [Type in ShellNodeType as Type | `${Type}:exit`]?: ShellListener<ShellNodeOf<Type>>;
1401
+ };
1402
+
1403
+ export { WordResolution }
1404
+
1405
+ /**
1406
+ * What a "must be run through a wrapper" rule enforces. Passed to
1407
+ * {@link createWrapperRule}.
1408
+ *
1409
+ * @public
1410
+ */
1411
+ export declare interface WrapperRuleConfig {
1412
+ /**
1413
+ * The command names that must be run through the wrapper. A static command
1414
+ * word matches a name when it equals it, or when it contains a slash and
1415
+ * its basename equals it (`/usr/bin/curl` and `./bin/curl` match `curl`).
1416
+ */
1417
+ readonly commands: readonly string[];
1418
+ /**
1419
+ * The wrapper the commands must be run through. A string names a wrapper
1420
+ * that takes no options of its own (`retry curl x`), unless it names one of
1421
+ * `sh-ast/analyze`'s default transparent wrappers (`timeout`, `nice`, …),
1422
+ * whose known option shape is used. A `WrapperSpec` declares the wrapper's
1423
+ * option shape, so that `retry -n 3 curl x` is recognized as `curl` run
1424
+ * through `retry`; any of its `names` counts as the wrapper. Like
1425
+ * `commands`, a wrapper run by path matches by basename
1426
+ * (`/usr/local/bin/retry curl x`).
1427
+ */
1428
+ readonly wrapper: string | WrapperSpec;
1429
+ /**
1430
+ * Listed commands that count only with some subcommands, keyed by command
1431
+ * name. A listed command with no entry here counts with any arguments.
1432
+ */
1433
+ readonly subcommands?: Readonly<Record<string, WrapperRuleSubcommands>>;
1434
+ /**
1435
+ * Invocations of a listed command that only act locally and so are not
1436
+ * reported, keyed by the command as the rule's message names it: the listed
1437
+ * name (`curl`), or the name with its subcommand (`git clone`).
1438
+ */
1439
+ readonly localInvocations?: Readonly<Record<string, WrapperRuleLocalInvocation>>;
1440
+ /**
1441
+ * The rule's `meta.docs` fields a rule pack owns. `url` defaults to this
1442
+ * package's wrapper-rules docs page, and `description` to a sentence naming
1443
+ * the commands and the wrapper. Set them to point users at the pack's own
1444
+ * documentation.
1445
+ */
1446
+ readonly docs?: {
1447
+ readonly url?: string;
1448
+ readonly description?: string;
1449
+ };
1450
+ }
1451
+
1452
+ /**
1453
+ * Ways to invoke one listed command that only act locally, so the policy has
1454
+ * nothing to guard. An invocation matching either field is not reported.
1455
+ *
1456
+ * @public
1457
+ */
1458
+ export declare interface WrapperRuleLocalInvocation {
1459
+ /**
1460
+ * Complete argument lists, after the command (and its subcommand, if it has
1461
+ * one), that make the invocation local. They match exactly: with
1462
+ * `[['--version']]`, `curl --version` is local and
1463
+ * `curl --version https://example.com` is not.
1464
+ */
1465
+ readonly argLists?: readonly (readonly string[])[];
1466
+ /** Local when the command's source operand is a local path. */
1467
+ readonly localOperand?: WrapperRuleLocalOperand;
1468
+ }
1469
+
1470
+ /**
1471
+ * Names the operand of a listed command whose value decides whether the
1472
+ * invocation can leave the machine, such as the source of `git clone`.
1473
+ *
1474
+ * The flags describe the options that may precede the operand, with the same
1475
+ * meaning as {@link WrapperRuleSubcommands}'s: `noArgFlags` take no value and
1476
+ * `argFlags` take one (as `--depth 1`, or `--depth=1` in one word). The
1477
+ * operand is the first word after them, or after a `--`. A flag that is not
1478
+ * declared leaves the operand unknown, and an unknown operand is never taken
1479
+ * as local.
1480
+ *
1481
+ * @public
1482
+ */
1483
+ export declare interface WrapperRuleLocalOperand extends Pick<WrapperSpec, 'noArgFlags' | 'argFlags'> {
1484
+ /**
1485
+ * The operand counts as local when it is a static word starting with one
1486
+ * of these, such as `['./', '../', '/', 'file://']`.
1487
+ */
1488
+ readonly localPrefixes: readonly string[];
1489
+ }
1490
+
1491
+ /**
1492
+ * Restricts a listed command to some of its subcommands, for commands such as
1493
+ * `git` where only some subcommands are in scope (`git fetch` is a network
1494
+ * call, `git status` is not).
1495
+ *
1496
+ * The flag fields are `WrapperSpec`'s own, picked from it so the two cannot
1497
+ * drift, and describe the command's options that may appear before the
1498
+ * subcommand (`git -C dir fetch`): `noArgFlags` take no operand (`--bare`),
1499
+ * `argFlags` take one (`-C <dir>`), and `stopsChainFlags` mean the command
1500
+ * runs no subcommand at all (`git --version fetch` prints the version, and is
1501
+ * never reported). The subcommand is found the way a wrapped command is:
1502
+ * declared options are skipped, and a flag the command does not declare
1503
+ * leaves the subcommand unknown, so the invocation is not reported.
1504
+ *
1505
+ * @public
1506
+ */
1507
+ export declare interface WrapperRuleSubcommands extends Pick<WrapperSpec, 'noArgFlags' | 'argFlags' | 'stopsChainFlags'> {
1508
+ /** The subcommands that make an invocation of the command count, such as `['fetch', 'push']`. */
1509
+ readonly names: readonly string[];
1510
+ }
1511
+
1512
+ export { WrapperSpec }
1513
+
355
1514
  export { }