@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,131 @@
|
|
|
1
|
+
# `sh/require-tty-guard`
|
|
2
|
+
|
|
3
|
+
Require interactive commands such as `read` and `select` to be guarded by a `[ -t 0 ]` test, or to
|
|
4
|
+
have their input supplied explicitly.
|
|
5
|
+
|
|
6
|
+
| Configs | Fixable | Languages |
|
|
7
|
+
| ---------------- | ---------------------------- | --------- |
|
|
8
|
+
| `agent-authored` | suggestions (wrap in a guard) | `sh/sh` |
|
|
9
|
+
|
|
10
|
+
## Rule details
|
|
11
|
+
|
|
12
|
+
A prompt that waits on the terminal hangs forever when a script runs without one: in CI, from
|
|
13
|
+
cron, or from an agent's tool call. In automation it also reads whatever happens to be on stdin.
|
|
14
|
+
This rule reports `read` and `select` (and any command you add with `commands`) that are not
|
|
15
|
+
guarded.
|
|
16
|
+
|
|
17
|
+
The check is **purely syntactic**. The rule never follows data or control flow, so what it treats
|
|
18
|
+
as guarded is exactly the following.
|
|
19
|
+
|
|
20
|
+
A command is guarded by a test when it is:
|
|
21
|
+
|
|
22
|
+
- lexically inside the `then` or `else` body of an `if` whose condition contains a TTY test, or
|
|
23
|
+
- on the right of an `&&` whose left side contains a TTY test (`[ -t 0 ] && [ -n "$X" ] && read x`).
|
|
24
|
+
|
|
25
|
+
A TTY test is `[ -t 0 ]`, `test -t 0` or `[[ -t 0 ]]`. Only file descriptor `0` counts, and the
|
|
26
|
+
test may sit anywhere in the condition: `[ ! -t 0 ]` is also recognized as a TTY test, because
|
|
27
|
+
the rule does not interpret negation.
|
|
28
|
+
|
|
29
|
+
A command has its input supplied explicitly, and is not reported, when:
|
|
30
|
+
|
|
31
|
+
- it, or any statement that encloses it, redirects fd 0 from a file, here-document or
|
|
32
|
+
here-string (`read x < file`, `read x <<< "$v"`, `while read l; do …; done < file`), or from
|
|
33
|
+
another descriptor (`read x <&3`);
|
|
34
|
+
- it is on the right of a pipe (`echo a | read x`, `cat f | while read l; do …; done`);
|
|
35
|
+
- it is inside a `coproc`, which reads its own input pipe;
|
|
36
|
+
- it is `read -u N` for a descriptor other than `0` (`read -u 3 x`, `read -ru3 x`). A descriptor
|
|
37
|
+
that is not a static value is also treated as not stdin.
|
|
38
|
+
|
|
39
|
+
A `read` with `-p` is still reported: the prompt does not make it safe without a terminal.
|
|
40
|
+
|
|
41
|
+
### Limits
|
|
42
|
+
|
|
43
|
+
The rule is honest about what it cannot see:
|
|
44
|
+
|
|
45
|
+
- **A guard in a calling function is not seen.** If `ask() { read x; }` is only ever called as
|
|
46
|
+
`[ -t 0 ] && ask`, the `read` is still reported.
|
|
47
|
+
- **Early exits are not seen.** `[ -t 0 ] || exit 1` followed by `read x` does not guard the
|
|
48
|
+
`read`; neither does a guard in an earlier statement, a `||` list, or a `while` condition.
|
|
49
|
+
- **Negation is not interpreted.** `if [ ! -t 0 ]; then read x; fi` counts as guarded.
|
|
50
|
+
- **Variables are not tracked.** `is_tty=$([ -t 0 ] && echo 1)` is not a guard.
|
|
51
|
+
- **Command names are read as written.** A command reached through a wrapper (`command read x`)
|
|
52
|
+
or with a dynamic name is not matched.
|
|
53
|
+
|
|
54
|
+
## Options
|
|
55
|
+
|
|
56
|
+
- `commands` (`string[]`, default `['read', 'select']`): the interactive commands to check. A
|
|
57
|
+
configured list **replaces** the default, as with `sh/require-network-wrapper`; include `read`
|
|
58
|
+
and `select` to keep them. `select` is parsed as a loop, not a command, and is checked whenever
|
|
59
|
+
the list contains `select`.
|
|
60
|
+
|
|
61
|
+
```js
|
|
62
|
+
// eslint.config.js
|
|
63
|
+
{
|
|
64
|
+
rules: {
|
|
65
|
+
'sh/require-tty-guard': ['error', { commands: ['read', 'select', 'confirm'] }],
|
|
66
|
+
},
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Suggestion
|
|
71
|
+
|
|
72
|
+
The rule offers one suggestion per report: wrap the statement in `if [ -t 0 ]; then … fi`:
|
|
73
|
+
|
|
74
|
+
<!-- transform: {"kind": "suggestion"} -->
|
|
75
|
+
|
|
76
|
+
```sh
|
|
77
|
+
read name
|
|
78
|
+
# becomes
|
|
79
|
+
if [ -t 0 ]; then read name; fi
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
A multi-line statement that starts its own line is wrapped on lines of its own. Anywhere else the
|
|
83
|
+
wrapper goes around it in place, because an earlier command on the line may have opened a
|
|
84
|
+
here-document whose body begins at the next line break; a line break goes before `fi` only when the
|
|
85
|
+
statement ends in a here-document of its own. A statement whose here-document opener shares its
|
|
86
|
+
line with another command (`read x 3<<EOF; echo hi`, or a trailing comment) gets no suggestion: the
|
|
87
|
+
statement's source runs on to the terminator, so wrapping it would pull that command into the guard
|
|
88
|
+
and it would run only on a terminal. A statement run in the background (`read x &`)
|
|
89
|
+
gets no suggestion, because wrapping it would change what is backgrounded. A statement whose exit
|
|
90
|
+
status steers control flow (an `if`/`while`/`until` condition, or an operand of `&&` or `||`) is
|
|
91
|
+
reported without a suggestion too: the wrapper would exit 0 when there is no terminal, which
|
|
92
|
+
changes which way the construct goes. The suggestion is
|
|
93
|
+
never applied by `--fix`; whether the script should skip the prompt or fail when there is no
|
|
94
|
+
terminal is your decision.
|
|
95
|
+
|
|
96
|
+
## Examples
|
|
97
|
+
|
|
98
|
+
### Incorrect
|
|
99
|
+
|
|
100
|
+
<!-- example: {"findings": 4, "messageIds": ["unguardedInteractive", "unguardedInteractive", "unguardedInteractive", "unguardedInteractive"]} -->
|
|
101
|
+
```sh
|
|
102
|
+
read name # waits on stdin with no terminal check
|
|
103
|
+
read -p "Name: " name # a prompt does not make it safe
|
|
104
|
+
select opt in a b; do :; done # select reads the terminal too
|
|
105
|
+
[ -t 0 ] || read x # `||` is not a guard
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### Correct
|
|
109
|
+
|
|
110
|
+
```sh
|
|
111
|
+
if [ -t 0 ]; then read name; fi
|
|
112
|
+
[ -t 0 ] && read -p "Name: " name
|
|
113
|
+
read name < answers.txt
|
|
114
|
+
read name <<< "$default"
|
|
115
|
+
cat answers.txt | while read -r line; do :; done
|
|
116
|
+
read -u 3 name # from descriptor 3, not stdin
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## When not to use it
|
|
120
|
+
|
|
121
|
+
Turn the rule off for scripts that are interactive by definition and never run unattended, such
|
|
122
|
+
as a local setup wizard.
|
|
123
|
+
|
|
124
|
+
## Relationship to shellcheck
|
|
125
|
+
|
|
126
|
+
eslint-sh is a convention and contract linter: a policy layer, where shellcheck is the gotcha
|
|
127
|
+
layer. Run shellcheck beside it.
|
|
128
|
+
|
|
129
|
+
shellcheck has no equivalent check: it does not report a `read` that can run without a terminal.
|
|
130
|
+
Its `read` diagnostics (for example SC2162, `read` without `-r`) concern how input is parsed, not
|
|
131
|
+
where it comes from.
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# `sh/shebang-matches-dialect`
|
|
2
|
+
|
|
3
|
+
Require the shebang to name the same kind of shell, bash or POSIX, as `languageOptions.dialect`.
|
|
4
|
+
|
|
5
|
+
| Configs | Fixable | Languages |
|
|
6
|
+
| ------------------------------- | ----------- | --------- |
|
|
7
|
+
| `recommended`, `agent-authored`, `posix-portable` | not fixable | `sh/sh` |
|
|
8
|
+
|
|
9
|
+
## Rule details
|
|
10
|
+
|
|
11
|
+
Dialect-sensitive rules read `languageOptions.dialect` from your config, not the file. When the
|
|
12
|
+
two disagree they advise for the wrong shell. A `#!/bin/sh` script linted under the default
|
|
13
|
+
`bash` dialect is told, by [`sh/require-strict-mode`](./require-strict-mode.md), to turn on
|
|
14
|
+
`pipefail`, which dash and older BusyBox `ash` reject. This rule reports the disagreement once,
|
|
15
|
+
at the shebang (line 1, in full), so the fix is one config entry or one line.
|
|
16
|
+
|
|
17
|
+
It reports two cases:
|
|
18
|
+
|
|
19
|
+
- **`posixShebangBashDialect`**: the shebang runs a POSIX shell (`sh`, `dash` or `ash`) but the
|
|
20
|
+
dialect is `bash` (the default).
|
|
21
|
+
- **`bashShebangPosixDialect`**: the shebang runs `bash` but the dialect is `posix`.
|
|
22
|
+
|
|
23
|
+
The shell is found the way [`sh/require-shebang`](./require-shebang.md) finds it: an absolute
|
|
24
|
+
interpreter path (`#!/bin/sh`, `#!/usr/local/bin/bash`), or an absolute-path `env` and the command
|
|
25
|
+
it runs (`#!/usr/bin/env sh`, `#!/usr/bin/env -S bash -e`). A relative interpreter such as
|
|
26
|
+
`#!sh` is not read; `sh/require-shebang` reports it.
|
|
27
|
+
|
|
28
|
+
Nothing else is reported. A file with no shebang is clean here (that is `sh/require-shebang`'s
|
|
29
|
+
concern), as is a shebang that names another interpreter, and any file whose dialect is `mksh`,
|
|
30
|
+
`zsh` or `bats`. A shebang and dialect that already agree are clean: `#!/usr/bin/env bash` under
|
|
31
|
+
`bash`, `#!/bin/sh` under `posix`.
|
|
32
|
+
|
|
33
|
+
The message tells you to set the dialect for these files, or to change the shebang. To set it,
|
|
34
|
+
scope `languageOptions.dialect` to the files that use that shell:
|
|
35
|
+
|
|
36
|
+
```js
|
|
37
|
+
// eslint.config.js
|
|
38
|
+
export default [
|
|
39
|
+
{
|
|
40
|
+
files: ['scripts/posix/**/*.sh'],
|
|
41
|
+
languageOptions: { dialect: 'posix' },
|
|
42
|
+
},
|
|
43
|
+
];
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Options
|
|
47
|
+
|
|
48
|
+
This rule has no options.
|
|
49
|
+
|
|
50
|
+
```js
|
|
51
|
+
// eslint.config.js
|
|
52
|
+
{
|
|
53
|
+
rules: {
|
|
54
|
+
'sh/shebang-matches-dialect': 'error',
|
|
55
|
+
},
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Examples
|
|
60
|
+
|
|
61
|
+
### Incorrect
|
|
62
|
+
|
|
63
|
+
<!-- example: {"dialect": "bash", "messageIds": ["posixShebangBashDialect"], "findings": 1} -->
|
|
64
|
+
```sh
|
|
65
|
+
#!/bin/sh
|
|
66
|
+
# dialect: 'bash' (the default): posixShebangBashDialect
|
|
67
|
+
echo "hello"
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
<!-- example: {"dialect": "bash", "messageIds": ["posixShebangBashDialect"], "findings": 1} -->
|
|
71
|
+
```sh
|
|
72
|
+
#!/usr/bin/env dash
|
|
73
|
+
# dialect: 'bash': posixShebangBashDialect
|
|
74
|
+
echo "hello"
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
<!-- example: {"dialect": "posix", "messageIds": ["bashShebangPosixDialect"], "findings": 1} -->
|
|
78
|
+
```sh
|
|
79
|
+
#!/usr/bin/env bash
|
|
80
|
+
# dialect: 'posix': bashShebangPosixDialect
|
|
81
|
+
echo "hello"
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Correct
|
|
85
|
+
|
|
86
|
+
<!-- example: {"dialect": "bash"} -->
|
|
87
|
+
```sh
|
|
88
|
+
#!/usr/bin/env bash
|
|
89
|
+
# dialect: 'bash'
|
|
90
|
+
echo "hello"
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
<!-- example: {"dialect": "posix"} -->
|
|
94
|
+
```sh
|
|
95
|
+
#!/bin/sh
|
|
96
|
+
# dialect: 'posix'
|
|
97
|
+
echo "hello"
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
```sh
|
|
101
|
+
echo "no shebang: sh/require-shebang's concern, not this rule's"
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## When not to use it
|
|
105
|
+
|
|
106
|
+
Turn it off if your project deliberately lints POSIX-shebang scripts as bash, for example
|
|
107
|
+
because every `sh` on your targets is bash. Also turn it off for files you only source, whose
|
|
108
|
+
shebang is never read.
|
|
109
|
+
|
|
110
|
+
## Relationship to shellcheck
|
|
111
|
+
|
|
112
|
+
eslint-sh is a convention and contract linter: a policy layer, where shellcheck is the gotcha
|
|
113
|
+
layer. Run shellcheck beside it.
|
|
114
|
+
|
|
115
|
+
shellcheck chooses its dialect from the shebang (or a `shell=` directive) and then checks the file
|
|
116
|
+
against it, so it has no notion of a mismatch between the shebang and a separately configured
|
|
117
|
+
dialect. Its SC3xxx checks report bash-only features in a `#!/bin/sh` script, which is
|
|
118
|
+
a different question: that the syntax is wrong for the shebang, not that your lint configuration
|
|
119
|
+
disagrees with it. This rule keeps eslint-sh's own dialect-sensitive rules honest about the
|
|
120
|
+
shell they are advising for.
|
package/docs/selectors.md
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
classes** against shell ASTs, in addition to matching bare node type names
|
|
6
6
|
(`CallExpr`, `IfClause`, ...). Selector-first rule authoring — writing a rule as a selector string
|
|
7
7
|
rather than a `create()` full of manual type checks — is a headline feature of this plugin; this
|
|
8
|
-
page is a copy-paste cookbook of real selectors, each backed by a passing test
|
|
9
|
-
|
|
8
|
+
page is a copy-paste cookbook of real selectors, each backed by a passing test in this package's
|
|
9
|
+
test suite so the examples can't silently rot as the plugin evolves.
|
|
10
10
|
|
|
11
11
|
Looking for a complete, runnable `create()` that uses one of these selectors — the full
|
|
12
12
|
`{ meta, create(context) }` rule module, `context.report`, and the `eslint.config.mjs`
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# Running eslint-sh beside shellcheck
|
|
2
|
+
|
|
3
|
+
Run both. They answer different questions, and each is blind to what the other checks.
|
|
4
|
+
|
|
5
|
+
- **shellcheck** is the general-correctness tool. It finds shell gotchas: quoting and
|
|
6
|
+
word-splitting mistakes, constructs that behave differently from how they read, and
|
|
7
|
+
portability problems for the shell named in the shebang. It works from a fixed catalog of
|
|
8
|
+
checks and has no plugin system, so it cannot learn your project's rules.
|
|
9
|
+
- **eslint-sh** is the policy tool. It enforces conventions and contracts that shellcheck
|
|
10
|
+
treats as style or cannot express: a required strict-mode prologue, declared commands,
|
|
11
|
+
network wrappers, and the shell version or dialect you deploy to.
|
|
12
|
+
|
|
13
|
+
eslint-sh does not aim to reproduce shellcheck's diagnostic catalog. If a check is only about
|
|
14
|
+
whether a construct is a bug, shellcheck owns it.
|
|
15
|
+
|
|
16
|
+
## Running both
|
|
17
|
+
|
|
18
|
+
They are independent commands with independent exit codes. Run them side by side in CI and in
|
|
19
|
+
your editor:
|
|
20
|
+
|
|
21
|
+
```sh
|
|
22
|
+
git ls-files -z '*.sh' '*.bash' 'bin/*' | xargs -0 sh -c '[ "$#" -eq 0 ] || exec shellcheck -- "$@"' sh
|
|
23
|
+
npx eslint . --max-warnings 0
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
The first command checks every tracked shell script, including extensionless ones under `bin/`,
|
|
27
|
+
and does nothing in a project with no such files. Adapt the pathspecs to where your scripts live.
|
|
28
|
+
|
|
29
|
+
`eslint .` lints the files your config's `files` patterns select, including extensionless
|
|
30
|
+
scripts when the config lists `bin/?*`. A CLI glob such as `'scripts/**/*.sh'` would skip them.
|
|
31
|
+
For a complete GitHub Actions job, exit codes, a pre-commit hook and editor setup, see the
|
|
32
|
+
[`eslint-sh` README](https://github.com/mike-north/eslint-sh/blob/main/packages/eslint-sh/README.md#continuous-integration).
|
|
33
|
+
|
|
34
|
+
Give them the same target shell, since each reads its own configuration. shellcheck takes it
|
|
35
|
+
from the shebang or a `# shellcheck shell=…` directive; eslint-sh takes it from
|
|
36
|
+
`languageOptions.dialect` and `languageOptions.shellVersion`. The `bash-3.2` and
|
|
37
|
+
`posix-portable` presets set the latter for you, and
|
|
38
|
+
[`sh/shebang-matches-dialect`](./rules/shebang-matches-dialect.md) reports a
|
|
39
|
+
shebang that disagrees with the configured dialect, so the two tools do not end up advising for
|
|
40
|
+
different shells.
|
|
41
|
+
|
|
42
|
+
Silence each tool with its own directive: `# shellcheck disable=SC2086` for shellcheck and
|
|
43
|
+
`# eslint-disable-next-line sh/quote-expansions -- reason` for eslint-sh. One does not
|
|
44
|
+
silence the other.
|
|
45
|
+
|
|
46
|
+
## Who owns what
|
|
47
|
+
|
|
48
|
+
| Concern | Owner | Notes |
|
|
49
|
+
| ------- | ----- | ----- |
|
|
50
|
+
| Quoting and word splitting in general (SC2086, SC2068, …) | shellcheck | eslint-sh's [`quote-expansions`](./rules/quote-expansions.md) covers only the list contexts it documents, deliberately. |
|
|
51
|
+
| Unreachable code, useless commands, misuse of `[` / `[[` / `test`, `$?` mistakes | shellcheck | Correctness gotchas with no policy dimension. |
|
|
52
|
+
| Bugs caused by subshells, pipelines, and `read` without `-r` | shellcheck | Parsing and scoping behavior. |
|
|
53
|
+
| `source` of a path shellcheck cannot follow (SC1090, SC1091) | shellcheck | Configure its `source-path`. eslint-sh only objects to a non-literal path as a policy. |
|
|
54
|
+
| Bash-only syntax in a script whose shebang says `sh` (SC3xxx) | shellcheck | It judges the file against its own shebang. eslint-sh reports only the few constructs [`posix-portable`](./compat.md#presets) names, and leaves `local`, `echo -e` and [the rest](./compat.md#what-posix-portable-does-not-report) here. |
|
|
55
|
+
| Bash-only features on a specific Bash version, such as the 3.2 macOS ships | eslint-sh | shellcheck cannot be pointed at a Bash version. See [version compatibility](./compat.md). |
|
|
56
|
+
| Whether the shebang agrees with the dialect you lint for | eslint-sh | [`shebang-matches-dialect`](./rules/shebang-matches-dialect.md). |
|
|
57
|
+
| A strict-mode prologue every script must start with | eslint-sh | shellcheck only warns about the consequences of lacking one. |
|
|
58
|
+
| Which commands a script may run, and that each is declared | eslint-sh | [`no-dynamic-invocation`](./rules/no-dynamic-invocation.md), [`no-undeclared-commands`](./rules/no-undeclared-commands.md), [`no-unused-declarations`](./rules/no-unused-declarations.md). Needs your declaration convention. |
|
|
59
|
+
| Network commands going through your wrapper | eslint-sh | [`require-network-wrapper`](./rules/require-network-wrapper.md). Needs your wrapper name. |
|
|
60
|
+
| Secrets passed as flags or read from environment variables | eslint-sh | [`no-secret-flags`](./rules/no-secret-flags.md), [`no-secret-env-reads`](./rules/no-secret-env-reads.md). |
|
|
61
|
+
| Size and structure caps on a script | eslint-sh | [`max-script-complexity`](./rules/max-script-complexity.md). |
|
|
62
|
+
| Guarding interactive `read` with a TTY check | eslint-sh | [`require-tty-guard`](./rules/require-tty-guard.md). shellcheck's `read` checks concern how input is parsed. |
|
|
63
|
+
|
|
64
|
+
## Deliberate overlaps
|
|
65
|
+
|
|
66
|
+
Some eslint-sh rules report the same code as a shellcheck check. They exist anyway for the
|
|
67
|
+
reason given, and each rule's page has a "Relationship to shellcheck" section. The shellcheck findings for the version-compat rules below were observed with shellcheck 0.11.0 under `-s sh` and `-s bash`.
|
|
68
|
+
|
|
69
|
+
| eslint-sh rule | Overlapping shellcheck code | Why the rule exists anyway |
|
|
70
|
+
| -------------- | --------------------------- | -------------------------- |
|
|
71
|
+
| [`sh/quote-expansions`](./rules/quote-expansions.md) | SC2086, SC2068 | Makes unquoted expansions a project policy with a severity you choose, and offers a quoting suggestion. It is `warn` in `recommended`; disable it if you already enforce SC2086. |
|
|
72
|
+
| [`sh/no-eval`](./rules/no-eval.md) | SC2294 (`eval` on arrays), SC1090 (non-literal `source`) | shellcheck flags `eval` only in specific misuse patterns. The rule treats every `eval` and every non-literal `source` as a reviewable exception. |
|
|
73
|
+
| [`sh/no-unchecked-cd`](./rules/no-unchecked-cd.md) | SC2164 | Kept for files that run without `set -e`; the two agree on the common cases. |
|
|
74
|
+
| [`sh/require-shebang`](./rules/require-shebang.md) | SC2148, SC1008 | A required, recognized shebang is a rule of the repository, not a hint about the target shell. |
|
|
75
|
+
| [`sh/no-masked-exit-status`](./rules/no-masked-exit-status.md) | SC2155 | `require-strict-mode` makes `set -euo pipefail` a contract, and `local x=$(cmd)` is the most common way a script that honours it still swallows a failure. shellcheck offers no fix; the rule offers the split as an editor suggestion and can be limited to files where errexit is on. It is not in `recommended`, because that preset runs beside shellcheck. |
|
|
76
|
+
| [`sh/prefer-local`](./rules/prefer-local.md) | SC2155 (a related check) | Requires `local` declarations, and its fix declares the variable on a separate line, which avoids SC2155. |
|
|
77
|
+
| [`sh/compat/no-associative-arrays`](./rules/compat/no-associative-arrays.md), [`no-case-modification`](./rules/compat/no-case-modification.md), [`no-negative-array-index`](./rules/compat/no-negative-array-index.md), [`no-mapfile`](./rules/compat/no-mapfile.md) | SC3044 (the `declare` or `mapfile` command, not the `-A` option), SC3043 (`local -A`), SC3054 (`${a[-1]}`), SC3059 (case modification) | shellcheck reports these only for scripts it treats as POSIX `sh`. These rules report them for a Bash version you name. |
|
|
78
|
+
| [`sh/compat/no-double-bracket`](./rules/compat/no-double-bracket.md), [`no-let`](./rules/compat/no-let.md), [`no-source`](./rules/compat/no-source.md) | SC3010 (`[[`), SC3039 (`let`), SC3046 (`source`), under `-s sh` only | Under `-s bash` shellcheck reports nothing for `[[` or `source`, and for `let` only SC2219, a style note that prefers `(( ))`. These rules report them for the dialect you configure, because they fail on dash and would otherwise defeat a preset named for portability. |
|
|
79
|
+
| [`sh/compat/no-test-v`](./rules/compat/no-test-v.md), [`no-wait-n`](./rules/compat/no-wait-n.md), [`no-globstar`](./rules/compat/no-globstar.md), [`no-pipe-all`](./rules/compat/no-pipe-all.md), [`no-fd-variable`](./rules/compat/no-fd-variable.md), [`no-unicode-escape`](./rules/compat/no-unicode-escape.md) | SC3016 (`test -v`, with SC3010 for `[[`), SC3045 (`wait -n`), SC3044 (`shopt`), SC3029 (`\|&`), SC3022 (`{fd}`), SC3003 (`$'…'`) | shellcheck reports these Bash features only for scripts it treats as POSIX `sh`. These rules report them for a Bash version you name. |
|
|
80
|
+
| [`sh/compat/no-nameref`](./rules/compat/no-nameref.md), [`no-case-attributes`](./rules/compat/no-case-attributes.md) | SC3043 (`local`), SC3044 (`declare`, `typeset`): the command, not the `-n`, `-l` or `-u` option | Under `-s sh` shellcheck reports the command that carries the option; under `-s bash` it reports nothing for either. These rules report the option for a Bash version you name. |
|
|
81
|
+
| [`sh/compat/no-brace-sequence-padding-step`](./rules/compat/no-brace-sequence-padding-step.md) | SC3009 (brace expansion, under `-s sh` only) | Under `-s sh` shellcheck reports every brace expansion, a plain `{1..3}` and `{a,b}` too, as undefined in POSIX sh. Under `-s bash` it reports nothing, so a Bash older than 4.0 gets no finding for the padded and stepped forms. The rule reports only those two forms, for a Bash version you name. |
|
|
82
|
+
| [`sh/compat/no-case-fallthrough`](./rules/compat/no-case-fallthrough.md) | SC2127 (an error, not an SC3xxx check) | Under `-s sh` shellcheck asks for a bash or ksh shebang; under `-s bash` it reports nothing. The rule reports `;&` and `;;&` for a Bash version you name. |
|
|
83
|
+
| [`sh/compat/no-epoch-variables`](./rules/compat/no-epoch-variables.md) | SC3028 (`$EPOCHSECONDS`, `$EPOCHREALTIME`, under `-s sh` only) | Under `-s bash` shellcheck reports nothing, so a Bash older than 5.0 gets no finding. The rule reports them for a Bash version you name. |
|
|
84
|
+
| [`sh/compat/no-append-all-redirect`](./rules/compat/no-append-all-redirect.md), [`no-parameter-transformation`](./rules/compat/no-parameter-transformation.md), [`no-printf-time-format`](./rules/compat/no-printf-time-format.md), [`no-nounset-empty-array`](./rules/compat/no-nounset-empty-array.md) | none | shellcheck reports nothing for `&>>`, `${v@op}`, `printf '%(…)T'` or an empty array under `set -u`, under `-s sh` or `-s bash` (it reports only the array syntax, SC3030 and SC3054, under `-s sh`). |
|
|
85
|
+
| [`sh/compat/no-lastpipe-semantics`](./rules/compat/no-lastpipe-semantics.md) | SC2030, SC2031 | shellcheck explains the subshell effect of lastpipe; the rule reports `shopt -s lastpipe` on a Bash that lacks it. |
|
|
86
|
+
| [`sh/shebang-matches-dialect`](./rules/shebang-matches-dialect.md) | SC3xxx | shellcheck judges the syntax against the shebang. This rule compares the shebang with your configured dialect, a different question. |
|
|
87
|
+
| [`sh/require-strict-mode`](./rules/require-strict-mode.md) | none directly (SC2164, an unchecked `cd`, is one consequence) | shellcheck has no check that requires strict mode. |
|