@eslint-sh/eslint-plugin 0.7.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -8
- package/dist/command-harvest/builtins/bash.d.ts +1 -1
- package/dist/command-harvest/builtins/bash.js +1 -1
- package/dist/command-harvest/builtins/bats.d.ts +2 -0
- package/dist/command-harvest/builtins/bats.js +3 -0
- package/dist/command-harvest/wrapper-table.d.ts +3 -19
- package/dist/command-harvest/wrapper-table.js +8 -38
- package/dist/configs/bash-3-2.d.ts +7 -6
- package/dist/configs/bash-3-2.js +4 -3
- package/dist/configs/posix-portable.d.ts +1 -1
- package/dist/declaration-conventions/read-declared-commands.d.ts +2 -2
- package/dist/declaration-conventions/read-declared-commands.js +2 -2
- package/dist/eslint-plugin-syntax.d.ts +4 -0
- package/dist/eslint-plugin.d.ts +86 -40
- package/dist/index.d.ts +5 -2
- package/dist/index.js +4 -2
- package/dist/language/shell-language-options.d.ts +18 -15
- package/dist/language/shell-language-options.js +33 -26
- package/dist/language/shell-language.d.ts +10 -9
- package/dist/language/shell-language.js +18 -35
- package/dist/language/shell-source-code.d.ts +17 -9
- package/dist/language/shell-source-code.js +53 -12
- package/dist/rules/bash-variable-semantics.d.ts +22 -0
- package/dist/rules/bash-variable-semantics.js +41 -0
- package/dist/rules/compat/create-compat-rule.js +1 -1
- package/dist/rules/compat/declaration-options.d.ts +2 -2
- package/dist/rules/compat/declaration-options.js +5 -2
- package/dist/rules/compat/no-append-all-redirect.js +1 -1
- package/dist/rules/compat/no-associative-arrays.js +2 -2
- package/dist/rules/compat/no-brace-sequence-padding-step.js +4 -4
- package/dist/rules/compat/no-case-attributes.js +3 -3
- package/dist/rules/compat/no-case-fallthrough.js +2 -2
- package/dist/rules/compat/no-case-modification.js +2 -2
- package/dist/rules/compat/no-coproc.js +1 -1
- package/dist/rules/compat/no-declare-g.js +3 -3
- package/dist/rules/compat/no-double-bracket.js +2 -2
- package/dist/rules/compat/no-epoch-variables.js +2 -2
- package/dist/rules/compat/no-fd-variable.js +1 -1
- package/dist/rules/compat/no-globstar.js +1 -1
- package/dist/rules/compat/no-lastpipe-semantics.js +2 -2
- package/dist/rules/compat/no-let.js +2 -1
- package/dist/rules/compat/no-mapfile.js +4 -2
- package/dist/rules/compat/no-nameref.js +3 -3
- package/dist/rules/compat/no-negative-array-index.js +39 -9
- package/dist/rules/compat/no-nounset-empty-array.js +15 -4
- package/dist/rules/compat/no-parameter-transformation.js +2 -2
- package/dist/rules/compat/no-pipe-all.js +1 -1
- package/dist/rules/compat/no-printf-time-format.js +4 -2
- package/dist/rules/compat/no-source.js +2 -1
- package/dist/rules/compat/no-test-v.js +4 -2
- package/dist/rules/compat/no-unicode-escape.js +2 -2
- package/dist/rules/compat/no-wait-n.js +4 -2
- package/dist/rules/compat/nounset-array-proof.d.ts +8 -0
- package/dist/rules/compat/nounset-array-proof.js +563 -0
- package/dist/rules/compat/posix-absent-command.d.ts +7 -8
- package/dist/rules/compat/posix-absent-command.js +17 -15
- package/dist/rules/compat/shopt-options.js +3 -1
- package/dist/rules/compat/version-gates.d.ts +9 -9
- package/dist/rules/compat/version-gates.js +13 -13
- package/dist/rules/create-wrapper-rule.js +12 -7
- package/dist/rules/no-dynamic-invocation.d.ts +0 -8
- package/dist/rules/no-dynamic-invocation.js +7 -15
- package/dist/rules/no-eval.js +3 -1
- package/dist/rules/no-masked-exit-status.js +15 -8
- package/dist/rules/no-secret-env-reads.d.ts +4 -3
- package/dist/rules/no-secret-env-reads.js +37 -14
- package/dist/rules/no-secret-flags.d.ts +2 -1
- package/dist/rules/no-secret-flags.js +7 -8
- package/dist/rules/no-unchecked-cd.js +3 -1
- package/dist/rules/no-undeclared-commands.d.ts +1 -15
- package/dist/rules/no-undeclared-commands.js +3 -15
- package/dist/rules/no-unused-declarations.d.ts +1 -18
- package/dist/rules/no-unused-declarations.js +9 -34
- package/dist/rules/prefer-local.js +4 -3
- package/dist/rules/quote-expansions.js +3 -28
- package/dist/rules/require-network-wrapper.d.ts +2 -17
- package/dist/rules/require-network-wrapper.js +33 -4
- package/dist/rules/resolve-declaration-convention.d.ts +5 -6
- package/dist/rules/resolve-declaration-convention.js +5 -6
- package/dist/rules/resolve-network-wrapper.d.ts +19 -0
- package/dist/rules/resolve-network-wrapper.js +57 -26
- package/dist/rules/resolve-transparent-wrappers.d.ts +43 -0
- package/dist/rules/resolve-transparent-wrappers.js +98 -0
- package/dist/rules/set-options.d.ts +2 -1
- package/dist/rules/set-options.js +17 -5
- package/dist/rules/shebang-interpreter.js +4 -2
- package/dist/rules/transparent-wrappers.d.ts +4 -3
- package/dist/rules/transparent-wrappers.js +5 -4
- package/dist/rules/user-pattern.d.ts +5 -3
- package/dist/rules/user-pattern.js +6 -4
- package/dist/syntax/index.d.ts +4 -0
- package/dist/syntax/index.js +4 -0
- package/docs/compat.md +76 -27
- package/docs/custom-rule-tutorial.md +9 -3
- package/docs/examples/no-listed-commands-rule.ts +2 -2
- package/docs/rules/README.md +36 -4
- package/docs/rules/_template.md +2 -2
- package/docs/rules/compat/no-append-all-redirect.md +12 -12
- package/docs/rules/compat/no-associative-arrays.md +14 -14
- package/docs/rules/compat/no-brace-sequence-padding-step.md +12 -12
- package/docs/rules/compat/no-case-attributes.md +13 -13
- package/docs/rules/compat/no-case-fallthrough.md +12 -12
- package/docs/rules/compat/no-case-modification.md +11 -11
- package/docs/rules/compat/no-coproc.md +13 -13
- package/docs/rules/compat/no-declare-g.md +13 -13
- package/docs/rules/compat/no-double-bracket.md +2 -2
- package/docs/rules/compat/no-epoch-variables.md +13 -13
- package/docs/rules/compat/no-fd-variable.md +12 -12
- package/docs/rules/compat/no-globstar.md +13 -13
- package/docs/rules/compat/no-lastpipe-semantics.md +13 -13
- package/docs/rules/compat/no-let.md +2 -2
- package/docs/rules/compat/no-mapfile.md +16 -16
- package/docs/rules/compat/no-nameref.md +13 -13
- package/docs/rules/compat/no-negative-array-index.md +14 -14
- package/docs/rules/compat/no-nounset-empty-array.md +72 -17
- package/docs/rules/compat/no-parameter-transformation.md +19 -19
- package/docs/rules/compat/no-pipe-all.md +13 -13
- package/docs/rules/compat/no-printf-time-format.md +13 -13
- package/docs/rules/compat/no-source.md +2 -2
- package/docs/rules/compat/no-test-v.md +12 -12
- package/docs/rules/compat/no-unicode-escape.md +13 -13
- package/docs/rules/compat/no-wait-n.md +13 -13
- package/docs/rules/no-dynamic-invocation.md +17 -12
- package/docs/rules/no-eval.md +6 -1
- package/docs/rules/no-masked-exit-status.md +4 -0
- package/docs/rules/no-secret-env-reads.md +34 -3
- package/docs/rules/no-secret-flags.md +19 -2
- package/docs/rules/no-unchecked-cd.md +9 -2
- package/docs/rules/no-undeclared-commands.md +16 -15
- package/docs/rules/no-unused-declarations.md +17 -20
- package/docs/rules/prefer-local.md +20 -2
- package/docs/rules/quote-expansions.md +8 -5
- package/docs/rules/require-network-wrapper.md +22 -17
- package/docs/rules/require-shebang.md +10 -4
- package/docs/rules/require-strict-mode.md +6 -0
- package/docs/shellcheck.md +9 -5
- package/docs/transparent-wrappers.md +97 -0
- package/docs/wrapper-rules.md +9 -0
- package/package.json +2 -2
- package/dist/language/end-of-input-marker.d.ts +0 -29
- package/dist/language/end-of-input-marker.js +0 -88
|
@@ -6,6 +6,10 @@ Disallow reading secret-shaped environment variables, such as `$GITHUB_TOKEN`, o
|
|
|
6
6
|
| ---------------- | ----------- | --------- |
|
|
7
7
|
| `agent-authored` (error) | not fixable | `sh/sh` |
|
|
8
8
|
|
|
9
|
+
Project wrapper facts are shared through
|
|
10
|
+
`settings: { sh: { transparentWrappers: ['with-retry'] } }`. This rule follows
|
|
11
|
+
those wrappers and the shared network wrapper; see [shared transparent wrappers](../transparent-wrappers.md).
|
|
12
|
+
|
|
9
13
|
## Rule details
|
|
10
14
|
|
|
11
15
|
Once a secret is expanded into a command's arguments, every user on the machine can read it
|
|
@@ -34,6 +38,13 @@ Four things are not reported:
|
|
|
34
38
|
command is resolved through transparent wrappers (`sudo vault …` is `vault`), and a command run
|
|
35
39
|
by path matches by basename.
|
|
36
40
|
|
|
41
|
+
Only arguments actually passed after the final resolved sink command receive this
|
|
42
|
+
exemption. Wrapper-owned options and operands are checked normally. With an approved
|
|
43
|
+
`sink` and `settings: { sh: { transparentWrappers: [{ names: ['with-retry'], argFlags: ['--log'] }] } }`,
|
|
44
|
+
`with-retry --log "$API_TOKEN" sink` reports, while
|
|
45
|
+
`with-retry --log audit.log sink "$API_TOKEN"` stays clean. Likewise, the secret in
|
|
46
|
+
`env -u "$API_TOKEN" sink` is owned by `env`, so approving `sink` does not exempt it.
|
|
47
|
+
|
|
37
48
|
Matching is a name pattern, not secret detection.
|
|
38
49
|
|
|
39
50
|
### Expected false positives
|
|
@@ -59,12 +70,16 @@ The rule matches parameter expansions only. These reads are not reported:
|
|
|
59
70
|
## Options
|
|
60
71
|
|
|
61
72
|
- `patterns` (`string[]`, at least one entry). Regular-expression sources tested against each
|
|
62
|
-
variable's name. They **replace** the default list. Sources
|
|
63
|
-
|
|
73
|
+
variable's name. They **replace** the default list. Sources use `u` (Unicode, case-sensitive):
|
|
74
|
+
`patterns: ['token']` does not catch `$GITHUB_TOKEN`, while `patterns: ['TOKEN']` does.
|
|
75
|
+
Lowercase names such as `$sort_key` do not match the uppercase default patterns.
|
|
76
|
+
See [regular-expression options](./README.md#regular-expression-options) for compilation and
|
|
77
|
+
configuration errors, which name the rule, option index and source.
|
|
64
78
|
|
|
65
79
|
Default: `["(_TOKEN|_SECRET|_PASSWORD|_KEY)$"]`.
|
|
66
80
|
|
|
67
|
-
- `allowedSinks` (`string[]`). Command names whose arguments may read a secret-shaped variable.
|
|
81
|
+
- `allowedSinks` (`string[]`). Command names whose own arguments may read a secret-shaped variable. Wrapper-owned
|
|
82
|
+
operands are not exempt.
|
|
68
83
|
|
|
69
84
|
Default: `[]`.
|
|
70
85
|
|
|
@@ -80,6 +95,22 @@ The rule matches parameter expansions only. These reads are not reported:
|
|
|
80
95
|
}
|
|
81
96
|
```
|
|
82
97
|
|
|
98
|
+
Regex syntax errors fail when this rule first runs on a file, naming the rule, option and
|
|
99
|
+
pattern. `--print-config` will not catch any regex syntax errors. See the
|
|
100
|
+
[shared compilation contract](./README.md#regular-expression-options).
|
|
101
|
+
|
|
102
|
+
### Matching examples
|
|
103
|
+
|
|
104
|
+
These examples use only this rule; `patterns` replaces the defaults. Option values below are JSON.
|
|
105
|
+
|
|
106
|
+
| Option value | Shell input | Findings |
|
|
107
|
+
| --- | --- | --- |
|
|
108
|
+
| `{"patterns":["token"]}` | `echo "$GITHUB_TOKEN"` | 0 |
|
|
109
|
+
| `{"patterns":["TOKEN"]}` | `echo "$GITHUB_TOKEN"` | 1 |
|
|
110
|
+
| `{}` | `echo "$sort_key"` | 0 |
|
|
111
|
+
| `{"patterns":["^\\p{Lu}+$"]}` | `echo "$TOKEN"` | 1 |
|
|
112
|
+
| `{"patterns":["^\\p{Lu}+$"]}` | `echo "$token"` | 0 |
|
|
113
|
+
|
|
83
114
|
### Patterns run against script-controlled text
|
|
84
115
|
|
|
85
116
|
`patterns` is tested against every variable name the linted script expands, and the script's author
|
|
@@ -60,8 +60,10 @@ positive with a disable comment that says why, or narrow the `patterns` option f
|
|
|
60
60
|
|
|
61
61
|
- `patterns` (`string[]`, at least one entry). Regular-expression sources tested against each flag's
|
|
62
62
|
name, without `=` or the value. They **replace** the default list. Each source is compiled
|
|
63
|
-
case-
|
|
64
|
-
|
|
63
|
+
with `iu` (Unicode, case-insensitive), like the default. For example, `patterns: ['token']`
|
|
64
|
+
catches `cli --TOKEN=abc`; capitals in a custom pattern also match lowercase flag names.
|
|
65
|
+
See [regular-expression options](./README.md#regular-expression-options) for compilation and
|
|
66
|
+
configuration errors, which name the rule, option index and source.
|
|
65
67
|
|
|
66
68
|
Default: `["^(?!.*-(?:stdin|file)$)--?(?:access-|auth-?)?(?:password|passwd|token|secret|api-?key|auth(?:key|orization)?)(?![a-z])"]`.
|
|
67
69
|
|
|
@@ -78,6 +80,21 @@ positive with a disable comment that says why, or narrow the `patterns` option f
|
|
|
78
80
|
}
|
|
79
81
|
```
|
|
80
82
|
|
|
83
|
+
Regex syntax errors fail when this rule first runs on a file, naming the rule, option and
|
|
84
|
+
pattern. `--print-config` will not catch any regex syntax errors. See the
|
|
85
|
+
[shared compilation contract](./README.md#regular-expression-options).
|
|
86
|
+
|
|
87
|
+
### Matching examples
|
|
88
|
+
|
|
89
|
+
These examples use only this rule; `patterns` replaces the defaults. Option values below are JSON.
|
|
90
|
+
|
|
91
|
+
| Option value | Shell input | Findings |
|
|
92
|
+
| --- | --- | --- |
|
|
93
|
+
| `{"patterns":["token"]}` | `cli --TOKEN=abc` | 1 |
|
|
94
|
+
| `{"patterns":["TOKEN"]}` | `cli --token=abc` | 1 |
|
|
95
|
+
| `{"patterns":["token"]}` | `cli --other=abc` | 0 |
|
|
96
|
+
| `{"patterns":["^--\\p{Lu}+$"]}` | `cli --TOKEN=abc` | 1 |
|
|
97
|
+
|
|
81
98
|
### Patterns run against script-controlled text
|
|
82
99
|
|
|
83
100
|
`patterns` is tested against every flag name the linted script writes, and the script's author
|
|
@@ -6,6 +6,10 @@ Require the failure of `cd`, `pushd` and `popd` to be checked, so a script never
|
|
|
6
6
|
| ------------------------------- | ---------------------------- | --------- |
|
|
7
7
|
| `recommended`, `agent-authored` | suggestions (append `\|\| exit`) | `sh/sh` |
|
|
8
8
|
|
|
9
|
+
Project wrapper facts are shared through
|
|
10
|
+
`settings: { sh: { transparentWrappers: ['with-retry'] } }`. This rule follows
|
|
11
|
+
those wrappers and the shared network wrapper; see [shared transparent wrappers](../transparent-wrappers.md).
|
|
12
|
+
|
|
9
13
|
## Rule details
|
|
10
14
|
|
|
11
15
|
When `cd` fails, the script keeps running in the directory it was already in, and every relative
|
|
@@ -31,7 +35,8 @@ read as `cd`.
|
|
|
31
35
|
by itself, so the rule is quiet there. "Prologue" is read exactly as
|
|
32
36
|
[`require-strict-mode`](./require-strict-mode.md) reads it: the first five top-level statements,
|
|
33
37
|
tracked in order, so a later `set +e` undoes an earlier `set -e`, and a `set -e` outside that
|
|
34
|
-
window does not count.
|
|
38
|
+
window does not count. Only `set` statements count; shebang flags do not. Set
|
|
39
|
+
`ignoreWhenErrexit` to `false` to report anyway.
|
|
35
40
|
|
|
36
41
|
**Files run by bats.** With `languageOptions.dialect` set to `"bats"`, a bare `cd` (or `pushd` or
|
|
37
42
|
`popd`) written directly in the body of a `@test` block, or of a top-level `setup`, `teardown`,
|
|
@@ -143,4 +148,6 @@ shellcheck overlaps with SC2164 ("Use `cd ... || exit` in case `cd` fails"), and
|
|
|
143
148
|
enforces the same practice. It exists anyway because it offers a suggestion, covers `pushd` and
|
|
144
149
|
`popd`, can be turned off per file by `set -e` detection that follows the same prologue reading as
|
|
145
150
|
`require-strict-mode`, and is part of the shipped presets, so the policy applies without running
|
|
146
|
-
shellcheck.
|
|
151
|
+
shellcheck. The checks differ when errexit comes from a shebang such as `#!/bin/bash -e`:
|
|
152
|
+
shellcheck treats it as `set -e` and suppresses SC2164, while `ignoreWhenErrexit` reads only
|
|
153
|
+
`set` statements in the prologue, so this rule still reports an unchecked `cd`.
|
|
@@ -14,7 +14,7 @@ machine it lands on. When your project states each script's dependencies, this r
|
|
|
14
14
|
statement complete: it reports each command the script uses but does not declare.
|
|
15
15
|
|
|
16
16
|
The rule needs a **declaration convention**, which says how your project declares commands. Bind
|
|
17
|
-
one with `settings
|
|
17
|
+
one with `settings: { sh: { declarationConvention: commentPragma } }`. The plugin ships one, `commentPragma`, which reads
|
|
18
18
|
`# uses: jq curl` comments from a script's header. See
|
|
19
19
|
[Declaration conventions](../declaration-conventions.md) to write your own. **With no convention
|
|
20
20
|
bound, the rule reports nothing**, so turning it on is safe until your project picks a convention.
|
|
@@ -61,16 +61,17 @@ In a file without a shebang, the message says to add the line as the first line
|
|
|
61
61
|
instead. A convention without a hint ends the message with the generic sentence
|
|
62
62
|
"Declare it with the project's declaration convention." A command run by path (`./build.sh`) is reported by that path.
|
|
63
63
|
|
|
64
|
-
##
|
|
64
|
+
## Shared settings
|
|
65
65
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
66
|
+
This rule has no options. Declare project wrapper facts once with
|
|
67
|
+
`settings: { sh: { transparentWrappers: ['with-retry'] } }`.
|
|
68
|
+
Every wrapper-following rule reads that setting, including the other declaration rule.
|
|
69
|
+
Entries accept names or flag-bearing specs, for example
|
|
70
|
+
`settings: { sh: { transparentWrappers: [{ names: ['with-retry'], argFlags: ['-n'] }] } }`.
|
|
71
|
+
Then `with-retry -n 3 curl` uses both `with-retry` and `curl`.
|
|
72
|
+
The shared network wrapper is also followed automatically. Utility wrappers invoked
|
|
73
|
+
by absolute or relative path count by basename; builtin specs require exact names.
|
|
74
|
+
See [shared transparent wrappers](../transparent-wrappers.md) for precedence and migration.
|
|
74
75
|
|
|
75
76
|
The convention is not a rule option. Bind it in `settings`, because ESLint copies rule options
|
|
76
77
|
with `structuredClone`, which cannot copy a convention's methods.
|
|
@@ -84,9 +85,9 @@ export default [
|
|
|
84
85
|
files: ['**/*.sh'],
|
|
85
86
|
plugins: { sh },
|
|
86
87
|
language: 'sh/sh',
|
|
87
|
-
settings: { sh: { declarationConvention: commentPragma } },
|
|
88
|
+
settings: { sh: { declarationConvention: commentPragma, transparentWrappers: ['with-retry'] } },
|
|
88
89
|
rules: {
|
|
89
|
-
'sh/no-undeclared-commands':
|
|
90
|
+
'sh/no-undeclared-commands': 'error',
|
|
90
91
|
},
|
|
91
92
|
},
|
|
92
93
|
];
|
|
@@ -94,11 +95,11 @@ export default [
|
|
|
94
95
|
|
|
95
96
|
## Examples
|
|
96
97
|
|
|
97
|
-
With `commentPragma` bound and `{ transparentWrappers: ['with-retry'] }`:
|
|
98
|
+
With `commentPragma` bound and `settings: { sh: { transparentWrappers: ['with-retry'] } }`:
|
|
98
99
|
|
|
99
100
|
### Incorrect
|
|
100
101
|
|
|
101
|
-
<!-- example: {"convention": "commentPragma", "
|
|
102
|
+
<!-- example: {"convention": "commentPragma", "settings": {"sh": {"transparentWrappers": ["with-retry"]}}, "findings": 6} -->
|
|
102
103
|
```sh
|
|
103
104
|
#!/usr/bin/env bash
|
|
104
105
|
# uses: jq
|
|
@@ -110,7 +111,7 @@ with-retry git pull # git, run by the wrapper, is not declared (nor is
|
|
|
110
111
|
|
|
111
112
|
### Correct
|
|
112
113
|
|
|
113
|
-
<!-- example: {"convention": "commentPragma", "
|
|
114
|
+
<!-- example: {"convention": "commentPragma", "settings": {"sh": {"transparentWrappers": ["with-retry"]}}} -->
|
|
114
115
|
```sh
|
|
115
116
|
#!/usr/bin/env bash
|
|
116
117
|
# uses: curl jq date
|
|
@@ -15,7 +15,7 @@ script's real surface. This rule reports each declared command the script never
|
|
|
15
15
|
converse of [`sh/no-undeclared-commands`](./no-undeclared-commands.md).
|
|
16
16
|
|
|
17
17
|
The rule needs a **declaration convention**, which says how your project declares commands. Bind
|
|
18
|
-
one with `settings
|
|
18
|
+
one with `settings: { sh: { declarationConvention: commentPragma } }`. The plugin ships one, `commentPragma`, which reads
|
|
19
19
|
`# uses: jq curl` comments from a script's header. See
|
|
20
20
|
[Declaration conventions](../declaration-conventions.md) to write your own. **With no convention
|
|
21
21
|
bound, the rule reports nothing**, so turning it on is safe until your project picks a convention.
|
|
@@ -135,18 +135,17 @@ the whole line goes with its `\n` or `\r\n`. When several names on one line are
|
|
|
135
135
|
edits touch, so ESLint applies one per pass. `eslint --fix` runs passes until nothing changes, so
|
|
136
136
|
a line whose every name is unused is removed entirely.
|
|
137
137
|
|
|
138
|
-
##
|
|
138
|
+
## Shared settings
|
|
139
139
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
(see [`sh/require-network-wrapper`](./require-network-wrapper.md#declaring-the-wrapper-once-settingsshnetworkwrapper)).
|
|
140
|
+
This rule has no options. Declare project wrapper facts once with
|
|
141
|
+
`settings: { sh: { transparentWrappers: ['with-retry'] } }`.
|
|
142
|
+
Every wrapper-following rule reads that setting, including the other declaration rule.
|
|
143
|
+
Entries accept names or flag-bearing specs, for example
|
|
144
|
+
`settings: { sh: { transparentWrappers: [{ names: ['with-retry'], argFlags: ['-n'] }] } }`.
|
|
145
|
+
Then `with-retry -n 3 curl` uses both `with-retry` and `curl`.
|
|
146
|
+
The shared network wrapper is also followed automatically. Utility wrappers invoked
|
|
147
|
+
by absolute or relative path count by basename; builtin specs require exact names.
|
|
148
|
+
See [shared transparent wrappers](../transparent-wrappers.md) for precedence and migration.
|
|
150
149
|
|
|
151
150
|
The convention is not a rule option. Bind it in `settings`, because ESLint copies rule options
|
|
152
151
|
with `structuredClone`, which cannot copy a convention's methods.
|
|
@@ -155,17 +154,15 @@ with `structuredClone`, which cannot copy a convention's methods.
|
|
|
155
154
|
// eslint.config.js
|
|
156
155
|
import sh, { commentPragma } from '@eslint-sh/eslint-plugin';
|
|
157
156
|
|
|
158
|
-
const wrappers = { transparentWrappers: ['with-retry'] };
|
|
159
|
-
|
|
160
157
|
export default [
|
|
161
158
|
{
|
|
162
159
|
files: ['**/*.sh'],
|
|
163
160
|
plugins: { sh },
|
|
164
161
|
language: 'sh/sh',
|
|
165
|
-
settings: { sh: { declarationConvention: commentPragma } },
|
|
162
|
+
settings: { sh: { declarationConvention: commentPragma, transparentWrappers: ['with-retry'] } },
|
|
166
163
|
rules: {
|
|
167
|
-
'sh/no-undeclared-commands':
|
|
168
|
-
'sh/no-unused-declarations':
|
|
164
|
+
'sh/no-undeclared-commands': 'error',
|
|
165
|
+
'sh/no-unused-declarations': 'error',
|
|
169
166
|
},
|
|
170
167
|
},
|
|
171
168
|
];
|
|
@@ -173,11 +170,11 @@ export default [
|
|
|
173
170
|
|
|
174
171
|
## Examples
|
|
175
172
|
|
|
176
|
-
With `commentPragma` bound and `{ transparentWrappers: ['with-retry'] }`:
|
|
173
|
+
With `commentPragma` bound and `settings: { sh: { transparentWrappers: ['with-retry'] } }`:
|
|
177
174
|
|
|
178
175
|
### Incorrect
|
|
179
176
|
|
|
180
|
-
<!-- example: {"convention": "commentPragma", "
|
|
177
|
+
<!-- example: {"convention": "commentPragma", "settings": {"sh": {"transparentWrappers": ["with-retry"]}}, "findings": 3} -->
|
|
181
178
|
```sh
|
|
182
179
|
#!/usr/bin/env bash
|
|
183
180
|
# uses: jq curl docker # docker is never run
|
|
@@ -189,7 +186,7 @@ curl -s "$URL" | jq .
|
|
|
189
186
|
|
|
190
187
|
### Correct
|
|
191
188
|
|
|
192
|
-
<!-- example: {"convention": "commentPragma", "
|
|
189
|
+
<!-- example: {"convention": "commentPragma", "settings": {"sh": {"transparentWrappers": ["with-retry"]}}} -->
|
|
193
190
|
```sh
|
|
194
191
|
#!/usr/bin/env bash
|
|
195
192
|
# uses: jq curl git sudo date with-retry
|
|
@@ -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
|
{
|
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,10 +68,10 @@ 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. |
|
|
@@ -85,3 +85,7 @@ reason given, and each rule's page has a "Relationship to shellcheck" section. T
|
|
|
85
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
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
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`.
|
package/docs/wrapper-rules.md
CHANGED
|
@@ -9,6 +9,10 @@ Which commands need a wrapper, and which wrapper, is your project's policy. This
|
|
|
9
9
|
wrapper name. You bind both, either in a rule pack with `createWrapperRule` or in your config
|
|
10
10
|
with [`sh/require-network-wrapper`](./rules/require-network-wrapper.md)'s `wrapper` option.
|
|
11
11
|
|
|
12
|
+
Project wrapper facts are shared through
|
|
13
|
+
`settings: { sh: { transparentWrappers: ['with-retry'] } }`. This rule follows
|
|
14
|
+
those wrappers and the shared network wrapper; see [shared transparent wrappers](./transparent-wrappers.md).
|
|
15
|
+
|
|
12
16
|
## `createWrapperRule`
|
|
13
17
|
|
|
14
18
|
```ts
|
|
@@ -204,3 +208,8 @@ command runs after its own options, located the way a wrapped command is located
|
|
|
204
208
|
`noArgFlags` and `argFlags` are skipped, and an option in `stopsChainFlags` means no subcommand
|
|
205
209
|
runs. The message names the command with its subcommand: `` `git fetch` must be run through
|
|
206
210
|
`retry`. ``
|
|
211
|
+
|
|
212
|
+
A `WrapperSpec` may set `shellBuiltin: true` for a wrapper that runs inside the current shell.
|
|
213
|
+
Such entries match exact names: an executable such as `/tmp/command` is not the `command`
|
|
214
|
+
builtin and its argument words remain opaque. Omitted or false retains utility-wrapper path
|
|
215
|
+
matching. The same boolean field is accepted in the network-wrapper option and setting.
|