@eslint-sh/eslint-plugin 0.4.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (256) hide show
  1. package/README.md +223 -17
  2. package/dist/command-harvest/builtins/bash.d.ts +36 -0
  3. package/dist/command-harvest/builtins/bash.js +98 -0
  4. package/dist/command-harvest/builtins/bats.d.ts +45 -0
  5. package/dist/command-harvest/builtins/bats.js +33 -0
  6. package/dist/command-harvest/builtins/index.d.ts +18 -0
  7. package/dist/command-harvest/builtins/index.js +39 -0
  8. package/dist/command-harvest/builtins/posix.d.ts +51 -0
  9. package/dist/command-harvest/builtins/posix.js +93 -0
  10. package/dist/command-harvest/harvest-commands.d.ts +66 -0
  11. package/dist/command-harvest/harvest-commands.js +97 -0
  12. package/dist/command-harvest/wrapper-table.d.ts +24 -0
  13. package/dist/command-harvest/wrapper-table.js +49 -0
  14. package/dist/configs/agent-authored.d.ts +41 -0
  15. package/dist/configs/agent-authored.js +33 -0
  16. package/dist/configs/bash-3-2.d.ts +28 -0
  17. package/dist/configs/bash-3-2.js +50 -0
  18. package/dist/configs/posix-portable.d.ts +40 -0
  19. package/dist/configs/posix-portable.js +52 -0
  20. package/dist/configs/recommended.d.ts +52 -0
  21. package/dist/configs/recommended.js +34 -0
  22. package/dist/declaration-conventions/comment-pragma.d.ts +33 -0
  23. package/dist/declaration-conventions/comment-pragma.js +153 -0
  24. package/dist/declaration-conventions/declaration-convention.d.ts +189 -0
  25. package/dist/declaration-conventions/declaration-convention.js +1 -0
  26. package/dist/declaration-conventions/read-declared-commands.d.ts +70 -0
  27. package/dist/declaration-conventions/read-declared-commands.js +128 -0
  28. package/dist/eslint-plugin-syntax-analyze.d.ts +23 -0
  29. package/dist/eslint-plugin-syntax.d.ts +27 -0
  30. package/dist/eslint-plugin.d.ts +1211 -52
  31. package/dist/index.d.ts +56 -8
  32. package/dist/index.js +53 -6
  33. package/dist/language/command-resolution.d.ts +124 -0
  34. package/dist/language/command-resolution.js +120 -0
  35. package/dist/language/end-of-input-marker.d.ts +29 -0
  36. package/dist/language/end-of-input-marker.js +88 -0
  37. package/dist/language/line-breaks.d.ts +18 -0
  38. package/dist/language/line-breaks.js +22 -0
  39. package/dist/language/lint-view-types.d.ts +104 -0
  40. package/dist/language/lint-view-types.js +1 -0
  41. package/dist/language/non-shell-file-guard.d.ts +35 -0
  42. package/dist/language/non-shell-file-guard.js +59 -0
  43. package/dist/language/selector-classes.d.ts +0 -1
  44. package/dist/language/selector-classes.js +0 -1
  45. package/dist/language/shell-language-options.d.ts +7 -1
  46. package/dist/language/shell-language-options.js +4 -4
  47. package/dist/language/shell-language.d.ts +10 -2
  48. package/dist/language/shell-language.js +59 -8
  49. package/dist/language/shell-source-code.d.ts +342 -5
  50. package/dist/language/shell-source-code.js +559 -30
  51. package/dist/language/source-coordinates.d.ts +13 -2
  52. package/dist/language/source-coordinates.js +68 -14
  53. package/dist/language/syntax-view.d.ts +60 -39
  54. package/dist/language/syntax-view.js +200 -45
  55. package/dist/language/word-resolution.d.ts +4 -3
  56. package/dist/language/word-resolution.js +4 -3
  57. package/dist/rules/compat/create-compat-rule.d.ts +59 -0
  58. package/dist/rules/compat/create-compat-rule.js +65 -0
  59. package/dist/rules/compat/declaration-options.d.ts +19 -0
  60. package/dist/rules/compat/declaration-options.js +110 -0
  61. package/dist/rules/compat/no-append-all-redirect.d.ts +13 -0
  62. package/dist/rules/compat/no-append-all-redirect.js +73 -0
  63. package/dist/rules/compat/no-associative-arrays.d.ts +12 -0
  64. package/dist/rules/compat/no-associative-arrays.js +39 -0
  65. package/dist/rules/compat/no-case-attributes.d.ts +12 -0
  66. package/dist/rules/compat/no-case-attributes.js +63 -0
  67. package/dist/rules/compat/no-case-fallthrough.d.ts +12 -0
  68. package/dist/rules/compat/no-case-fallthrough.js +67 -0
  69. package/dist/rules/compat/no-case-modification.d.ts +35 -0
  70. package/dist/rules/compat/no-case-modification.js +86 -0
  71. package/dist/rules/compat/no-coproc.d.ts +12 -0
  72. package/dist/rules/compat/no-coproc.js +47 -0
  73. package/dist/rules/compat/no-declare-g.d.ts +12 -0
  74. package/dist/rules/compat/no-declare-g.js +59 -0
  75. package/dist/rules/compat/no-double-bracket.d.ts +12 -0
  76. package/dist/rules/compat/no-double-bracket.js +52 -0
  77. package/dist/rules/compat/no-epoch-variables.d.ts +12 -0
  78. package/dist/rules/compat/no-epoch-variables.js +57 -0
  79. package/dist/rules/compat/no-fd-variable.d.ts +12 -0
  80. package/dist/rules/compat/no-fd-variable.js +48 -0
  81. package/dist/rules/compat/no-globstar.d.ts +12 -0
  82. package/dist/rules/compat/no-globstar.js +39 -0
  83. package/dist/rules/compat/no-lastpipe-semantics.d.ts +12 -0
  84. package/dist/rules/compat/no-lastpipe-semantics.js +53 -0
  85. package/dist/rules/compat/no-let.d.ts +12 -0
  86. package/dist/rules/compat/no-let.js +62 -0
  87. package/dist/rules/compat/no-mapfile.d.ts +13 -0
  88. package/dist/rules/compat/no-mapfile.js +122 -0
  89. package/dist/rules/compat/no-nameref.d.ts +12 -0
  90. package/dist/rules/compat/no-nameref.js +44 -0
  91. package/dist/rules/compat/no-negative-array-index.d.ts +13 -0
  92. package/dist/rules/compat/no-negative-array-index.js +123 -0
  93. package/dist/rules/compat/no-nounset-empty-array.d.ts +13 -0
  94. package/dist/rules/compat/no-nounset-empty-array.js +158 -0
  95. package/dist/rules/compat/no-parameter-transformation.d.ts +13 -0
  96. package/dist/rules/compat/no-parameter-transformation.js +123 -0
  97. package/dist/rules/compat/no-pipe-all.d.ts +13 -0
  98. package/dist/rules/compat/no-pipe-all.js +68 -0
  99. package/dist/rules/compat/no-printf-time-format.d.ts +12 -0
  100. package/dist/rules/compat/no-printf-time-format.js +99 -0
  101. package/dist/rules/compat/no-source.d.ts +13 -0
  102. package/dist/rules/compat/no-source.js +103 -0
  103. package/dist/rules/compat/no-test-v.d.ts +12 -0
  104. package/dist/rules/compat/no-test-v.js +280 -0
  105. package/dist/rules/compat/no-unicode-escape.d.ts +14 -0
  106. package/dist/rules/compat/no-unicode-escape.js +120 -0
  107. package/dist/rules/compat/no-wait-n.d.ts +12 -0
  108. package/dist/rules/compat/no-wait-n.js +73 -0
  109. package/dist/rules/compat/operator-range.d.ts +10 -0
  110. package/dist/rules/compat/operator-range.js +15 -0
  111. package/dist/rules/compat/posix-absent-command.d.ts +12 -0
  112. package/dist/rules/compat/posix-absent-command.js +134 -0
  113. package/dist/rules/compat/shopt-options.d.ts +9 -0
  114. package/dist/rules/compat/shopt-options.js +63 -0
  115. package/dist/rules/compat/version-gates.d.ts +76 -0
  116. package/dist/rules/compat/version-gates.js +197 -0
  117. package/dist/rules/create-rule.d.ts +91 -0
  118. package/dist/rules/create-rule.js +51 -0
  119. package/dist/rules/create-wrapper-rule.d.ts +166 -0
  120. package/dist/rules/create-wrapper-rule.js +407 -0
  121. package/dist/rules/define-shell-rule.d.ts +184 -0
  122. package/dist/rules/define-shell-rule.js +56 -0
  123. package/dist/rules/index.d.ts +26 -0
  124. package/dist/rules/index.js +88 -0
  125. package/dist/rules/max-script-complexity.d.ts +28 -0
  126. package/dist/rules/max-script-complexity.js +130 -0
  127. package/dist/rules/no-dynamic-invocation.d.ts +35 -0
  128. package/dist/rules/no-dynamic-invocation.js +117 -0
  129. package/dist/rules/no-eval.d.ts +13 -0
  130. package/dist/rules/no-eval.js +89 -0
  131. package/dist/rules/no-masked-exit-status.d.ts +21 -0
  132. package/dist/rules/no-masked-exit-status.js +667 -0
  133. package/dist/rules/no-secret-env-reads.d.ts +24 -0
  134. package/dist/rules/no-secret-env-reads.js +133 -0
  135. package/dist/rules/no-secret-flags.d.ts +32 -0
  136. package/dist/rules/no-secret-flags.js +162 -0
  137. package/dist/rules/no-unchecked-cd.d.ts +44 -0
  138. package/dist/rules/no-unchecked-cd.js +250 -0
  139. package/dist/rules/no-undeclared-commands.d.ts +20 -0
  140. package/dist/rules/no-undeclared-commands.js +115 -0
  141. package/dist/rules/no-unused-declarations.d.ts +23 -0
  142. package/dist/rules/no-unused-declarations.js +259 -0
  143. package/dist/rules/prefer-local.d.ts +16 -0
  144. package/dist/rules/prefer-local.js +249 -0
  145. package/dist/rules/quote-expansions.d.ts +13 -0
  146. package/dist/rules/quote-expansions.js +229 -0
  147. package/dist/rules/require-network-wrapper.d.ts +40 -0
  148. package/dist/rules/require-network-wrapper.js +244 -0
  149. package/dist/rules/require-shebang.d.ts +29 -0
  150. package/dist/rules/require-shebang.js +101 -0
  151. package/dist/rules/require-strict-mode.d.ts +52 -0
  152. package/dist/rules/require-strict-mode.js +177 -0
  153. package/dist/rules/require-tty-guard.d.ts +13 -0
  154. package/dist/rules/require-tty-guard.js +289 -0
  155. package/dist/rules/resolve-declaration-convention.d.ts +96 -0
  156. package/dist/rules/resolve-declaration-convention.js +109 -0
  157. package/dist/rules/resolve-network-wrapper.d.ts +19 -0
  158. package/dist/rules/resolve-network-wrapper.js +116 -0
  159. package/dist/rules/set-options.d.ts +109 -0
  160. package/dist/rules/set-options.js +201 -0
  161. package/dist/rules/shebang-interpreter.d.ts +18 -0
  162. package/dist/rules/shebang-interpreter.js +81 -0
  163. package/dist/rules/shebang-matches-dialect.d.ts +12 -0
  164. package/dist/rules/shebang-matches-dialect.js +69 -0
  165. package/dist/rules/transparent-wrappers.d.ts +52 -0
  166. package/dist/rules/transparent-wrappers.js +99 -0
  167. package/dist/rules/user-pattern.d.ts +63 -0
  168. package/dist/rules/user-pattern.js +184 -0
  169. package/dist/syntax/analyze.d.ts +19 -0
  170. package/dist/syntax/analyze.js +21 -0
  171. package/dist/syntax/index.d.ts +23 -0
  172. package/dist/syntax/index.js +25 -0
  173. package/dist/version.d.ts +0 -1
  174. package/dist/version.js +0 -1
  175. package/docs/compat.md +347 -0
  176. package/docs/custom-rule-tutorial.md +780 -34
  177. package/docs/declaration-conventions.md +478 -0
  178. package/docs/examples/audit-wrapper-pack.mjs +15 -0
  179. package/docs/examples/dep-line-convention.mjs +22 -0
  180. package/docs/examples/eslint.config.mjs +3 -2
  181. package/docs/examples/no-and-or-as-if-rule.mjs +61 -0
  182. package/docs/examples/no-backticks-rule-tester.mjs +106 -0
  183. package/docs/examples/no-backticks-rule.mjs +60 -0
  184. package/docs/examples/no-curl-in-functions-rule.mjs +53 -0
  185. package/docs/examples/no-function-keyword-rule.mjs +59 -0
  186. package/docs/examples/no-listed-commands-rule.ts +63 -0
  187. package/docs/examples/no-rm-rf-rule.mjs +26 -15
  188. package/docs/examples/no-unquoted-rm-variable-rule.ts +54 -0
  189. package/docs/examples/sidecar-convention.mjs +38 -0
  190. package/docs/examples/sidecar-snapshot-convention.mjs +26 -0
  191. package/docs/examples/sorted-declarations-rule.mjs +52 -0
  192. package/docs/reading-nodes.md +159 -0
  193. package/docs/rules/README.md +78 -0
  194. package/docs/rules/_template.md +83 -0
  195. package/docs/rules/compat/no-append-all-redirect.md +87 -0
  196. package/docs/rules/compat/no-associative-arrays.md +95 -0
  197. package/docs/rules/compat/no-case-attributes.md +82 -0
  198. package/docs/rules/compat/no-case-fallthrough.md +90 -0
  199. package/docs/rules/compat/no-case-modification.md +74 -0
  200. package/docs/rules/compat/no-coproc.md +76 -0
  201. package/docs/rules/compat/no-declare-g.md +80 -0
  202. package/docs/rules/compat/no-double-bracket.md +72 -0
  203. package/docs/rules/compat/no-epoch-variables.md +79 -0
  204. package/docs/rules/compat/no-fd-variable.md +78 -0
  205. package/docs/rules/compat/no-globstar.md +74 -0
  206. package/docs/rules/compat/no-lastpipe-semantics.md +73 -0
  207. package/docs/rules/compat/no-let.md +78 -0
  208. package/docs/rules/compat/no-mapfile.md +118 -0
  209. package/docs/rules/compat/no-nameref.md +81 -0
  210. package/docs/rules/compat/no-negative-array-index.md +87 -0
  211. package/docs/rules/compat/no-nounset-empty-array.md +103 -0
  212. package/docs/rules/compat/no-parameter-transformation.md +94 -0
  213. package/docs/rules/compat/no-pipe-all.md +85 -0
  214. package/docs/rules/compat/no-printf-time-format.md +75 -0
  215. package/docs/rules/compat/no-source.md +98 -0
  216. package/docs/rules/compat/no-test-v.md +82 -0
  217. package/docs/rules/compat/no-unicode-escape.md +91 -0
  218. package/docs/rules/compat/no-wait-n.md +85 -0
  219. package/docs/rules/max-script-complexity.md +109 -0
  220. package/docs/rules/no-dynamic-invocation.md +139 -0
  221. package/docs/rules/no-eval.md +106 -0
  222. package/docs/rules/no-masked-exit-status.md +239 -0
  223. package/docs/rules/no-secret-env-reads.md +173 -0
  224. package/docs/rules/no-secret-flags.md +169 -0
  225. package/docs/rules/no-unchecked-cd.md +146 -0
  226. package/docs/rules/no-undeclared-commands.md +141 -0
  227. package/docs/rules/no-unused-declarations.md +216 -0
  228. package/docs/rules/prefer-local.md +221 -0
  229. package/docs/rules/quote-expansions.md +157 -0
  230. package/docs/rules/require-network-wrapper.md +171 -0
  231. package/docs/rules/require-shebang.md +157 -0
  232. package/docs/rules/require-strict-mode.md +253 -0
  233. package/docs/rules/require-tty-guard.md +128 -0
  234. package/docs/rules/shebang-matches-dialect.md +120 -0
  235. package/docs/selectors.md +2 -2
  236. package/docs/shellcheck.md +86 -0
  237. package/docs/wrapper-rules.md +206 -0
  238. package/package.json +26 -20
  239. package/dist/index.d.ts.map +0 -1
  240. package/dist/index.js.map +0 -1
  241. package/dist/language/selector-classes.d.ts.map +0 -1
  242. package/dist/language/selector-classes.js.map +0 -1
  243. package/dist/language/shell-language-options.d.ts.map +0 -1
  244. package/dist/language/shell-language-options.js.map +0 -1
  245. package/dist/language/shell-language.d.ts.map +0 -1
  246. package/dist/language/shell-language.js.map +0 -1
  247. package/dist/language/shell-source-code.d.ts.map +0 -1
  248. package/dist/language/shell-source-code.js.map +0 -1
  249. package/dist/language/source-coordinates.d.ts.map +0 -1
  250. package/dist/language/source-coordinates.js.map +0 -1
  251. package/dist/language/syntax-view.d.ts.map +0 -1
  252. package/dist/language/syntax-view.js.map +0 -1
  253. package/dist/language/word-resolution.d.ts.map +0 -1
  254. package/dist/language/word-resolution.js.map +0 -1
  255. package/dist/version.d.ts.map +0 -1
  256. package/dist/version.js.map +0 -1
@@ -0,0 +1,104 @@
1
+ /**
2
+ * The static types of the lint view's nodes, derived from the faithful syntax
3
+ * interfaces `sh-ast` generates from mvdan/sh.
4
+ *
5
+ * Nothing here restates a field list. Each lint-view node type is computed from
6
+ * the matching `sh-ast` interface by the same rules the runtime projection in
7
+ * `syntax-view.ts` applies to `SYNTAX_SCHEMA`, so an mvdan/sh upgrade that
8
+ * adds, removes, renames or retypes a field changes these types in the same
9
+ * `pnpm install`, and any rule (built-in or third-party) that reads a field
10
+ * that no longer exists stops compiling. The remaining way for types and
11
+ * runtime to part is a change to the rules themselves; `test/lint-view-types-drift.test.ts`
12
+ * compares both against the runtime schema to catch that.
13
+ *
14
+ * The projection rules mirrored here:
15
+ * - `Type` becomes the discriminator `type`; every other field is renamed to
16
+ * its lowercase spelling (`Cmd` → `cmd`, `RsrvWord` → `rsrvword`).
17
+ * - Position fields (`SyntaxPos`) have no counterpart: the view carries
18
+ * `range` and `loc` instead.
19
+ * - A nil child is an absent key (an optional property); a nil slice is an
20
+ * empty array and nil slice elements are dropped.
21
+ * - Go strings are decoded to JavaScript strings; booleans and operator
22
+ * values are copied unchanged.
23
+ */
24
+ import type { Expansion, Node, Replace, Slice, SyntaxPos } from 'sh-ast';
25
+ /** An ESLint location counts UTF-16 units, with both line and column starting at one. @public */
26
+ export interface ShellPosition {
27
+ /** One-based source line, with CRLF treated as one line break. */
28
+ readonly line: number;
29
+ /** One-based UTF-16 column; tabs and grapheme widths are not expanded. */
30
+ readonly column: number;
31
+ }
32
+ /**
33
+ * The lint-view type of one upstream field value `Value`: a record becomes the
34
+ * node type of that record (a union of records, a union of node types), a slice
35
+ * becomes a read-only array of such nodes with its nil elements removed, a Go
36
+ * string becomes a JavaScript `string`, and booleans and operator values stay
37
+ * as they are. Upstream nil (`null`) and typed nils never reach a view, so
38
+ * neither appears in the result.
39
+ *
40
+ * It is the type of every field of every {@link ShellNodeMap} entry, exported
41
+ * so a helper that takes or returns a node's field value can name it.
42
+ *
43
+ * @public
44
+ */
45
+ export type ShellNodeField<Value> = Exclude<Value, null | {
46
+ readonly Type: 'TypedNil';
47
+ }> extends infer Present ? Present extends readonly (infer Element)[] ? readonly ShellNodeField<Element>[] : Present extends {
48
+ readonly Type: infer Type extends ShellNodeType;
49
+ } ? ShellNodeMap[Type] : Present extends string | Uint8Array ? string : Present : never;
50
+ /**
51
+ * Lint-view node types by their `type` discriminator, one entry per upstream
52
+ * syntax record. It is computed from `sh-ast`'s generated interfaces, so it
53
+ * follows the pinned mvdan/sh version.
54
+ *
55
+ * A node has `type`, `range` and `loc`, plus a lowercase field for each
56
+ * non-position upstream field. A field that is a single child that may be nil
57
+ * upstream is optional, because the view omits the key when the child is
58
+ * absent; a slice is always present.
59
+ *
60
+ * `range` is a half-open UTF-16 span of the source text and `loc` its ESLint
61
+ * line/column form; both describe the view, not the upstream byte positions. A
62
+ * node whose line ends in CRLF ends before the `\r` (and a text field such as
63
+ * `value` loses that one trailing `\r`), so a CRLF file reads like its LF
64
+ * twin. Auxiliary records (`Expansion`, `Replace`, `Slice`) take their span
65
+ * from their children.
66
+ *
67
+ * @public
68
+ */
69
+ export type ShellNodeMap = {
70
+ [Record in Exclude<Node, {
71
+ readonly Type: 'TypedNil';
72
+ }> | Expansion | Replace | Slice as Record['Type']]: {
73
+ /** Syntax kind of this node; the discriminator to narrow a ShellNode by. */
74
+ readonly type: Record['Type'];
75
+ /**
76
+ * Half-open UTF-16 span of the node in the source text. Its type is the
77
+ * mutable tuple ESLint's `fixer` methods take, so `node.range` can be passed
78
+ * to `fixer.insertTextBeforeRange` and the others without a cast; treat it
79
+ * as read-only.
80
+ */
81
+ range: [number, number];
82
+ /** ESLint line and column of the span's two ends. */
83
+ readonly loc: {
84
+ readonly start: ShellPosition;
85
+ readonly end: ShellPosition;
86
+ };
87
+ } & {
88
+ readonly [Name in keyof Record & string as Name extends 'Type' ? never : [Record[Name]] extends [SyntaxPos] ? never : null extends Record[Name] ? Exclude<Record[Name], null | {
89
+ readonly Type: 'TypedNil';
90
+ }> extends {
91
+ readonly Type: string;
92
+ } ? never : Lowercase<Name> : Lowercase<Name>]: ShellNodeField<Record[Name]>;
93
+ } & {
94
+ readonly [Name in keyof Record & string as null extends Record[Name] ? Exclude<Record[Name], null | {
95
+ readonly Type: 'TypedNil';
96
+ }> extends {
97
+ readonly Type: string;
98
+ } ? Lowercase<Name> : never : never]?: ShellNodeField<Record[Name]>;
99
+ };
100
+ };
101
+ /** Every value a lint-view node's `type` can hold. @public */
102
+ export type ShellNodeType = keyof ShellNodeMap;
103
+ /** The lint-view node whose `type` is `Type`, for example `ShellNodeOf<'Stmt'>`. @public */
104
+ export type ShellNodeOf<Type extends ShellNodeType> = ShellNodeMap[Type];
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Recognizes files that are certainly not shell scripts but reached
3
+ * `ShellLanguage.parse` anyway, and explains why.
4
+ *
5
+ * `sh.configs.recommended` deliberately carries no `files` key: the user's own
6
+ * globs decide which paths are shell. A config entry that selects
7
+ * `language: 'sh/sh'` without `files` (most often the preset dropped bare into
8
+ * the config array) applies to every file some other entry matches,
9
+ * including ESLint's default JavaScript globs. Without this guard each such
10
+ * file gets an upstream shell syntax error that says nothing about the
11
+ * misconfiguration. Here this plugin's own code is running, so it can name
12
+ * the real cause instead.
13
+ *
14
+ * Scope boundaries:
15
+ *
16
+ * - Detection is by file extension only, against a fixed list of common
17
+ * non-shell source types. Content is never sniffed: an extensionless file,
18
+ * a shell extension, or any extension not on the list goes through the
19
+ * normal shell parse.
20
+ * - Matching is exact and case-sensitive, the same way ESLint's own `files`
21
+ * globs match extensions.
22
+ * - The guard is not configurable.
23
+ */
24
+ import type { FileError } from '@eslint/core';
25
+ /**
26
+ * Returns the diagnostic for a file whose extension marks it as a known
27
+ * non-shell source type, or `undefined` when the file should be parsed as
28
+ * shell. The diagnostic names the file type, says the file is being linted
29
+ * as shell, and shows the fix as a one-line config entry. It sits at line 1,
30
+ * column 1, since it describes the whole file rather than any position in
31
+ * it.
32
+ *
33
+ * @internal
34
+ */
35
+ export declare function nonShellFileError(filePath: string): FileError | undefined;
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Known non-shell source extensions, each mapped to the human name used in
3
+ * the diagnostic. Lookup is by the exact extension, leading dot included.
4
+ */
5
+ const NON_SHELL_FILE_TYPES = new Map([
6
+ ['.js', 'JavaScript'],
7
+ ['.mjs', 'JavaScript'],
8
+ ['.cjs', 'JavaScript'],
9
+ ['.jsx', 'JavaScript (JSX)'],
10
+ ['.ts', 'TypeScript'],
11
+ ['.mts', 'TypeScript'],
12
+ ['.cts', 'TypeScript'],
13
+ ['.tsx', 'TypeScript (TSX)'],
14
+ ['.json', 'JSON'],
15
+ ['.jsonc', 'JSONC'],
16
+ ['.json5', 'JSON5'],
17
+ ['.md', 'Markdown'],
18
+ ['.css', 'CSS'],
19
+ ['.html', 'HTML'],
20
+ ['.vue', 'Vue'],
21
+ ['.svelte', 'Svelte'],
22
+ ['.yml', 'YAML'],
23
+ ['.yaml', 'YAML'],
24
+ ]);
25
+ /**
26
+ * The extension of the last path segment, leading dot included, or `''`
27
+ * when there is none. A leading dot alone (a dotfile such as `.bashrc`) is
28
+ * not an extension. Both `/` and `\` separate segments so Windows paths
29
+ * resolve the same way.
30
+ */
31
+ function extensionOf(filePath) {
32
+ const baseName = filePath.slice(Math.max(filePath.lastIndexOf('/'), filePath.lastIndexOf('\\')) + 1);
33
+ const dot = baseName.lastIndexOf('.');
34
+ return dot > 0 ? baseName.slice(dot) : '';
35
+ }
36
+ /**
37
+ * Returns the diagnostic for a file whose extension marks it as a known
38
+ * non-shell source type, or `undefined` when the file should be parsed as
39
+ * shell. The diagnostic names the file type, says the file is being linted
40
+ * as shell, and shows the fix as a one-line config entry. It sits at line 1,
41
+ * column 1, since it describes the whole file rather than any position in
42
+ * it.
43
+ *
44
+ * @internal
45
+ */
46
+ export function nonShellFileError(filePath) {
47
+ const extension = extensionOf(filePath);
48
+ const fileType = NON_SHELL_FILE_TYPES.get(extension);
49
+ if (fileType === undefined) {
50
+ return undefined;
51
+ }
52
+ return {
53
+ message: `This ${fileType} file (${extension}) is being linted as shell: a config entry selects ` +
54
+ `language 'sh/sh' (for example sh.configs.recommended) without a files list. ` +
55
+ `Wrap the preset in an entry with files, e.g. { files: ['**/*.sh'], extends: [sh.configs.recommended] }`,
56
+ line: 1,
57
+ column: 1,
58
+ };
59
+ }
@@ -50,4 +50,3 @@ export type SelectorClassName = keyof typeof SELECTOR_CLASS_NODE_TYPES;
50
50
  * @public
51
51
  */
52
52
  export declare function matchesSelectorClass(className: string, node: ShNode): boolean;
53
- //# sourceMappingURL=selector-classes.d.ts.map
@@ -49,4 +49,3 @@ const nodeTypesByClassName = new Map(Object.entries(SELECTOR_CLASS_NODE_TYPES).m
49
49
  export function matchesSelectorClass(className, node) {
50
50
  return nodeTypesByClassName.get(className)?.has(node.type) ?? false;
51
51
  }
52
- //# sourceMappingURL=selector-classes.js.map
@@ -18,6 +18,13 @@ export type ShellDialect = 'bash' | 'posix' | 'mksh' | 'bats' | 'zsh';
18
18
  */
19
19
  export interface ShellLanguageOptions extends Record<string, unknown> {
20
20
  readonly dialect?: ShellDialect;
21
+ /**
22
+ * The Bash version (`MAJOR[.MINOR]`, such as `"3.2"`) the scripts must run
23
+ * on. The parser ignores it. The `compat/*` rules read it: unset means the
24
+ * newest version, versions compare numerically by major then minor, and
25
+ * under `dialect: "posix"` Bash-only features are unavailable at any
26
+ * version.
27
+ */
21
28
  readonly shellVersion?: string;
22
29
  /**
23
30
  * The prefix directive/inline-config comments must start with — e.g. the
@@ -99,4 +106,3 @@ export declare function validateShellLanguageOptions(languageOptions: Record<str
99
106
  * @public
100
107
  */
101
108
  export declare function normalizeShellLanguageOptions(languageOptions: Record<string, unknown>): NormalizedShellLanguageOptions;
102
- //# sourceMappingURL=shell-language-options.d.ts.map
@@ -36,9 +36,10 @@ function isValidDirectivePrefix(value) {
36
36
  * string — including `''` and `'not-a-version'` — with zero feedback). This
37
37
  * validates *shape only*, not membership in a known-versions list: a
38
38
  * shape-valid but nonexistent version like `'99.99'` is intentionally
39
- * accepted, per this issue's acceptance criteria and non-goals (deciding
40
- * `shellVersion`'s consumption semantics, and per-dialect known-version
41
- * allowlists, are separate concerns left to the rule layer).
39
+ * accepted, per this issue's acceptance criteria and non-goals (per-dialect
40
+ * known-version allowlists are out of scope). How rules consume
41
+ * `shellVersion` is defined by the `compat/*` rules' gate
42
+ * (`src/rules/compat/version-gates.ts`, documented in `docs/compat.md`).
42
43
  */
43
44
  const SHELL_VERSION_PATTERN = /^\d+(?:\.\d+)?$/u;
44
45
  function isValidShellVersionShape(value) {
@@ -105,4 +106,3 @@ export function normalizeShellLanguageOptions(languageOptions) {
105
106
  },
106
107
  };
107
108
  }
108
- //# sourceMappingURL=shell-language-options.js.map
@@ -60,6 +60,13 @@ export declare class ShellLanguage implements Language {
60
60
  * the `{ok: true, ast}` / `{ok: false, errors}` shape `Language.parse` is
61
61
  * required to return. Never throws — parse failures are reported as
62
62
  * fatal lint messages, per the `Language` contract.
63
+ *
64
+ * A file whose extension marks it as a known non-shell source type (for
65
+ * example `.js` or `.json`) is never shell-parsed: it fails with a single
66
+ * diagnostic explaining that a `language: 'sh/sh'` config entry without
67
+ * `files` reached it, and how to scope that entry. A source in which
68
+ * the parser silently drops text at a U+0080 fails at that character; a
69
+ * U+0080 the parser reads as an ordinary character does not.
63
70
  */
64
71
  parse(file: File, context: {
65
72
  languageOptions: ShellLanguageOptions;
@@ -73,10 +80,11 @@ export declare class ShellLanguage implements Language {
73
80
  * `createSourceCode` is called) — `directivePrefix` is therefore always
74
81
  * present, but `?? 'eslint'` stays as an explicit, honest fallback for
75
82
  * direct construction (e.g. tests that skip the `Language` normalization
76
- * step), mirroring `parse()`'s `dialect` default above.
83
+ * step), mirroring `parse()`'s `dialect` default above. `dialect` and
84
+ * `shellVersion` are forwarded so rules read the same normalized values
85
+ * through `sourceCode.dialect` / `sourceCode.shellVersion`.
77
86
  */
78
87
  createSourceCode(file: File, parseResult: OkParseResult<ShellFile>, context: {
79
88
  languageOptions: ShellLanguageOptions;
80
89
  }): ShellSourceCode;
81
90
  }
82
- //# sourceMappingURL=shell-language.d.ts.map
@@ -1,5 +1,7 @@
1
1
  /** Adapts upstream parsing and diagnostics to ESLint's synchronous Language contract. */
2
2
  import { LangBash, LangBats, LangError, LangMirBSDKorn, LangPOSIX, LangZsh, ParseError, parseWithPositionsSync, } from 'sh-ast';
3
+ import { firstDroppedTextIndex } from './end-of-input-marker.js';
4
+ import { nonShellFileError } from './non-shell-file-guard.js';
3
5
  import { matchesSelectorClass as matchesShellSelectorClass } from './selector-classes.js';
4
6
  import { ShellSourceCode } from './shell-source-code.js';
5
7
  import { SourceCoordinates } from './source-coordinates.js';
@@ -24,6 +26,15 @@ function stripPositionPrefix(message, filename) {
24
26
  const escaped = filename.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
25
27
  return message.replace(new RegExp(`^${escaped}:(?:\\d+|\\?):(?:\\d+|\\?):\\s*`), '');
26
28
  }
29
+ /**
30
+ * Starts the message of a parse failure that is not a syntax error: the parser
31
+ * runtime aborted or the bridge to it failed, so nothing is known about the
32
+ * input. A syntax error never carries it. ESLint itself puts `Parsing error: `
33
+ * in front of every message a language returns from `parse`; this prefix is
34
+ * what tells the two kinds apart after that, for users and for tooling such as
35
+ * the corpus soak.
36
+ */
37
+ const PARSER_RUNTIME_FAILURE_PREFIX = 'eslint-sh parser runtime failure (not a syntax error in this file): ';
27
38
  /** Preserve actual syntax positions while converting byte offsets into this source's UTF-16 diagnostics. */
28
39
  function toFileError(error, source) {
29
40
  if (error instanceof ParseError || error instanceof LangError) {
@@ -40,7 +51,26 @@ function toFileError(error, source) {
40
51
  };
41
52
  }
42
53
  // Bridge/input failures carry no asserted syntax position. The fallback is an ESLint presentation policy.
43
- return { message: error instanceof Error ? error.message : String(error), line: 1, column: 1 };
54
+ const detail = error instanceof Error ? error.message : String(error);
55
+ return { message: `${PARSER_RUNTIME_FAILURE_PREFIX}${detail}`, line: 1, column: 1 };
56
+ }
57
+ /**
58
+ * The fatal parse error for a source in which the parser drops text at a U+0080, at that
59
+ * character, or `undefined` when nothing is dropped. Checked before parsing so a silently
60
+ * truncated tree never reaches the rules. A U+0080 the parser reads as an ordinary character
61
+ * (inside a quoted-delimiter heredoc body, after a backslash) is not an error; one the parser
62
+ * rejects (inside quotes or an expansion) is reported by the parser itself.
63
+ */
64
+ function endOfInputMarkerError(source, options) {
65
+ const index = firstDroppedTextIndex(source, options);
66
+ if (index === undefined)
67
+ return undefined;
68
+ const location = new SourceCoordinates(source).atIndex(index);
69
+ return {
70
+ message: 'the character U+0080 is not supported: the parser reads it as end of input',
71
+ line: location.line,
72
+ column: location.column,
73
+ };
44
74
  }
45
75
  /**
46
76
  * `ShellLanguage` — the `@eslint/core` `Language` implementation for shell
@@ -106,16 +136,32 @@ export class ShellLanguage {
106
136
  * the `{ok: true, ast}` / `{ok: false, errors}` shape `Language.parse` is
107
137
  * required to return. Never throws — parse failures are reported as
108
138
  * fatal lint messages, per the `Language` contract.
139
+ *
140
+ * A file whose extension marks it as a known non-shell source type (for
141
+ * example `.js` or `.json`) is never shell-parsed: it fails with a single
142
+ * diagnostic explaining that a `language: 'sh/sh'` config entry without
143
+ * `files` reached it, and how to scope that entry. A source in which
144
+ * the parser silently drops text at a U+0080 fails at that character; a
145
+ * U+0080 the parser reads as an ordinary character does not.
109
146
  */
110
147
  parse(file, context) {
148
+ const misappliedError = nonShellFileError(file.path);
149
+ if (misappliedError !== undefined) {
150
+ return { ok: false, errors: [misappliedError] };
151
+ }
111
152
  const text = file.body;
112
153
  const dialect = context.languageOptions.dialect ?? 'bash';
154
+ const parseOptions = {
155
+ Variant: DIALECT_VARIANTS[dialect],
156
+ Filename: file.path,
157
+ KeepComments: true,
158
+ };
159
+ const unsupported = endOfInputMarkerError(text, parseOptions);
160
+ if (unsupported !== undefined) {
161
+ return { ok: false, errors: [unsupported] };
162
+ }
113
163
  try {
114
- const parsed = parseWithPositionsSync(text, {
115
- Variant: DIALECT_VARIANTS[dialect],
116
- Filename: file.path,
117
- KeepComments: true,
118
- });
164
+ const parsed = parseWithPositionsSync(text, parseOptions);
119
165
  const ast = adaptSyntaxFile(parsed.File, text, parsed.Positions);
120
166
  return { ok: true, ast };
121
167
  }
@@ -132,14 +178,19 @@ export class ShellLanguage {
132
178
  * `createSourceCode` is called) — `directivePrefix` is therefore always
133
179
  * present, but `?? 'eslint'` stays as an explicit, honest fallback for
134
180
  * direct construction (e.g. tests that skip the `Language` normalization
135
- * step), mirroring `parse()`'s `dialect` default above.
181
+ * step), mirroring `parse()`'s `dialect` default above. `dialect` and
182
+ * `shellVersion` are forwarded so rules read the same normalized values
183
+ * through `sourceCode.dialect` / `sourceCode.shellVersion`.
136
184
  */
137
185
  createSourceCode(file, parseResult, context) {
138
186
  return new ShellSourceCode({
139
187
  text: file.body,
140
188
  ast: parseResult.ast,
141
189
  directivePrefix: context.languageOptions.directivePrefix ?? 'eslint',
190
+ dialect: context.languageOptions.dialect ?? 'bash',
191
+ ...(context.languageOptions.shellVersion === undefined
192
+ ? {}
193
+ : { shellVersion: context.languageOptions.shellVersion }),
142
194
  });
143
195
  }
144
196
  }
145
- //# sourceMappingURL=shell-language.js.map