@eslint-sh/eslint-plugin 0.6.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -8
- package/dist/command-harvest/builtins/bash.d.ts +1 -1
- package/dist/command-harvest/builtins/bash.js +1 -1
- package/dist/command-harvest/builtins/bats.d.ts +2 -0
- package/dist/command-harvest/builtins/bats.js +3 -0
- package/dist/command-harvest/wrapper-table.d.ts +3 -19
- package/dist/command-harvest/wrapper-table.js +8 -38
- package/dist/configs/bash-3-2.d.ts +7 -6
- package/dist/configs/bash-3-2.js +5 -3
- package/dist/configs/posix-portable.d.ts +1 -1
- package/dist/configs/posix-portable.js +1 -0
- package/dist/declaration-conventions/read-declared-commands.d.ts +2 -2
- package/dist/declaration-conventions/read-declared-commands.js +2 -2
- package/dist/eslint-plugin-syntax.d.ts +4 -0
- package/dist/eslint-plugin.d.ts +86 -40
- package/dist/index.d.ts +5 -2
- package/dist/index.js +4 -2
- package/dist/language/shell-language-options.d.ts +18 -15
- package/dist/language/shell-language-options.js +33 -26
- package/dist/language/shell-language.d.ts +10 -9
- package/dist/language/shell-language.js +18 -35
- package/dist/language/shell-source-code.d.ts +17 -9
- package/dist/language/shell-source-code.js +53 -12
- package/dist/rules/bash-variable-semantics.d.ts +22 -0
- package/dist/rules/bash-variable-semantics.js +41 -0
- package/dist/rules/compat/create-compat-rule.js +1 -1
- package/dist/rules/compat/declaration-options.d.ts +2 -2
- package/dist/rules/compat/declaration-options.js +5 -2
- package/dist/rules/compat/no-append-all-redirect.js +1 -1
- package/dist/rules/compat/no-associative-arrays.js +2 -2
- package/dist/rules/compat/no-brace-sequence-padding-step.d.ts +15 -0
- package/dist/rules/compat/no-brace-sequence-padding-step.js +287 -0
- package/dist/rules/compat/no-case-attributes.js +3 -3
- package/dist/rules/compat/no-case-fallthrough.js +2 -2
- package/dist/rules/compat/no-case-modification.js +2 -2
- package/dist/rules/compat/no-coproc.js +1 -1
- package/dist/rules/compat/no-declare-g.js +3 -3
- package/dist/rules/compat/no-double-bracket.js +2 -2
- package/dist/rules/compat/no-epoch-variables.js +2 -2
- package/dist/rules/compat/no-fd-variable.js +1 -1
- package/dist/rules/compat/no-globstar.js +1 -1
- package/dist/rules/compat/no-lastpipe-semantics.js +2 -2
- package/dist/rules/compat/no-let.js +2 -1
- package/dist/rules/compat/no-mapfile.js +4 -2
- package/dist/rules/compat/no-nameref.js +3 -3
- package/dist/rules/compat/no-negative-array-index.js +39 -9
- package/dist/rules/compat/no-nounset-empty-array.js +15 -4
- package/dist/rules/compat/no-parameter-transformation.js +2 -2
- package/dist/rules/compat/no-pipe-all.js +1 -1
- package/dist/rules/compat/no-printf-time-format.js +4 -2
- package/dist/rules/compat/no-source.js +2 -1
- package/dist/rules/compat/no-test-v.js +4 -2
- package/dist/rules/compat/no-unicode-escape.js +2 -2
- package/dist/rules/compat/no-wait-n.js +4 -2
- package/dist/rules/compat/nounset-array-proof.d.ts +8 -0
- package/dist/rules/compat/nounset-array-proof.js +563 -0
- package/dist/rules/compat/posix-absent-command.d.ts +7 -8
- package/dist/rules/compat/posix-absent-command.js +17 -15
- package/dist/rules/compat/shopt-options.js +3 -1
- package/dist/rules/compat/version-gates.d.ts +10 -10
- package/dist/rules/compat/version-gates.js +21 -13
- package/dist/rules/create-wrapper-rule.js +12 -7
- package/dist/rules/index.js +2 -0
- package/dist/rules/no-dynamic-invocation.d.ts +0 -8
- package/dist/rules/no-dynamic-invocation.js +7 -15
- package/dist/rules/no-eval.js +3 -1
- package/dist/rules/no-masked-exit-status.js +15 -8
- package/dist/rules/no-secret-env-reads.d.ts +4 -3
- package/dist/rules/no-secret-env-reads.js +37 -14
- package/dist/rules/no-secret-flags.d.ts +2 -1
- package/dist/rules/no-secret-flags.js +7 -8
- package/dist/rules/no-unchecked-cd.js +3 -1
- package/dist/rules/no-undeclared-commands.d.ts +1 -15
- package/dist/rules/no-undeclared-commands.js +3 -15
- package/dist/rules/no-unused-declarations.d.ts +1 -18
- package/dist/rules/no-unused-declarations.js +9 -34
- package/dist/rules/prefer-local.js +4 -3
- package/dist/rules/quote-expansions.js +3 -28
- package/dist/rules/require-network-wrapper.d.ts +2 -17
- package/dist/rules/require-network-wrapper.js +33 -4
- package/dist/rules/require-tty-guard.js +30 -1
- package/dist/rules/resolve-declaration-convention.d.ts +5 -6
- package/dist/rules/resolve-declaration-convention.js +5 -6
- package/dist/rules/resolve-network-wrapper.d.ts +19 -0
- package/dist/rules/resolve-network-wrapper.js +57 -26
- package/dist/rules/resolve-transparent-wrappers.d.ts +43 -0
- package/dist/rules/resolve-transparent-wrappers.js +98 -0
- package/dist/rules/set-options.d.ts +2 -1
- package/dist/rules/set-options.js +17 -5
- package/dist/rules/shebang-interpreter.js +4 -2
- package/dist/rules/transparent-wrappers.d.ts +4 -3
- package/dist/rules/transparent-wrappers.js +5 -4
- package/dist/rules/user-pattern.d.ts +5 -3
- package/dist/rules/user-pattern.js +6 -4
- package/dist/syntax/index.d.ts +4 -0
- package/dist/syntax/index.js +4 -0
- package/docs/compat.md +90 -34
- package/docs/custom-rule-tutorial.md +9 -3
- package/docs/examples/no-listed-commands-rule.ts +2 -2
- package/docs/rules/README.md +38 -5
- package/docs/rules/_template.md +2 -2
- package/docs/rules/compat/no-append-all-redirect.md +12 -12
- package/docs/rules/compat/no-associative-arrays.md +14 -14
- package/docs/rules/compat/no-brace-sequence-padding-step.md +90 -0
- package/docs/rules/compat/no-case-attributes.md +13 -13
- package/docs/rules/compat/no-case-fallthrough.md +12 -12
- package/docs/rules/compat/no-case-modification.md +11 -11
- package/docs/rules/compat/no-coproc.md +13 -13
- package/docs/rules/compat/no-declare-g.md +13 -13
- package/docs/rules/compat/no-double-bracket.md +2 -2
- package/docs/rules/compat/no-epoch-variables.md +13 -13
- package/docs/rules/compat/no-fd-variable.md +12 -12
- package/docs/rules/compat/no-globstar.md +13 -13
- package/docs/rules/compat/no-lastpipe-semantics.md +13 -13
- package/docs/rules/compat/no-let.md +2 -2
- package/docs/rules/compat/no-mapfile.md +16 -16
- package/docs/rules/compat/no-nameref.md +13 -13
- package/docs/rules/compat/no-negative-array-index.md +14 -14
- package/docs/rules/compat/no-nounset-empty-array.md +72 -17
- package/docs/rules/compat/no-parameter-transformation.md +19 -19
- package/docs/rules/compat/no-pipe-all.md +13 -13
- package/docs/rules/compat/no-printf-time-format.md +13 -13
- package/docs/rules/compat/no-source.md +2 -2
- package/docs/rules/compat/no-test-v.md +12 -12
- package/docs/rules/compat/no-unicode-escape.md +13 -13
- package/docs/rules/compat/no-wait-n.md +13 -13
- package/docs/rules/no-dynamic-invocation.md +17 -12
- package/docs/rules/no-eval.md +6 -1
- package/docs/rules/no-masked-exit-status.md +4 -0
- package/docs/rules/no-secret-env-reads.md +34 -3
- package/docs/rules/no-secret-flags.md +19 -2
- package/docs/rules/no-unchecked-cd.md +9 -2
- package/docs/rules/no-undeclared-commands.md +16 -15
- package/docs/rules/no-unused-declarations.md +17 -20
- package/docs/rules/prefer-local.md +20 -2
- package/docs/rules/quote-expansions.md +8 -5
- package/docs/rules/require-network-wrapper.md +22 -17
- package/docs/rules/require-shebang.md +10 -4
- package/docs/rules/require-strict-mode.md +6 -0
- package/docs/rules/require-tty-guard.md +4 -1
- package/docs/shellcheck.md +10 -5
- package/docs/transparent-wrappers.md +97 -0
- package/docs/untrusted-code.md +440 -0
- package/docs/wrapper-rules.md +9 -0
- package/package.json +2 -2
- package/dist/language/end-of-input-marker.d.ts +0 -29
- package/dist/language/end-of-input-marker.js +0 -88
|
@@ -88,9 +88,11 @@ This rule takes one options object:
|
|
|
88
88
|
- `allow` (`string[]`, default `[]`): variable names that may be assigned in a function without
|
|
89
89
|
`local`.
|
|
90
90
|
- `allowPattern` (`string`, default `"^[A-Z][A-Z0-9_]*$"`): a regular expression, compiled with the
|
|
91
|
-
`u` flag, that exempts every name it matches. A value that is not a valid regular expression is a
|
|
91
|
+
`u` flag (Unicode, case-sensitive), that exempts every name it matches. A value that is not a valid regular expression is a
|
|
92
92
|
configuration error. Setting it replaces the default, so `allowPattern: '^g_'` stops exempting
|
|
93
|
-
UPPER_CASE names.
|
|
93
|
+
UPPER_CASE names. For example, `'^g_'` exempts `g_cache` but not `G_cache`.
|
|
94
|
+
See [regular-expression options](./README.md#regular-expression-options) for the shared
|
|
95
|
+
compilation and configuration-error contract.
|
|
94
96
|
|
|
95
97
|
```js
|
|
96
98
|
// eslint.config.js
|
|
@@ -104,6 +106,22 @@ This rule takes one options object:
|
|
|
104
106
|
}
|
|
105
107
|
```
|
|
106
108
|
|
|
109
|
+
Regex syntax errors fail when this rule first runs on a file, naming the rule, option and
|
|
110
|
+
pattern. `--print-config` will not catch any regex syntax errors. See the
|
|
111
|
+
[shared compilation contract](./README.md#regular-expression-options).
|
|
112
|
+
|
|
113
|
+
### Matching examples
|
|
114
|
+
|
|
115
|
+
These examples use only this rule; a finding means the assignment needs `local`.
|
|
116
|
+
Option values below are JSON.
|
|
117
|
+
|
|
118
|
+
| Option value | Shell input | Findings |
|
|
119
|
+
| --- | --- | --- |
|
|
120
|
+
| `{"allowPattern":"^g_"}` | `f(){ g_cache=1; }` | 0 |
|
|
121
|
+
| `{"allowPattern":"^g_"}` | `f(){ G_cache=1; }` | 1 |
|
|
122
|
+
| `{"allowPattern":"^\\p{Lu}+$"}` | `f(){ CACHE=1; }` | 0 |
|
|
123
|
+
| `{"allowPattern":"^\\p{Lu}+$"}` | `f(){ cache=1; }` | 1 |
|
|
124
|
+
|
|
107
125
|
### Patterns run against script-controlled text
|
|
108
126
|
|
|
109
127
|
`allowPattern` is tested against every variable name the linted script assigns in a function, and
|
|
@@ -137,7 +137,8 @@ exec "$@" # "$@" keeps every argument intact
|
|
|
137
137
|
|
|
138
138
|
## When not to use it
|
|
139
139
|
|
|
140
|
-
Disable the rule if you run shellcheck
|
|
140
|
+
Disable the rule if you run shellcheck and want to avoid double reports. This also drops
|
|
141
|
+
its checks in command-name position and `for … in` lists, which SC2086 does not report
|
|
141
142
|
(see below). For one line that relies on splitting on purpose, such as passing `$CFLAGS` to a
|
|
142
143
|
compiler, disable it on that line (`# eslint-disable-next-line sh/quote-expansions`) or rewrite
|
|
143
144
|
the value as an array (`"${cflags[@]}"`).
|
|
@@ -147,11 +148,13 @@ the value as an array (`"${cflags[@]}"`).
|
|
|
147
148
|
eslint-sh is a convention and contract linter: a policy layer, where shellcheck is the gotcha
|
|
148
149
|
layer. Run shellcheck beside it.
|
|
149
150
|
|
|
150
|
-
This rule is one of eslint-sh's
|
|
151
|
+
This rule is one of eslint-sh's
|
|
152
|
+
[deliberate overlaps with shellcheck](../shellcheck.md#deliberate-overlaps). It covers the same
|
|
151
153
|
problem as SC2086 (double quote to prevent globbing and word splitting), and SC2068 for unquoted
|
|
152
154
|
`$@`. It exists for teams that run eslint-sh without shellcheck, so that this common mistake is
|
|
153
|
-
still caught. **
|
|
154
|
-
|
|
155
|
-
|
|
155
|
+
still caught. **Disabling this rule when you run shellcheck avoids double reports**, but also
|
|
156
|
+
drops checks in command-name position (`$cmd arg`) and `for … in` lists (`for x in $LIST`),
|
|
157
|
+
which SC2086 never reports. The rule does not try to match SC2086's heuristics: shellcheck skips
|
|
158
|
+
some expansions it can prove are safe, such as a variable it saw assigned a number, and this rule reports them
|
|
156
159
|
because it does no dataflow analysis. That is why `recommended` enables it at `warn` instead of
|
|
157
160
|
`error`.
|
|
@@ -62,7 +62,7 @@ It does not depend on `languageOptions`.
|
|
|
62
62
|
## Options
|
|
63
63
|
|
|
64
64
|
- `wrapper` (`string` or `WrapperSpec` object, no default): the wrapper network commands must be
|
|
65
|
-
run through. When it is unset, the rule uses `settings
|
|
65
|
+
run through. When it is unset, the rule uses `settings: { sh: { networkWrapper: 'with-retry' } }` (below); with
|
|
66
66
|
neither, the rule reports nothing. A string names a wrapper that takes no options
|
|
67
67
|
(or one of the default transparent wrappers, such as `timeout`, whose options are known). An
|
|
68
68
|
object declares the wrapper's options so the command after them is found:
|
|
@@ -85,31 +85,36 @@ It does not depend on `languageOptions`.
|
|
|
85
85
|
}
|
|
86
86
|
```
|
|
87
87
|
|
|
88
|
-
### Declaring the wrapper once
|
|
88
|
+
### Declaring the wrapper once
|
|
89
89
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
`wrapper` option is invisible to them, and an undeclared `curl` behind it goes unreported.
|
|
94
|
-
|
|
95
|
-
Set the wrapper in shared settings instead, and all three rules read it:
|
|
90
|
+
Set the network policy in shared settings so every wrapper-following rule sees the
|
|
91
|
+
same command chain, including the declaration rules, `sh/no-eval`, and
|
|
92
|
+
`sh/no-dynamic-invocation`:
|
|
96
93
|
|
|
97
94
|
```js
|
|
98
95
|
// eslint.config.js
|
|
99
96
|
{
|
|
100
97
|
settings: { sh: { declarationConvention: commentPragma, networkWrapper: 'with-retry' } },
|
|
101
|
-
rules: { 'sh/require-network-wrapper': 'error' },
|
|
98
|
+
rules: { 'sh/require-network-wrapper': 'error' },
|
|
102
99
|
}
|
|
103
100
|
```
|
|
104
101
|
|
|
105
|
-
`networkWrapper`
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
with
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
102
|
+
`networkWrapper` accepts a name or a JSON-expressible `WrapperSpec` object, like the
|
|
103
|
+
`wrapper` rule option. A custom spec for a default wrapper name takes effect in every
|
|
104
|
+
consumer. Absolute and relative utility wrapper paths match by basename:
|
|
105
|
+
`/opt/bin/with-retry curl x` and `./bin/with-retry curl x` both run `curl` through
|
|
106
|
+
`with-retry`, and an undeclared `curl` is reported by the declaration rule.
|
|
107
|
+
|
|
108
|
+
A `wrapper` option that disagrees with the shared network policy now throws a clear
|
|
109
|
+
configuration error. Equivalent values are accepted; there is no silent override.
|
|
110
|
+
A wrapper set only as a rule option remains local to this network policy because other
|
|
111
|
+
rules cannot read its options. Prefer the shared setting. Malformed settings also stop linting.
|
|
112
|
+
|
|
113
|
+
Other project wrappers belong in
|
|
114
|
+
`settings: { sh: { transparentWrappers: ['with-lock'] } }`. This factory-based rule
|
|
115
|
+
reads those facts too, so `with-lock curl x` cannot hide an unwrapped network call.
|
|
116
|
+
[Shared transparent wrappers](../transparent-wrappers.md) documents the common resolver,
|
|
117
|
+
conflicting specs, path matching, and migration from the removed per-rule options.
|
|
113
118
|
|
|
114
119
|
## Examples
|
|
115
120
|
|
|
@@ -24,6 +24,11 @@ This rule reads line 1 of the file and reports two problems:
|
|
|
24
24
|
`#!/usr/bin/env -S bash -e`. The rule skips `env` options, their arguments and
|
|
25
25
|
`NAME=value` assignments to find the command `env` runs.
|
|
26
26
|
|
|
27
|
+
The space-separated long form `#!/usr/bin/env --split-string bash -eu` is reported:
|
|
28
|
+
GNU env receives `--split-string bash -eu` as one option name and rejects it. GNU
|
|
29
|
+
`--split-string=bash -eu` is accepted here, but macOS env does not support the long form;
|
|
30
|
+
use `-S` across both systems.
|
|
31
|
+
|
|
27
32
|
A relative interpreter such as `#!bash` or `#!env bash` is reported, because the kernel
|
|
28
33
|
resolves it against the caller's working directory, not `PATH`.
|
|
29
34
|
|
|
@@ -150,8 +155,9 @@ eslint-sh is a convention and contract linter: a policy layer, where shellcheck
|
|
|
150
155
|
layer. Run shellcheck beside it.
|
|
151
156
|
|
|
152
157
|
shellcheck's SC2148 ("Tips depend on target shell and yours is unknown") fires when a file has
|
|
153
|
-
no shebang and
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
158
|
+
no shebang and it cannot otherwise infer the shell. A `.bash`, `.bats` or `.ksh` extension,
|
|
159
|
+
a `# shellcheck shell=bash` directive or a `--shell` flag supplies that information and
|
|
160
|
+
silences SC2148. This rule requires the script itself to name its interpreter, so it still
|
|
161
|
+
reports a missing shebang in those files. It also autofixes the missing line from your configured
|
|
162
|
+
dialect.
|
|
157
163
|
shellcheck's SC1008 ("This shebang was unrecognized") overlaps `unrecognizedInterpreter`.
|
|
@@ -142,6 +142,12 @@ This rule takes one options object:
|
|
|
142
142
|
`bash script.sh`, so a `set` line is the only prologue that applies however the script is
|
|
143
143
|
started.
|
|
144
144
|
|
|
145
|
+
Relative interpreters such as `#!env -S bash -eu` receive no credit. Neither does
|
|
146
|
+
`#!/usr/bin/env --split-string bash -eu`: GNU env rejects the space-separated long
|
|
147
|
+
option when invoked through a kernel shebang. GNU's equals form
|
|
148
|
+
`#!/usr/bin/env --split-string=bash -eu` receives credit; macOS env does not support that long
|
|
149
|
+
option. Use an absolute env path and `-S` for both systems.
|
|
150
|
+
|
|
145
151
|
```js
|
|
146
152
|
// eslint.config.js
|
|
147
153
|
{
|
|
@@ -82,7 +82,10 @@ if [ -t 0 ]; then read name; fi
|
|
|
82
82
|
A multi-line statement that starts its own line is wrapped on lines of its own. Anywhere else the
|
|
83
83
|
wrapper goes around it in place, because an earlier command on the line may have opened a
|
|
84
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
|
|
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 &`)
|
|
86
89
|
gets no suggestion, because wrapping it would change what is backgrounded. A statement whose exit
|
|
87
90
|
status steers control flow (an `if`/`while`/`until` condition, or an operand of `&&` or `||`) is
|
|
88
91
|
reported without a suggestion too: the wrapper would exit 0 when there is no terminal, which
|
package/docs/shellcheck.md
CHANGED
|
@@ -32,8 +32,8 @@ For a complete GitHub Actions job, exit codes, a pre-commit hook and editor setu
|
|
|
32
32
|
[`eslint-sh` README](https://github.com/mike-north/eslint-sh/blob/main/packages/eslint-sh/README.md#continuous-integration).
|
|
33
33
|
|
|
34
34
|
Give them the same target shell, since each reads its own configuration. shellcheck takes it
|
|
35
|
-
from the shebang
|
|
36
|
-
`languageOptions.dialect` and `languageOptions.
|
|
35
|
+
from the shebang, a `# shellcheck shell=…` directive, or a `.bash`, `.bats` or `.ksh` extension;
|
|
36
|
+
eslint-sh takes it from `languageOptions.dialect` and `languageOptions.minBashVersion`. The `bash-3.2` and
|
|
37
37
|
`posix-portable` presets set the latter for you, and
|
|
38
38
|
[`sh/shebang-matches-dialect`](./rules/shebang-matches-dialect.md) reports a
|
|
39
39
|
shebang that disagrees with the configured dialect, so the two tools do not end up advising for
|
|
@@ -68,19 +68,24 @@ reason given, and each rule's page has a "Relationship to shellcheck" section. T
|
|
|
68
68
|
|
|
69
69
|
| eslint-sh rule | Overlapping shellcheck code | Why the rule exists anyway |
|
|
70
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`;
|
|
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`; disabling it when you enforce SC2086 avoids double reports, but also drops checks in command-name position and `for … in` lists, which SC2086 never reports. |
|
|
72
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 |
|
|
74
|
-
| [`sh/require-shebang`](./rules/require-shebang.md) | SC2148, SC1008 |
|
|
73
|
+
| [`sh/no-unchecked-cd`](./rules/no-unchecked-cd.md) | SC2164 | Offers a suggestion and covers `pushd` and `popd` too. shellcheck suppresses SC2164 for shebang errexit flags; `ignoreWhenErrexit` reads only prologue `set` statements, so the rule still reports those unchecked directory changes. |
|
|
74
|
+
| [`sh/require-shebang`](./rules/require-shebang.md) | SC2148, SC1008 | Requires a recognized shebang even when shellcheck infers the shell from a `.bash`, `.bats` or `.ksh` extension, a directive or a command-line flag and emits no SC2148. |
|
|
75
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
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
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
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
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
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. |
|
|
81
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. |
|
|
82
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. |
|
|
83
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`). |
|
|
84
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. |
|
|
85
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. |
|
|
86
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. |
|
|
88
|
+
|
|
89
|
+
`sh/compat/no-nounset-empty-array` requires explicit opt-in for `bash-3.2`. Enable it when a
|
|
90
|
+
script runs with `set -u` on Bash below 4.4 and its arrays can really be empty. Local checks
|
|
91
|
+
cannot prove safety across every caller or helper contract. `posix-portable` still enables it.
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# Shared transparent wrappers
|
|
2
|
+
|
|
3
|
+
A transparent wrapper runs a command named in its arguments. Declare that project fact
|
|
4
|
+
once so every wrapper-following rule sees the same command chain:
|
|
5
|
+
|
|
6
|
+
```js
|
|
7
|
+
{
|
|
8
|
+
settings: {
|
|
9
|
+
sh: {
|
|
10
|
+
transparentWrappers: ['with-retry', { names: ['audit-run'], argFlags: ['--ticket'] }],
|
|
11
|
+
},
|
|
12
|
+
},
|
|
13
|
+
}
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Entries are non-empty names or JSON-expressible `WrapperSpec` objects. A name preserves
|
|
17
|
+
the known option shape of a default wrapper such as `timeout`; an unfamiliar name
|
|
18
|
+
takes no flags. A spec can declare `names`, `noArgFlags`, `argFlags`,
|
|
19
|
+
`unresolvableFlags`, `stopsChainFlags`, `positionalOperandsBeforeCommand`,
|
|
20
|
+
`assignmentOperands` (`shell-identifier`, `env`, or `sudo`), and `shellBuiltin`.
|
|
21
|
+
`names` is required and non-empty. Regex fields and functions are not accepted.
|
|
22
|
+
An unknown flag stops analysis rather than guessing which operand is the command.
|
|
23
|
+
|
|
24
|
+
Project specs precede each rule's built-in table, including when a project spec names a
|
|
25
|
+
default wrapper. Replaced names are removed from lower-priority entries while their
|
|
26
|
+
unrelated aliases remain available; a builtin override cannot regain utility path semantics.
|
|
27
|
+
Duplicate project names and malformed entries are configuration errors.
|
|
28
|
+
The shared network policy, for example
|
|
29
|
+
`settings: { sh: { networkWrapper: 'with-retry' } }`, also supplies a transparent-wrapper
|
|
30
|
+
fact. Overlapping names must have the same option shape; extra transparent aliases do
|
|
31
|
+
not become approved network wrappers. Different option shapes for shared names are
|
|
32
|
+
configuration errors. A network rule's `wrapper` option must agree with both the option
|
|
33
|
+
shape and the full approved name set of a configured shared network policy. It cannot
|
|
34
|
+
override that policy silently. Omitted flag lists and empty flag lists are equivalent;
|
|
35
|
+
object key order and flag-list order do not matter.
|
|
36
|
+
|
|
37
|
+
Both comparisons use the published matcher's effective grammar: omitted flag lists
|
|
38
|
+
are empty, `shellBuiltin: false` means utility matching, and
|
|
39
|
+
`positionalOperandsBeforeCommand: 0` consumes no positional operand. Assignment
|
|
40
|
+
handling uses the selected grammar; the internal legacy alias means
|
|
41
|
+
`shell-identifier` only when no explicit grammar was supplied. Expanded built-in
|
|
42
|
+
flag patterns compare by source and flags, including presence versus absence.
|
|
43
|
+
This is structural grammar comparison, not arbitrary regex-language equivalence.
|
|
44
|
+
Explicit specs remain complete replacements: a patternless `nice` spec does not
|
|
45
|
+
inherit the built-in numeric-priority pattern, and conflicts with the string default
|
|
46
|
+
when both are configured for the same name. Internal regex fields remain forbidden
|
|
47
|
+
in settings; the accepted JSON fields above are unchanged.
|
|
48
|
+
|
|
49
|
+
Utility wrappers invoked by absolute or relative path match by basename:
|
|
50
|
+
`/opt/bin/with-retry curl x` and `./bin/with-retry curl x` both run `curl`.
|
|
51
|
+
An explicit path spec takes precedence over a basename fallback in either list order.
|
|
52
|
+
Path aliases are also built for a rule's built-in table when no shared setting is present.
|
|
53
|
+
The written wrapper path remains a dependency in declaration harvesting. Specs with
|
|
54
|
+
`shellBuiltin: true` require exact names: `/tmp/command` cannot acquire the shell
|
|
55
|
+
builtin's semantics. Unused-declaration completeness uses the resolved table too,
|
|
56
|
+
without restoring overridden built-in path semantics. These are syntactic declarations; no wrapper or corpus script is
|
|
57
|
+
executed or inspected to infer its behavior.
|
|
58
|
+
|
|
59
|
+
Every built-in that follows wrappers reads these facts: `no-undeclared-commands`,
|
|
60
|
+
`no-unused-declarations`, `no-dynamic-invocation`, `no-eval`, `no-unchecked-cd`,
|
|
61
|
+
`no-secret-env-reads`, `no-masked-exit-status`, and the command-based compatibility
|
|
62
|
+
rules. `require-network-wrapper` and rules made with `createWrapperRule` do too.
|
|
63
|
+
Rules retain their own built-in scopes and reporting policies. `quote-expansions`
|
|
64
|
+
checks expansion positions directly and does not follow wrappers.
|
|
65
|
+
|
|
66
|
+
Allowed-secret-sink exemptions apply only to argument words after the final resolved
|
|
67
|
+
command. In `with-retry --log "$API_TOKEN" sink`, the secret is owned by the wrapper
|
|
68
|
+
and is checked normally, even if `sink` is approved. In
|
|
69
|
+
`with-retry --log audit.log sink "$API_TOKEN"`, it is an actual sink argument and can
|
|
70
|
+
be exempt. The same boundary applies to default wrappers such as `env -u`.
|
|
71
|
+
|
|
72
|
+
## Migration
|
|
73
|
+
|
|
74
|
+
Remove the `transparentWrappers` option from `no-undeclared-commands` and
|
|
75
|
+
`no-unused-declarations`, and remove `allowWrappers` from `no-dynamic-invocation`.
|
|
76
|
+
Move the list to `settings: { sh: { transparentWrappers: ['with-retry'] } }`.
|
|
77
|
+
Removed rule options fail ESLint schema validation; no per-rule override exists.
|
|
78
|
+
|
|
79
|
+
`allowWrappers` added checks: it made the rule follow the wrapper and report a
|
|
80
|
+
dynamic operand such as `with-retry "$cmd"`. Moving its list preserves that check
|
|
81
|
+
and makes every other wrapper-following rule see it too. To suppress a dynamic
|
|
82
|
+
dispatcher, use `allowInFunctions` or a justified ESLint disable comment.
|
|
83
|
+
|
|
84
|
+
Findings can change on unchanged scripts once project wrappers are configured:
|
|
85
|
+
wrapped `eval`, unchecked `cd`, dynamic invocations, compatibility features, and
|
|
86
|
+
unwrapped network commands become visible. An allowed secret sink behind a project
|
|
87
|
+
wrapper can become recognized, and incomplete usage behind a wrapper can prevent an
|
|
88
|
+
unsafe unused-declaration fix.
|
|
89
|
+
|
|
90
|
+
## Rule packs
|
|
91
|
+
|
|
92
|
+
Call `resolveTransparentWrappers(context, builtIns)` from a rule's `create()` and
|
|
93
|
+
pass its result as the analysis `transparentWrappers` table. Omit `builtIns` to use
|
|
94
|
+
the default table. The resolver composes shared facts with that scope, validates
|
|
95
|
+
configuration, and adds path spellings from the linted file without mutating inputs.
|
|
96
|
+
Both `eslint-sh` and `@eslint-sh/eslint-plugin` export it and
|
|
97
|
+
`TransparentWrappersBindingContext`.
|