@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,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `sh/compat/no-case-modification`: reports the case-modifying parameter
|
|
3
|
+
* expansions `${v^}`, `${v^^}`, `${v,}` and `${v,,}` (with or without a
|
|
4
|
+
* pattern) in files whose target shell lacks them, that is, Bash older than
|
|
5
|
+
* 4.0 (macOS's stock `/bin/bash` is 3.2) and POSIX sh.
|
|
6
|
+
*
|
|
7
|
+
* Bash 3.2 rejects the expansion with "bad substitution" at the moment the
|
|
8
|
+
* word is expanded. The message suggests the portable alternative: pipe the
|
|
9
|
+
* value through `tr`. The example it shows is for the operator written: all
|
|
10
|
+
* characters for `^^` and `,,`, the first character for `^` and `,`
|
|
11
|
+
* (`${v%"${v#?}"}` is the first character and `${v#?}` the rest).
|
|
12
|
+
*
|
|
13
|
+
* A pattern form (`${v^^a}`) changes only the characters the pattern matches,
|
|
14
|
+
* which a plain case-converting `tr` does not reproduce, so it gets a message
|
|
15
|
+
* that offers no `tr` equivalent.
|
|
16
|
+
*
|
|
17
|
+
* What is read: a `ParamExp` whose operator is one of the four case
|
|
18
|
+
* operators. The report is on the whole expansion.
|
|
19
|
+
*/
|
|
20
|
+
import { UpperAll, UpperFirst, LowerAll, LowerFirst } from 'sh-ast';
|
|
21
|
+
import { createCompatRule } from './create-compat-rule.js';
|
|
22
|
+
/** `tr` arguments that convert to upper case. @internal */
|
|
23
|
+
export const TO_UPPER = "tr '[:lower:]' '[:upper:]'";
|
|
24
|
+
/** `tr` arguments that convert to lower case. @internal */
|
|
25
|
+
export const TO_LOWER = "tr '[:upper:]' '[:lower:]'";
|
|
26
|
+
/** The four case operators, keyed by the parser's operator constant. */
|
|
27
|
+
const CASE_OPERATORS = new Map([
|
|
28
|
+
[UpperAll, { operator: '^^', all: true, tr: TO_UPPER }],
|
|
29
|
+
[UpperFirst, { operator: '^', all: false, tr: TO_UPPER }],
|
|
30
|
+
[LowerAll, { operator: ',,', all: true, tr: TO_LOWER }],
|
|
31
|
+
[LowerFirst, { operator: ',', all: false, tr: TO_LOWER }],
|
|
32
|
+
]);
|
|
33
|
+
/**
|
|
34
|
+
* The `tr` replacement shown in the message for `name` under `spec`.
|
|
35
|
+
*
|
|
36
|
+
* @internal
|
|
37
|
+
*/
|
|
38
|
+
export function trExample(name, spec) {
|
|
39
|
+
const whole = `"$${name}"`;
|
|
40
|
+
return spec.all
|
|
41
|
+
? `$(printf '%s' ${whole} | ${spec.tr})`
|
|
42
|
+
: `$(printf '%s' "\${${name}%"\${${name}#?}"}" | ${spec.tr})"\${${name}#?}"`;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* The `sh/compat/no-case-modification` rule module.
|
|
46
|
+
*
|
|
47
|
+
* @internal
|
|
48
|
+
*/
|
|
49
|
+
export const noCaseModification = createCompatRule({
|
|
50
|
+
name: 'compat/no-case-modification',
|
|
51
|
+
feature: 'caseModification',
|
|
52
|
+
meta: {
|
|
53
|
+
type: 'problem',
|
|
54
|
+
docs: {
|
|
55
|
+
description: 'Disallow case-modifying expansions (`${v^^}`, `${v,,}` and variants) when the target shell lacks them',
|
|
56
|
+
},
|
|
57
|
+
messages: {
|
|
58
|
+
caseModification: '`${…{{operator}}}` needs {{required}}, but this file targets {{target}}. Convert the case with `tr` instead, for example {{example}}.',
|
|
59
|
+
caseModificationPattern: '`${…{{operator}}pattern}` needs {{required}}, but this file targets {{target}}. It changes only the characters the pattern matches, so a plain case-converting `tr` is not equivalent; spell out the matching characters instead (for example `tr a A`).',
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
create(context, gate) {
|
|
63
|
+
return {
|
|
64
|
+
ParamExp(node) {
|
|
65
|
+
const { exp } = node;
|
|
66
|
+
const spec = exp === undefined ? undefined : CASE_OPERATORS.get(exp.op);
|
|
67
|
+
if (exp === undefined || spec === undefined)
|
|
68
|
+
return;
|
|
69
|
+
const name = node.param?.value ?? 'v';
|
|
70
|
+
if (exp.word !== undefined) {
|
|
71
|
+
context.report({
|
|
72
|
+
node,
|
|
73
|
+
messageId: 'caseModificationPattern',
|
|
74
|
+
data: { ...gate, operator: spec.operator },
|
|
75
|
+
});
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
context.report({
|
|
79
|
+
node,
|
|
80
|
+
messageId: 'caseModification',
|
|
81
|
+
data: { ...gate, operator: spec.operator, example: trExample(name, spec) },
|
|
82
|
+
});
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
},
|
|
86
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Message ids this rule reports with.
|
|
3
|
+
*
|
|
4
|
+
* @internal
|
|
5
|
+
*/
|
|
6
|
+
export type NoCoprocMessageId = 'coproc';
|
|
7
|
+
/**
|
|
8
|
+
* The `sh/compat/no-coproc` rule module.
|
|
9
|
+
*
|
|
10
|
+
* @internal
|
|
11
|
+
*/
|
|
12
|
+
export declare const noCoproc: import("../define-shell-rule.js").ShellRule<[], "coproc">;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `sh/compat/no-coproc`: reports the `coproc` reserved word (a coprocess:
|
|
3
|
+
* a background command with a pipe pair to the shell) in files whose target
|
|
4
|
+
* shell lacks it, that is, Bash older than 4.0 (macOS's stock `/bin/bash` is
|
|
5
|
+
* 3.2) and POSIX sh.
|
|
6
|
+
*
|
|
7
|
+
* Bash 3.2 does not know the word, so `coproc cat` runs an external command
|
|
8
|
+
* named `coproc` and fails with `command not found`. The message suggests the
|
|
9
|
+
* portable alternative: a named pipe made with `mkfifo`, with the producer
|
|
10
|
+
* started in the background.
|
|
11
|
+
*
|
|
12
|
+
* What is read: Bash's `CoprocClause`. Under `dialect: "posix"` the parser
|
|
13
|
+
* has no such clause and reads `coproc cat` as an ordinary command, so a
|
|
14
|
+
* command whose name is `coproc` is reported too.
|
|
15
|
+
*/
|
|
16
|
+
import { createCompatRule } from './create-compat-rule.js';
|
|
17
|
+
/**
|
|
18
|
+
* The `sh/compat/no-coproc` rule module.
|
|
19
|
+
*
|
|
20
|
+
* @internal
|
|
21
|
+
*/
|
|
22
|
+
export const noCoproc = createCompatRule({
|
|
23
|
+
name: 'compat/no-coproc',
|
|
24
|
+
feature: 'coproc',
|
|
25
|
+
meta: {
|
|
26
|
+
type: 'problem',
|
|
27
|
+
docs: {
|
|
28
|
+
description: 'Disallow the `coproc` reserved word when the target shell lacks it',
|
|
29
|
+
},
|
|
30
|
+
messages: {
|
|
31
|
+
coproc: '`coproc` needs {{required}}, but this file targets {{target}}. Use a named pipe instead: create it with `mkfifo`, start the coprocess in the background reading or writing the pipe, and open the pipe from the script.',
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
create(context, gate) {
|
|
35
|
+
const { sourceCode } = context;
|
|
36
|
+
return {
|
|
37
|
+
CoprocClause(node) {
|
|
38
|
+
context.report({ node, messageId: 'coproc', data: { ...gate } });
|
|
39
|
+
},
|
|
40
|
+
CallExpr(node) {
|
|
41
|
+
if (sourceCode.getCommandName(node) === 'coproc') {
|
|
42
|
+
context.report({ node, messageId: 'coproc', data: { ...gate } });
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
},
|
|
47
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Message ids this rule reports with.
|
|
3
|
+
*
|
|
4
|
+
* @internal
|
|
5
|
+
*/
|
|
6
|
+
export type NoDeclareGMessageId = 'declareGlobal' | 'declareGlobalWithAttributes';
|
|
7
|
+
/**
|
|
8
|
+
* The `sh/compat/no-declare-g` rule module.
|
|
9
|
+
*
|
|
10
|
+
* @internal
|
|
11
|
+
*/
|
|
12
|
+
export declare const noDeclareG: import("../define-shell-rule.js").ShellRule<[], NoDeclareGMessageId>;
|
|
@@ -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>;
|