@eslint-sh/eslint-plugin 0.4.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,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `sh/compat/no-unicode-escape`: reports the `\uXXXX` and `\UXXXXXXXX`
|
|
3
|
+
* escapes in a `$'…'` string in files whose target shell does not decode
|
|
4
|
+
* them to UTF-8. The minimum Bash depends on the code point:
|
|
5
|
+
*
|
|
6
|
+
* - Bash 4.1 and older leave every such escape as literal text, with no
|
|
7
|
+
* error: Bash 4.2 is the first release that decodes them;
|
|
8
|
+
* - the Bash 4.2 release writes a code point from U+0080 to U+00FF as a
|
|
9
|
+
* single Latin-1 byte even in a UTF-8 locale (observed: `e9` for U+00E9,
|
|
10
|
+
* where UTF-8 is `c3 a9`), while every other code point is UTF-8. Bash 4.3
|
|
11
|
+
* is the first release that writes them all as UTF-8.
|
|
12
|
+
*
|
|
13
|
+
* The message suggests the portable alternative: write the character's
|
|
14
|
+
* UTF-8 bytes as octal escapes, which every Bash decodes in `$'…'` (for
|
|
15
|
+
* example `$'\303\251'` for U+00E9). Under `dialect: "posix"` the parser
|
|
16
|
+
* reads `$'…'` as a literal `$` followed by an ordinary single-quoted string,
|
|
17
|
+
* as dash does, so there is no `$'…'` string to report; the gate's
|
|
18
|
+
* `posix: false` records that POSIX sh does not decode these escapes.
|
|
19
|
+
*
|
|
20
|
+
* What is read: a `$'…'` string's text, scanned as Bash decodes it: `\\` is a
|
|
21
|
+
* backslash, `\cX` is one control character (so in `\cu00e9` the `u`
|
|
22
|
+
* belongs to `\c`), and `\u` takes one to four hex digits, `\U` one to
|
|
23
|
+
* eight. A `\u` with no hex digit after it is literal text. Plain `'…'`
|
|
24
|
+
* and `"…"` strings do not decode escapes and are not read. The report is on
|
|
25
|
+
* the whole string, once per string.
|
|
26
|
+
*/
|
|
27
|
+
import { createMultiFeatureCompatRule } from './create-compat-rule.js';
|
|
28
|
+
/** The backslash that starts every escape. */
|
|
29
|
+
const BACKSLASH = '\\';
|
|
30
|
+
/** The unicode escapes of a `$'…'` string's text, in order. */
|
|
31
|
+
function unicodeEscapes(text) {
|
|
32
|
+
const found = [];
|
|
33
|
+
let index = 0;
|
|
34
|
+
while (index < text.length) {
|
|
35
|
+
if (text[index] !== BACKSLASH) {
|
|
36
|
+
index++;
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
const letter = text[index + 1];
|
|
40
|
+
if (letter === 'c') {
|
|
41
|
+
index += 3;
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
const maxDigits = letter === 'u' ? 4 : letter === 'U' ? 8 : 0;
|
|
45
|
+
if (maxDigits === 0) {
|
|
46
|
+
index += 2;
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
const digits = /^[0-9A-Fa-f]+/u.exec(text.slice(index + 2, index + 2 + maxDigits))?.[0];
|
|
50
|
+
if (digits === undefined) {
|
|
51
|
+
index += 2;
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
const length = 2 + digits.length;
|
|
55
|
+
found.push({ text: text.slice(index, index + length), codePoint: parseInt(digits, 16) });
|
|
56
|
+
index += length;
|
|
57
|
+
}
|
|
58
|
+
return found;
|
|
59
|
+
}
|
|
60
|
+
/** Whether a code point is one the Bash 4.2 release writes as a Latin-1 byte. */
|
|
61
|
+
function isLatin1Upper(codePoint) {
|
|
62
|
+
return codePoint >= 0x80 && codePoint <= 0xff;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* The UTF-8 bytes of `codePoint` as octal escapes (`\303\251`), or
|
|
66
|
+
* `undefined` for a value that is not a Unicode scalar value.
|
|
67
|
+
*/
|
|
68
|
+
function octalBytes(codePoint) {
|
|
69
|
+
if (codePoint > 0x10ffff || (codePoint >= 0xd800 && codePoint <= 0xdfff))
|
|
70
|
+
return undefined;
|
|
71
|
+
const bytes = new TextEncoder().encode(String.fromCodePoint(codePoint));
|
|
72
|
+
return [...bytes].map((byte) => BACKSLASH + byte.toString(8).padStart(3, '0')).join('');
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* The `sh/compat/no-unicode-escape` rule module.
|
|
76
|
+
*
|
|
77
|
+
* @internal
|
|
78
|
+
*/
|
|
79
|
+
export const noUnicodeEscape = createMultiFeatureCompatRule({
|
|
80
|
+
name: 'compat/no-unicode-escape',
|
|
81
|
+
features: ['unicodeEscape', 'unicodeEscapeLatin1'],
|
|
82
|
+
meta: {
|
|
83
|
+
type: 'problem',
|
|
84
|
+
docs: {
|
|
85
|
+
description: "Disallow `\\u`/`\\U` escapes in `$'…'` strings when the target shell does not decode them to UTF-8",
|
|
86
|
+
},
|
|
87
|
+
messages: {
|
|
88
|
+
unicodeEscape: "`{{escape}}` in `$'…'` needs {{required}}, but this file targets {{target}}, which leaves it as literal text. Write the UTF-8 bytes as octal escapes instead, for example `$'{{octal}}'`.",
|
|
89
|
+
unicodeEscapeLatin1: "`{{escape}}` in `$'…'` needs {{required}}, but this file targets {{target}}, which writes U+0080 to U+00FF as a single Latin-1 byte, not UTF-8. Write the UTF-8 bytes as octal escapes instead, for example `$'{{octal}}'`.",
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
create(context, gates) {
|
|
93
|
+
const escapeGate = gates.get('unicodeEscape');
|
|
94
|
+
const latin1Gate = gates.get('unicodeEscapeLatin1');
|
|
95
|
+
return {
|
|
96
|
+
SglQuoted(node) {
|
|
97
|
+
if (!node.dollar)
|
|
98
|
+
return;
|
|
99
|
+
const escapes = unicodeEscapes(node.value);
|
|
100
|
+
const latin1 = escapes.find((escape) => isLatin1Upper(escape.codePoint));
|
|
101
|
+
// The escape the message names, and the gate it needs: a Latin-1
|
|
102
|
+
// escape needs the newer release, so it decides when present.
|
|
103
|
+
const shown = latin1 ?? escapes.at(0);
|
|
104
|
+
const gate = latin1 === undefined ? escapeGate : latin1Gate;
|
|
105
|
+
if (shown === undefined || gate === undefined)
|
|
106
|
+
return;
|
|
107
|
+
const messageId = escapeGate === undefined ? 'unicodeEscapeLatin1' : 'unicodeEscape';
|
|
108
|
+
context.report({
|
|
109
|
+
node,
|
|
110
|
+
messageId,
|
|
111
|
+
data: {
|
|
112
|
+
...gate,
|
|
113
|
+
escape: shown.text,
|
|
114
|
+
octal: octalBytes(shown.codePoint) ?? '\\NNN',
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
},
|
|
118
|
+
};
|
|
119
|
+
},
|
|
120
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Message ids this rule reports with.
|
|
3
|
+
*
|
|
4
|
+
* @internal
|
|
5
|
+
*/
|
|
6
|
+
export type NoWaitNMessageId = 'waitNext';
|
|
7
|
+
/**
|
|
8
|
+
* The `sh/compat/no-wait-n` rule module.
|
|
9
|
+
*
|
|
10
|
+
* @internal
|
|
11
|
+
*/
|
|
12
|
+
export declare const noWaitN: import("../define-shell-rule.js").ShellRule<[], "waitNext">;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `sh/compat/no-wait-n`: reports `wait -n` in files whose target shell lacks
|
|
3
|
+
* it, that is, Bash older than 4.3 (macOS's stock `/bin/bash` is 3.2) and
|
|
4
|
+
* POSIX sh.
|
|
5
|
+
*
|
|
6
|
+
* Bash 4.2 and older reject `-n` as an invalid option and return 2 at once,
|
|
7
|
+
* so a job pool that throttles itself with `wait -n` stops waiting and runs
|
|
8
|
+
* every job together. The message suggests the portable alternative: keep
|
|
9
|
+
* each job's `$!` and wait for one job at a time with `wait "$pid"`, which
|
|
10
|
+
* returns that job's exit status.
|
|
11
|
+
*
|
|
12
|
+
* What is read: a `wait` command (after transparent wrappers such as
|
|
13
|
+
* `builtin`) whose option words, up to the first operand or `--`, include a
|
|
14
|
+
* cluster containing `n` (`-n`, `-fn`). The value after a `-p` cluster
|
|
15
|
+
* (Bash 5.1's `wait -p var`) is its operand, not an option. The report is on
|
|
16
|
+
* the option word.
|
|
17
|
+
*/
|
|
18
|
+
import { DEFAULT_TRANSPARENT_WRAPPERS } from 'sh-ast/analyze';
|
|
19
|
+
import { BUILTIN_WRAPPER } from '../transparent-wrappers.js';
|
|
20
|
+
import { createCompatRule } from './create-compat-rule.js';
|
|
21
|
+
/**
|
|
22
|
+
* The wrappers followed to find the `wait` command: `sh-ast/analyze`'s
|
|
23
|
+
* defaults (which include `command`) plus Bash's `builtin`.
|
|
24
|
+
*/
|
|
25
|
+
const TRANSPARENT_WRAPPERS = [
|
|
26
|
+
...DEFAULT_TRANSPARENT_WRAPPERS,
|
|
27
|
+
BUILTIN_WRAPPER,
|
|
28
|
+
];
|
|
29
|
+
/** An option cluster of `wait`: letters after a single `-`. */
|
|
30
|
+
const OPTION_CLUSTER = /^-[A-Za-z]+$/u;
|
|
31
|
+
/**
|
|
32
|
+
* The `sh/compat/no-wait-n` rule module.
|
|
33
|
+
*
|
|
34
|
+
* @internal
|
|
35
|
+
*/
|
|
36
|
+
export const noWaitN = createCompatRule({
|
|
37
|
+
name: 'compat/no-wait-n',
|
|
38
|
+
feature: 'waitNext',
|
|
39
|
+
meta: {
|
|
40
|
+
type: 'problem',
|
|
41
|
+
docs: {
|
|
42
|
+
description: 'Disallow `wait -n` when the target shell lacks it',
|
|
43
|
+
},
|
|
44
|
+
messages: {
|
|
45
|
+
waitNext: '`wait -n` needs {{required}}, but this file targets {{target}}. Wait for one job at a time instead: keep each job\'s `$!` and run `wait "$pid"`, which returns that job\'s exit status.',
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
create(context, gate) {
|
|
49
|
+
const { sourceCode } = context;
|
|
50
|
+
return {
|
|
51
|
+
CallExpr(node) {
|
|
52
|
+
const invoked = sourceCode.resolveInvokedCommand(node, {
|
|
53
|
+
transparentWrappers: TRANSPARENT_WRAPPERS,
|
|
54
|
+
});
|
|
55
|
+
if (invoked?.effective.static !== true || invoked.effective.text !== 'wait')
|
|
56
|
+
return;
|
|
57
|
+
// `chainIndices` index `node.args` directly, so slice before filtering.
|
|
58
|
+
const words = node.args.slice((invoked.chainIndices.at(-1) ?? 0) + 1);
|
|
59
|
+
for (let index = 0; index < words.length; index++) {
|
|
60
|
+
const text = sourceCode.resolveWordText(words[index]);
|
|
61
|
+
if (text === null || !OPTION_CLUSTER.test(text))
|
|
62
|
+
return;
|
|
63
|
+
if (text.includes('n')) {
|
|
64
|
+
context.report({ node: words[index], messageId: 'waitNext', data: { ...gate } });
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
if (text.includes('p'))
|
|
68
|
+
index++;
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
},
|
|
73
|
+
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ShellSourceCode } from '../../language/shell-source-code.js';
|
|
2
|
+
import type { ShellNode } from '../../language/syntax-view.js';
|
|
3
|
+
/**
|
|
4
|
+
* The `[start, end)` UTF-16 range of `operator` at `node`'s `OpPos`, or
|
|
5
|
+
* `undefined` when the record has no valid `OpPos` or the text there is not
|
|
6
|
+
* `operator`.
|
|
7
|
+
*
|
|
8
|
+
* @internal
|
|
9
|
+
*/
|
|
10
|
+
export declare function operatorRange(sourceCode: ShellSourceCode, node: ShellNode, operator: string): [number, number] | undefined;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `[start, end)` UTF-16 range of `operator` at `node`'s `OpPos`, or
|
|
3
|
+
* `undefined` when the record has no valid `OpPos` or the text there is not
|
|
4
|
+
* `operator`.
|
|
5
|
+
*
|
|
6
|
+
* @internal
|
|
7
|
+
*/
|
|
8
|
+
export function operatorRange(sourceCode, node, operator) {
|
|
9
|
+
const syntax = sourceCode.getSyntax(node);
|
|
10
|
+
const pos = syntax?.OpPos;
|
|
11
|
+
if (pos?.IsValid !== true)
|
|
12
|
+
return undefined;
|
|
13
|
+
const start = sourceCode.getIndexFromSyntaxPos(pos);
|
|
14
|
+
return sourceCode.text.startsWith(operator, start) ? [start, start + operator.length] : undefined;
|
|
15
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ShellSourceCode } from '../../language/shell-source-code.js';
|
|
2
|
+
import type { ShellNode, ShellNodeOf } from '../../language/syntax-view.js';
|
|
3
|
+
/**
|
|
4
|
+
* The word that names `command` in `node`, when `command` is what `node`
|
|
5
|
+
* effectively runs and a shell without it can reach `node` (see the module
|
|
6
|
+
* comment), or `undefined` otherwise. Reporting this word rather than the whole
|
|
7
|
+
* command matters for `[[`: the parser ends that command at the first `&&` or
|
|
8
|
+
* `||`, so the node would cover an arbitrary part of the expression.
|
|
9
|
+
*
|
|
10
|
+
* @internal
|
|
11
|
+
*/
|
|
12
|
+
export declare function reachableCommandWord(sourceCode: ShellSourceCode, node: ShellNodeOf<'CallExpr'>, command: string): ShellNode | undefined;
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How a POSIX-only compat rule finds a use of a command word POSIX sh lacks.
|
|
3
|
+
*
|
|
4
|
+
* Under `dialect: "posix"` the parser has no `[[` clause, `let` clause or
|
|
5
|
+
* similar: it reads `[[ a ]]`, `let n=1` and `source f` as ordinary commands,
|
|
6
|
+
* so the rules recognize them by the command they run. That reading is shared
|
|
7
|
+
* here so the three rules agree on what counts as a use:
|
|
8
|
+
*
|
|
9
|
+
* - **It is the effective command**, after following transparent wrappers,
|
|
10
|
+
* which is what actually runs (`command let n=1` fails on dash as `let n=1`
|
|
11
|
+
* does). `command -v let` asks a question instead of running `let`, and
|
|
12
|
+
* resolves to no such command.
|
|
13
|
+
* - **A shell without the construct can reach it.** These constructs are
|
|
14
|
+
* commands, not syntax, so a POSIX shell only fails when it runs one. A script
|
|
15
|
+
* meant for `sh` can hold a branch for one shell, and dash never enters it:
|
|
16
|
+
* `if [ -n "${BASH_VERSION-}" ]; then source ~/.bashrc; fi`. Such a use is not
|
|
17
|
+
* reported, because the finding's claim would be false.
|
|
18
|
+
*
|
|
19
|
+
* Recognizing that branch is deliberately shallow and syntactic, like the guard
|
|
20
|
+
* in `require-tty-guard`: no dataflow, and only the shapes {@link isGuarded}
|
|
21
|
+
* lists, each testing one of `BASH_VERSION`, `KSH_VERSION` or `ZSH_VERSION`
|
|
22
|
+
* directly. Anything else, including a version captured in another variable,
|
|
23
|
+
* reports.
|
|
24
|
+
*/
|
|
25
|
+
import { DEFAULT_TRANSPARENT_WRAPPERS } from 'sh-ast/analyze';
|
|
26
|
+
import { BUILTIN_WRAPPER } from '../transparent-wrappers.js';
|
|
27
|
+
/**
|
|
28
|
+
* The wrappers followed to find the command an invocation runs:
|
|
29
|
+
* `sh-ast/analyze`'s defaults (which include `command`) plus Bash's `builtin`.
|
|
30
|
+
*/
|
|
31
|
+
const TRANSPARENT_WRAPPERS = [
|
|
32
|
+
...DEFAULT_TRANSPARENT_WRAPPERS,
|
|
33
|
+
BUILTIN_WRAPPER,
|
|
34
|
+
];
|
|
35
|
+
/** `sh-ast`'s `BinCmdOperator` codes for `&&` and `||`, which the lint view preserves. */
|
|
36
|
+
const AND_STMT = 11;
|
|
37
|
+
const OR_STMT = 12;
|
|
38
|
+
/**
|
|
39
|
+
* The source text of a word that reads a shell's own version variable: `$X`,
|
|
40
|
+
* `${X}`, `${X-}` or `${X:-}`, quoted or not. dash sets none of these variables.
|
|
41
|
+
*/
|
|
42
|
+
const SHELL_VERSION_WORD = /^"?\$(?:\{(?:BASH|KSH|ZSH)_VERSION(?::?-)?\}|(?:BASH|KSH|ZSH)_VERSION)"?$/u;
|
|
43
|
+
/**
|
|
44
|
+
* What `stmt` tests, when it is exactly `[ -n V ]`, `test -n V` (`set`) or the
|
|
45
|
+
* `-z` forms (`unset`) for a version variable `V`, not negated. Anything else
|
|
46
|
+
* is `undefined`.
|
|
47
|
+
*/
|
|
48
|
+
function shellVersionTest(sourceCode, stmt) {
|
|
49
|
+
if (stmt?.type !== 'Stmt' || stmt.negated || stmt.cmd?.type !== 'CallExpr')
|
|
50
|
+
return undefined;
|
|
51
|
+
const { args } = stmt.cmd;
|
|
52
|
+
const name = sourceCode.getCommandName(stmt.cmd);
|
|
53
|
+
if (args.length !== (name === '[' ? 4 : name === 'test' ? 3 : 0))
|
|
54
|
+
return undefined;
|
|
55
|
+
if (name === '[' && sourceCode.resolveWordText(args[3]) !== ']')
|
|
56
|
+
return undefined;
|
|
57
|
+
if (!SHELL_VERSION_WORD.test(sourceCode.getText(args[2])))
|
|
58
|
+
return undefined;
|
|
59
|
+
const operator = sourceCode.resolveWordText(args[1]);
|
|
60
|
+
if (operator === '-n')
|
|
61
|
+
return 'set';
|
|
62
|
+
return operator === '-z' ? 'unset' : undefined;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Whether `stmt` leaves the script or function unless a version variable is
|
|
66
|
+
* set: `[ -n V ] || exit` or `[ -z V ] && exit`, with `exit` or `return`.
|
|
67
|
+
*/
|
|
68
|
+
function leavesUnlessVersioned(sourceCode, stmt) {
|
|
69
|
+
if (stmt.type !== 'Stmt' || stmt.cmd?.type !== 'BinaryCmd')
|
|
70
|
+
return false;
|
|
71
|
+
const { op, x, y } = stmt.cmd;
|
|
72
|
+
const test = shellVersionTest(sourceCode, x);
|
|
73
|
+
if (!((op === OR_STMT && test === 'set') || (op === AND_STMT && test === 'unset')))
|
|
74
|
+
return false;
|
|
75
|
+
if (y?.cmd?.type !== 'CallExpr')
|
|
76
|
+
return false;
|
|
77
|
+
const name = sourceCode.getCommandName(y.cmd);
|
|
78
|
+
return name === 'exit' || name === 'return';
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Whether only a shell that sets a version variable can run `node`, because it
|
|
82
|
+
* sits in one of these:
|
|
83
|
+
*
|
|
84
|
+
* - the `then` body of `if [ -n V ]`, or the `else` body of `if [ -z V ]`;
|
|
85
|
+
* - the right side of `[ -n V ] && …` or `[ -z V ] || …`;
|
|
86
|
+
* - a statement list, after an earlier `[ -n V ] || exit` or `[ -z V ] && exit`
|
|
87
|
+
* in that list (the file, or a `{ … }` block such as a function body).
|
|
88
|
+
*/
|
|
89
|
+
function isGuarded(sourceCode, node) {
|
|
90
|
+
let child = node;
|
|
91
|
+
for (let parent = sourceCode.getParent(child); parent; parent = sourceCode.getParent(child)) {
|
|
92
|
+
if (parent.type === 'IfClause' && parent.cond.length === 1) {
|
|
93
|
+
const test = shellVersionTest(sourceCode, parent.cond[0]);
|
|
94
|
+
if (test === 'set' && parent.then.some((stmt) => stmt === child))
|
|
95
|
+
return true;
|
|
96
|
+
if (test === 'unset' && parent.else === child)
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
else if (parent.type === 'BinaryCmd' && parent.y === child) {
|
|
100
|
+
const test = shellVersionTest(sourceCode, parent.x);
|
|
101
|
+
if ((parent.op === AND_STMT && test === 'set') ||
|
|
102
|
+
(parent.op === OR_STMT && test === 'unset')) {
|
|
103
|
+
return true;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
else if (parent.type === 'File' || parent.type === 'Block') {
|
|
107
|
+
const index = parent.stmts.findIndex((stmt) => stmt === child);
|
|
108
|
+
if (parent.stmts.slice(0, index).some((stmt) => leavesUnlessVersioned(sourceCode, stmt))) {
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
child = parent;
|
|
113
|
+
}
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* The word that names `command` in `node`, when `command` is what `node`
|
|
118
|
+
* effectively runs and a shell without it can reach `node` (see the module
|
|
119
|
+
* comment), or `undefined` otherwise. Reporting this word rather than the whole
|
|
120
|
+
* command matters for `[[`: the parser ends that command at the first `&&` or
|
|
121
|
+
* `||`, so the node would cover an arbitrary part of the expression.
|
|
122
|
+
*
|
|
123
|
+
* @internal
|
|
124
|
+
*/
|
|
125
|
+
export function reachableCommandWord(sourceCode, node, command) {
|
|
126
|
+
const invoked = sourceCode.resolveInvokedCommand(node, {
|
|
127
|
+
transparentWrappers: TRANSPARENT_WRAPPERS,
|
|
128
|
+
});
|
|
129
|
+
if (invoked?.effective.static !== true || invoked.effective.text !== command)
|
|
130
|
+
return undefined;
|
|
131
|
+
if (isGuarded(sourceCode, node))
|
|
132
|
+
return undefined;
|
|
133
|
+
return node.args[invoked.chainIndices.at(-1) ?? 0];
|
|
134
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ShellNode } from '../../language/syntax-view.js';
|
|
2
|
+
import type { ShellRuleContext, ShellRuleVisitor } from '../define-shell-rule.js';
|
|
3
|
+
/**
|
|
4
|
+
* Builds the listeners that call `report(word)` for each operand word of a
|
|
5
|
+
* `shopt -s` command whose static text is `option`.
|
|
6
|
+
*
|
|
7
|
+
* @internal
|
|
8
|
+
*/
|
|
9
|
+
export declare function shoptEnableVisitor<MessageIds extends string>(context: ShellRuleContext<[], MessageIds>, option: string, report: (word: ShellNode) => void): ShellRuleVisitor;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared detection of `shopt -s NAME`, for the compat rules that gate one
|
|
3
|
+
* `shopt` option (`lastpipe`, `globstar`).
|
|
4
|
+
*
|
|
5
|
+
* What is read: a `shopt` command (after transparent wrappers such as
|
|
6
|
+
* `builtin`) whose option words, up to the first operand, include `-s`
|
|
7
|
+
* (possibly inside a cluster such as `-sq`). Its operands are the option
|
|
8
|
+
* names it turns on. `shopt -u` turns options off (so does a `-u` beside the
|
|
9
|
+
* `-s`), `shopt -o` names `set -o` options, and an operand that is not a
|
|
10
|
+
* static word cannot be read, so none of those count.
|
|
11
|
+
*/
|
|
12
|
+
import { DEFAULT_TRANSPARENT_WRAPPERS } from 'sh-ast/analyze';
|
|
13
|
+
import { BUILTIN_WRAPPER } from '../transparent-wrappers.js';
|
|
14
|
+
/**
|
|
15
|
+
* The wrappers followed to find the `shopt` command: `sh-ast/analyze`'s
|
|
16
|
+
* defaults (which include `command`) plus Bash's `builtin`.
|
|
17
|
+
*/
|
|
18
|
+
const TRANSPARENT_WRAPPERS = [
|
|
19
|
+
...DEFAULT_TRANSPARENT_WRAPPERS,
|
|
20
|
+
BUILTIN_WRAPPER,
|
|
21
|
+
];
|
|
22
|
+
/**
|
|
23
|
+
* Builds the listeners that call `report(word)` for each operand word of a
|
|
24
|
+
* `shopt -s` command whose static text is `option`.
|
|
25
|
+
*
|
|
26
|
+
* @internal
|
|
27
|
+
*/
|
|
28
|
+
export function shoptEnableVisitor(context, option, report) {
|
|
29
|
+
const { sourceCode } = context;
|
|
30
|
+
return {
|
|
31
|
+
CallExpr(node) {
|
|
32
|
+
const invoked = sourceCode.resolveInvokedCommand(node, {
|
|
33
|
+
transparentWrappers: TRANSPARENT_WRAPPERS,
|
|
34
|
+
});
|
|
35
|
+
if (invoked?.effective.static !== true || invoked.effective.text !== 'shopt')
|
|
36
|
+
return;
|
|
37
|
+
// `chainIndices` index `node.args` directly, so slice before filtering.
|
|
38
|
+
const words = node.args.slice((invoked.chainIndices.at(-1) ?? 0) + 1);
|
|
39
|
+
let enables = false;
|
|
40
|
+
let disables = false;
|
|
41
|
+
let namesShellOptions = false;
|
|
42
|
+
let index = 0;
|
|
43
|
+
for (; index < words.length; index++) {
|
|
44
|
+
const text = sourceCode.resolveWordText(words[index]);
|
|
45
|
+
if (text === null || !/^-./u.test(text))
|
|
46
|
+
break;
|
|
47
|
+
if (text === '--') {
|
|
48
|
+
index++;
|
|
49
|
+
break;
|
|
50
|
+
}
|
|
51
|
+
enables ||= text.includes('s');
|
|
52
|
+
disables ||= text.includes('u');
|
|
53
|
+
namesShellOptions ||= text.includes('o');
|
|
54
|
+
}
|
|
55
|
+
if (!enables || disables || namesShellOptions)
|
|
56
|
+
return;
|
|
57
|
+
for (const word of words.slice(index)) {
|
|
58
|
+
if (sourceCode.resolveWordText(word) === option)
|
|
59
|
+
report(word);
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The version-gate table every `compat/*` rule reads, and the one function
|
|
3
|
+
* that decides whether a gated feature is available to a file.
|
|
4
|
+
*
|
|
5
|
+
* A gate is data: a feature name mapped to the oldest Bash release that has
|
|
6
|
+
* it and whether POSIX sh has it. A new gate is a new row here, not new
|
|
7
|
+
* decision logic in a rule. The semantics of `languageOptions.shellVersion`
|
|
8
|
+
* and `languageOptions.dialect` for gating are defined once, by
|
|
9
|
+
* {@link isFeatureAvailable}, and documented for users in `docs/compat.md`.
|
|
10
|
+
*
|
|
11
|
+
* Every row is checked against real shells by `scripts/compat-matrix/run.sh`,
|
|
12
|
+
* which builds each Bash release and dash from source and runs one probe per
|
|
13
|
+
* feature on each; the observed results are the Evidence table in
|
|
14
|
+
* `docs/compat.md`. A changed row needs a probe there that shows the new
|
|
15
|
+
* boundary.
|
|
16
|
+
*
|
|
17
|
+
* @see https://tiswww.case.edu/php/chet/bash/NEWS — Bash release notes, the
|
|
18
|
+
* source of every minimum version below
|
|
19
|
+
* @see https://pubs.opengroup.org/onlinepubs/9799919799/utilities/V3_chap02.html
|
|
20
|
+
* — POSIX Shell Command Language: section 2.4 (reserved words) and section
|
|
21
|
+
* 2.9.1.4 (command search) leave the results of the `doubleBracket`,
|
|
22
|
+
* `letCommand` and `sourceCommand` rows unspecified
|
|
23
|
+
*/
|
|
24
|
+
import type { ShellLanguageOptions } from '../../language/shell-language-options.js';
|
|
25
|
+
/**
|
|
26
|
+
* The name of a gated feature: a row of {@link FEATURE_GATES}.
|
|
27
|
+
*
|
|
28
|
+
* @internal
|
|
29
|
+
*/
|
|
30
|
+
export type CompatFeature = 'appendAllRedirect' | 'associativeArrays' | 'caseAttributes' | 'caseFallthrough' | 'caseModification' | 'coproc' | 'declareGlobal' | 'doubleBracket' | 'epochVariables' | 'fdVariable' | 'globstar' | 'lastpipe' | 'letCommand' | 'mapfile' | 'nameref' | 'negativeArrayIndex' | 'nounsetEmptyArray' | 'parameterTransformation' | 'parameterTransformationCase' | 'parameterTransformationKeys' | 'parameterTransformationQuotedKeys' | 'pipeAll' | 'printfTimeFormat' | 'sourceCommand' | 'testVariableSet' | 'unicodeEscape' | 'unicodeEscapeLatin1' | 'waitNext';
|
|
31
|
+
/**
|
|
32
|
+
* The two `languageOptions` fields gating reads. Both the raw and the
|
|
33
|
+
* normalized options satisfy it; an unset `dialect` means `"bash"`, as it
|
|
34
|
+
* does for the parser.
|
|
35
|
+
*
|
|
36
|
+
* @internal
|
|
37
|
+
*/
|
|
38
|
+
export type GateLanguageOptions = Pick<ShellLanguageOptions, 'dialect' | 'shellVersion'>;
|
|
39
|
+
/**
|
|
40
|
+
* Whether `feature` is available to a file linted with `languageOptions`.
|
|
41
|
+
*
|
|
42
|
+
* - `dialect: "posix"`: available only when the gate's `posix` is `true`,
|
|
43
|
+
* at any `shellVersion` (a Bash-only feature is never available).
|
|
44
|
+
* - `dialect: "bash"` (the default) or `"bats"`: an unset `shellVersion`
|
|
45
|
+
* means the newest Bash, so every Bash feature is available. A set
|
|
46
|
+
* `shellVersion` makes the feature available when it is the gate's `bash`
|
|
47
|
+
* minimum or newer, comparing major then minor numerically (`"4"` is
|
|
48
|
+
* `4.0`; `"3.10"` is newer than `"3.2"`). A `null` minimum is available at
|
|
49
|
+
* every version.
|
|
50
|
+
* - `dialect: "mksh"` or `"zsh"`: the table carries no data for these
|
|
51
|
+
* shells, and their `shellVersion` numbers their own releases, not Bash's,
|
|
52
|
+
* so no gate applies and every feature is available.
|
|
53
|
+
*
|
|
54
|
+
* @internal
|
|
55
|
+
*/
|
|
56
|
+
export declare function isFeatureAvailable(feature: CompatFeature, languageOptions: GateLanguageOptions): boolean;
|
|
57
|
+
/**
|
|
58
|
+
* Human-readable words for a gate's report: what the feature needs
|
|
59
|
+
* (`required`, such as `bash 4.0 or newer`) and what the file targets
|
|
60
|
+
* (`target`, such as `bash 3.2` or `POSIX sh`). Compat rule messages
|
|
61
|
+
* interpolate both as `{{required}}` and `{{target}}`.
|
|
62
|
+
*
|
|
63
|
+
* @internal
|
|
64
|
+
*/
|
|
65
|
+
export interface GateDescription {
|
|
66
|
+
readonly required: string;
|
|
67
|
+
readonly target: string;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Describes why `feature` is unavailable under `languageOptions`, for a
|
|
71
|
+
* report's message data. Call it only when {@link isFeatureAvailable} is
|
|
72
|
+
* `false`.
|
|
73
|
+
*
|
|
74
|
+
* @internal
|
|
75
|
+
*/
|
|
76
|
+
export declare function describeGate(feature: CompatFeature, languageOptions: GateLanguageOptions): GateDescription;
|