@eslint-sh/eslint-plugin 0.5.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +228 -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 +51 -0
- package/dist/configs/posix-portable.d.ts +40 -0
- package/dist/configs/posix-portable.js +53 -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-brace-sequence-padding-step.d.ts +15 -0
- package/dist/rules/compat/no-brace-sequence-padding-step.js +287 -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 +205 -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 +90 -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 +318 -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 +354 -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 +79 -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-brace-sequence-padding-step.md +90 -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 +131 -0
- package/docs/rules/shebang-matches-dialect.md +120 -0
- package/docs/selectors.md +2 -2
- package/docs/shellcheck.md +87 -0
- package/docs/untrusted-code.md +440 -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
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# `sh/compat/no-case-attributes`
|
|
2
|
+
|
|
3
|
+
Disallow the case-converting attributes `declare -l` and `declare -u` (also through `typeset` and
|
|
4
|
+
`local`) in scripts whose target shell lacks them: Bash older than 4.0, including the Bash 3.2
|
|
5
|
+
that macOS ships as `/bin/bash`, and POSIX sh.
|
|
6
|
+
|
|
7
|
+
| Configs | Fixable | Languages |
|
|
8
|
+
| ---------------------------- | ----------- | --------- |
|
|
9
|
+
| `bash-3.2`, `posix-portable` | not fixable | `sh/sh` |
|
|
10
|
+
|
|
11
|
+
## Rule details
|
|
12
|
+
|
|
13
|
+
The `-l` and `-u` attributes arrived in Bash 4.0 (Bash NEWS for 4.0), with the case-modifying expansions that [`sh/compat/no-case-modification`](./no-case-modification.md) reports. Bash 3.2 rejects the option with `declare: -l: invalid option`, so the declaration fails and the variable is left unset.
|
|
14
|
+
|
|
15
|
+
The rule reads the options of every `declare`, `typeset` and `local` command, up to the first operand, as the builtin does. An option word reports when it is a `-` cluster containing `l` or `u` (`-l`, `-u`, `-lx`); the report is on that word. A cluster with both (`-lu`) gets its own message: where the attributes exist, the two cancel out and leave the value as written (observed on Bash 4.0 and 5.3), so the fix is to drop both letters. `-u` of another command (`read -u 3`) and an option after the first operand or after `--` are not read.
|
|
16
|
+
|
|
17
|
+
The portable alternative is to convert the value when it is assigned, with `tr`: `v=$(printf '%s' "$v" | tr '[:upper:]' '[:lower:]')` for `-l`, and `tr '[:lower:]' '[:upper:]'` for `-u`. An attribute converts every later assignment too, so convert at each assignment that needs it.
|
|
18
|
+
|
|
19
|
+
This is a version-compat rule. Whether it reports depends only on `languageOptions`, as
|
|
20
|
+
described in [version compatibility](../../compat.md):
|
|
21
|
+
|
|
22
|
+
- `shellVersion` unset: never reports. An unset version means the newest Bash.
|
|
23
|
+
- `dialect: "bash"` (the default) or `"bats"`: reports when `shellVersion` is older than `4.0`.
|
|
24
|
+
- `dialect: "posix"`: reports at any `shellVersion`, because POSIX sh has no `declare`.
|
|
25
|
+
- `dialect: "mksh"` or `"zsh"`: never reports.
|
|
26
|
+
|
|
27
|
+
## Options
|
|
28
|
+
|
|
29
|
+
This rule has no options. Set the target through `languageOptions`:
|
|
30
|
+
|
|
31
|
+
```js
|
|
32
|
+
// eslint.config.js
|
|
33
|
+
{
|
|
34
|
+
languageOptions: { dialect: 'bash', shellVersion: '3.2' },
|
|
35
|
+
rules: {
|
|
36
|
+
'sh/compat/no-case-attributes': 'error',
|
|
37
|
+
},
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Examples
|
|
42
|
+
|
|
43
|
+
### Incorrect
|
|
44
|
+
|
|
45
|
+
With `shellVersion: "3.2"`:
|
|
46
|
+
|
|
47
|
+
<!-- example: {"dialect": "bash", "shellVersion": "3.2", "findings": 2} -->
|
|
48
|
+
|
|
49
|
+
```sh
|
|
50
|
+
declare -l answer=$1
|
|
51
|
+
f() {
|
|
52
|
+
local -u code=$2
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Correct
|
|
57
|
+
|
|
58
|
+
With `shellVersion: "3.2"`:
|
|
59
|
+
|
|
60
|
+
<!-- example: {"dialect": "bash", "shellVersion": "3.2"} -->
|
|
61
|
+
|
|
62
|
+
```sh
|
|
63
|
+
answer=$(printf '%s' "$1" | tr '[:upper:]' '[:lower:]')
|
|
64
|
+
f() {
|
|
65
|
+
local code
|
|
66
|
+
code=$(printf '%s' "$2" | tr '[:lower:]' '[:upper:]')
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
With `shellVersion: "4.0"` or newer, or with no `shellVersion`, the attributes are fine.
|
|
71
|
+
|
|
72
|
+
## When not to use it
|
|
73
|
+
|
|
74
|
+
Turn the rule off when your scripts never run on Bash older than 4.0, or leave `shellVersion`
|
|
75
|
+
unset, which makes the rule report nothing.
|
|
76
|
+
|
|
77
|
+
## Relationship to shellcheck
|
|
78
|
+
|
|
79
|
+
eslint-sh is a convention and contract linter: a policy layer, where shellcheck is the gotcha
|
|
80
|
+
layer. Run shellcheck beside it.
|
|
81
|
+
|
|
82
|
+
Under `-s sh`, shellcheck 0.11.0 reports the command, not the option: `declare -l` and `declare -u` draw SC3044 (`declare` is undefined in POSIX sh), and `local -l` and `local -u` draw SC3043. Under `-s bash` it reports nothing for these options. It does not know which Bash release a script must run on, so it reports nothing for `declare -l` in a Bash 3.2 target. This rule exists for that target.
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# `sh/compat/no-case-fallthrough`
|
|
2
|
+
|
|
3
|
+
Disallow the `case` terminators `;&` (run the next branch without testing its pattern) and `;;&`
|
|
4
|
+
(go on testing the next patterns) in scripts whose target shell lacks them: Bash older than 4.0,
|
|
5
|
+
including the Bash 3.2 that macOS ships as `/bin/bash`, and POSIX sh.
|
|
6
|
+
|
|
7
|
+
| Configs | Fixable | Languages |
|
|
8
|
+
| ------- | ------- | --------- |
|
|
9
|
+
| `bash-3.2`, `posix-portable` | not fixable | `sh/sh` |
|
|
10
|
+
|
|
11
|
+
## Rule details
|
|
12
|
+
|
|
13
|
+
Both terminators arrived in Bash 4.0 (Bash NEWS for 4.0). Bash 3.2 rejects them as a syntax error (``syntax error near unexpected token `&'``), so the whole script fails to run. POSIX.1-2024 adds `;&`, but dash 0.5.12 still rejects it (`Syntax error: "&" unexpected`), so the rule treats a POSIX target as lacking it until the `sh` binaries people deploy to catch up.
|
|
14
|
+
|
|
15
|
+
The rule reports a `case` branch whose terminator is `;&` or `;;&`; the report is on the terminator. The usual `;;`, a branch with no terminator, and these characters in quotes or in a pattern are not reported.
|
|
16
|
+
|
|
17
|
+
There is no mechanical rewrite, so the rule offers no suggestion. The message names the usual restructuring: for `;&`, repeat the next branch's commands at the end of this branch and end it with `;;`; for `;;&`, end this branch with `;;` and test the patterns that follow it in a second `case` statement.
|
|
18
|
+
|
|
19
|
+
Under `dialect: "posix"` the parser itself rejects both terminators, so a POSIX file that uses them fails to parse and this rule does not run on it.
|
|
20
|
+
|
|
21
|
+
This is a version-compat rule. Whether it reports depends only on `languageOptions`, as
|
|
22
|
+
described in [version compatibility](../../compat.md):
|
|
23
|
+
|
|
24
|
+
- `shellVersion` unset: never reports. An unset version means the newest Bash.
|
|
25
|
+
- `dialect: "bash"` (the default) or `"bats"`: reports when `shellVersion` is older than `4.0`.
|
|
26
|
+
- `dialect: "mksh"` or `"zsh"`: never reports.
|
|
27
|
+
|
|
28
|
+
## Options
|
|
29
|
+
|
|
30
|
+
This rule has no options. Set the target through `languageOptions`:
|
|
31
|
+
|
|
32
|
+
```js
|
|
33
|
+
// eslint.config.js
|
|
34
|
+
{
|
|
35
|
+
languageOptions: { dialect: 'bash', shellVersion: '3.2' },
|
|
36
|
+
rules: {
|
|
37
|
+
'sh/compat/no-case-fallthrough': 'error',
|
|
38
|
+
},
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Examples
|
|
43
|
+
|
|
44
|
+
### Incorrect
|
|
45
|
+
|
|
46
|
+
With `shellVersion: "3.2"`:
|
|
47
|
+
|
|
48
|
+
<!-- example: {"dialect": "bash", "shellVersion": "3.2", "findings": 2} -->
|
|
49
|
+
```sh
|
|
50
|
+
case $level in
|
|
51
|
+
debug) verbose=1 ;&
|
|
52
|
+
info) log=1 ;;
|
|
53
|
+
esac
|
|
54
|
+
case $file in
|
|
55
|
+
*.tar.gz) compressed=1 ;;&
|
|
56
|
+
*.tar*) archive=1 ;;
|
|
57
|
+
esac
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Correct
|
|
61
|
+
|
|
62
|
+
With `shellVersion: "3.2"`:
|
|
63
|
+
|
|
64
|
+
<!-- example: {"dialect": "bash", "shellVersion": "3.2"} -->
|
|
65
|
+
```sh
|
|
66
|
+
case $level in
|
|
67
|
+
debug) verbose=1; log=1 ;;
|
|
68
|
+
info) log=1 ;;
|
|
69
|
+
esac
|
|
70
|
+
case $file in
|
|
71
|
+
*.tar.gz) compressed=1 ;;
|
|
72
|
+
esac
|
|
73
|
+
case $file in
|
|
74
|
+
*.tar*) archive=1 ;;
|
|
75
|
+
esac
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
With `shellVersion: "4.0"` or newer, or with no `shellVersion`, both terminators are fine.
|
|
79
|
+
|
|
80
|
+
## When not to use it
|
|
81
|
+
|
|
82
|
+
Turn the rule off when your scripts never run on Bash older than 4.0, or leave `shellVersion`
|
|
83
|
+
unset, which makes the rule report nothing.
|
|
84
|
+
|
|
85
|
+
## Relationship to shellcheck
|
|
86
|
+
|
|
87
|
+
eslint-sh is a convention and contract linter: a policy layer, where shellcheck is the gotcha
|
|
88
|
+
layer. Run shellcheck beside it.
|
|
89
|
+
|
|
90
|
+
Under `-s sh`, shellcheck 0.11.0 reports `;&` and `;;&` as an error, SC2127 ("To use cases with ;&, specify #!/usr/bin/env bash or ksh", or the `;;&` equivalent naming bash), not as one of its SC3xxx portability checks. It reports the same SC2127 under `-s dash`, reports `;;&` (but not `;&`) under `-s ksh`, and reports nothing under `-s bash`. It does not know which Bash release a script must run on, so it reports nothing for either operator in a Bash 3.2 target. This rule exists for that target.
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# `sh/compat/no-case-modification`
|
|
2
|
+
|
|
3
|
+
Disallow the case-modifying parameter expansions `${v^}`, `${v^^}`, `${v,}` and `${v,,}` in scripts whose
|
|
4
|
+
target shell lacks them: Bash older than 4.0, including the Bash 3.2 that macOS ships as `/bin/bash`.
|
|
5
|
+
|
|
6
|
+
| Configs | Fixable | Languages |
|
|
7
|
+
| ------- | ------- | --------- |
|
|
8
|
+
| `bash-3.2`, `posix-portable` | not fixable | `sh/sh` |
|
|
9
|
+
|
|
10
|
+
## Rule details
|
|
11
|
+
|
|
12
|
+
The four case-modifying expansions arrived in Bash 4.0 (Bash NEWS for 4.0: "new case-modifying word
|
|
13
|
+
expansions"). Bash 3.2 fails the command with `bad substitution` when the word is expanded.
|
|
14
|
+
|
|
15
|
+
The report is on the whole expansion, with or without a trailing pattern (`${v^^[aeiou]}`). The message shows the `tr` replacement for the operator written: `tr` over the whole value for `^^` and `,,`, and `tr` over the first character (`${v%"${v#?}"}`) followed by the rest (`${v#?}`) for `^` and `,`. A pattern form such as `${w^^a}` changes only the characters the pattern matches, which a plain case-converting `tr` does not reproduce (`banana` becomes `bAnAnA`, not `BANANA`), so for those forms the message offers no `tr` equivalent and tells you to spell out the matching characters.
|
|
16
|
+
|
|
17
|
+
This is a version-compat rule. Whether it reports depends only on `languageOptions`, as
|
|
18
|
+
described in [version compatibility](../../compat.md):
|
|
19
|
+
|
|
20
|
+
- `shellVersion` unset: never reports. An unset version means the newest Bash.
|
|
21
|
+
- `dialect: "bash"` (the default) or `"bats"`: reports when `shellVersion` is older than `4.0`.
|
|
22
|
+
- `dialect: "posix"`: reports nothing in practice for this syntax, because the POSIX parser rejects it with a parse error before any rule runs.
|
|
23
|
+
- `dialect: "mksh"` or `"zsh"`: never reports.
|
|
24
|
+
|
|
25
|
+
## Options
|
|
26
|
+
|
|
27
|
+
This rule has no options. Set the target through `languageOptions`:
|
|
28
|
+
|
|
29
|
+
```js
|
|
30
|
+
// eslint.config.js
|
|
31
|
+
{
|
|
32
|
+
languageOptions: { dialect: 'bash', shellVersion: '3.2' },
|
|
33
|
+
rules: {
|
|
34
|
+
'sh/compat/no-case-modification': 'error',
|
|
35
|
+
},
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Examples
|
|
40
|
+
|
|
41
|
+
### Incorrect
|
|
42
|
+
|
|
43
|
+
With `shellVersion: "3.2"`:
|
|
44
|
+
|
|
45
|
+
<!-- example: {"dialect": "bash", "shellVersion": "3.2", "findings": 3} -->
|
|
46
|
+
```sh
|
|
47
|
+
name=${input,,} # lowercase every character
|
|
48
|
+
title=${word^} # uppercase the first character
|
|
49
|
+
flag=${arg^^[a-z]} # with a pattern
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Correct
|
|
53
|
+
|
|
54
|
+
With `shellVersion: "3.2"`:
|
|
55
|
+
|
|
56
|
+
<!-- example: {"dialect": "bash", "shellVersion": "3.2"} -->
|
|
57
|
+
```sh
|
|
58
|
+
name=$(printf '%s' "$input" | tr '[:upper:]' '[:lower:]')
|
|
59
|
+
title=$(printf '%s' "${word%"${word#?}"}" | tr '[:lower:]' '[:upper:]')"${word#?}"
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
With `shellVersion: "4.0"` or newer, or with no `shellVersion`, the original forms are fine.
|
|
63
|
+
|
|
64
|
+
## When not to use it
|
|
65
|
+
|
|
66
|
+
Turn the rule off when your scripts never run on Bash older than 4.0, or leave
|
|
67
|
+
`shellVersion` unset, which makes the rule report nothing.
|
|
68
|
+
|
|
69
|
+
## Relationship to shellcheck
|
|
70
|
+
|
|
71
|
+
eslint-sh is a convention and contract linter: a policy layer, where shellcheck is the gotcha
|
|
72
|
+
layer. Run shellcheck beside it.
|
|
73
|
+
|
|
74
|
+
shellcheck reports case-modifying expansions in scripts it treats as POSIX sh (SC3059), but it cannot target a Bash version, so it cannot report `${v,,}` in a script that must run on Bash 3.2. This rule exists for that target.
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# `sh/compat/no-coproc`
|
|
2
|
+
|
|
3
|
+
Disallow the `coproc` reserved word in scripts whose target shell lacks it: Bash older than 4.0,
|
|
4
|
+
including the Bash 3.2 that macOS ships as `/bin/bash`, and POSIX sh.
|
|
5
|
+
|
|
6
|
+
| Configs | Fixable | Languages |
|
|
7
|
+
| ------- | ------- | --------- |
|
|
8
|
+
| `bash-3.2`, `posix-portable` | not fixable | `sh/sh` |
|
|
9
|
+
|
|
10
|
+
## Rule details
|
|
11
|
+
|
|
12
|
+
`coproc` arrived in Bash 4.0 (Bash NEWS for 4.0: "a new `coproc` reserved word"). On Bash 3.2 the word is an ordinary command name, so `coproc cat` fails with `command not found` and the `COPROC` array is never set.
|
|
13
|
+
|
|
14
|
+
The rule reports every `coproc` clause, named or not, on the whole clause. Under `dialect: "posix"` the parser reads `coproc cat` as an ordinary command, and the rule reports a command named `coproc` too.
|
|
15
|
+
|
|
16
|
+
The portable alternative is a named pipe: create it with `mkfifo`, start the coprocess in the background reading or writing the pipe, and open the pipe from the script.
|
|
17
|
+
|
|
18
|
+
This is a version-compat rule. Whether it reports depends only on `languageOptions`, as
|
|
19
|
+
described in [version compatibility](../../compat.md):
|
|
20
|
+
|
|
21
|
+
- `shellVersion` unset: never reports. An unset version means the newest Bash.
|
|
22
|
+
- `dialect: "bash"` (the default) or `"bats"`: reports when `shellVersion` is older than `4.0`.
|
|
23
|
+
- `dialect: "posix"`: reports at any `shellVersion`.
|
|
24
|
+
- `dialect: "mksh"` or `"zsh"`: never reports.
|
|
25
|
+
|
|
26
|
+
## Options
|
|
27
|
+
|
|
28
|
+
This rule has no options. Set the target through `languageOptions`:
|
|
29
|
+
|
|
30
|
+
```js
|
|
31
|
+
// eslint.config.js
|
|
32
|
+
{
|
|
33
|
+
languageOptions: { dialect: 'bash', shellVersion: '3.2' },
|
|
34
|
+
rules: {
|
|
35
|
+
'sh/compat/no-coproc': 'error',
|
|
36
|
+
},
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Examples
|
|
41
|
+
|
|
42
|
+
### Incorrect
|
|
43
|
+
|
|
44
|
+
With `shellVersion: "3.2"`:
|
|
45
|
+
|
|
46
|
+
<!-- example: {"dialect": "bash", "shellVersion": "3.2", "findings": 2} -->
|
|
47
|
+
```sh
|
|
48
|
+
coproc worker { cat; }
|
|
49
|
+
coproc sort
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Correct
|
|
53
|
+
|
|
54
|
+
With `shellVersion: "3.2"`:
|
|
55
|
+
|
|
56
|
+
<!-- example: {"dialect": "bash", "shellVersion": "3.2"} -->
|
|
57
|
+
```sh
|
|
58
|
+
fifo=$(mktemp -u) && mkfifo "$fifo"
|
|
59
|
+
cat < "$fifo" &
|
|
60
|
+
exec 3> "$fifo"
|
|
61
|
+
printf '%s\n' hello >&3
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
With `shellVersion: "4.0"` or newer, or with no `shellVersion`, the original forms are fine.
|
|
65
|
+
|
|
66
|
+
## When not to use it
|
|
67
|
+
|
|
68
|
+
Turn the rule off when your scripts never run on Bash older than 4.0, or leave
|
|
69
|
+
`shellVersion` unset, which makes the rule report nothing.
|
|
70
|
+
|
|
71
|
+
## Relationship to shellcheck
|
|
72
|
+
|
|
73
|
+
eslint-sh is a convention and contract linter: a policy layer, where shellcheck is the gotcha
|
|
74
|
+
layer. Run shellcheck beside it.
|
|
75
|
+
|
|
76
|
+
shellcheck has no check for `coproc` in a script that must run on an older Bash; it cannot target a Bash version. This rule exists for that target.
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# `sh/compat/no-declare-g`
|
|
2
|
+
|
|
3
|
+
Disallow `declare -g` and `typeset -g` in scripts whose target shell lacks the `-g` option: Bash
|
|
4
|
+
older than 4.2, including the Bash 3.2 that macOS ships as `/bin/bash`, and POSIX sh.
|
|
5
|
+
|
|
6
|
+
| Configs | Fixable | Languages |
|
|
7
|
+
| ------- | ------- | --------- |
|
|
8
|
+
| `bash-3.2`, `posix-portable` | not fixable | `sh/sh` |
|
|
9
|
+
|
|
10
|
+
## Rule details
|
|
11
|
+
|
|
12
|
+
`declare -g` arrived in Bash 4.2 (Bash NEWS for 4.2: "declare/typeset has a new `-g` option"). Bash 3.2 rejects it as an invalid option, so the declaration fails and the variable is never created.
|
|
13
|
+
|
|
14
|
+
The rule reads the options of every `declare` and `typeset` command, up to the first operand, as the builtin does. An option word reports when it is a `-` cluster containing `g` (`-g`, `-gx`, `-Ag`); the report is on that word. `local`, `readonly` and `export` are ignored, and so is a `-g` after the first operand or after `--`.
|
|
15
|
+
|
|
16
|
+
The portable alternative: in Bash before 4.2 a plain `name=value` inside a function sets the global, as long as the function has not declared the name `local`. The two are not identical: a plain `name=value` assigns to a `local` of the same name declared by a calling function (dynamic scope) instead of creating a global, and it does not carry the attributes `declare -g` can set, such as `-i`, `-A` and `-r`, so a script using those must restructure rather than drop the flag.
|
|
17
|
+
|
|
18
|
+
The message says so. For a plain `-g` (and `-gx`, which `export name=value` covers) it suggests the assignment. For any other option (`declare -Ag m`, `typeset -gi n=1`, or as separate words `declare -g -A m`, `typeset -g -r c`) it does not suggest `name=value`, which would drop the attribute; it points at declaring the variable outside any function, or raising the target.
|
|
19
|
+
|
|
20
|
+
This is a version-compat rule. Whether it reports depends only on `languageOptions`, as
|
|
21
|
+
described in [version compatibility](../../compat.md):
|
|
22
|
+
|
|
23
|
+
- `shellVersion` unset: never reports. An unset version means the newest Bash.
|
|
24
|
+
- `dialect: "bash"` (the default) or `"bats"`: reports when `shellVersion` is older than `4.2`.
|
|
25
|
+
- `dialect: "posix"`: reports at any `shellVersion`, because POSIX sh has no `declare`.
|
|
26
|
+
- `dialect: "mksh"` or `"zsh"`: never reports.
|
|
27
|
+
|
|
28
|
+
## Options
|
|
29
|
+
|
|
30
|
+
This rule has no options. Set the target through `languageOptions`:
|
|
31
|
+
|
|
32
|
+
```js
|
|
33
|
+
// eslint.config.js
|
|
34
|
+
{
|
|
35
|
+
languageOptions: { dialect: 'bash', shellVersion: '3.2' },
|
|
36
|
+
rules: {
|
|
37
|
+
'sh/compat/no-declare-g': 'error',
|
|
38
|
+
},
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Examples
|
|
43
|
+
|
|
44
|
+
### Incorrect
|
|
45
|
+
|
|
46
|
+
With `shellVersion: "3.2"`:
|
|
47
|
+
|
|
48
|
+
<!-- example: {"dialect": "bash", "shellVersion": "3.2", "findings": 2} -->
|
|
49
|
+
```sh
|
|
50
|
+
f() {
|
|
51
|
+
declare -g counter=0
|
|
52
|
+
typeset -gx exported=1
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Correct
|
|
57
|
+
|
|
58
|
+
With `shellVersion: "3.2"`:
|
|
59
|
+
|
|
60
|
+
<!-- example: {"dialect": "bash", "shellVersion": "3.2"} -->
|
|
61
|
+
```sh
|
|
62
|
+
f() {
|
|
63
|
+
counter=0
|
|
64
|
+
export exported=1
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
With `shellVersion: "4.2"` or newer, or with no `shellVersion`, the original forms are fine.
|
|
69
|
+
|
|
70
|
+
## When not to use it
|
|
71
|
+
|
|
72
|
+
Turn the rule off when your scripts never run on Bash older than 4.2, or leave
|
|
73
|
+
`shellVersion` unset, which makes the rule report nothing.
|
|
74
|
+
|
|
75
|
+
## Relationship to shellcheck
|
|
76
|
+
|
|
77
|
+
eslint-sh is a convention and contract linter: a policy layer, where shellcheck is the gotcha
|
|
78
|
+
layer. Run shellcheck beside it.
|
|
79
|
+
|
|
80
|
+
shellcheck does not know which Bash a script must run on, so it cannot report `declare -g` for a Bash 3.2 target. This rule exists for that target.
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# `sh/compat/no-double-bracket`
|
|
2
|
+
|
|
3
|
+
Disallow `[[ … ]]` in scripts that target POSIX sh.
|
|
4
|
+
|
|
5
|
+
| Configs | Fixable | Languages |
|
|
6
|
+
| ------- | ------- | --------- |
|
|
7
|
+
| `posix-portable` | not fixable | `sh/sh` |
|
|
8
|
+
|
|
9
|
+
## Rule details
|
|
10
|
+
|
|
11
|
+
POSIX lists `[[` and `]]` among the words whose results are unspecified ([Shell Command Language, section 2.4](https://pubs.opengroup.org/onlinepubs/9799919799/utilities/V3_chap02.html#tag_19_04)). dash, the `/bin/sh` of Debian and Ubuntu, looks up a command named `[[` and fails with `[[: not found` and status 127. Under `set -e` a bare test stops the script. In an `if` condition the script carries on down the wrong branch: `if [[ a == a ]]; then echo yes; else echo no; fi` prints `no`.
|
|
12
|
+
|
|
13
|
+
`bash --posix` and BusyBox ash both accept `[[`, so a script can pass on them and still fail on dash.
|
|
14
|
+
|
|
15
|
+
Under `dialect: "posix"` the parser reads `[[` as an ordinary command, and the rule reports a command whose effective name is `[[`, after following wrappers such as `command`. It reports the `[[` word, because the parser ends that command at the first `&&` or `||`. `command -v [[` only asks about the word and is not reported. A use only a shell with a version variable can reach is not reported either, such as the body of `if [ -n "${BASH_VERSION-}" ]`, because dash never runs it; [version compatibility](../../compat.md#a-branch-for-one-shell) lists the shapes recognized. A `[[` containing a parenthesis, such as `[[ $a =~ ^(x|y)$ ]]`, is a parse error under `dialect: "posix"` and never reaches this rule.
|
|
16
|
+
|
|
17
|
+
The portable alternatives are `[ … ]` for tests and `case` for pattern matching. The rule offers no suggestion, because `[` splits and globs its unquoted operands and has no spelling for `==` patterns or `=~`.
|
|
18
|
+
|
|
19
|
+
This is a POSIX-only gate. Every Bash has `[[`, so whether the rule reports depends only on `dialect`, as described in [version compatibility](../../compat.md):
|
|
20
|
+
|
|
21
|
+
- `dialect: "posix"`: reports at any `shellVersion`.
|
|
22
|
+
- `dialect: "bash"` (the default), `"bats"`, `"mksh"` or `"zsh"`: never reports.
|
|
23
|
+
|
|
24
|
+
## Options
|
|
25
|
+
|
|
26
|
+
This rule has no options. Set the target through `languageOptions`:
|
|
27
|
+
|
|
28
|
+
```js
|
|
29
|
+
// eslint.config.js
|
|
30
|
+
{
|
|
31
|
+
languageOptions: { dialect: 'posix' },
|
|
32
|
+
rules: {
|
|
33
|
+
'sh/compat/no-double-bracket': 'error',
|
|
34
|
+
},
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Examples
|
|
39
|
+
|
|
40
|
+
### Incorrect
|
|
41
|
+
|
|
42
|
+
With `dialect: "posix"`:
|
|
43
|
+
|
|
44
|
+
<!-- example: {"dialect": "posix", "findings": 2} -->
|
|
45
|
+
```sh
|
|
46
|
+
if [[ "$1" == "x" ]]; then echo hi; fi
|
|
47
|
+
[[ -n "$a" && -n "$b" ]]
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Correct
|
|
51
|
+
|
|
52
|
+
With `dialect: "posix"`:
|
|
53
|
+
|
|
54
|
+
<!-- example: {"dialect": "posix"} -->
|
|
55
|
+
```sh
|
|
56
|
+
if [ "$1" = "x" ]; then echo hi; fi
|
|
57
|
+
[ -n "$a" ] && [ -n "$b" ]
|
|
58
|
+
case "$1" in
|
|
59
|
+
x*) echo starts-with-x ;;
|
|
60
|
+
esac
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## When not to use it
|
|
64
|
+
|
|
65
|
+
Turn the rule off when your scripts only ever run where `[[` exists, such as under Bash or BusyBox ash, and set `dialect` to match if you can.
|
|
66
|
+
|
|
67
|
+
## Relationship to shellcheck
|
|
68
|
+
|
|
69
|
+
eslint-sh is a convention and contract linter: a policy layer, where shellcheck is the gotcha
|
|
70
|
+
layer. Run shellcheck beside it.
|
|
71
|
+
|
|
72
|
+
shellcheck reports this construct as SC3010 for a script whose shebang says `sh`. This rule reports it for the dialect you configure, whatever the shebang says, and at the severity you choose. shellcheck keeps the misuse of `[[` and `[`, which this rule does not look at.
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# `sh/compat/no-epoch-variables`
|
|
2
|
+
|
|
3
|
+
Disallow `$EPOCHSECONDS` and `$EPOCHREALTIME` in scripts whose target shell lacks them: Bash older
|
|
4
|
+
than 5.0, including the Bash 3.2 that macOS ships as `/bin/bash`, and POSIX sh.
|
|
5
|
+
|
|
6
|
+
| Configs | Fixable | Languages |
|
|
7
|
+
| ------- | ------- | --------- |
|
|
8
|
+
| `bash-3.2`, `posix-portable` | not fixable | `sh/sh` |
|
|
9
|
+
|
|
10
|
+
## Rule details
|
|
11
|
+
|
|
12
|
+
Both variables arrived in Bash 5.0 (Bash NEWS for 5.0). On an older Bash they are simply unset: nothing fails, and the script quietly gets an empty string where it expected a timestamp. Under `set -u` it aborts instead.
|
|
13
|
+
|
|
14
|
+
The rule reports every parameter expansion of either name (`$EPOCHSECONDS`, `${EPOCHSECONDS:-0}`), unquoted, in double quotes, or in an unquoted here-document, and either name written bare as an operand in arithmetic (`$(( EPOCHSECONDS - start ))`, `(( t = EPOCHREALTIME ))`). Text in single quotes or in a quoted here-document is not an expansion and is not reported, and neither is a longer name such as `$EPOCHSECONDSX`. The report is on the expansion or the bare name.
|
|
15
|
+
|
|
16
|
+
A read inside a feature-detect that already falls back (`case "${EPOCHREALTIME:-}" in [0-9]*) … ;; *) fallback ;; esac`, or `${EPOCHSECONDS:-$(date +%s)}`) still reports: the rule cannot tell that the fallback makes the read safe, so suppress those lines or use the portable form.
|
|
17
|
+
|
|
18
|
+
The usual alternative is `$(date +%s)`. It is not POSIX: `%s` is an extension that GNU, BSD and BusyBox `date` all have. It gives whole seconds, so `$EPOCHREALTIME`'s fraction has no portable equivalent; its message says so.
|
|
19
|
+
|
|
20
|
+
This is a version-compat rule. Whether it reports depends only on `languageOptions`, as
|
|
21
|
+
described in [version compatibility](../../compat.md):
|
|
22
|
+
|
|
23
|
+
- `shellVersion` unset: never reports. An unset version means the newest Bash.
|
|
24
|
+
- `dialect: "bash"` (the default) or `"bats"`: reports when `shellVersion` is older than `5.0`.
|
|
25
|
+
- `dialect: "posix"`: reports at any `shellVersion`, because POSIX sh does not set either variable.
|
|
26
|
+
- `dialect: "mksh"` or `"zsh"`: never reports.
|
|
27
|
+
|
|
28
|
+
## Options
|
|
29
|
+
|
|
30
|
+
This rule has no options. Set the target through `languageOptions`:
|
|
31
|
+
|
|
32
|
+
```js
|
|
33
|
+
// eslint.config.js
|
|
34
|
+
{
|
|
35
|
+
languageOptions: { dialect: 'bash', shellVersion: '3.2' },
|
|
36
|
+
rules: {
|
|
37
|
+
'sh/compat/no-epoch-variables': 'error',
|
|
38
|
+
},
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Examples
|
|
43
|
+
|
|
44
|
+
### Incorrect
|
|
45
|
+
|
|
46
|
+
With `shellVersion: "3.2"`:
|
|
47
|
+
|
|
48
|
+
<!-- example: {"dialect": "bash", "shellVersion": "3.2", "findings": 2} -->
|
|
49
|
+
```sh
|
|
50
|
+
start=$EPOCHSECONDS
|
|
51
|
+
run_tests
|
|
52
|
+
echo "took $(( EPOCHSECONDS - start ))s"
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### Correct
|
|
56
|
+
|
|
57
|
+
With `shellVersion: "3.2"`:
|
|
58
|
+
|
|
59
|
+
<!-- example: {"dialect": "bash", "shellVersion": "3.2"} -->
|
|
60
|
+
```sh
|
|
61
|
+
start=$(date +%s)
|
|
62
|
+
run_tests
|
|
63
|
+
echo "took $(( $(date +%s) - start ))s"
|
|
64
|
+
echo 'the variable is spelled $EPOCHSECONDS'
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
With `shellVersion: "5.0"` or newer, or with no `shellVersion`, both variables are fine.
|
|
68
|
+
|
|
69
|
+
## When not to use it
|
|
70
|
+
|
|
71
|
+
Turn the rule off when your scripts never run on Bash older than 5.0, or leave `shellVersion`
|
|
72
|
+
unset, which makes the rule report nothing.
|
|
73
|
+
|
|
74
|
+
## Relationship to shellcheck
|
|
75
|
+
|
|
76
|
+
eslint-sh is a convention and contract linter: a policy layer, where shellcheck is the gotcha
|
|
77
|
+
layer. Run shellcheck beside it.
|
|
78
|
+
|
|
79
|
+
shellcheck 0.11.0 reports `$EPOCHSECONDS` and `$EPOCHREALTIME` as undefined in POSIX sh (SC3028) under `-s sh`, and nothing under `-s bash`. It does not know which Bash release a script must run on, so it reports nothing for either variable in a Bash 4.4 target. This rule exists for that target.
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# `sh/compat/no-fd-variable`
|
|
2
|
+
|
|
3
|
+
Disallow `{varname}` redirections, which have the shell pick a free file descriptor and store its
|
|
4
|
+
number in a variable (`exec {fd}>file`), in scripts whose target shell lacks them: Bash older than
|
|
5
|
+
4.1, including the Bash 3.2 that macOS ships as `/bin/bash`, and POSIX sh.
|
|
6
|
+
|
|
7
|
+
| Configs | Fixable | Languages |
|
|
8
|
+
| ------- | ------- | --------- |
|
|
9
|
+
| `bash-3.2`, `posix-portable` | not fixable | `sh/sh` |
|
|
10
|
+
|
|
11
|
+
## Rule details
|
|
12
|
+
|
|
13
|
+
Descriptor allocation arrived in Bash 4.1 (Bash NEWS for 4.1). Bash 4.0 and older treat `{fd}` as an ordinary word, so `exec {fd}>out` tries to run a command named `{fd}` and fails with `exec: {fd}: not found`.
|
|
14
|
+
|
|
15
|
+
The rule reports a redirection whose descriptor is written `{name}`, which the parser records separately from the redirection's target; the report is on the whole redirection, including when it closes the descriptor (`exec {fd}>&-`). A numbered descriptor (`3>file`), a `{…}` word separated from the redirection by a space (`echo {fd} >out`), a brace group, and the same text in quotes are not reported.
|
|
16
|
+
|
|
17
|
+
The portable alternative is a fixed descriptor number: `exec 3>file`, write with `>&3`, then close it with `exec 3>&-`. Pick a number the script does not use for anything else.
|
|
18
|
+
|
|
19
|
+
Under `dialect: "posix"` the parser itself rejects `{varname}` redirections, so a POSIX file that uses one fails to parse and this rule does not run on it.
|
|
20
|
+
|
|
21
|
+
This is a version-compat rule. Whether it reports depends only on `languageOptions`, as
|
|
22
|
+
described in [version compatibility](../../compat.md):
|
|
23
|
+
|
|
24
|
+
- `shellVersion` unset: never reports. An unset version means the newest Bash.
|
|
25
|
+
- `dialect: "bash"` (the default) or `"bats"`: reports when `shellVersion` is older than `4.1`.
|
|
26
|
+
- `dialect: "mksh"` or `"zsh"`: never reports.
|
|
27
|
+
|
|
28
|
+
## Options
|
|
29
|
+
|
|
30
|
+
This rule has no options. Set the target through `languageOptions`:
|
|
31
|
+
|
|
32
|
+
```js
|
|
33
|
+
// eslint.config.js
|
|
34
|
+
{
|
|
35
|
+
languageOptions: { dialect: 'bash', shellVersion: '3.2' },
|
|
36
|
+
rules: {
|
|
37
|
+
'sh/compat/no-fd-variable': 'error',
|
|
38
|
+
},
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Examples
|
|
43
|
+
|
|
44
|
+
### Incorrect
|
|
45
|
+
|
|
46
|
+
With `shellVersion: "3.2"`:
|
|
47
|
+
|
|
48
|
+
<!-- example: {"dialect": "bash", "shellVersion": "3.2", "findings": 2} -->
|
|
49
|
+
```sh
|
|
50
|
+
exec {log}>>"$logfile"
|
|
51
|
+
echo started >&"$log"
|
|
52
|
+
exec {log}>&-
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### Correct
|
|
56
|
+
|
|
57
|
+
With `shellVersion: "3.2"`:
|
|
58
|
+
|
|
59
|
+
<!-- example: {"dialect": "bash", "shellVersion": "3.2"} -->
|
|
60
|
+
```sh
|
|
61
|
+
exec 3>>"$logfile"
|
|
62
|
+
echo started >&3
|
|
63
|
+
exec 3>&-
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
With `shellVersion: "4.1"` or newer, or with no `shellVersion`, `{varname}` redirections are fine.
|
|
67
|
+
|
|
68
|
+
## When not to use it
|
|
69
|
+
|
|
70
|
+
Turn the rule off when your scripts never run on Bash older than 4.1, or leave `shellVersion`
|
|
71
|
+
unset, which makes the rule report nothing.
|
|
72
|
+
|
|
73
|
+
## Relationship to shellcheck
|
|
74
|
+
|
|
75
|
+
eslint-sh is a convention and contract linter: a policy layer, where shellcheck is the gotcha
|
|
76
|
+
layer. Run shellcheck beside it.
|
|
77
|
+
|
|
78
|
+
shellcheck reports `{varname}` redirections only for a script it treats as POSIX `sh` (its SC3xxx portability checks). It does not know which Bash release a script must run on, so it cannot report them for a Bash 3.2 target. This rule exists for that target.
|