@eslint-sh/eslint-plugin 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +223 -17
- package/dist/command-harvest/builtins/bash.d.ts +36 -0
- package/dist/command-harvest/builtins/bash.js +98 -0
- package/dist/command-harvest/builtins/bats.d.ts +45 -0
- package/dist/command-harvest/builtins/bats.js +33 -0
- package/dist/command-harvest/builtins/index.d.ts +18 -0
- package/dist/command-harvest/builtins/index.js +39 -0
- package/dist/command-harvest/builtins/posix.d.ts +51 -0
- package/dist/command-harvest/builtins/posix.js +93 -0
- package/dist/command-harvest/harvest-commands.d.ts +66 -0
- package/dist/command-harvest/harvest-commands.js +97 -0
- package/dist/command-harvest/wrapper-table.d.ts +24 -0
- package/dist/command-harvest/wrapper-table.js +49 -0
- package/dist/configs/agent-authored.d.ts +41 -0
- package/dist/configs/agent-authored.js +33 -0
- package/dist/configs/bash-3-2.d.ts +28 -0
- package/dist/configs/bash-3-2.js +50 -0
- package/dist/configs/posix-portable.d.ts +40 -0
- package/dist/configs/posix-portable.js +52 -0
- package/dist/configs/recommended.d.ts +52 -0
- package/dist/configs/recommended.js +34 -0
- package/dist/declaration-conventions/comment-pragma.d.ts +33 -0
- package/dist/declaration-conventions/comment-pragma.js +153 -0
- package/dist/declaration-conventions/declaration-convention.d.ts +189 -0
- package/dist/declaration-conventions/declaration-convention.js +1 -0
- package/dist/declaration-conventions/read-declared-commands.d.ts +70 -0
- package/dist/declaration-conventions/read-declared-commands.js +128 -0
- package/dist/eslint-plugin-syntax-analyze.d.ts +23 -0
- package/dist/eslint-plugin-syntax.d.ts +27 -0
- package/dist/eslint-plugin.d.ts +1211 -52
- package/dist/index.d.ts +56 -8
- package/dist/index.js +53 -6
- package/dist/language/command-resolution.d.ts +124 -0
- package/dist/language/command-resolution.js +120 -0
- package/dist/language/end-of-input-marker.d.ts +29 -0
- package/dist/language/end-of-input-marker.js +88 -0
- package/dist/language/line-breaks.d.ts +18 -0
- package/dist/language/line-breaks.js +22 -0
- package/dist/language/lint-view-types.d.ts +104 -0
- package/dist/language/lint-view-types.js +1 -0
- package/dist/language/non-shell-file-guard.d.ts +35 -0
- package/dist/language/non-shell-file-guard.js +59 -0
- package/dist/language/selector-classes.d.ts +0 -1
- package/dist/language/selector-classes.js +0 -1
- package/dist/language/shell-language-options.d.ts +7 -1
- package/dist/language/shell-language-options.js +4 -4
- package/dist/language/shell-language.d.ts +10 -2
- package/dist/language/shell-language.js +59 -8
- package/dist/language/shell-source-code.d.ts +342 -5
- package/dist/language/shell-source-code.js +559 -30
- package/dist/language/source-coordinates.d.ts +13 -2
- package/dist/language/source-coordinates.js +68 -14
- package/dist/language/syntax-view.d.ts +60 -39
- package/dist/language/syntax-view.js +200 -45
- package/dist/language/word-resolution.d.ts +4 -3
- package/dist/language/word-resolution.js +4 -3
- package/dist/rules/compat/create-compat-rule.d.ts +59 -0
- package/dist/rules/compat/create-compat-rule.js +65 -0
- package/dist/rules/compat/declaration-options.d.ts +19 -0
- package/dist/rules/compat/declaration-options.js +110 -0
- package/dist/rules/compat/no-append-all-redirect.d.ts +13 -0
- package/dist/rules/compat/no-append-all-redirect.js +73 -0
- package/dist/rules/compat/no-associative-arrays.d.ts +12 -0
- package/dist/rules/compat/no-associative-arrays.js +39 -0
- package/dist/rules/compat/no-case-attributes.d.ts +12 -0
- package/dist/rules/compat/no-case-attributes.js +63 -0
- package/dist/rules/compat/no-case-fallthrough.d.ts +12 -0
- package/dist/rules/compat/no-case-fallthrough.js +67 -0
- package/dist/rules/compat/no-case-modification.d.ts +35 -0
- package/dist/rules/compat/no-case-modification.js +86 -0
- package/dist/rules/compat/no-coproc.d.ts +12 -0
- package/dist/rules/compat/no-coproc.js +47 -0
- package/dist/rules/compat/no-declare-g.d.ts +12 -0
- package/dist/rules/compat/no-declare-g.js +59 -0
- package/dist/rules/compat/no-double-bracket.d.ts +12 -0
- package/dist/rules/compat/no-double-bracket.js +52 -0
- package/dist/rules/compat/no-epoch-variables.d.ts +12 -0
- package/dist/rules/compat/no-epoch-variables.js +57 -0
- package/dist/rules/compat/no-fd-variable.d.ts +12 -0
- package/dist/rules/compat/no-fd-variable.js +48 -0
- package/dist/rules/compat/no-globstar.d.ts +12 -0
- package/dist/rules/compat/no-globstar.js +39 -0
- package/dist/rules/compat/no-lastpipe-semantics.d.ts +12 -0
- package/dist/rules/compat/no-lastpipe-semantics.js +53 -0
- package/dist/rules/compat/no-let.d.ts +12 -0
- package/dist/rules/compat/no-let.js +62 -0
- package/dist/rules/compat/no-mapfile.d.ts +13 -0
- package/dist/rules/compat/no-mapfile.js +122 -0
- package/dist/rules/compat/no-nameref.d.ts +12 -0
- package/dist/rules/compat/no-nameref.js +44 -0
- package/dist/rules/compat/no-negative-array-index.d.ts +13 -0
- package/dist/rules/compat/no-negative-array-index.js +123 -0
- package/dist/rules/compat/no-nounset-empty-array.d.ts +13 -0
- package/dist/rules/compat/no-nounset-empty-array.js +158 -0
- package/dist/rules/compat/no-parameter-transformation.d.ts +13 -0
- package/dist/rules/compat/no-parameter-transformation.js +123 -0
- package/dist/rules/compat/no-pipe-all.d.ts +13 -0
- package/dist/rules/compat/no-pipe-all.js +68 -0
- package/dist/rules/compat/no-printf-time-format.d.ts +12 -0
- package/dist/rules/compat/no-printf-time-format.js +99 -0
- package/dist/rules/compat/no-source.d.ts +13 -0
- package/dist/rules/compat/no-source.js +103 -0
- package/dist/rules/compat/no-test-v.d.ts +12 -0
- package/dist/rules/compat/no-test-v.js +280 -0
- package/dist/rules/compat/no-unicode-escape.d.ts +14 -0
- package/dist/rules/compat/no-unicode-escape.js +120 -0
- package/dist/rules/compat/no-wait-n.d.ts +12 -0
- package/dist/rules/compat/no-wait-n.js +73 -0
- package/dist/rules/compat/operator-range.d.ts +10 -0
- package/dist/rules/compat/operator-range.js +15 -0
- package/dist/rules/compat/posix-absent-command.d.ts +12 -0
- package/dist/rules/compat/posix-absent-command.js +134 -0
- package/dist/rules/compat/shopt-options.d.ts +9 -0
- package/dist/rules/compat/shopt-options.js +63 -0
- package/dist/rules/compat/version-gates.d.ts +76 -0
- package/dist/rules/compat/version-gates.js +197 -0
- package/dist/rules/create-rule.d.ts +91 -0
- package/dist/rules/create-rule.js +51 -0
- package/dist/rules/create-wrapper-rule.d.ts +166 -0
- package/dist/rules/create-wrapper-rule.js +407 -0
- package/dist/rules/define-shell-rule.d.ts +184 -0
- package/dist/rules/define-shell-rule.js +56 -0
- package/dist/rules/index.d.ts +26 -0
- package/dist/rules/index.js +88 -0
- package/dist/rules/max-script-complexity.d.ts +28 -0
- package/dist/rules/max-script-complexity.js +130 -0
- package/dist/rules/no-dynamic-invocation.d.ts +35 -0
- package/dist/rules/no-dynamic-invocation.js +117 -0
- package/dist/rules/no-eval.d.ts +13 -0
- package/dist/rules/no-eval.js +89 -0
- package/dist/rules/no-masked-exit-status.d.ts +21 -0
- package/dist/rules/no-masked-exit-status.js +667 -0
- package/dist/rules/no-secret-env-reads.d.ts +24 -0
- package/dist/rules/no-secret-env-reads.js +133 -0
- package/dist/rules/no-secret-flags.d.ts +32 -0
- package/dist/rules/no-secret-flags.js +162 -0
- package/dist/rules/no-unchecked-cd.d.ts +44 -0
- package/dist/rules/no-unchecked-cd.js +250 -0
- package/dist/rules/no-undeclared-commands.d.ts +20 -0
- package/dist/rules/no-undeclared-commands.js +115 -0
- package/dist/rules/no-unused-declarations.d.ts +23 -0
- package/dist/rules/no-unused-declarations.js +259 -0
- package/dist/rules/prefer-local.d.ts +16 -0
- package/dist/rules/prefer-local.js +249 -0
- package/dist/rules/quote-expansions.d.ts +13 -0
- package/dist/rules/quote-expansions.js +229 -0
- package/dist/rules/require-network-wrapper.d.ts +40 -0
- package/dist/rules/require-network-wrapper.js +244 -0
- package/dist/rules/require-shebang.d.ts +29 -0
- package/dist/rules/require-shebang.js +101 -0
- package/dist/rules/require-strict-mode.d.ts +52 -0
- package/dist/rules/require-strict-mode.js +177 -0
- package/dist/rules/require-tty-guard.d.ts +13 -0
- package/dist/rules/require-tty-guard.js +289 -0
- package/dist/rules/resolve-declaration-convention.d.ts +96 -0
- package/dist/rules/resolve-declaration-convention.js +109 -0
- package/dist/rules/resolve-network-wrapper.d.ts +19 -0
- package/dist/rules/resolve-network-wrapper.js +116 -0
- package/dist/rules/set-options.d.ts +109 -0
- package/dist/rules/set-options.js +201 -0
- package/dist/rules/shebang-interpreter.d.ts +18 -0
- package/dist/rules/shebang-interpreter.js +81 -0
- package/dist/rules/shebang-matches-dialect.d.ts +12 -0
- package/dist/rules/shebang-matches-dialect.js +69 -0
- package/dist/rules/transparent-wrappers.d.ts +52 -0
- package/dist/rules/transparent-wrappers.js +99 -0
- package/dist/rules/user-pattern.d.ts +63 -0
- package/dist/rules/user-pattern.js +184 -0
- package/dist/syntax/analyze.d.ts +19 -0
- package/dist/syntax/analyze.js +21 -0
- package/dist/syntax/index.d.ts +23 -0
- package/dist/syntax/index.js +25 -0
- package/dist/version.d.ts +0 -1
- package/dist/version.js +0 -1
- package/docs/compat.md +347 -0
- package/docs/custom-rule-tutorial.md +780 -34
- package/docs/declaration-conventions.md +478 -0
- package/docs/examples/audit-wrapper-pack.mjs +15 -0
- package/docs/examples/dep-line-convention.mjs +22 -0
- package/docs/examples/eslint.config.mjs +3 -2
- package/docs/examples/no-and-or-as-if-rule.mjs +61 -0
- package/docs/examples/no-backticks-rule-tester.mjs +106 -0
- package/docs/examples/no-backticks-rule.mjs +60 -0
- package/docs/examples/no-curl-in-functions-rule.mjs +53 -0
- package/docs/examples/no-function-keyword-rule.mjs +59 -0
- package/docs/examples/no-listed-commands-rule.ts +63 -0
- package/docs/examples/no-rm-rf-rule.mjs +26 -15
- package/docs/examples/no-unquoted-rm-variable-rule.ts +54 -0
- package/docs/examples/sidecar-convention.mjs +38 -0
- package/docs/examples/sidecar-snapshot-convention.mjs +26 -0
- package/docs/examples/sorted-declarations-rule.mjs +52 -0
- package/docs/reading-nodes.md +159 -0
- package/docs/rules/README.md +78 -0
- package/docs/rules/_template.md +83 -0
- package/docs/rules/compat/no-append-all-redirect.md +87 -0
- package/docs/rules/compat/no-associative-arrays.md +95 -0
- package/docs/rules/compat/no-case-attributes.md +82 -0
- package/docs/rules/compat/no-case-fallthrough.md +90 -0
- package/docs/rules/compat/no-case-modification.md +74 -0
- package/docs/rules/compat/no-coproc.md +76 -0
- package/docs/rules/compat/no-declare-g.md +80 -0
- package/docs/rules/compat/no-double-bracket.md +72 -0
- package/docs/rules/compat/no-epoch-variables.md +79 -0
- package/docs/rules/compat/no-fd-variable.md +78 -0
- package/docs/rules/compat/no-globstar.md +74 -0
- package/docs/rules/compat/no-lastpipe-semantics.md +73 -0
- package/docs/rules/compat/no-let.md +78 -0
- package/docs/rules/compat/no-mapfile.md +118 -0
- package/docs/rules/compat/no-nameref.md +81 -0
- package/docs/rules/compat/no-negative-array-index.md +87 -0
- package/docs/rules/compat/no-nounset-empty-array.md +103 -0
- package/docs/rules/compat/no-parameter-transformation.md +94 -0
- package/docs/rules/compat/no-pipe-all.md +85 -0
- package/docs/rules/compat/no-printf-time-format.md +75 -0
- package/docs/rules/compat/no-source.md +98 -0
- package/docs/rules/compat/no-test-v.md +82 -0
- package/docs/rules/compat/no-unicode-escape.md +91 -0
- package/docs/rules/compat/no-wait-n.md +85 -0
- package/docs/rules/max-script-complexity.md +109 -0
- package/docs/rules/no-dynamic-invocation.md +139 -0
- package/docs/rules/no-eval.md +106 -0
- package/docs/rules/no-masked-exit-status.md +239 -0
- package/docs/rules/no-secret-env-reads.md +173 -0
- package/docs/rules/no-secret-flags.md +169 -0
- package/docs/rules/no-unchecked-cd.md +146 -0
- package/docs/rules/no-undeclared-commands.md +141 -0
- package/docs/rules/no-unused-declarations.md +216 -0
- package/docs/rules/prefer-local.md +221 -0
- package/docs/rules/quote-expansions.md +157 -0
- package/docs/rules/require-network-wrapper.md +171 -0
- package/docs/rules/require-shebang.md +157 -0
- package/docs/rules/require-strict-mode.md +253 -0
- package/docs/rules/require-tty-guard.md +128 -0
- package/docs/rules/shebang-matches-dialect.md +120 -0
- package/docs/selectors.md +2 -2
- package/docs/shellcheck.md +86 -0
- package/docs/wrapper-rules.md +206 -0
- package/package.json +26 -20
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/language/selector-classes.d.ts.map +0 -1
- package/dist/language/selector-classes.js.map +0 -1
- package/dist/language/shell-language-options.d.ts.map +0 -1
- package/dist/language/shell-language-options.js.map +0 -1
- package/dist/language/shell-language.d.ts.map +0 -1
- package/dist/language/shell-language.js.map +0 -1
- package/dist/language/shell-source-code.d.ts.map +0 -1
- package/dist/language/shell-source-code.js.map +0 -1
- package/dist/language/source-coordinates.d.ts.map +0 -1
- package/dist/language/source-coordinates.js.map +0 -1
- package/dist/language/syntax-view.d.ts.map +0 -1
- package/dist/language/syntax-view.js.map +0 -1
- package/dist/language/word-resolution.d.ts.map +0 -1
- package/dist/language/word-resolution.js.map +0 -1
- package/dist/version.d.ts.map +0 -1
- package/dist/version.js.map +0 -1
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The commands a POSIX `sh` runs as builtins: data for the command harvest,
|
|
3
|
+
* never rule logic.
|
|
4
|
+
*
|
|
5
|
+
* Three groups, each from its own source (POSIX.1-2017, XCU Issue 7, whose
|
|
6
|
+
* section numbers are used here):
|
|
7
|
+
*
|
|
8
|
+
* - **Special built-ins**, XCU §2.14 "Special Built-In Utilities": `break`,
|
|
9
|
+
* `:`, `continue`, `.`, `eval`, `exec`, `exit`, `export`, `readonly`,
|
|
10
|
+
* `return`, `set`, `shift`, `times`, `trap`, `unset`.
|
|
11
|
+
* - **Regular built-ins**, XCU §2.9.1.1 "Command Search and Execution",
|
|
12
|
+
* step 1.d: the utilities a shell must invoke before searching `PATH`
|
|
13
|
+
* (`alias`, `bg`, `cd`, `command`, `false`, `fc`, `fg`, `getopts`, `hash`,
|
|
14
|
+
* `jobs`, `kill`, `newgrp`, `pwd`, `read`, `true`, `umask`, `unalias`,
|
|
15
|
+
* `wait`), plus `type` and `ulimit`, which the same step lists under the
|
|
16
|
+
* XSI option. POSIX.1-2024 (Issue 8) calls most of these "intrinsic
|
|
17
|
+
* utilities" (XCU §1.7).
|
|
18
|
+
* - **Built in by every POSIX shell in practice**: `echo`, `printf`, `test`
|
|
19
|
+
* and `[`. POSIX specifies them as ordinary utilities that a shell may
|
|
20
|
+
* build in (XCU §1.6), and dash, ash, ksh, mksh and bash all do (see the
|
|
21
|
+
* "Builtins" section of dash(1)). Counting them as external would make
|
|
22
|
+
* nearly every script declare `echo`.
|
|
23
|
+
*
|
|
24
|
+
* Bash-only builtins such as `local`, `source` and `mapfile` are
|
|
25
|
+
* deliberately absent: in a `posix` file they are commands that a POSIX
|
|
26
|
+
* shell need not provide.
|
|
27
|
+
*
|
|
28
|
+
* @see https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_14 — XCU §2.14 Special Built-In Utilities
|
|
29
|
+
* @see https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_09_01_01 — XCU §2.9.1.1 Command Search and Execution
|
|
30
|
+
* @see https://pubs.opengroup.org/onlinepubs/9799919799/utilities/V3_chap01.html — POSIX.1-2024 XCU §1.7 Intrinsic Utilities
|
|
31
|
+
* @see https://man7.org/linux/man-pages/man1/dash.1.html — dash(1), "Builtins"
|
|
32
|
+
*/
|
|
33
|
+
/**
|
|
34
|
+
* POSIX special built-in utilities (XCU §2.14).
|
|
35
|
+
*
|
|
36
|
+
* @internal
|
|
37
|
+
*/
|
|
38
|
+
export const POSIX_SPECIAL_BUILTINS = Object.freeze([
|
|
39
|
+
'break',
|
|
40
|
+
':',
|
|
41
|
+
'continue',
|
|
42
|
+
'.',
|
|
43
|
+
'eval',
|
|
44
|
+
'exec',
|
|
45
|
+
'exit',
|
|
46
|
+
'export',
|
|
47
|
+
'readonly',
|
|
48
|
+
'return',
|
|
49
|
+
'set',
|
|
50
|
+
'shift',
|
|
51
|
+
'times',
|
|
52
|
+
'trap',
|
|
53
|
+
'unset',
|
|
54
|
+
]);
|
|
55
|
+
/**
|
|
56
|
+
* POSIX regular built-in utilities (XCU §2.9.1.1 step 1.d, including the
|
|
57
|
+
* XSI `type` and `ulimit`).
|
|
58
|
+
*
|
|
59
|
+
* @internal
|
|
60
|
+
*/
|
|
61
|
+
export const POSIX_REGULAR_BUILTINS = Object.freeze([
|
|
62
|
+
'alias',
|
|
63
|
+
'bg',
|
|
64
|
+
'cd',
|
|
65
|
+
'command',
|
|
66
|
+
'false',
|
|
67
|
+
'fc',
|
|
68
|
+
'fg',
|
|
69
|
+
'getopts',
|
|
70
|
+
'hash',
|
|
71
|
+
'jobs',
|
|
72
|
+
'kill',
|
|
73
|
+
'newgrp',
|
|
74
|
+
'pwd',
|
|
75
|
+
'read',
|
|
76
|
+
'true',
|
|
77
|
+
'type',
|
|
78
|
+
'ulimit',
|
|
79
|
+
'umask',
|
|
80
|
+
'unalias',
|
|
81
|
+
'wait',
|
|
82
|
+
]);
|
|
83
|
+
/**
|
|
84
|
+
* Utilities POSIX lets a shell build in, and every POSIX shell does.
|
|
85
|
+
*
|
|
86
|
+
* @internal
|
|
87
|
+
*/
|
|
88
|
+
export const POSIX_CONVENTIONAL_BUILTINS = Object.freeze([
|
|
89
|
+
'echo',
|
|
90
|
+
'printf',
|
|
91
|
+
'test',
|
|
92
|
+
'[',
|
|
93
|
+
]);
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { WrapperSpec } from 'sh-ast/analyze';
|
|
2
|
+
import type { ShellCommandSite } from '../language/command-resolution.js';
|
|
3
|
+
import type { ShellSourceCode } from '../language/shell-source-code.js';
|
|
4
|
+
import type { ShellNode } from '../language/syntax-view.js';
|
|
5
|
+
/**
|
|
6
|
+
* Options for {@link harvestCommands}.
|
|
7
|
+
*
|
|
8
|
+
* @internal
|
|
9
|
+
*/
|
|
10
|
+
export interface HarvestCommandsOptions {
|
|
11
|
+
/**
|
|
12
|
+
* The transparent-wrapper table to follow, forwarded to
|
|
13
|
+
* `resolveInvokedCommand` unchanged. It **replaces** `sh-ast/analyze`'s
|
|
14
|
+
* `DEFAULT_TRANSPARENT_WRAPPERS` (spread the defaults in to extend them).
|
|
15
|
+
* Defaults to `DEFAULT_TRANSPARENT_WRAPPERS`.
|
|
16
|
+
*/
|
|
17
|
+
readonly transparentWrappers?: readonly WrapperSpec[];
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* The first use of one external command in a file.
|
|
21
|
+
*
|
|
22
|
+
* @internal
|
|
23
|
+
*/
|
|
24
|
+
export interface HarvestedCommand {
|
|
25
|
+
/** The command name, exactly as the shell would look it up (`jq`, `./build.sh`). */
|
|
26
|
+
readonly name: string;
|
|
27
|
+
/**
|
|
28
|
+
* The `Word` node that names the command at its first use: the command
|
|
29
|
+
* word itself, or for a wrapped command the word after the wrapper
|
|
30
|
+
* (`curl` in `sudo curl x`). Falls back to the whole `CallExpr` in the
|
|
31
|
+
* unexpected case that the word cannot be located.
|
|
32
|
+
*/
|
|
33
|
+
readonly node: ShellNode;
|
|
34
|
+
/** The command site of that first use. */
|
|
35
|
+
readonly site: ShellCommandSite;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* The names of every function the file defines, anywhere in it (top level,
|
|
39
|
+
* nested in another function, inside a conditional, …). Position is not read,
|
|
40
|
+
* so this answers "could this name be a function", not "is it one at this
|
|
41
|
+
* call": a call in a function body usually runs after a later definition, but a
|
|
42
|
+
* top-level call before the definition does not run it. zsh's
|
|
43
|
+
* `function a b { … }` defines every listed name.
|
|
44
|
+
*
|
|
45
|
+
* @internal
|
|
46
|
+
*/
|
|
47
|
+
export declare function definedFunctionNames(sourceCode: ShellSourceCode): ReadonlySet<string>;
|
|
48
|
+
/**
|
|
49
|
+
* Returns every external command `sourceCode` runs, keyed by name, each with
|
|
50
|
+
* its first use. The map iterates in first-use (source) order.
|
|
51
|
+
*
|
|
52
|
+
* A command is every static word in an invocation's wrapper chain (the
|
|
53
|
+
* wrappers and the effective command), excluding:
|
|
54
|
+
*
|
|
55
|
+
* - dynamic words, which name no knowable command;
|
|
56
|
+
* - builtins of the file's dialect (`sourceCode.dialect`);
|
|
57
|
+
* - functions the file defines anywhere.
|
|
58
|
+
*
|
|
59
|
+
* @throws `ShAnalyzeMaxDepthError` (from `sh-ast/analyze`) when the file's
|
|
60
|
+
* nesting exceeds analysis's recursion guard: a partial harvest would miss
|
|
61
|
+
* real commands.
|
|
62
|
+
* @throws `ShAnalyzeInvalidWrapperSpecError` (from `sh-ast/analyze`) when
|
|
63
|
+
* `options.transparentWrappers` contains a malformed entry.
|
|
64
|
+
* @internal
|
|
65
|
+
*/
|
|
66
|
+
export declare function harvestCommands(sourceCode: ShellSourceCode, options?: HarvestCommandsOptions): ReadonlyMap<string, HarvestedCommand>;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The command harvest: which external commands a shell file depends on.
|
|
3
|
+
*
|
|
4
|
+
* "Usage" here is the definition the declaration-convention rules compare
|
|
5
|
+
* declarations against: every command name the file statically runs, minus
|
|
6
|
+
* the shell's builtins and minus functions the file defines itself, following
|
|
7
|
+
* transparent wrappers so that `sudo curl …` uses both `sudo` and `curl`.
|
|
8
|
+
*
|
|
9
|
+
* Every fact comes from `sh-ast/analyze` through the `ShellSourceCode`
|
|
10
|
+
* helpers. `getCommandSites` supplies every command in the file, wherever it
|
|
11
|
+
* is nested (function bodies, `$(…)` in arguments, assignment values,
|
|
12
|
+
* redirect targets, loop lists, case subjects, …), and each site's
|
|
13
|
+
* `resolveInvokedCommand` supplies the wrapper chain. Whether a word is
|
|
14
|
+
* static is the analysis answer (`WordResolution.static`), never decided
|
|
15
|
+
* here.
|
|
16
|
+
*
|
|
17
|
+
* Out of scope, by design:
|
|
18
|
+
*
|
|
19
|
+
* - Dynamic command words (`"$cmd" x`, `sudo "$prog"`, `$(pick) x`). They
|
|
20
|
+
* name no knowable command; `no-dynamic-invocation` reports them. Static
|
|
21
|
+
* wrappers in front of a dynamic word (`sudo` in `sudo "$prog"`) are still
|
|
22
|
+
* used.
|
|
23
|
+
* - Commands inside strings that another program runs (`bash -c '…'`,
|
|
24
|
+
* `env -S '…'`). Those are text, not syntax.
|
|
25
|
+
*
|
|
26
|
+
* This module is internal. It is shaped to move, unchanged, into a public
|
|
27
|
+
* analysis-services package later; until then rules in this package use it.
|
|
28
|
+
*/
|
|
29
|
+
import { DEFAULT_TRANSPARENT_WRAPPERS } from 'sh-ast/analyze';
|
|
30
|
+
import { walkShell } from '../language/syntax-view.js';
|
|
31
|
+
import { builtinsForDialect } from './builtins/index.js';
|
|
32
|
+
/**
|
|
33
|
+
* The names of every function the file defines, anywhere in it (top level,
|
|
34
|
+
* nested in another function, inside a conditional, …). Position is not read,
|
|
35
|
+
* so this answers "could this name be a function", not "is it one at this
|
|
36
|
+
* call": a call in a function body usually runs after a later definition, but a
|
|
37
|
+
* top-level call before the definition does not run it. zsh's
|
|
38
|
+
* `function a b { … }` defines every listed name.
|
|
39
|
+
*
|
|
40
|
+
* @internal
|
|
41
|
+
*/
|
|
42
|
+
export function definedFunctionNames(sourceCode) {
|
|
43
|
+
const names = new Set();
|
|
44
|
+
walkShell(sourceCode.ast, (node) => {
|
|
45
|
+
if (node.type !== 'FuncDecl')
|
|
46
|
+
return;
|
|
47
|
+
if (node.name)
|
|
48
|
+
names.add(node.name.value);
|
|
49
|
+
for (const lit of node.names)
|
|
50
|
+
names.add(lit.value);
|
|
51
|
+
});
|
|
52
|
+
return names;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* The `Word` node in `site`'s command at argv position `index`. argv and the
|
|
56
|
+
* `CallExpr`'s `args` list the same words in the same order, so a position
|
|
57
|
+
* from `chainIndices` selects the syntax word directly.
|
|
58
|
+
*/
|
|
59
|
+
function wordNodeAt(site, index) {
|
|
60
|
+
return site.node.args[index] ?? site.node;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Returns every external command `sourceCode` runs, keyed by name, each with
|
|
64
|
+
* its first use. The map iterates in first-use (source) order.
|
|
65
|
+
*
|
|
66
|
+
* A command is every static word in an invocation's wrapper chain (the
|
|
67
|
+
* wrappers and the effective command), excluding:
|
|
68
|
+
*
|
|
69
|
+
* - dynamic words, which name no knowable command;
|
|
70
|
+
* - builtins of the file's dialect (`sourceCode.dialect`);
|
|
71
|
+
* - functions the file defines anywhere.
|
|
72
|
+
*
|
|
73
|
+
* @throws `ShAnalyzeMaxDepthError` (from `sh-ast/analyze`) when the file's
|
|
74
|
+
* nesting exceeds analysis's recursion guard: a partial harvest would miss
|
|
75
|
+
* real commands.
|
|
76
|
+
* @throws `ShAnalyzeInvalidWrapperSpecError` (from `sh-ast/analyze`) when
|
|
77
|
+
* `options.transparentWrappers` contains a malformed entry.
|
|
78
|
+
* @internal
|
|
79
|
+
*/
|
|
80
|
+
export function harvestCommands(sourceCode, options = {}) {
|
|
81
|
+
const transparentWrappers = options.transparentWrappers ?? DEFAULT_TRANSPARENT_WRAPPERS;
|
|
82
|
+
const builtins = builtinsForDialect(sourceCode.dialect);
|
|
83
|
+
const functions = definedFunctionNames(sourceCode);
|
|
84
|
+
const harvested = new Map();
|
|
85
|
+
for (const site of sourceCode.getCommandSites()) {
|
|
86
|
+
const { chain, chainIndices } = site.resolveInvokedCommand({ transparentWrappers });
|
|
87
|
+
for (const [position, word] of chain.entries()) {
|
|
88
|
+
if (!word.static)
|
|
89
|
+
continue;
|
|
90
|
+
const name = word.text;
|
|
91
|
+
if (harvested.has(name) || builtins.has(name) || functions.has(name))
|
|
92
|
+
continue;
|
|
93
|
+
harvested.set(name, { name, node: wordNodeAt(site, chainIndices[position]), site });
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return harvested;
|
|
97
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { WrapperSpec } from 'sh-ast/analyze';
|
|
2
|
+
import type { ShellDialect } from '../language/shell-language-options.js';
|
|
3
|
+
/**
|
|
4
|
+
* Returns the transparent-wrapper table for harvesting a `dialect` file
|
|
5
|
+
* with the given configured wrapper names, in this order:
|
|
6
|
+
*
|
|
7
|
+
* 1. `sh-ast/analyze`'s `DEFAULT_TRANSPARENT_WRAPPERS` (`sudo`, `env`, …);
|
|
8
|
+
* 2. the dialect's own wrappers (`run` under `bats`);
|
|
9
|
+
* 3. the project's network wrapper (`settings.sh.networkWrapper`), when set,
|
|
10
|
+
* with the options its spec declares: a wrapper that network commands must
|
|
11
|
+
* run through runs the command after it;
|
|
12
|
+
* 4. one name-only entry per configured name. A name-only entry recognizes
|
|
13
|
+
* no flags, so `with-retry -n 3 curl` stops at `-n` rather than guessing
|
|
14
|
+
* past it.
|
|
15
|
+
*
|
|
16
|
+
* The first entry whose names match is the one used, so a network wrapper
|
|
17
|
+
* spec with options wins over a name-only entry for the same name.
|
|
18
|
+
*
|
|
19
|
+
* @param dialect - The linted file's `sourceCode.dialect`.
|
|
20
|
+
* @param wrapperNames - The rule's `transparentWrappers` option names.
|
|
21
|
+
* @param networkWrapper - The spec from `settings.sh.networkWrapper`, or `null`.
|
|
22
|
+
* @internal
|
|
23
|
+
*/
|
|
24
|
+
export declare function harvestWrapperTable(dialect: ShellDialect, wrapperNames: readonly string[], networkWrapper?: WrapperSpec | null): readonly WrapperSpec[];
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one wrapper table that rules comparing declarations against usage
|
|
3
|
+
* pass to `harvestCommands` (`./harvest-commands.ts`).
|
|
4
|
+
*
|
|
5
|
+
* `no-undeclared-commands` and `no-unused-declarations` must agree exactly on
|
|
6
|
+
* which commands a file uses, or one would report a command the other calls
|
|
7
|
+
* unused. Usage depends on the wrapper table, so both build it here from the
|
|
8
|
+
* same inputs: the file's dialect, the network wrapper from
|
|
9
|
+
* `settings.sh.networkWrapper`, and the rule's `transparentWrappers` option
|
|
10
|
+
* names.
|
|
11
|
+
*/
|
|
12
|
+
import { DEFAULT_TRANSPARENT_WRAPPERS } from 'sh-ast/analyze';
|
|
13
|
+
import { BATS_RUN_WRAPPER } from './builtins/bats.js';
|
|
14
|
+
/**
|
|
15
|
+
* Wrappers a dialect's runtime provides beyond sh-ast's defaults: bats' `run`
|
|
16
|
+
* for `bats` files.
|
|
17
|
+
*/
|
|
18
|
+
const DIALECT_WRAPPERS = {
|
|
19
|
+
bats: [BATS_RUN_WRAPPER],
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Returns the transparent-wrapper table for harvesting a `dialect` file
|
|
23
|
+
* with the given configured wrapper names, in this order:
|
|
24
|
+
*
|
|
25
|
+
* 1. `sh-ast/analyze`'s `DEFAULT_TRANSPARENT_WRAPPERS` (`sudo`, `env`, …);
|
|
26
|
+
* 2. the dialect's own wrappers (`run` under `bats`);
|
|
27
|
+
* 3. the project's network wrapper (`settings.sh.networkWrapper`), when set,
|
|
28
|
+
* with the options its spec declares: a wrapper that network commands must
|
|
29
|
+
* run through runs the command after it;
|
|
30
|
+
* 4. one name-only entry per configured name. A name-only entry recognizes
|
|
31
|
+
* no flags, so `with-retry -n 3 curl` stops at `-n` rather than guessing
|
|
32
|
+
* past it.
|
|
33
|
+
*
|
|
34
|
+
* The first entry whose names match is the one used, so a network wrapper
|
|
35
|
+
* spec with options wins over a name-only entry for the same name.
|
|
36
|
+
*
|
|
37
|
+
* @param dialect - The linted file's `sourceCode.dialect`.
|
|
38
|
+
* @param wrapperNames - The rule's `transparentWrappers` option names.
|
|
39
|
+
* @param networkWrapper - The spec from `settings.sh.networkWrapper`, or `null`.
|
|
40
|
+
* @internal
|
|
41
|
+
*/
|
|
42
|
+
export function harvestWrapperTable(dialect, wrapperNames, networkWrapper = null) {
|
|
43
|
+
return [
|
|
44
|
+
...DEFAULT_TRANSPARENT_WRAPPERS,
|
|
45
|
+
...(DIALECT_WRAPPERS[dialect] ?? []),
|
|
46
|
+
...(networkWrapper === null ? [] : [networkWrapper]),
|
|
47
|
+
...wrapperNames.map((name) => ({ names: [name] })),
|
|
48
|
+
];
|
|
49
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `sh/agent-authored` shared config: the strict preset for scripts that
|
|
3
|
+
* are machine-written and human-reviewed.
|
|
4
|
+
*
|
|
5
|
+
* Such a script is only as trustworthy as a reviewer's ability to vouch for
|
|
6
|
+
* it mechanically, so this preset adds the contract rules on top of
|
|
7
|
+
* `sh/recommended`: every command a script runs must be statically
|
|
8
|
+
* enumerable, declared, and (for network access) routed through the
|
|
9
|
+
* project's wrapper, and no script may stop to prompt a terminal that may not
|
|
10
|
+
* exist (`require-tty-guard`). Each contract rule reports at `error`, and so
|
|
11
|
+
* do `no-secret-flags` and `quote-expansions`, which `recommended` keeps at
|
|
12
|
+
* `warn`.
|
|
13
|
+
*
|
|
14
|
+
* It follows the same shape contract as `sh/recommended` (see
|
|
15
|
+
* `./recommended.ts`): one files-less config object that registers this
|
|
16
|
+
* plugin under `sh` and selects `language: 'sh/sh'`, so the two presets are
|
|
17
|
+
* interchangeable inside a user's `extends`.
|
|
18
|
+
*
|
|
19
|
+
* `max-script-complexity` keeps a machine-written script small enough for a
|
|
20
|
+
* reviewer to read in one sitting.
|
|
21
|
+
*
|
|
22
|
+
* Three members are inert until configured: `no-undeclared-commands` and
|
|
23
|
+
* `no-unused-declarations` need a declaration convention bound in
|
|
24
|
+
* `settings.sh.declarationConvention`, and `require-network-wrapper` needs a
|
|
25
|
+
* wrapper, set once in `settings.sh.networkWrapper` (which the declaration
|
|
26
|
+
* rules also read) or as the rule's `wrapper` option. They are enabled here so that binding the convention or
|
|
27
|
+
* wrapper is the only step a project takes; shipping a default convention or
|
|
28
|
+
* wrapper would impose one project's policy on every project.
|
|
29
|
+
*
|
|
30
|
+
* Rule membership follows the **Configs** column of the rule catalog
|
|
31
|
+
* (rules marked `A`), which a test checks against this
|
|
32
|
+
* module.
|
|
33
|
+
*/
|
|
34
|
+
import type { ConfigObject, Plugin } from '@eslint/core';
|
|
35
|
+
/**
|
|
36
|
+
* Builds the `sh/agent-authored` config bound to `plugin`, which must be this
|
|
37
|
+
* package's default export (the preset registers it under `plugins.sh`).
|
|
38
|
+
*
|
|
39
|
+
* @internal
|
|
40
|
+
*/
|
|
41
|
+
export declare function createAgentAuthoredConfig(plugin: Plugin): ConfigObject;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { recommendedRules } from './recommended.js';
|
|
2
|
+
/**
|
|
3
|
+
* The rules `sh/agent-authored` enables: every `sh/recommended` rule, then
|
|
4
|
+
* the contract rules, all at `error`.
|
|
5
|
+
*/
|
|
6
|
+
const agentAuthoredRules = {
|
|
7
|
+
...recommendedRules,
|
|
8
|
+
'sh/no-secret-flags': 'error',
|
|
9
|
+
'sh/quote-expansions': 'error',
|
|
10
|
+
'sh/no-dynamic-invocation': 'error',
|
|
11
|
+
'sh/no-masked-exit-status': 'error',
|
|
12
|
+
'sh/max-script-complexity': 'error',
|
|
13
|
+
'sh/no-secret-env-reads': 'error',
|
|
14
|
+
'sh/no-undeclared-commands': 'error',
|
|
15
|
+
'sh/no-unused-declarations': 'error',
|
|
16
|
+
'sh/prefer-local': 'error',
|
|
17
|
+
'sh/require-network-wrapper': 'error',
|
|
18
|
+
'sh/require-tty-guard': 'error',
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Builds the `sh/agent-authored` config bound to `plugin`, which must be this
|
|
22
|
+
* package's default export (the preset registers it under `plugins.sh`).
|
|
23
|
+
*
|
|
24
|
+
* @internal
|
|
25
|
+
*/
|
|
26
|
+
export function createAgentAuthoredConfig(plugin) {
|
|
27
|
+
return {
|
|
28
|
+
name: 'sh/agent-authored',
|
|
29
|
+
plugins: { sh: plugin },
|
|
30
|
+
language: 'sh/sh',
|
|
31
|
+
rules: { ...agentAuthoredRules },
|
|
32
|
+
};
|
|
33
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `sh/bash-3.2` shared config: the preset for scripts that must run on the
|
|
3
|
+
* Bash 3.2 that macOS ships as `/bin/bash`.
|
|
4
|
+
*
|
|
5
|
+
* The parser accepts every construct its grammar knows whatever target is
|
|
6
|
+
* configured, so a Bash 3.2 target is enforced only by the `compat/*` rules.
|
|
7
|
+
* This preset names that target once (`languageOptions.shellVersion: '3.2'`)
|
|
8
|
+
* and turns on every version-gated rule that reads it, so a project states its
|
|
9
|
+
* deployment target as a config choice instead of enumerating rules.
|
|
10
|
+
*
|
|
11
|
+
* It follows the shape contract in `./recommended.ts`, with one deliberate
|
|
12
|
+
* extension: it carries `languageOptions`, because the target version is the
|
|
13
|
+
* preset's whole meaning and a pack that left it to the user would enable
|
|
14
|
+
* rules that report nothing. It still carries no `files` or `ignores`, so it
|
|
15
|
+
* inherits the user's globs, and it composes with the other presets through
|
|
16
|
+
* `extends: [sh.configs.recommended, sh.configs['bash-3.2']]`.
|
|
17
|
+
*
|
|
18
|
+
* Rule membership follows the **Configs** column of the rule catalog (rows
|
|
19
|
+
* marked `3.2`), which a test checks against this module.
|
|
20
|
+
*/
|
|
21
|
+
import type { ConfigObject, Plugin } from '@eslint/core';
|
|
22
|
+
/**
|
|
23
|
+
* Builds the `sh/bash-3.2` config bound to `plugin`, which must be this
|
|
24
|
+
* package's default export (the preset registers it under `plugins.sh`).
|
|
25
|
+
*
|
|
26
|
+
* @internal
|
|
27
|
+
*/
|
|
28
|
+
export declare function createBash32Config(plugin: Plugin): ConfigObject;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The rules `sh/bash-3.2` enables: every version-gated `compat/*` rule, all at
|
|
3
|
+
* `error`, keyed as a user writes them (`sh/<rule-name>`). Each reports a
|
|
4
|
+
* feature that Bash 3.2 lacks and names the portable alternative.
|
|
5
|
+
* `shebang-matches-dialect` is not a version gate and belongs to the
|
|
6
|
+
* presets whose catalog row names it. The POSIX-only gates
|
|
7
|
+
* (`compat/no-double-bracket`, `compat/no-let`, `compat/no-source`) are not
|
|
8
|
+
* version gates either: every Bash has those features, so they could never
|
|
9
|
+
* report here.
|
|
10
|
+
*
|
|
11
|
+
* @internal
|
|
12
|
+
*/
|
|
13
|
+
const bash32Rules = {
|
|
14
|
+
'sh/compat/no-append-all-redirect': 'error',
|
|
15
|
+
'sh/compat/no-associative-arrays': 'error',
|
|
16
|
+
'sh/compat/no-case-attributes': 'error',
|
|
17
|
+
'sh/compat/no-case-fallthrough': 'error',
|
|
18
|
+
'sh/compat/no-case-modification': 'error',
|
|
19
|
+
'sh/compat/no-coproc': 'error',
|
|
20
|
+
'sh/compat/no-declare-g': 'error',
|
|
21
|
+
'sh/compat/no-epoch-variables': 'error',
|
|
22
|
+
'sh/compat/no-fd-variable': 'error',
|
|
23
|
+
'sh/compat/no-globstar': 'error',
|
|
24
|
+
'sh/compat/no-lastpipe-semantics': 'error',
|
|
25
|
+
'sh/compat/no-mapfile': 'error',
|
|
26
|
+
'sh/compat/no-nameref': 'error',
|
|
27
|
+
'sh/compat/no-negative-array-index': 'error',
|
|
28
|
+
'sh/compat/no-nounset-empty-array': 'error',
|
|
29
|
+
'sh/compat/no-parameter-transformation': 'error',
|
|
30
|
+
'sh/compat/no-pipe-all': 'error',
|
|
31
|
+
'sh/compat/no-printf-time-format': 'error',
|
|
32
|
+
'sh/compat/no-test-v': 'error',
|
|
33
|
+
'sh/compat/no-unicode-escape': 'error',
|
|
34
|
+
'sh/compat/no-wait-n': 'error',
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Builds the `sh/bash-3.2` config bound to `plugin`, which must be this
|
|
38
|
+
* package's default export (the preset registers it under `plugins.sh`).
|
|
39
|
+
*
|
|
40
|
+
* @internal
|
|
41
|
+
*/
|
|
42
|
+
export function createBash32Config(plugin) {
|
|
43
|
+
return {
|
|
44
|
+
name: 'sh/bash-3.2',
|
|
45
|
+
plugins: { sh: plugin },
|
|
46
|
+
language: 'sh/sh',
|
|
47
|
+
languageOptions: { dialect: 'bash', shellVersion: '3.2' },
|
|
48
|
+
rules: { ...bash32Rules },
|
|
49
|
+
};
|
|
50
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `sh/posix-portable` shared config: the preset for scripts meant for a
|
|
3
|
+
* POSIX `sh` rather than for Bash.
|
|
4
|
+
*
|
|
5
|
+
* It sets `languageOptions.dialect: 'posix'`, which makes every Bash-only
|
|
6
|
+
* feature unavailable at any `shellVersion`, and enables the `compat/*` rules
|
|
7
|
+
* that report those features. The dialect is also what every dialect-aware
|
|
8
|
+
* rule reads, so the preset is one switch rather than a list of settings that
|
|
9
|
+
* could drift apart.
|
|
10
|
+
*
|
|
11
|
+
* The preset is not a proof of portability. Its rules report named constructs
|
|
12
|
+
* that really fail on dash, and the parser rejects some others; it deliberately
|
|
13
|
+
* leaves `local`, `echo -e` and the rest of shellcheck's SC3xxx catalog to
|
|
14
|
+
* shellcheck, which is why it does not promise "any POSIX `sh`". The scope and
|
|
15
|
+
* the shell behaviour behind it are stated for users in `docs/compat.md`.
|
|
16
|
+
*
|
|
17
|
+
* `require-strict-mode` is configured, not merely enabled. Its default under
|
|
18
|
+
* `posix` is already `set -eu`, but the preset states `mode: 'set -eu'`
|
|
19
|
+
* explicitly so that neither a later edit to that default nor an inherited
|
|
20
|
+
* `recommended` severity-only entry can make the pack demand `pipefail`,
|
|
21
|
+
* which POSIX gained only in POSIX.1-2024 and which dash and older BusyBox
|
|
22
|
+
* ash reject. A pack that asked for `pipefail` would make its own autofix
|
|
23
|
+
* break a working `#!/bin/sh` script.
|
|
24
|
+
*
|
|
25
|
+
* It follows the shape contract in `./recommended.ts`, extended with
|
|
26
|
+
* `languageOptions` for the reason given in `./bash-3-2.ts`. It composes
|
|
27
|
+
* through `extends: [sh.configs.recommended, sh.configs['posix-portable']]`;
|
|
28
|
+
* the later entry's `require-strict-mode` options win.
|
|
29
|
+
*
|
|
30
|
+
* Rule membership follows the **Configs** column of the rule catalog (rows
|
|
31
|
+
* marked `P`), which a test checks against this module.
|
|
32
|
+
*/
|
|
33
|
+
import type { ConfigObject, Plugin } from '@eslint/core';
|
|
34
|
+
/**
|
|
35
|
+
* Builds the `sh/posix-portable` config bound to `plugin`, which must be this
|
|
36
|
+
* package's default export (the preset registers it under `plugins.sh`).
|
|
37
|
+
*
|
|
38
|
+
* @internal
|
|
39
|
+
*/
|
|
40
|
+
export declare function createPosixPortableConfig(plugin: Plugin): ConfigObject;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The rules `sh/posix-portable` enables, all at `error`, keyed as a user
|
|
3
|
+
* writes them (`sh/<rule-name>`): every `compat/*` rule (each reports under
|
|
4
|
+
* `dialect: 'posix'` whatever the version), including the POSIX-only gates
|
|
5
|
+
* `bash-3.2` has no use for, the shebang/dialect check, and strict mode in its
|
|
6
|
+
* POSIX spelling.
|
|
7
|
+
*
|
|
8
|
+
* @internal
|
|
9
|
+
*/
|
|
10
|
+
const posixPortableRules = {
|
|
11
|
+
'sh/require-strict-mode': ['error', { mode: 'set -eu' }],
|
|
12
|
+
'sh/shebang-matches-dialect': 'error',
|
|
13
|
+
'sh/compat/no-append-all-redirect': 'error',
|
|
14
|
+
'sh/compat/no-associative-arrays': 'error',
|
|
15
|
+
'sh/compat/no-case-attributes': 'error',
|
|
16
|
+
'sh/compat/no-case-fallthrough': 'error',
|
|
17
|
+
'sh/compat/no-case-modification': 'error',
|
|
18
|
+
'sh/compat/no-coproc': 'error',
|
|
19
|
+
'sh/compat/no-declare-g': 'error',
|
|
20
|
+
'sh/compat/no-double-bracket': 'error',
|
|
21
|
+
'sh/compat/no-epoch-variables': 'error',
|
|
22
|
+
'sh/compat/no-fd-variable': 'error',
|
|
23
|
+
'sh/compat/no-globstar': 'error',
|
|
24
|
+
'sh/compat/no-lastpipe-semantics': 'error',
|
|
25
|
+
'sh/compat/no-let': 'error',
|
|
26
|
+
'sh/compat/no-mapfile': 'error',
|
|
27
|
+
'sh/compat/no-nameref': 'error',
|
|
28
|
+
'sh/compat/no-negative-array-index': 'error',
|
|
29
|
+
'sh/compat/no-nounset-empty-array': 'error',
|
|
30
|
+
'sh/compat/no-parameter-transformation': 'error',
|
|
31
|
+
'sh/compat/no-pipe-all': 'error',
|
|
32
|
+
'sh/compat/no-printf-time-format': 'error',
|
|
33
|
+
'sh/compat/no-source': 'error',
|
|
34
|
+
'sh/compat/no-test-v': 'error',
|
|
35
|
+
'sh/compat/no-unicode-escape': 'error',
|
|
36
|
+
'sh/compat/no-wait-n': 'error',
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* Builds the `sh/posix-portable` config bound to `plugin`, which must be this
|
|
40
|
+
* package's default export (the preset registers it under `plugins.sh`).
|
|
41
|
+
*
|
|
42
|
+
* @internal
|
|
43
|
+
*/
|
|
44
|
+
export function createPosixPortableConfig(plugin) {
|
|
45
|
+
return {
|
|
46
|
+
name: 'sh/posix-portable',
|
|
47
|
+
plugins: { sh: plugin },
|
|
48
|
+
language: 'sh/sh',
|
|
49
|
+
languageOptions: { dialect: 'posix' },
|
|
50
|
+
rules: { ...posixPortableRules },
|
|
51
|
+
};
|
|
52
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `sh/recommended` shared config: the preset most users extend to adopt
|
|
3
|
+
* eslint-sh.
|
|
4
|
+
*
|
|
5
|
+
* Its job is to take `language: 'sh/sh'` out of the user's hands. A config
|
|
6
|
+
* that forgets that key makes ESLint parse shell scripts as JavaScript, and
|
|
7
|
+
* none of this plugin's code runs to explain why. Extending this preset
|
|
8
|
+
* supplies the key, together with the plugin registration it resolves
|
|
9
|
+
* against.
|
|
10
|
+
*
|
|
11
|
+
* Shape contract (binding for every config this plugin ships):
|
|
12
|
+
*
|
|
13
|
+
* - A single config object, not an array, so it composes through
|
|
14
|
+
* `extends: [sh.configs.recommended, ...]` and through object spread.
|
|
15
|
+
* - No `files`, `ignores` or `languageOptions`. ESLint intersects an extended
|
|
16
|
+
* config's `files` with the extending entry's `files`, so a preset that
|
|
17
|
+
* carried its own globs would silently drop any path the user matched
|
|
18
|
+
* outside them (extensionless scripts such as `scripts/deploy` in
|
|
19
|
+
* particular). Without `files`, the preset inherits exactly the globs the
|
|
20
|
+
* user wrote. Shell paths are the user's knowledge; the preset never
|
|
21
|
+
* guesses them.
|
|
22
|
+
* - The plugin is registered under the key `sh`, because `language: 'sh/sh'`
|
|
23
|
+
* resolves against the config's plugin key, not `meta.namespace`. The
|
|
24
|
+
* registered value is the plugin's own default export, so a user who also
|
|
25
|
+
* writes `plugins: { sh }` registers the identical object and ESLint
|
|
26
|
+
* accepts it.
|
|
27
|
+
*
|
|
28
|
+
* Rule membership follows the **Configs** column of the rule catalog
|
|
29
|
+
* (rules marked `R`): safe defaults that assume no project
|
|
30
|
+
* conventions. A rule joins this preset only by appearing in that column; a
|
|
31
|
+
* test parses the catalog and fails when the two drift apart.
|
|
32
|
+
*/
|
|
33
|
+
import type { ConfigObject, Plugin, RulesConfig } from '@eslint/core';
|
|
34
|
+
/**
|
|
35
|
+
* The rules `sh/recommended` enables, with their severities, keyed as a user
|
|
36
|
+
* writes them (`sh/<rule-name>`).
|
|
37
|
+
*
|
|
38
|
+
* `no-secret-flags` is a heuristic screen with documented false positives, and
|
|
39
|
+
* `quote-expansions` duplicates shellcheck's SC2086 for teams that also run
|
|
40
|
+
* shellcheck, so both report at `warn` here; every other member is `error`. `agent-authored`
|
|
41
|
+
* extends this record, so a rule added here is enabled there too.
|
|
42
|
+
*
|
|
43
|
+
* @internal
|
|
44
|
+
*/
|
|
45
|
+
export declare const recommendedRules: Readonly<RulesConfig>;
|
|
46
|
+
/**
|
|
47
|
+
* Builds the `sh/recommended` config bound to `plugin`, which must be this
|
|
48
|
+
* package's default export (the preset registers it under `plugins.sh`).
|
|
49
|
+
*
|
|
50
|
+
* @internal
|
|
51
|
+
*/
|
|
52
|
+
export declare function createRecommendedConfig(plugin: Plugin): ConfigObject;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The rules `sh/recommended` enables, with their severities, keyed as a user
|
|
3
|
+
* writes them (`sh/<rule-name>`).
|
|
4
|
+
*
|
|
5
|
+
* `no-secret-flags` is a heuristic screen with documented false positives, and
|
|
6
|
+
* `quote-expansions` duplicates shellcheck's SC2086 for teams that also run
|
|
7
|
+
* shellcheck, so both report at `warn` here; every other member is `error`. `agent-authored`
|
|
8
|
+
* extends this record, so a rule added here is enabled there too.
|
|
9
|
+
*
|
|
10
|
+
* @internal
|
|
11
|
+
*/
|
|
12
|
+
export const recommendedRules = {
|
|
13
|
+
'sh/require-strict-mode': 'error',
|
|
14
|
+
'sh/require-shebang': 'error',
|
|
15
|
+
'sh/no-eval': 'error',
|
|
16
|
+
'sh/no-secret-flags': 'warn',
|
|
17
|
+
'sh/no-unchecked-cd': 'error',
|
|
18
|
+
'sh/shebang-matches-dialect': 'error',
|
|
19
|
+
'sh/quote-expansions': 'warn',
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Builds the `sh/recommended` config bound to `plugin`, which must be this
|
|
23
|
+
* package's default export (the preset registers it under `plugins.sh`).
|
|
24
|
+
*
|
|
25
|
+
* @internal
|
|
26
|
+
*/
|
|
27
|
+
export function createRecommendedConfig(plugin) {
|
|
28
|
+
return {
|
|
29
|
+
name: 'sh/recommended',
|
|
30
|
+
plugins: { sh: plugin },
|
|
31
|
+
language: 'sh/sh',
|
|
32
|
+
rules: { ...recommendedRules },
|
|
33
|
+
};
|
|
34
|
+
}
|