@eslint-sh/eslint-plugin 0.5.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +228 -17
- package/dist/command-harvest/builtins/bash.d.ts +36 -0
- package/dist/command-harvest/builtins/bash.js +98 -0
- package/dist/command-harvest/builtins/bats.d.ts +45 -0
- package/dist/command-harvest/builtins/bats.js +33 -0
- package/dist/command-harvest/builtins/index.d.ts +18 -0
- package/dist/command-harvest/builtins/index.js +39 -0
- package/dist/command-harvest/builtins/posix.d.ts +51 -0
- package/dist/command-harvest/builtins/posix.js +93 -0
- package/dist/command-harvest/harvest-commands.d.ts +66 -0
- package/dist/command-harvest/harvest-commands.js +97 -0
- package/dist/command-harvest/wrapper-table.d.ts +24 -0
- package/dist/command-harvest/wrapper-table.js +49 -0
- package/dist/configs/agent-authored.d.ts +41 -0
- package/dist/configs/agent-authored.js +33 -0
- package/dist/configs/bash-3-2.d.ts +28 -0
- package/dist/configs/bash-3-2.js +51 -0
- package/dist/configs/posix-portable.d.ts +40 -0
- package/dist/configs/posix-portable.js +53 -0
- package/dist/configs/recommended.d.ts +52 -0
- package/dist/configs/recommended.js +34 -0
- package/dist/declaration-conventions/comment-pragma.d.ts +33 -0
- package/dist/declaration-conventions/comment-pragma.js +153 -0
- package/dist/declaration-conventions/declaration-convention.d.ts +189 -0
- package/dist/declaration-conventions/declaration-convention.js +1 -0
- package/dist/declaration-conventions/read-declared-commands.d.ts +70 -0
- package/dist/declaration-conventions/read-declared-commands.js +128 -0
- package/dist/eslint-plugin-syntax-analyze.d.ts +23 -0
- package/dist/eslint-plugin-syntax.d.ts +27 -0
- package/dist/eslint-plugin.d.ts +1211 -52
- package/dist/index.d.ts +56 -8
- package/dist/index.js +53 -6
- package/dist/language/command-resolution.d.ts +124 -0
- package/dist/language/command-resolution.js +120 -0
- package/dist/language/end-of-input-marker.d.ts +29 -0
- package/dist/language/end-of-input-marker.js +88 -0
- package/dist/language/line-breaks.d.ts +18 -0
- package/dist/language/line-breaks.js +22 -0
- package/dist/language/lint-view-types.d.ts +104 -0
- package/dist/language/lint-view-types.js +1 -0
- package/dist/language/non-shell-file-guard.d.ts +35 -0
- package/dist/language/non-shell-file-guard.js +59 -0
- package/dist/language/selector-classes.d.ts +0 -1
- package/dist/language/selector-classes.js +0 -1
- package/dist/language/shell-language-options.d.ts +7 -1
- package/dist/language/shell-language-options.js +4 -4
- package/dist/language/shell-language.d.ts +10 -2
- package/dist/language/shell-language.js +59 -8
- package/dist/language/shell-source-code.d.ts +342 -5
- package/dist/language/shell-source-code.js +559 -30
- package/dist/language/source-coordinates.d.ts +13 -2
- package/dist/language/source-coordinates.js +68 -14
- package/dist/language/syntax-view.d.ts +60 -39
- package/dist/language/syntax-view.js +200 -45
- package/dist/language/word-resolution.d.ts +4 -3
- package/dist/language/word-resolution.js +4 -3
- package/dist/rules/compat/create-compat-rule.d.ts +59 -0
- package/dist/rules/compat/create-compat-rule.js +65 -0
- package/dist/rules/compat/declaration-options.d.ts +19 -0
- package/dist/rules/compat/declaration-options.js +110 -0
- package/dist/rules/compat/no-append-all-redirect.d.ts +13 -0
- package/dist/rules/compat/no-append-all-redirect.js +73 -0
- package/dist/rules/compat/no-associative-arrays.d.ts +12 -0
- package/dist/rules/compat/no-associative-arrays.js +39 -0
- package/dist/rules/compat/no-brace-sequence-padding-step.d.ts +15 -0
- package/dist/rules/compat/no-brace-sequence-padding-step.js +287 -0
- package/dist/rules/compat/no-case-attributes.d.ts +12 -0
- package/dist/rules/compat/no-case-attributes.js +63 -0
- package/dist/rules/compat/no-case-fallthrough.d.ts +12 -0
- package/dist/rules/compat/no-case-fallthrough.js +67 -0
- package/dist/rules/compat/no-case-modification.d.ts +35 -0
- package/dist/rules/compat/no-case-modification.js +86 -0
- package/dist/rules/compat/no-coproc.d.ts +12 -0
- package/dist/rules/compat/no-coproc.js +47 -0
- package/dist/rules/compat/no-declare-g.d.ts +12 -0
- package/dist/rules/compat/no-declare-g.js +59 -0
- package/dist/rules/compat/no-double-bracket.d.ts +12 -0
- package/dist/rules/compat/no-double-bracket.js +52 -0
- package/dist/rules/compat/no-epoch-variables.d.ts +12 -0
- package/dist/rules/compat/no-epoch-variables.js +57 -0
- package/dist/rules/compat/no-fd-variable.d.ts +12 -0
- package/dist/rules/compat/no-fd-variable.js +48 -0
- package/dist/rules/compat/no-globstar.d.ts +12 -0
- package/dist/rules/compat/no-globstar.js +39 -0
- package/dist/rules/compat/no-lastpipe-semantics.d.ts +12 -0
- package/dist/rules/compat/no-lastpipe-semantics.js +53 -0
- package/dist/rules/compat/no-let.d.ts +12 -0
- package/dist/rules/compat/no-let.js +62 -0
- package/dist/rules/compat/no-mapfile.d.ts +13 -0
- package/dist/rules/compat/no-mapfile.js +122 -0
- package/dist/rules/compat/no-nameref.d.ts +12 -0
- package/dist/rules/compat/no-nameref.js +44 -0
- package/dist/rules/compat/no-negative-array-index.d.ts +13 -0
- package/dist/rules/compat/no-negative-array-index.js +123 -0
- package/dist/rules/compat/no-nounset-empty-array.d.ts +13 -0
- package/dist/rules/compat/no-nounset-empty-array.js +158 -0
- package/dist/rules/compat/no-parameter-transformation.d.ts +13 -0
- package/dist/rules/compat/no-parameter-transformation.js +123 -0
- package/dist/rules/compat/no-pipe-all.d.ts +13 -0
- package/dist/rules/compat/no-pipe-all.js +68 -0
- package/dist/rules/compat/no-printf-time-format.d.ts +12 -0
- package/dist/rules/compat/no-printf-time-format.js +99 -0
- package/dist/rules/compat/no-source.d.ts +13 -0
- package/dist/rules/compat/no-source.js +103 -0
- package/dist/rules/compat/no-test-v.d.ts +12 -0
- package/dist/rules/compat/no-test-v.js +280 -0
- package/dist/rules/compat/no-unicode-escape.d.ts +14 -0
- package/dist/rules/compat/no-unicode-escape.js +120 -0
- package/dist/rules/compat/no-wait-n.d.ts +12 -0
- package/dist/rules/compat/no-wait-n.js +73 -0
- package/dist/rules/compat/operator-range.d.ts +10 -0
- package/dist/rules/compat/operator-range.js +15 -0
- package/dist/rules/compat/posix-absent-command.d.ts +12 -0
- package/dist/rules/compat/posix-absent-command.js +134 -0
- package/dist/rules/compat/shopt-options.d.ts +9 -0
- package/dist/rules/compat/shopt-options.js +63 -0
- package/dist/rules/compat/version-gates.d.ts +76 -0
- package/dist/rules/compat/version-gates.js +205 -0
- package/dist/rules/create-rule.d.ts +91 -0
- package/dist/rules/create-rule.js +51 -0
- package/dist/rules/create-wrapper-rule.d.ts +166 -0
- package/dist/rules/create-wrapper-rule.js +407 -0
- package/dist/rules/define-shell-rule.d.ts +184 -0
- package/dist/rules/define-shell-rule.js +56 -0
- package/dist/rules/index.d.ts +26 -0
- package/dist/rules/index.js +90 -0
- package/dist/rules/max-script-complexity.d.ts +28 -0
- package/dist/rules/max-script-complexity.js +130 -0
- package/dist/rules/no-dynamic-invocation.d.ts +35 -0
- package/dist/rules/no-dynamic-invocation.js +117 -0
- package/dist/rules/no-eval.d.ts +13 -0
- package/dist/rules/no-eval.js +89 -0
- package/dist/rules/no-masked-exit-status.d.ts +21 -0
- package/dist/rules/no-masked-exit-status.js +667 -0
- package/dist/rules/no-secret-env-reads.d.ts +24 -0
- package/dist/rules/no-secret-env-reads.js +133 -0
- package/dist/rules/no-secret-flags.d.ts +32 -0
- package/dist/rules/no-secret-flags.js +162 -0
- package/dist/rules/no-unchecked-cd.d.ts +44 -0
- package/dist/rules/no-unchecked-cd.js +250 -0
- package/dist/rules/no-undeclared-commands.d.ts +20 -0
- package/dist/rules/no-undeclared-commands.js +115 -0
- package/dist/rules/no-unused-declarations.d.ts +23 -0
- package/dist/rules/no-unused-declarations.js +259 -0
- package/dist/rules/prefer-local.d.ts +16 -0
- package/dist/rules/prefer-local.js +249 -0
- package/dist/rules/quote-expansions.d.ts +13 -0
- package/dist/rules/quote-expansions.js +229 -0
- package/dist/rules/require-network-wrapper.d.ts +40 -0
- package/dist/rules/require-network-wrapper.js +244 -0
- package/dist/rules/require-shebang.d.ts +29 -0
- package/dist/rules/require-shebang.js +101 -0
- package/dist/rules/require-strict-mode.d.ts +52 -0
- package/dist/rules/require-strict-mode.js +177 -0
- package/dist/rules/require-tty-guard.d.ts +13 -0
- package/dist/rules/require-tty-guard.js +318 -0
- package/dist/rules/resolve-declaration-convention.d.ts +96 -0
- package/dist/rules/resolve-declaration-convention.js +109 -0
- package/dist/rules/resolve-network-wrapper.d.ts +19 -0
- package/dist/rules/resolve-network-wrapper.js +116 -0
- package/dist/rules/set-options.d.ts +109 -0
- package/dist/rules/set-options.js +201 -0
- package/dist/rules/shebang-interpreter.d.ts +18 -0
- package/dist/rules/shebang-interpreter.js +81 -0
- package/dist/rules/shebang-matches-dialect.d.ts +12 -0
- package/dist/rules/shebang-matches-dialect.js +69 -0
- package/dist/rules/transparent-wrappers.d.ts +52 -0
- package/dist/rules/transparent-wrappers.js +99 -0
- package/dist/rules/user-pattern.d.ts +63 -0
- package/dist/rules/user-pattern.js +184 -0
- package/dist/syntax/analyze.d.ts +19 -0
- package/dist/syntax/analyze.js +21 -0
- package/dist/syntax/index.d.ts +23 -0
- package/dist/syntax/index.js +25 -0
- package/dist/version.d.ts +0 -1
- package/dist/version.js +0 -1
- package/docs/compat.md +354 -0
- package/docs/custom-rule-tutorial.md +780 -34
- package/docs/declaration-conventions.md +478 -0
- package/docs/examples/audit-wrapper-pack.mjs +15 -0
- package/docs/examples/dep-line-convention.mjs +22 -0
- package/docs/examples/eslint.config.mjs +3 -2
- package/docs/examples/no-and-or-as-if-rule.mjs +61 -0
- package/docs/examples/no-backticks-rule-tester.mjs +106 -0
- package/docs/examples/no-backticks-rule.mjs +60 -0
- package/docs/examples/no-curl-in-functions-rule.mjs +53 -0
- package/docs/examples/no-function-keyword-rule.mjs +59 -0
- package/docs/examples/no-listed-commands-rule.ts +63 -0
- package/docs/examples/no-rm-rf-rule.mjs +26 -15
- package/docs/examples/no-unquoted-rm-variable-rule.ts +54 -0
- package/docs/examples/sidecar-convention.mjs +38 -0
- package/docs/examples/sidecar-snapshot-convention.mjs +26 -0
- package/docs/examples/sorted-declarations-rule.mjs +52 -0
- package/docs/reading-nodes.md +159 -0
- package/docs/rules/README.md +79 -0
- package/docs/rules/_template.md +83 -0
- package/docs/rules/compat/no-append-all-redirect.md +87 -0
- package/docs/rules/compat/no-associative-arrays.md +95 -0
- package/docs/rules/compat/no-brace-sequence-padding-step.md +90 -0
- package/docs/rules/compat/no-case-attributes.md +82 -0
- package/docs/rules/compat/no-case-fallthrough.md +90 -0
- package/docs/rules/compat/no-case-modification.md +74 -0
- package/docs/rules/compat/no-coproc.md +76 -0
- package/docs/rules/compat/no-declare-g.md +80 -0
- package/docs/rules/compat/no-double-bracket.md +72 -0
- package/docs/rules/compat/no-epoch-variables.md +79 -0
- package/docs/rules/compat/no-fd-variable.md +78 -0
- package/docs/rules/compat/no-globstar.md +74 -0
- package/docs/rules/compat/no-lastpipe-semantics.md +73 -0
- package/docs/rules/compat/no-let.md +78 -0
- package/docs/rules/compat/no-mapfile.md +118 -0
- package/docs/rules/compat/no-nameref.md +81 -0
- package/docs/rules/compat/no-negative-array-index.md +87 -0
- package/docs/rules/compat/no-nounset-empty-array.md +103 -0
- package/docs/rules/compat/no-parameter-transformation.md +94 -0
- package/docs/rules/compat/no-pipe-all.md +85 -0
- package/docs/rules/compat/no-printf-time-format.md +75 -0
- package/docs/rules/compat/no-source.md +98 -0
- package/docs/rules/compat/no-test-v.md +82 -0
- package/docs/rules/compat/no-unicode-escape.md +91 -0
- package/docs/rules/compat/no-wait-n.md +85 -0
- package/docs/rules/max-script-complexity.md +109 -0
- package/docs/rules/no-dynamic-invocation.md +139 -0
- package/docs/rules/no-eval.md +106 -0
- package/docs/rules/no-masked-exit-status.md +239 -0
- package/docs/rules/no-secret-env-reads.md +173 -0
- package/docs/rules/no-secret-flags.md +169 -0
- package/docs/rules/no-unchecked-cd.md +146 -0
- package/docs/rules/no-undeclared-commands.md +141 -0
- package/docs/rules/no-unused-declarations.md +216 -0
- package/docs/rules/prefer-local.md +221 -0
- package/docs/rules/quote-expansions.md +157 -0
- package/docs/rules/require-network-wrapper.md +171 -0
- package/docs/rules/require-shebang.md +157 -0
- package/docs/rules/require-strict-mode.md +253 -0
- package/docs/rules/require-tty-guard.md +131 -0
- package/docs/rules/shebang-matches-dialect.md +120 -0
- package/docs/selectors.md +2 -2
- package/docs/shellcheck.md +87 -0
- package/docs/untrusted-code.md +440 -0
- package/docs/wrapper-rules.md +206 -0
- package/package.json +26 -20
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/language/selector-classes.d.ts.map +0 -1
- package/dist/language/selector-classes.js.map +0 -1
- package/dist/language/shell-language-options.d.ts.map +0 -1
- package/dist/language/shell-language-options.js.map +0 -1
- package/dist/language/shell-language.d.ts.map +0 -1
- package/dist/language/shell-language.js.map +0 -1
- package/dist/language/shell-source-code.d.ts.map +0 -1
- package/dist/language/shell-source-code.js.map +0 -1
- package/dist/language/source-coordinates.d.ts.map +0 -1
- package/dist/language/source-coordinates.js.map +0 -1
- package/dist/language/syntax-view.d.ts.map +0 -1
- package/dist/language/syntax-view.js.map +0 -1
- package/dist/language/word-resolution.d.ts.map +0 -1
- package/dist/language/word-resolution.js.map +0 -1
- package/dist/version.d.ts.map +0 -1
- package/dist/version.js.map +0 -1
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `sh/compat/no-declare-g`: reports `declare -g` and `typeset -g` in files
|
|
3
|
+
* whose target shell lacks the `-g` option, that is, Bash older than 4.2
|
|
4
|
+
* (macOS's stock `/bin/bash` is 3.2) and POSIX sh.
|
|
5
|
+
*
|
|
6
|
+
* Bash 3.2 rejects `-g` as an invalid option, so the declaration fails and the
|
|
7
|
+
* variable is never created. The message suggests the portable alternative:
|
|
8
|
+
* in Bash before 4.2 a plain `name=value` assignment inside a function already
|
|
9
|
+
* sets the global, as long as the function has not declared the name `local`.
|
|
10
|
+
*
|
|
11
|
+
* What is read: `declare`/`typeset` option clusters containing `g` (`-g`,
|
|
12
|
+
* `-gx`, `-Ag`), found as `declarationFlagVisitor` defines. `local` and
|
|
13
|
+
* `export` are outside this rule's scope.
|
|
14
|
+
*/
|
|
15
|
+
import { createCompatRule } from './create-compat-rule.js';
|
|
16
|
+
import { declarationFlagVisitor } from './declaration-options.js';
|
|
17
|
+
/**
|
|
18
|
+
* Whether a plain assignment can stand in for a command's options, which
|
|
19
|
+
* contain `g`: only when every option word is a `-` cluster of `g` and `x`
|
|
20
|
+
* letters (`-g`, `-gx`, `-g -x`), since `export name=value` expresses `x`. Any
|
|
21
|
+
* other option sets an attribute (`-A`, `-i`, `-r`, …) that an assignment
|
|
22
|
+
* cannot, whether it is joined to the `g` (`-Ag`) or a word of its own
|
|
23
|
+
* (`-g -A`).
|
|
24
|
+
*/
|
|
25
|
+
function isPlainAssignmentEquivalent(options) {
|
|
26
|
+
return options.every((option) => /^-[gx]+$/u.test(option));
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* The `sh/compat/no-declare-g` rule module.
|
|
30
|
+
*
|
|
31
|
+
* @internal
|
|
32
|
+
*/
|
|
33
|
+
export const noDeclareG = createCompatRule({
|
|
34
|
+
name: 'compat/no-declare-g',
|
|
35
|
+
feature: 'declareGlobal',
|
|
36
|
+
meta: {
|
|
37
|
+
type: 'problem',
|
|
38
|
+
docs: {
|
|
39
|
+
description: 'Disallow `declare -g` and `typeset -g` when the target shell lacks the option',
|
|
40
|
+
},
|
|
41
|
+
messages: {
|
|
42
|
+
declareGlobal: '`{{command}} -g` needs {{required}}, but this file targets {{target}}. Assign the global with a plain `name=value` (a variable not declared `local` is global), or `export name=value` to also export it.',
|
|
43
|
+
declareGlobalWithAttributes: '`{{command}} {{flags}}` needs {{required}}, but this file targets {{target}}. A plain `name=value` cannot express its other options, so dropping `g` is not enough: declare the variable outside any function, or raise the target.',
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
create(context, gate) {
|
|
47
|
+
return declarationFlagVisitor(context.sourceCode, 'g', (node, command, options) => {
|
|
48
|
+
if (!isPlainAssignmentEquivalent(options)) {
|
|
49
|
+
context.report({
|
|
50
|
+
node,
|
|
51
|
+
messageId: 'declareGlobalWithAttributes',
|
|
52
|
+
data: { ...gate, command, flags: options.join(' ') },
|
|
53
|
+
});
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
context.report({ node, messageId: 'declareGlobal', data: { ...gate, command } });
|
|
57
|
+
});
|
|
58
|
+
},
|
|
59
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Message ids this rule reports with.
|
|
3
|
+
*
|
|
4
|
+
* @internal
|
|
5
|
+
*/
|
|
6
|
+
export type NoDoubleBracketMessageId = 'doubleBracket';
|
|
7
|
+
/**
|
|
8
|
+
* The `sh/compat/no-double-bracket` rule module.
|
|
9
|
+
*
|
|
10
|
+
* @internal
|
|
11
|
+
*/
|
|
12
|
+
export declare const noDoubleBracket: import("../define-shell-rule.js").ShellRule<[], "doubleBracket">;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `sh/compat/no-double-bracket`: reports `[[ … ]]` in files that target POSIX
|
|
3
|
+
* sh. Every Bash has it, so this is a POSIX-only gate.
|
|
4
|
+
*
|
|
5
|
+
* POSIX section 2.4 leaves the results of `[[` and `]]` unspecified. dash does
|
|
6
|
+
* not treat `[[` as a word of its grammar: it looks up a command by that name
|
|
7
|
+
* and fails with `[[: not found` and status 127. Under `set -e` a bare test
|
|
8
|
+
* stops the script, and in an `if` condition the failure is silent in effect:
|
|
9
|
+
* dash takes the `else` branch. `bash --posix` and BusyBox ash accept it, so
|
|
10
|
+
* only some POSIX shells show the problem.
|
|
11
|
+
*
|
|
12
|
+
* What is read: a command whose effective name is `[[`, because the POSIX
|
|
13
|
+
* parser has no conditional clause, outside a branch for one shell (see
|
|
14
|
+
* `./posix-absent-command.ts`). The expression is not interpreted, and
|
|
15
|
+
* the parser ends the command at `&&` or `||`, so one finding is reported per
|
|
16
|
+
* `[[`, on that word. A `[[` the parser cannot read as a command (`=~` with a
|
|
17
|
+
* parenthesis, `[[ ( a ) ]]`) is a parse error and never reaches this rule.
|
|
18
|
+
*
|
|
19
|
+
* No suggestion: `[` splits and globs its unquoted operands and `==` and `=~`
|
|
20
|
+
* have no `[` spelling, so no rewrite keeps the behaviour in general.
|
|
21
|
+
*/
|
|
22
|
+
import { createCompatRule } from './create-compat-rule.js';
|
|
23
|
+
import { reachableCommandWord } from './posix-absent-command.js';
|
|
24
|
+
/**
|
|
25
|
+
* The `sh/compat/no-double-bracket` rule module.
|
|
26
|
+
*
|
|
27
|
+
* @internal
|
|
28
|
+
*/
|
|
29
|
+
export const noDoubleBracket = createCompatRule({
|
|
30
|
+
name: 'compat/no-double-bracket',
|
|
31
|
+
feature: 'doubleBracket',
|
|
32
|
+
meta: {
|
|
33
|
+
type: 'problem',
|
|
34
|
+
docs: {
|
|
35
|
+
description: 'Disallow `[[ … ]]` when the target shell is POSIX sh',
|
|
36
|
+
},
|
|
37
|
+
messages: {
|
|
38
|
+
doubleBracket: '`[[` is not part of {{target}}. dash fails with `[[: not found`, and an `if` on it takes its `else` branch. Use `[ … ]` for tests and `case` for pattern matching.',
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
create(context, gate) {
|
|
42
|
+
const { sourceCode } = context;
|
|
43
|
+
return {
|
|
44
|
+
CallExpr(node) {
|
|
45
|
+
const word = reachableCommandWord(sourceCode, node, '[[');
|
|
46
|
+
if (word === undefined)
|
|
47
|
+
return;
|
|
48
|
+
context.report({ node: word, messageId: 'doubleBracket', data: { target: gate.target } });
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
},
|
|
52
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Message ids this rule reports with.
|
|
3
|
+
*
|
|
4
|
+
* @internal
|
|
5
|
+
*/
|
|
6
|
+
export type NoEpochVariablesMessageId = 'epochSeconds' | 'epochRealtime';
|
|
7
|
+
/**
|
|
8
|
+
* The `sh/compat/no-epoch-variables` rule module.
|
|
9
|
+
*
|
|
10
|
+
* @internal
|
|
11
|
+
*/
|
|
12
|
+
export declare const noEpochVariables: import("../define-shell-rule.js").ShellRule<[], NoEpochVariablesMessageId>;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { createCompatRule } from './create-compat-rule.js';
|
|
2
|
+
/** The two variables, and the message each reports with. */
|
|
3
|
+
const EPOCH_VARIABLES = new Map([
|
|
4
|
+
['EPOCHSECONDS', 'epochSeconds'],
|
|
5
|
+
['EPOCHREALTIME', 'epochRealtime'],
|
|
6
|
+
]);
|
|
7
|
+
/** The arithmetic nodes whose `Word` operands name variables. */
|
|
8
|
+
const ARITHMETIC_PARENTS = new Set([
|
|
9
|
+
'ArithmCmd',
|
|
10
|
+
'ArithmExp',
|
|
11
|
+
'BinaryArithm',
|
|
12
|
+
'UnaryArithm',
|
|
13
|
+
'ParenArithm',
|
|
14
|
+
'CStyleLoop',
|
|
15
|
+
]);
|
|
16
|
+
/**
|
|
17
|
+
* The `sh/compat/no-epoch-variables` rule module.
|
|
18
|
+
*
|
|
19
|
+
* @internal
|
|
20
|
+
*/
|
|
21
|
+
export const noEpochVariables = createCompatRule({
|
|
22
|
+
name: 'compat/no-epoch-variables',
|
|
23
|
+
feature: 'epochVariables',
|
|
24
|
+
meta: {
|
|
25
|
+
type: 'problem',
|
|
26
|
+
docs: {
|
|
27
|
+
description: 'Disallow `$EPOCHSECONDS` and `$EPOCHREALTIME` when the target shell lacks them',
|
|
28
|
+
},
|
|
29
|
+
messages: {
|
|
30
|
+
epochSeconds: '`$EPOCHSECONDS` needs {{required}}, but this file targets {{target}}, where it is unset. Use `$(date +%s)` instead (GNU, BSD and BusyBox `date`; POSIX `date` has no `%s`).',
|
|
31
|
+
epochRealtime: '`$EPOCHREALTIME` needs {{required}}, but this file targets {{target}}, where it is unset. `$(date +%s)` gives whole seconds (GNU, BSD and BusyBox `date`); there is no portable source of the fraction.',
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
create(context, gate) {
|
|
35
|
+
const { sourceCode } = context;
|
|
36
|
+
/** Reports `node` if `name` is one of the two variables. */
|
|
37
|
+
function check(node, name) {
|
|
38
|
+
const messageId = name === undefined ? undefined : EPOCH_VARIABLES.get(name);
|
|
39
|
+
if (messageId !== undefined)
|
|
40
|
+
context.report({ node, messageId, data: { ...gate } });
|
|
41
|
+
}
|
|
42
|
+
return {
|
|
43
|
+
ParamExp(node) {
|
|
44
|
+
check(node, node.param?.value);
|
|
45
|
+
},
|
|
46
|
+
Word(node) {
|
|
47
|
+
const parent = sourceCode.getParent(node);
|
|
48
|
+
if (parent === undefined || !ARITHMETIC_PARENTS.has(parent.type))
|
|
49
|
+
return;
|
|
50
|
+
const [part] = node.parts;
|
|
51
|
+
if (node.parts.length !== 1 || part.type !== 'Lit')
|
|
52
|
+
return;
|
|
53
|
+
check(node, part.value);
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
},
|
|
57
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Message ids this rule reports with.
|
|
3
|
+
*
|
|
4
|
+
* @internal
|
|
5
|
+
*/
|
|
6
|
+
export type NoFdVariableMessageId = 'fdVariable';
|
|
7
|
+
/**
|
|
8
|
+
* The `sh/compat/no-fd-variable` rule module.
|
|
9
|
+
*
|
|
10
|
+
* @internal
|
|
11
|
+
*/
|
|
12
|
+
export declare const noFdVariable: import("../define-shell-rule.js").ShellRule<[], "fdVariable">;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `sh/compat/no-fd-variable`: reports a `{varname}` redirection, which has
|
|
3
|
+
* the shell pick a free file descriptor and store its number in the
|
|
4
|
+
* variable (`exec {fd}>file`), in files whose target shell lacks it, that is,
|
|
5
|
+
* Bash older than 4.1 (macOS's stock `/bin/bash` is 3.2) and POSIX sh.
|
|
6
|
+
*
|
|
7
|
+
* Bash 4.0 and older treat `{fd}` as an ordinary word: `exec {fd}>out` tries
|
|
8
|
+
* to run a command named `{fd}`, which fails. The message suggests the
|
|
9
|
+
* portable alternative: use a fixed descriptor number, such as
|
|
10
|
+
* `exec 3>file`, `>&3`, then `exec 3>&-`.
|
|
11
|
+
*
|
|
12
|
+
* What is read: a `Redirect` whose descriptor is written as `{name}`, which
|
|
13
|
+
* the parser records separately from the redirection's target; a numbered
|
|
14
|
+
* descriptor (`3>file`) and a `{…}` word anywhere else (an argument, a brace
|
|
15
|
+
* group, quoted text) are not reported. The report is on the whole
|
|
16
|
+
* redirection.
|
|
17
|
+
*/
|
|
18
|
+
import { createCompatRule } from './create-compat-rule.js';
|
|
19
|
+
/** A `{varname}` descriptor as the parser records it. */
|
|
20
|
+
const FD_VARIABLE = /^\{([A-Za-z_][A-Za-z0-9_]*)\}$/u;
|
|
21
|
+
/**
|
|
22
|
+
* The `sh/compat/no-fd-variable` rule module.
|
|
23
|
+
*
|
|
24
|
+
* @internal
|
|
25
|
+
*/
|
|
26
|
+
export const noFdVariable = createCompatRule({
|
|
27
|
+
name: 'compat/no-fd-variable',
|
|
28
|
+
feature: 'fdVariable',
|
|
29
|
+
meta: {
|
|
30
|
+
type: 'problem',
|
|
31
|
+
docs: {
|
|
32
|
+
description: 'Disallow `{varname}` file-descriptor redirections (`exec {fd}>file`) when the target shell lacks them',
|
|
33
|
+
},
|
|
34
|
+
messages: {
|
|
35
|
+
fdVariable: '`{{{name}}}` descriptor allocation needs {{required}}, but this file targets {{target}}. Use a fixed descriptor number instead, for example `exec 3>file`, then `>&3`, then `exec 3>&-`.',
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
create(context, gate) {
|
|
39
|
+
return {
|
|
40
|
+
Redirect(node) {
|
|
41
|
+
const match = node.n === undefined ? null : FD_VARIABLE.exec(node.n.value);
|
|
42
|
+
if (match === null)
|
|
43
|
+
return;
|
|
44
|
+
context.report({ node, messageId: 'fdVariable', data: { ...gate, name: match[1] } });
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
},
|
|
48
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Message ids this rule reports with.
|
|
3
|
+
*
|
|
4
|
+
* @internal
|
|
5
|
+
*/
|
|
6
|
+
export type NoGlobstarMessageId = 'globstar';
|
|
7
|
+
/**
|
|
8
|
+
* The `sh/compat/no-globstar` rule module.
|
|
9
|
+
*
|
|
10
|
+
* @internal
|
|
11
|
+
*/
|
|
12
|
+
export declare const noGlobstar: import("../define-shell-rule.js").ShellRule<[], "globstar">;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `sh/compat/no-globstar`: reports `shopt -s globstar` in files whose target
|
|
3
|
+
* shell lacks the option, that is, Bash older than 4.0 (macOS's stock
|
|
4
|
+
* `/bin/bash` is 3.2) and POSIX sh.
|
|
5
|
+
*
|
|
6
|
+
* Bash 3.2 has no such option: `shopt` prints "invalid shell option name"
|
|
7
|
+
* and the script carries on, and `**` then matches like `*`, so a recursive
|
|
8
|
+
* glob silently matches fewer files (often none). The message suggests the
|
|
9
|
+
* portable alternative: search the tree with `find`.
|
|
10
|
+
*
|
|
11
|
+
* What is read: a `shopt -s` command naming `globstar`, as
|
|
12
|
+
* `shoptEnableVisitor` defines (`shopt -u`, `shopt -o` and a name that is not
|
|
13
|
+
* a static word do not count). The report is on the `globstar` word.
|
|
14
|
+
*/
|
|
15
|
+
import { createCompatRule } from './create-compat-rule.js';
|
|
16
|
+
import { shoptEnableVisitor } from './shopt-options.js';
|
|
17
|
+
/**
|
|
18
|
+
* The `sh/compat/no-globstar` rule module.
|
|
19
|
+
*
|
|
20
|
+
* @internal
|
|
21
|
+
*/
|
|
22
|
+
export const noGlobstar = createCompatRule({
|
|
23
|
+
name: 'compat/no-globstar',
|
|
24
|
+
feature: 'globstar',
|
|
25
|
+
meta: {
|
|
26
|
+
type: 'problem',
|
|
27
|
+
docs: {
|
|
28
|
+
description: 'Disallow `shopt -s globstar` when the target shell lacks the option',
|
|
29
|
+
},
|
|
30
|
+
messages: {
|
|
31
|
+
globstar: "`shopt -s globstar` needs {{required}}, but this file targets {{target}}, where `**` matches like `*`. Search the tree with `find` instead, for example `find dir -name '*.txt'`.",
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
create(context, gate) {
|
|
35
|
+
return shoptEnableVisitor(context, 'globstar', (word) => {
|
|
36
|
+
context.report({ node: word, messageId: 'globstar', data: { ...gate } });
|
|
37
|
+
});
|
|
38
|
+
},
|
|
39
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Message ids this rule reports with.
|
|
3
|
+
*
|
|
4
|
+
* @internal
|
|
5
|
+
*/
|
|
6
|
+
export type NoLastpipeSemanticsMessageId = 'lastpipe' | 'lastpipePosix';
|
|
7
|
+
/**
|
|
8
|
+
* The `sh/compat/no-lastpipe-semantics` rule module.
|
|
9
|
+
*
|
|
10
|
+
* @internal
|
|
11
|
+
*/
|
|
12
|
+
export declare const noLastpipeSemantics: import("../define-shell-rule.js").ShellRule<[], NoLastpipeSemanticsMessageId>;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `sh/compat/no-lastpipe-semantics`: reports `shopt -s lastpipe` in files
|
|
3
|
+
* whose target shell lacks the `lastpipe` option, that is, Bash older than 4.2
|
|
4
|
+
* (macOS's stock `/bin/bash` is 3.2) and POSIX sh.
|
|
5
|
+
*
|
|
6
|
+
* Bash 3.2 has no such option: `shopt` fails with "invalid shell option name"
|
|
7
|
+
* and the last command of every pipeline keeps running in a subshell, so a
|
|
8
|
+
* script that relied on `cmd | while read …; do total=…; done` leaving
|
|
9
|
+
* `total` set in the current shell silently loses the assignment. The message
|
|
10
|
+
* suggests the portable alternative: feed the loop with process substitution
|
|
11
|
+
* (`done < <(cmd)`), which keeps the loop in the current shell on every Bash
|
|
12
|
+
* that has it. A POSIX target has no process substitution, so its message
|
|
13
|
+
* suggests a temporary file or here-document instead.
|
|
14
|
+
*
|
|
15
|
+
* What is read: a `shopt` command (after transparent wrappers such as
|
|
16
|
+
* `builtin`) whose option words, up to the first operand, include `-s`
|
|
17
|
+
* (possibly inside a cluster such as `-sq`) and which names `lastpipe` as an
|
|
18
|
+
* operand. `shopt -u lastpipe` turns the option off (so does a `-u` beside the
|
|
19
|
+
* `-s`), `shopt -o` names `set -o` options, and a `lastpipe` that is not a static word cannot be read, so none
|
|
20
|
+
* of those reports.
|
|
21
|
+
*/
|
|
22
|
+
import { createCompatRule } from './create-compat-rule.js';
|
|
23
|
+
import { shoptEnableVisitor } from './shopt-options.js';
|
|
24
|
+
/** The `shopt` option this rule gates. */
|
|
25
|
+
const LASTPIPE = 'lastpipe';
|
|
26
|
+
/**
|
|
27
|
+
* The `sh/compat/no-lastpipe-semantics` rule module.
|
|
28
|
+
*
|
|
29
|
+
* @internal
|
|
30
|
+
*/
|
|
31
|
+
export const noLastpipeSemantics = createCompatRule({
|
|
32
|
+
name: 'compat/no-lastpipe-semantics',
|
|
33
|
+
feature: 'lastpipe',
|
|
34
|
+
meta: {
|
|
35
|
+
type: 'problem',
|
|
36
|
+
docs: {
|
|
37
|
+
description: 'Disallow `shopt -s lastpipe` when the target shell lacks the option',
|
|
38
|
+
},
|
|
39
|
+
messages: {
|
|
40
|
+
lastpipe: '`shopt -s lastpipe` needs {{required}}, but this file targets {{target}}. Keep the loop in the current shell by feeding it with process substitution instead, for example `while read -r line; do …; done < <(cmd)`.',
|
|
41
|
+
lastpipePosix: '`shopt -s lastpipe` needs {{required}}, but this file targets {{target}}. Process substitution is not POSIX either; keep the loop in the current shell by writing the output to a temporary file and reading that, for example `cmd > "$tmp"` then `while read -r line; do …; done < "$tmp"`, or by feeding the loop a here-document.',
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
create(context, gate) {
|
|
45
|
+
const { sourceCode } = context;
|
|
46
|
+
// Process substitution is a Bash feature, so a POSIX target gets advice
|
|
47
|
+
// that uses only POSIX constructs.
|
|
48
|
+
const messageId = sourceCode.dialect === 'posix' ? 'lastpipePosix' : 'lastpipe';
|
|
49
|
+
return shoptEnableVisitor(context, LASTPIPE, (word) => {
|
|
50
|
+
context.report({ node: word, messageId, data: { ...gate } });
|
|
51
|
+
});
|
|
52
|
+
},
|
|
53
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Message ids this rule reports with.
|
|
3
|
+
*
|
|
4
|
+
* @internal
|
|
5
|
+
*/
|
|
6
|
+
export type NoLetMessageId = 'let';
|
|
7
|
+
/**
|
|
8
|
+
* The `sh/compat/no-let` rule module.
|
|
9
|
+
*
|
|
10
|
+
* @internal
|
|
11
|
+
*/
|
|
12
|
+
export declare const noLet: import("../define-shell-rule.js").ShellRule<[], "let">;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `sh/compat/no-let`: reports the `let` builtin in files that target POSIX sh.
|
|
3
|
+
* Every Bash has it, so this is a POSIX-only gate.
|
|
4
|
+
*
|
|
5
|
+
* POSIX section 2.9.1.4 leaves the results of a command named `let`
|
|
6
|
+
* unspecified. dash has no such command: `let n=1+2` fails with `let: not
|
|
7
|
+
* found` and status 127, and `n` stays unset. `bash --posix` and BusyBox ash
|
|
8
|
+
* accept it, so only some POSIX shells show the problem.
|
|
9
|
+
*
|
|
10
|
+
* What is read: a command whose effective name is `let`, because the POSIX
|
|
11
|
+
* parser has no `let` clause, outside a branch for one shell (see
|
|
12
|
+
* `./posix-absent-command.ts`).
|
|
13
|
+
*
|
|
14
|
+
* A function named `let` anywhere in the file exempts a call that names it
|
|
15
|
+
* directly, since dash can then run that function. This is a name check across
|
|
16
|
+
* the file and does not prove the function exists at the call: execution order,
|
|
17
|
+
* a definition in a branch or subshell, and `unset -f` are not read, so some
|
|
18
|
+
* exempt calls still fail on dash. A call through a wrapper is never exempt,
|
|
19
|
+
* because wrappers such as `command`, `env` and `exec` do not run functions.
|
|
20
|
+
*
|
|
21
|
+
* No suggestion: `let` sets its exit status from the value of its last
|
|
22
|
+
* expression, which `n=$((1 + 2))` does not, so the rewrite can change what
|
|
23
|
+
* `set -e` or an `if` sees.
|
|
24
|
+
*/
|
|
25
|
+
import { definedFunctionNames } from '../../command-harvest/harvest-commands.js';
|
|
26
|
+
import { createCompatRule } from './create-compat-rule.js';
|
|
27
|
+
import { reachableCommandWord } from './posix-absent-command.js';
|
|
28
|
+
/** The builtin this rule gates. */
|
|
29
|
+
const LET = 'let';
|
|
30
|
+
/**
|
|
31
|
+
* The `sh/compat/no-let` rule module.
|
|
32
|
+
*
|
|
33
|
+
* @internal
|
|
34
|
+
*/
|
|
35
|
+
export const noLet = createCompatRule({
|
|
36
|
+
name: 'compat/no-let',
|
|
37
|
+
feature: 'letCommand',
|
|
38
|
+
meta: {
|
|
39
|
+
type: 'problem',
|
|
40
|
+
docs: {
|
|
41
|
+
description: 'Disallow the `let` builtin when the target shell is POSIX sh',
|
|
42
|
+
},
|
|
43
|
+
messages: {
|
|
44
|
+
let: '`let` is not part of {{target}}. dash fails with `let: not found` and the assignment never happens. Use arithmetic expansion instead, for example `n=$((1 + 2))`.',
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
create(context, gate) {
|
|
48
|
+
const { sourceCode } = context;
|
|
49
|
+
const fileDefinesLet = definedFunctionNames(sourceCode).has(LET);
|
|
50
|
+
return {
|
|
51
|
+
CallExpr(node) {
|
|
52
|
+
const word = reachableCommandWord(sourceCode, node, LET);
|
|
53
|
+
if (word === undefined)
|
|
54
|
+
return;
|
|
55
|
+
// Only a name written directly can run a function: a wrapper skips it.
|
|
56
|
+
if (fileDefinesLet && sourceCode.getCommandName(node) === LET)
|
|
57
|
+
return;
|
|
58
|
+
context.report({ node: word, messageId: 'let', data: { target: gate.target } });
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
},
|
|
62
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Message ids this rule reports with: `mapfile` for the report and
|
|
3
|
+
* `useReadLoop` for its suggestion.
|
|
4
|
+
*
|
|
5
|
+
* @internal
|
|
6
|
+
*/
|
|
7
|
+
export type NoMapfileMessageId = 'mapfile' | 'useReadLoop';
|
|
8
|
+
/**
|
|
9
|
+
* The `sh/compat/no-mapfile` rule module.
|
|
10
|
+
*
|
|
11
|
+
* @internal
|
|
12
|
+
*/
|
|
13
|
+
export declare const noMapfile: import("../define-shell-rule.js").ShellRule<[], NoMapfileMessageId>;
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `sh/compat/no-mapfile`: reports the `mapfile` builtin and its `readarray`
|
|
3
|
+
* synonym in files whose target shell lacks them, that is, Bash older than
|
|
4
|
+
* 4.0 (macOS's stock `/bin/bash` is 3.2) and POSIX sh. Under Bash 3.2 the
|
|
5
|
+
* name is looked up as an external command, which does not exist, so the
|
|
6
|
+
* array is never filled.
|
|
7
|
+
*
|
|
8
|
+
* The command is recognized as the effective command after following
|
|
9
|
+
* transparent wrappers (`command mapfile …`, `builtin readarray …`), through
|
|
10
|
+
* `ShellSourceCode.resolveInvokedCommand`.
|
|
11
|
+
*
|
|
12
|
+
* The suggestion rewrites the one shape whose array contents a loop
|
|
13
|
+
* reproduces exactly, `mapfile -t [NAME]` written directly (no wrapper, no leading
|
|
14
|
+
* `VAR=value` assignment) in a Bash-family file: it becomes a brace group
|
|
15
|
+
* that empties the array and appends each line read by
|
|
16
|
+
* `while IFS= read -r … || [ -n "$REPLY" ]`. The `|| [ -n "$REPLY" ]` keeps a
|
|
17
|
+
* final line that has no trailing newline, as `mapfile -t` does. Reading with
|
|
18
|
+
* no variable name keeps every byte of the line, but it assigns `REPLY`:
|
|
19
|
+
* unlike `mapfile`, the loop leaves `REPLY` set to the empty string, whatever
|
|
20
|
+
* it held (or whether it was set) before. `REPLY` is `read`'s own default
|
|
21
|
+
* variable, so this is the narrowest side effect a read loop can have; the
|
|
22
|
+
* alternatives (a named loop variable, or saving and restoring `REPLY` in
|
|
23
|
+
* another variable) overwrite a name the script is likelier to use. The
|
|
24
|
+
* suggestion message states the tradeoff. The brace group keeps the command a single
|
|
25
|
+
* unit, so a redirection (`< file`) or pipeline (`cmd | mapfile -t a`) still
|
|
26
|
+
* feeds the loop. Other options (`-d`, `-n`, `-O`, `-s`, `-u`, `-C`, or no
|
|
27
|
+
* `-t`, which keeps each newline) and POSIX files, which have no arrays at
|
|
28
|
+
* all, get the report without a suggestion.
|
|
29
|
+
*/
|
|
30
|
+
import { DEFAULT_TRANSPARENT_WRAPPERS } from 'sh-ast/analyze';
|
|
31
|
+
import { BUILTIN_WRAPPER } from '../transparent-wrappers.js';
|
|
32
|
+
import { createCompatRule } from './create-compat-rule.js';
|
|
33
|
+
/** The builtin's two names. */
|
|
34
|
+
const MAPFILE_COMMANDS = new Set(['mapfile', 'readarray']);
|
|
35
|
+
/** The array `mapfile` fills when no name is given. */
|
|
36
|
+
const DEFAULT_ARRAY = 'MAPFILE';
|
|
37
|
+
/** A shell variable name, the only operand shape the suggestion rewrites. */
|
|
38
|
+
const IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_]*$/u;
|
|
39
|
+
/**
|
|
40
|
+
* The wrappers followed to find the command an invocation runs:
|
|
41
|
+
* `sh-ast/analyze`'s defaults (which include `command`) plus Bash's
|
|
42
|
+
* `builtin`.
|
|
43
|
+
*/
|
|
44
|
+
const TRANSPARENT_WRAPPERS = [
|
|
45
|
+
...DEFAULT_TRANSPARENT_WRAPPERS,
|
|
46
|
+
BUILTIN_WRAPPER,
|
|
47
|
+
];
|
|
48
|
+
/**
|
|
49
|
+
* The loop that fills `array` exactly as `mapfile -t array` does, wrapped in
|
|
50
|
+
* a brace group so redirections and pipelines still apply to all of it.
|
|
51
|
+
*/
|
|
52
|
+
function readLoop(array) {
|
|
53
|
+
return `{ ${array}=(); while IFS= read -r || [ -n "$REPLY" ]; do ${array}+=("$REPLY"); done; }`;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* The array a directly written `mapfile -t [NAME]` fills, or `undefined` when
|
|
57
|
+
* `node` has any other shape (see the module comment), so no suggestion is
|
|
58
|
+
* offered.
|
|
59
|
+
*/
|
|
60
|
+
function rewritableArray(sourceCode, node) {
|
|
61
|
+
const command = sourceCode.getCommandName(node);
|
|
62
|
+
if (command === null || !MAPFILE_COMMANDS.has(command))
|
|
63
|
+
return undefined;
|
|
64
|
+
if (node.assigns.length > 0)
|
|
65
|
+
return undefined;
|
|
66
|
+
const words = node.args.slice(1).map((word) => sourceCode.resolveWordText(word));
|
|
67
|
+
if (words.length === 0 || words.length > 2 || words[0] !== '-t')
|
|
68
|
+
return undefined;
|
|
69
|
+
const name = words.length === 2 ? words[1] : DEFAULT_ARRAY;
|
|
70
|
+
return name !== null && IDENTIFIER.test(name) ? name : undefined;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* The `sh/compat/no-mapfile` rule module.
|
|
74
|
+
*
|
|
75
|
+
* @internal
|
|
76
|
+
*/
|
|
77
|
+
export const noMapfile = createCompatRule({
|
|
78
|
+
name: 'compat/no-mapfile',
|
|
79
|
+
feature: 'mapfile',
|
|
80
|
+
meta: {
|
|
81
|
+
type: 'problem',
|
|
82
|
+
hasSuggestions: true,
|
|
83
|
+
docs: {
|
|
84
|
+
description: 'Disallow `mapfile` and `readarray` when the target shell lacks them',
|
|
85
|
+
},
|
|
86
|
+
messages: {
|
|
87
|
+
mapfile: '`{{command}}` needs {{required}}, but this file targets {{target}}. Read the lines with a `while IFS= read -r` loop instead.',
|
|
88
|
+
useReadLoop: 'Replace with a `while IFS= read -r` loop that appends each line to `{{array}}` (the loop also sets `REPLY` to the empty string).',
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
create(context, gate) {
|
|
92
|
+
const { sourceCode } = context;
|
|
93
|
+
const arraysAvailable = sourceCode.dialect !== 'posix';
|
|
94
|
+
return {
|
|
95
|
+
CallExpr(node) {
|
|
96
|
+
const invoked = sourceCode.resolveInvokedCommand(node, {
|
|
97
|
+
transparentWrappers: TRANSPARENT_WRAPPERS,
|
|
98
|
+
});
|
|
99
|
+
if (invoked?.effective.static !== true)
|
|
100
|
+
return;
|
|
101
|
+
const command = invoked.effective.text;
|
|
102
|
+
if (!MAPFILE_COMMANDS.has(command))
|
|
103
|
+
return;
|
|
104
|
+
const array = arraysAvailable ? rewritableArray(sourceCode, node) : undefined;
|
|
105
|
+
context.report({
|
|
106
|
+
node,
|
|
107
|
+
messageId: 'mapfile',
|
|
108
|
+
data: { ...gate, command },
|
|
109
|
+
suggest: array === undefined
|
|
110
|
+
? []
|
|
111
|
+
: [
|
|
112
|
+
{
|
|
113
|
+
messageId: 'useReadLoop',
|
|
114
|
+
data: { array },
|
|
115
|
+
fix: (fixer) => fixer.replaceText(node, readLoop(array)),
|
|
116
|
+
},
|
|
117
|
+
],
|
|
118
|
+
});
|
|
119
|
+
},
|
|
120
|
+
};
|
|
121
|
+
},
|
|
122
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Message ids this rule reports with.
|
|
3
|
+
*
|
|
4
|
+
* @internal
|
|
5
|
+
*/
|
|
6
|
+
export type NoNamerefMessageId = 'nameref' | 'namerefPosix';
|
|
7
|
+
/**
|
|
8
|
+
* The `sh/compat/no-nameref` rule module.
|
|
9
|
+
*
|
|
10
|
+
* @internal
|
|
11
|
+
*/
|
|
12
|
+
export declare const noNameref: import("../define-shell-rule.js").ShellRule<[], NoNamerefMessageId>;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `sh/compat/no-nameref`: reports namerefs (`declare -n`, `typeset -n`,
|
|
3
|
+
* `local -n`) in files whose target shell lacks them, that is, Bash older
|
|
4
|
+
* than 4.3 (macOS's stock `/bin/bash` is 3.2) and POSIX sh.
|
|
5
|
+
*
|
|
6
|
+
* Bash 4.2 and older reject `-n` as an invalid option, so the reference is
|
|
7
|
+
* never made and every assignment through it lands in a variable of its own
|
|
8
|
+
* name instead. The message suggests the portable alternative for the
|
|
9
|
+
* target: in Bash, assign by name with `printf -v "$name" '%s' "$value"`
|
|
10
|
+
* and read by name with `${!name}`, both available in Bash 3.2; in POSIX
|
|
11
|
+
* sh, which has neither, use `eval`.
|
|
12
|
+
*
|
|
13
|
+
* What is read: `declare`/`typeset`/`local` option clusters containing `n`
|
|
14
|
+
* (`-n`, `-rn`), found as `declarationFlagVisitor` defines. The report is on
|
|
15
|
+
* the option word.
|
|
16
|
+
*/
|
|
17
|
+
import { createCompatRule } from './create-compat-rule.js';
|
|
18
|
+
import { DECLARATION_AND_LOCAL_COMMANDS, declarationFlagVisitor } from './declaration-options.js';
|
|
19
|
+
/**
|
|
20
|
+
* The `sh/compat/no-nameref` rule module.
|
|
21
|
+
*
|
|
22
|
+
* @internal
|
|
23
|
+
*/
|
|
24
|
+
export const noNameref = createCompatRule({
|
|
25
|
+
name: 'compat/no-nameref',
|
|
26
|
+
feature: 'nameref',
|
|
27
|
+
meta: {
|
|
28
|
+
type: 'problem',
|
|
29
|
+
docs: {
|
|
30
|
+
description: 'Disallow namerefs (`declare -n`, `local -n`) when the target shell lacks them',
|
|
31
|
+
},
|
|
32
|
+
messages: {
|
|
33
|
+
nameref: '`{{command}} -n` needs {{required}}, but this file targets {{target}}. Pass the variable name instead: assign through it with `printf -v "$name" \'%s\' "$value"` and read it with `${!name}`.',
|
|
34
|
+
namerefPosix: '`{{command}} -n` needs {{required}}, but this file targets {{target}}. Pass the variable name instead and use `eval`: assign with `eval "$name=\\$value"` and read with `eval "value=\\$$name"`, after checking that the name is a valid identifier.',
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
create(context, gate) {
|
|
38
|
+
const { sourceCode } = context;
|
|
39
|
+
const messageId = sourceCode.dialect === 'posix' ? 'namerefPosix' : 'nameref';
|
|
40
|
+
return declarationFlagVisitor(sourceCode, 'n', (node, command) => {
|
|
41
|
+
context.report({ node, messageId, data: { ...gate, command } });
|
|
42
|
+
}, DECLARATION_AND_LOCAL_COMMANDS);
|
|
43
|
+
},
|
|
44
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Message ids this rule reports with: `negativeIndex` for the report and
|
|
3
|
+
* `useArrayLength` for its suggestion.
|
|
4
|
+
*
|
|
5
|
+
* @internal
|
|
6
|
+
*/
|
|
7
|
+
export type NoNegativeArrayIndexMessageId = 'negativeIndex' | 'useArrayLength';
|
|
8
|
+
/**
|
|
9
|
+
* The `sh/compat/no-negative-array-index` rule module.
|
|
10
|
+
*
|
|
11
|
+
* @internal
|
|
12
|
+
*/
|
|
13
|
+
export declare const noNegativeArrayIndex: import("../define-shell-rule.js").ShellRule<[], NoNegativeArrayIndexMessageId>;
|