@eslint-sh/eslint-plugin 0.7.0 → 0.8.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 (141) hide show
  1. package/README.md +14 -8
  2. package/dist/command-harvest/builtins/bash.d.ts +1 -1
  3. package/dist/command-harvest/builtins/bash.js +1 -1
  4. package/dist/command-harvest/builtins/bats.d.ts +2 -0
  5. package/dist/command-harvest/builtins/bats.js +3 -0
  6. package/dist/command-harvest/wrapper-table.d.ts +3 -19
  7. package/dist/command-harvest/wrapper-table.js +8 -38
  8. package/dist/configs/bash-3-2.d.ts +7 -6
  9. package/dist/configs/bash-3-2.js +4 -3
  10. package/dist/configs/posix-portable.d.ts +1 -1
  11. package/dist/declaration-conventions/read-declared-commands.d.ts +2 -2
  12. package/dist/declaration-conventions/read-declared-commands.js +2 -2
  13. package/dist/eslint-plugin-syntax.d.ts +4 -0
  14. package/dist/eslint-plugin.d.ts +86 -40
  15. package/dist/index.d.ts +5 -2
  16. package/dist/index.js +4 -2
  17. package/dist/language/shell-language-options.d.ts +18 -15
  18. package/dist/language/shell-language-options.js +33 -26
  19. package/dist/language/shell-language.d.ts +10 -9
  20. package/dist/language/shell-language.js +18 -35
  21. package/dist/language/shell-source-code.d.ts +17 -9
  22. package/dist/language/shell-source-code.js +53 -12
  23. package/dist/rules/bash-variable-semantics.d.ts +22 -0
  24. package/dist/rules/bash-variable-semantics.js +41 -0
  25. package/dist/rules/compat/create-compat-rule.js +1 -1
  26. package/dist/rules/compat/declaration-options.d.ts +2 -2
  27. package/dist/rules/compat/declaration-options.js +5 -2
  28. package/dist/rules/compat/no-append-all-redirect.js +1 -1
  29. package/dist/rules/compat/no-associative-arrays.js +2 -2
  30. package/dist/rules/compat/no-brace-sequence-padding-step.js +4 -4
  31. package/dist/rules/compat/no-case-attributes.js +3 -3
  32. package/dist/rules/compat/no-case-fallthrough.js +2 -2
  33. package/dist/rules/compat/no-case-modification.js +2 -2
  34. package/dist/rules/compat/no-coproc.js +1 -1
  35. package/dist/rules/compat/no-declare-g.js +3 -3
  36. package/dist/rules/compat/no-double-bracket.js +2 -2
  37. package/dist/rules/compat/no-epoch-variables.js +2 -2
  38. package/dist/rules/compat/no-fd-variable.js +1 -1
  39. package/dist/rules/compat/no-globstar.js +1 -1
  40. package/dist/rules/compat/no-lastpipe-semantics.js +2 -2
  41. package/dist/rules/compat/no-let.js +2 -1
  42. package/dist/rules/compat/no-mapfile.js +4 -2
  43. package/dist/rules/compat/no-nameref.js +3 -3
  44. package/dist/rules/compat/no-negative-array-index.js +39 -9
  45. package/dist/rules/compat/no-nounset-empty-array.js +15 -4
  46. package/dist/rules/compat/no-parameter-transformation.js +2 -2
  47. package/dist/rules/compat/no-pipe-all.js +1 -1
  48. package/dist/rules/compat/no-printf-time-format.js +4 -2
  49. package/dist/rules/compat/no-source.js +2 -1
  50. package/dist/rules/compat/no-test-v.js +4 -2
  51. package/dist/rules/compat/no-unicode-escape.js +2 -2
  52. package/dist/rules/compat/no-wait-n.js +4 -2
  53. package/dist/rules/compat/nounset-array-proof.d.ts +8 -0
  54. package/dist/rules/compat/nounset-array-proof.js +563 -0
  55. package/dist/rules/compat/posix-absent-command.d.ts +7 -8
  56. package/dist/rules/compat/posix-absent-command.js +17 -15
  57. package/dist/rules/compat/shopt-options.js +3 -1
  58. package/dist/rules/compat/version-gates.d.ts +9 -9
  59. package/dist/rules/compat/version-gates.js +13 -13
  60. package/dist/rules/create-wrapper-rule.js +12 -7
  61. package/dist/rules/no-dynamic-invocation.d.ts +0 -8
  62. package/dist/rules/no-dynamic-invocation.js +7 -15
  63. package/dist/rules/no-eval.js +3 -1
  64. package/dist/rules/no-masked-exit-status.js +15 -8
  65. package/dist/rules/no-secret-env-reads.d.ts +4 -3
  66. package/dist/rules/no-secret-env-reads.js +37 -14
  67. package/dist/rules/no-secret-flags.d.ts +2 -1
  68. package/dist/rules/no-secret-flags.js +7 -8
  69. package/dist/rules/no-unchecked-cd.js +3 -1
  70. package/dist/rules/no-undeclared-commands.d.ts +1 -15
  71. package/dist/rules/no-undeclared-commands.js +3 -15
  72. package/dist/rules/no-unused-declarations.d.ts +1 -18
  73. package/dist/rules/no-unused-declarations.js +9 -34
  74. package/dist/rules/prefer-local.js +4 -3
  75. package/dist/rules/quote-expansions.js +3 -28
  76. package/dist/rules/require-network-wrapper.d.ts +2 -17
  77. package/dist/rules/require-network-wrapper.js +33 -4
  78. package/dist/rules/resolve-declaration-convention.d.ts +5 -6
  79. package/dist/rules/resolve-declaration-convention.js +5 -6
  80. package/dist/rules/resolve-network-wrapper.d.ts +19 -0
  81. package/dist/rules/resolve-network-wrapper.js +57 -26
  82. package/dist/rules/resolve-transparent-wrappers.d.ts +43 -0
  83. package/dist/rules/resolve-transparent-wrappers.js +98 -0
  84. package/dist/rules/set-options.d.ts +2 -1
  85. package/dist/rules/set-options.js +17 -5
  86. package/dist/rules/shebang-interpreter.js +4 -2
  87. package/dist/rules/transparent-wrappers.d.ts +4 -3
  88. package/dist/rules/transparent-wrappers.js +5 -4
  89. package/dist/rules/user-pattern.d.ts +5 -3
  90. package/dist/rules/user-pattern.js +6 -4
  91. package/dist/syntax/index.d.ts +4 -0
  92. package/dist/syntax/index.js +4 -0
  93. package/docs/compat.md +76 -27
  94. package/docs/custom-rule-tutorial.md +9 -3
  95. package/docs/examples/no-listed-commands-rule.ts +2 -2
  96. package/docs/rules/README.md +36 -4
  97. package/docs/rules/_template.md +2 -2
  98. package/docs/rules/compat/no-append-all-redirect.md +12 -12
  99. package/docs/rules/compat/no-associative-arrays.md +14 -14
  100. package/docs/rules/compat/no-brace-sequence-padding-step.md +12 -12
  101. package/docs/rules/compat/no-case-attributes.md +13 -13
  102. package/docs/rules/compat/no-case-fallthrough.md +12 -12
  103. package/docs/rules/compat/no-case-modification.md +11 -11
  104. package/docs/rules/compat/no-coproc.md +13 -13
  105. package/docs/rules/compat/no-declare-g.md +13 -13
  106. package/docs/rules/compat/no-double-bracket.md +2 -2
  107. package/docs/rules/compat/no-epoch-variables.md +13 -13
  108. package/docs/rules/compat/no-fd-variable.md +12 -12
  109. package/docs/rules/compat/no-globstar.md +13 -13
  110. package/docs/rules/compat/no-lastpipe-semantics.md +13 -13
  111. package/docs/rules/compat/no-let.md +2 -2
  112. package/docs/rules/compat/no-mapfile.md +16 -16
  113. package/docs/rules/compat/no-nameref.md +13 -13
  114. package/docs/rules/compat/no-negative-array-index.md +14 -14
  115. package/docs/rules/compat/no-nounset-empty-array.md +72 -17
  116. package/docs/rules/compat/no-parameter-transformation.md +19 -19
  117. package/docs/rules/compat/no-pipe-all.md +13 -13
  118. package/docs/rules/compat/no-printf-time-format.md +13 -13
  119. package/docs/rules/compat/no-source.md +2 -2
  120. package/docs/rules/compat/no-test-v.md +12 -12
  121. package/docs/rules/compat/no-unicode-escape.md +13 -13
  122. package/docs/rules/compat/no-wait-n.md +13 -13
  123. package/docs/rules/no-dynamic-invocation.md +17 -12
  124. package/docs/rules/no-eval.md +6 -1
  125. package/docs/rules/no-masked-exit-status.md +4 -0
  126. package/docs/rules/no-secret-env-reads.md +34 -3
  127. package/docs/rules/no-secret-flags.md +19 -2
  128. package/docs/rules/no-unchecked-cd.md +9 -2
  129. package/docs/rules/no-undeclared-commands.md +16 -15
  130. package/docs/rules/no-unused-declarations.md +17 -20
  131. package/docs/rules/prefer-local.md +20 -2
  132. package/docs/rules/quote-expansions.md +8 -5
  133. package/docs/rules/require-network-wrapper.md +22 -17
  134. package/docs/rules/require-shebang.md +10 -4
  135. package/docs/rules/require-strict-mode.md +6 -0
  136. package/docs/shellcheck.md +9 -5
  137. package/docs/transparent-wrappers.md +97 -0
  138. package/docs/wrapper-rules.md +9 -0
  139. package/package.json +2 -2
  140. package/dist/language/end-of-input-marker.d.ts +0 -29
  141. package/dist/language/end-of-input-marker.js +0 -88
@@ -11,7 +11,8 @@ import type { WrapperSpec } from 'sh-ast/analyze';
11
11
  /**
12
12
  * Bash's `builtin [shell-builtin [args …]]`: runs the named builtin, bypassing
13
13
  * any function of the same name. It takes no options of its own, so the word
14
- * after it is the command it runs.
14
+ * after it is the command it runs. Only the exact builtin name has these
15
+ * semantics; an executable path with the same basename is an ordinary program.
15
16
  *
16
17
  * `sh-ast/analyze`'s default wrapper table leaves `builtin` out, so
17
18
  * `builtin "$x"` would otherwise resolve to the static command `builtin`.
@@ -42,8 +43,8 @@ export declare const BUILTIN_WRAPPER: WrapperSpec;
42
43
  * clusters ending in `c` or `s` are listed as exact tokens. Longer clusters
43
44
  * (`-xec`) end in `'unknown-flag'`.
44
45
  *
45
- * Names match exactly, like every `sh-ast/analyze` wrapper: `/bin/bash` is not
46
- * `bash`.
46
+ * Wrapper names also match the final segment of absolute paths (`/bin/bash`).
47
+ * Relative paths require an exact table entry.
47
48
  *
48
49
  * @see https://www.gnu.org/software/bash/manual/bash.html#Invoking-Bash
49
50
  * @see https://pubs.opengroup.org/onlinepubs/9699919799/utilities/sh.html — POSIX `sh`
@@ -1,7 +1,8 @@
1
1
  /**
2
2
  * Bash's `builtin [shell-builtin [args …]]`: runs the named builtin, bypassing
3
3
  * any function of the same name. It takes no options of its own, so the word
4
- * after it is the command it runs.
4
+ * after it is the command it runs. Only the exact builtin name has these
5
+ * semantics; an executable path with the same basename is an ordinary program.
5
6
  *
6
7
  * `sh-ast/analyze`'s default wrapper table leaves `builtin` out, so
7
8
  * `builtin "$x"` would otherwise resolve to the static command `builtin`.
@@ -9,7 +10,7 @@
9
10
  * @see https://www.gnu.org/software/bash/manual/bash.html#Bash-Builtins — `builtin`
10
11
  * @internal
11
12
  */
12
- export const BUILTIN_WRAPPER = { names: ['builtin'] };
13
+ export const BUILTIN_WRAPPER = { names: ['builtin'], shellBuiltin: true };
13
14
  /**
14
15
  * The single-letter shell options that take no operand when a shell is
15
16
  * invoked: every option letter the `set` builtin accepts, plus Bash's
@@ -67,8 +68,8 @@ const SHELL_NO_ARG_LETTERS = [
67
68
  * clusters ending in `c` or `s` are listed as exact tokens. Longer clusters
68
69
  * (`-xec`) end in `'unknown-flag'`.
69
70
  *
70
- * Names match exactly, like every `sh-ast/analyze` wrapper: `/bin/bash` is not
71
- * `bash`.
71
+ * Wrapper names also match the final segment of absolute paths (`/bin/bash`).
72
+ * Relative paths require an exact table entry.
72
73
  *
73
74
  * @see https://www.gnu.org/software/bash/manual/bash.html#Invoking-Bash
74
75
  * @see https://pubs.opengroup.org/onlinepubs/9699919799/utilities/sh.html — POSIX `sh`
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * Compiling regular-expression sources that a rule takes from its options and
3
- * tests against text from the linted script.
3
+ * tests against text from the linted script. Every source uses Unicode (`u`)
4
+ * semantics; callers choose whether matching also ignores case.
4
5
  *
5
6
  * The configuration is trusted, but the subject is not: when the plugin lints
6
7
  * a repository it does not control, the script's author picks the names a
@@ -48,7 +49,7 @@ export declare function findNestedRepetition(source: string): NestedRepetition |
48
49
  /**
49
50
  * Compiles a regular-expression source from a rule option that will be tested
50
51
  * against script-controlled text. Throws a `TypeError` naming the rule and the
51
- * option when the source is not a valid expression, or when it nests one
52
+ * option and source when it is not a valid Unicode expression, or when it nests one
52
53
  * repetition inside another (see {@link findNestedRepetition}). ESLint reports
53
54
  * the error from the rule's `create()`, so it surfaces on the first file linted
54
55
  * with the rule ("Occurred while linting <file>"), not when the config loads.
@@ -56,7 +57,8 @@ export declare function findNestedRepetition(source: string): NestedRepetition |
56
57
  * @param rule - The rule's full name, such as `sh/prefer-local`.
57
58
  * @param option - The option as the user wrote it, such as `patterns[0]`.
58
59
  * @param source - The regular-expression source.
59
- * @param flags - The flags to compile with.
60
+ * @param flags - Rule-specific flags. Unicode semantics are always added;
61
+ * case-insensitivity is preserved without changing the subject or source.
60
62
  *
61
63
  * @internal
62
64
  */
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * Compiling regular-expression sources that a rule takes from its options and
3
- * tests against text from the linted script.
3
+ * tests against text from the linted script. Every source uses Unicode (`u`)
4
+ * semantics; callers choose whether matching also ignores case.
4
5
  *
5
6
  * The configuration is trusted, but the subject is not: when the plugin lints
6
7
  * a repository it does not control, the script's author picks the names a
@@ -153,7 +154,7 @@ export function findNestedRepetition(source) {
153
154
  /**
154
155
  * Compiles a regular-expression source from a rule option that will be tested
155
156
  * against script-controlled text. Throws a `TypeError` naming the rule and the
156
- * option when the source is not a valid expression, or when it nests one
157
+ * option and source when it is not a valid Unicode expression, or when it nests one
157
158
  * repetition inside another (see {@link findNestedRepetition}). ESLint reports
158
159
  * the error from the rule's `create()`, so it surfaces on the first file linted
159
160
  * with the rule ("Occurred while linting <file>"), not when the config loads.
@@ -161,14 +162,15 @@ export function findNestedRepetition(source) {
161
162
  * @param rule - The rule's full name, such as `sh/prefer-local`.
162
163
  * @param option - The option as the user wrote it, such as `patterns[0]`.
163
164
  * @param source - The regular-expression source.
164
- * @param flags - The flags to compile with.
165
+ * @param flags - Rule-specific flags. Unicode semantics are always added;
166
+ * case-insensitivity is preserved without changing the subject or source.
165
167
  *
166
168
  * @internal
167
169
  */
168
170
  export function compileUserPattern(rule, option, source, flags) {
169
171
  let pattern;
170
172
  try {
171
- pattern = new RegExp(source, flags);
173
+ pattern = new RegExp(source, flags.includes('u') ? flags : `${flags}u`);
172
174
  }
173
175
  catch {
174
176
  throw new TypeError(`${rule}: \`${option}\` must be a valid regular expression; got ${JSON.stringify(source)}.`);
@@ -18,6 +18,10 @@
18
18
  * `@eslint-sh/eslint-plugin/syntax/analyze`. That is why these names live on their own subpath
19
19
  * and are not merged into the root.
20
20
  *
21
+ * The low-level `printSync` keeps upstream behavior, including changes to literal vertical tabs
22
+ * and form feeds. It does not apply the high-level content refusal used by `format` and
23
+ * `ShellSourceCode.getPrintedReplacement`; callers printing directly must account for that loss.
24
+ *
21
25
  * @packageDocumentation
22
26
  */
23
27
  export * from 'sh-ast';
@@ -20,6 +20,10 @@
20
20
  * `@eslint-sh/eslint-plugin/syntax/analyze`. That is why these names live on their own subpath
21
21
  * and are not merged into the root.
22
22
  *
23
+ * The low-level `printSync` keeps upstream behavior, including changes to literal vertical tabs
24
+ * and form feeds. It does not apply the high-level content refusal used by `format` and
25
+ * `ShellSourceCode.getPrintedReplacement`; callers printing directly must account for that loss.
26
+ *
23
27
  * @packageDocumentation
24
28
  */
25
29
  export * from 'sh-ast';
package/docs/compat.md CHANGED
@@ -5,14 +5,14 @@ are for scripts that must run on an older Bash, such as the Bash 3.2 that macOS
5
5
  `/bin/bash`, or on a plain POSIX `sh`.
6
6
 
7
7
  `compat/*` is reserved for version gates: rules that read the gate table below and your
8
- `shellVersion`/`dialect`. A rule that checks the file against your config without reading the
8
+ `minBashVersion`/`dialect`. A rule that checks the file against your config without reading the
9
9
  target, such as [`sh/shebang-matches-dialect`](./rules/shebang-matches-dialect.md), lives outside
10
10
  the namespace.
11
11
 
12
- The parser does not do this for you. It accepts everything its grammar knows, whatever target
13
- you configure: Bash mode parses `declare -A` and `mapfile` even with `shellVersion: "3.2"`, and
12
+ The parser does not do this for you. It accepts everything its grammar knows, whatever support requirement
13
+ you configure: Bash mode parses `declare -A` and `mapfile` even with `minBashVersion: "3.2"`, and
14
14
  POSIX mode parses `declare -A` as an ordinary command. Only the `compat/*` rules enforce the
15
- target.
15
+ support requirement.
16
16
 
17
17
  A `compat/*` finding stays reported when the code is behind a runtime version guard
18
18
  (`if (( BASH_VERSINFO[0] >= 4 )); then …`): the rules do not follow control flow. Silence a guarded
@@ -65,11 +65,12 @@ points but 4.3 for U+0080 to U+00FF, because the Bash 4.2 release writes those a
65
65
  instead of UTF-8. `sh/compat/no-nounset-empty-array` gates a behavior, not syntax: every Bash has
66
66
  `"${a[@]}"`, but through 4.3 an empty array aborts the script under `set -u`. `every` means every Bash release has the feature, so only
67
67
  `dialect: "posix"` can lack it: those three are the POSIX-only gates, and the other twenty-two are
68
- the version gates. The `bash-3.2` preset enables the version gates and the `posix-portable` preset
68
+ the version gates. The `bash-3.2` preset enables twenty-one version gates, excluding the opt-in
69
+ `sh/compat/no-nounset-empty-array`, and the `posix-portable` preset
69
70
  enables all twenty-five; see
70
71
  [Presets](#presets).
71
72
 
72
- ## Setting the target
73
+ ## Setting required shell support
73
74
 
74
75
  Every `compat/*` rule reads the same two `languageOptions`, and nothing else. They have no rule
75
76
  options of their own:
@@ -82,7 +83,7 @@ import sh from '@eslint-sh/eslint-plugin';
82
83
  export default defineConfig({
83
84
  files: ['**/*.sh'],
84
85
  extends: [sh.configs.recommended],
85
- languageOptions: { dialect: 'bash', shellVersion: '3.2' },
86
+ languageOptions: { dialect: 'bash', minBashVersion: '3.2' },
86
87
  rules: {
87
88
  'sh/compat/no-associative-arrays': 'error',
88
89
  'sh/compat/no-mapfile': 'error',
@@ -93,28 +94,62 @@ export default defineConfig({
93
94
 
94
95
  A gated feature is available, and its rule reports nothing, according to these rules:
95
96
 
96
- - **`shellVersion` unset means the newest version.** With no `shellVersion`, every Bash feature
97
- is available and no `compat/*` rule reports under `dialect: "bash"`. Set a version to opt in.
98
- - **Versions compare numerically, major first, then minor.** `shellVersion` has the shape
97
+ - **`minBashVersion` is a floor.** `"3.2"` means the script must support Bash 3.2 and
98
+ every newer Bash. Nothing checks for a version being too new. Unset means no Bash
99
+ version constraint, so Bash version gates report nothing under `bash` or `bats`.
100
+ - **Versions compare numerically, major first, then minor.** `minBashVersion` has the shape
99
101
  `MAJOR[.MINOR]`. A missing minor is `0`, so `"4"` is the same as `"4.0"`. Each part is a
100
102
  number, not text, so `"3.10"` is newer than `"3.2"` and still older than `"4.0"`. A feature
101
- is available when `shellVersion` is its minimum version or newer.
102
- - **`dialect: "posix"` makes Bash-only features unavailable at any `shellVersion`.** A POSIX sh
103
- script cannot rely on a feature that only Bash has, whatever version number is configured.
104
- - **`dialect: "bats"` is gated as Bash**, because Bats test files run under Bash.
105
- `shellVersion` is the version of that Bash.
106
- - **`dialect: "mksh"` and `dialect: "zsh"` are not gated.** These shells number their own
107
- releases, so a `shellVersion` there is not a Bash version, and the rules have no data on
108
- which of their releases added which feature. The `compat/*` rules report nothing for them.
103
+ is available when the configured floor is its first Bash release or newer.
104
+ - **Only `bash` and `bats` accept `minBashVersion`.** An omitted `dialect` defaults to
105
+ `bash`. Bats test files run under Bash and use the same floor. With `posix`, `mksh` or
106
+ `zsh`, remove `minBashVersion` or change the dialect to `bash` or `bats`; otherwise
107
+ ESLint rejects the config before linting.
108
+ - **`dialect: "posix"` makes Bash-only features unavailable.** A POSIX sh script cannot
109
+ rely on a feature that only Bash has.
110
+ - **`dialect: "mksh"` and `dialect: "zsh"` are not gated.** The table has no release data
111
+ for these shells, so the `compat/*` rules report nothing for them.
112
+
113
+ ### Migrating from `shellVersion`
114
+
115
+ Replace `languageOptions.shellVersion` with `languageOptions.minBashVersion` and custom
116
+ rule reads of `sourceCode.shellVersion` with `sourceCode.minBashVersion`. The old option
117
+ is removed, with no alias: ESLint rejects it with migration guidance. The normalized and
118
+ JSON option types use `minBashVersion` too. For `posix`, `mksh` and `zsh`, remove the old
119
+ option rather than renaming it, because a Bash floor does not apply to these dialects.
120
+
121
+ Check inherited options too. Extending `sh.configs['bash-3.2']` and then setting
122
+ `languageOptions: { dialect: 'posix' }` retains the preset's Bash 3.2 floor and now
123
+ throws a config error. Extending `[sh.configs['bash-3.2'], sh.configs['posix-portable']]`
124
+ also throws: the later POSIX preset changes the dialect without removing the inherited
125
+ floor. Setting `minBashVersion: undefined` does **not** clear that floor during ESLint's
126
+ config merge, so it does not fix either composition.
127
+
128
+ For scripts that must support POSIX sh, replace the Bash preset with the intended POSIX
129
+ preset, without inheriting `bash-3.2` from any config that applies to those files:
130
+
131
+ ```js
132
+ // eslint.config.js
133
+ import { defineConfig } from 'eslint/config';
134
+ import sh from '@eslint-sh/eslint-plugin';
135
+
136
+ export default defineConfig({
137
+ files: ['**/*.sh'],
138
+ extends: [sh.configs.recommended, sh.configs['posix-portable']],
139
+ });
140
+ ```
109
141
 
110
142
  ## Presets
111
143
 
112
- Two presets name a deployment target and enable every rule that enforces it, so you do not list
113
- the rules yourself:
144
+ Two presets name a deployment target and enable its checks, with the empty-array nounset check
145
+ an explicit opt-in for Bash:
114
146
 
115
- - **`sh.configs['bash-3.2']`** sets `languageOptions: { dialect: 'bash', shellVersion: '3.2' }`
116
- and enables all twenty-two version-gated `compat/*` rules at `error`. Use it for scripts that must run on the Bash
117
- 3.2 that macOS ships as `/bin/bash`.
147
+ - **`sh.configs['bash-3.2']`** sets `languageOptions: { dialect: 'bash', minBashVersion: '3.2' }`
148
+ and enables twenty-one version-gated `compat/*` rules at `error`. Use it for scripts that must run on the Bash
149
+ 3.2 that macOS ships as `/bin/bash`. It leaves `sh/compat/no-nounset-empty-array` off: enable
150
+ that rule explicitly when a script uses `set -u` and arrays that can really be empty. The rule
151
+ cannot prove safety across every caller or helper contract; see its
152
+ [configuration example](./rules/compat/no-nounset-empty-array.md#options).
118
153
  - **`sh.configs['posix-portable']`** reports the Bash-only constructs in the table above, which
119
154
  fail or read as unset on dash, but not `local`, `echo -e` or the others listed under
120
155
  [What `posix-portable` does not report](#what-posix-portable-does-not-report), so a clean run does
@@ -223,6 +258,14 @@ is installed system-wide), and runs every probe in
223
258
  scripts/compat-matrix/run.sh
224
259
  ```
225
260
 
261
+ Builds use at most two compiler jobs and retry serially if parallel `make` fails. Bash 3.2–4.2
262
+ use `config.guess` and `config.sub` from the checksum-pinned Bash 5.3 archive so their configure
263
+ scripts recognize aarch64, including Linux containers on Apple Silicon. Only platform-detection
264
+ helpers are replaced; the shell sources retain their pinned release checksums. The script keeps
265
+ running other shells after a failure, labels the affected column `download failed`,
266
+ `checksum mismatch`, or `build failed`, and exits 1 after printing the table. Invalid arguments,
267
+ including an inaccessible `--probes` directory, exit 2.
268
+
226
269
  A probe is a short script that uses one feature, or one recommended alternative, and prints a known
227
270
  value. "yes" means the shell ran it, exited 0, and printed exactly that value; "no" means it did not.
228
271
  Each Bash column is the x.y release tarball with no upstream patches applied, pinned by SHA-256 in
@@ -282,11 +325,11 @@ the script. The `dash` column is the evidence for the POSIX sh column.
282
325
  | `nounset-empty-array-star-alt` | the suggestion's `"${a[*]-}"` under `set -u` gives one empty word for an empty array, and the joined words otherwise | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | no |
283
326
  | `nounset-empty-array-star` | `"${a[*]}"` and unquoted `${a[@]}` of an empty array under `set -u` | no | no | no | no | no | yes | yes | yes | yes | yes | no |
284
327
  | `nounset-empty-array` | `"${a[@]}"` of an empty array under `set -u` | no | no | no | no | no | yes | yes | yes | yes | yes | no |
285
- | `param-transform-alt-case-operators` | the case messages' Bash 4.0 spellings for a target from 4.0 to 5.0: `"${v^^}"`, `"${v^}"`, `"${V,,}"` | no | yes | yes | yes | yes | yes | yes | yes | yes | yes | no |
328
+ | `param-transform-alt-case-operators` | the case messages' Bash 4.0 spellings when the minimum supported Bash is from 4.0 to 5.0: `"${v^^}"`, `"${v^}"`, `"${V,,}"` | no | yes | yes | yes | yes | yes | yes | yes | yes | yes | no |
286
329
  | `param-transform-alt-declare-p` | the `@A` message's `declare -p`, whose output re-creates the variable | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | no |
287
330
  | `param-transform-alt-printf-b` | the `@E` message's `printf '%b'` expanding backslash escapes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes |
288
331
  | `param-transform-alt-printf-q` | the `@Q` message's `printf '%q'`, whose output `eval` reads back as the same value | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | no |
289
- | `param-transform-alt-tr` | the `tr` examples the `@U`, `@u` and `@L` messages print for a target older than 4.0, written exactly as printed | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes |
332
+ | `param-transform-alt-tr` | the `tr` examples the `@U`, `@u` and `@L` messages print when the minimum supported Bash is older than 4.0, written exactly as printed | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes |
290
333
  | `param-transform-eapa` | the other Bash 4.4 operators `${v@E}`, `${v@P}`, `${v@A}`, `${v@a}` | no | no | no | no | no | yes | yes | yes | yes | yes | no |
291
334
  | `param-transform-k-lower` | `${a[@]@k}` key-value pairs as separate words | no | no | no | no | no | no | no | no | yes | yes | no |
292
335
  | `param-transform-k-upper` | `${a[@]@K}` quoted key-value pairs | no | no | no | no | no | no | no | yes | yes | yes | no |
@@ -315,7 +358,7 @@ How to read it:
315
358
  matrix because it is the release just before the 4.2 boundary of `declare -g` and `lastpipe`.
316
359
  Bash 4.0 is the release just before the 4.1 boundary of `{fd}` redirections.
317
360
  - **POSIX-only gates.** The rows for `[[`, `let` and `source`, the rule table's `every`, are "yes" on
318
- every Bash in the matrix and "no" under dash, so no `shellVersion` can change whether they report.
361
+ every Bash in the matrix and "no" under dash, so no `minBashVersion` can change whether they report.
319
362
  dash runs a script past each one without a syntax error, which is why a shell's own version check
320
363
  can guard them; see [A branch for one shell](#a-branch-for-one-shell).
321
364
  - **Parameter transformations.** `@Q`, `@E`, `@P`, `@A` and `@a` start working in 4.4; `@U`, `@u`,
@@ -340,7 +383,7 @@ How to read it:
340
383
  running `scripts/compat-matrix/run.sh`.
341
384
  - **Alternatives.** Every alternative a message recommends for a Bash target works on Bash 3.2,
342
385
  except `param-transform-alt-case-operators` (`"${v^^}"` and its twins), which the
343
- `no-parameter-transformation` message offers only for a target from 4.0 to 5.0, where it works.
386
+ `no-parameter-transformation` message offers only when the minimum supported Bash is from 4.0 to 5.0, where it works.
344
387
  The ones that are "no" under dash (`< <(cmd)`, the `mapfile` suggestion's array loop, the
345
388
  `${arr[$((${#arr[@]}-1))]}` count, `printf -v`, `printf '%q'`, `declare -p`, the `${a[@]+…}` and
346
389
  `${a[*]-}` guards, `$'\303\251'`) use Bash syntax and appear only in Bash-target messages; the
@@ -351,4 +394,10 @@ How to read it:
351
394
  `date`. `%s` is not in POSIX `date`; the message says so.
352
395
  - **`jq`.** The `no-associative-arrays` advice runs an external program, so its row shows that the
353
396
  command as written works wherever `jq` is installed, not that a shell provides it.
397
+ - **Plain assignments.** `declare-g-alt-assign` confirms that `name=value` and `export name=value`
398
+ inside a function leave values visible after the function returns when no caller has a
399
+ same-named local. It is not a discriminating test of `declare -g`: ordinary assignment follows
400
+ a caller's local scope, whereas `declare -g` explicitly targets the global in Bash 4.3–5.3 but
401
+ assigns a same-named caller local in Bash 4.2, leaving the global unset. The row does not
402
+ establish that the plain-assignment advice is interchangeable with `declare -g` in that case.
354
403
 
@@ -421,6 +421,11 @@ with, so you don't install `sh-ast` and its version always matches the records `
421
421
  They track the plugin's pinned `sh-ast` minor: an `sh-ast` 0.x minor bump is a minor release of the
422
422
  plugin.
423
423
 
424
+ The low-level `/syntax` export `printSync` retains upstream behavior: literal vertical tabs and
425
+ form feeds can change during printing, including inside quotes. It does not apply the high-level
426
+ content refusal used by `format` or `getPrintedReplacement`. For an ESLint printed fix, use
427
+ `getPrintedReplacement`, which refuses these values with `TypeError`.
428
+
424
429
  ## 8. Printed fixes
425
430
 
426
431
  Some fixes are easier to describe as an edit to the syntax than as new text. Turning
@@ -437,7 +442,8 @@ into an ESLint fix:
437
442
  returns `{ range, text }`, ready for `fixer.replaceTextRange(range, text)`. The range is
438
443
  `node.range`, or the whole file when `edited` is a `File`. `edited` must be a root `printSync`
439
444
  accepts (`File`, `Stmt`, `Word`, `Assign`, a `Command` or a `WordPart`), and output that is not
440
- valid UTF-8 throws a `TypeError` instead of becoming a fix.
445
+ valid UTF-8 or edited syntax containing literal vertical tabs or form feeds throws a `TypeError`
446
+ instead of becoming a fix. A `File`'s `Name` is unprinted metadata and does not trigger that refusal.
441
447
 
442
448
  `docs/examples/no-backticks-rule.mjs` reports backtick command substitution and fixes it to `$(…)`:
443
449
 
@@ -714,13 +720,13 @@ import type { ShellCommandSite, ShellRuleContext } from '@eslint-sh/eslint-plugi
714
720
  /** The rule's one options object; `commands` defaults to `[]`. */
715
721
  type Options = [{ commands?: string[] }?];
716
722
 
717
- /** Reports `site` when the command it runs, after wrappers, is in `listed`. */
723
+ /** Reports the program named by `site`, after wrappers, including interpreters running strings. */
718
724
  function reportIfListed(
719
725
  context: ShellRuleContext<Options, 'listed'>,
720
726
  site: ShellCommandSite,
721
727
  listed: ReadonlySet<string>,
722
728
  ): void {
723
- const { effective } = site.resolveInvokedCommand();
729
+ const { effective } = site.resolveInvokedCommand({ embeddedCommandInterpreters: [] });
724
730
  if (effective.static && listed.has(effective.text)) {
725
731
  context.report({ node: site.node, messageId: 'listed', data: { name: effective.text } });
726
732
  }
@@ -19,13 +19,13 @@ import type { ShellCommandSite, ShellRuleContext } from '@eslint-sh/eslint-plugi
19
19
  /** The rule's one options object; `commands` defaults to `[]`. */
20
20
  type Options = [{ commands?: string[] }?];
21
21
 
22
- /** Reports `site` when the command it runs, after wrappers, is in `listed`. */
22
+ /** Reports the program named by `site`, after wrappers, including interpreters running strings. */
23
23
  function reportIfListed(
24
24
  context: ShellRuleContext<Options, 'listed'>,
25
25
  site: ShellCommandSite,
26
26
  listed: ReadonlySet<string>,
27
27
  ): void {
28
- const { effective } = site.resolveInvokedCommand();
28
+ const { effective } = site.resolveInvokedCommand({ embeddedCommandInterpreters: [] });
29
29
  if (effective.static && listed.has(effective.text)) {
30
30
  context.report({ node: site.node, messageId: 'listed', data: { name: effective.text } });
31
31
  }
@@ -27,7 +27,7 @@ See the [canonical quickstart](https://github.com/mike-north/eslint-sh/blob/main
27
27
  | [`sh/compat/no-mapfile`](./compat/no-mapfile.md) | `bash-3.2`, `posix-portable` | No | Yes | Disallow `mapfile` / `readarray` when the target shell lacks them, suggesting a read loop. |
28
28
  | [`sh/compat/no-nameref`](./compat/no-nameref.md) | `bash-3.2`, `posix-portable` | No | No | Disallow namerefs (`declare -n`, `local -n`) when the target shell lacks them. |
29
29
  | [`sh/compat/no-negative-array-index`](./compat/no-negative-array-index.md) | `bash-3.2`, `posix-portable` | No | Yes | Disallow negative array subscripts when the target shell lacks them, suggesting a length-based index. |
30
- | [`sh/compat/no-nounset-empty-array`](./compat/no-nounset-empty-array.md) | `bash-3.2`, `posix-portable` | No | Yes | Disallow whole-array expansions under `set -u` when the target shell aborts on an empty array, suggesting a guarded form. |
30
+ | [`sh/compat/no-nounset-empty-array`](./compat/no-nounset-empty-array.md) | `posix-portable` | No | Yes | Disallow whole-array expansions under `set -u` when the target shell aborts on an empty array, suggesting a guarded form. |
31
31
  | [`sh/compat/no-parameter-transformation`](./compat/no-parameter-transformation.md) | `bash-3.2`, `posix-portable` | No | No | Disallow `${v@op}` transformations when the target shell lacks the operator. |
32
32
  | [`sh/compat/no-pipe-all`](./compat/no-pipe-all.md) | `bash-3.2`, `posix-portable` | No | Yes | Disallow the stderr-merging pipe (pipe-ampersand) when the target shell lacks it, suggesting an explicit `2>&1` before the pipe. |
33
33
  | [`sh/compat/no-printf-time-format`](./compat/no-printf-time-format.md) | `bash-3.2`, `posix-portable` | No | No | Disallow `printf`'s `%(fmt)T` date format when the target shell lacks it. |
@@ -60,9 +60,11 @@ describes edits available through `eslint --fix`. `quote-expansions` suggests qu
60
60
  fixing because only the script's author knows whether the splitting was intended.
61
61
 
62
62
  The version-gated `compat/*` rules (`compat/no-append-all-redirect`, `compat/no-associative-arrays`, `compat/no-brace-sequence-padding-step`, `compat/no-case-attributes`, `compat/no-case-fallthrough`, `compat/no-case-modification`, `compat/no-coproc`, `compat/no-declare-g`, `compat/no-epoch-variables`, `compat/no-fd-variable`, `compat/no-globstar`, `compat/no-lastpipe-semantics`, `compat/no-mapfile`, `compat/no-nameref`, `compat/no-negative-array-index`, `compat/no-nounset-empty-array`, `compat/no-parameter-transformation`, `compat/no-pipe-all`, `compat/no-printf-time-format`, `compat/no-test-v`, `compat/no-unicode-escape`, `compat/no-wait-n`) report
63
- shell features that your target shell lacks, as set by `languageOptions.shellVersion` and
64
- `dialect`. They report nothing when `shellVersion` is unset, so the `bash-3.2` preset sets it to `3.2`
65
- and the `posix-portable` preset sets `dialect: "posix"`; each enables all of them.
63
+ shell features that your target shell lacks, as set by `languageOptions.minBashVersion` and
64
+ `dialect`. They report nothing when `minBashVersion` is unset, so the `bash-3.2` preset sets it to `3.2`
65
+ and the `posix-portable` preset sets `dialect: "posix"`. `bash-3.2` enables all except
66
+ `compat/no-nounset-empty-array`, which is opt-in for scripts using `set -u` on Bash below 4.4
67
+ with arrays that can really be empty; `posix-portable` retains all of them.
66
68
  See [version compatibility](../compat.md). `compat/*` means target gates only; `shebang-matches-dialect`
67
69
  compares the file with your config whatever the target, so it lives outside that namespace. `posix-portable` also enables
68
70
  `shebang-matches-dialect`, `require-strict-mode` (as `set -eu`) and the POSIX-only gates
@@ -77,3 +79,33 @@ To run these rules beside shellcheck, see [shellcheck](../shellcheck.md).
77
79
  `require-network-wrapper` reports nothing until you set its `wrapper` option (see
78
80
  [wrapper rules](../wrapper-rules.md)). Each linked rule page documents its options, limitations,
79
81
  and relationship to shellcheck.
82
+
83
+ ## Regular-expression options
84
+
85
+ Every regular-expression source in a rule option is compiled with JavaScript's `u` flag
86
+ (Unicode). `sh/no-secret-flags` also uses `i`, preserving case-insensitive matching for
87
+ both default and custom patterns. `sh/no-secret-env-reads` and `sh/prefer-local` match
88
+ case-sensitively. The original name and pattern are used without lower-casing either.
89
+ Thus `patterns: ['token']` catches `--TOKEN` in `no-secret-flags`, but does not catch
90
+ `$GITHUB_TOKEN` in `no-secret-env-reads`. `allowPattern: '^g_'` exempts `g_cache`, but not
91
+ `G_cache`, in `prefer-local`. Sources are strings, without `/` delimiters or flag suffixes;
92
+ write `\\p{Lu}` in a JavaScript string to match an uppercase Unicode letter.
93
+
94
+ The shared compiler validates syntax with these actual flags, then applies the
95
+ [pattern-safety guard](./no-secret-flags.md#patterns-run-against-script-controlled-text).
96
+ All regex syntax errors, including `(`, fail when the enabled rule first runs on a file.
97
+ The error names the rule, option (including the array index) and pattern; ESLint reports
98
+ "Occurred while linting <file>" and exits with status 2.
99
+
100
+ Config resolution does not compile regex sources: `--print-config` will not catch any regex syntax errors.
101
+ The schemas still check option types and array lengths, but deliberately omit the `regex`
102
+ format. ESLint validates that format without flags, which would reject Unicode-valid sources
103
+ such as `^[\u{41}-\u{5A}]+$` (an uppercase-letter range under `u`). Keeping compilation in the
104
+ rule allows these sources and gives every syntax error the same first-rule-run timing.
105
+
106
+ Migration: sources accepted without `u` may now fail, including identity escapes such as
107
+ `\-` outside a character class, `\q` (also invalid inside `[\q]`), and incomplete quantifiers
108
+ such as `a{,5}`. Use `-`, `q` or `a\\{,5\\}` respectively in JavaScript strings for those
109
+ literal characters. An escaped hyphen **inside** a class (`[\-]`, written `'[\\-]'` in a
110
+ JavaScript string) remains valid. Unicode property escapes (`\p{Lu}`) and code point escapes
111
+ (`\u{1F510}`) now have their Unicode meaning in every regex option.
@@ -15,7 +15,7 @@
15
15
  `{"options": {"maxNesting": 2}, "dialect": "bash"}`, then the usual closing. See any
16
16
  existing page, e.g. `max-script-complexity.md`, for the exact spelling.
17
17
 
18
- Keys: `options` (the rule's options value), `dialect`, `shellVersion`, `convention`
18
+ Keys: `options` (the rule's options value), `dialect`, `minBashVersion`, `convention`
19
19
  (`"commentPragma"`) and `filename`. Under `### Incorrect` you may also state what the prose
20
20
  claims exactly: `findings` (a count), `messageIds` (in report order), `output` (the source
21
21
  after autofix) and `suggestionOutputs`. A typo in the annotation fails the test.
@@ -38,7 +38,7 @@
38
38
 
39
39
  <Why this policy matters for shell scripts, and exactly what the rule reports. Say which
40
40
  node types or constructs it inspects and which it deliberately ignores. If the rule depends on
41
- `languageOptions` (`dialect`, `shellVersion`), say how.>
41
+ `languageOptions` (`dialect`, `minBashVersion`), say how.>
42
42
 
43
43
  ## Options
44
44
 
@@ -21,15 +21,15 @@ Under `dialect: "posix"` the parser itself rejects `&>>`, so a POSIX file that u
21
21
  This is a version-compat rule. Whether it reports depends only on `languageOptions`, as
22
22
  described in [version compatibility](../../compat.md):
23
23
 
24
- - `shellVersion` unset: never reports. An unset version means the newest Bash.
25
- - `dialect: "bash"` (the default) or `"bats"`: reports when `shellVersion` is older than `4.0`.
24
+ - `minBashVersion` unset: no Bash version constraint, so no report under `bash` or `bats`.
25
+ - `dialect: "bash"` (the default) or `"bats"`: reports when `minBashVersion` is older than `4.0`.
26
26
  - `dialect: "mksh"` or `"zsh"`: never reports.
27
27
 
28
28
  ### Suggestion
29
29
 
30
30
  A suggestion rewrites the redirection:
31
31
 
32
- <!-- transform: {"kind": "suggestion", "dialect": "bash", "shellVersion": "3.2"} -->
32
+ <!-- transform: {"kind": "suggestion", "dialect": "bash", "minBashVersion": "3.2"} -->
33
33
  ```sh
34
34
  build &>> build.log
35
35
  # becomes
@@ -38,12 +38,12 @@ build >> build.log 2>&1
38
38
 
39
39
  ## Options
40
40
 
41
- This rule has no options. Set the target through `languageOptions`:
41
+ This rule has no options. Set the required support through `languageOptions`:
42
42
 
43
43
  ```js
44
44
  // eslint.config.js
45
45
  {
46
- languageOptions: { dialect: 'bash', shellVersion: '3.2' },
46
+ languageOptions: { dialect: 'bash', minBashVersion: '3.2' },
47
47
  rules: {
48
48
  'sh/compat/no-append-all-redirect': 'error',
49
49
  },
@@ -54,29 +54,29 @@ This rule has no options. Set the target through `languageOptions`:
54
54
 
55
55
  ### Incorrect
56
56
 
57
- With `shellVersion: "3.2"`:
57
+ With `minBashVersion: "3.2"`:
58
58
 
59
- <!-- example: {"dialect": "bash", "shellVersion": "3.2", "findings": 1} -->
59
+ <!-- example: {"dialect": "bash", "minBashVersion": "3.2", "findings": 1} -->
60
60
  ```sh
61
61
  build &>> build.log
62
62
  ```
63
63
 
64
64
  ### Correct
65
65
 
66
- With `shellVersion: "3.2"`:
66
+ With `minBashVersion: "3.2"`:
67
67
 
68
- <!-- example: {"dialect": "bash", "shellVersion": "3.2"} -->
68
+ <!-- example: {"dialect": "bash", "minBashVersion": "3.2"} -->
69
69
  ```sh
70
70
  build >> build.log 2>&1
71
71
  build &> build.log
72
72
  echo "use &>> on Bash 4"
73
73
  ```
74
74
 
75
- With `shellVersion: "4.0"` or newer, or with no `shellVersion`, `&>>` is fine.
75
+ With `minBashVersion: "4.0"` or newer, or with no `minBashVersion`, `&>>` is fine.
76
76
 
77
77
  ## When not to use it
78
78
 
79
- Turn the rule off when your scripts never run on Bash older than 4.0, or leave `shellVersion`
79
+ Turn the rule off when your scripts never run on Bash older than 4.0, or leave `minBashVersion`
80
80
  unset, which makes the rule report nothing.
81
81
 
82
82
  ## Relationship to shellcheck
@@ -84,4 +84,4 @@ unset, which makes the rule report nothing.
84
84
  eslint-sh is a convention and contract linter: a policy layer, where shellcheck is the gotcha
85
85
  layer. Run shellcheck beside it.
86
86
 
87
- shellcheck 0.11.0 reports nothing for `cmd &>> log` under either `-s sh` or `-s bash`. (`cmd &> log` is different: it reports SC3020 under `-s sh`.) So shellcheck gives no finding for `&>>` in a POSIX sh script or in a Bash 3.2 target. This rule exists for both.
87
+ shellcheck 0.11.0 reports nothing for `cmd &>> log` under either `-s sh` or `-s bash`. (`cmd &> log` is different: it reports SC3020 under `-s sh`.) So shellcheck gives no finding for `&>>` in a POSIX sh script or in a script that must support Bash 3.2. This rule exists for both.
@@ -1,6 +1,6 @@
1
1
  # `sh/compat/no-associative-arrays`
2
2
 
3
- Disallow associative arrays (`declare -A`, `typeset -A`) in scripts whose target shell lacks
3
+ Disallow associative arrays (`declare -A`, `typeset -A`) in scripts that must support a shell lacking
4
4
  them: Bash older than 4.0, including the Bash 3.2 that macOS ships as `/bin/bash`, and POSIX sh.
5
5
 
6
6
  | Configs | Fixable | Languages |
@@ -17,9 +17,9 @@ return the wrong values without an error.
17
17
  This is a version-compat rule. Whether it reports depends only on `languageOptions`, as
18
18
  described in [version compatibility](../../compat.md):
19
19
 
20
- - `shellVersion` unset: never reports. An unset version means the newest Bash.
21
- - `dialect: "bash"` (the default) or `"bats"`: reports when `shellVersion` is older than `4.0`.
22
- - `dialect: "posix"`: reports at any `shellVersion`, because POSIX sh has no arrays.
20
+ - `minBashVersion` unset: no Bash version constraint, so no report under `bash` or `bats`.
21
+ - `dialect: "bash"` (the default) or `"bats"`: reports when `minBashVersion` is older than `4.0`.
22
+ - `dialect: "posix"`: reports regardless of Bash releases, because POSIX sh has no arrays.
23
23
  - `dialect: "mksh"` or `"zsh"`: never reports.
24
24
 
25
25
  The rule reads the options of every `declare` and `typeset` command, up to the first operand,
@@ -37,12 +37,12 @@ The rule deliberately ignores:
37
37
 
38
38
  ## Options
39
39
 
40
- This rule has no options. Set the target through `languageOptions`:
40
+ This rule has no options. Set the required support through `languageOptions`:
41
41
 
42
42
  ```js
43
43
  // eslint.config.js
44
44
  {
45
- languageOptions: { dialect: 'bash', shellVersion: '3.2' },
45
+ languageOptions: { dialect: 'bash', minBashVersion: '3.2' },
46
46
  rules: {
47
47
  'sh/compat/no-associative-arrays': 'error',
48
48
  },
@@ -53,9 +53,9 @@ This rule has no options. Set the target through `languageOptions`:
53
53
 
54
54
  ### Incorrect
55
55
 
56
- With `shellVersion: "3.2"`:
56
+ With `minBashVersion: "3.2"`:
57
57
 
58
- <!-- example: {"dialect": "bash", "shellVersion": "3.2", "findings": 4} -->
58
+ <!-- example: {"dialect": "bash", "minBashVersion": "3.2", "findings": 4} -->
59
59
  ```sh
60
60
  declare -A ports # associative arrays need Bash 4.0
61
61
  typeset -A ports # the same declaration through typeset
@@ -65,9 +65,9 @@ builtin declare -A ports # reached through the builtin wrapper
65
65
 
66
66
  ### Correct
67
67
 
68
- With `shellVersion: "3.2"`:
68
+ With `minBashVersion: "3.2"`:
69
69
 
70
- <!-- example: {"dialect": "bash", "shellVersion": "3.2"} -->
70
+ <!-- example: {"dialect": "bash", "minBashVersion": "3.2"} -->
71
71
  ```sh
72
72
  # Keep the mapping as JSON and look keys up with jq.
73
73
  port=$(jq -r --arg k "$service" '.[$k]' ports.json)
@@ -75,16 +75,16 @@ port=$(jq -r --arg k "$service" '.[$k]' ports.json)
75
75
  declare -a hosts # indexed arrays exist in Bash 3.2
76
76
  ```
77
77
 
78
- With `shellVersion: "4.0"` or newer, or with no `shellVersion`:
78
+ With `minBashVersion: "4.0"` or newer, or with no `minBashVersion`:
79
79
 
80
- <!-- example: {"dialect": "bash", "shellVersion": "4.0"} -->
80
+ <!-- example: {"dialect": "bash", "minBashVersion": "4.0"} -->
81
81
  ```sh
82
82
  declare -A ports
83
83
  ```
84
84
 
85
85
  ## When not to use it
86
86
 
87
- Turn the rule off when your scripts never run on Bash older than 4.0, or leave `shellVersion`
87
+ Turn the rule off when your scripts never run on Bash older than 4.0, or leave `minBashVersion`
88
88
  unset, which makes the rule report nothing.
89
89
 
90
90
  ## Relationship to shellcheck
@@ -92,4 +92,4 @@ unset, which makes the rule report nothing.
92
92
  eslint-sh is a convention and contract linter: a policy layer, where shellcheck is the gotcha
93
93
  layer. Run shellcheck beside it.
94
94
 
95
- shellcheck 0.11.0 reports the command, not the option: under `-s sh`, `declare -A` draws SC3044 (`declare` is undefined in POSIX sh), and `local -A` draws SC3043. Under `-s bash` it reports nothing. It has no way to target a Bash version, so it reports nothing for `declare -A` in a script that must run on Bash 3.2. This rule exists for that target.
95
+ shellcheck 0.11.0 reports the command, not the option: under `-s sh`, `declare -A` draws SC3044 (`declare` is undefined in POSIX sh), and `local -A` draws SC3043. Under `-s bash` it reports nothing. It has no way to check support for a minimum Bash version, so it reports nothing for `declare -A` in a script that must run on Bash 3.2. This rule checks that support requirement.