@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
package/dist/eslint-plugin.d.ts
CHANGED
|
@@ -1,31 +1,384 @@
|
|
|
1
|
+
import { Argv0Resolution } from 'sh-ast/analyze';
|
|
2
|
+
import { CommandContext } from 'sh-ast/analyze';
|
|
3
|
+
import type { ConfigObject } from '@eslint/core';
|
|
1
4
|
import type { Directive } from '@eslint/core';
|
|
5
|
+
import type { Expansion } from 'sh-ast';
|
|
2
6
|
import type { File as File_2 } from '@eslint/core';
|
|
3
7
|
import type { FileProblem } from '@eslint/core';
|
|
4
8
|
import type { Language } from '@eslint/core';
|
|
9
|
+
import type { Node } from 'sh-ast';
|
|
5
10
|
import type { OkParseResult } from '@eslint/core';
|
|
6
11
|
import type { ParseResult } from '@eslint/core';
|
|
12
|
+
import type { PrintOptions } from 'sh-ast';
|
|
13
|
+
import type { Replace } from 'sh-ast';
|
|
14
|
+
import { ResolveArgv0Options } from 'sh-ast/analyze';
|
|
7
15
|
import type { ResolveWordOptions } from 'sh-ast/analyze';
|
|
16
|
+
import type { RuleContext } from '@eslint/core';
|
|
17
|
+
import { RuleDefinition } from '@eslint/core';
|
|
18
|
+
import { RuleDefinitionTypeOptions } from '@eslint/core';
|
|
8
19
|
import type { RulesConfig } from '@eslint/core';
|
|
20
|
+
import type { RulesMeta } from '@eslint/core';
|
|
21
|
+
import type { RuleTextEdit } from '@eslint/core';
|
|
22
|
+
import type { RuleTextEditor } from '@eslint/core';
|
|
23
|
+
import type { SettingsConfig } from '@eslint/core';
|
|
24
|
+
import type { Slice } from 'sh-ast';
|
|
9
25
|
import type { SourceLocation } from '@eslint/core';
|
|
26
|
+
import type { SyntaxPos } from 'sh-ast';
|
|
10
27
|
import { TextSourceCodeBase } from '@eslint/plugin-kit';
|
|
11
28
|
import type { TraversalStep } from '@eslint/core';
|
|
12
|
-
import
|
|
29
|
+
import { WordResolution } from 'sh-ast/analyze';
|
|
30
|
+
import { WrapperSpec } from 'sh-ast/analyze';
|
|
31
|
+
|
|
32
|
+
export { Argv0Resolution }
|
|
33
|
+
|
|
34
|
+
export { CommandContext }
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* The reference {@link DeclarationConvention}: a script declares the external
|
|
38
|
+
* commands it uses in `# uses:` comments in its header.
|
|
39
|
+
*
|
|
40
|
+
* ```sh
|
|
41
|
+
* #!/usr/bin/env bash
|
|
42
|
+
* # uses: jq curl
|
|
43
|
+
* # uses: git
|
|
44
|
+
* set -euo pipefail
|
|
45
|
+
* ```
|
|
46
|
+
*
|
|
47
|
+
* declares `jq`, `curl`, and `git`. The header is the run of blank and
|
|
48
|
+
* comment-only lines at the top of the file; a `# uses:` comment after the
|
|
49
|
+
* first command is not a declaration. Several pragma lines accumulate, names
|
|
50
|
+
* are whitespace-separated, and each declaration is `kind: 'script'` with a
|
|
51
|
+
* `range` that covers exactly that name. A word starting with `#` ends the list and begins a trailing
|
|
52
|
+
* remark: `# uses: jq # for json` declares only `jq`.
|
|
53
|
+
*
|
|
54
|
+
* Its `removeDeclaration` hook deletes one name and the whitespace that
|
|
55
|
+
* separated it, or the whole pragma line (with its `\n` or `\r\n`) when that
|
|
56
|
+
* name was the line's last.
|
|
57
|
+
*
|
|
58
|
+
* Its `declarationHint` hook tells a person exactly what to add: the line
|
|
59
|
+
* `# uses: curl`, on a line below the shebang, or as the first line of the
|
|
60
|
+
* file when there is no shebang. A name a pragma cannot express (one with
|
|
61
|
+
* whitespace, or starting with `#`) gets no hint.
|
|
62
|
+
*
|
|
63
|
+
* Bind it with `settings: { sh: { declarationConvention: commentPragma } }`.
|
|
64
|
+
*
|
|
65
|
+
* @public
|
|
66
|
+
*/
|
|
67
|
+
export declare const commentPragma: Readonly<Required<DeclarationConvention>>;
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Creates a rule that reports every listed command that is not run through
|
|
71
|
+
* `config.wrapper`, for a rule pack's "must be wrapped by X" policies:
|
|
72
|
+
*
|
|
73
|
+
* ```js
|
|
74
|
+
* import { createWrapperRule } from '@eslint-sh/eslint-plugin';
|
|
75
|
+
*
|
|
76
|
+
* export const requireAudit = createWrapperRule({
|
|
77
|
+
* commands: ['kubectl', 'terraform'],
|
|
78
|
+
* wrapper: 'audit-run',
|
|
79
|
+
* });
|
|
80
|
+
* ```
|
|
81
|
+
*
|
|
82
|
+
* The rule declares `meta.languages: ["sh/sh"]`, takes no options (the
|
|
83
|
+
* config is bound here), and reports with message id `unwrappedCommand`,
|
|
84
|
+
* naming the command and the wrapper. Its `meta.docs.url` and
|
|
85
|
+
* `meta.docs.description` default to this package's wrapper-rules page and a
|
|
86
|
+
* sentence naming the policy; `config.docs` overrides either. A listed
|
|
87
|
+
* command is wrapped when the wrapper encloses it in `sh-ast/analyze`'s
|
|
88
|
+
* transparent-wrapper chain, so `audit-run sudo kubectl apply` is wrapped.
|
|
89
|
+
* Command and wrapper words written as paths match by basename
|
|
90
|
+
* (`/usr/bin/kubectl`). Commands inside `$(…)`, functions, and pipelines are
|
|
91
|
+
* checked; a command whose name or subcommand is not static is not reported.
|
|
92
|
+
*
|
|
93
|
+
* @param config - The commands, the wrapper they must be run through, any
|
|
94
|
+
* subcommand restrictions, and optional `meta.docs` overrides.
|
|
95
|
+
* @returns A {@link ShellRule}: an ESLint rule for the `sh/sh` language,
|
|
96
|
+
* typed so a pack's own declarations need no `@eslint/core` import to name it.
|
|
97
|
+
* @throws TypeError when `config` is malformed (no `commands` array, an empty
|
|
98
|
+
* wrapper name, a subcommand restriction without `names`, or an empty or
|
|
99
|
+
* non-string `docs.url` / `docs.description`).
|
|
100
|
+
*
|
|
101
|
+
* @public
|
|
102
|
+
*/
|
|
103
|
+
export declare function createWrapperRule(config: WrapperRuleConfig): ShellRule<[], 'unwrappedCommand'>;
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* How a project declares the external commands its shell scripts depend on.
|
|
107
|
+
*
|
|
108
|
+
* Implement this to teach the plugin a project's own declaration syntax, then
|
|
109
|
+
* bind it with `settings: { sh: { declarationConvention: myConvention } }`.
|
|
110
|
+
* Until a convention is bound, rules that consume one report nothing.
|
|
111
|
+
*
|
|
112
|
+
* Bind it through `settings`, not through a rule's options: ESLint copies
|
|
113
|
+
* rule options with `structuredClone`, which cannot copy functions. Define it
|
|
114
|
+
* as a plain object whose methods are its own properties, and bind it in one
|
|
115
|
+
* config entry: when two matching config entries both set it, ESLint merges
|
|
116
|
+
* the two objects property by property, which drops methods inherited from a
|
|
117
|
+
* class prototype and can mix methods from two different conventions.
|
|
118
|
+
*
|
|
119
|
+
* One convention object serves every file its config entry matches, so its
|
|
120
|
+
* answer must depend only on its arguments and on data it holds, never on
|
|
121
|
+
* which file it saw before. ESLint's `--cache` sees a convention's own
|
|
122
|
+
* enumerable data properties (they are part of the config it hashes) but not
|
|
123
|
+
* files the convention reads while linting.
|
|
124
|
+
*
|
|
125
|
+
* {@link commentPragma} is the reference implementation.
|
|
126
|
+
*
|
|
127
|
+
* @public
|
|
128
|
+
*/
|
|
129
|
+
export declare interface DeclarationConvention {
|
|
130
|
+
/**
|
|
131
|
+
* Returns every command the linted file declares, in source order. A name
|
|
132
|
+
* declared twice appears twice, each with its own source. `file` names the
|
|
133
|
+
* file being linted, for a convention whose declarations live beside it;
|
|
134
|
+
* a convention that reads only `sourceCode` can leave it out.
|
|
135
|
+
*/
|
|
136
|
+
declaredCommands(sourceCode: ShellSourceCode, file: LintedFile): DeclaredCommand[];
|
|
137
|
+
/**
|
|
138
|
+
* Optional edit hook: returns the fix that deletes the single stale
|
|
139
|
+
* declaration `declared` (an in-script entry previously returned by
|
|
140
|
+
* {@link DeclarationConvention.declaredCommands} for the same
|
|
141
|
+
* `sourceCode`), built with the rule's `fixer`. Returns `null` when that
|
|
142
|
+
* declaration cannot be removed safely.
|
|
143
|
+
*
|
|
144
|
+
* It is only ever called with a {@link ScriptDeclaredCommand}: a fix edits
|
|
145
|
+
* only the linted script, and no edit to the script removes a declaration
|
|
146
|
+
* kept anywhere else.
|
|
147
|
+
*
|
|
148
|
+
* A convention without this hook still reports stale declarations; those
|
|
149
|
+
* reports simply carry no autofix.
|
|
150
|
+
*/
|
|
151
|
+
removeDeclaration?(sourceCode: ShellSourceCode, declared: ScriptDeclaredCommand, fixer: RuleTextEditor): RuleTextEdit | RuleTextEdit[] | null;
|
|
152
|
+
/**
|
|
153
|
+
* Optional hint hook: returns one short sentence that tells a person how to
|
|
154
|
+
* declare `command`, with text they can copy. {@link commentPragma}'s hint says to
|
|
155
|
+
* add `# uses: curl` on a line below the shebang.
|
|
156
|
+
* `sh/no-undeclared-commands` appends it to its report. Following the
|
|
157
|
+
* sentence literally must make that report go away.
|
|
158
|
+
*
|
|
159
|
+
* Return `null` when this convention cannot declare `command` as written
|
|
160
|
+
* (for example, a name its syntax cannot express). The report then falls
|
|
161
|
+
* back to a generic sentence, as it does for a convention without this
|
|
162
|
+
* hook. Anything other than `null` must be a non-empty single-line string.
|
|
163
|
+
*/
|
|
164
|
+
declarationHint?(command: string, sourceCode: ShellSourceCode, file: LintedFile): string | null;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* The slice of an ESLint rule context that binding resolution reads: the rule
|
|
169
|
+
* id (for error messages), its configured options, and the shared settings.
|
|
170
|
+
* Any rule `context` satisfies it, so a rule passes its own `context`.
|
|
171
|
+
*
|
|
172
|
+
* @public
|
|
173
|
+
*/
|
|
174
|
+
export declare interface DeclarationConventionBindingContext {
|
|
175
|
+
/** The rule id as configured (`pack/my-rule`), named in an option-binding error. */
|
|
176
|
+
readonly id: string;
|
|
177
|
+
/** The rule's configured options; the first may carry `declarationConvention`. */
|
|
178
|
+
readonly options: readonly unknown[];
|
|
179
|
+
/** The shared settings; `settings.sh.declarationConvention` is the usual binding. */
|
|
180
|
+
readonly settings: SettingsConfig;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* What {@link readDeclaredCommands} reads from a rule's context: the linted
|
|
185
|
+
* file and its source code. Any shell rule's `context` satisfies it.
|
|
186
|
+
*
|
|
187
|
+
* @public
|
|
188
|
+
*/
|
|
189
|
+
export declare interface DeclarationReadContext extends LintedFile {
|
|
190
|
+
/** The source code of the file being linted. */
|
|
191
|
+
readonly sourceCode: ShellSourceCode;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* One command a script declares it depends on, as reported by a
|
|
196
|
+
* {@link DeclarationConvention}. Where the declaration lives is part of what
|
|
197
|
+
* it is, so it is one of three kinds, told apart by the required `kind`:
|
|
198
|
+
* written in the script ({@link ScriptDeclaredCommand}), in another file
|
|
199
|
+
* ({@link FileDeclaredCommand}), or in a source that can only be described in
|
|
200
|
+
* words ({@link DescribedDeclaredCommand}). Only the script kind has a `range`, so
|
|
201
|
+
* narrow on `kind` before reading it.
|
|
202
|
+
*
|
|
203
|
+
* @public
|
|
204
|
+
*/
|
|
205
|
+
export declare type DeclaredCommand = ScriptDeclaredCommand | FileDeclaredCommand | DescribedDeclaredCommand;
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Builds a shell rule from its spec, setting `meta.languages` to
|
|
209
|
+
* exactly `["sh/sh"]` so ESLint refuses to run the rule on any other language.
|
|
210
|
+
* Everything else in `meta`, and `create`, is kept as written; message ids
|
|
211
|
+
* and option types are inferred from `meta` or may be given explicitly
|
|
212
|
+
* (`defineShellRule<[], 'found'>(…)`).
|
|
213
|
+
*
|
|
214
|
+
* The language fields are written last, so a JavaScript caller that bypasses
|
|
215
|
+
* the types and supplies its own `languages` or `language` still gets
|
|
216
|
+
* exactly `["sh/sh"]` (and no `language`). A JavaScript caller that omits
|
|
217
|
+
* `meta`, which ESLint allows for a rule, is treated as having written
|
|
218
|
+
* `meta: {}`.
|
|
219
|
+
*
|
|
220
|
+
* @example
|
|
221
|
+
* ```ts
|
|
222
|
+
* import { defineShellRule } from '@eslint-sh/eslint-plugin';
|
|
223
|
+
*
|
|
224
|
+
* export const noEval = defineShellRule({
|
|
225
|
+
* meta: { type: 'problem', schema: [], messages: { evalUsed: 'Avoid eval.' } },
|
|
226
|
+
* create(context) {
|
|
227
|
+
* return {
|
|
228
|
+
* CallExpr(node) {
|
|
229
|
+
* if (context.sourceCode.getCommandName(node) === 'eval') {
|
|
230
|
+
* context.report({ node, messageId: 'evalUsed' });
|
|
231
|
+
* }
|
|
232
|
+
* },
|
|
233
|
+
* };
|
|
234
|
+
* },
|
|
235
|
+
* });
|
|
236
|
+
* ```
|
|
237
|
+
*
|
|
238
|
+
* @public
|
|
239
|
+
*/
|
|
240
|
+
export declare function defineShellRule<RuleOptions extends unknown[] = [], MessageIds extends string = string>(spec: ShellRuleSpec<RuleOptions, MessageIds>): ShellRule<RuleOptions, MessageIds>;
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* A declaration whose source can only be described in words: there is no
|
|
244
|
+
* range in the script and no path to point at, only a phrase that tells a
|
|
245
|
+
* person where it comes from, such as a list written in the ESLint config, a
|
|
246
|
+
* convention's own constant, or a snapshot of a tool registry. A report about
|
|
247
|
+
* it names the source by `description` and carries no fix.
|
|
248
|
+
*
|
|
249
|
+
* @public
|
|
250
|
+
*/
|
|
251
|
+
export declare interface DescribedDeclaredCommand {
|
|
252
|
+
/** Says the declaration's source is described in words, by `description`. */
|
|
253
|
+
readonly kind: 'described';
|
|
254
|
+
/** The declared command name, exactly as it would be invoked. */
|
|
255
|
+
readonly name: string;
|
|
256
|
+
/**
|
|
257
|
+
* Names the source for a person who wants to change the declaration, as a
|
|
258
|
+
* phrase that reads after "declared in": "the web image list in
|
|
259
|
+
* eslint.config.js", "the tool registry snapshot of 2026-11-01". It must be a
|
|
260
|
+
* non-empty single line.
|
|
261
|
+
*/
|
|
262
|
+
readonly description: string;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* A declaration written in a file other than the linted script: a per-script
|
|
267
|
+
* sidecar, or a manifest several scripts share. A report about it names the
|
|
268
|
+
* file, and carries no fix, since a fix can only edit the linted script.
|
|
269
|
+
*
|
|
270
|
+
* @public
|
|
271
|
+
*/
|
|
272
|
+
export declare interface FileDeclaredCommand {
|
|
273
|
+
/** Says the declaration is in another file. */
|
|
274
|
+
readonly kind: 'file';
|
|
275
|
+
/** The declared command name, exactly as it would be invoked. */
|
|
276
|
+
readonly name: string;
|
|
277
|
+
/**
|
|
278
|
+
* Where a person or a tool should look for the declaration: a path
|
|
279
|
+
* relative to {@link LintedFile.cwd}, or an absolute path. It must be a
|
|
280
|
+
* non-empty single line. Rules name it in their reports as given; they never
|
|
281
|
+
* read it.
|
|
282
|
+
*/
|
|
283
|
+
readonly path: string;
|
|
284
|
+
/** The 1-based line of `path` that declares the command, when the convention knows it. */
|
|
285
|
+
readonly line?: number;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* The file ESLint is linting, as its rule context names it. A
|
|
290
|
+
* {@link DeclarationConvention} receives it so it can find declarations kept
|
|
291
|
+
* outside the script, such as a per-script sidecar file next to it.
|
|
292
|
+
*
|
|
293
|
+
* A rule's own `context` is a `LintedFile`, so a rule that calls
|
|
294
|
+
* `declaredCommands` passes `context`.
|
|
295
|
+
*
|
|
296
|
+
* @public
|
|
297
|
+
*/
|
|
298
|
+
export declare interface LintedFile {
|
|
299
|
+
/**
|
|
300
|
+
* The name ESLint lints the code under (`context.filename`). For a code
|
|
301
|
+
* block a processor extracts from another file it is virtual
|
|
302
|
+
* (`README.md/0_0.sh`).
|
|
303
|
+
*/
|
|
304
|
+
readonly filename: string;
|
|
305
|
+
/**
|
|
306
|
+
* The path on disk ESLint associates with the code
|
|
307
|
+
* (`context.physicalFilename`): the file it read, or the file a
|
|
308
|
+
* processor's code block came from. With `--stdin --stdin-filename`, it is
|
|
309
|
+
* the name given on the command line; the code came from standard input,
|
|
310
|
+
* and that file may not exist. Resolve sidecar paths from this one, and
|
|
311
|
+
* treat a missing sidecar as a normal case.
|
|
312
|
+
*/
|
|
313
|
+
readonly physicalFilename: string;
|
|
314
|
+
/**
|
|
315
|
+
* ESLint's working directory (`context.cwd`). A relative
|
|
316
|
+
* {@link FileDeclaredCommand.path} is relative to it.
|
|
317
|
+
*/
|
|
318
|
+
readonly cwd: string;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* The result of {@link ShellLanguage.normalizeLanguageOptions}: `dialect` and
|
|
323
|
+
* `directivePrefix` are always resolved to concrete values (defaulted when
|
|
324
|
+
* the caller didn't specify them), and `toJSON()` provides the "custom
|
|
325
|
+
* serialization" ESLint's languages guide recommends
|
|
326
|
+
* (https://eslint.org/docs/latest/extend/languages, "normalizeLanguageOptions")
|
|
327
|
+
* so that config caching keyed off `JSON.stringify(languageOptions)` is
|
|
328
|
+
* stable irrespective of the input object's own key order — `JSON.stringify`
|
|
329
|
+
* calls `toJSON()` automatically when present.
|
|
330
|
+
*
|
|
331
|
+
* @public
|
|
332
|
+
*/
|
|
333
|
+
export declare interface NormalizedShellLanguageOptions extends Record<string, unknown> {
|
|
334
|
+
readonly dialect: ShellDialect;
|
|
335
|
+
readonly directivePrefix: string;
|
|
336
|
+
readonly shellVersion?: string;
|
|
337
|
+
toJSON(): ShellLanguageOptionsJSON;
|
|
338
|
+
}
|
|
13
339
|
|
|
14
340
|
/**
|
|
15
|
-
* The `@eslint-sh/eslint-plugin` plugin object
|
|
341
|
+
* The `@eslint-sh/eslint-plugin` plugin object, this package's default
|
|
342
|
+
* export (`import sh from '@eslint-sh/eslint-plugin'`). `languages.sh` provides
|
|
16
343
|
* `ShellLanguage` / `ShellSourceCode`, selected in a flat config via
|
|
17
344
|
* `language: "sh/sh"` (`namespace: "sh"`). `meta.version` is read from this
|
|
18
345
|
* package's own `package.json` at runtime — never hardcoded (see
|
|
19
346
|
* `version.ts`).
|
|
20
347
|
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
348
|
+
* `rules` holds the plugin's built-in rules, each built with
|
|
349
|
+
* `defineShellRule` (so each declares `meta.languages: ["sh/sh"]`) and
|
|
350
|
+
* linking its docs page from `meta.docs.url`. Consumers can also supply their
|
|
351
|
+
* own rules, built with the same `defineShellRule`, while sharing parsing,
|
|
352
|
+
* selectors, source coordinates, and directive semantics.
|
|
353
|
+
*
|
|
354
|
+
* `configs.recommended` is the `sh/recommended` preset: a single config
|
|
355
|
+
* object carrying `plugins: { sh }` and `language: "sh/sh"` but no `files`,
|
|
356
|
+
* so it inherits the globs of the entry that extends it:
|
|
357
|
+
* `{ files: [...], extends: [sh.configs.recommended] }`. It enables the
|
|
358
|
+
* convention-free policy rules. The rules index at
|
|
359
|
+
* https://github.com/mike-north/eslint-sh/blob/main/packages/eslint-plugin/docs/rules/README.md
|
|
360
|
+
* describes preset membership and available autofixes.
|
|
361
|
+
* `configs['agent-authored']` is the strict `sh/agent-authored` preset for
|
|
362
|
+
* machine-written, human-reviewed scripts:
|
|
363
|
+
* every `recommended` rule plus the contract rules, all at `error`.
|
|
364
|
+
* `configs['bash-3.2']` and `configs['posix-portable']` are deployment-target
|
|
365
|
+
* packs: the first sets `languageOptions.shellVersion: "3.2"` and enables
|
|
366
|
+
* every version-gated `compat/*` rule; the second sets
|
|
367
|
+
* `languageOptions.dialect: "posix"`, enables the `compat/*` rules and the
|
|
368
|
+
* shebang/dialect check, and requires `set -eu`; it reports named constructs
|
|
369
|
+
* that fail on dash, not every non-POSIX construct, and leaves `local`,
|
|
370
|
+
* `echo -e` and the rest of shellcheck's SC3xxx catalog to shellcheck. Every
|
|
371
|
+
* shipped config is a
|
|
372
|
+
* single object with no `files`; the two packs also carry `languageOptions`,
|
|
373
|
+
* because the target they name is their meaning.
|
|
23
374
|
*
|
|
24
375
|
* Re-exported, unchanged, as the default export of the `eslint-sh` umbrella
|
|
25
376
|
* package: importing this package or the umbrella package both yield the
|
|
26
377
|
* same plugin object.
|
|
378
|
+
*
|
|
379
|
+
* @public
|
|
27
380
|
*/
|
|
28
|
-
declare const
|
|
381
|
+
declare const plugin: {
|
|
29
382
|
meta: {
|
|
30
383
|
name: string;
|
|
31
384
|
version: string;
|
|
@@ -34,57 +387,204 @@ declare const _default: {
|
|
|
34
387
|
languages: {
|
|
35
388
|
sh: ShellLanguage;
|
|
36
389
|
};
|
|
37
|
-
rules:
|
|
390
|
+
rules: Record<string, RuleDefinition<RuleDefinitionTypeOptions>>;
|
|
391
|
+
configs: {
|
|
392
|
+
recommended: ConfigObject;
|
|
393
|
+
"agent-authored": ConfigObject;
|
|
394
|
+
"bash-3.2": ConfigObject;
|
|
395
|
+
"posix-portable": ConfigObject;
|
|
396
|
+
};
|
|
38
397
|
};
|
|
39
|
-
export default
|
|
398
|
+
export default plugin;
|
|
40
399
|
|
|
41
400
|
/**
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
401
|
+
* Returns the declarations `convention` makes for the file `context` is
|
|
402
|
+
* linting, after checking each one against its kind. Call it from a rule's
|
|
403
|
+
* listener with the convention `resolveDeclarationConvention` returned and
|
|
404
|
+
* the rule's own `context`.
|
|
405
|
+
*
|
|
406
|
+
* The convention receives a frozen {@link LintedFile} holding only
|
|
407
|
+
* `filename`, `physicalFilename` and `cwd`, never the whole context, so it
|
|
408
|
+
* sees which file it is reading and nothing it could report through.
|
|
409
|
+
*
|
|
410
|
+
* Throws an `Error` that names the setting, starting
|
|
411
|
+
* `Invalid settings.sh.declarationConvention:`, and shows the offending value when
|
|
412
|
+
* `declaredCommands` does not return an array, or returns a declaration with
|
|
413
|
+
* a missing or unknown `kind`, without its kind's fields, or with a `range`
|
|
414
|
+
* outside the linted text.
|
|
415
|
+
*
|
|
416
|
+
* @example
|
|
417
|
+
* ```ts
|
|
418
|
+
* const convention = resolveDeclarationConvention(context);
|
|
419
|
+
* if (convention === null) return {};
|
|
420
|
+
* return {
|
|
421
|
+
* File() {
|
|
422
|
+
* for (const declared of readDeclaredCommands(convention, context)) {
|
|
423
|
+
* if (declared.kind === 'script') {
|
|
424
|
+
* // declared.range is an offset pair into context.sourceCode.text
|
|
425
|
+
* }
|
|
426
|
+
* }
|
|
427
|
+
* },
|
|
428
|
+
* };
|
|
429
|
+
* ```
|
|
50
430
|
*
|
|
51
431
|
* @public
|
|
52
432
|
*/
|
|
53
|
-
export declare
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
433
|
+
export declare function readDeclaredCommands(convention: DeclarationConvention, context: DeclarationReadContext): DeclaredCommand[];
|
|
434
|
+
|
|
435
|
+
export { ResolveArgv0Options }
|
|
436
|
+
|
|
437
|
+
/**
|
|
438
|
+
* Resolves the declaration convention bound for the rule running in
|
|
439
|
+
* `context`: the rule's `declarationConvention` option, else
|
|
440
|
+
* `settings.sh.declarationConvention`, else `null` (the rule is inert).
|
|
441
|
+
*
|
|
442
|
+
* Throws an `Error` naming the offending setting when the configured value is
|
|
443
|
+
* present but does not implement `declaredCommands` (or has a non-function
|
|
444
|
+
* `removeDeclaration`). The message names where the value came from and what
|
|
445
|
+
* it was, for example one beginning
|
|
446
|
+
* `Invalid settings.sh.declarationConvention: expected a declaration convention`
|
|
447
|
+
* and ending `but got string "jq".` Call it from `create()`, so the error
|
|
448
|
+
* surfaces as ESLint's
|
|
449
|
+
* configuration message for the file being linted.
|
|
450
|
+
*
|
|
451
|
+
* A rule that receives `null` should register no listeners: with no
|
|
452
|
+
* convention bound, a convention consumer has nothing to check.
|
|
453
|
+
*
|
|
454
|
+
* @example
|
|
455
|
+
* ```ts
|
|
456
|
+
* import {
|
|
457
|
+
* defineShellRule,
|
|
458
|
+
* readDeclaredCommands,
|
|
459
|
+
* resolveDeclarationConvention,
|
|
460
|
+
* } from '@eslint-sh/eslint-plugin';
|
|
461
|
+
*
|
|
462
|
+
* export const maxDeclaredCommands = defineShellRule({
|
|
463
|
+
* meta: { schema: [], messages: { tooMany: 'Declare at most 5 commands.' } },
|
|
464
|
+
* create(context) {
|
|
465
|
+
* const convention = resolveDeclarationConvention(context);
|
|
466
|
+
* if (convention === null) return {};
|
|
467
|
+
* return {
|
|
468
|
+
* File(node) {
|
|
469
|
+
* // Validated declarations for the file `context` is linting.
|
|
470
|
+
* if (readDeclaredCommands(convention, context).length > 5) {
|
|
471
|
+
* context.report({ node, messageId: 'tooMany' });
|
|
472
|
+
* }
|
|
473
|
+
* },
|
|
474
|
+
* };
|
|
475
|
+
* },
|
|
476
|
+
* });
|
|
477
|
+
* ```
|
|
478
|
+
*
|
|
479
|
+
* @public
|
|
480
|
+
*/
|
|
481
|
+
export declare function resolveDeclarationConvention(context: DeclarationConventionBindingContext): DeclarationConvention | null;
|
|
59
482
|
|
|
60
483
|
/**
|
|
61
484
|
* Resolves a parsed lint Word using the upstream-shaped syntax associated with it.
|
|
62
485
|
* The view supplies locations and selector fields; sh-ast/analyze owns the word
|
|
63
486
|
* interpretation. Fabricated or synthetic view nodes have no source syntax and
|
|
64
487
|
* are rejected instead of being treated as known or unknown shell values.
|
|
488
|
+
* A static word that ends a line of a CRLF file resolves without the line
|
|
489
|
+
* terminator's carriage return, as it would on LF input.
|
|
65
490
|
* @public
|
|
66
491
|
*/
|
|
67
|
-
export declare function resolveWord(word:
|
|
492
|
+
export declare function resolveWord(word: ShellNodeOf<'Word'>, options?: ResolveWordOptions): WordResolution;
|
|
68
493
|
|
|
69
|
-
/**
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
494
|
+
/**
|
|
495
|
+
* A declaration written in the linted script's own text: the only kind an
|
|
496
|
+
* ESLint fix can edit, because a fix edits only the file being linted.
|
|
497
|
+
*
|
|
498
|
+
* @public
|
|
499
|
+
*/
|
|
500
|
+
export declare interface ScriptDeclaredCommand {
|
|
501
|
+
/** Says the declaration is in the script. */
|
|
502
|
+
readonly kind: 'script';
|
|
503
|
+
/** The declared command name, exactly as it would be invoked (`jq`, `docker-compose`). */
|
|
504
|
+
readonly name: string;
|
|
505
|
+
/**
|
|
506
|
+
* The `[start, end)` source range of this declaration, as UTF-16 offsets
|
|
507
|
+
* into `sourceCode.text` (the same coordinates as every node's `range`),
|
|
508
|
+
* with `0 <= start <= end <= sourceCode.text.length`. Rules report a
|
|
509
|
+
* declaration at this range, so it should cover the text that declares
|
|
510
|
+
* this one name and nothing else.
|
|
511
|
+
*/
|
|
512
|
+
readonly range: readonly [number, number];
|
|
75
513
|
}
|
|
76
514
|
|
|
515
|
+
/**
|
|
516
|
+
* One command invocation in a linted shell file, in lint-view terms: an
|
|
517
|
+
* `sh-ast/analyze` `CommandSite` whose `node` is the `CallExpr` view node
|
|
518
|
+
* ESLint passes to rule visitors, so a rule can `context.report()` on it.
|
|
519
|
+
*
|
|
520
|
+
* The data comes from `sh-ast/analyze`'s `enumerateCommands` and is facts
|
|
521
|
+
* only: how the command is reached and what its words resolve to, never a
|
|
522
|
+
* verdict on whether running it is safe. A dynamic (`static: false`) `argv0`
|
|
523
|
+
* is a normal result.
|
|
524
|
+
*
|
|
525
|
+
* Read one through `ShellSourceCode.getCommandSites()`.
|
|
526
|
+
*
|
|
527
|
+
* Every rule linting a file sees the same sites, so each one is deeply
|
|
528
|
+
* frozen: the entry, its `context` array and every frame in it, its `argv`
|
|
529
|
+
* array, and every `WordResolution` in `argv` (`argv0` included). Writing to
|
|
530
|
+
* any of them throws in strict-mode code and is ignored otherwise, so one rule
|
|
531
|
+
* cannot change what another rule reads. The `node` is the shared lint view
|
|
532
|
+
* node, which, like every node ESLint passes to rules, is not frozen and must
|
|
533
|
+
* not be modified.
|
|
534
|
+
*
|
|
535
|
+
* @public
|
|
536
|
+
*/
|
|
537
|
+
export declare interface ShellCommandSite {
|
|
538
|
+
/**
|
|
539
|
+
* The `CallExpr` view node for this command: the same object (`===`)
|
|
540
|
+
* ESLint passes to a `CallExpr` visitor, with `range` and `loc` in this
|
|
541
|
+
* file's UTF-16 coordinates.
|
|
542
|
+
*/
|
|
543
|
+
readonly node: ShellNodeOf<'CallExpr'>;
|
|
544
|
+
/**
|
|
545
|
+
* The path from the file root to this command, outermost frame first,
|
|
546
|
+
* exactly as `sh-ast/analyze` reports it. For example, `date` in
|
|
547
|
+
* `f() { echo "$(date)"; }` has
|
|
548
|
+
* `[{ kind: 'function', name: 'f' }, { kind: 'cmdSubst' }]`.
|
|
549
|
+
*/
|
|
550
|
+
readonly context: readonly CommandContext[];
|
|
551
|
+
/** The command word (`argv[0]`) resolved with `{ context: 'command-argument' }`. */
|
|
552
|
+
readonly argv0: WordResolution;
|
|
553
|
+
/**
|
|
554
|
+
* Every word of the command, `argv0` included, in argument order, each
|
|
555
|
+
* resolved with `{ context: 'command-argument' }`. Leading `VAR=val`
|
|
556
|
+
* assignments are not part of argv.
|
|
557
|
+
*/
|
|
558
|
+
readonly argv: readonly WordResolution[];
|
|
559
|
+
/**
|
|
560
|
+
* Follows this command's argv0 through transparent wrappers (`env`, `sudo`,
|
|
561
|
+
* `nohup`, `exec`, …) and returns `sh-ast/analyze`'s `resolveArgv0`
|
|
562
|
+
* result. Same answer as
|
|
563
|
+
* `ShellSourceCode.resolveInvokedCommand(node, options)`; both read the
|
|
564
|
+
* same per-file index.
|
|
565
|
+
*
|
|
566
|
+
* @param options - Forwarded to `resolveArgv0` unchanged.
|
|
567
|
+
* `transparentWrappers` replaces `sh-ast/analyze`'s
|
|
568
|
+
* `DEFAULT_TRANSPARENT_WRAPPERS`; spread the defaults into the array to
|
|
569
|
+
* extend them instead.
|
|
570
|
+
* @returns A new result on every call. Its `chain` and `chainIndices`
|
|
571
|
+
* arrays belong to the caller. `chainIndices` gives each chain entry's
|
|
572
|
+
* position in this site's `argv`.
|
|
573
|
+
* @throws `ShAnalyzeInvalidWrapperSpecError` (from `sh-ast/analyze`) when
|
|
574
|
+
* `options.transparentWrappers` contains a malformed entry.
|
|
575
|
+
*/
|
|
576
|
+
resolveInvokedCommand(options?: ResolveArgv0Options): Argv0Resolution;
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
/** Directive processing uses the comment text without its leading shell hash. @public */
|
|
580
|
+
export declare type ShellComment = ShellNodeOf<'Comment'>;
|
|
581
|
+
|
|
77
582
|
/** Owns ESLint configuration labels and validates their consumer-facing meaning before parsing. */
|
|
78
583
|
/** The dialect labels understood by ESLint configuration, mapped to upstream numeric variants when parsing. @public */
|
|
79
584
|
export declare type ShellDialect = 'bash' | 'posix' | 'mksh' | 'bats' | 'zsh';
|
|
80
585
|
|
|
81
586
|
/** The lint root has a statement collection even when upstream represents it as nil. @public */
|
|
82
|
-
export declare
|
|
83
|
-
/** Root discriminator retained for selectors. */
|
|
84
|
-
readonly type: 'File';
|
|
85
|
-
/** Top-level statements in source order; nil upstream slices become an empty view. */
|
|
86
|
-
readonly stmts: readonly ShellNode[];
|
|
87
|
-
}
|
|
587
|
+
export declare type ShellFile = ShellNodeOf<'File'>;
|
|
88
588
|
|
|
89
589
|
/**
|
|
90
590
|
* `ShellLanguage` — the `@eslint/core` `Language` implementation for shell
|
|
@@ -144,6 +644,13 @@ export declare class ShellLanguage implements Language {
|
|
|
144
644
|
* the `{ok: true, ast}` / `{ok: false, errors}` shape `Language.parse` is
|
|
145
645
|
* required to return. Never throws — parse failures are reported as
|
|
146
646
|
* fatal lint messages, per the `Language` contract.
|
|
647
|
+
*
|
|
648
|
+
* A file whose extension marks it as a known non-shell source type (for
|
|
649
|
+
* example `.js` or `.json`) is never shell-parsed: it fails with a single
|
|
650
|
+
* diagnostic explaining that a `language: 'sh/sh'` config entry without
|
|
651
|
+
* `files` reached it, and how to scope that entry. A source in which
|
|
652
|
+
* the parser silently drops text at a U+0080 fails at that character; a
|
|
653
|
+
* U+0080 the parser reads as an ordinary character does not.
|
|
147
654
|
*/
|
|
148
655
|
parse(file: File_2, context: {
|
|
149
656
|
languageOptions: ShellLanguageOptions;
|
|
@@ -157,7 +664,9 @@ export declare class ShellLanguage implements Language {
|
|
|
157
664
|
* `createSourceCode` is called) — `directivePrefix` is therefore always
|
|
158
665
|
* present, but `?? 'eslint'` stays as an explicit, honest fallback for
|
|
159
666
|
* direct construction (e.g. tests that skip the `Language` normalization
|
|
160
|
-
* step), mirroring `parse()`'s `dialect` default above.
|
|
667
|
+
* step), mirroring `parse()`'s `dialect` default above. `dialect` and
|
|
668
|
+
* `shellVersion` are forwarded so rules read the same normalized values
|
|
669
|
+
* through `sourceCode.dialect` / `sourceCode.shellVersion`.
|
|
161
670
|
*/
|
|
162
671
|
createSourceCode(file: File_2, parseResult: OkParseResult<ShellFile>, context: {
|
|
163
672
|
languageOptions: ShellLanguageOptions;
|
|
@@ -181,6 +690,13 @@ export declare class ShellLanguage implements Language {
|
|
|
181
690
|
*/
|
|
182
691
|
export declare interface ShellLanguageOptions extends Record<string, unknown> {
|
|
183
692
|
readonly dialect?: ShellDialect;
|
|
693
|
+
/**
|
|
694
|
+
* The Bash version (`MAJOR[.MINOR]`, such as `"3.2"`) the scripts must run
|
|
695
|
+
* on. The parser ignores it. The `compat/*` rules read it: unset means the
|
|
696
|
+
* newest version, versions compare numerically by major then minor, and
|
|
697
|
+
* under `dialect: "posix"` Bash-only features are unavailable at any
|
|
698
|
+
* version.
|
|
699
|
+
*/
|
|
184
700
|
readonly shellVersion?: string;
|
|
185
701
|
/**
|
|
186
702
|
* The prefix directive/inline-config comments must start with — e.g. the
|
|
@@ -217,24 +733,107 @@ export declare interface ShellLanguageOptionsJSON {
|
|
|
217
733
|
}
|
|
218
734
|
|
|
219
735
|
/**
|
|
220
|
-
* A node
|
|
221
|
-
*
|
|
222
|
-
*
|
|
736
|
+
* A listener for a node of type `Node`. It is declared with method syntax on
|
|
737
|
+
* purpose: method parameters are checked bivariantly, which lets a listener
|
|
738
|
+
* for one node type sit alongside the union-typed listener every other
|
|
739
|
+
* selector key gets (see {@link ShellRuleVisitor}) without a type error.
|
|
740
|
+
*
|
|
741
|
+
* Exported so a helper that builds listeners for several rules can name the
|
|
742
|
+
* type it returns; `node` is the matched node and `parent` its parent
|
|
743
|
+
* (`undefined` for the root `File`).
|
|
744
|
+
*
|
|
223
745
|
* @public
|
|
224
746
|
*/
|
|
225
|
-
export declare
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
747
|
+
export declare type ShellListener<Node extends ShellNode = ShellNode> = {
|
|
748
|
+
listener(node: Node, parent: ShellNode | undefined): void;
|
|
749
|
+
}['listener'];
|
|
750
|
+
|
|
751
|
+
/**
|
|
752
|
+
* A node in the consumer's lint view: the union of every node type, so
|
|
753
|
+
* narrowing on `type` gives that node's fields with no cast. Scalar/child fields
|
|
754
|
+
* use lowercase upstream spellings; syntax fidelity belongs to the associated
|
|
755
|
+
* upstream tree. Auxiliary view nodes may have spans derived from children and
|
|
756
|
+
* are not upstream Nodes.
|
|
757
|
+
* @public
|
|
758
|
+
*/
|
|
759
|
+
export declare type ShellNode = ShellNodeMap[ShellNodeType];
|
|
760
|
+
|
|
761
|
+
/**
|
|
762
|
+
* The lint-view type of one upstream field value `Value`: a record becomes the
|
|
763
|
+
* node type of that record (a union of records, a union of node types), a slice
|
|
764
|
+
* becomes a read-only array of such nodes with its nil elements removed, a Go
|
|
765
|
+
* string becomes a JavaScript `string`, and booleans and operator values stay
|
|
766
|
+
* as they are. Upstream nil (`null`) and typed nils never reach a view, so
|
|
767
|
+
* neither appears in the result.
|
|
768
|
+
*
|
|
769
|
+
* It is the type of every field of every {@link ShellNodeMap} entry, exported
|
|
770
|
+
* so a helper that takes or returns a node's field value can name it.
|
|
771
|
+
*
|
|
772
|
+
* @public
|
|
773
|
+
*/
|
|
774
|
+
export declare type ShellNodeField<Value> = Exclude<Value, null | {
|
|
775
|
+
readonly Type: 'TypedNil';
|
|
776
|
+
}> extends infer Present ? Present extends readonly (infer Element)[] ? readonly ShellNodeField<Element>[] : Present extends {
|
|
777
|
+
readonly Type: infer Type extends ShellNodeType;
|
|
778
|
+
} ? ShellNodeMap[Type] : Present extends string | Uint8Array ? string : Present : never;
|
|
779
|
+
|
|
780
|
+
/**
|
|
781
|
+
* Lint-view node types by their `type` discriminator, one entry per upstream
|
|
782
|
+
* syntax record. It is computed from `sh-ast`'s generated interfaces, so it
|
|
783
|
+
* follows the pinned mvdan/sh version.
|
|
784
|
+
*
|
|
785
|
+
* A node has `type`, `range` and `loc`, plus a lowercase field for each
|
|
786
|
+
* non-position upstream field. A field that is a single child that may be nil
|
|
787
|
+
* upstream is optional, because the view omits the key when the child is
|
|
788
|
+
* absent; a slice is always present.
|
|
789
|
+
*
|
|
790
|
+
* `range` is a half-open UTF-16 span of the source text and `loc` its ESLint
|
|
791
|
+
* line/column form; both describe the view, not the upstream byte positions. A
|
|
792
|
+
* node whose line ends in CRLF ends before the `\r` (and a text field such as
|
|
793
|
+
* `value` loses that one trailing `\r`), so a CRLF file reads like its LF
|
|
794
|
+
* twin. Auxiliary records (`Expansion`, `Replace`, `Slice`) take their span
|
|
795
|
+
* from their children.
|
|
796
|
+
*
|
|
797
|
+
* @public
|
|
798
|
+
*/
|
|
799
|
+
export declare type ShellNodeMap = {
|
|
800
|
+
[Record in Exclude<Node, {
|
|
801
|
+
readonly Type: 'TypedNil';
|
|
802
|
+
}> | Expansion | Replace | Slice as Record['Type']]: {
|
|
803
|
+
/** Syntax kind of this node; the discriminator to narrow a ShellNode by. */
|
|
804
|
+
readonly type: Record['Type'];
|
|
805
|
+
/**
|
|
806
|
+
* Half-open UTF-16 span of the node in the source text. Its type is the
|
|
807
|
+
* mutable tuple ESLint's `fixer` methods take, so `node.range` can be passed
|
|
808
|
+
* to `fixer.insertTextBeforeRange` and the others without a cast; treat it
|
|
809
|
+
* as read-only.
|
|
810
|
+
*/
|
|
811
|
+
range: [number, number];
|
|
812
|
+
/** ESLint line and column of the span's two ends. */
|
|
813
|
+
readonly loc: {
|
|
814
|
+
readonly start: ShellPosition;
|
|
815
|
+
readonly end: ShellPosition;
|
|
816
|
+
};
|
|
817
|
+
} & {
|
|
818
|
+
readonly [Name in keyof Record & string as Name extends 'Type' ? never : [Record[Name]] extends [SyntaxPos] ? never : null extends Record[Name] ? Exclude<Record[Name], null | {
|
|
819
|
+
readonly Type: 'TypedNil';
|
|
820
|
+
}> extends {
|
|
821
|
+
readonly Type: string;
|
|
822
|
+
} ? never : Lowercase<Name> : Lowercase<Name>]: ShellNodeField<Record[Name]>;
|
|
823
|
+
} & {
|
|
824
|
+
readonly [Name in keyof Record & string as null extends Record[Name] ? Exclude<Record[Name], null | {
|
|
825
|
+
readonly Type: 'TypedNil';
|
|
826
|
+
}> extends {
|
|
827
|
+
readonly Type: string;
|
|
828
|
+
} ? Lowercase<Name> : never : never]?: ShellNodeField<Record[Name]>;
|
|
234
829
|
};
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
830
|
+
};
|
|
831
|
+
|
|
832
|
+
/** The lint-view node whose `type` is `Type`, for example `ShellNodeOf<'Stmt'>`. @public */
|
|
833
|
+
export declare type ShellNodeOf<Type extends ShellNodeType> = ShellNodeMap[Type];
|
|
834
|
+
|
|
835
|
+
/** Every value a lint-view node's `type` can hold. @public */
|
|
836
|
+
export declare type ShellNodeType = keyof ShellNodeMap;
|
|
238
837
|
|
|
239
838
|
/** An ESLint location counts UTF-16 units, with both line and column starting at one. @public */
|
|
240
839
|
export declare interface ShellPosition {
|
|
@@ -244,6 +843,105 @@ export declare interface ShellPosition {
|
|
|
244
843
|
readonly column: number;
|
|
245
844
|
}
|
|
246
845
|
|
|
846
|
+
/**
|
|
847
|
+
* A finished shell rule, as {@link defineShellRule} returns it: an ordinary
|
|
848
|
+
* ESLint rule definition (it goes in a plugin's `rules` like any other)
|
|
849
|
+
* specialized to the shell language, whose `meta.languages` is always exactly
|
|
850
|
+
* `["sh/sh"]`. The `meta.languages` requirement is part of the type, so a
|
|
851
|
+
* hand-written object that omits it, or names another language, is not a
|
|
852
|
+
* `ShellRule`.
|
|
853
|
+
*
|
|
854
|
+
* @public
|
|
855
|
+
*/
|
|
856
|
+
export declare type ShellRule<RuleOptions extends unknown[] = [], MessageIds extends string = string> = Omit<RuleDefinition<{
|
|
857
|
+
LangOptions: NormalizedShellLanguageOptions;
|
|
858
|
+
Code: ShellSourceCode;
|
|
859
|
+
RuleOptions: RuleOptions;
|
|
860
|
+
Visitor: ShellRuleVisitor;
|
|
861
|
+
Node: ShellNode;
|
|
862
|
+
MessageIds: MessageIds;
|
|
863
|
+
ExtRuleDocs: unknown;
|
|
864
|
+
}>, 'meta'> & {
|
|
865
|
+
readonly meta: Omit<RulesMeta<MessageIds, RuleOptions>, 'languages'> & {
|
|
866
|
+
languages: ['sh/sh'];
|
|
867
|
+
};
|
|
868
|
+
};
|
|
869
|
+
|
|
870
|
+
/**
|
|
871
|
+
* The `context` a shell rule's `create()` receives: ESLint's rule context
|
|
872
|
+
* specialized to the shell language, so `context.sourceCode` is
|
|
873
|
+
* {@link ShellSourceCode}, `context.languageOptions` is
|
|
874
|
+
* {@link NormalizedShellLanguageOptions}, `context.options` is `RuleOptions`,
|
|
875
|
+
* and `context.report` takes {@link ShellNode}s and only the `MessageIds` the
|
|
876
|
+
* rule declares. Name it when a helper outside `create()` needs the context.
|
|
877
|
+
*
|
|
878
|
+
* `context.languageOptions` is the object `ShellLanguage.normalizeLanguageOptions`
|
|
879
|
+
* returned, not the user's config input, so `dialect` is always set (`"bash"`
|
|
880
|
+
* by default) and needs no fallback. `context.sourceCode.dialect` carries that
|
|
881
|
+
* same normalized value; read whichever is closer to hand.
|
|
882
|
+
*
|
|
883
|
+
* @public
|
|
884
|
+
*/
|
|
885
|
+
export declare type ShellRuleContext<RuleOptions extends unknown[] = [], MessageIds extends string = string> = RuleContext<{
|
|
886
|
+
LangOptions: NormalizedShellLanguageOptions;
|
|
887
|
+
Code: ShellSourceCode;
|
|
888
|
+
RuleOptions: RuleOptions;
|
|
889
|
+
Node: ShellNode;
|
|
890
|
+
MessageIds: MessageIds;
|
|
891
|
+
}>;
|
|
892
|
+
|
|
893
|
+
/**
|
|
894
|
+
* The `meta` a rule author writes: every field ESLint's rule `meta` accepts
|
|
895
|
+
* (`type`, `docs`, `schema`, `messages`, `fixable`, `defaultOptions`, …)
|
|
896
|
+
* except the language fields, which {@link defineShellRule} owns. `languages`
|
|
897
|
+
* and its deprecated singular predecessor `language` are typed `never`, so
|
|
898
|
+
* writing either — in particular a language other than `sh/sh` — is a
|
|
899
|
+
* compile error rather than a value the helper silently replaces.
|
|
900
|
+
*
|
|
901
|
+
* @public
|
|
902
|
+
*/
|
|
903
|
+
export declare type ShellRuleMeta<RuleOptions extends unknown[] = [], MessageIds extends string = string> = Omit<RulesMeta<MessageIds, RuleOptions>, 'languages' | 'language'> & {
|
|
904
|
+
readonly languages?: never;
|
|
905
|
+
readonly language?: never;
|
|
906
|
+
};
|
|
907
|
+
|
|
908
|
+
/**
|
|
909
|
+
* What a rule author hands to {@link defineShellRule}: the rule's `meta` and
|
|
910
|
+
* its `create()`, exactly as in any ESLint rule, minus `meta.languages`. It is
|
|
911
|
+
* the rule before the helper finishes it; {@link ShellRule} is the finished
|
|
912
|
+
* rule (ESLint's `RuleDefinition`), so the input is named a spec, not a
|
|
913
|
+
* definition.
|
|
914
|
+
*
|
|
915
|
+
* @public
|
|
916
|
+
*/
|
|
917
|
+
export declare interface ShellRuleSpec<RuleOptions extends unknown[] = [], MessageIds extends string = string> {
|
|
918
|
+
/** The rule's metadata; see {@link ShellRuleMeta}. */
|
|
919
|
+
readonly meta: ShellRuleMeta<RuleOptions, MessageIds>;
|
|
920
|
+
/** Builds the rule's listeners for one file from the shell-typed context. */
|
|
921
|
+
readonly create: (context: ShellRuleContext<RuleOptions, MessageIds>) => ShellRuleVisitor;
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
/**
|
|
925
|
+
* The listener map a shell rule's `create()` returns. Keys are node types or
|
|
926
|
+
* esquery selectors written against the lint view's spelling (for example
|
|
927
|
+
* `CallExpr[args.0.parts.0.value='jq']` or the `:command` selector class),
|
|
928
|
+
* optionally suffixed with `:exit`. ESLint calls each listener with the
|
|
929
|
+
* matched node and its parent (the parent is `undefined` for the root
|
|
930
|
+
* `File`).
|
|
931
|
+
*
|
|
932
|
+
* A key that is exactly a node type, with or without `:exit` (`CallExpr`,
|
|
933
|
+
* `Word:exit`), types its listener's node as that node type, so
|
|
934
|
+
* `CallExpr(node) { node.args }` reads `args` with no `type` check. Any other
|
|
935
|
+
* selector string (`*`, `CallExpr[…]`, `:command`, `Stmt > CallExpr`) matches
|
|
936
|
+
* nodes of more than one type, or of one type the key does not state, so its
|
|
937
|
+
* listener receives the whole {@link ShellNode} union: narrow it on `type`.
|
|
938
|
+
*
|
|
939
|
+
* @public
|
|
940
|
+
*/
|
|
941
|
+
export declare interface ShellRuleVisitor extends ShellTypedListeners {
|
|
942
|
+
[selector: string]: ShellListener | undefined;
|
|
943
|
+
}
|
|
944
|
+
|
|
247
945
|
/**
|
|
248
946
|
* `ShellSourceCode` — the `SourceCode` object ESLint uses to work with a
|
|
249
947
|
* parsed shell file. Extends `@eslint/plugin-kit`'s `TextSourceCodeBase`, which
|
|
@@ -260,8 +958,8 @@ export declare interface ShellPosition {
|
|
|
260
958
|
* `applyInlineConfig`) recognizes all four disable-comment forms
|
|
261
959
|
* (`<prefix>-disable`, `<prefix>-enable`, `<prefix>-disable-line`,
|
|
262
960
|
* `<prefix>-disable-next-line`) plus whole-file inline rule config
|
|
263
|
-
* (bare `<prefix> <rule-config>`), sourced from `Comment` nodes
|
|
264
|
-
*
|
|
961
|
+
* (bare `<prefix> <rule-config>`), sourced from the tree's `Comment` nodes
|
|
962
|
+
* and parsed with
|
|
265
963
|
* `@eslint/plugin-kit`'s `ConfigCommentParser` — the same building blocks
|
|
266
964
|
* `@eslint/json`'s `JSONSourceCode` uses, generalized so the `<prefix>` is
|
|
267
965
|
* configurable (default `"eslint"`; see `ShellLanguageOptions.directivePrefix`)
|
|
@@ -271,6 +969,24 @@ export declare interface ShellPosition {
|
|
|
271
969
|
* classes) is implemented on `ShellLanguage` — see `shell-language.ts` and
|
|
272
970
|
* `selector-classes.ts`.
|
|
273
971
|
*
|
|
972
|
+
* Rule-author helpers (`resolveWordText`, `getCommandName`,
|
|
973
|
+
* `resolveInvokedCommand`, `getCommandSites`) answer "is this word statically
|
|
974
|
+
* the string `jq`?", "which command does this invocation run?", and "which
|
|
975
|
+
* commands does this file run, and where?" by delegating to
|
|
976
|
+
* `sh-ast/analyze`. They never reinterpret words themselves, so every rule
|
|
977
|
+
* gets the same answer for the same syntax. `dialect` and `shellVersion`
|
|
978
|
+
* expose the file's normalized language options.
|
|
979
|
+
*
|
|
980
|
+
* Syntax access (`getSyntax`, `getViewNode`, `getIndexFromSyntaxPos`,
|
|
981
|
+
* `getLocFromSyntaxPos`) reaches behind the lint view to the sh-ast records
|
|
982
|
+
* this file was parsed into: from a view node to its record and back, and
|
|
983
|
+
* from a record's byte positions (such as `BinaryCmd.OpPos`, which the view
|
|
984
|
+
* leaves out) to ESLint coordinates in this file's text.
|
|
985
|
+
*
|
|
986
|
+
* Printed fixes (`getEditableSyntax`, `getPrintedReplacement`) edit an
|
|
987
|
+
* independent copy of a view node's record and print it with sh-ast's
|
|
988
|
+
* printer, returning the source range and text for `fixer.replaceTextRange`.
|
|
989
|
+
*
|
|
274
990
|
* @public
|
|
275
991
|
*/
|
|
276
992
|
export declare class ShellSourceCode extends TextSourceCodeBase<{
|
|
@@ -284,7 +1000,324 @@ export declare class ShellSourceCode extends TextSourceCodeBase<{
|
|
|
284
1000
|
text: string;
|
|
285
1001
|
ast: ShellFile;
|
|
286
1002
|
directivePrefix?: string;
|
|
1003
|
+
dialect?: ShellDialect;
|
|
1004
|
+
shellVersion?: string;
|
|
287
1005
|
});
|
|
1006
|
+
/**
|
|
1007
|
+
* The shell dialect this file was parsed as: the normalized
|
|
1008
|
+
* `languageOptions.dialect`, which defaults to `"bash"` when the config
|
|
1009
|
+
* leaves it unset. Rules use it to gate dialect-specific checks.
|
|
1010
|
+
*/
|
|
1011
|
+
get dialect(): ShellDialect;
|
|
1012
|
+
/**
|
|
1013
|
+
* The configured `languageOptions.shellVersion` (a `MAJOR[.MINOR]` string
|
|
1014
|
+
* such as `"3.2"`), or `undefined` when the config does not set one. The
|
|
1015
|
+
* parser never reads it; it exists for rules that gate on shell version.
|
|
1016
|
+
*/
|
|
1017
|
+
get shellVersion(): string | undefined;
|
|
1018
|
+
/**
|
|
1019
|
+
* The one-based location of a UTF-16 `index` in this file's text, where
|
|
1020
|
+
* `index` may be anywhere from 0 to `text.length`. CRLF counts as one line
|
|
1021
|
+
* break.
|
|
1022
|
+
*
|
|
1023
|
+
* Overrides the base class, which measures every index from the root
|
|
1024
|
+
* node's own start and so assumes the root spans the whole text. A shell
|
|
1025
|
+
* file's root starts at its first statement or comment, so the base answer
|
|
1026
|
+
* was shifted by any leading blank lines or indentation.
|
|
1027
|
+
*
|
|
1028
|
+
* @throws TypeError when `index` is not a number.
|
|
1029
|
+
* @throws RangeError when `index` is not an integer from 0 to `text.length`.
|
|
1030
|
+
*/
|
|
1031
|
+
getLocFromIndex(index: number): {
|
|
1032
|
+
line: number;
|
|
1033
|
+
column: number;
|
|
1034
|
+
};
|
|
1035
|
+
/**
|
|
1036
|
+
* The UTF-16 index of a one-based `loc` in this file's text: the inverse of
|
|
1037
|
+
* {@link ShellSourceCode.getLocFromIndex}. The column may name any position
|
|
1038
|
+
* on its line up to and including the line break.
|
|
1039
|
+
*
|
|
1040
|
+
* Overrides the base class for the same reason as `getLocFromIndex`.
|
|
1041
|
+
*
|
|
1042
|
+
* @throws TypeError when `loc` is not an object with numeric `line` and `column`.
|
|
1043
|
+
* @throws RangeError when `loc` is not inside the text.
|
|
1044
|
+
*/
|
|
1045
|
+
getIndexFromLoc(loc: {
|
|
1046
|
+
line: number;
|
|
1047
|
+
column: number;
|
|
1048
|
+
}): number;
|
|
1049
|
+
/**
|
|
1050
|
+
* Returns the literal text of `word` when `sh-ast/analyze`'s `resolveWord`
|
|
1051
|
+
* reports it static, or `null` otherwise.
|
|
1052
|
+
*
|
|
1053
|
+
* Delegates to `resolveWord` (through this package's `resolveWord` export),
|
|
1054
|
+
* which alone defines what "static" means: a word is static iff
|
|
1055
|
+
* `resolveWord` reports `static: true`. Its non-static reasons are
|
|
1056
|
+
* `expansion`, `tilde`, `glob`, `brace`, `locale`, and `unsupported`.
|
|
1057
|
+
* Call the root `resolveWord` export instead when the reason matters.
|
|
1058
|
+
*
|
|
1059
|
+
* `options` is forwarded to `resolveWord` unchanged. When it is omitted, the
|
|
1060
|
+
* word's grammar position picks the tilde context: a word in a `CallExpr`'s
|
|
1061
|
+
* `args` uses `{ context: 'command-argument' }`, the context
|
|
1062
|
+
* `enumerateCommands` uses for argv, so `resolveWordText(callExpr.args[0])`
|
|
1063
|
+
* equals `getCommandName(callExpr)`. Any other word uses `resolveWord`'s
|
|
1064
|
+
* conservative default, which also treats a `~` after an unquoted `:` as a
|
|
1065
|
+
* tilde expansion, as assignment values do.
|
|
1066
|
+
*
|
|
1067
|
+
* @param word - A parsed `Word` node from this file's lint tree.
|
|
1068
|
+
* @param options - Forwarded to `resolveWord` unchanged when supplied.
|
|
1069
|
+
* @returns The resolved text, or `null` when the word is not static.
|
|
1070
|
+
* @throws TypeError when `word` is not a parsed shell `Word`.
|
|
1071
|
+
*/
|
|
1072
|
+
resolveWordText(word: ShellNodeOf<'Word'>, options?: ResolveWordOptions): string | null;
|
|
1073
|
+
/**
|
|
1074
|
+
* Returns the static text of `callExpr`'s command word (`argv[0]`) as
|
|
1075
|
+
* written, or `null` when that word is dynamic or absent. Transparent
|
|
1076
|
+
* wrappers are **not** followed: `sudo rm x` yields `"sudo"`. Use
|
|
1077
|
+
* {@link ShellSourceCode.resolveInvokedCommand} for the command a wrapper
|
|
1078
|
+
* chain actually runs.
|
|
1079
|
+
*
|
|
1080
|
+
* Delegates to `sh-ast/analyze`: the answer is the `argv0` resolution of the
|
|
1081
|
+
* `CommandSite` that `enumerateCommands` reports for this `CallExpr` in this
|
|
1082
|
+
* file. Leading `VAR=val` assignments are not part of argv, so
|
|
1083
|
+
* `FOO=1 jq .` yields `"jq"`. An assignment-only statement (`FOO=1`) has no
|
|
1084
|
+
* argv0 and no `CommandSite`, and yields `null`.
|
|
1085
|
+
*
|
|
1086
|
+
* @param callExpr - A parsed `CallExpr` node from this file's lint tree.
|
|
1087
|
+
* @returns The command word's static text, or `null`.
|
|
1088
|
+
* @throws TypeError when `callExpr` is not a parsed shell `CallExpr`.
|
|
1089
|
+
* @throws `ShAnalyzeMaxDepthError` (from `sh-ast/analyze`) when this file's
|
|
1090
|
+
* nesting exceeds analysis's recursion guard; see
|
|
1091
|
+
* {@link ShellSourceCode.resolveInvokedCommand}.
|
|
1092
|
+
*/
|
|
1093
|
+
getCommandName(callExpr: ShellNodeOf<'CallExpr'>): string | null;
|
|
1094
|
+
/**
|
|
1095
|
+
* Follows `callExpr`'s argv0 through transparent wrappers (`env`, `sudo`,
|
|
1096
|
+
* `nohup`, `exec`, …) and returns `sh-ast/analyze`'s full `resolveArgv0`
|
|
1097
|
+
* result, or `null` for a `CallExpr` that has no `CommandSite` (an
|
|
1098
|
+
* assignment-only statement such as `FOO=1`).
|
|
1099
|
+
*
|
|
1100
|
+
* Delegates to `resolveArgv0` with the `CommandSite` that `enumerateCommands`
|
|
1101
|
+
* reports for this `CallExpr` in this file, so it inherits that function's
|
|
1102
|
+
* guarantee that an unknowable word is never guessed through. Read the
|
|
1103
|
+
* effective command's name as
|
|
1104
|
+
* `res.effective.static ? res.effective.text : null`.
|
|
1105
|
+
* `sudo -u x "$prog"` and `exec "$@"` have a non-static `effective` with
|
|
1106
|
+
* reason `expansion`; `env -S '…'` has reason `embedded-command`; `chain`
|
|
1107
|
+
* lists every word resolved along the way, outermost wrapper first, and
|
|
1108
|
+
* `chainIndices` gives each entry's position in the command's `argv` (as
|
|
1109
|
+
* {@link ShellSourceCode.getCommandSites} reports it). When `effective` is
|
|
1110
|
+
* static, the effective command's arguments are
|
|
1111
|
+
* `argv.slice(chainIndices.at(-1)! + 1)`: every entry after the effective
|
|
1112
|
+
* command's own index.
|
|
1113
|
+
*
|
|
1114
|
+
* Enumeration runs once per file, the first time any command helper is
|
|
1115
|
+
* called. If the file's nesting exceeds `enumerateCommands`'s recursion
|
|
1116
|
+
* guard, its `ShAnalyzeMaxDepthError` propagates unchanged and ESLint
|
|
1117
|
+
* reports the rule as crashed for that file. That fails closed: a partial
|
|
1118
|
+
* index would silently omit real commands.
|
|
1119
|
+
*
|
|
1120
|
+
* @param callExpr - A parsed `CallExpr` node from this file's lint tree.
|
|
1121
|
+
* @param options - Forwarded to `resolveArgv0` unchanged.
|
|
1122
|
+
* `transparentWrappers` replaces `sh-ast/analyze`'s
|
|
1123
|
+
* `DEFAULT_TRANSPARENT_WRAPPERS`; spread the defaults into the array to
|
|
1124
|
+
* extend them instead.
|
|
1125
|
+
* @returns The resolution, or `null` when `callExpr` invokes no command.
|
|
1126
|
+
* @throws TypeError when `callExpr` is not a parsed shell `CallExpr`.
|
|
1127
|
+
* @throws `ShAnalyzeMaxDepthError` (from `sh-ast/analyze`) when this file's
|
|
1128
|
+
* nesting exceeds analysis's recursion guard.
|
|
1129
|
+
* @throws `ShAnalyzeInvalidWrapperSpecError` (from `sh-ast/analyze`) when
|
|
1130
|
+
* `options.transparentWrappers` contains a malformed entry.
|
|
1131
|
+
*/
|
|
1132
|
+
resolveInvokedCommand(callExpr: ShellNodeOf<'CallExpr'>, options?: ResolveArgv0Options): Argv0Resolution | null;
|
|
1133
|
+
/**
|
|
1134
|
+
* Returns every command invocation in this file, one entry per
|
|
1135
|
+
* `sh-ast/analyze` `CommandSite`, in source order. Use it when a rule needs
|
|
1136
|
+
* the whole file's commands with their structural context, such as every
|
|
1137
|
+
* command inside a function or inside `$(…)`, instead of visiting
|
|
1138
|
+
* `CallExpr` nodes one by one.
|
|
1139
|
+
*
|
|
1140
|
+
* The data comes from `sh-ast/analyze`'s `enumerateCommands`: facts about
|
|
1141
|
+
* how each command is reached and what its words resolve to, never a
|
|
1142
|
+
* verdict. Each entry's `context`, `argv0`, and `argv` are analysis's own
|
|
1143
|
+
* values, unchanged; `context` lists frames outermost first, as in sh-ast.
|
|
1144
|
+
* Each entry's `node` is the `CallExpr` view node (`===` the node ESLint
|
|
1145
|
+
* passes to a `CallExpr` visitor), so it can be passed to
|
|
1146
|
+
* `context.report()`.
|
|
1147
|
+
*
|
|
1148
|
+
* An assignment-only statement (`FOO=1`) runs no command and has no entry,
|
|
1149
|
+
* although a command substitution in its value (`FOO=$(date)`) does.
|
|
1150
|
+
*
|
|
1151
|
+
* Enumeration runs at most once per file, shared with
|
|
1152
|
+
* {@link ShellSourceCode.getCommandName} and
|
|
1153
|
+
* {@link ShellSourceCode.resolveInvokedCommand}, and every call returns the
|
|
1154
|
+
* same array. Because every rule shares it, the array and everything in each
|
|
1155
|
+
* entry except `node` are deeply frozen (see `ShellCommandSite`): no rule can
|
|
1156
|
+
* change what another rule reads. If the file's nesting exceeds `enumerateCommands`'s
|
|
1157
|
+
* recursion guard, its `ShAnalyzeMaxDepthError` propagates unchanged, as it
|
|
1158
|
+
* does for those helpers: a partial list would silently omit real commands.
|
|
1159
|
+
*
|
|
1160
|
+
* @returns This file's command sites, in source order.
|
|
1161
|
+
* @throws `ShAnalyzeMaxDepthError` (from `sh-ast/analyze`) when this file's
|
|
1162
|
+
* nesting exceeds analysis's recursion guard.
|
|
1163
|
+
*/
|
|
1164
|
+
getCommandSites(): readonly ShellCommandSite[];
|
|
1165
|
+
/**
|
|
1166
|
+
* Returns the sh-ast record `node` was projected from: this file's original
|
|
1167
|
+
* parse of `node`, with every field the lint view leaves out, including
|
|
1168
|
+
* named token positions such as `BinaryCmd.OpPos`, `Stmt.Semicolon` and
|
|
1169
|
+
* `Redirect.OpPos`. Pass those positions to
|
|
1170
|
+
* {@link ShellSourceCode.getIndexFromSyntaxPos} or
|
|
1171
|
+
* {@link ShellSourceCode.getLocFromSyntaxPos} to report on them, or pass the
|
|
1172
|
+
* record to an `sh-ast/analyze` function.
|
|
1173
|
+
*
|
|
1174
|
+
* The returned record is read-only by contract. It is the language's
|
|
1175
|
+
* original parse, shared by every rule and helper linting this file, and
|
|
1176
|
+
* mutating it is unsupported: helpers such as
|
|
1177
|
+
* {@link ShellSourceCode.resolveWordText} read the same record and would see
|
|
1178
|
+
* the edit. It is not frozen, so a write does not throw. Make edits on an
|
|
1179
|
+
* editable copy from {@link ShellSourceCode.getEditableSyntax} instead.
|
|
1180
|
+
*
|
|
1181
|
+
* @param node - A view node from this file's lint tree.
|
|
1182
|
+
* @returns The original sh-ast record, or `undefined` when `node` has none
|
|
1183
|
+
* in this file: an auxiliary view (a record whose type is not an sh-ast
|
|
1184
|
+
* `Node`, such as the `Slice` of `${x:1:2}`), a view node from another
|
|
1185
|
+
* file's tree, or an
|
|
1186
|
+
* object that did not come from the parser.
|
|
1187
|
+
*/
|
|
1188
|
+
getSyntax(node: ShellNode): Node | undefined;
|
|
1189
|
+
/**
|
|
1190
|
+
* Returns the lint view node projected from `syntax`: the inverse of
|
|
1191
|
+
* {@link ShellSourceCode.getSyntax}. The result is the same object (`===`)
|
|
1192
|
+
* ESLint passes to rule visitors, so it can be passed to
|
|
1193
|
+
* `context.report()`. Use it to come back to the view after navigating the
|
|
1194
|
+
* original records, for example from a `Stmt` record's `Cmd`.
|
|
1195
|
+
*
|
|
1196
|
+
* Lookup is by object identity, so only the parser's own records match.
|
|
1197
|
+
* sh-ast's `walk` passes its callback a fresh copy of each comment stored
|
|
1198
|
+
* on a `Stmt`, `CaseItem` or `ArrayElem`, as Go's `syntax.Walk` does, so
|
|
1199
|
+
* those `Comment` copies have no view node. Read the stored comment from
|
|
1200
|
+
* the record's `Comments` array instead.
|
|
1201
|
+
*
|
|
1202
|
+
* @param syntax - An sh-ast `Node` record from this file's original parse.
|
|
1203
|
+
* @returns The view node, or `undefined` for any other record, including a
|
|
1204
|
+
* copy of one of this file's records (such as a `structuredClone`) and a
|
|
1205
|
+
* record from another parse.
|
|
1206
|
+
*/
|
|
1207
|
+
getViewNode(syntax: Node): ShellNode | undefined;
|
|
1208
|
+
/**
|
|
1209
|
+
* Returns an independent, editable deep copy of the sh-ast record `node`
|
|
1210
|
+
* was parsed from: the first half of a printed fix. Edit the copy, then pass
|
|
1211
|
+
* it to {@link ShellSourceCode.getPrintedReplacement}.
|
|
1212
|
+
*
|
|
1213
|
+
* The copy is `structuredClone` of the record
|
|
1214
|
+
* {@link ShellSourceCode.getSyntax} returns. sh-ast documents and tests
|
|
1215
|
+
* ordinary `structuredClone` as the supported way to make an independent
|
|
1216
|
+
* editable copy of a syntax subtree (sh-ast 0.7.0 README, "Editing and
|
|
1217
|
+
* printing"), so no second parse is involved. The copy owns its records,
|
|
1218
|
+
* arrays, positions and byte buffers. Editing it leaves this file's original
|
|
1219
|
+
* syntax, its lint view, its cached traversal and the answers of view-aware
|
|
1220
|
+
* helpers such as `resolveWord` and
|
|
1221
|
+
* {@link ShellSourceCode.resolveWordText} unchanged. Each call returns a new
|
|
1222
|
+
* copy.
|
|
1223
|
+
*
|
|
1224
|
+
* The copy's position fields keep their original values, and the copy has
|
|
1225
|
+
* no view node: {@link ShellSourceCode.getViewNode},
|
|
1226
|
+
* {@link ShellSourceCode.getIndexFromSyntaxPos} and the parse's position
|
|
1227
|
+
* snapshot describe the original source, not the edit. Keep `node` to know
|
|
1228
|
+
* which source range the fix replaces.
|
|
1229
|
+
*
|
|
1230
|
+
* @param node - A view node from this file's lint tree.
|
|
1231
|
+
* @returns A deep copy of `node`'s original sh-ast record.
|
|
1232
|
+
* @throws TypeError when `node` has no original record in this file: an
|
|
1233
|
+
* auxiliary view (such as the `Slice` of `${x:1:2}`), a view node of another
|
|
1234
|
+
* file, or an object that did not come from the parser.
|
|
1235
|
+
*/
|
|
1236
|
+
getEditableSyntax(node: ShellNode): Node;
|
|
1237
|
+
/**
|
|
1238
|
+
* Prints `edited` with sh-ast's `printSync` and pairs the text with the
|
|
1239
|
+
* source range it replaces, so a printed fix is
|
|
1240
|
+
* `fix: (fixer) => fixer.replaceTextRange(r.range, r.text)`. `edited` is
|
|
1241
|
+
* usually a copy from {@link ShellSourceCode.getEditableSyntax} of `node`
|
|
1242
|
+
* or of one of its ancestors.
|
|
1243
|
+
*
|
|
1244
|
+
* The range is `node.range`, except that a `File` root replaces the whole
|
|
1245
|
+
* text (`[0, text.length]`) whatever `node` is, which allows whole-file
|
|
1246
|
+
* regeneration. `options` is passed to `printSync` unchanged.
|
|
1247
|
+
*
|
|
1248
|
+
* The printer formats; it does not replay the source. Its output starts at
|
|
1249
|
+
* column 0 and indents nested lines with upstream's indentation (tabs by
|
|
1250
|
+
* default, `Indent` spaces when `options.Indent` is set). Replacing a
|
|
1251
|
+
* multi-line root nested inside other code, such as an `if` statement in a
|
|
1252
|
+
* function body, therefore keeps the shell meaning but may change the
|
|
1253
|
+
* formatting of its lines. Use a `Word` or `WordPart` root for a surgical
|
|
1254
|
+
* fix that leaves the surrounding source as written.
|
|
1255
|
+
*
|
|
1256
|
+
* A `Stmt` root is adjusted so its text matches `node.range`. A statement's
|
|
1257
|
+
* own leading and trailing comments lie outside its range, so they are not
|
|
1258
|
+
* printed (the copy passed in is not modified). When `node` is a statement
|
|
1259
|
+
* whose range ends in its `;` or `&` terminator, upstream prints the
|
|
1260
|
+
* statement without a `;`, so a `;` is appended unless the edited statement
|
|
1261
|
+
* prints its own `&`, `|&` or disown operator. Without this, replacing `a`
|
|
1262
|
+
* in `a; b` would produce `a b`.
|
|
1263
|
+
*
|
|
1264
|
+
* @param node - A view node from this file's lint tree: the node the fix
|
|
1265
|
+
* replaces.
|
|
1266
|
+
* @param edited - The record to print: a `File`, `Stmt`, `Word`, `Assign`,
|
|
1267
|
+
* `Command` or `WordPart`.
|
|
1268
|
+
* @param options - Forwarded to `printSync` unchanged.
|
|
1269
|
+
* @returns The range to replace and the printed text to put there.
|
|
1270
|
+
* @throws TypeError when `node` has no original record in this file (see
|
|
1271
|
+
* {@link ShellSourceCode.getEditableSyntax}), when `edited` is not a
|
|
1272
|
+
* printable root (the message names its type), or when the printer's output
|
|
1273
|
+
* is not valid UTF-8. Non-UTF-8 output comes back from `printSync` as a
|
|
1274
|
+
* `Uint8Array` and is never decoded lossily into a fix.
|
|
1275
|
+
* @throws `PrintError` or `PrintPanicError` (from sh-ast) when upstream's
|
|
1276
|
+
* printer fails for `edited` or `options`.
|
|
1277
|
+
*/
|
|
1278
|
+
getPrintedReplacement(node: ShellNode, edited: Node, options?: PrintOptions): {
|
|
1279
|
+
range: [number, number];
|
|
1280
|
+
text: string;
|
|
1281
|
+
};
|
|
1282
|
+
/**
|
|
1283
|
+
* Converts a byte position from this file's original parse into the
|
|
1284
|
+
* UTF-16 index ESLint uses for `range` and `text.slice`. sh-ast positions
|
|
1285
|
+
* count UTF-8 bytes, so a raw `Offset` is wrong for any source with
|
|
1286
|
+
* non-ASCII text before it. For example, in `echo 🎉 && echo 中文`,
|
|
1287
|
+
* `getIndexFromSyntaxPos(binaryCmd.OpPos)` is the index where `&&` starts.
|
|
1288
|
+
*
|
|
1289
|
+
* The conversion is only correct for positions of this file's original
|
|
1290
|
+
* parse, as read from {@link ShellSourceCode.getSyntax}'s records. The live
|
|
1291
|
+
* positions of an edited or copied tree describe that edit, not this
|
|
1292
|
+
* source.
|
|
1293
|
+
*
|
|
1294
|
+
* @param pos - A `SyntaxPos` from one of this file's original records.
|
|
1295
|
+
* @returns The UTF-16 index of `pos` in this file's text.
|
|
1296
|
+
* @throws RangeError when `pos` is invalid (`IsValid === false`), recovered
|
|
1297
|
+
* (`IsRecovered === true`), or overflowed (`OffsetOverflow === true`), or
|
|
1298
|
+
* when its offset is not a UTF-8 character boundary of this source. Such a
|
|
1299
|
+
* position is never rounded to a nearby boundary.
|
|
1300
|
+
* @throws TypeError when `pos` is not a `SyntaxPos`, or when this source
|
|
1301
|
+
* code's tree was not built by this language's parser.
|
|
1302
|
+
*/
|
|
1303
|
+
getIndexFromSyntaxPos(pos: SyntaxPos): number;
|
|
1304
|
+
/**
|
|
1305
|
+
* Converts a byte position from this file's original parse into a one-based
|
|
1306
|
+
* ESLint location, the shape `context.report({ loc })` takes. The column
|
|
1307
|
+
* counts UTF-16 code units, not bytes, and CRLF counts as one line break.
|
|
1308
|
+
* Same contract as {@link ShellSourceCode.getIndexFromSyntaxPos}.
|
|
1309
|
+
*
|
|
1310
|
+
* @param pos - A `SyntaxPos` from one of this file's original records.
|
|
1311
|
+
* @returns The one-based line and UTF-16 column of `pos`.
|
|
1312
|
+
* @throws RangeError when `pos` is invalid, recovered, or overflowed, or
|
|
1313
|
+
* when its offset is not a UTF-8 character boundary of this source.
|
|
1314
|
+
* @throws TypeError when `pos` is not a `SyntaxPos`, or when this source
|
|
1315
|
+
* code's tree was not built by this language's parser.
|
|
1316
|
+
*/
|
|
1317
|
+
getLocFromSyntaxPos(pos: SyntaxPos): {
|
|
1318
|
+
line: number;
|
|
1319
|
+
column: number;
|
|
1320
|
+
};
|
|
288
1321
|
/**
|
|
289
1322
|
* Returns the parent of `node`, or `undefined` for the root `File` node
|
|
290
1323
|
* (or any node visited before `traverse()` has run once). ESLint always
|
|
@@ -352,4 +1385,130 @@ export declare class ShellSourceCode extends TextSourceCodeBase<{
|
|
|
352
1385
|
};
|
|
353
1386
|
}
|
|
354
1387
|
|
|
1388
|
+
/**
|
|
1389
|
+
* The listeners whose key names one node type, or that node type with the
|
|
1390
|
+
* `:exit` suffix, each receiving exactly that node type. The key is
|
|
1391
|
+
* unambiguous in both forms (`CallExpr`, `CallExpr:exit`), so a listener
|
|
1392
|
+
* needs no `node.type` guard. Wildcard (`*`, `*:exit`) and attribute or
|
|
1393
|
+
* class selectors name no single type and fall under the index signature.
|
|
1394
|
+
*
|
|
1395
|
+
* Exported because {@link ShellRuleVisitor} extends it.
|
|
1396
|
+
*
|
|
1397
|
+
* @public
|
|
1398
|
+
*/
|
|
1399
|
+
export declare type ShellTypedListeners = {
|
|
1400
|
+
[Type in ShellNodeType as Type | `${Type}:exit`]?: ShellListener<ShellNodeOf<Type>>;
|
|
1401
|
+
};
|
|
1402
|
+
|
|
1403
|
+
export { WordResolution }
|
|
1404
|
+
|
|
1405
|
+
/**
|
|
1406
|
+
* What a "must be run through a wrapper" rule enforces. Passed to
|
|
1407
|
+
* {@link createWrapperRule}.
|
|
1408
|
+
*
|
|
1409
|
+
* @public
|
|
1410
|
+
*/
|
|
1411
|
+
export declare interface WrapperRuleConfig {
|
|
1412
|
+
/**
|
|
1413
|
+
* The command names that must be run through the wrapper. A static command
|
|
1414
|
+
* word matches a name when it equals it, or when it contains a slash and
|
|
1415
|
+
* its basename equals it (`/usr/bin/curl` and `./bin/curl` match `curl`).
|
|
1416
|
+
*/
|
|
1417
|
+
readonly commands: readonly string[];
|
|
1418
|
+
/**
|
|
1419
|
+
* The wrapper the commands must be run through. A string names a wrapper
|
|
1420
|
+
* that takes no options of its own (`retry curl x`), unless it names one of
|
|
1421
|
+
* `sh-ast/analyze`'s default transparent wrappers (`timeout`, `nice`, …),
|
|
1422
|
+
* whose known option shape is used. A `WrapperSpec` declares the wrapper's
|
|
1423
|
+
* option shape, so that `retry -n 3 curl x` is recognized as `curl` run
|
|
1424
|
+
* through `retry`; any of its `names` counts as the wrapper. Like
|
|
1425
|
+
* `commands`, a wrapper run by path matches by basename
|
|
1426
|
+
* (`/usr/local/bin/retry curl x`).
|
|
1427
|
+
*/
|
|
1428
|
+
readonly wrapper: string | WrapperSpec;
|
|
1429
|
+
/**
|
|
1430
|
+
* Listed commands that count only with some subcommands, keyed by command
|
|
1431
|
+
* name. A listed command with no entry here counts with any arguments.
|
|
1432
|
+
*/
|
|
1433
|
+
readonly subcommands?: Readonly<Record<string, WrapperRuleSubcommands>>;
|
|
1434
|
+
/**
|
|
1435
|
+
* Invocations of a listed command that only act locally and so are not
|
|
1436
|
+
* reported, keyed by the command as the rule's message names it: the listed
|
|
1437
|
+
* name (`curl`), or the name with its subcommand (`git clone`).
|
|
1438
|
+
*/
|
|
1439
|
+
readonly localInvocations?: Readonly<Record<string, WrapperRuleLocalInvocation>>;
|
|
1440
|
+
/**
|
|
1441
|
+
* The rule's `meta.docs` fields a rule pack owns. `url` defaults to this
|
|
1442
|
+
* package's wrapper-rules docs page, and `description` to a sentence naming
|
|
1443
|
+
* the commands and the wrapper. Set them to point users at the pack's own
|
|
1444
|
+
* documentation.
|
|
1445
|
+
*/
|
|
1446
|
+
readonly docs?: {
|
|
1447
|
+
readonly url?: string;
|
|
1448
|
+
readonly description?: string;
|
|
1449
|
+
};
|
|
1450
|
+
}
|
|
1451
|
+
|
|
1452
|
+
/**
|
|
1453
|
+
* Ways to invoke one listed command that only act locally, so the policy has
|
|
1454
|
+
* nothing to guard. An invocation matching either field is not reported.
|
|
1455
|
+
*
|
|
1456
|
+
* @public
|
|
1457
|
+
*/
|
|
1458
|
+
export declare interface WrapperRuleLocalInvocation {
|
|
1459
|
+
/**
|
|
1460
|
+
* Complete argument lists, after the command (and its subcommand, if it has
|
|
1461
|
+
* one), that make the invocation local. They match exactly: with
|
|
1462
|
+
* `[['--version']]`, `curl --version` is local and
|
|
1463
|
+
* `curl --version https://example.com` is not.
|
|
1464
|
+
*/
|
|
1465
|
+
readonly argLists?: readonly (readonly string[])[];
|
|
1466
|
+
/** Local when the command's source operand is a local path. */
|
|
1467
|
+
readonly localOperand?: WrapperRuleLocalOperand;
|
|
1468
|
+
}
|
|
1469
|
+
|
|
1470
|
+
/**
|
|
1471
|
+
* Names the operand of a listed command whose value decides whether the
|
|
1472
|
+
* invocation can leave the machine, such as the source of `git clone`.
|
|
1473
|
+
*
|
|
1474
|
+
* The flags describe the options that may precede the operand, with the same
|
|
1475
|
+
* meaning as {@link WrapperRuleSubcommands}'s: `noArgFlags` take no value and
|
|
1476
|
+
* `argFlags` take one (as `--depth 1`, or `--depth=1` in one word). The
|
|
1477
|
+
* operand is the first word after them, or after a `--`. A flag that is not
|
|
1478
|
+
* declared leaves the operand unknown, and an unknown operand is never taken
|
|
1479
|
+
* as local.
|
|
1480
|
+
*
|
|
1481
|
+
* @public
|
|
1482
|
+
*/
|
|
1483
|
+
export declare interface WrapperRuleLocalOperand extends Pick<WrapperSpec, 'noArgFlags' | 'argFlags'> {
|
|
1484
|
+
/**
|
|
1485
|
+
* The operand counts as local when it is a static word starting with one
|
|
1486
|
+
* of these, such as `['./', '../', '/', 'file://']`.
|
|
1487
|
+
*/
|
|
1488
|
+
readonly localPrefixes: readonly string[];
|
|
1489
|
+
}
|
|
1490
|
+
|
|
1491
|
+
/**
|
|
1492
|
+
* Restricts a listed command to some of its subcommands, for commands such as
|
|
1493
|
+
* `git` where only some subcommands are in scope (`git fetch` is a network
|
|
1494
|
+
* call, `git status` is not).
|
|
1495
|
+
*
|
|
1496
|
+
* The flag fields are `WrapperSpec`'s own, picked from it so the two cannot
|
|
1497
|
+
* drift, and describe the command's options that may appear before the
|
|
1498
|
+
* subcommand (`git -C dir fetch`): `noArgFlags` take no operand (`--bare`),
|
|
1499
|
+
* `argFlags` take one (`-C <dir>`), and `stopsChainFlags` mean the command
|
|
1500
|
+
* runs no subcommand at all (`git --version fetch` prints the version, and is
|
|
1501
|
+
* never reported). The subcommand is found the way a wrapped command is:
|
|
1502
|
+
* declared options are skipped, and a flag the command does not declare
|
|
1503
|
+
* leaves the subcommand unknown, so the invocation is not reported.
|
|
1504
|
+
*
|
|
1505
|
+
* @public
|
|
1506
|
+
*/
|
|
1507
|
+
export declare interface WrapperRuleSubcommands extends Pick<WrapperSpec, 'noArgFlags' | 'argFlags' | 'stopsChainFlags'> {
|
|
1508
|
+
/** The subcommands that make an invocation of the command count, such as `['fetch', 'push']`. */
|
|
1509
|
+
readonly names: readonly string[];
|
|
1510
|
+
}
|
|
1511
|
+
|
|
1512
|
+
export { WrapperSpec }
|
|
1513
|
+
|
|
355
1514
|
export { }
|