@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/README.md
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
# @eslint-sh/eslint-plugin
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
Shell script linting for ESLint: enforce project conventions and contracts with built-in rules,
|
|
4
|
+
shared presets, and custom rules over a typed shell AST. This is the underlying plugin behind
|
|
5
|
+
the `eslint-sh` umbrella package. It provides the
|
|
5
6
|
`ShellLanguage` / `ShellSourceCode` core that lets ESLint parse and traverse `.sh`/`.bash` files,
|
|
6
7
|
backed by `sh-ast`.
|
|
7
8
|
|
|
8
|
-
For installation, the config example, the `languageOptions` reference table, the
|
|
9
|
-
|
|
9
|
+
For installation, the config example, the `languageOptions` reference table, the shellcheck
|
|
10
|
+
positioning, and troubleshooting, see the [`eslint-sh` package's README](https://github.com/mike-north/eslint-sh/blob/main/packages/eslint-sh/README.md) —
|
|
10
11
|
that's the canonical quickstart for this project, and applies whether you install `eslint-sh` or
|
|
11
12
|
this package directly.
|
|
12
13
|
|
|
@@ -14,18 +15,66 @@ this package directly.
|
|
|
14
15
|
|
|
15
16
|
`ShellLanguage`/`ShellSourceCode` core, plus the six launch esquery selector classes — see the
|
|
16
17
|
[selector cookbook](./docs/selectors.md) (shipped with this package) for ready-made selector
|
|
17
|
-
recipes.
|
|
18
|
-
|
|
18
|
+
recipes. Four shared configs ship, each supplying `plugins: { sh }` and `language: 'sh/sh'`:
|
|
19
|
+
`configs.recommended` enables the convention-free rules (`sh/require-strict-mode`,
|
|
20
|
+
`sh/require-shebang`, `sh/no-eval`, `sh/no-unchecked-cd`, `sh/shebang-matches-dialect` at `error`; `sh/no-secret-flags`, `sh/quote-expansions` at `warn`), and
|
|
21
|
+
`configs['agent-authored']` adds the contract rules (`sh/no-dynamic-invocation`,
|
|
22
|
+
`sh/no-secret-env-reads`, `sh/no-undeclared-commands`, `sh/no-unused-declarations`,
|
|
23
|
+
`sh/require-network-wrapper`, `sh/require-tty-guard`, `sh/prefer-local`, `sh/no-masked-exit-status`) and
|
|
24
|
+
`sh/max-script-complexity`, with every rule at `error`. Its convention and wrapper rules report nothing until you bind a declaration
|
|
25
|
+
convention or a wrapper. The [rules index](./docs/rules/README.md) lists all forty built-in rules,
|
|
26
|
+
their presets, and
|
|
27
|
+
autofix availability. The version-gated rules `sh/compat/no-append-all-redirect`, `sh/compat/no-associative-arrays`, `sh/compat/no-brace-sequence-padding-step`, `sh/compat/no-case-attributes`, `sh/compat/no-case-fallthrough`, `sh/compat/no-case-modification`, `sh/compat/no-coproc`, `sh/compat/no-declare-g`, `sh/compat/no-epoch-variables`, `sh/compat/no-fd-variable`, `sh/compat/no-globstar`, `sh/compat/no-lastpipe-semantics`, `sh/compat/no-mapfile`, `sh/compat/no-nameref`, `sh/compat/no-negative-array-index`, `sh/compat/no-nounset-empty-array`, `sh/compat/no-parameter-transformation`, `sh/compat/no-pipe-all`, `sh/compat/no-printf-time-format`, `sh/compat/no-test-v`, `sh/compat/no-unicode-escape` and `sh/compat/no-wait-n`, report features your target shell version
|
|
28
|
+
lacks; `configs['bash-3.2']` (`shellVersion: '3.2'`) and `configs['posix-portable']` (`dialect: 'posix'`, with
|
|
29
|
+
`sh/require-strict-mode` as `set -eu`) enable them for those targets. `configs['posix-portable']` also reports `[[`, `let` and `source`, which fail on dash (`sh/compat/no-double-bracket`, `sh/compat/no-let`, `sh/compat/no-source`), but not `local`, `echo -e` or the other constructs it leaves to shellcheck, so a clean run does not prove a script runs under every POSIX `sh`; see [version compatibility](./docs/compat.md). Run shellcheck alongside eslint-sh: shellcheck catches shell correctness
|
|
30
|
+
problems; eslint-sh enforces project policy. [Running eslint-sh beside shellcheck](./docs/shellcheck.md)
|
|
31
|
+
covers who owns what and the rules that overlap it deliberately (see also the
|
|
32
|
+
[design rationale](https://github.com/mike-north/eslint-sh/blob/main/design/README.md#eslint-sh-and-shellcheck)).
|
|
33
|
+
|
|
34
|
+
Disable directives / inline config are implemented and live: all four
|
|
19
35
|
`# eslint-disable*` comment forms, unused-directive detection, and `linterOptions.noInlineConfig`
|
|
20
|
-
interplay work through ESLint's native machinery.
|
|
21
|
-
|
|
22
|
-
suppress it: for a rule you've configured as `sh/your-rule`,
|
|
36
|
+
interplay work through ESLint's native machinery. The rule id below is illustrative; the
|
|
37
|
+
directives suppress built-in rules and your own custom rules alike: for a rule you've configured as `sh/your-rule`,
|
|
23
38
|
`# eslint-disable-next-line sh/your-rule -- justification` suppresses it on the following line.
|
|
39
|
+
A script can therefore switch any rule off for itself. When the scripts you lint come from someone
|
|
40
|
+
who doesn't own the policy, see [linting code you don't control](./docs/untrusted-code.md): a
|
|
41
|
+
config that ignores those comments, and how to run ESLint from outside the checkout under review,
|
|
42
|
+
so that it can't supply the config, ESLint, the plugin, or anything else ESLint looks for in the
|
|
43
|
+
directory it runs in.
|
|
44
|
+
|
|
45
|
+
## CommonJS config
|
|
46
|
+
|
|
47
|
+
In a package without `"type": "module"` (the ESLint default), `eslint.config.js` is CommonJS, as
|
|
48
|
+
is `eslint.config.cjs` in any package. `@eslint-sh/eslint-plugin` is ESM-only, so
|
|
49
|
+
`require('@eslint-sh/eslint-plugin')` returns the module namespace and the plugin object is its
|
|
50
|
+
`.default`. Named exports such as `commentPragma` are ordinary properties of the `require` result.
|
|
51
|
+
Without `.default`, `sh.configs` is `undefined` and ESLint fails with
|
|
52
|
+
`Cannot read properties of undefined (reading 'recommended')`.
|
|
53
|
+
|
|
54
|
+
```js
|
|
55
|
+
// eslint.config.js (CommonJS package) or eslint.config.cjs
|
|
56
|
+
const { defineConfig } = require('eslint/config');
|
|
57
|
+
const sh = require('@eslint-sh/eslint-plugin').default;
|
|
58
|
+
// Named exports need no `.default`.
|
|
59
|
+
const { commentPragma } = require('@eslint-sh/eslint-plugin');
|
|
60
|
+
|
|
61
|
+
module.exports = defineConfig([
|
|
62
|
+
{
|
|
63
|
+
files: ['**/*.sh'],
|
|
64
|
+
extends: [sh.configs.recommended],
|
|
65
|
+
settings: { sh: { declarationConvention: commentPragma } },
|
|
66
|
+
rules: { 'sh/no-undeclared-commands': 'error' },
|
|
67
|
+
},
|
|
68
|
+
]);
|
|
69
|
+
```
|
|
24
70
|
|
|
25
71
|
## Public API
|
|
26
72
|
|
|
27
73
|
- `plugin` (default export) — `{ meta: { name, version, namespace: "sh" }, languages: { sh:
|
|
28
|
-
ShellLanguage }, rules: {} }`. `meta.version` always
|
|
74
|
+
ShellLanguage }, rules, configs: { recommended, 'agent-authored', 'bash-3.2', 'posix-portable' } }`. `meta.version` always
|
|
75
|
+
matches this package's `package.json`. Each config is a single config object with no `files`
|
|
76
|
+
key (`bash-3.2` and `posix-portable` also set `languageOptions`); extend it under your own `files`:
|
|
77
|
+
`defineConfig({ files: ['**/*.sh'], extends: [sh.configs.recommended] })`.
|
|
29
78
|
- `ShellLanguage` — the `@eslint/core` `Language` implementation: `fileType: "text"`,
|
|
30
79
|
`lineStart`/`columnStart: 1`, `nodeTypeKey: "type"`, the consumer's schema-derived `visitorKeys`,
|
|
31
80
|
`validateLanguageOptions`/`normalizeLanguageOptions` for the
|
|
@@ -36,7 +85,13 @@ ShellLanguage }, rules: {} }`. `meta.version` always matches this package's `pac
|
|
|
36
85
|
- `ShellSourceCode` — extends `@eslint/plugin-kit`'s `TextSourceCodeBase`; implements `traverse()`
|
|
37
86
|
(driven by the consumer's `visitorKeys`, yielding two-phase `VisitNodeStep`s), `getParent()`, and
|
|
38
87
|
the disable-directive surface (`getDisableDirectives()`, `getInlineConfigNodes()`,
|
|
39
|
-
`applyInlineConfig()`).
|
|
88
|
+
`applyInlineConfig()`). Rule authors get helpers that delegate to `sh-ast/analyze`, so every rule
|
|
89
|
+
gets the same answer: `resolveWordText(word, options?)` (a word's literal text, or `null` if
|
|
90
|
+
`resolveWord` does not report it static), `getCommandName(callExpr)` (the static command word,
|
|
91
|
+
without following wrappers), and `resolveInvokedCommand(callExpr, { transparentWrappers? })` (the
|
|
92
|
+
full `Argv0Resolution` once wrappers such as `env`, `sudo`, and `exec` are followed, including the
|
|
93
|
+
wrapper `chain` and the `effective` command or the reason it is unknowable). The `dialect` and
|
|
94
|
+
`shellVersion` accessors return the file's normalized language options.
|
|
40
95
|
- `ShellLanguageOptions` / `NormalizedShellLanguageOptions` / `ShellLanguageOptionsJSON` — the
|
|
41
96
|
`languageOptions` types (`dialect`, `shellVersion`, `directivePrefix`). `directivePrefix` is the
|
|
42
97
|
prefix directive/inline-config comments must start with — e.g. the default `"eslint"` recognizes
|
|
@@ -45,20 +100,176 @@ ShellLanguage }, rules: {} }`. `meta.version` always matches this package's `pac
|
|
|
45
100
|
- `ShellNode` / `ShellFile` / `ShellComment` / `ShellPosition` — the plugin's lint view: lowercase
|
|
46
101
|
fields, UTF-16 ranges, and one-based UTF-16 locations, independent of the raw parser syntax.
|
|
47
102
|
Locations come from one parse-time position snapshot and remain bound to that original source;
|
|
48
|
-
projecting the view does not run live bounds operations on every syntax subtree.
|
|
103
|
+
projecting the view does not run live bounds operations on every syntax subtree. `ShellNode` is
|
|
104
|
+
the union of every node type, so narrowing on `node.type` gives that node's fields, typed, with
|
|
105
|
+
no cast. See [Reading nodes](./docs/reading-nodes.md).
|
|
106
|
+
- `ShellNodeOf` / `ShellNodeType` / `ShellNodeMap` / `ShellNodeField` — the types behind the union.
|
|
107
|
+
`ShellNodeOf<'Stmt'>` is one node type by name, `ShellNodeType` is every name, and
|
|
108
|
+
`ShellNodeMap` maps one to the other. They are computed from `sh-ast`'s generated typings, so
|
|
109
|
+
they follow the pinned mvdan/sh version. `ShellNodeField<T>` is the type of one node field
|
|
110
|
+
(`ShellNodeField<ShellNodeOf<'Stmt'>['cmd']>`), for a helper that takes or returns field values.
|
|
111
|
+
- `ShellCommandSite` / `CommandContext` / `ResolveArgv0Options` — one command in a file as `getCommandSites()` reports it:
|
|
112
|
+
its `CallExpr` node (`node`), the `context` path of enclosing constructs from the file root
|
|
113
|
+
(each frame a `CommandContext`, discriminated by `kind`: `and`, `or`, `pipeline`, `subshell`,
|
|
114
|
+
`cmdSubst`, …), its `argv0` and `argv` as `WordResolution`s, and `resolveInvokedCommand()`,
|
|
115
|
+
which follows wrappers. `ResolveArgv0Options` is that method's option bag (and
|
|
116
|
+
`ShellSourceCode.resolveInvokedCommand`'s): `transparentWrappers` replaces the wrapper table
|
|
117
|
+
followed.
|
|
118
|
+
- `WordResolution` — what `resolveWord` and a site's `argv` words report: `{ static: true, text }`
|
|
119
|
+
when the word's value is known without running the script, or `{ static: false, reason }` when
|
|
120
|
+
it is not (`reason` is `'expansion'`, `'tilde'`, `'glob'`, `'brace'`, `'locale'` or
|
|
121
|
+
`'unsupported'`; a new reason can appear in a minor release, so keep a `default` branch). Check
|
|
122
|
+
`static` before reading `text`.
|
|
123
|
+
- `ShellDialect` — the `dialect` language option's values: `'bash'`, `'posix'`, `'mksh'`,
|
|
124
|
+
`'bats'` and `'zsh'`.
|
|
125
|
+
- `Argv0Resolution` — the type `ShellSourceCode.resolveInvokedCommand` returns, re-exported from
|
|
126
|
+
`sh-ast/analyze`.
|
|
49
127
|
- `resolveWord` — adapts a parsed lint Word to `sh-ast`'s word-resolution helper, so a project that installs only
|
|
50
128
|
this package can write custom rules that resolve static shell words (e.g. the `no-rm-rf` example
|
|
51
129
|
in the [custom-rule tutorial](./docs/custom-rule-tutorial.md)) without a separate `sh-ast`
|
|
52
130
|
install. Fabricated nodes are rejected because they have no original syntax association.
|
|
131
|
+
- `DeclarationConvention` / `DeclaredCommand` / `commentPragma` — the extension point for how a
|
|
132
|
+
project declares the external commands its scripts use, bound with
|
|
133
|
+
`settings: { sh: { declarationConvention } }`. `commentPragma` is the reference convention,
|
|
134
|
+
reading `# uses: jq curl` header comments. See [declaration
|
|
135
|
+
conventions](./docs/declaration-conventions.md).
|
|
136
|
+
- `ScriptDeclaredCommand` / `FileDeclaredCommand` / `DescribedDeclaredCommand` / `LintedFile` — the
|
|
137
|
+
three kinds of `DeclaredCommand`, by where a declaration lives (in the script, in another file
|
|
138
|
+
such as a sidecar, or in a source described in words, such as a config list or a registry
|
|
139
|
+
snapshot), and the
|
|
140
|
+
linted file a convention is told about so it can find a per-script sidecar. See [declarations
|
|
141
|
+
outside the script](./docs/declaration-conventions.md#declarations-outside-the-script).
|
|
142
|
+
- `readDeclaredCommands` / `DeclarationReadContext` — reads the bound convention's declarations
|
|
143
|
+
for the file a rule is linting, checked exactly as the built-in rules check them. See [reading
|
|
144
|
+
the convention in your own
|
|
145
|
+
rule](./docs/declaration-conventions.md#reading-the-convention-in-your-own-rule).
|
|
146
|
+
- `defineShellRule` / `ShellRule` / `ShellRuleSpec` / `ShellRuleMeta` / `ShellRuleContext` /
|
|
147
|
+
`ShellRuleVisitor` / `ShellListener` / `ShellTypedListeners` — the rule-authoring helper and its types. `defineShellRule({ meta, create })`
|
|
148
|
+
returns an ordinary ESLint rule with `meta.languages: ['sh/sh']` set, so ESLint refuses to run it
|
|
149
|
+
on another language; `context.sourceCode` is typed `ShellSourceCode`, a listener keyed by a node
|
|
150
|
+
type (`CallExpr`, `Word:exit`) receives that node type, and any other selector's listener receives
|
|
151
|
+
`ShellNode`. Writing `meta.languages` yourself is a type error. The built-in rules are made with
|
|
152
|
+
it too. `ShellListener<Node>` is the type of one listener, so a helper that builds listeners for
|
|
153
|
+
several rules can name its return type; `ShellTypedListeners` is the node-type-keyed half of
|
|
154
|
+
`ShellRuleVisitor`. See [Writing your own rules](#writing-your-own-rules).
|
|
155
|
+
- `resolveDeclarationConvention` / `DeclarationConventionBindingContext` — resolves the declaration
|
|
156
|
+
convention bound for a rule (its `declarationConvention` option, else
|
|
157
|
+
`settings.sh.declarationConvention`, else `null`) with the validation and error messages the
|
|
158
|
+
built-in consumers use, so a rule of your own can read the convention. See [declaration
|
|
159
|
+
conventions](./docs/declaration-conventions.md#reading-the-convention-in-your-own-rule).
|
|
160
|
+
- `createWrapperRule` / `WrapperRuleConfig` / `WrapperRuleSubcommands` / `WrapperSpec` — the
|
|
161
|
+
factory for "command X must be run through wrapper Y" rules a rule pack
|
|
162
|
+
binds to its own commands and wrapper. `sh/require-network-wrapper` is the built-in rule made
|
|
163
|
+
with it. See [wrapper rules](./docs/wrapper-rules.md).
|
|
164
|
+
- `WrapperRuleLocalInvocation` / `WrapperRuleLocalOperand` — the `localInvocations` entry of a
|
|
165
|
+
`createWrapperRule` config: invocations of a listed command that only act locally and are not
|
|
166
|
+
reported, such as `curl --version` or `git clone ./x`. See
|
|
167
|
+
[wrapper rules](./docs/wrapper-rules.md#local-only-invocations).
|
|
168
|
+
|
|
169
|
+
## Faithful syntax: `@eslint-sh/eslint-plugin/syntax`
|
|
170
|
+
|
|
171
|
+
The root exports work on the plugin's lint view (`ShellNode`). A rule that edits or analyzes the
|
|
172
|
+
parser's own records needs `sh-ast` itself: `walk`, `parseSync`, `printSync` and `PrintOptions`,
|
|
173
|
+
the record types (`Node`, `Word`, `CallExpr`, `SyntaxPos`, `GoString`, …), and the analysis
|
|
174
|
+
helpers. Import them from this package's subpaths, not from `sh-ast`:
|
|
175
|
+
|
|
176
|
+
```js
|
|
177
|
+
// View-aware helpers: take lint-view nodes.
|
|
178
|
+
import { resolveWord } from '@eslint-sh/eslint-plugin';
|
|
179
|
+
// sh-ast's root entry point: parse, walk, and print faithful records.
|
|
180
|
+
import { parseSync, printSync, walk } from '@eslint-sh/eslint-plugin/syntax';
|
|
181
|
+
// sh-ast/analyze: command enumeration and wrapper resolution over faithful records.
|
|
182
|
+
import {
|
|
183
|
+
DEFAULT_TRANSPARENT_WRAPPERS,
|
|
184
|
+
enumerateCommands,
|
|
185
|
+
} from '@eslint-sh/eslint-plugin/syntax/analyze';
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
- `@eslint-sh/eslint-plugin/syntax` re-exports the complete public surface of `sh-ast`, and
|
|
189
|
+
`@eslint-sh/eslint-plugin/syntax/analyze` re-exports `sh-ast/analyze`. Both come from the
|
|
190
|
+
`sh-ast` this plugin depends on, the same module instance its language parses with, so you
|
|
191
|
+
don't install `sh-ast` yourself and records always match the parser that made them. Nothing is
|
|
192
|
+
wrapped or renamed; each name keeps `sh-ast`'s own contract and docs.
|
|
193
|
+
- The two `resolveWord`s differ. The root's takes a lint-view `ShellNode`; the one in
|
|
194
|
+
`…/syntax/analyze` takes a faithful `Node`. That is why the subpaths are kept apart from the
|
|
195
|
+
root.
|
|
196
|
+
- **Versioning.** `…/syntax` and `…/syntax/analyze` track this plugin's pinned `sh-ast` minor
|
|
197
|
+
(currently `sh-ast@^0.7.0`). Under 0.x, a different `sh-ast` minor is a breaking change, so
|
|
198
|
+
an `sh-ast` 0.x minor bump is a minor bump of this plugin, and the changelog entry names the
|
|
199
|
+
new `sh-ast` version.
|
|
53
200
|
|
|
54
201
|
Syntax failures surface as fatal lint messages at mvdan/sh's reported byte offset, converted to
|
|
55
|
-
the source's UTF-16 location. Failures without a valid syntax position use a 1:1 fallback.
|
|
202
|
+
the source's UTF-16 location. Failures without a valid syntax position use a 1:1 fallback. A
|
|
203
|
+
failure of the parser runtime itself (an abort, or a failure of the bridge to it) is not a
|
|
204
|
+
syntax error, and the file may be perfectly valid. It is reported at 1:1 as
|
|
205
|
+
`Parsing error: eslint-sh parser runtime failure (not a syntax error in this file): <detail>`.
|
|
206
|
+
ESLint adds the `Parsing error: ` prefix to every message a language returns from `parse`, and a
|
|
207
|
+
language cannot remove it, so look for the marker text `eslint-sh parser runtime failure` to tell
|
|
208
|
+
this apart from a real syntax error, which has no marker and sits at the offending position. Linting
|
|
209
|
+
the file again, or in a fresh process, usually gets past a runtime failure.
|
|
210
|
+
|
|
211
|
+
### Known parser gaps
|
|
212
|
+
|
|
213
|
+
The parser is the upstream `sh-ast`, and it rejects three valid Bash forms. Each is reported as a
|
|
214
|
+
syntax error. Until the parser accepts them, write the workaround in the right-hand column.
|
|
215
|
+
|
|
216
|
+
| Valid Bash form | Workaround |
|
|
217
|
+
| ------------------------ | -------------------------- |
|
|
218
|
+
| `x=$((a \|\| b) \| c)` | `x=$( (a \|\| b) \| c )` |
|
|
219
|
+
| `x=(${y}[0])` | `word=${y}[0]; x=($word)` |
|
|
220
|
+
| `declare -A m=([a.b]=1)` | `declare -A m=(["a.b"]=1)` |
|
|
221
|
+
|
|
222
|
+
- `x=$((a || b) | c)`: the parser reads `$((` as the start of arithmetic. A space after `$(` makes
|
|
223
|
+
it a command substitution containing a subshell.
|
|
224
|
+
- `x=(${y}[0])`: inside an array literal, any element word that ends in an unquoted `[…]` is read
|
|
225
|
+
as an indexed element (`[0]=value`) and must be followed by `=`. It is not limited to a word
|
|
226
|
+
that starts with an expansion: `x=(a[0])`, `x=(a${y}[0])` and `x=("${y}"[0])` are rejected too
|
|
227
|
+
(a `[0]` that is meant as a glob character class). Building the word in its own statement keeps
|
|
228
|
+
the same splitting and globbing.
|
|
229
|
+
- An associative-array subscript containing a `.` that is not quoted: the parser reads the
|
|
230
|
+
subscript as arithmetic and rejects the dot ("floating point arithmetic is a zsh feature"). The
|
|
231
|
+
gap is wider than the `declare` form above. It also rejects `m[a.b]=1` and `${m[a.b]}`, which
|
|
232
|
+
parse once quoted: `m["a.b"]=1` and `${m["a.b"]}`.
|
|
56
233
|
|
|
57
234
|
## Writing your own rules
|
|
58
235
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
236
|
+
Build a rule with `defineShellRule`. It takes the `{ meta, create(context) }` object you would
|
|
237
|
+
write for any ESLint rule and returns an ESLint rule you register in a plugin's `rules`:
|
|
238
|
+
|
|
239
|
+
```js
|
|
240
|
+
import { defineShellRule } from '@eslint-sh/eslint-plugin';
|
|
241
|
+
|
|
242
|
+
export default defineShellRule({
|
|
243
|
+
meta: {
|
|
244
|
+
type: 'problem',
|
|
245
|
+
schema: [],
|
|
246
|
+
messages: { noEval: 'Avoid `eval`; it runs a string as code.' },
|
|
247
|
+
},
|
|
248
|
+
create(context) {
|
|
249
|
+
return {
|
|
250
|
+
CallExpr(node) {
|
|
251
|
+
if (context.sourceCode.getCommandName(node) === 'eval') {
|
|
252
|
+
context.report({ node, messageId: 'noEval' });
|
|
253
|
+
}
|
|
254
|
+
},
|
|
255
|
+
};
|
|
256
|
+
},
|
|
257
|
+
});
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
`defineShellRule` sets `meta.languages: ['sh/sh']`, the declaration that makes ESLint refuse to
|
|
261
|
+
run the rule on a file of another language (usually because a config entry is missing
|
|
262
|
+
`language: 'sh/sh'`) instead of letting it silently match nothing. In TypeScript,
|
|
263
|
+
`context.sourceCode` is `ShellSourceCode`, a listener keyed by a node type receives that node type
|
|
264
|
+
(`CallExpr(node)` reads `node.args` with no check), and `context.report`
|
|
265
|
+
accepts only the message ids in `meta.messages`, with no `@eslint/core` import of your own.
|
|
266
|
+
|
|
267
|
+
[Reading nodes](./docs/reading-nodes.md) is the field reference for the nodes your listeners
|
|
268
|
+
receive: narrow a `ShellNode` on `type` and read its fields, typed, with one worked rule.
|
|
269
|
+
|
|
270
|
+
The [custom-rule tutorial](./docs/custom-rule-tutorial.md) is a complete, CI-tested walkthrough: a
|
|
271
|
+
selector from the cookbook, a full rule with `context.report`, the flat-config wiring to run it,
|
|
272
|
+
fixes (and when a printed fix reflows code, so a position-based text edit is the better choice),
|
|
273
|
+
and a TypeScript rule. `docs/` ships with this package (see `package.json#files`), so the
|
|
63
274
|
tutorial and its example files (`docs/examples/`) are available in every installed copy — not just
|
|
64
275
|
on GitHub.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The commands bash runs as builtins: data for the command harvest, never
|
|
3
|
+
* rule logic.
|
|
4
|
+
*
|
|
5
|
+
* Source: the Bash Reference Manual for **Bash 5.3**, chapter 4 "Shell
|
|
6
|
+
* Builtin Commands" (§4.1 Bourne Shell Builtins, §4.2 Bash Builtin Commands,
|
|
7
|
+
* §4.3 Modifying Shell Behavior: `set`, `shopt`), plus the builtins that
|
|
8
|
+
* chapter defers to other chapters: §6.8.1 Directory Stack Builtins
|
|
9
|
+
* (`dirs`, `popd`, `pushd`), §7.2 Job Control Builtins (`bg`, `fg`, `jobs`,
|
|
10
|
+
* `kill`, `wait`, `disown`, `suspend`), §8.7 Programmable Completion
|
|
11
|
+
* Builtins (`compgen`, `complete`, `compopt`) and §9.2 Bash History Builtins
|
|
12
|
+
* (`fc`, `history`). `true` and `false` are builtins in every bash release
|
|
13
|
+
* (`help true`) although the manual describes them only as utilities.
|
|
14
|
+
* Cross-checked against `compgen -b` from bash 5.3.9, which prints exactly
|
|
15
|
+
* this list.
|
|
16
|
+
*
|
|
17
|
+
* Reserved words (`if`, `time`, `[[`, `function`, …) are not here: the
|
|
18
|
+
* parser never produces them as command words.
|
|
19
|
+
*
|
|
20
|
+
* The list is the latest bash's, and is not narrowed by `shellVersion`:
|
|
21
|
+
* `mapfile`, `readarray` and `compopt` (added in bash 4.0) are builtins here
|
|
22
|
+
* even for a file configured as bash 3.2. Whether a script may use them on
|
|
23
|
+
* an old bash is a version-compatibility question, not a dependency one.
|
|
24
|
+
*
|
|
25
|
+
* @see https://www.gnu.org/software/bash/manual/bash.html#Shell-Builtin-Commands
|
|
26
|
+
* @see https://www.gnu.org/software/bash/manual/bash.html#Directory-Stack-Builtins
|
|
27
|
+
* @see https://www.gnu.org/software/bash/manual/bash.html#Job-Control-Builtins
|
|
28
|
+
* @see https://www.gnu.org/software/bash/manual/bash.html#Programmable-Completion-Builtins
|
|
29
|
+
* @see https://www.gnu.org/software/bash/manual/bash.html#Bash-History-Builtins
|
|
30
|
+
*/
|
|
31
|
+
/**
|
|
32
|
+
* Every bash 5.3 builtin command name, in `compgen -b` order.
|
|
33
|
+
*
|
|
34
|
+
* @internal
|
|
35
|
+
*/
|
|
36
|
+
export declare const BASH_BUILTINS: readonly string[];
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The commands bash runs as builtins: data for the command harvest, never
|
|
3
|
+
* rule logic.
|
|
4
|
+
*
|
|
5
|
+
* Source: the Bash Reference Manual for **Bash 5.3**, chapter 4 "Shell
|
|
6
|
+
* Builtin Commands" (§4.1 Bourne Shell Builtins, §4.2 Bash Builtin Commands,
|
|
7
|
+
* §4.3 Modifying Shell Behavior: `set`, `shopt`), plus the builtins that
|
|
8
|
+
* chapter defers to other chapters: §6.8.1 Directory Stack Builtins
|
|
9
|
+
* (`dirs`, `popd`, `pushd`), §7.2 Job Control Builtins (`bg`, `fg`, `jobs`,
|
|
10
|
+
* `kill`, `wait`, `disown`, `suspend`), §8.7 Programmable Completion
|
|
11
|
+
* Builtins (`compgen`, `complete`, `compopt`) and §9.2 Bash History Builtins
|
|
12
|
+
* (`fc`, `history`). `true` and `false` are builtins in every bash release
|
|
13
|
+
* (`help true`) although the manual describes them only as utilities.
|
|
14
|
+
* Cross-checked against `compgen -b` from bash 5.3.9, which prints exactly
|
|
15
|
+
* this list.
|
|
16
|
+
*
|
|
17
|
+
* Reserved words (`if`, `time`, `[[`, `function`, …) are not here: the
|
|
18
|
+
* parser never produces them as command words.
|
|
19
|
+
*
|
|
20
|
+
* The list is the latest bash's, and is not narrowed by `shellVersion`:
|
|
21
|
+
* `mapfile`, `readarray` and `compopt` (added in bash 4.0) are builtins here
|
|
22
|
+
* even for a file configured as bash 3.2. Whether a script may use them on
|
|
23
|
+
* an old bash is a version-compatibility question, not a dependency one.
|
|
24
|
+
*
|
|
25
|
+
* @see https://www.gnu.org/software/bash/manual/bash.html#Shell-Builtin-Commands
|
|
26
|
+
* @see https://www.gnu.org/software/bash/manual/bash.html#Directory-Stack-Builtins
|
|
27
|
+
* @see https://www.gnu.org/software/bash/manual/bash.html#Job-Control-Builtins
|
|
28
|
+
* @see https://www.gnu.org/software/bash/manual/bash.html#Programmable-Completion-Builtins
|
|
29
|
+
* @see https://www.gnu.org/software/bash/manual/bash.html#Bash-History-Builtins
|
|
30
|
+
*/
|
|
31
|
+
/**
|
|
32
|
+
* Every bash 5.3 builtin command name, in `compgen -b` order.
|
|
33
|
+
*
|
|
34
|
+
* @internal
|
|
35
|
+
*/
|
|
36
|
+
export const BASH_BUILTINS = Object.freeze([
|
|
37
|
+
'.',
|
|
38
|
+
':',
|
|
39
|
+
'[',
|
|
40
|
+
'alias',
|
|
41
|
+
'bg',
|
|
42
|
+
'bind',
|
|
43
|
+
'break',
|
|
44
|
+
'builtin',
|
|
45
|
+
'caller',
|
|
46
|
+
'cd',
|
|
47
|
+
'command',
|
|
48
|
+
'compgen',
|
|
49
|
+
'complete',
|
|
50
|
+
'compopt',
|
|
51
|
+
'continue',
|
|
52
|
+
'declare',
|
|
53
|
+
'dirs',
|
|
54
|
+
'disown',
|
|
55
|
+
'echo',
|
|
56
|
+
'enable',
|
|
57
|
+
'eval',
|
|
58
|
+
'exec',
|
|
59
|
+
'exit',
|
|
60
|
+
'export',
|
|
61
|
+
'false',
|
|
62
|
+
'fc',
|
|
63
|
+
'fg',
|
|
64
|
+
'getopts',
|
|
65
|
+
'hash',
|
|
66
|
+
'help',
|
|
67
|
+
'history',
|
|
68
|
+
'jobs',
|
|
69
|
+
'kill',
|
|
70
|
+
'let',
|
|
71
|
+
'local',
|
|
72
|
+
'logout',
|
|
73
|
+
'mapfile',
|
|
74
|
+
'popd',
|
|
75
|
+
'printf',
|
|
76
|
+
'pushd',
|
|
77
|
+
'pwd',
|
|
78
|
+
'read',
|
|
79
|
+
'readarray',
|
|
80
|
+
'readonly',
|
|
81
|
+
'return',
|
|
82
|
+
'set',
|
|
83
|
+
'shift',
|
|
84
|
+
'shopt',
|
|
85
|
+
'source',
|
|
86
|
+
'suspend',
|
|
87
|
+
'test',
|
|
88
|
+
'times',
|
|
89
|
+
'trap',
|
|
90
|
+
'true',
|
|
91
|
+
'type',
|
|
92
|
+
'typeset',
|
|
93
|
+
'ulimit',
|
|
94
|
+
'umask',
|
|
95
|
+
'unalias',
|
|
96
|
+
'unset',
|
|
97
|
+
'wait',
|
|
98
|
+
]);
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The helpers bats-core defines for every test file: data for the command
|
|
3
|
+
* harvest under the `bats` dialect, never rule logic.
|
|
4
|
+
*
|
|
5
|
+
* A `.bats` file runs inside bats, which defines these as shell functions
|
|
6
|
+
* before any test runs. They are not external commands, so a test file never
|
|
7
|
+
* declares them. They are not defined in the file either, so the harvest's
|
|
8
|
+
* in-file function check cannot see them. Hence this list.
|
|
9
|
+
*
|
|
10
|
+
* Source: the bats-core manual, "Writing tests": `run` ("Test other
|
|
11
|
+
* commands"), `bats_pipe` ("Run commands with pipes"), `load` and
|
|
12
|
+
* `bats_load_safe` ("Share common code"), `bats_load_library` ("Load system
|
|
13
|
+
* wide libraries"), `skip` ("Easily skip tests"),
|
|
14
|
+
* `bats_require_minimum_version`, and `bats_test_function` ("Dynamic test
|
|
15
|
+
* registration").
|
|
16
|
+
*
|
|
17
|
+
* Absent by design: the hooks a test file defines itself (`setup`,
|
|
18
|
+
* `teardown`, `setup_file`, `teardown_file`, `bats::on_failure`), which the
|
|
19
|
+
* in-file function check already covers, and helpers from separately
|
|
20
|
+
* installed libraries such as bats-assert (`assert_success`), which are not
|
|
21
|
+
* part of bats-core.
|
|
22
|
+
*
|
|
23
|
+
* @see https://bats-core.readthedocs.io/en/stable/writing-tests.html
|
|
24
|
+
*/
|
|
25
|
+
import type { WrapperSpec } from 'sh-ast/analyze';
|
|
26
|
+
/**
|
|
27
|
+
* Every helper function bats-core provides to test files.
|
|
28
|
+
*
|
|
29
|
+
* @internal
|
|
30
|
+
*/
|
|
31
|
+
export declare const BATS_HELPERS: readonly string[];
|
|
32
|
+
/**
|
|
33
|
+
* bats' `run`, as a transparent wrapper: it runs the command after its own
|
|
34
|
+
* options and captures the result, so `run curl x` uses `curl`.
|
|
35
|
+
*
|
|
36
|
+
* Its options, all before the command (bats-core manual, "`run`: Test other
|
|
37
|
+
* commands"): `-N` (expect exit status N, 0–255), `!` (expect a nonzero
|
|
38
|
+
* status), `--keep-empty-lines`, `--separate-stderr`, and `--` to end option
|
|
39
|
+
* parsing. `!` is not dash-prefixed, but `noArgFlags` matches it as an exact
|
|
40
|
+
* word all the same.
|
|
41
|
+
*
|
|
42
|
+
* @see https://bats-core.readthedocs.io/en/stable/writing-tests.html#run-test-other-commands
|
|
43
|
+
* @internal
|
|
44
|
+
*/
|
|
45
|
+
export declare const BATS_RUN_WRAPPER: WrapperSpec;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Every helper function bats-core provides to test files.
|
|
3
|
+
*
|
|
4
|
+
* @internal
|
|
5
|
+
*/
|
|
6
|
+
export const BATS_HELPERS = Object.freeze([
|
|
7
|
+
'run',
|
|
8
|
+
'bats_pipe',
|
|
9
|
+
'load',
|
|
10
|
+
'bats_load_safe',
|
|
11
|
+
'bats_load_library',
|
|
12
|
+
'skip',
|
|
13
|
+
'bats_require_minimum_version',
|
|
14
|
+
'bats_test_function',
|
|
15
|
+
]);
|
|
16
|
+
/**
|
|
17
|
+
* bats' `run`, as a transparent wrapper: it runs the command after its own
|
|
18
|
+
* options and captures the result, so `run curl x` uses `curl`.
|
|
19
|
+
*
|
|
20
|
+
* Its options, all before the command (bats-core manual, "`run`: Test other
|
|
21
|
+
* commands"): `-N` (expect exit status N, 0–255), `!` (expect a nonzero
|
|
22
|
+
* status), `--keep-empty-lines`, `--separate-stderr`, and `--` to end option
|
|
23
|
+
* parsing. `!` is not dash-prefixed, but `noArgFlags` matches it as an exact
|
|
24
|
+
* word all the same.
|
|
25
|
+
*
|
|
26
|
+
* @see https://bats-core.readthedocs.io/en/stable/writing-tests.html#run-test-other-commands
|
|
27
|
+
* @internal
|
|
28
|
+
*/
|
|
29
|
+
export const BATS_RUN_WRAPPER = Object.freeze({
|
|
30
|
+
names: Object.freeze(['run']),
|
|
31
|
+
noArgFlags: Object.freeze(['!', '--keep-empty-lines', '--separate-stderr']),
|
|
32
|
+
noArgFlagPattern: /^-\d+$/u,
|
|
33
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which command names the shell itself provides for a given dialect.
|
|
3
|
+
*
|
|
4
|
+
* A builtin runs inside the shell, so a script never depends on it being
|
|
5
|
+
* installed, and the command harvest drops it. Under `bats`, the helper
|
|
6
|
+
* functions bats-core defines for every test file (`run`, `load`, `skip`, …)
|
|
7
|
+
* are provided the same way and are dropped too. The lists themselves are
|
|
8
|
+
* data, each citing its source, in `./bash.ts`, `./posix.ts` and `./bats.ts`;
|
|
9
|
+
* this module only maps each supported dialect to a combination of them.
|
|
10
|
+
*/
|
|
11
|
+
import type { ShellDialect } from '../../language/shell-language-options.js';
|
|
12
|
+
/**
|
|
13
|
+
* Returns the set of command names that `dialect` provides without an
|
|
14
|
+
* external command: its builtins, plus bats-core's helpers for `bats`.
|
|
15
|
+
*
|
|
16
|
+
* @internal
|
|
17
|
+
*/
|
|
18
|
+
export declare function builtinsForDialect(dialect: ShellDialect): ReadonlySet<string>;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { BASH_BUILTINS } from './bash.js';
|
|
2
|
+
import { BATS_HELPERS } from './bats.js';
|
|
3
|
+
import { POSIX_CONVENTIONAL_BUILTINS, POSIX_REGULAR_BUILTINS, POSIX_SPECIAL_BUILTINS, } from './posix.js';
|
|
4
|
+
/** Every bash builtin. */
|
|
5
|
+
const BASH = new Set(BASH_BUILTINS);
|
|
6
|
+
/** Every bash builtin, plus the helpers bats-core defines for test files. */
|
|
7
|
+
const BATS = new Set([...BASH_BUILTINS, ...BATS_HELPERS]);
|
|
8
|
+
/** Every POSIX builtin: special, regular, and conventionally built in. */
|
|
9
|
+
const POSIX = new Set([
|
|
10
|
+
...POSIX_SPECIAL_BUILTINS,
|
|
11
|
+
...POSIX_REGULAR_BUILTINS,
|
|
12
|
+
...POSIX_CONVENTIONAL_BUILTINS,
|
|
13
|
+
]);
|
|
14
|
+
/**
|
|
15
|
+
* The builtin set for each dialect.
|
|
16
|
+
*
|
|
17
|
+
* - `bats` files are bash scripts run by bats, so they use the bash list
|
|
18
|
+
* plus bats-core's helpers.
|
|
19
|
+
* - `mksh` and `zsh` have no list of their own yet. Both implement every
|
|
20
|
+
* POSIX builtin, so the POSIX list is a safe floor: it never counts a real
|
|
21
|
+
* external command as a builtin. Their own extra builtins (`print`,
|
|
22
|
+
* `whence`, `setopt`, …) are reported as commands until they get lists.
|
|
23
|
+
*/
|
|
24
|
+
const BUILTINS_BY_DIALECT = {
|
|
25
|
+
bash: BASH,
|
|
26
|
+
bats: BATS,
|
|
27
|
+
posix: POSIX,
|
|
28
|
+
mksh: POSIX,
|
|
29
|
+
zsh: POSIX,
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Returns the set of command names that `dialect` provides without an
|
|
33
|
+
* external command: its builtins, plus bats-core's helpers for `bats`.
|
|
34
|
+
*
|
|
35
|
+
* @internal
|
|
36
|
+
*/
|
|
37
|
+
export function builtinsForDialect(dialect) {
|
|
38
|
+
return BUILTINS_BY_DIALECT[dialect];
|
|
39
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The commands a POSIX `sh` runs as builtins: data for the command harvest,
|
|
3
|
+
* never rule logic.
|
|
4
|
+
*
|
|
5
|
+
* Three groups, each from its own source (POSIX.1-2017, XCU Issue 7, whose
|
|
6
|
+
* section numbers are used here):
|
|
7
|
+
*
|
|
8
|
+
* - **Special built-ins**, XCU §2.14 "Special Built-In Utilities": `break`,
|
|
9
|
+
* `:`, `continue`, `.`, `eval`, `exec`, `exit`, `export`, `readonly`,
|
|
10
|
+
* `return`, `set`, `shift`, `times`, `trap`, `unset`.
|
|
11
|
+
* - **Regular built-ins**, XCU §2.9.1.1 "Command Search and Execution",
|
|
12
|
+
* step 1.d: the utilities a shell must invoke before searching `PATH`
|
|
13
|
+
* (`alias`, `bg`, `cd`, `command`, `false`, `fc`, `fg`, `getopts`, `hash`,
|
|
14
|
+
* `jobs`, `kill`, `newgrp`, `pwd`, `read`, `true`, `umask`, `unalias`,
|
|
15
|
+
* `wait`), plus `type` and `ulimit`, which the same step lists under the
|
|
16
|
+
* XSI option. POSIX.1-2024 (Issue 8) calls most of these "intrinsic
|
|
17
|
+
* utilities" (XCU §1.7).
|
|
18
|
+
* - **Built in by every POSIX shell in practice**: `echo`, `printf`, `test`
|
|
19
|
+
* and `[`. POSIX specifies them as ordinary utilities that a shell may
|
|
20
|
+
* build in (XCU §1.6), and dash, ash, ksh, mksh and bash all do (see the
|
|
21
|
+
* "Builtins" section of dash(1)). Counting them as external would make
|
|
22
|
+
* nearly every script declare `echo`.
|
|
23
|
+
*
|
|
24
|
+
* Bash-only builtins such as `local`, `source` and `mapfile` are
|
|
25
|
+
* deliberately absent: in a `posix` file they are commands that a POSIX
|
|
26
|
+
* shell need not provide.
|
|
27
|
+
*
|
|
28
|
+
* @see https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_14 — XCU §2.14 Special Built-In Utilities
|
|
29
|
+
* @see https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_09_01_01 — XCU §2.9.1.1 Command Search and Execution
|
|
30
|
+
* @see https://pubs.opengroup.org/onlinepubs/9799919799/utilities/V3_chap01.html — POSIX.1-2024 XCU §1.7 Intrinsic Utilities
|
|
31
|
+
* @see https://man7.org/linux/man-pages/man1/dash.1.html — dash(1), "Builtins"
|
|
32
|
+
*/
|
|
33
|
+
/**
|
|
34
|
+
* POSIX special built-in utilities (XCU §2.14).
|
|
35
|
+
*
|
|
36
|
+
* @internal
|
|
37
|
+
*/
|
|
38
|
+
export declare const POSIX_SPECIAL_BUILTINS: readonly string[];
|
|
39
|
+
/**
|
|
40
|
+
* POSIX regular built-in utilities (XCU §2.9.1.1 step 1.d, including the
|
|
41
|
+
* XSI `type` and `ulimit`).
|
|
42
|
+
*
|
|
43
|
+
* @internal
|
|
44
|
+
*/
|
|
45
|
+
export declare const POSIX_REGULAR_BUILTINS: readonly string[];
|
|
46
|
+
/**
|
|
47
|
+
* Utilities POSIX lets a shell build in, and every POSIX shell does.
|
|
48
|
+
*
|
|
49
|
+
* @internal
|
|
50
|
+
*/
|
|
51
|
+
export declare const POSIX_CONVENTIONAL_BUILTINS: readonly string[];
|