@eslint-sh/eslint-plugin 0.7.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -8
- package/dist/command-harvest/builtins/bash.d.ts +1 -1
- package/dist/command-harvest/builtins/bash.js +1 -1
- package/dist/command-harvest/builtins/bats.d.ts +2 -0
- package/dist/command-harvest/builtins/bats.js +3 -0
- package/dist/command-harvest/wrapper-table.d.ts +3 -19
- package/dist/command-harvest/wrapper-table.js +8 -38
- package/dist/configs/bash-3-2.d.ts +7 -6
- package/dist/configs/bash-3-2.js +4 -3
- package/dist/configs/posix-portable.d.ts +1 -1
- package/dist/declaration-conventions/read-declared-commands.d.ts +2 -2
- package/dist/declaration-conventions/read-declared-commands.js +2 -2
- package/dist/eslint-plugin-syntax.d.ts +4 -0
- package/dist/eslint-plugin.d.ts +86 -40
- package/dist/index.d.ts +5 -2
- package/dist/index.js +4 -2
- package/dist/language/shell-language-options.d.ts +18 -15
- package/dist/language/shell-language-options.js +33 -26
- package/dist/language/shell-language.d.ts +10 -9
- package/dist/language/shell-language.js +18 -35
- package/dist/language/shell-source-code.d.ts +17 -9
- package/dist/language/shell-source-code.js +53 -12
- package/dist/rules/bash-variable-semantics.d.ts +22 -0
- package/dist/rules/bash-variable-semantics.js +41 -0
- package/dist/rules/compat/create-compat-rule.js +1 -1
- package/dist/rules/compat/declaration-options.d.ts +2 -2
- package/dist/rules/compat/declaration-options.js +5 -2
- package/dist/rules/compat/no-append-all-redirect.js +1 -1
- package/dist/rules/compat/no-associative-arrays.js +2 -2
- package/dist/rules/compat/no-brace-sequence-padding-step.js +4 -4
- package/dist/rules/compat/no-case-attributes.js +3 -3
- package/dist/rules/compat/no-case-fallthrough.js +2 -2
- package/dist/rules/compat/no-case-modification.js +2 -2
- package/dist/rules/compat/no-coproc.js +1 -1
- package/dist/rules/compat/no-declare-g.js +3 -3
- package/dist/rules/compat/no-double-bracket.js +2 -2
- package/dist/rules/compat/no-epoch-variables.js +2 -2
- package/dist/rules/compat/no-fd-variable.js +1 -1
- package/dist/rules/compat/no-globstar.js +1 -1
- package/dist/rules/compat/no-lastpipe-semantics.js +2 -2
- package/dist/rules/compat/no-let.js +2 -1
- package/dist/rules/compat/no-mapfile.js +4 -2
- package/dist/rules/compat/no-nameref.js +3 -3
- package/dist/rules/compat/no-negative-array-index.js +39 -9
- package/dist/rules/compat/no-nounset-empty-array.js +15 -4
- package/dist/rules/compat/no-parameter-transformation.js +2 -2
- package/dist/rules/compat/no-pipe-all.js +1 -1
- package/dist/rules/compat/no-printf-time-format.js +4 -2
- package/dist/rules/compat/no-source.js +2 -1
- package/dist/rules/compat/no-test-v.js +4 -2
- package/dist/rules/compat/no-unicode-escape.js +2 -2
- package/dist/rules/compat/no-wait-n.js +4 -2
- package/dist/rules/compat/nounset-array-proof.d.ts +8 -0
- package/dist/rules/compat/nounset-array-proof.js +563 -0
- package/dist/rules/compat/posix-absent-command.d.ts +7 -8
- package/dist/rules/compat/posix-absent-command.js +17 -15
- package/dist/rules/compat/shopt-options.js +3 -1
- package/dist/rules/compat/version-gates.d.ts +9 -9
- package/dist/rules/compat/version-gates.js +13 -13
- package/dist/rules/create-wrapper-rule.js +12 -7
- package/dist/rules/no-dynamic-invocation.d.ts +0 -8
- package/dist/rules/no-dynamic-invocation.js +7 -15
- package/dist/rules/no-eval.js +3 -1
- package/dist/rules/no-masked-exit-status.js +15 -8
- package/dist/rules/no-secret-env-reads.d.ts +4 -3
- package/dist/rules/no-secret-env-reads.js +37 -14
- package/dist/rules/no-secret-flags.d.ts +2 -1
- package/dist/rules/no-secret-flags.js +7 -8
- package/dist/rules/no-unchecked-cd.js +3 -1
- package/dist/rules/no-undeclared-commands.d.ts +1 -15
- package/dist/rules/no-undeclared-commands.js +3 -15
- package/dist/rules/no-unused-declarations.d.ts +1 -18
- package/dist/rules/no-unused-declarations.js +9 -34
- package/dist/rules/prefer-local.js +4 -3
- package/dist/rules/quote-expansions.js +3 -28
- package/dist/rules/require-network-wrapper.d.ts +2 -17
- package/dist/rules/require-network-wrapper.js +33 -4
- package/dist/rules/resolve-declaration-convention.d.ts +5 -6
- package/dist/rules/resolve-declaration-convention.js +5 -6
- package/dist/rules/resolve-network-wrapper.d.ts +19 -0
- package/dist/rules/resolve-network-wrapper.js +57 -26
- package/dist/rules/resolve-transparent-wrappers.d.ts +43 -0
- package/dist/rules/resolve-transparent-wrappers.js +98 -0
- package/dist/rules/set-options.d.ts +2 -1
- package/dist/rules/set-options.js +17 -5
- package/dist/rules/shebang-interpreter.js +4 -2
- package/dist/rules/transparent-wrappers.d.ts +4 -3
- package/dist/rules/transparent-wrappers.js +5 -4
- package/dist/rules/user-pattern.d.ts +5 -3
- package/dist/rules/user-pattern.js +6 -4
- package/dist/syntax/index.d.ts +4 -0
- package/dist/syntax/index.js +4 -0
- package/docs/compat.md +76 -27
- package/docs/custom-rule-tutorial.md +9 -3
- package/docs/examples/no-listed-commands-rule.ts +2 -2
- package/docs/rules/README.md +36 -4
- package/docs/rules/_template.md +2 -2
- package/docs/rules/compat/no-append-all-redirect.md +12 -12
- package/docs/rules/compat/no-associative-arrays.md +14 -14
- package/docs/rules/compat/no-brace-sequence-padding-step.md +12 -12
- package/docs/rules/compat/no-case-attributes.md +13 -13
- package/docs/rules/compat/no-case-fallthrough.md +12 -12
- package/docs/rules/compat/no-case-modification.md +11 -11
- package/docs/rules/compat/no-coproc.md +13 -13
- package/docs/rules/compat/no-declare-g.md +13 -13
- package/docs/rules/compat/no-double-bracket.md +2 -2
- package/docs/rules/compat/no-epoch-variables.md +13 -13
- package/docs/rules/compat/no-fd-variable.md +12 -12
- package/docs/rules/compat/no-globstar.md +13 -13
- package/docs/rules/compat/no-lastpipe-semantics.md +13 -13
- package/docs/rules/compat/no-let.md +2 -2
- package/docs/rules/compat/no-mapfile.md +16 -16
- package/docs/rules/compat/no-nameref.md +13 -13
- package/docs/rules/compat/no-negative-array-index.md +14 -14
- package/docs/rules/compat/no-nounset-empty-array.md +72 -17
- package/docs/rules/compat/no-parameter-transformation.md +19 -19
- package/docs/rules/compat/no-pipe-all.md +13 -13
- package/docs/rules/compat/no-printf-time-format.md +13 -13
- package/docs/rules/compat/no-source.md +2 -2
- package/docs/rules/compat/no-test-v.md +12 -12
- package/docs/rules/compat/no-unicode-escape.md +13 -13
- package/docs/rules/compat/no-wait-n.md +13 -13
- package/docs/rules/no-dynamic-invocation.md +17 -12
- package/docs/rules/no-eval.md +6 -1
- package/docs/rules/no-masked-exit-status.md +4 -0
- package/docs/rules/no-secret-env-reads.md +34 -3
- package/docs/rules/no-secret-flags.md +19 -2
- package/docs/rules/no-unchecked-cd.md +9 -2
- package/docs/rules/no-undeclared-commands.md +16 -15
- package/docs/rules/no-unused-declarations.md +17 -20
- package/docs/rules/prefer-local.md +20 -2
- package/docs/rules/quote-expansions.md +8 -5
- package/docs/rules/require-network-wrapper.md +22 -17
- package/docs/rules/require-shebang.md +10 -4
- package/docs/rules/require-strict-mode.md +6 -0
- package/docs/shellcheck.md +9 -5
- package/docs/transparent-wrappers.md +97 -0
- package/docs/wrapper-rules.md +9 -0
- package/package.json +2 -2
- package/dist/language/end-of-input-marker.d.ts +0 -29
- package/dist/language/end-of-input-marker.js +0 -88
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
export type ShellDialect = 'bash' | 'posix' | 'mksh' | 'bats' | 'zsh';
|
|
4
4
|
/**
|
|
5
5
|
* `languageOptions` accepted by {@link ShellLanguage}: `dialect` selects
|
|
6
|
-
* mvdan/sh's `LangVariant` through a consumer-owned mapping (default `"bash"`); `
|
|
6
|
+
* mvdan/sh's `LangVariant` through a consumer-owned mapping (default `"bash"`); `minBashVersion`
|
|
7
7
|
* is consumed by the rule layer (e.g. the bash-3.2 compatibility pack),
|
|
8
8
|
* never by the parser itself.
|
|
9
9
|
*
|
|
@@ -19,13 +19,14 @@ export type ShellDialect = 'bash' | 'posix' | 'mksh' | 'bats' | 'zsh';
|
|
|
19
19
|
export interface ShellLanguageOptions extends Record<string, unknown> {
|
|
20
20
|
readonly dialect?: ShellDialect;
|
|
21
21
|
/**
|
|
22
|
-
* The Bash version (`MAJOR[.MINOR]`, such as `"3.2"`) the
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
* version.
|
|
22
|
+
* The minimum Bash version (`MAJOR[.MINOR]`, such as `"3.2"`) the
|
|
23
|
+
* script must support, including every newer Bash. Only valid with the
|
|
24
|
+
* `bash` or `bats` dialect (the default is `bash`). The parser ignores
|
|
25
|
+
* it; `compat/*` rules report features unavailable at this floor. Unset
|
|
26
|
+
* means no Bash version constraint. Versions compare numerically by
|
|
27
|
+
* major then minor, with no upper version limit.
|
|
27
28
|
*/
|
|
28
|
-
readonly
|
|
29
|
+
readonly minBashVersion?: string;
|
|
29
30
|
/**
|
|
30
31
|
* The prefix directive/inline-config comments must start with — e.g. the
|
|
31
32
|
* default `"eslint"` recognizes `# eslint-disable-next-line ...` and
|
|
@@ -38,14 +39,14 @@ export interface ShellLanguageOptions extends Record<string, unknown> {
|
|
|
38
39
|
}
|
|
39
40
|
/**
|
|
40
41
|
* The plain-data shape {@link NormalizedShellLanguageOptions.toJSON} returns:
|
|
41
|
-
* `dialect` and `directivePrefix` always present (both defaulted), `
|
|
42
|
+
* `dialect` and `directivePrefix` always present (both defaulted), `minBashVersion`
|
|
42
43
|
* present only when the caller supplied one. Keys are always emitted in this
|
|
43
44
|
* fixed order so that two logically-identical inputs serialize identically
|
|
44
45
|
* regardless of the key order the caller happened to use — the property this
|
|
45
46
|
* type exists to guarantee for cache-key stability (see
|
|
46
47
|
* {@link NormalizedShellLanguageOptions}). `directivePrefix` is included
|
|
47
|
-
* unconditionally (like `dialect`, unlike `
|
|
48
|
-
* `dialect` and unlike `
|
|
48
|
+
* unconditionally (like `dialect`, unlike `minBashVersion`) because — like
|
|
49
|
+
* `dialect` and unlike `minBashVersion` — it always has a concrete effect on
|
|
49
50
|
* linting results (which comments the `SourceCode` recognizes as directives)
|
|
50
51
|
* whether or not the caller supplied one, so two configs that differ only in
|
|
51
52
|
* an *implicit* default vs. an *explicit* `directivePrefix: "eslint"` must
|
|
@@ -56,7 +57,7 @@ export interface ShellLanguageOptions extends Record<string, unknown> {
|
|
|
56
57
|
export interface ShellLanguageOptionsJSON {
|
|
57
58
|
readonly dialect: ShellDialect;
|
|
58
59
|
readonly directivePrefix: string;
|
|
59
|
-
readonly
|
|
60
|
+
readonly minBashVersion?: string;
|
|
60
61
|
}
|
|
61
62
|
/**
|
|
62
63
|
* The result of {@link ShellLanguage.normalizeLanguageOptions}: `dialect` and
|
|
@@ -73,19 +74,21 @@ export interface ShellLanguageOptionsJSON {
|
|
|
73
74
|
export interface NormalizedShellLanguageOptions extends Record<string, unknown> {
|
|
74
75
|
readonly dialect: ShellDialect;
|
|
75
76
|
readonly directivePrefix: string;
|
|
76
|
-
readonly
|
|
77
|
+
readonly minBashVersion?: string;
|
|
77
78
|
toJSON(): ShellLanguageOptionsJSON;
|
|
78
79
|
}
|
|
79
80
|
/**
|
|
80
81
|
* Validates `languageOptions` for {@link ShellLanguage}. Throws a
|
|
81
82
|
* `TypeError` when `dialect` is present but not one of the five supported
|
|
82
|
-
* dialects, when `
|
|
83
|
-
* match the `MAJOR[.MINOR]` numeric shape (see {@link
|
|
83
|
+
* dialects, when `minBashVersion` is present but not a string or does not
|
|
84
|
+
* match the `MAJOR[.MINOR]` numeric shape (see {@link MIN_BASH_VERSION_PATTERN};
|
|
84
85
|
* e.g. `''` and `'not-a-version'` are rejected, but a shape-valid,
|
|
85
86
|
* nonexistent version like `'99.99'` is accepted — shape is validated, not
|
|
86
87
|
* membership in a known-versions list), or when `directivePrefix` is present
|
|
87
88
|
* but not a valid directive-comment label (see {@link DIRECTIVE_PREFIX_PATTERN}).
|
|
88
|
-
*
|
|
89
|
+
* The Bash floor is rejected for dialects other than `bash` and `bats`.
|
|
90
|
+
* The removed `shellVersion` key throws with migration guidance, even if
|
|
91
|
+
* its value is undefined. Other unknown keys are silently ignored.
|
|
89
92
|
*
|
|
90
93
|
* The parameter is treated as `Record<string, unknown>` internally (rather
|
|
91
94
|
* than trusting {@link ShellLanguageOptions}'s declared field types) because
|
|
@@ -30,31 +30,27 @@ function isValidDirectivePrefix(value) {
|
|
|
30
30
|
return typeof value === 'string' && DIRECTIVE_PREFIX_PATTERN.test(value);
|
|
31
31
|
}
|
|
32
32
|
/**
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
* string — including `''` and `'not-a-version'` — with zero feedback). This
|
|
37
|
-
* validates *shape only*, not membership in a known-versions list: a
|
|
38
|
-
* shape-valid but nonexistent version like `'99.99'` is intentionally
|
|
39
|
-
* accepted, per this issue's acceptance criteria and non-goals (per-dialect
|
|
40
|
-
* known-version allowlists are out of scope). How rules consume
|
|
41
|
-
* `shellVersion` is defined by the `compat/*` rules' gate
|
|
42
|
-
* (`src/rules/compat/version-gates.ts`, documented in `docs/compat.md`).
|
|
33
|
+
* The Bash floor accepts numeric MAJOR[.MINOR] values, including future
|
|
34
|
+
* releases. Validation checks shape rather than a release allowlist;
|
|
35
|
+
* compatibility rules compare the floor with each feature's first release.
|
|
43
36
|
*/
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
37
|
+
const MIN_BASH_VERSION_PATTERN = /^\d+(?:\.\d+)?$/u;
|
|
38
|
+
/** Whether a Bash floor has the numeric shape required for comparison. */
|
|
39
|
+
function isValidMinBashVersionShape(value) {
|
|
40
|
+
return MIN_BASH_VERSION_PATTERN.test(value);
|
|
47
41
|
}
|
|
48
42
|
/**
|
|
49
43
|
* Validates `languageOptions` for {@link ShellLanguage}. Throws a
|
|
50
44
|
* `TypeError` when `dialect` is present but not one of the five supported
|
|
51
|
-
* dialects, when `
|
|
52
|
-
* match the `MAJOR[.MINOR]` numeric shape (see {@link
|
|
45
|
+
* dialects, when `minBashVersion` is present but not a string or does not
|
|
46
|
+
* match the `MAJOR[.MINOR]` numeric shape (see {@link MIN_BASH_VERSION_PATTERN};
|
|
53
47
|
* e.g. `''` and `'not-a-version'` are rejected, but a shape-valid,
|
|
54
48
|
* nonexistent version like `'99.99'` is accepted — shape is validated, not
|
|
55
49
|
* membership in a known-versions list), or when `directivePrefix` is present
|
|
56
50
|
* but not a valid directive-comment label (see {@link DIRECTIVE_PREFIX_PATTERN}).
|
|
57
|
-
*
|
|
51
|
+
* The Bash floor is rejected for dialects other than `bash` and `bats`.
|
|
52
|
+
* The removed `shellVersion` key throws with migration guidance, even if
|
|
53
|
+
* its value is undefined. Other unknown keys are silently ignored.
|
|
58
54
|
*
|
|
59
55
|
* The parameter is treated as `Record<string, unknown>` internally (rather
|
|
60
56
|
* than trusting {@link ShellLanguageOptions}'s declared field types) because
|
|
@@ -64,15 +60,26 @@ function isValidShellVersionShape(value) {
|
|
|
64
60
|
* @public
|
|
65
61
|
*/
|
|
66
62
|
export function validateShellLanguageOptions(languageOptions) {
|
|
67
|
-
|
|
63
|
+
// A removed key must not disappear during normalization: that would
|
|
64
|
+
// silently disable the consumer's compatibility requirement.
|
|
65
|
+
if ('shellVersion' in languageOptions) {
|
|
66
|
+
throw new TypeError('@eslint-sh/eslint-plugin: languageOptions.shellVersion has been removed. Use languageOptions.minBashVersion with dialect "bash" or "bats" instead.');
|
|
67
|
+
}
|
|
68
|
+
const { dialect, minBashVersion, directivePrefix } = languageOptions;
|
|
68
69
|
if (dialect !== undefined && !isShellDialect(dialect)) {
|
|
69
70
|
throw new TypeError(`@eslint-sh/eslint-plugin: languageOptions.dialect must be one of ${SUPPORTED_SHELL_DIALECTS.join(', ')}; received ${JSON.stringify(dialect)}.`);
|
|
70
71
|
}
|
|
71
|
-
if (
|
|
72
|
-
|
|
72
|
+
if (minBashVersion !== undefined &&
|
|
73
|
+
dialect !== undefined &&
|
|
74
|
+
dialect !== 'bash' &&
|
|
75
|
+
dialect !== 'bats') {
|
|
76
|
+
throw new TypeError(`@eslint-sh/eslint-plugin: languageOptions.minBashVersion applies only to bash and bats; dialect is "${dialect}". Remove minBashVersion or set dialect to "bash" or "bats".`);
|
|
77
|
+
}
|
|
78
|
+
if (minBashVersion !== undefined && typeof minBashVersion !== 'string') {
|
|
79
|
+
throw new TypeError(`@eslint-sh/eslint-plugin: languageOptions.minBashVersion must be a string (e.g. "3.2"); received ${JSON.stringify(minBashVersion)}.`);
|
|
73
80
|
}
|
|
74
|
-
if (typeof
|
|
75
|
-
throw new TypeError(`@eslint-sh/eslint-plugin: languageOptions.
|
|
81
|
+
if (typeof minBashVersion === 'string' && !isValidMinBashVersionShape(minBashVersion)) {
|
|
82
|
+
throw new TypeError(`@eslint-sh/eslint-plugin: languageOptions.minBashVersion must match the shape MAJOR[.MINOR] (e.g. "3.2", "5"); received ${JSON.stringify(minBashVersion)}.`);
|
|
76
83
|
}
|
|
77
84
|
if (directivePrefix !== undefined && !isValidDirectivePrefix(directivePrefix)) {
|
|
78
85
|
throw new TypeError(`@eslint-sh/eslint-plugin: languageOptions.directivePrefix must be one or more lowercase, hyphen-separated words (e.g. "eslint", "shellcheck-sh"); received ${JSON.stringify(directivePrefix)}.`);
|
|
@@ -91,18 +98,18 @@ export function validateShellLanguageOptions(languageOptions) {
|
|
|
91
98
|
export function normalizeShellLanguageOptions(languageOptions) {
|
|
92
99
|
const dialect = languageOptions.dialect ?? 'bash';
|
|
93
100
|
const directivePrefix = languageOptions.directivePrefix ?? DEFAULT_DIRECTIVE_PREFIX;
|
|
94
|
-
const
|
|
101
|
+
const minBashVersion = languageOptions.minBashVersion;
|
|
95
102
|
return {
|
|
96
103
|
dialect,
|
|
97
104
|
directivePrefix,
|
|
98
105
|
// `exactOptionalPropertyTypes` forbids assigning `undefined` to an
|
|
99
106
|
// optional property directly — omit the key entirely rather than set it
|
|
100
|
-
// to `undefined` when the caller didn't supply a
|
|
101
|
-
...(
|
|
107
|
+
// to `undefined` when the caller didn't supply a minBashVersion.
|
|
108
|
+
...(minBashVersion === undefined ? {} : { minBashVersion }),
|
|
102
109
|
toJSON() {
|
|
103
|
-
return
|
|
110
|
+
return minBashVersion === undefined
|
|
104
111
|
? { dialect, directivePrefix }
|
|
105
|
-
: { dialect, directivePrefix,
|
|
112
|
+
: { dialect, directivePrefix, minBashVersion };
|
|
106
113
|
},
|
|
107
114
|
};
|
|
108
115
|
}
|
|
@@ -26,11 +26,12 @@ export declare class ShellLanguage implements Language {
|
|
|
26
26
|
readonly defaultLanguageOptions: ShellLanguageOptions;
|
|
27
27
|
/**
|
|
28
28
|
* Validates `languageOptions.dialect` (must be one of the five supported
|
|
29
|
-
* dialects, if present), `languageOptions.
|
|
30
|
-
* string matching the `MAJOR[.MINOR]` numeric shape, if present
|
|
29
|
+
* dialects, if present), `languageOptions.minBashVersion` (must be a
|
|
30
|
+
* string matching the `MAJOR[.MINOR]` numeric shape, if present, and
|
|
31
|
+
* applies only to `bash` and `bats`), and
|
|
31
32
|
* `languageOptions.directivePrefix` (must be a valid directive-comment
|
|
32
|
-
* label, if present).
|
|
33
|
-
*
|
|
33
|
+
* label, if present). The removed `shellVersion` key throws with
|
|
34
|
+
* migration guidance; other unknown keys are silently ignored. See `shell-language-options.ts`'s
|
|
34
35
|
* `validateShellLanguageOptions` for the full rationale.
|
|
35
36
|
*/
|
|
36
37
|
validateLanguageOptions(languageOptions: ShellLanguageOptions): void;
|
|
@@ -64,9 +65,9 @@ export declare class ShellLanguage implements Language {
|
|
|
64
65
|
* A file whose extension marks it as a known non-shell source type (for
|
|
65
66
|
* example `.js` or `.json`) is never shell-parsed: it fails with a single
|
|
66
67
|
* diagnostic explaining that a `language: 'sh/sh'` config entry without
|
|
67
|
-
* `files` reached it, and how to scope that entry.
|
|
68
|
-
*
|
|
69
|
-
*
|
|
68
|
+
* `files` reached it, and how to scope that entry. Unicode characters remain
|
|
69
|
+
* source text. A native nesting-budget refusal returns no partial tree and
|
|
70
|
+
* becomes a fatal resource diagnostic at 1:1.
|
|
70
71
|
*/
|
|
71
72
|
parse(file: File, context: {
|
|
72
73
|
languageOptions: ShellLanguageOptions;
|
|
@@ -81,8 +82,8 @@ export declare class ShellLanguage implements Language {
|
|
|
81
82
|
* present, but `?? 'eslint'` stays as an explicit, honest fallback for
|
|
82
83
|
* direct construction (e.g. tests that skip the `Language` normalization
|
|
83
84
|
* step), mirroring `parse()`'s `dialect` default above. `dialect` and
|
|
84
|
-
* `
|
|
85
|
-
* through `sourceCode.dialect` / `sourceCode.
|
|
85
|
+
* `minBashVersion` are forwarded so rules read the same normalized values
|
|
86
|
+
* through `sourceCode.dialect` / `sourceCode.minBashVersion`.
|
|
86
87
|
*/
|
|
87
88
|
createSourceCode(file: File, parseResult: OkParseResult<ShellFile>, context: {
|
|
88
89
|
languageOptions: ShellLanguageOptions;
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
/** Adapts upstream parsing and diagnostics to ESLint's synchronous Language contract. */
|
|
2
|
-
import { LangBash, LangBats, LangError, LangMirBSDKorn, LangPOSIX, LangZsh, ParseError, parseWithPositionsSync, } from 'sh-ast';
|
|
3
|
-
import { firstDroppedTextIndex } from './end-of-input-marker.js';
|
|
2
|
+
import { LangBash, LangBats, LangError, LangMirBSDKorn, LangPOSIX, LangZsh, ParseError, ShParseMaxDepthError, parseWithPositionsSync, } from 'sh-ast';
|
|
4
3
|
import { nonShellFileError } from './non-shell-file-guard.js';
|
|
5
4
|
import { matchesSelectorClass as matchesShellSelectorClass } from './selector-classes.js';
|
|
6
5
|
import { ShellSourceCode } from './shell-source-code.js';
|
|
@@ -50,28 +49,15 @@ function toFileError(error, source) {
|
|
|
50
49
|
column: location.column,
|
|
51
50
|
};
|
|
52
51
|
}
|
|
52
|
+
// A native budget refusal carries no partial tree or source position. It is
|
|
53
|
+
// an input resource limit, not a broken parser runtime; later files may parse.
|
|
54
|
+
if (error instanceof ShParseMaxDepthError) {
|
|
55
|
+
return { message: error.message, line: 1, column: 1 };
|
|
56
|
+
}
|
|
53
57
|
// Bridge/input failures carry no asserted syntax position. The fallback is an ESLint presentation policy.
|
|
54
58
|
const detail = error instanceof Error ? error.message : String(error);
|
|
55
59
|
return { message: `${PARSER_RUNTIME_FAILURE_PREFIX}${detail}`, line: 1, column: 1 };
|
|
56
60
|
}
|
|
57
|
-
/**
|
|
58
|
-
* The fatal parse error for a source in which the parser drops text at a U+0080, at that
|
|
59
|
-
* character, or `undefined` when nothing is dropped. Checked before parsing so a silently
|
|
60
|
-
* truncated tree never reaches the rules. A U+0080 the parser reads as an ordinary character
|
|
61
|
-
* (inside a quoted-delimiter heredoc body, after a backslash) is not an error; one the parser
|
|
62
|
-
* rejects (inside quotes or an expansion) is reported by the parser itself.
|
|
63
|
-
*/
|
|
64
|
-
function endOfInputMarkerError(source, options) {
|
|
65
|
-
const index = firstDroppedTextIndex(source, options);
|
|
66
|
-
if (index === undefined)
|
|
67
|
-
return undefined;
|
|
68
|
-
const location = new SourceCoordinates(source).atIndex(index);
|
|
69
|
-
return {
|
|
70
|
-
message: 'the character U+0080 is not supported: the parser reads it as end of input',
|
|
71
|
-
line: location.line,
|
|
72
|
-
column: location.column,
|
|
73
|
-
};
|
|
74
|
-
}
|
|
75
61
|
/**
|
|
76
62
|
* `ShellLanguage` — the `@eslint/core` `Language` implementation for shell
|
|
77
63
|
* scripts. Registered by the plugin as `languages.sh`, so consumers select
|
|
@@ -96,11 +82,12 @@ export class ShellLanguage {
|
|
|
96
82
|
defaultLanguageOptions = { dialect: 'bash' };
|
|
97
83
|
/**
|
|
98
84
|
* Validates `languageOptions.dialect` (must be one of the five supported
|
|
99
|
-
* dialects, if present), `languageOptions.
|
|
100
|
-
* string matching the `MAJOR[.MINOR]` numeric shape, if present
|
|
85
|
+
* dialects, if present), `languageOptions.minBashVersion` (must be a
|
|
86
|
+
* string matching the `MAJOR[.MINOR]` numeric shape, if present, and
|
|
87
|
+
* applies only to `bash` and `bats`), and
|
|
101
88
|
* `languageOptions.directivePrefix` (must be a valid directive-comment
|
|
102
|
-
* label, if present).
|
|
103
|
-
*
|
|
89
|
+
* label, if present). The removed `shellVersion` key throws with
|
|
90
|
+
* migration guidance; other unknown keys are silently ignored. See `shell-language-options.ts`'s
|
|
104
91
|
* `validateShellLanguageOptions` for the full rationale.
|
|
105
92
|
*/
|
|
106
93
|
validateLanguageOptions(languageOptions) {
|
|
@@ -140,9 +127,9 @@ export class ShellLanguage {
|
|
|
140
127
|
* A file whose extension marks it as a known non-shell source type (for
|
|
141
128
|
* example `.js` or `.json`) is never shell-parsed: it fails with a single
|
|
142
129
|
* diagnostic explaining that a `language: 'sh/sh'` config entry without
|
|
143
|
-
* `files` reached it, and how to scope that entry.
|
|
144
|
-
*
|
|
145
|
-
*
|
|
130
|
+
* `files` reached it, and how to scope that entry. Unicode characters remain
|
|
131
|
+
* source text. A native nesting-budget refusal returns no partial tree and
|
|
132
|
+
* becomes a fatal resource diagnostic at 1:1.
|
|
146
133
|
*/
|
|
147
134
|
parse(file, context) {
|
|
148
135
|
const misappliedError = nonShellFileError(file.path);
|
|
@@ -156,10 +143,6 @@ export class ShellLanguage {
|
|
|
156
143
|
Filename: file.path,
|
|
157
144
|
KeepComments: true,
|
|
158
145
|
};
|
|
159
|
-
const unsupported = endOfInputMarkerError(text, parseOptions);
|
|
160
|
-
if (unsupported !== undefined) {
|
|
161
|
-
return { ok: false, errors: [unsupported] };
|
|
162
|
-
}
|
|
163
146
|
try {
|
|
164
147
|
const parsed = parseWithPositionsSync(text, parseOptions);
|
|
165
148
|
const ast = adaptSyntaxFile(parsed.File, text, parsed.Positions);
|
|
@@ -179,8 +162,8 @@ export class ShellLanguage {
|
|
|
179
162
|
* present, but `?? 'eslint'` stays as an explicit, honest fallback for
|
|
180
163
|
* direct construction (e.g. tests that skip the `Language` normalization
|
|
181
164
|
* step), mirroring `parse()`'s `dialect` default above. `dialect` and
|
|
182
|
-
* `
|
|
183
|
-
* through `sourceCode.dialect` / `sourceCode.
|
|
165
|
+
* `minBashVersion` are forwarded so rules read the same normalized values
|
|
166
|
+
* through `sourceCode.dialect` / `sourceCode.minBashVersion`.
|
|
184
167
|
*/
|
|
185
168
|
createSourceCode(file, parseResult, context) {
|
|
186
169
|
return new ShellSourceCode({
|
|
@@ -188,9 +171,9 @@ export class ShellLanguage {
|
|
|
188
171
|
ast: parseResult.ast,
|
|
189
172
|
directivePrefix: context.languageOptions.directivePrefix ?? 'eslint',
|
|
190
173
|
dialect: context.languageOptions.dialect ?? 'bash',
|
|
191
|
-
...(context.languageOptions.
|
|
174
|
+
...(context.languageOptions.minBashVersion === undefined
|
|
192
175
|
? {}
|
|
193
|
-
: {
|
|
176
|
+
: { minBashVersion: context.languageOptions.minBashVersion }),
|
|
194
177
|
});
|
|
195
178
|
}
|
|
196
179
|
}
|
|
@@ -37,7 +37,7 @@ import type { ShellCommandSite } from './command-resolution.js';
|
|
|
37
37
|
* the string `jq`?", "which command does this invocation run?", and "which
|
|
38
38
|
* commands does this file run, and where?" by delegating to
|
|
39
39
|
* `sh-ast/analyze`. They never reinterpret words themselves, so every rule
|
|
40
|
-
* gets the same answer for the same syntax. `dialect` and `
|
|
40
|
+
* gets the same answer for the same syntax. `dialect` and `minBashVersion`
|
|
41
41
|
* expose the file's normalized language options.
|
|
42
42
|
*
|
|
43
43
|
* Syntax access (`getSyntax`, `getViewNode`, `getIndexFromSyntaxPos`,
|
|
@@ -64,7 +64,7 @@ export declare class ShellSourceCode extends TextSourceCodeBase<{
|
|
|
64
64
|
ast: ShFile;
|
|
65
65
|
directivePrefix?: string;
|
|
66
66
|
dialect?: ShellDialect;
|
|
67
|
-
|
|
67
|
+
minBashVersion?: string;
|
|
68
68
|
});
|
|
69
69
|
/**
|
|
70
70
|
* The shell dialect this file was parsed as: the normalized
|
|
@@ -73,11 +73,12 @@ export declare class ShellSourceCode extends TextSourceCodeBase<{
|
|
|
73
73
|
*/
|
|
74
74
|
get dialect(): ShellDialect;
|
|
75
75
|
/**
|
|
76
|
-
* The configured `languageOptions.
|
|
76
|
+
* The configured `languageOptions.minBashVersion` (a `MAJOR[.MINOR]` string
|
|
77
77
|
* such as `"3.2"`), or `undefined` when the config does not set one. The
|
|
78
|
-
*
|
|
78
|
+
* script must support this Bash and every newer release. The parser never
|
|
79
|
+
* reads it; compatibility rules use it as a floor with no upper limit.
|
|
79
80
|
*/
|
|
80
|
-
get
|
|
81
|
+
get minBashVersion(): string | undefined;
|
|
81
82
|
/**
|
|
82
83
|
* The one-based location of a UTF-16 `index` in this file's text, where
|
|
83
84
|
* `index` may be anywhere from 0 to `text.length`. CRLF counts as one line
|
|
@@ -166,13 +167,19 @@ export declare class ShellSourceCode extends TextSourceCodeBase<{
|
|
|
166
167
|
* effective command's name as
|
|
167
168
|
* `res.effective.static ? res.effective.text : null`.
|
|
168
169
|
* `sudo -u x "$prog"` and `exec "$@"` have a non-static `effective` with
|
|
169
|
-
* reason `expansion`; `env -S '…'`
|
|
170
|
+
* reason `expansion`; `env -S '…'` and `bash -c '…'` have reason
|
|
171
|
+
* `embedded-command`; `chain`
|
|
170
172
|
* lists every word resolved along the way, outermost wrapper first, and
|
|
171
173
|
* `chainIndices` gives each entry's position in the command's `argv` (as
|
|
172
174
|
* {@link ShellSourceCode.getCommandSites} reports it). When `effective` is
|
|
173
175
|
* static, the effective command's arguments are
|
|
174
176
|
* `argv.slice(chainIndices.at(-1)! + 1)`: every entry after the effective
|
|
175
|
-
* command's own index.
|
|
177
|
+
* command's own index. For an interpreter command string the final chain
|
|
178
|
+
* index instead locates the selector flag; the interpreter is in `chain.at(-2)`.
|
|
179
|
+
* `embeddedCommandInterpreters: []` retains the interpreter as the static
|
|
180
|
+
* effective command when a policy needs program identity. Utility wrappers
|
|
181
|
+
* can match absolute-path basenames; `shellBuiltin: true` entries require
|
|
182
|
+
* exact names, so external paths do not acquire in-process semantics.
|
|
176
183
|
*
|
|
177
184
|
* Enumeration runs once per file, the first time any command helper is
|
|
178
185
|
* called. If the file's nesting exceeds `enumerateCommands`'s recursion
|
|
@@ -333,8 +340,9 @@ export declare class ShellSourceCode extends TextSourceCodeBase<{
|
|
|
333
340
|
* @throws TypeError when `node` has no original record in this file (see
|
|
334
341
|
* {@link ShellSourceCode.getEditableSyntax}), when `edited` is not a
|
|
335
342
|
* printable root (the message names its type), or when the printer's output
|
|
336
|
-
* is not valid UTF-8
|
|
337
|
-
*
|
|
343
|
+
* is not valid UTF-8, or the edited syntax contains literal vertical tabs or
|
|
344
|
+
* form feeds that the printer cannot preserve. Non-UTF-8 output comes back
|
|
345
|
+
* from `printSync` as a `Uint8Array` and is never decoded lossily into a fix.
|
|
338
346
|
* @throws `PrintError` or `PrintPanicError` (from sh-ast) when upstream's
|
|
339
347
|
* printer fails for `edited` or `options`.
|
|
340
348
|
*/
|
|
@@ -5,6 +5,38 @@ import { SYNTAX_INTERFACES, printSync } from 'sh-ast';
|
|
|
5
5
|
import { resolveArgv0 } from 'sh-ast/analyze';
|
|
6
6
|
import { buildCommandSiteIndex, rawCallExprFor, viewCommandSites } from './command-resolution.js';
|
|
7
7
|
import { resolveWord } from './word-resolution.js';
|
|
8
|
+
/**
|
|
9
|
+
* Refuses edited syntax whose string or byte values the upstream printer treats as layout.
|
|
10
|
+
* Checking the edited tree also catches controls introduced by a rule, without refusing ordinary
|
|
11
|
+
* text edits elsewhere in a file. The input filename is metadata, never printer output.
|
|
12
|
+
* Iteration tolerates shared/cyclic records; sh-ast still validates
|
|
13
|
+
* the printable syntax itself.
|
|
14
|
+
*/
|
|
15
|
+
// format.ts's refusePrinterControlCharacters independently guards the formatter's source text.
|
|
16
|
+
function refusePrinterControlCharacters(root) {
|
|
17
|
+
const pending = [root.Type === 'File' ? { ...root, Name: '' } : root];
|
|
18
|
+
const seen = new Set();
|
|
19
|
+
while (pending.length > 0) {
|
|
20
|
+
const current = pending.pop();
|
|
21
|
+
const unsafe = (typeof current === 'string' && /[\v\f]/u.test(current)) ||
|
|
22
|
+
(current instanceof Uint8Array && current.some((byte) => byte === 0x0b || byte === 0x0c));
|
|
23
|
+
if (unsafe) {
|
|
24
|
+
throw new TypeError('cannot print a text fix containing literal vertical tabs and form feeds: ' +
|
|
25
|
+
'the printer changes their value');
|
|
26
|
+
}
|
|
27
|
+
// A checked byte-valued string is a leaf; its individual numbers are not syntax children.
|
|
28
|
+
if (current instanceof Uint8Array) {
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
if (current !== null && typeof current === 'object' && !seen.has(current)) {
|
|
32
|
+
seen.add(current);
|
|
33
|
+
const children = Object.values(current);
|
|
34
|
+
for (const child of children) {
|
|
35
|
+
pending.push(child);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
8
40
|
/**
|
|
9
41
|
* Shared instance — `ConfigCommentParser` is stateless (see
|
|
10
42
|
* `@eslint/plugin-kit`'s implementation), so every `ShellSourceCode`
|
|
@@ -296,7 +328,7 @@ function withUnusedDirectiveFixRange(comment, text) {
|
|
|
296
328
|
* the string `jq`?", "which command does this invocation run?", and "which
|
|
297
329
|
* commands does this file run, and where?" by delegating to
|
|
298
330
|
* `sh-ast/analyze`. They never reinterpret words themselves, so every rule
|
|
299
|
-
* gets the same answer for the same syntax. `dialect` and `
|
|
331
|
+
* gets the same answer for the same syntax. `dialect` and `minBashVersion`
|
|
300
332
|
* expose the file's normalized language options.
|
|
301
333
|
*
|
|
302
334
|
* Syntax access (`getSyntax`, `getViewNode`, `getIndexFromSyntaxPos`,
|
|
@@ -356,8 +388,8 @@ export class ShellSourceCode extends TextSourceCodeBase {
|
|
|
356
388
|
#inlineConfigNodes;
|
|
357
389
|
/** The normalized `languageOptions.dialect` this file was parsed with. */
|
|
358
390
|
#dialect;
|
|
359
|
-
/** The configured `languageOptions.
|
|
360
|
-
#
|
|
391
|
+
/** The configured `languageOptions.minBashVersion`, if the config supplied one. */
|
|
392
|
+
#minBashVersion;
|
|
361
393
|
/** This file's coordinate index; see `#textCoordinates`. */
|
|
362
394
|
#ownCoordinates;
|
|
363
395
|
/**
|
|
@@ -380,7 +412,7 @@ export class ShellSourceCode extends TextSourceCodeBase {
|
|
|
380
412
|
// supplies the already-normalized, defaulted values.
|
|
381
413
|
this.#directivePrefix = options.directivePrefix ?? 'eslint';
|
|
382
414
|
this.#dialect = options.dialect ?? 'bash';
|
|
383
|
-
this.#
|
|
415
|
+
this.#minBashVersion = options.minBashVersion;
|
|
384
416
|
this.#directiveLabels = buildDirectiveLabels(this.#directivePrefix);
|
|
385
417
|
this.#inlineConfigPattern = buildInlineConfigPattern(this.#directivePrefix);
|
|
386
418
|
}
|
|
@@ -393,12 +425,13 @@ export class ShellSourceCode extends TextSourceCodeBase {
|
|
|
393
425
|
return this.#dialect;
|
|
394
426
|
}
|
|
395
427
|
/**
|
|
396
|
-
* The configured `languageOptions.
|
|
428
|
+
* The configured `languageOptions.minBashVersion` (a `MAJOR[.MINOR]` string
|
|
397
429
|
* such as `"3.2"`), or `undefined` when the config does not set one. The
|
|
398
|
-
*
|
|
430
|
+
* script must support this Bash and every newer release. The parser never
|
|
431
|
+
* reads it; compatibility rules use it as a floor with no upper limit.
|
|
399
432
|
*/
|
|
400
|
-
get
|
|
401
|
-
return this.#
|
|
433
|
+
get minBashVersion() {
|
|
434
|
+
return this.#minBashVersion;
|
|
402
435
|
}
|
|
403
436
|
/**
|
|
404
437
|
* The one-based location of a UTF-16 `index` in this file's text, where
|
|
@@ -521,13 +554,19 @@ export class ShellSourceCode extends TextSourceCodeBase {
|
|
|
521
554
|
* effective command's name as
|
|
522
555
|
* `res.effective.static ? res.effective.text : null`.
|
|
523
556
|
* `sudo -u x "$prog"` and `exec "$@"` have a non-static `effective` with
|
|
524
|
-
* reason `expansion`; `env -S '…'`
|
|
557
|
+
* reason `expansion`; `env -S '…'` and `bash -c '…'` have reason
|
|
558
|
+
* `embedded-command`; `chain`
|
|
525
559
|
* lists every word resolved along the way, outermost wrapper first, and
|
|
526
560
|
* `chainIndices` gives each entry's position in the command's `argv` (as
|
|
527
561
|
* {@link ShellSourceCode.getCommandSites} reports it). When `effective` is
|
|
528
562
|
* static, the effective command's arguments are
|
|
529
563
|
* `argv.slice(chainIndices.at(-1)! + 1)`: every entry after the effective
|
|
530
|
-
* command's own index.
|
|
564
|
+
* command's own index. For an interpreter command string the final chain
|
|
565
|
+
* index instead locates the selector flag; the interpreter is in `chain.at(-2)`.
|
|
566
|
+
* `embeddedCommandInterpreters: []` retains the interpreter as the static
|
|
567
|
+
* effective command when a policy needs program identity. Utility wrappers
|
|
568
|
+
* can match absolute-path basenames; `shellBuiltin: true` entries require
|
|
569
|
+
* exact names, so external paths do not acquire in-process semantics.
|
|
531
570
|
*
|
|
532
571
|
* Enumeration runs once per file, the first time any command helper is
|
|
533
572
|
* called. If the file's nesting exceeds `enumerateCommands`'s recursion
|
|
@@ -702,8 +741,9 @@ export class ShellSourceCode extends TextSourceCodeBase {
|
|
|
702
741
|
* @throws TypeError when `node` has no original record in this file (see
|
|
703
742
|
* {@link ShellSourceCode.getEditableSyntax}), when `edited` is not a
|
|
704
743
|
* printable root (the message names its type), or when the printer's output
|
|
705
|
-
* is not valid UTF-8
|
|
706
|
-
*
|
|
744
|
+
* is not valid UTF-8, or the edited syntax contains literal vertical tabs or
|
|
745
|
+
* form feeds that the printer cannot preserve. Non-UTF-8 output comes back
|
|
746
|
+
* from `printSync` as a `Uint8Array` and is never decoded lossily into a fix.
|
|
707
747
|
* @throws `PrintError` or `PrintPanicError` (from sh-ast) when upstream's
|
|
708
748
|
* printer fails for `edited` or `options`.
|
|
709
749
|
*/
|
|
@@ -711,6 +751,7 @@ export class ShellSourceCode extends TextSourceCodeBase {
|
|
|
711
751
|
const original = this.#originalSyntaxFor(node);
|
|
712
752
|
const rootType = printableRootType(edited);
|
|
713
753
|
const root = edited.Type === 'Stmt' ? { ...edited, Comments: null } : edited;
|
|
754
|
+
refusePrinterControlCharacters(root);
|
|
714
755
|
const printed = printSync(root, options);
|
|
715
756
|
if (typeof printed !== 'string') {
|
|
716
757
|
throw new TypeError('printSync produced output that is not valid UTF-8; a text fix cannot hold it without ' +
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The common Bash 3.2+ intrinsic numeric profile used by shell rules. Value
|
|
3
|
+
* shape and assignment behavior are separate properties: a variable that
|
|
4
|
+
* always yields an integer need not evaluate assigned text arithmetically.
|
|
5
|
+
* Writable intrinsic setters still differ from ordinary string bindings;
|
|
6
|
+
* their handling can vary between Bash versions.
|
|
7
|
+
*
|
|
8
|
+
* This is a name profile, not dataflow over unset, local, or declaration
|
|
9
|
+
* attributes. Callers decide when intrinsic meaning applies. POSIX and other
|
|
10
|
+
* shell dialects do not inherit this Bash profile, and newer Bash-only names
|
|
11
|
+
* are excluded from the common minimum-version guarantee.
|
|
12
|
+
*/
|
|
13
|
+
/** A name whose intrinsic value is numeric throughout the common Bash profile. @internal */
|
|
14
|
+
export declare function isBashIntegerVariable(name: string): boolean;
|
|
15
|
+
/**
|
|
16
|
+
* A name with a non-ordinary writable integer setter. OPTIND can evaluate
|
|
17
|
+
* assigned text as arithmetic; numeric output alone makes no such promise
|
|
18
|
+
* about the other setters. Proofs that do not model these setters must fence
|
|
19
|
+
* their writes rather than treat them as ordinary string assignments.
|
|
20
|
+
* @internal
|
|
21
|
+
*/
|
|
22
|
+
export declare function hasSpecialBashIntegerSetter(name: string): boolean;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The common Bash 3.2+ intrinsic numeric profile used by shell rules. Value
|
|
3
|
+
* shape and assignment behavior are separate properties: a variable that
|
|
4
|
+
* always yields an integer need not evaluate assigned text arithmetically.
|
|
5
|
+
* Writable intrinsic setters still differ from ordinary string bindings;
|
|
6
|
+
* their handling can vary between Bash versions.
|
|
7
|
+
*
|
|
8
|
+
* This is a name profile, not dataflow over unset, local, or declaration
|
|
9
|
+
* attributes. Callers decide when intrinsic meaning applies. POSIX and other
|
|
10
|
+
* shell dialects do not inherit this Bash profile, and newer Bash-only names
|
|
11
|
+
* are excluded from the common minimum-version guarantee.
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Every listed intrinsic yields a decimal integer. Writable setters control
|
|
15
|
+
* the random generator, elapsed clock, or option parser; the other bindings
|
|
16
|
+
* ignore assignments or reject them. Environment-controlled names such as
|
|
17
|
+
* UID/EUID/COLUMNS, and later additions such as BASHPID/SRANDOM, are outside
|
|
18
|
+
* this common profile.
|
|
19
|
+
*/
|
|
20
|
+
const INTEGER_BINDINGS = new Map([
|
|
21
|
+
['RANDOM', 'writable'],
|
|
22
|
+
['SECONDS', 'writable'],
|
|
23
|
+
['OPTIND', 'writable'],
|
|
24
|
+
['LINENO', 'ignored'],
|
|
25
|
+
['BASH_SUBSHELL', 'ignored'],
|
|
26
|
+
['PPID', 'readonly'],
|
|
27
|
+
]);
|
|
28
|
+
/** A name whose intrinsic value is numeric throughout the common Bash profile. @internal */
|
|
29
|
+
export function isBashIntegerVariable(name) {
|
|
30
|
+
return INTEGER_BINDINGS.has(name);
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* A name with a non-ordinary writable integer setter. OPTIND can evaluate
|
|
34
|
+
* assigned text as arithmetic; numeric output alone makes no such promise
|
|
35
|
+
* about the other setters. Proofs that do not model these setters must fence
|
|
36
|
+
* their writes rather than treat them as ordinary string assignments.
|
|
37
|
+
* @internal
|
|
38
|
+
*/
|
|
39
|
+
export function hasSpecialBashIntegerSetter(name) {
|
|
40
|
+
return INTEGER_BINDINGS.get(name) === 'writable';
|
|
41
|
+
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* A compat rule reports one gated feature (a row of the gate table in
|
|
5
5
|
* `./version-gates.ts`) wherever a file uses it while its `languageOptions`
|
|
6
6
|
* say the feature is unavailable. The factory owns the gate decision, so no
|
|
7
|
-
* compat rule reads `
|
|
7
|
+
* compat rule reads `minBashVersion` or `dialect` itself:
|
|
8
8
|
*
|
|
9
9
|
* - when {@link isFeatureAvailable} says the feature is available, the rule
|
|
10
10
|
* returns no listeners at all, so it costs nothing and cannot report;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { TransparentWrappersBindingContext } from '../resolve-transparent-wrappers.js';
|
|
2
2
|
import type { ShellNode } from '../../language/syntax-view.js';
|
|
3
3
|
import type { ShellRuleVisitor } from '../define-shell-rule.js';
|
|
4
4
|
/**
|
|
@@ -16,4 +16,4 @@ export declare const DECLARATION_AND_LOCAL_COMMANDS: ReadonlySet<string>;
|
|
|
16
16
|
*
|
|
17
17
|
* @internal
|
|
18
18
|
*/
|
|
19
|
-
export declare function declarationFlagVisitor(
|
|
19
|
+
export declare function declarationFlagVisitor(context: TransparentWrappersBindingContext, flag: string, report: (option: ShellNode, command: string, options: readonly string[]) => void, commands?: ReadonlySet<string>): ShellRuleVisitor;
|