@eslint-sh/eslint-plugin 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +223 -17
- package/dist/command-harvest/builtins/bash.d.ts +36 -0
- package/dist/command-harvest/builtins/bash.js +98 -0
- package/dist/command-harvest/builtins/bats.d.ts +45 -0
- package/dist/command-harvest/builtins/bats.js +33 -0
- package/dist/command-harvest/builtins/index.d.ts +18 -0
- package/dist/command-harvest/builtins/index.js +39 -0
- package/dist/command-harvest/builtins/posix.d.ts +51 -0
- package/dist/command-harvest/builtins/posix.js +93 -0
- package/dist/command-harvest/harvest-commands.d.ts +66 -0
- package/dist/command-harvest/harvest-commands.js +97 -0
- package/dist/command-harvest/wrapper-table.d.ts +24 -0
- package/dist/command-harvest/wrapper-table.js +49 -0
- package/dist/configs/agent-authored.d.ts +41 -0
- package/dist/configs/agent-authored.js +33 -0
- package/dist/configs/bash-3-2.d.ts +28 -0
- package/dist/configs/bash-3-2.js +50 -0
- package/dist/configs/posix-portable.d.ts +40 -0
- package/dist/configs/posix-portable.js +52 -0
- package/dist/configs/recommended.d.ts +52 -0
- package/dist/configs/recommended.js +34 -0
- package/dist/declaration-conventions/comment-pragma.d.ts +33 -0
- package/dist/declaration-conventions/comment-pragma.js +153 -0
- package/dist/declaration-conventions/declaration-convention.d.ts +189 -0
- package/dist/declaration-conventions/declaration-convention.js +1 -0
- package/dist/declaration-conventions/read-declared-commands.d.ts +70 -0
- package/dist/declaration-conventions/read-declared-commands.js +128 -0
- package/dist/eslint-plugin-syntax-analyze.d.ts +23 -0
- package/dist/eslint-plugin-syntax.d.ts +27 -0
- package/dist/eslint-plugin.d.ts +1211 -52
- package/dist/index.d.ts +56 -8
- package/dist/index.js +53 -6
- package/dist/language/command-resolution.d.ts +124 -0
- package/dist/language/command-resolution.js +120 -0
- package/dist/language/end-of-input-marker.d.ts +29 -0
- package/dist/language/end-of-input-marker.js +88 -0
- package/dist/language/line-breaks.d.ts +18 -0
- package/dist/language/line-breaks.js +22 -0
- package/dist/language/lint-view-types.d.ts +104 -0
- package/dist/language/lint-view-types.js +1 -0
- package/dist/language/non-shell-file-guard.d.ts +35 -0
- package/dist/language/non-shell-file-guard.js +59 -0
- package/dist/language/selector-classes.d.ts +0 -1
- package/dist/language/selector-classes.js +0 -1
- package/dist/language/shell-language-options.d.ts +7 -1
- package/dist/language/shell-language-options.js +4 -4
- package/dist/language/shell-language.d.ts +10 -2
- package/dist/language/shell-language.js +59 -8
- package/dist/language/shell-source-code.d.ts +342 -5
- package/dist/language/shell-source-code.js +559 -30
- package/dist/language/source-coordinates.d.ts +13 -2
- package/dist/language/source-coordinates.js +68 -14
- package/dist/language/syntax-view.d.ts +60 -39
- package/dist/language/syntax-view.js +200 -45
- package/dist/language/word-resolution.d.ts +4 -3
- package/dist/language/word-resolution.js +4 -3
- package/dist/rules/compat/create-compat-rule.d.ts +59 -0
- package/dist/rules/compat/create-compat-rule.js +65 -0
- package/dist/rules/compat/declaration-options.d.ts +19 -0
- package/dist/rules/compat/declaration-options.js +110 -0
- package/dist/rules/compat/no-append-all-redirect.d.ts +13 -0
- package/dist/rules/compat/no-append-all-redirect.js +73 -0
- package/dist/rules/compat/no-associative-arrays.d.ts +12 -0
- package/dist/rules/compat/no-associative-arrays.js +39 -0
- package/dist/rules/compat/no-case-attributes.d.ts +12 -0
- package/dist/rules/compat/no-case-attributes.js +63 -0
- package/dist/rules/compat/no-case-fallthrough.d.ts +12 -0
- package/dist/rules/compat/no-case-fallthrough.js +67 -0
- package/dist/rules/compat/no-case-modification.d.ts +35 -0
- package/dist/rules/compat/no-case-modification.js +86 -0
- package/dist/rules/compat/no-coproc.d.ts +12 -0
- package/dist/rules/compat/no-coproc.js +47 -0
- package/dist/rules/compat/no-declare-g.d.ts +12 -0
- package/dist/rules/compat/no-declare-g.js +59 -0
- package/dist/rules/compat/no-double-bracket.d.ts +12 -0
- package/dist/rules/compat/no-double-bracket.js +52 -0
- package/dist/rules/compat/no-epoch-variables.d.ts +12 -0
- package/dist/rules/compat/no-epoch-variables.js +57 -0
- package/dist/rules/compat/no-fd-variable.d.ts +12 -0
- package/dist/rules/compat/no-fd-variable.js +48 -0
- package/dist/rules/compat/no-globstar.d.ts +12 -0
- package/dist/rules/compat/no-globstar.js +39 -0
- package/dist/rules/compat/no-lastpipe-semantics.d.ts +12 -0
- package/dist/rules/compat/no-lastpipe-semantics.js +53 -0
- package/dist/rules/compat/no-let.d.ts +12 -0
- package/dist/rules/compat/no-let.js +62 -0
- package/dist/rules/compat/no-mapfile.d.ts +13 -0
- package/dist/rules/compat/no-mapfile.js +122 -0
- package/dist/rules/compat/no-nameref.d.ts +12 -0
- package/dist/rules/compat/no-nameref.js +44 -0
- package/dist/rules/compat/no-negative-array-index.d.ts +13 -0
- package/dist/rules/compat/no-negative-array-index.js +123 -0
- package/dist/rules/compat/no-nounset-empty-array.d.ts +13 -0
- package/dist/rules/compat/no-nounset-empty-array.js +158 -0
- package/dist/rules/compat/no-parameter-transformation.d.ts +13 -0
- package/dist/rules/compat/no-parameter-transformation.js +123 -0
- package/dist/rules/compat/no-pipe-all.d.ts +13 -0
- package/dist/rules/compat/no-pipe-all.js +68 -0
- package/dist/rules/compat/no-printf-time-format.d.ts +12 -0
- package/dist/rules/compat/no-printf-time-format.js +99 -0
- package/dist/rules/compat/no-source.d.ts +13 -0
- package/dist/rules/compat/no-source.js +103 -0
- package/dist/rules/compat/no-test-v.d.ts +12 -0
- package/dist/rules/compat/no-test-v.js +280 -0
- package/dist/rules/compat/no-unicode-escape.d.ts +14 -0
- package/dist/rules/compat/no-unicode-escape.js +120 -0
- package/dist/rules/compat/no-wait-n.d.ts +12 -0
- package/dist/rules/compat/no-wait-n.js +73 -0
- package/dist/rules/compat/operator-range.d.ts +10 -0
- package/dist/rules/compat/operator-range.js +15 -0
- package/dist/rules/compat/posix-absent-command.d.ts +12 -0
- package/dist/rules/compat/posix-absent-command.js +134 -0
- package/dist/rules/compat/shopt-options.d.ts +9 -0
- package/dist/rules/compat/shopt-options.js +63 -0
- package/dist/rules/compat/version-gates.d.ts +76 -0
- package/dist/rules/compat/version-gates.js +197 -0
- package/dist/rules/create-rule.d.ts +91 -0
- package/dist/rules/create-rule.js +51 -0
- package/dist/rules/create-wrapper-rule.d.ts +166 -0
- package/dist/rules/create-wrapper-rule.js +407 -0
- package/dist/rules/define-shell-rule.d.ts +184 -0
- package/dist/rules/define-shell-rule.js +56 -0
- package/dist/rules/index.d.ts +26 -0
- package/dist/rules/index.js +88 -0
- package/dist/rules/max-script-complexity.d.ts +28 -0
- package/dist/rules/max-script-complexity.js +130 -0
- package/dist/rules/no-dynamic-invocation.d.ts +35 -0
- package/dist/rules/no-dynamic-invocation.js +117 -0
- package/dist/rules/no-eval.d.ts +13 -0
- package/dist/rules/no-eval.js +89 -0
- package/dist/rules/no-masked-exit-status.d.ts +21 -0
- package/dist/rules/no-masked-exit-status.js +667 -0
- package/dist/rules/no-secret-env-reads.d.ts +24 -0
- package/dist/rules/no-secret-env-reads.js +133 -0
- package/dist/rules/no-secret-flags.d.ts +32 -0
- package/dist/rules/no-secret-flags.js +162 -0
- package/dist/rules/no-unchecked-cd.d.ts +44 -0
- package/dist/rules/no-unchecked-cd.js +250 -0
- package/dist/rules/no-undeclared-commands.d.ts +20 -0
- package/dist/rules/no-undeclared-commands.js +115 -0
- package/dist/rules/no-unused-declarations.d.ts +23 -0
- package/dist/rules/no-unused-declarations.js +259 -0
- package/dist/rules/prefer-local.d.ts +16 -0
- package/dist/rules/prefer-local.js +249 -0
- package/dist/rules/quote-expansions.d.ts +13 -0
- package/dist/rules/quote-expansions.js +229 -0
- package/dist/rules/require-network-wrapper.d.ts +40 -0
- package/dist/rules/require-network-wrapper.js +244 -0
- package/dist/rules/require-shebang.d.ts +29 -0
- package/dist/rules/require-shebang.js +101 -0
- package/dist/rules/require-strict-mode.d.ts +52 -0
- package/dist/rules/require-strict-mode.js +177 -0
- package/dist/rules/require-tty-guard.d.ts +13 -0
- package/dist/rules/require-tty-guard.js +289 -0
- package/dist/rules/resolve-declaration-convention.d.ts +96 -0
- package/dist/rules/resolve-declaration-convention.js +109 -0
- package/dist/rules/resolve-network-wrapper.d.ts +19 -0
- package/dist/rules/resolve-network-wrapper.js +116 -0
- package/dist/rules/set-options.d.ts +109 -0
- package/dist/rules/set-options.js +201 -0
- package/dist/rules/shebang-interpreter.d.ts +18 -0
- package/dist/rules/shebang-interpreter.js +81 -0
- package/dist/rules/shebang-matches-dialect.d.ts +12 -0
- package/dist/rules/shebang-matches-dialect.js +69 -0
- package/dist/rules/transparent-wrappers.d.ts +52 -0
- package/dist/rules/transparent-wrappers.js +99 -0
- package/dist/rules/user-pattern.d.ts +63 -0
- package/dist/rules/user-pattern.js +184 -0
- package/dist/syntax/analyze.d.ts +19 -0
- package/dist/syntax/analyze.js +21 -0
- package/dist/syntax/index.d.ts +23 -0
- package/dist/syntax/index.js +25 -0
- package/dist/version.d.ts +0 -1
- package/dist/version.js +0 -1
- package/docs/compat.md +347 -0
- package/docs/custom-rule-tutorial.md +780 -34
- package/docs/declaration-conventions.md +478 -0
- package/docs/examples/audit-wrapper-pack.mjs +15 -0
- package/docs/examples/dep-line-convention.mjs +22 -0
- package/docs/examples/eslint.config.mjs +3 -2
- package/docs/examples/no-and-or-as-if-rule.mjs +61 -0
- package/docs/examples/no-backticks-rule-tester.mjs +106 -0
- package/docs/examples/no-backticks-rule.mjs +60 -0
- package/docs/examples/no-curl-in-functions-rule.mjs +53 -0
- package/docs/examples/no-function-keyword-rule.mjs +59 -0
- package/docs/examples/no-listed-commands-rule.ts +63 -0
- package/docs/examples/no-rm-rf-rule.mjs +26 -15
- package/docs/examples/no-unquoted-rm-variable-rule.ts +54 -0
- package/docs/examples/sidecar-convention.mjs +38 -0
- package/docs/examples/sidecar-snapshot-convention.mjs +26 -0
- package/docs/examples/sorted-declarations-rule.mjs +52 -0
- package/docs/reading-nodes.md +159 -0
- package/docs/rules/README.md +78 -0
- package/docs/rules/_template.md +83 -0
- package/docs/rules/compat/no-append-all-redirect.md +87 -0
- package/docs/rules/compat/no-associative-arrays.md +95 -0
- package/docs/rules/compat/no-case-attributes.md +82 -0
- package/docs/rules/compat/no-case-fallthrough.md +90 -0
- package/docs/rules/compat/no-case-modification.md +74 -0
- package/docs/rules/compat/no-coproc.md +76 -0
- package/docs/rules/compat/no-declare-g.md +80 -0
- package/docs/rules/compat/no-double-bracket.md +72 -0
- package/docs/rules/compat/no-epoch-variables.md +79 -0
- package/docs/rules/compat/no-fd-variable.md +78 -0
- package/docs/rules/compat/no-globstar.md +74 -0
- package/docs/rules/compat/no-lastpipe-semantics.md +73 -0
- package/docs/rules/compat/no-let.md +78 -0
- package/docs/rules/compat/no-mapfile.md +118 -0
- package/docs/rules/compat/no-nameref.md +81 -0
- package/docs/rules/compat/no-negative-array-index.md +87 -0
- package/docs/rules/compat/no-nounset-empty-array.md +103 -0
- package/docs/rules/compat/no-parameter-transformation.md +94 -0
- package/docs/rules/compat/no-pipe-all.md +85 -0
- package/docs/rules/compat/no-printf-time-format.md +75 -0
- package/docs/rules/compat/no-source.md +98 -0
- package/docs/rules/compat/no-test-v.md +82 -0
- package/docs/rules/compat/no-unicode-escape.md +91 -0
- package/docs/rules/compat/no-wait-n.md +85 -0
- package/docs/rules/max-script-complexity.md +109 -0
- package/docs/rules/no-dynamic-invocation.md +139 -0
- package/docs/rules/no-eval.md +106 -0
- package/docs/rules/no-masked-exit-status.md +239 -0
- package/docs/rules/no-secret-env-reads.md +173 -0
- package/docs/rules/no-secret-flags.md +169 -0
- package/docs/rules/no-unchecked-cd.md +146 -0
- package/docs/rules/no-undeclared-commands.md +141 -0
- package/docs/rules/no-unused-declarations.md +216 -0
- package/docs/rules/prefer-local.md +221 -0
- package/docs/rules/quote-expansions.md +157 -0
- package/docs/rules/require-network-wrapper.md +171 -0
- package/docs/rules/require-shebang.md +157 -0
- package/docs/rules/require-strict-mode.md +253 -0
- package/docs/rules/require-tty-guard.md +128 -0
- package/docs/rules/shebang-matches-dialect.md +120 -0
- package/docs/selectors.md +2 -2
- package/docs/shellcheck.md +86 -0
- package/docs/wrapper-rules.md +206 -0
- package/package.json +26 -20
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/language/selector-classes.d.ts.map +0 -1
- package/dist/language/selector-classes.js.map +0 -1
- package/dist/language/shell-language-options.d.ts.map +0 -1
- package/dist/language/shell-language-options.js.map +0 -1
- package/dist/language/shell-language.d.ts.map +0 -1
- package/dist/language/shell-language.js.map +0 -1
- package/dist/language/shell-source-code.d.ts.map +0 -1
- package/dist/language/shell-source-code.js.map +0 -1
- package/dist/language/source-coordinates.d.ts.map +0 -1
- package/dist/language/source-coordinates.js.map +0 -1
- package/dist/language/syntax-view.d.ts.map +0 -1
- package/dist/language/syntax-view.js.map +0 -1
- package/dist/language/word-resolution.d.ts.map +0 -1
- package/dist/language/word-resolution.js.map +0 -1
- package/dist/version.d.ts.map +0 -1
- package/dist/version.js.map +0 -1
package/docs/compat.md
ADDED
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
# Version compatibility (`compat/*` rules)
|
|
2
|
+
|
|
3
|
+
The `compat/*` rules report shell features that the shell you deploy to does not have. They
|
|
4
|
+
are for scripts that must run on an older Bash, such as the Bash 3.2 that macOS ships as
|
|
5
|
+
`/bin/bash`, or on a plain POSIX `sh`.
|
|
6
|
+
|
|
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
|
|
9
|
+
target, such as [`sh/shebang-matches-dialect`](./rules/shebang-matches-dialect.md), lives outside
|
|
10
|
+
the namespace.
|
|
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
|
|
14
|
+
POSIX mode parses `declare -A` as an ordinary command. Only the `compat/*` rules enforce the
|
|
15
|
+
target.
|
|
16
|
+
|
|
17
|
+
A `compat/*` finding stays reported when the code is behind a runtime version guard
|
|
18
|
+
(`if (( BASH_VERSINFO[0] >= 4 )); then …`): the rules do not follow control flow. Silence a guarded
|
|
19
|
+
use with a disable comment on the line before it:
|
|
20
|
+
|
|
21
|
+
```sh
|
|
22
|
+
if (( BASH_VERSINFO[0] >= 4 )); then
|
|
23
|
+
# eslint-disable-next-line sh/compat/no-mapfile
|
|
24
|
+
mapfile -t lines < file
|
|
25
|
+
fi
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Rules
|
|
29
|
+
|
|
30
|
+
| Rule | Feature | Bash | POSIX sh |
|
|
31
|
+
| ---- | ------- | ---- | -------- |
|
|
32
|
+
| [`sh/compat/no-append-all-redirect`](./rules/compat/no-append-all-redirect.md) | `&>>` | 4.0 | no |
|
|
33
|
+
| [`sh/compat/no-associative-arrays`](./rules/compat/no-associative-arrays.md) | `declare -A`, `typeset -A` | 4.0 | no |
|
|
34
|
+
| [`sh/compat/no-case-attributes`](./rules/compat/no-case-attributes.md) | `declare -l`, `declare -u` (and `typeset`, `local`) | 4.0 | no |
|
|
35
|
+
| [`sh/compat/no-case-fallthrough`](./rules/compat/no-case-fallthrough.md) | `;&`, `;;&` in `case` | 4.0 | no |
|
|
36
|
+
| [`sh/compat/no-case-modification`](./rules/compat/no-case-modification.md) | `${v^}`, `${v^^}`, `${v,}`, `${v,,}` | 4.0 | no |
|
|
37
|
+
| [`sh/compat/no-coproc`](./rules/compat/no-coproc.md) | `coproc` | 4.0 | no |
|
|
38
|
+
| [`sh/compat/no-declare-g`](./rules/compat/no-declare-g.md) | `declare -g`, `typeset -g` | 4.2 | no |
|
|
39
|
+
| [`sh/compat/no-double-bracket`](./rules/compat/no-double-bracket.md) | `[[ … ]]` | every | no |
|
|
40
|
+
| [`sh/compat/no-epoch-variables`](./rules/compat/no-epoch-variables.md) | `$EPOCHSECONDS`, `$EPOCHREALTIME` | 5.0 | no |
|
|
41
|
+
| [`sh/compat/no-fd-variable`](./rules/compat/no-fd-variable.md) | `exec {fd}>file` | 4.1 | no |
|
|
42
|
+
| [`sh/compat/no-globstar`](./rules/compat/no-globstar.md) | `shopt -s globstar` | 4.0 | no |
|
|
43
|
+
| [`sh/compat/no-lastpipe-semantics`](./rules/compat/no-lastpipe-semantics.md) | `shopt -s lastpipe` | 4.2 | no |
|
|
44
|
+
| [`sh/compat/no-let`](./rules/compat/no-let.md) | `let` | every | no |
|
|
45
|
+
| [`sh/compat/no-mapfile`](./rules/compat/no-mapfile.md) | `mapfile`, `readarray` | 4.0 | no |
|
|
46
|
+
| [`sh/compat/no-nameref`](./rules/compat/no-nameref.md) | `declare -n`, `local -n` | 4.3 | no |
|
|
47
|
+
| [`sh/compat/no-negative-array-index`](./rules/compat/no-negative-array-index.md) | `${arr[-1]}`, `arr[-1]=x` | 4.3 | no |
|
|
48
|
+
| [`sh/compat/no-nounset-empty-array`](./rules/compat/no-nounset-empty-array.md) | `"${a[@]}"` of an empty array under `set -u` | 4.4 | no |
|
|
49
|
+
| [`sh/compat/no-parameter-transformation`](./rules/compat/no-parameter-transformation.md) | `${v@Q}`, `@E`, `@P`, `@A`, `@a`; `@U`, `@u`, `@L`, `@K`; `@k` | 4.4; 5.1; 5.2 | no |
|
|
50
|
+
| [`sh/compat/no-pipe-all`](./rules/compat/no-pipe-all.md) | `\|&` | 4.0 | no |
|
|
51
|
+
| [`sh/compat/no-printf-time-format`](./rules/compat/no-printf-time-format.md) | `printf '%(fmt)T'` | 4.2 | no |
|
|
52
|
+
| [`sh/compat/no-source`](./rules/compat/no-source.md) | `source` | every | no |
|
|
53
|
+
| [`sh/compat/no-test-v`](./rules/compat/no-test-v.md) | `[[ -v x ]]`, `test -v x` | 4.2 | no |
|
|
54
|
+
| [`sh/compat/no-unicode-escape`](./rules/compat/no-unicode-escape.md) | `\u`, `\U` in `$'…'`; U+0080 to U+00FF | 4.2; 4.3 | no |
|
|
55
|
+
| [`sh/compat/no-wait-n`](./rules/compat/no-wait-n.md) | `wait -n` | 4.3 | no |
|
|
56
|
+
|
|
57
|
+
The Bash column is the oldest Bash release that has the feature, taken from the Bash release notes
|
|
58
|
+
([NEWS](https://tiswww.case.edu/php/chet/bash/NEWS)) and checked against real shells built from
|
|
59
|
+
source; see [Evidence](#evidence). Bash 4.2 already reads a negative array subscript (`${arr[-1]}`), but
|
|
60
|
+
it rejects the element length `${#arr[-1]}`, assigning and unsetting; the table uses 4.3, the first
|
|
61
|
+
release where every use works. Two rules gate more than one release. `sh/compat/no-parameter-transformation`
|
|
62
|
+
reports each operator against its own release. `sh/compat/no-unicode-escape` needs 4.2 for most code
|
|
63
|
+
points but 4.3 for U+0080 to U+00FF, because the Bash 4.2 release writes those as a single Latin-1 byte
|
|
64
|
+
instead of UTF-8. `sh/compat/no-nounset-empty-array` gates a behavior, not syntax: every Bash has
|
|
65
|
+
`"${a[@]}"`, but through 4.3 an empty array aborts the script under `set -u`. `every` means every Bash release has the feature, so only
|
|
66
|
+
`dialect: "posix"` can lack it: those three are the POSIX-only gates, and the other twenty-one are
|
|
67
|
+
the version gates. The `bash-3.2` preset enables the version gates and the `posix-portable` preset
|
|
68
|
+
enables all twenty-four; see
|
|
69
|
+
[Presets](#presets).
|
|
70
|
+
|
|
71
|
+
## Setting the target
|
|
72
|
+
|
|
73
|
+
Every `compat/*` rule reads the same two `languageOptions`, and nothing else. They have no rule
|
|
74
|
+
options of their own:
|
|
75
|
+
|
|
76
|
+
```js
|
|
77
|
+
// eslint.config.js
|
|
78
|
+
import { defineConfig } from 'eslint/config';
|
|
79
|
+
import sh from '@eslint-sh/eslint-plugin';
|
|
80
|
+
|
|
81
|
+
export default defineConfig({
|
|
82
|
+
files: ['**/*.sh'],
|
|
83
|
+
extends: [sh.configs.recommended],
|
|
84
|
+
languageOptions: { dialect: 'bash', shellVersion: '3.2' },
|
|
85
|
+
rules: {
|
|
86
|
+
'sh/compat/no-associative-arrays': 'error',
|
|
87
|
+
'sh/compat/no-mapfile': 'error',
|
|
88
|
+
// …and the other rules in the table above.
|
|
89
|
+
},
|
|
90
|
+
});
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
A gated feature is available, and its rule reports nothing, according to these rules:
|
|
94
|
+
|
|
95
|
+
- **`shellVersion` unset means the newest version.** With no `shellVersion`, every Bash feature
|
|
96
|
+
is available and no `compat/*` rule reports under `dialect: "bash"`. Set a version to opt in.
|
|
97
|
+
- **Versions compare numerically, major first, then minor.** `shellVersion` has the shape
|
|
98
|
+
`MAJOR[.MINOR]`. A missing minor is `0`, so `"4"` is the same as `"4.0"`. Each part is a
|
|
99
|
+
number, not text, so `"3.10"` is newer than `"3.2"` and still older than `"4.0"`. A feature
|
|
100
|
+
is available when `shellVersion` is its minimum version or newer.
|
|
101
|
+
- **`dialect: "posix"` makes Bash-only features unavailable at any `shellVersion`.** A POSIX sh
|
|
102
|
+
script cannot rely on a feature that only Bash has, whatever version number is configured.
|
|
103
|
+
- **`dialect: "bats"` is gated as Bash**, because Bats test files run under Bash.
|
|
104
|
+
`shellVersion` is the version of that Bash.
|
|
105
|
+
- **`dialect: "mksh"` and `dialect: "zsh"` are not gated.** These shells number their own
|
|
106
|
+
releases, so a `shellVersion` there is not a Bash version, and the rules have no data on
|
|
107
|
+
which of their releases added which feature. The `compat/*` rules report nothing for them.
|
|
108
|
+
|
|
109
|
+
## Presets
|
|
110
|
+
|
|
111
|
+
Two presets name a deployment target and enable every rule that enforces it, so you do not list
|
|
112
|
+
the rules yourself:
|
|
113
|
+
|
|
114
|
+
- **`sh.configs['bash-3.2']`** sets `languageOptions: { dialect: 'bash', shellVersion: '3.2' }`
|
|
115
|
+
and enables all twenty-one version-gated `compat/*` rules at `error`. Use it for scripts that must run on the Bash
|
|
116
|
+
3.2 that macOS ships as `/bin/bash`.
|
|
117
|
+
- **`sh.configs['posix-portable']`** reports the Bash-only constructs in the table above, which
|
|
118
|
+
fail or read as unset on dash, but not `local`, `echo -e` or the others listed under
|
|
119
|
+
[What `posix-portable` does not report](#what-posix-portable-does-not-report), so a clean run does
|
|
120
|
+
not prove a script runs under every POSIX `sh`. It sets `languageOptions: { dialect: 'posix' }` and
|
|
121
|
+
enables all twenty-four `compat/*` rules, `sh/shebang-matches-dialect`, and
|
|
122
|
+
`sh/require-strict-mode` configured as `set -eu`, all at `error`. Strict mode is `set -eu` and not
|
|
123
|
+
`set -euo pipefail` because `pipefail` joined POSIX only in POSIX.1-2024, and older `dash` and
|
|
124
|
+
BusyBox `ash` reject it, so a `pipefail` requirement would make the autofix break a working
|
|
125
|
+
`#!/bin/sh` script.
|
|
126
|
+
|
|
127
|
+
Both presets are single objects with no `files`; extend them from an entry that lists your
|
|
128
|
+
scripts, after `recommended` if you want its rules too:
|
|
129
|
+
|
|
130
|
+
```js
|
|
131
|
+
// eslint.config.js
|
|
132
|
+
import { defineConfig } from 'eslint/config';
|
|
133
|
+
import sh from '@eslint-sh/eslint-plugin';
|
|
134
|
+
|
|
135
|
+
export default defineConfig([
|
|
136
|
+
{ files: ['scripts/**/*.sh'], extends: [sh.configs.recommended, sh.configs['bash-3.2']] },
|
|
137
|
+
{ files: ['install/?*'], extends: [sh.configs.recommended, sh.configs['posix-portable']] },
|
|
138
|
+
]);
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Because the presets carry `languageOptions`, a `languageOptions` of your own in the same entry
|
|
142
|
+
overrides them key by key. To run these rules beside shellcheck, see [shellcheck](./shellcheck.md).
|
|
143
|
+
|
|
144
|
+
## A branch for one shell
|
|
145
|
+
|
|
146
|
+
`[[`, `let` and `source` are commands to dash, so it only fails when it runs one. A script meant for
|
|
147
|
+
`sh` can hold a branch for one shell, and `sh/compat/no-double-bracket`, `sh/compat/no-let` and
|
|
148
|
+
`sh/compat/no-source` do not report inside it. `V` below is `$BASH_VERSION`, `$KSH_VERSION` or
|
|
149
|
+
`$ZSH_VERSION`, written `$X`, `${X}`, `${X-}` or `${X:-}`, quoted or not. dash sets none of them.
|
|
150
|
+
|
|
151
|
+
```sh
|
|
152
|
+
if [ -n "${BASH_VERSION-}" ]; then source ~/.bashrc; fi
|
|
153
|
+
[ -n "${BASH_VERSION-}" ] && source ~/.bashrc
|
|
154
|
+
[ -n "${ZSH_VERSION-}" ] || exit 0
|
|
155
|
+
[[ -o nomatch ]]
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
The shapes recognized are the `then` body of `if [ -n V ]` and the `else` body of `if [ -z V ]`, the
|
|
159
|
+
right side of `[ -n V ] && …` and `[ -z V ] || …`, and the statements after `[ -n V ] || exit` or
|
|
160
|
+
`[ -z V ] && exit` (`return` too) in the same file or `{ … }` block. `test -n V` and `test -z V`
|
|
161
|
+
count as well. The recognition is syntactic, so a version captured in another variable is not
|
|
162
|
+
understood and still reports. A construct dash cannot parse is a syntax error whichever branch it
|
|
163
|
+
is in, and these exemptions do not apply to it.
|
|
164
|
+
|
|
165
|
+
## What `posix-portable` does not report
|
|
166
|
+
|
|
167
|
+
These constructs pass `posix-portable` with no finding. shellcheck reports each of them for a
|
|
168
|
+
script whose shebang says `sh`, so run it beside eslint-sh; see [shellcheck](./shellcheck.md).
|
|
169
|
+
|
|
170
|
+
| Construct | On dash | On BusyBox ash | shellcheck |
|
|
171
|
+
| --------- | ------- | -------------- | ---------- |
|
|
172
|
+
| `local`, other than `-n`, `-l` and `-u` | works | works | SC3043 |
|
|
173
|
+
| `echo -e` | exits 0, prints the `-e` as text | exits 0, reads it as an option | SC3037 |
|
|
174
|
+
| `declare`, `typeset`, other than `-A`, `-g`, `-n`, `-l` and `-u` | `not found` | `not found` | SC3044 |
|
|
175
|
+
| `shopt`, any option but `lastpipe` and `globstar` | `not found` | `not found` | SC3044 |
|
|
176
|
+
| `(( … ))` | `not found` | `not found` | SC3006 |
|
|
177
|
+
| `x+=1` | `not found` | `not found` | SC3024 |
|
|
178
|
+
| `[ a == b ]` | status 2 | works | SC3014 |
|
|
179
|
+
| `$'…'` | prints the `$` as text | works | SC3003 |
|
|
180
|
+
| `$"…"` | prints the `$` as text | prints the `$` as text | SC3004 |
|
|
181
|
+
| `{a,b}` | prints it as text | prints it as text | SC3009 |
|
|
182
|
+
|
|
183
|
+
`local` is left out because it works on dash and BusyBox ash, although POSIX does not define it and
|
|
184
|
+
ksh93 has no such command. `echo -e` is left out because it never fails: only its output differs.
|
|
185
|
+
`declare`, `typeset`, `shopt`, `(( … ))` and `x+=1` do fail on dash. They are left out because
|
|
186
|
+
shellcheck reports each one and scripts meant for `sh` rarely use them.
|
|
187
|
+
|
|
188
|
+
A `let` or `source` call written directly also passes when the file defines a function of that name
|
|
189
|
+
anywhere, even if that function cannot run at the call. The check reads the name only, and dash
|
|
190
|
+
still fails on such a call; see [`no-let`](./rules/compat/no-let.md) and
|
|
191
|
+
[`no-source`](./rules/compat/no-source.md).
|
|
192
|
+
|
|
193
|
+
Some other Bash-only constructs are parse errors under `dialect: "posix"`, so they are reported one
|
|
194
|
+
at a time: `function f {}`, `${v//a/b}`, `${v:1:1}`, `${!v}`, `<<<`, `<(…)`, `&>`, `|&`, `select`,
|
|
195
|
+
arrays, C-style `for`, and a `[[` containing a parenthesis.
|
|
196
|
+
|
|
197
|
+
`bash --posix` is not a way to check any of this. It accepts `[[`, `let`, `source`, `local` and the
|
|
198
|
+
rest, because POSIX mode does not turn off Bash's extensions.
|
|
199
|
+
|
|
200
|
+
## Fixes
|
|
201
|
+
|
|
202
|
+
The `compat/*` rules never autofix, because the portable alternative can change how a script
|
|
203
|
+
behaves. Where a rewrite with the same behavior exists, the rule offers it as an editor
|
|
204
|
+
suggestion that you apply by choice. Each rule's page says when it offers one.
|
|
205
|
+
|
|
206
|
+
## Not covered
|
|
207
|
+
|
|
208
|
+
- **Zero-padded and stepped brace sequences** (`{01..10}`, `{1..10..2}`, Bash 4.0). Bash 3.2 drops
|
|
209
|
+
the padding and leaves the stepped form unexpanded, with no error. The parser keeps a brace
|
|
210
|
+
expansion as part of a word's literal text and gives it no node of its own, so a rule could find
|
|
211
|
+
one only by matching text, which would also match literal braces. No rule reports it.
|
|
212
|
+
- **Bash syntax that the POSIX parser rejects.** Under `dialect: "posix"`, `|&`, `&>>`, `;&`, `;;&`,
|
|
213
|
+
`{fd}` redirections, `${v@op}` and arrays are parse errors, so a POSIX file that uses them is
|
|
214
|
+
reported as unparsable, and the rules for those features never run on it. `$'…'` is read as a
|
|
215
|
+
literal `$` and a single-quoted string, as dash reads it.
|
|
216
|
+
|
|
217
|
+
## Evidence
|
|
218
|
+
|
|
219
|
+
Every row of the table above, and the alternative each rule's message recommends, has been run on
|
|
220
|
+
real shells. [`scripts/compat-matrix/run.sh`](https://github.com/mike-north/eslint-sh/blob/main/scripts/compat-matrix/run.sh) downloads the
|
|
221
|
+
GNU Bash release tarballs and dash, builds each one under the gitignored `scratch/` folder (nothing
|
|
222
|
+
is installed system-wide), and runs every probe in
|
|
223
|
+
[`scripts/compat-matrix/probes/`](https://github.com/mike-north/eslint-sh/tree/main/scripts/compat-matrix/probes) under each shell:
|
|
224
|
+
|
|
225
|
+
```sh
|
|
226
|
+
scripts/compat-matrix/run.sh
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
A probe is a short script that uses one feature, or one recommended alternative, and prints a known
|
|
230
|
+
value. "yes" means the shell ran it, exited 0, and printed exactly that value; "no" means it did not.
|
|
231
|
+
Each Bash column is the x.y release tarball with no upstream patches applied, pinned by SHA-256 in
|
|
232
|
+
the script. The `dash` column is the evidence for the POSIX sh column.
|
|
233
|
+
|
|
234
|
+
| Probe | Checks | bash-3.2 | bash-4.0 | bash-4.1 | bash-4.2 | bash-4.3 | bash-4.4 | bash-5.0 | bash-5.1 | bash-5.2 | bash-5.3 | dash-0.5.12 |
|
|
235
|
+
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
|
|
236
|
+
| `associative-arrays-alt-jq` | the message's `jq -r --arg k "$key" '.[$k]' map.json` lookup (needs `jq` on PATH) | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes |
|
|
237
|
+
| `associative-arrays-typeset` | `typeset -A m` keeps two string keys apart | no | yes | yes | yes | yes | yes | yes | yes | yes | yes | no |
|
|
238
|
+
| `associative-arrays` | `declare -A m` keeps two string keys apart | no | yes | yes | yes | yes | yes | yes | yes | yes | yes | no |
|
|
239
|
+
| `case-attributes-alt-tr` | the message's `$(printf '%s' "$v" \| tr '[:upper:]' '[:lower:]')` and its upper-case twin | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes |
|
|
240
|
+
| `case-fallthrough-alt` | the message's rewrites: the next branch's commands repeated for `;&`, and a second `case` for `;;&` | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes |
|
|
241
|
+
| `case-fallthrough` | `;&` runs the next branch without testing its pattern | no | yes | yes | yes | yes | yes | yes | yes | yes | yes | no |
|
|
242
|
+
| `case-modification-alt-pattern-tr` | the pattern message's `tr a A` advice, spelling out the matched characters | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes |
|
|
243
|
+
| `case-modification-alt-tr` | the four `tr` examples the message prints, written exactly as it prints them | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes |
|
|
244
|
+
| `case-modification-pattern` | the pattern forms `${v^^l}` and `${v,,L}` | no | yes | yes | yes | yes | yes | yes | yes | yes | yes | no |
|
|
245
|
+
| `case-modification` | `${v^}`, `${v^^}`, `${v,}`, `${v,,}` | no | yes | yes | yes | yes | yes | yes | yes | yes | yes | no |
|
|
246
|
+
| `case-resume` | `;;&` goes on to test the next patterns | no | yes | yes | yes | yes | yes | yes | yes | yes | yes | no |
|
|
247
|
+
| `coproc-alt-mkfifo` | the message's named-pipe pattern: `mkfifo`, a background reader/writer, the script opening both pipes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes |
|
|
248
|
+
| `coproc` | `coproc cat`, writing to and reading back from `${COPROC[@]}` | no | yes | yes | yes | yes | yes | yes | yes | yes | yes | no |
|
|
249
|
+
| `declare-g-alt-assign` | the message's plain `name=value` and `export name=value` inside a function | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes |
|
|
250
|
+
| `declare-g-typeset` | `typeset -g` inside a function sets a global | no | no | no | yes | yes | yes | yes | yes | yes | yes | no |
|
|
251
|
+
| `declare-g` | `declare -g` inside a function sets a global | no | no | no | yes | yes | yes | yes | yes | yes | yes | no |
|
|
252
|
+
| `declare-lu-local` | `local -l` and `typeset -u` | no | yes | yes | yes | yes | yes | yes | yes | yes | yes | no |
|
|
253
|
+
| `declare-lu` | `declare -l` / `declare -u` case-converting attributes | no | yes | yes | yes | yes | yes | yes | yes | yes | yes | no |
|
|
254
|
+
| `double-bracket-alt-test-case` | the message's `[ … ]` for tests and `case` for pattern matching | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes |
|
|
255
|
+
| `double-bracket` | `[[ abc == a* ]]` matches a pattern, and an `if` on it takes its `then` branch | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | no |
|
|
256
|
+
| `epoch-alt-date` | the message's `$(date +%s)` (GNU, BSD and BusyBox `date`, not POSIX) is a number | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes |
|
|
257
|
+
| `epochrealtime` | `$EPOCHREALTIME` is set to seconds with a fraction | no | no | no | no | no | no | yes | yes | yes | yes | no |
|
|
258
|
+
| `epochseconds` | `$EPOCHSECONDS` is set to a number | no | no | no | no | no | no | yes | yes | yes | yes | no |
|
|
259
|
+
| `fd-variable-alt-fixed` | the message's fixed descriptor number, `exec 3>file` … `exec 3>&-` | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes |
|
|
260
|
+
| `fd-variable` | `exec {fd}>file` allocates a descriptor into a variable | no | no | yes | yes | yes | yes | yes | yes | yes | yes | no |
|
|
261
|
+
| `globstar-alt-find` | the message's `find a -name 'f.txt'` recursive search | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes |
|
|
262
|
+
| `globstar` | `shopt -s globstar` makes `**` recurse | no | yes | yes | yes | yes | yes | yes | yes | yes | yes | no |
|
|
263
|
+
| `lastpipe-alt-posix-heredoc` | the POSIX message's here-document feeding the loop | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes |
|
|
264
|
+
| `lastpipe-alt-posix-tmpfile` | the POSIX message's `cmd > "$tmp"` then `while read -r line; do …; done < "$tmp"` | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes |
|
|
265
|
+
| `lastpipe-alt-procsub` | the message's `while read -r line; do …; done < <(cmd)` | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | no |
|
|
266
|
+
| `lastpipe` | `shopt -s lastpipe` runs the last pipeline element in the current shell | no | no | no | yes | yes | yes | yes | yes | yes | yes | no |
|
|
267
|
+
| `let-alt-arithmetic` | the message's arithmetic expansion, `n=$((1 + 2))` | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes |
|
|
268
|
+
| `let` | `let n=1+2` assigns the sum | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | no |
|
|
269
|
+
| `mapfile-alt-posix-read-loop` | the message's `while IFS= read -r` loop, with a named variable as POSIX requires | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes |
|
|
270
|
+
| `mapfile-alt-suggestion` | the suggestion's exact rewrite of `mapfile -t a < in.txt`, from a pipeline too | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | no |
|
|
271
|
+
| `mapfile-readarray` | the `readarray -t a < file` synonym | no | yes | yes | yes | yes | yes | yes | yes | yes | yes | no |
|
|
272
|
+
| `mapfile` | `mapfile -t a < file`, including a last line with no newline | no | yes | yes | yes | yes | yes | yes | yes | yes | yes | no |
|
|
273
|
+
| `nameref-alt-posix-eval` | the POSIX message's `eval "$name=\$value"` to assign and `eval "value=\$$name"` to read by name | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes |
|
|
274
|
+
| `nameref-alt-printf-v` | the message's `printf -v "$name" '%s' value` to assign and `${!name}` to read by name | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | no |
|
|
275
|
+
| `nameref-declare` | `declare -n` and `typeset -n` read through a nameref | no | no | no | no | yes | yes | yes | yes | yes | yes | no |
|
|
276
|
+
| `nameref` | `local -n` makes a nameref that assigns the caller's variable | no | no | no | no | yes | yes | yes | yes | yes | yes | no |
|
|
277
|
+
| `negative-index-alt-length` | the message's `${arr[$((${#arr[@]}-1))]}`, the suggestion's `-2` form, and the docs' assignment form | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | no |
|
|
278
|
+
| `negative-index-assign` | assigning `a[-1]=Z` | no | no | no | no | yes | yes | yes | yes | yes | yes | no |
|
|
279
|
+
| `negative-index-length` | the element length `${#a[-1]}` | no | no | no | no | yes | yes | yes | yes | yes | yes | no |
|
|
280
|
+
| `negative-index-read` | reading `${a[-1]}` and `${a[-2]}` | no | no | no | yes | yes | yes | yes | yes | yes | yes | no |
|
|
281
|
+
| `negative-index-unset` | `unset 'a[-1]'` | no | no | no | no | yes | yes | yes | yes | yes | yes | no |
|
|
282
|
+
| `nounset-empty-array-alt` | the message's and suggestion's `${a[@]+"${a[@]}"}` under `set -u`, empty and with words that hold spaces | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | no |
|
|
283
|
+
| `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 |
|
|
284
|
+
| `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 |
|
|
285
|
+
| `nounset-empty-array` | `"${a[@]}"` of an empty array under `set -u` | no | no | no | no | no | yes | yes | yes | yes | yes | no |
|
|
286
|
+
| `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 |
|
|
287
|
+
| `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 |
|
|
288
|
+
| `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 |
|
|
289
|
+
| `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 |
|
|
290
|
+
| `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 |
|
|
291
|
+
| `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 |
|
|
292
|
+
| `param-transform-k-lower` | `${a[@]@k}` key-value pairs as separate words | no | no | no | no | no | no | no | no | yes | yes | no |
|
|
293
|
+
| `param-transform-k-upper` | `${a[@]@K}` quoted key-value pairs | no | no | no | no | no | no | no | yes | yes | yes | no |
|
|
294
|
+
| `param-transform-q` | `${v@Q}` quoting transformation | no | no | no | no | no | yes | yes | yes | yes | yes | no |
|
|
295
|
+
| `param-transform-ul` | `${v@U}` / `${v@L}` / `${v@u}` case transformations | no | no | no | no | no | no | no | yes | yes | yes | no |
|
|
296
|
+
| `pipe-amp-alt` | the message's and suggestion's `2>&1 \|`, also after a redirection of the left side, as `\|&` orders it | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes |
|
|
297
|
+
| `pipe-amp` | `\|&` pipes stderr too | no | yes | yes | yes | yes | yes | yes | yes | yes | yes | no |
|
|
298
|
+
| `printf-time-alt-date` | the message's `date '+FORMAT'` for the current time, here `date '+%Y'` | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes |
|
|
299
|
+
| `printf-time` | `printf '%(%Y)T' 0` builtin date formatting | no | no | no | yes | yes | yes | yes | yes | yes | yes | no |
|
|
300
|
+
| `redirect-amp-append-alt` | the message's and suggestion's `>> log 2>&1` appends stdout and stderr | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes |
|
|
301
|
+
| `redirect-amp-append` | `&>>` appends stdout and stderr | no | yes | yes | yes | yes | yes | yes | yes | yes | yes | no |
|
|
302
|
+
| `source-alt-dot` | the message's `. ./lib.sh`, the `.` special built-in with a path that contains a slash | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes |
|
|
303
|
+
| `source` | `source ./lib.sh` reads the file into the current shell | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | no |
|
|
304
|
+
| `test-v-alt-plus-set` | the message's `[ -n "${name+set}" ]`, under `set -u`, for a set, an empty and an unset variable | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes |
|
|
305
|
+
| `test-v` | `[[ -v name ]]` and `test -v name` | no | no | no | yes | yes | yes | yes | yes | yes | yes | no |
|
|
306
|
+
| `unicode-escape-alt-octal` | the message's UTF-8 bytes as octal escapes, `$'\303\251'`, with no locale set | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | no |
|
|
307
|
+
| `unicode-escape-beyond-latin1` | `$'\u0041'`, `$'\u2713'` and `$'\U0001F389'`, outside U+0080–U+00FF, are decoded to UTF-8 | no | no | no | yes | yes | yes | yes | yes | yes | yes | no |
|
|
308
|
+
| `unicode-escape` | `$'\u00e9'` (U+00E9, inside U+0080–U+00FF) is decoded to UTF-8 e-acute | no | no | no | no | yes | yes | yes | yes | yes | yes | no |
|
|
309
|
+
| `wait-n-alt-pid` | the message's `wait "$pid"` on a job's saved `$!`, which returns that job's status | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes |
|
|
310
|
+
| `wait-n` | `wait -n` waits for the next job to finish | no | no | no | no | yes | yes | yes | yes | yes | yes | no |
|
|
311
|
+
|
|
312
|
+
How to read it:
|
|
313
|
+
|
|
314
|
+
- **Gates.** Each feature probe turns from "no" to "yes" at exactly the release in the Bash column
|
|
315
|
+
of the rule table, and is "no" under dash, matching every `POSIX sh: no`. Bash 4.1 is in the
|
|
316
|
+
matrix because it is the release just before the 4.2 boundary of `declare -g` and `lastpipe`.
|
|
317
|
+
Bash 4.0 is the release just before the 4.1 boundary of `{fd}` redirections.
|
|
318
|
+
- **POSIX-only gates.** The rows for `[[`, `let` and `source`, the rule table's `every`, are "yes" on
|
|
319
|
+
every Bash in the matrix and "no" under dash, so no `shellVersion` can change whether they report.
|
|
320
|
+
dash runs a script past each one without a syntax error, which is why a shell's own version check
|
|
321
|
+
can guard them; see [A branch for one shell](#a-branch-for-one-shell).
|
|
322
|
+
- **Parameter transformations.** `@Q`, `@E`, `@P`, `@A` and `@a` start working in 4.4; `@U`, `@u`,
|
|
323
|
+
`@L` and `@K` in 5.1; `@k` in 5.2.
|
|
324
|
+
- **Unicode escapes.** `unicode-escape-beyond-latin1` (`\u0041`, `\u2713`, `\U0001F389`) works from
|
|
325
|
+
4.2, but `unicode-escape` (`\u00e9`) only from 4.3: the 4.2 release prints `e9`, the Latin-1 byte,
|
|
326
|
+
for U+00E9. Bash 3.2 to 4.1 print the escape as literal text, with no error.
|
|
327
|
+
- **Empty arrays under `set -u`.** `"${a[@]}"`, `"${a[*]}"` and an unquoted `${a[@]}` abort with
|
|
328
|
+
`unbound variable` through 4.3. `${#a[@]}`, `${!a[@]}` and `${a[@]-}` work in every Bash, so the
|
|
329
|
+
rule does not report them. The slice `${a[@]:0}` and the replacement `${a[@]/x/y}` were also
|
|
330
|
+
probed outside this table: they work on 3.2 to 4.1, fail on 4.2 and 4.3, and work again from 4.4,
|
|
331
|
+
which a minimum-version gate cannot express, so the rule does not report them.
|
|
332
|
+
- **Negative subscripts.** Bash 4.2 reads `${a[-1]}`, but rejects `${#a[-1]}`, `a[-1]=Z` and
|
|
333
|
+
`unset 'a[-1]'` with `bad array subscript`. 4.3 is the first release where every form works.
|
|
334
|
+
- **Alternatives.** Every alternative a message recommends for a Bash target works on Bash 3.2,
|
|
335
|
+
except `param-transform-alt-case-operators` (`"${v^^}"` and its twins), which the
|
|
336
|
+
`no-parameter-transformation` message offers only for a target from 4.0 to 5.0, where it works.
|
|
337
|
+
The ones that are "no" under dash (`< <(cmd)`, the `mapfile` suggestion's array loop, the
|
|
338
|
+
`${arr[$((${#arr[@]}-1))]}` count, `printf -v`, `printf '%q'`, `declare -p`, the `${a[@]+…}` and
|
|
339
|
+
`${a[*]-}` guards, `$'\303\251'`) use Bash syntax and appear only in Bash-target messages; the
|
|
340
|
+
alternatives the POSIX-target messages give (`tr`, a named pipe, a temporary file or here-document,
|
|
341
|
+
a `while IFS= read -r line` loop, a plain assignment, `eval`, `[ -n "${x+set}" ]`, `date`, `find`,
|
|
342
|
+
`wait "$pid"`, `[ … ]` and `case`, `$(( … ))`, `. ./lib.sh`) all work under dash.
|
|
343
|
+
- **`date +%s`.** `epoch-alt-date` passes under every shell here because the probes run with GNU
|
|
344
|
+
`date`. `%s` is not in POSIX `date`; the message says so.
|
|
345
|
+
- **`jq`.** The `no-associative-arrays` advice runs an external program, so its row shows that the
|
|
346
|
+
command as written works wherever `jq` is installed, not that a shell provides it.
|
|
347
|
+
|