@eslint-sh/eslint-plugin 0.5.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +228 -17
- package/dist/command-harvest/builtins/bash.d.ts +36 -0
- package/dist/command-harvest/builtins/bash.js +98 -0
- package/dist/command-harvest/builtins/bats.d.ts +45 -0
- package/dist/command-harvest/builtins/bats.js +33 -0
- package/dist/command-harvest/builtins/index.d.ts +18 -0
- package/dist/command-harvest/builtins/index.js +39 -0
- package/dist/command-harvest/builtins/posix.d.ts +51 -0
- package/dist/command-harvest/builtins/posix.js +93 -0
- package/dist/command-harvest/harvest-commands.d.ts +66 -0
- package/dist/command-harvest/harvest-commands.js +97 -0
- package/dist/command-harvest/wrapper-table.d.ts +24 -0
- package/dist/command-harvest/wrapper-table.js +49 -0
- package/dist/configs/agent-authored.d.ts +41 -0
- package/dist/configs/agent-authored.js +33 -0
- package/dist/configs/bash-3-2.d.ts +28 -0
- package/dist/configs/bash-3-2.js +51 -0
- package/dist/configs/posix-portable.d.ts +40 -0
- package/dist/configs/posix-portable.js +53 -0
- package/dist/configs/recommended.d.ts +52 -0
- package/dist/configs/recommended.js +34 -0
- package/dist/declaration-conventions/comment-pragma.d.ts +33 -0
- package/dist/declaration-conventions/comment-pragma.js +153 -0
- package/dist/declaration-conventions/declaration-convention.d.ts +189 -0
- package/dist/declaration-conventions/declaration-convention.js +1 -0
- package/dist/declaration-conventions/read-declared-commands.d.ts +70 -0
- package/dist/declaration-conventions/read-declared-commands.js +128 -0
- package/dist/eslint-plugin-syntax-analyze.d.ts +23 -0
- package/dist/eslint-plugin-syntax.d.ts +27 -0
- package/dist/eslint-plugin.d.ts +1211 -52
- package/dist/index.d.ts +56 -8
- package/dist/index.js +53 -6
- package/dist/language/command-resolution.d.ts +124 -0
- package/dist/language/command-resolution.js +120 -0
- package/dist/language/end-of-input-marker.d.ts +29 -0
- package/dist/language/end-of-input-marker.js +88 -0
- package/dist/language/line-breaks.d.ts +18 -0
- package/dist/language/line-breaks.js +22 -0
- package/dist/language/lint-view-types.d.ts +104 -0
- package/dist/language/lint-view-types.js +1 -0
- package/dist/language/non-shell-file-guard.d.ts +35 -0
- package/dist/language/non-shell-file-guard.js +59 -0
- package/dist/language/selector-classes.d.ts +0 -1
- package/dist/language/selector-classes.js +0 -1
- package/dist/language/shell-language-options.d.ts +7 -1
- package/dist/language/shell-language-options.js +4 -4
- package/dist/language/shell-language.d.ts +10 -2
- package/dist/language/shell-language.js +59 -8
- package/dist/language/shell-source-code.d.ts +342 -5
- package/dist/language/shell-source-code.js +559 -30
- package/dist/language/source-coordinates.d.ts +13 -2
- package/dist/language/source-coordinates.js +68 -14
- package/dist/language/syntax-view.d.ts +60 -39
- package/dist/language/syntax-view.js +200 -45
- package/dist/language/word-resolution.d.ts +4 -3
- package/dist/language/word-resolution.js +4 -3
- package/dist/rules/compat/create-compat-rule.d.ts +59 -0
- package/dist/rules/compat/create-compat-rule.js +65 -0
- package/dist/rules/compat/declaration-options.d.ts +19 -0
- package/dist/rules/compat/declaration-options.js +110 -0
- package/dist/rules/compat/no-append-all-redirect.d.ts +13 -0
- package/dist/rules/compat/no-append-all-redirect.js +73 -0
- package/dist/rules/compat/no-associative-arrays.d.ts +12 -0
- package/dist/rules/compat/no-associative-arrays.js +39 -0
- package/dist/rules/compat/no-brace-sequence-padding-step.d.ts +15 -0
- package/dist/rules/compat/no-brace-sequence-padding-step.js +287 -0
- package/dist/rules/compat/no-case-attributes.d.ts +12 -0
- package/dist/rules/compat/no-case-attributes.js +63 -0
- package/dist/rules/compat/no-case-fallthrough.d.ts +12 -0
- package/dist/rules/compat/no-case-fallthrough.js +67 -0
- package/dist/rules/compat/no-case-modification.d.ts +35 -0
- package/dist/rules/compat/no-case-modification.js +86 -0
- package/dist/rules/compat/no-coproc.d.ts +12 -0
- package/dist/rules/compat/no-coproc.js +47 -0
- package/dist/rules/compat/no-declare-g.d.ts +12 -0
- package/dist/rules/compat/no-declare-g.js +59 -0
- package/dist/rules/compat/no-double-bracket.d.ts +12 -0
- package/dist/rules/compat/no-double-bracket.js +52 -0
- package/dist/rules/compat/no-epoch-variables.d.ts +12 -0
- package/dist/rules/compat/no-epoch-variables.js +57 -0
- package/dist/rules/compat/no-fd-variable.d.ts +12 -0
- package/dist/rules/compat/no-fd-variable.js +48 -0
- package/dist/rules/compat/no-globstar.d.ts +12 -0
- package/dist/rules/compat/no-globstar.js +39 -0
- package/dist/rules/compat/no-lastpipe-semantics.d.ts +12 -0
- package/dist/rules/compat/no-lastpipe-semantics.js +53 -0
- package/dist/rules/compat/no-let.d.ts +12 -0
- package/dist/rules/compat/no-let.js +62 -0
- package/dist/rules/compat/no-mapfile.d.ts +13 -0
- package/dist/rules/compat/no-mapfile.js +122 -0
- package/dist/rules/compat/no-nameref.d.ts +12 -0
- package/dist/rules/compat/no-nameref.js +44 -0
- package/dist/rules/compat/no-negative-array-index.d.ts +13 -0
- package/dist/rules/compat/no-negative-array-index.js +123 -0
- package/dist/rules/compat/no-nounset-empty-array.d.ts +13 -0
- package/dist/rules/compat/no-nounset-empty-array.js +158 -0
- package/dist/rules/compat/no-parameter-transformation.d.ts +13 -0
- package/dist/rules/compat/no-parameter-transformation.js +123 -0
- package/dist/rules/compat/no-pipe-all.d.ts +13 -0
- package/dist/rules/compat/no-pipe-all.js +68 -0
- package/dist/rules/compat/no-printf-time-format.d.ts +12 -0
- package/dist/rules/compat/no-printf-time-format.js +99 -0
- package/dist/rules/compat/no-source.d.ts +13 -0
- package/dist/rules/compat/no-source.js +103 -0
- package/dist/rules/compat/no-test-v.d.ts +12 -0
- package/dist/rules/compat/no-test-v.js +280 -0
- package/dist/rules/compat/no-unicode-escape.d.ts +14 -0
- package/dist/rules/compat/no-unicode-escape.js +120 -0
- package/dist/rules/compat/no-wait-n.d.ts +12 -0
- package/dist/rules/compat/no-wait-n.js +73 -0
- package/dist/rules/compat/operator-range.d.ts +10 -0
- package/dist/rules/compat/operator-range.js +15 -0
- package/dist/rules/compat/posix-absent-command.d.ts +12 -0
- package/dist/rules/compat/posix-absent-command.js +134 -0
- package/dist/rules/compat/shopt-options.d.ts +9 -0
- package/dist/rules/compat/shopt-options.js +63 -0
- package/dist/rules/compat/version-gates.d.ts +76 -0
- package/dist/rules/compat/version-gates.js +205 -0
- package/dist/rules/create-rule.d.ts +91 -0
- package/dist/rules/create-rule.js +51 -0
- package/dist/rules/create-wrapper-rule.d.ts +166 -0
- package/dist/rules/create-wrapper-rule.js +407 -0
- package/dist/rules/define-shell-rule.d.ts +184 -0
- package/dist/rules/define-shell-rule.js +56 -0
- package/dist/rules/index.d.ts +26 -0
- package/dist/rules/index.js +90 -0
- package/dist/rules/max-script-complexity.d.ts +28 -0
- package/dist/rules/max-script-complexity.js +130 -0
- package/dist/rules/no-dynamic-invocation.d.ts +35 -0
- package/dist/rules/no-dynamic-invocation.js +117 -0
- package/dist/rules/no-eval.d.ts +13 -0
- package/dist/rules/no-eval.js +89 -0
- package/dist/rules/no-masked-exit-status.d.ts +21 -0
- package/dist/rules/no-masked-exit-status.js +667 -0
- package/dist/rules/no-secret-env-reads.d.ts +24 -0
- package/dist/rules/no-secret-env-reads.js +133 -0
- package/dist/rules/no-secret-flags.d.ts +32 -0
- package/dist/rules/no-secret-flags.js +162 -0
- package/dist/rules/no-unchecked-cd.d.ts +44 -0
- package/dist/rules/no-unchecked-cd.js +250 -0
- package/dist/rules/no-undeclared-commands.d.ts +20 -0
- package/dist/rules/no-undeclared-commands.js +115 -0
- package/dist/rules/no-unused-declarations.d.ts +23 -0
- package/dist/rules/no-unused-declarations.js +259 -0
- package/dist/rules/prefer-local.d.ts +16 -0
- package/dist/rules/prefer-local.js +249 -0
- package/dist/rules/quote-expansions.d.ts +13 -0
- package/dist/rules/quote-expansions.js +229 -0
- package/dist/rules/require-network-wrapper.d.ts +40 -0
- package/dist/rules/require-network-wrapper.js +244 -0
- package/dist/rules/require-shebang.d.ts +29 -0
- package/dist/rules/require-shebang.js +101 -0
- package/dist/rules/require-strict-mode.d.ts +52 -0
- package/dist/rules/require-strict-mode.js +177 -0
- package/dist/rules/require-tty-guard.d.ts +13 -0
- package/dist/rules/require-tty-guard.js +318 -0
- package/dist/rules/resolve-declaration-convention.d.ts +96 -0
- package/dist/rules/resolve-declaration-convention.js +109 -0
- package/dist/rules/resolve-network-wrapper.d.ts +19 -0
- package/dist/rules/resolve-network-wrapper.js +116 -0
- package/dist/rules/set-options.d.ts +109 -0
- package/dist/rules/set-options.js +201 -0
- package/dist/rules/shebang-interpreter.d.ts +18 -0
- package/dist/rules/shebang-interpreter.js +81 -0
- package/dist/rules/shebang-matches-dialect.d.ts +12 -0
- package/dist/rules/shebang-matches-dialect.js +69 -0
- package/dist/rules/transparent-wrappers.d.ts +52 -0
- package/dist/rules/transparent-wrappers.js +99 -0
- package/dist/rules/user-pattern.d.ts +63 -0
- package/dist/rules/user-pattern.js +184 -0
- package/dist/syntax/analyze.d.ts +19 -0
- package/dist/syntax/analyze.js +21 -0
- package/dist/syntax/index.d.ts +23 -0
- package/dist/syntax/index.js +25 -0
- package/dist/version.d.ts +0 -1
- package/dist/version.js +0 -1
- package/docs/compat.md +354 -0
- package/docs/custom-rule-tutorial.md +780 -34
- package/docs/declaration-conventions.md +478 -0
- package/docs/examples/audit-wrapper-pack.mjs +15 -0
- package/docs/examples/dep-line-convention.mjs +22 -0
- package/docs/examples/eslint.config.mjs +3 -2
- package/docs/examples/no-and-or-as-if-rule.mjs +61 -0
- package/docs/examples/no-backticks-rule-tester.mjs +106 -0
- package/docs/examples/no-backticks-rule.mjs +60 -0
- package/docs/examples/no-curl-in-functions-rule.mjs +53 -0
- package/docs/examples/no-function-keyword-rule.mjs +59 -0
- package/docs/examples/no-listed-commands-rule.ts +63 -0
- package/docs/examples/no-rm-rf-rule.mjs +26 -15
- package/docs/examples/no-unquoted-rm-variable-rule.ts +54 -0
- package/docs/examples/sidecar-convention.mjs +38 -0
- package/docs/examples/sidecar-snapshot-convention.mjs +26 -0
- package/docs/examples/sorted-declarations-rule.mjs +52 -0
- package/docs/reading-nodes.md +159 -0
- package/docs/rules/README.md +79 -0
- package/docs/rules/_template.md +83 -0
- package/docs/rules/compat/no-append-all-redirect.md +87 -0
- package/docs/rules/compat/no-associative-arrays.md +95 -0
- package/docs/rules/compat/no-brace-sequence-padding-step.md +90 -0
- package/docs/rules/compat/no-case-attributes.md +82 -0
- package/docs/rules/compat/no-case-fallthrough.md +90 -0
- package/docs/rules/compat/no-case-modification.md +74 -0
- package/docs/rules/compat/no-coproc.md +76 -0
- package/docs/rules/compat/no-declare-g.md +80 -0
- package/docs/rules/compat/no-double-bracket.md +72 -0
- package/docs/rules/compat/no-epoch-variables.md +79 -0
- package/docs/rules/compat/no-fd-variable.md +78 -0
- package/docs/rules/compat/no-globstar.md +74 -0
- package/docs/rules/compat/no-lastpipe-semantics.md +73 -0
- package/docs/rules/compat/no-let.md +78 -0
- package/docs/rules/compat/no-mapfile.md +118 -0
- package/docs/rules/compat/no-nameref.md +81 -0
- package/docs/rules/compat/no-negative-array-index.md +87 -0
- package/docs/rules/compat/no-nounset-empty-array.md +103 -0
- package/docs/rules/compat/no-parameter-transformation.md +94 -0
- package/docs/rules/compat/no-pipe-all.md +85 -0
- package/docs/rules/compat/no-printf-time-format.md +75 -0
- package/docs/rules/compat/no-source.md +98 -0
- package/docs/rules/compat/no-test-v.md +82 -0
- package/docs/rules/compat/no-unicode-escape.md +91 -0
- package/docs/rules/compat/no-wait-n.md +85 -0
- package/docs/rules/max-script-complexity.md +109 -0
- package/docs/rules/no-dynamic-invocation.md +139 -0
- package/docs/rules/no-eval.md +106 -0
- package/docs/rules/no-masked-exit-status.md +239 -0
- package/docs/rules/no-secret-env-reads.md +173 -0
- package/docs/rules/no-secret-flags.md +169 -0
- package/docs/rules/no-unchecked-cd.md +146 -0
- package/docs/rules/no-undeclared-commands.md +141 -0
- package/docs/rules/no-unused-declarations.md +216 -0
- package/docs/rules/prefer-local.md +221 -0
- package/docs/rules/quote-expansions.md +157 -0
- package/docs/rules/require-network-wrapper.md +171 -0
- package/docs/rules/require-shebang.md +157 -0
- package/docs/rules/require-strict-mode.md +253 -0
- package/docs/rules/require-tty-guard.md +131 -0
- package/docs/rules/shebang-matches-dialect.md +120 -0
- package/docs/selectors.md +2 -2
- package/docs/shellcheck.md +87 -0
- package/docs/untrusted-code.md +440 -0
- package/docs/wrapper-rules.md +206 -0
- package/package.json +26 -20
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/language/selector-classes.d.ts.map +0 -1
- package/dist/language/selector-classes.js.map +0 -1
- package/dist/language/shell-language-options.d.ts.map +0 -1
- package/dist/language/shell-language-options.js.map +0 -1
- package/dist/language/shell-language.d.ts.map +0 -1
- package/dist/language/shell-language.js.map +0 -1
- package/dist/language/shell-source-code.d.ts.map +0 -1
- package/dist/language/shell-source-code.js.map +0 -1
- package/dist/language/source-coordinates.d.ts.map +0 -1
- package/dist/language/source-coordinates.js.map +0 -1
- package/dist/language/syntax-view.d.ts.map +0 -1
- package/dist/language/syntax-view.js.map +0 -1
- package/dist/language/word-resolution.d.ts.map +0 -1
- package/dist/language/word-resolution.js.map +0 -1
- package/dist/version.d.ts.map +0 -1
- package/dist/version.js.map +0 -1
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { BuiltInRuleMeta } from '../create-rule.js';
|
|
2
|
+
import type { ShellRule, ShellRuleContext, ShellRuleVisitor } from '../define-shell-rule.js';
|
|
3
|
+
import type { CompatFeature, GateDescription } from './version-gates.js';
|
|
4
|
+
/**
|
|
5
|
+
* What a compat rule hands to {@link createCompatRule}.
|
|
6
|
+
*
|
|
7
|
+
* @internal
|
|
8
|
+
*/
|
|
9
|
+
export interface CompatRuleSpec<MessageIds extends string> {
|
|
10
|
+
/**
|
|
11
|
+
* The registry name, always under the `compat/` namespace
|
|
12
|
+
* (`compat/no-mapfile`, written `sh/compat/no-mapfile` in a config). Its
|
|
13
|
+
* docs page is `docs/rules/compat/<name>.md`.
|
|
14
|
+
*/
|
|
15
|
+
readonly name: `compat/${string}`;
|
|
16
|
+
/** The gate-table row this rule enforces. */
|
|
17
|
+
readonly feature: CompatFeature;
|
|
18
|
+
/** The rule's metadata; `schema` is fixed to `[]` by the factory. */
|
|
19
|
+
readonly meta: Omit<BuiltInRuleMeta<[], MessageIds>, 'schema'>;
|
|
20
|
+
/**
|
|
21
|
+
* Builds the listeners for a file in which the feature is unavailable.
|
|
22
|
+
* `gate` describes the requirement and the file's target for messages.
|
|
23
|
+
*/
|
|
24
|
+
readonly create: (context: ShellRuleContext<[], MessageIds>, gate: GateDescription) => ShellRuleVisitor;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Builds a `compat/*` rule: a built-in rule (through `createRule`) that is
|
|
28
|
+
* a no-op unless the file's `languageOptions` make `spec.feature`
|
|
29
|
+
* unavailable.
|
|
30
|
+
*
|
|
31
|
+
* @internal
|
|
32
|
+
*/
|
|
33
|
+
export declare function createCompatRule<MessageIds extends string>(spec: CompatRuleSpec<MessageIds>): ShellRule<[], MessageIds>;
|
|
34
|
+
/**
|
|
35
|
+
* What a compat rule that reports several gate-table rows hands to
|
|
36
|
+
* {@link createMultiFeatureCompatRule}: one rule for one construct whose
|
|
37
|
+
* minimum version depends on how it is written (such as `${v@op}`, where
|
|
38
|
+
* the operator decides the release).
|
|
39
|
+
*
|
|
40
|
+
* @internal
|
|
41
|
+
*/
|
|
42
|
+
export interface MultiFeatureCompatRuleSpec<MessageIds extends string> extends Omit<CompatRuleSpec<MessageIds>, 'feature' | 'create'> {
|
|
43
|
+
/** The gate-table rows this rule enforces. */
|
|
44
|
+
readonly features: readonly CompatFeature[];
|
|
45
|
+
/**
|
|
46
|
+
* Builds the listeners for a file in which at least one of `features` is
|
|
47
|
+
* unavailable. `gates` holds a {@link GateDescription} for exactly the
|
|
48
|
+
* unavailable ones, so a feature missing from it must not be reported.
|
|
49
|
+
*/
|
|
50
|
+
readonly create: (context: ShellRuleContext<[], MessageIds>, gates: ReadonlyMap<CompatFeature, GateDescription>) => ShellRuleVisitor;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Builds a `compat/*` rule over several gate-table rows: a no-op unless the
|
|
54
|
+
* file's `languageOptions` make at least one of `spec.features`
|
|
55
|
+
* unavailable, and then given the description of each unavailable one.
|
|
56
|
+
*
|
|
57
|
+
* @internal
|
|
58
|
+
*/
|
|
59
|
+
export declare function createMultiFeatureCompatRule<MessageIds extends string>(spec: MultiFeatureCompatRuleSpec<MessageIds>): ShellRule<[], MessageIds>;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `createCompatRule`: the factory every `compat/*` rule is built with.
|
|
3
|
+
*
|
|
4
|
+
* A compat rule reports one gated feature (a row of the gate table in
|
|
5
|
+
* `./version-gates.ts`) wherever a file uses it while its `languageOptions`
|
|
6
|
+
* say the feature is unavailable. The factory owns the gate decision, so no
|
|
7
|
+
* compat rule reads `shellVersion` or `dialect` itself:
|
|
8
|
+
*
|
|
9
|
+
* - when {@link isFeatureAvailable} says the feature is available, the rule
|
|
10
|
+
* returns no listeners at all, so it costs nothing and cannot report;
|
|
11
|
+
* - otherwise the rule's own `create()` runs and receives the
|
|
12
|
+
* {@link GateDescription} (`required`, `target`) to interpolate into its
|
|
13
|
+
* messages as `{{required}}` and `{{target}}`.
|
|
14
|
+
*
|
|
15
|
+
* Compat rules have no options: what they gate on is the language options,
|
|
16
|
+
* which the user already sets once for the whole file set.
|
|
17
|
+
*/
|
|
18
|
+
import { createRule } from '../create-rule.js';
|
|
19
|
+
import { describeGate, isFeatureAvailable } from './version-gates.js';
|
|
20
|
+
/**
|
|
21
|
+
* Builds a `compat/*` rule: a built-in rule (through `createRule`) that is
|
|
22
|
+
* a no-op unless the file's `languageOptions` make `spec.feature`
|
|
23
|
+
* unavailable.
|
|
24
|
+
*
|
|
25
|
+
* @internal
|
|
26
|
+
*/
|
|
27
|
+
export function createCompatRule(spec) {
|
|
28
|
+
const { name, feature, meta, create } = spec;
|
|
29
|
+
return createRule({
|
|
30
|
+
name,
|
|
31
|
+
meta: { ...meta, schema: [] },
|
|
32
|
+
create(context) {
|
|
33
|
+
const { languageOptions } = context;
|
|
34
|
+
if (isFeatureAvailable(feature, languageOptions))
|
|
35
|
+
return {};
|
|
36
|
+
return create(context, describeGate(feature, languageOptions));
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Builds a `compat/*` rule over several gate-table rows: a no-op unless the
|
|
42
|
+
* file's `languageOptions` make at least one of `spec.features`
|
|
43
|
+
* unavailable, and then given the description of each unavailable one.
|
|
44
|
+
*
|
|
45
|
+
* @internal
|
|
46
|
+
*/
|
|
47
|
+
export function createMultiFeatureCompatRule(spec) {
|
|
48
|
+
const { name, features, meta, create } = spec;
|
|
49
|
+
return createRule({
|
|
50
|
+
name,
|
|
51
|
+
meta: { ...meta, schema: [] },
|
|
52
|
+
create(context) {
|
|
53
|
+
const { languageOptions } = context;
|
|
54
|
+
const gates = new Map();
|
|
55
|
+
for (const feature of features) {
|
|
56
|
+
if (!isFeatureAvailable(feature, languageOptions)) {
|
|
57
|
+
gates.set(feature, describeGate(feature, languageOptions));
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
if (gates.size === 0)
|
|
61
|
+
return {};
|
|
62
|
+
return create(context, gates);
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { ShellSourceCode } from '../../language/shell-source-code.js';
|
|
2
|
+
import type { ShellNode } from '../../language/syntax-view.js';
|
|
3
|
+
import type { ShellRuleVisitor } from '../define-shell-rule.js';
|
|
4
|
+
/**
|
|
5
|
+
* `declare`, `typeset` and `local`: the commands for an option that `local`
|
|
6
|
+
* accepts as well, which is every `declare` option except `-g`, `-f` and
|
|
7
|
+
* `-F`.
|
|
8
|
+
*
|
|
9
|
+
* @internal
|
|
10
|
+
*/
|
|
11
|
+
export declare const DECLARATION_AND_LOCAL_COMMANDS: ReadonlySet<string>;
|
|
12
|
+
/**
|
|
13
|
+
* Builds the listeners that call `report(optionWord, command, optionWords)` for each
|
|
14
|
+
* `declare` / `typeset` (or each of `commands`) whose option cluster contains
|
|
15
|
+
* `flag`: one letter, or a character class such as `[lu]` for either of two.
|
|
16
|
+
*
|
|
17
|
+
* @internal
|
|
18
|
+
*/
|
|
19
|
+
export declare function declarationFlagVisitor(sourceCode: ShellSourceCode, flag: string, report: (option: ShellNode, command: string, options: readonly string[]) => void, commands?: ReadonlySet<string>): ShellRuleVisitor;
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared detection of an option flag on `declare` / `typeset`, for the compat
|
|
3
|
+
* rules that gate one flag of those builtins (`-A` associative arrays, `-g`
|
|
4
|
+
* global scope).
|
|
5
|
+
*
|
|
6
|
+
* What is read: the option words of a `declare` or `typeset` command, up to
|
|
7
|
+
* its first operand (a name, `--`, or a word that is not static), exactly as
|
|
8
|
+
* the builtin's option parser stops. An option word matches when it is a `-`
|
|
9
|
+
* cluster containing the flag (`-A`, `-gA`, `-Ar`). The command is found both
|
|
10
|
+
* as Bash's declaration clause and as an ordinary command (POSIX mode parses
|
|
11
|
+
* `declare -A m` as one, and so does a wrapper such as `builtin declare -A m`).
|
|
12
|
+
* By default `local`, `readonly` and `export` are outside scope; a caller
|
|
13
|
+
* gating an option `local` shares (`-n`, `-l`, `-u`) adds `local` with
|
|
14
|
+
* {@link DECLARATION_AND_LOCAL_COMMANDS}.
|
|
15
|
+
*/
|
|
16
|
+
import { DEFAULT_TRANSPARENT_WRAPPERS } from 'sh-ast/analyze';
|
|
17
|
+
import { BUILTIN_WRAPPER } from '../transparent-wrappers.js';
|
|
18
|
+
/** The declaration commands that take option clusters. */
|
|
19
|
+
const DECLARATION_COMMANDS = new Set(['declare', 'typeset']);
|
|
20
|
+
/**
|
|
21
|
+
* `declare`, `typeset` and `local`: the commands for an option that `local`
|
|
22
|
+
* accepts as well, which is every `declare` option except `-g`, `-f` and
|
|
23
|
+
* `-F`.
|
|
24
|
+
*
|
|
25
|
+
* @internal
|
|
26
|
+
*/
|
|
27
|
+
export const DECLARATION_AND_LOCAL_COMMANDS = new Set([
|
|
28
|
+
'declare',
|
|
29
|
+
'typeset',
|
|
30
|
+
'local',
|
|
31
|
+
]);
|
|
32
|
+
/**
|
|
33
|
+
* The wrappers followed to find a declaration command written as an ordinary
|
|
34
|
+
* command: `sh-ast/analyze`'s defaults (which include `command`) plus Bash's
|
|
35
|
+
* `builtin`.
|
|
36
|
+
*/
|
|
37
|
+
const TRANSPARENT_WRAPPERS = [
|
|
38
|
+
...DEFAULT_TRANSPARENT_WRAPPERS,
|
|
39
|
+
BUILTIN_WRAPPER,
|
|
40
|
+
];
|
|
41
|
+
/**
|
|
42
|
+
* Finds the option word carrying `flag` among a declaration command's
|
|
43
|
+
* arguments (the words after the command name), together with all of the
|
|
44
|
+
* command's option words, or `undefined` when no option word carries it.
|
|
45
|
+
* Option processing stops at the first operand, at `--`, and at a word that
|
|
46
|
+
* is not static, because what follows it may not be an option.
|
|
47
|
+
*/
|
|
48
|
+
function findFlagOption(sourceCode, flagPattern, args) {
|
|
49
|
+
let report;
|
|
50
|
+
const options = [];
|
|
51
|
+
for (const { report: node, word } of args) {
|
|
52
|
+
if (word === undefined)
|
|
53
|
+
break;
|
|
54
|
+
const text = sourceCode.resolveWordText(word);
|
|
55
|
+
if (text === null || text === '--' || !/^[-+]./u.test(text))
|
|
56
|
+
break;
|
|
57
|
+
options.push(text);
|
|
58
|
+
if (report === undefined && flagPattern.test(text))
|
|
59
|
+
report = node;
|
|
60
|
+
}
|
|
61
|
+
return report === undefined ? undefined : { report, options };
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* The arguments of a `DeclClause` as candidates. An option such as `-A` is an
|
|
65
|
+
* `Assign` with no `name` and the option word as its `value`; an `Assign` that
|
|
66
|
+
* has a `name` is an operand and contributes no word.
|
|
67
|
+
*/
|
|
68
|
+
function declClauseArgs(node) {
|
|
69
|
+
return node.args.map((assign) => {
|
|
70
|
+
const word = assign.name === undefined ? assign.value : undefined;
|
|
71
|
+
return { report: word ?? assign, word };
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Builds the listeners that call `report(optionWord, command, optionWords)` for each
|
|
76
|
+
* `declare` / `typeset` (or each of `commands`) whose option cluster contains
|
|
77
|
+
* `flag`: one letter, or a character class such as `[lu]` for either of two.
|
|
78
|
+
*
|
|
79
|
+
* @internal
|
|
80
|
+
*/
|
|
81
|
+
export function declarationFlagVisitor(sourceCode, flag, report, commands = DECLARATION_COMMANDS) {
|
|
82
|
+
const flagPattern = new RegExp(`^-[a-zA-Z]*${flag}[a-zA-Z]*$`, 'u');
|
|
83
|
+
return {
|
|
84
|
+
DeclClause(node) {
|
|
85
|
+
const command = node.variant?.value;
|
|
86
|
+
if (command === undefined || !commands.has(command))
|
|
87
|
+
return;
|
|
88
|
+
const option = findFlagOption(sourceCode, flagPattern, declClauseArgs(node));
|
|
89
|
+
if (option !== undefined)
|
|
90
|
+
report(option.report, command, option.options);
|
|
91
|
+
},
|
|
92
|
+
CallExpr(node) {
|
|
93
|
+
const invoked = sourceCode.resolveInvokedCommand(node, {
|
|
94
|
+
transparentWrappers: TRANSPARENT_WRAPPERS,
|
|
95
|
+
});
|
|
96
|
+
if (invoked?.effective.static !== true)
|
|
97
|
+
return;
|
|
98
|
+
const command = invoked.effective.text;
|
|
99
|
+
if (!commands.has(command))
|
|
100
|
+
return;
|
|
101
|
+
// `chainIndices` index `node.args` directly, so slice before filtering.
|
|
102
|
+
const operands = node.args
|
|
103
|
+
.slice((invoked.chainIndices.at(-1) ?? 0) + 1)
|
|
104
|
+
.map((word) => ({ report: word, word }));
|
|
105
|
+
const option = findFlagOption(sourceCode, flagPattern, operands);
|
|
106
|
+
if (option !== undefined)
|
|
107
|
+
report(option.report, command, option.options);
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Message ids this rule reports with: `appendAll` for the report and
|
|
3
|
+
* `useAppendRedirects` for its suggestion.
|
|
4
|
+
*
|
|
5
|
+
* @internal
|
|
6
|
+
*/
|
|
7
|
+
export type NoAppendAllRedirectMessageId = 'appendAll' | 'useAppendRedirects';
|
|
8
|
+
/**
|
|
9
|
+
* The `sh/compat/no-append-all-redirect` rule module.
|
|
10
|
+
*
|
|
11
|
+
* @internal
|
|
12
|
+
*/
|
|
13
|
+
export declare const noAppendAllRedirect: import("../define-shell-rule.js").ShellRule<[], NoAppendAllRedirectMessageId>;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `sh/compat/no-append-all-redirect`: reports the `&>>` redirection, which
|
|
3
|
+
* appends both stdout and stderr to a file, in files whose target shell
|
|
4
|
+
* lacks it, that is, Bash older than 4.0 (macOS's stock `/bin/bash` is 3.2)
|
|
5
|
+
* and POSIX sh.
|
|
6
|
+
*
|
|
7
|
+
* Bash 3.2 rejects `&>>` as a syntax error, so the whole script fails to
|
|
8
|
+
* run. POSIX sh reads `cmd &>> file` as `cmd &` (run in the background)
|
|
9
|
+
* followed by an empty command that appends nothing to `file`. The portable
|
|
10
|
+
* spelling is `>> file 2>&1`, which the Bash manual defines `&>>` to mean.
|
|
11
|
+
*
|
|
12
|
+
* The rule offers that rewrite as a suggestion: the operator becomes `>>`
|
|
13
|
+
* and ` 2>&1` follows the target word, so the redirection keeps its place
|
|
14
|
+
* among the command's other redirections.
|
|
15
|
+
*
|
|
16
|
+
* What is read: a `Redirect` whose operator is `&>>`. The two-way `&>` and
|
|
17
|
+
* the plain `>>`, `>&` and `2>&1` are different operators and are not
|
|
18
|
+
* reported, and neither is any of them inside quotes. The report is on the
|
|
19
|
+
* whole redirection.
|
|
20
|
+
*/
|
|
21
|
+
import { AppAll } from 'sh-ast';
|
|
22
|
+
import { createCompatRule } from './create-compat-rule.js';
|
|
23
|
+
/** The operator as written. */
|
|
24
|
+
const OPERATOR = '&>>';
|
|
25
|
+
/**
|
|
26
|
+
* The `sh/compat/no-append-all-redirect` rule module.
|
|
27
|
+
*
|
|
28
|
+
* @internal
|
|
29
|
+
*/
|
|
30
|
+
export const noAppendAllRedirect = createCompatRule({
|
|
31
|
+
name: 'compat/no-append-all-redirect',
|
|
32
|
+
feature: 'appendAllRedirect',
|
|
33
|
+
meta: {
|
|
34
|
+
type: 'problem',
|
|
35
|
+
hasSuggestions: true,
|
|
36
|
+
docs: {
|
|
37
|
+
description: 'Disallow the `&>>` redirection when the target shell lacks it',
|
|
38
|
+
},
|
|
39
|
+
messages: {
|
|
40
|
+
appendAll: '`&>>` needs {{required}}, but this file targets {{target}}. Append both streams with `>> file 2>&1` instead.',
|
|
41
|
+
useAppendRedirects: 'Replace `&>> {{file}}` with `>> {{file}} 2>&1`.',
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
create(context, gate) {
|
|
45
|
+
const { sourceCode } = context;
|
|
46
|
+
return {
|
|
47
|
+
Redirect(node) {
|
|
48
|
+
if (node.op !== AppAll)
|
|
49
|
+
return;
|
|
50
|
+
const [start] = sourceCode.getRange(node);
|
|
51
|
+
const { word } = node;
|
|
52
|
+
const exact = word !== undefined && sourceCode.text.startsWith(OPERATOR, start);
|
|
53
|
+
context.report({
|
|
54
|
+
node,
|
|
55
|
+
messageId: 'appendAll',
|
|
56
|
+
data: { ...gate },
|
|
57
|
+
suggest: exact
|
|
58
|
+
? [
|
|
59
|
+
{
|
|
60
|
+
messageId: 'useAppendRedirects',
|
|
61
|
+
data: { file: sourceCode.getText(word) },
|
|
62
|
+
fix: (fixer) => [
|
|
63
|
+
fixer.replaceTextRange([start, start + OPERATOR.length], '>>'),
|
|
64
|
+
fixer.insertTextAfter(word, ' 2>&1'),
|
|
65
|
+
],
|
|
66
|
+
},
|
|
67
|
+
]
|
|
68
|
+
: [],
|
|
69
|
+
});
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
},
|
|
73
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Message ids this rule reports with.
|
|
3
|
+
*
|
|
4
|
+
* @internal
|
|
5
|
+
*/
|
|
6
|
+
export type NoAssociativeArraysMessageId = 'associativeArray';
|
|
7
|
+
/**
|
|
8
|
+
* The `sh/compat/no-associative-arrays` rule module.
|
|
9
|
+
*
|
|
10
|
+
* @internal
|
|
11
|
+
*/
|
|
12
|
+
export declare const noAssociativeArrays: import("../define-shell-rule.js").ShellRule<[], "associativeArray">;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `sh/compat/no-associative-arrays`: reports associative-array declarations
|
|
3
|
+
* (`declare -A`, `typeset -A`) in files whose target shell lacks them, that
|
|
4
|
+
* is, Bash older than 4.0 (macOS's stock `/bin/bash` is 3.2) and POSIX sh.
|
|
5
|
+
*
|
|
6
|
+
* Bash 3.2 rejects `-A` as an invalid option, so the script fails where the
|
|
7
|
+
* array is declared, and every later `${map[key]}` silently indexes an
|
|
8
|
+
* ordinary array by the arithmetic value of `key`. The message suggests the
|
|
9
|
+
* portable alternative: move the keyed lookup into `jq` over a JSON object.
|
|
10
|
+
*
|
|
11
|
+
* What is read: `declare`/`typeset` option clusters containing `A` (`-A`,
|
|
12
|
+
* `-gA`, `-Ar`), found as `declarationFlagVisitor` defines. `local -A`,
|
|
13
|
+
* `readonly -A` and `export -A` are outside this rule's scope.
|
|
14
|
+
*/
|
|
15
|
+
import { createCompatRule } from './create-compat-rule.js';
|
|
16
|
+
import { declarationFlagVisitor } from './declaration-options.js';
|
|
17
|
+
/**
|
|
18
|
+
* The `sh/compat/no-associative-arrays` rule module.
|
|
19
|
+
*
|
|
20
|
+
* @internal
|
|
21
|
+
*/
|
|
22
|
+
export const noAssociativeArrays = createCompatRule({
|
|
23
|
+
name: 'compat/no-associative-arrays',
|
|
24
|
+
feature: 'associativeArrays',
|
|
25
|
+
meta: {
|
|
26
|
+
type: 'problem',
|
|
27
|
+
docs: {
|
|
28
|
+
description: 'Disallow associative arrays (`declare -A`, `typeset -A`) when the target shell lacks them',
|
|
29
|
+
},
|
|
30
|
+
messages: {
|
|
31
|
+
associativeArray: 'Associative arrays (`{{command}} -A`) need {{required}}, but this file targets {{target}}. Consider moving the keyed lookup into `jq`, for example `jq -r --arg k "$key" \'.[$k]\' map.json`.',
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
create(context, gate) {
|
|
35
|
+
return declarationFlagVisitor(context.sourceCode, 'A', (node, command) => {
|
|
36
|
+
context.report({ node, messageId: 'associativeArray', data: { ...gate, command } });
|
|
37
|
+
});
|
|
38
|
+
},
|
|
39
|
+
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Message ids this rule reports with: `padded` for a zero-padded sequence on
|
|
3
|
+
* a Bash that expands it without the padding, `stepped` for a stepped one on
|
|
4
|
+
* a Bash that leaves it literal, and `posixPadded` / `posixStepped` for the
|
|
5
|
+
* same two under a POSIX target, where the result depends on the shell.
|
|
6
|
+
*
|
|
7
|
+
* @internal
|
|
8
|
+
*/
|
|
9
|
+
export type NoBraceSequencePaddingStepMessageId = 'padded' | 'stepped' | 'posixPadded' | 'posixStepped';
|
|
10
|
+
/**
|
|
11
|
+
* The `sh/compat/no-brace-sequence-padding-step` rule module.
|
|
12
|
+
*
|
|
13
|
+
* @internal
|
|
14
|
+
*/
|
|
15
|
+
export declare const noBraceSequencePaddingStep: import("../define-shell-rule.js").ShellRule<[], NoBraceSequencePaddingStepMessageId>;
|