@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.
- package/README.md +14 -8
- package/dist/command-harvest/builtins/bash.d.ts +1 -1
- package/dist/command-harvest/builtins/bash.js +1 -1
- package/dist/command-harvest/builtins/bats.d.ts +2 -0
- package/dist/command-harvest/builtins/bats.js +3 -0
- package/dist/command-harvest/wrapper-table.d.ts +3 -19
- package/dist/command-harvest/wrapper-table.js +8 -38
- package/dist/configs/bash-3-2.d.ts +7 -6
- package/dist/configs/bash-3-2.js +4 -3
- package/dist/configs/posix-portable.d.ts +1 -1
- package/dist/declaration-conventions/read-declared-commands.d.ts +2 -2
- package/dist/declaration-conventions/read-declared-commands.js +2 -2
- package/dist/eslint-plugin-syntax.d.ts +4 -0
- package/dist/eslint-plugin.d.ts +86 -40
- package/dist/index.d.ts +5 -2
- package/dist/index.js +4 -2
- package/dist/language/shell-language-options.d.ts +18 -15
- package/dist/language/shell-language-options.js +33 -26
- package/dist/language/shell-language.d.ts +10 -9
- package/dist/language/shell-language.js +18 -35
- package/dist/language/shell-source-code.d.ts +17 -9
- package/dist/language/shell-source-code.js +53 -12
- package/dist/rules/bash-variable-semantics.d.ts +22 -0
- package/dist/rules/bash-variable-semantics.js +41 -0
- package/dist/rules/compat/create-compat-rule.js +1 -1
- package/dist/rules/compat/declaration-options.d.ts +2 -2
- package/dist/rules/compat/declaration-options.js +5 -2
- package/dist/rules/compat/no-append-all-redirect.js +1 -1
- package/dist/rules/compat/no-associative-arrays.js +2 -2
- package/dist/rules/compat/no-brace-sequence-padding-step.js +4 -4
- package/dist/rules/compat/no-case-attributes.js +3 -3
- package/dist/rules/compat/no-case-fallthrough.js +2 -2
- package/dist/rules/compat/no-case-modification.js +2 -2
- package/dist/rules/compat/no-coproc.js +1 -1
- package/dist/rules/compat/no-declare-g.js +3 -3
- package/dist/rules/compat/no-double-bracket.js +2 -2
- package/dist/rules/compat/no-epoch-variables.js +2 -2
- package/dist/rules/compat/no-fd-variable.js +1 -1
- package/dist/rules/compat/no-globstar.js +1 -1
- package/dist/rules/compat/no-lastpipe-semantics.js +2 -2
- package/dist/rules/compat/no-let.js +2 -1
- package/dist/rules/compat/no-mapfile.js +4 -2
- package/dist/rules/compat/no-nameref.js +3 -3
- package/dist/rules/compat/no-negative-array-index.js +39 -9
- package/dist/rules/compat/no-nounset-empty-array.js +15 -4
- package/dist/rules/compat/no-parameter-transformation.js +2 -2
- package/dist/rules/compat/no-pipe-all.js +1 -1
- package/dist/rules/compat/no-printf-time-format.js +4 -2
- package/dist/rules/compat/no-source.js +2 -1
- package/dist/rules/compat/no-test-v.js +4 -2
- package/dist/rules/compat/no-unicode-escape.js +2 -2
- package/dist/rules/compat/no-wait-n.js +4 -2
- package/dist/rules/compat/nounset-array-proof.d.ts +8 -0
- package/dist/rules/compat/nounset-array-proof.js +563 -0
- package/dist/rules/compat/posix-absent-command.d.ts +7 -8
- package/dist/rules/compat/posix-absent-command.js +17 -15
- package/dist/rules/compat/shopt-options.js +3 -1
- package/dist/rules/compat/version-gates.d.ts +9 -9
- package/dist/rules/compat/version-gates.js +13 -13
- package/dist/rules/create-wrapper-rule.js +12 -7
- package/dist/rules/no-dynamic-invocation.d.ts +0 -8
- package/dist/rules/no-dynamic-invocation.js +7 -15
- package/dist/rules/no-eval.js +3 -1
- package/dist/rules/no-masked-exit-status.js +15 -8
- package/dist/rules/no-secret-env-reads.d.ts +4 -3
- package/dist/rules/no-secret-env-reads.js +37 -14
- package/dist/rules/no-secret-flags.d.ts +2 -1
- package/dist/rules/no-secret-flags.js +7 -8
- package/dist/rules/no-unchecked-cd.js +3 -1
- package/dist/rules/no-undeclared-commands.d.ts +1 -15
- package/dist/rules/no-undeclared-commands.js +3 -15
- package/dist/rules/no-unused-declarations.d.ts +1 -18
- package/dist/rules/no-unused-declarations.js +9 -34
- package/dist/rules/prefer-local.js +4 -3
- package/dist/rules/quote-expansions.js +3 -28
- package/dist/rules/require-network-wrapper.d.ts +2 -17
- package/dist/rules/require-network-wrapper.js +33 -4
- package/dist/rules/resolve-declaration-convention.d.ts +5 -6
- package/dist/rules/resolve-declaration-convention.js +5 -6
- package/dist/rules/resolve-network-wrapper.d.ts +19 -0
- package/dist/rules/resolve-network-wrapper.js +57 -26
- package/dist/rules/resolve-transparent-wrappers.d.ts +43 -0
- package/dist/rules/resolve-transparent-wrappers.js +98 -0
- package/dist/rules/set-options.d.ts +2 -1
- package/dist/rules/set-options.js +17 -5
- package/dist/rules/shebang-interpreter.js +4 -2
- package/dist/rules/transparent-wrappers.d.ts +4 -3
- package/dist/rules/transparent-wrappers.js +5 -4
- package/dist/rules/user-pattern.d.ts +5 -3
- package/dist/rules/user-pattern.js +6 -4
- package/dist/syntax/index.d.ts +4 -0
- package/dist/syntax/index.js +4 -0
- package/docs/compat.md +76 -27
- package/docs/custom-rule-tutorial.md +9 -3
- package/docs/examples/no-listed-commands-rule.ts +2 -2
- package/docs/rules/README.md +36 -4
- package/docs/rules/_template.md +2 -2
- package/docs/rules/compat/no-append-all-redirect.md +12 -12
- package/docs/rules/compat/no-associative-arrays.md +14 -14
- package/docs/rules/compat/no-brace-sequence-padding-step.md +12 -12
- package/docs/rules/compat/no-case-attributes.md +13 -13
- package/docs/rules/compat/no-case-fallthrough.md +12 -12
- package/docs/rules/compat/no-case-modification.md +11 -11
- package/docs/rules/compat/no-coproc.md +13 -13
- package/docs/rules/compat/no-declare-g.md +13 -13
- package/docs/rules/compat/no-double-bracket.md +2 -2
- package/docs/rules/compat/no-epoch-variables.md +13 -13
- package/docs/rules/compat/no-fd-variable.md +12 -12
- package/docs/rules/compat/no-globstar.md +13 -13
- package/docs/rules/compat/no-lastpipe-semantics.md +13 -13
- package/docs/rules/compat/no-let.md +2 -2
- package/docs/rules/compat/no-mapfile.md +16 -16
- package/docs/rules/compat/no-nameref.md +13 -13
- package/docs/rules/compat/no-negative-array-index.md +14 -14
- package/docs/rules/compat/no-nounset-empty-array.md +72 -17
- package/docs/rules/compat/no-parameter-transformation.md +19 -19
- package/docs/rules/compat/no-pipe-all.md +13 -13
- package/docs/rules/compat/no-printf-time-format.md +13 -13
- package/docs/rules/compat/no-source.md +2 -2
- package/docs/rules/compat/no-test-v.md +12 -12
- package/docs/rules/compat/no-unicode-escape.md +13 -13
- package/docs/rules/compat/no-wait-n.md +13 -13
- package/docs/rules/no-dynamic-invocation.md +17 -12
- package/docs/rules/no-eval.md +6 -1
- package/docs/rules/no-masked-exit-status.md +4 -0
- package/docs/rules/no-secret-env-reads.md +34 -3
- package/docs/rules/no-secret-flags.md +19 -2
- package/docs/rules/no-unchecked-cd.md +9 -2
- package/docs/rules/no-undeclared-commands.md +16 -15
- package/docs/rules/no-unused-declarations.md +17 -20
- package/docs/rules/prefer-local.md +20 -2
- package/docs/rules/quote-expansions.md +8 -5
- package/docs/rules/require-network-wrapper.md +22 -17
- package/docs/rules/require-shebang.md +10 -4
- package/docs/rules/require-strict-mode.md +6 -0
- package/docs/shellcheck.md +9 -5
- package/docs/transparent-wrappers.md +97 -0
- package/docs/wrapper-rules.md +9 -0
- package/package.json +2 -2
- package/dist/language/end-of-input-marker.d.ts +0 -29
- 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
|
-
*
|
|
46
|
-
*
|
|
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
|
-
*
|
|
71
|
-
*
|
|
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
|
|
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 -
|
|
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
|
|
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 -
|
|
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)}.`);
|
package/dist/syntax/index.d.ts
CHANGED
|
@@ -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';
|
package/dist/syntax/index.js
CHANGED
|
@@ -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
|
-
`
|
|
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
|
|
13
|
-
you configure: Bash mode parses `declare -A` and `mapfile` even with `
|
|
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
|
-
|
|
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
|
|
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
|
|
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',
|
|
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
|
-
- **`
|
|
97
|
-
|
|
98
|
-
|
|
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
|
|
102
|
-
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
- **`dialect: "
|
|
107
|
-
|
|
108
|
-
|
|
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
|
|
113
|
-
|
|
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',
|
|
116
|
-
and enables
|
|
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
|
|
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
|
|
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 `
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
}
|
package/docs/rules/README.md
CHANGED
|
@@ -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) | `
|
|
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.
|
|
64
|
-
`dialect`. They report nothing when `
|
|
65
|
-
and the `posix-portable` preset sets `dialect: "posix"
|
|
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.
|
package/docs/rules/_template.md
CHANGED
|
@@ -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`, `
|
|
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`, `
|
|
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
|
-
- `
|
|
25
|
-
- `dialect: "bash"` (the default) or `"bats"`: reports when `
|
|
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", "
|
|
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
|
|
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',
|
|
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 `
|
|
57
|
+
With `minBashVersion: "3.2"`:
|
|
58
58
|
|
|
59
|
-
<!-- example: {"dialect": "bash", "
|
|
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 `
|
|
66
|
+
With `minBashVersion: "3.2"`:
|
|
67
67
|
|
|
68
|
-
<!-- example: {"dialect": "bash", "
|
|
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 `
|
|
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 `
|
|
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
|
|
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
|
|
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
|
-
- `
|
|
21
|
-
- `dialect: "bash"` (the default) or `"bats"`: reports when `
|
|
22
|
-
- `dialect: "posix"`: reports
|
|
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
|
|
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',
|
|
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 `
|
|
56
|
+
With `minBashVersion: "3.2"`:
|
|
57
57
|
|
|
58
|
-
<!-- example: {"dialect": "bash", "
|
|
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 `
|
|
68
|
+
With `minBashVersion: "3.2"`:
|
|
69
69
|
|
|
70
|
-
<!-- example: {"dialect": "bash", "
|
|
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 `
|
|
78
|
+
With `minBashVersion: "4.0"` or newer, or with no `minBashVersion`:
|
|
79
79
|
|
|
80
|
-
<!-- example: {"dialect": "bash", "
|
|
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 `
|
|
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
|
|
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.
|