@eslint-sh/eslint-plugin 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +223 -17
- package/dist/command-harvest/builtins/bash.d.ts +36 -0
- package/dist/command-harvest/builtins/bash.js +98 -0
- package/dist/command-harvest/builtins/bats.d.ts +45 -0
- package/dist/command-harvest/builtins/bats.js +33 -0
- package/dist/command-harvest/builtins/index.d.ts +18 -0
- package/dist/command-harvest/builtins/index.js +39 -0
- package/dist/command-harvest/builtins/posix.d.ts +51 -0
- package/dist/command-harvest/builtins/posix.js +93 -0
- package/dist/command-harvest/harvest-commands.d.ts +66 -0
- package/dist/command-harvest/harvest-commands.js +97 -0
- package/dist/command-harvest/wrapper-table.d.ts +24 -0
- package/dist/command-harvest/wrapper-table.js +49 -0
- package/dist/configs/agent-authored.d.ts +41 -0
- package/dist/configs/agent-authored.js +33 -0
- package/dist/configs/bash-3-2.d.ts +28 -0
- package/dist/configs/bash-3-2.js +50 -0
- package/dist/configs/posix-portable.d.ts +40 -0
- package/dist/configs/posix-portable.js +52 -0
- package/dist/configs/recommended.d.ts +52 -0
- package/dist/configs/recommended.js +34 -0
- package/dist/declaration-conventions/comment-pragma.d.ts +33 -0
- package/dist/declaration-conventions/comment-pragma.js +153 -0
- package/dist/declaration-conventions/declaration-convention.d.ts +189 -0
- package/dist/declaration-conventions/declaration-convention.js +1 -0
- package/dist/declaration-conventions/read-declared-commands.d.ts +70 -0
- package/dist/declaration-conventions/read-declared-commands.js +128 -0
- package/dist/eslint-plugin-syntax-analyze.d.ts +23 -0
- package/dist/eslint-plugin-syntax.d.ts +27 -0
- package/dist/eslint-plugin.d.ts +1211 -52
- package/dist/index.d.ts +56 -8
- package/dist/index.js +53 -6
- package/dist/language/command-resolution.d.ts +124 -0
- package/dist/language/command-resolution.js +120 -0
- package/dist/language/end-of-input-marker.d.ts +29 -0
- package/dist/language/end-of-input-marker.js +88 -0
- package/dist/language/line-breaks.d.ts +18 -0
- package/dist/language/line-breaks.js +22 -0
- package/dist/language/lint-view-types.d.ts +104 -0
- package/dist/language/lint-view-types.js +1 -0
- package/dist/language/non-shell-file-guard.d.ts +35 -0
- package/dist/language/non-shell-file-guard.js +59 -0
- package/dist/language/selector-classes.d.ts +0 -1
- package/dist/language/selector-classes.js +0 -1
- package/dist/language/shell-language-options.d.ts +7 -1
- package/dist/language/shell-language-options.js +4 -4
- package/dist/language/shell-language.d.ts +10 -2
- package/dist/language/shell-language.js +59 -8
- package/dist/language/shell-source-code.d.ts +342 -5
- package/dist/language/shell-source-code.js +559 -30
- package/dist/language/source-coordinates.d.ts +13 -2
- package/dist/language/source-coordinates.js +68 -14
- package/dist/language/syntax-view.d.ts +60 -39
- package/dist/language/syntax-view.js +200 -45
- package/dist/language/word-resolution.d.ts +4 -3
- package/dist/language/word-resolution.js +4 -3
- package/dist/rules/compat/create-compat-rule.d.ts +59 -0
- package/dist/rules/compat/create-compat-rule.js +65 -0
- package/dist/rules/compat/declaration-options.d.ts +19 -0
- package/dist/rules/compat/declaration-options.js +110 -0
- package/dist/rules/compat/no-append-all-redirect.d.ts +13 -0
- package/dist/rules/compat/no-append-all-redirect.js +73 -0
- package/dist/rules/compat/no-associative-arrays.d.ts +12 -0
- package/dist/rules/compat/no-associative-arrays.js +39 -0
- package/dist/rules/compat/no-case-attributes.d.ts +12 -0
- package/dist/rules/compat/no-case-attributes.js +63 -0
- package/dist/rules/compat/no-case-fallthrough.d.ts +12 -0
- package/dist/rules/compat/no-case-fallthrough.js +67 -0
- package/dist/rules/compat/no-case-modification.d.ts +35 -0
- package/dist/rules/compat/no-case-modification.js +86 -0
- package/dist/rules/compat/no-coproc.d.ts +12 -0
- package/dist/rules/compat/no-coproc.js +47 -0
- package/dist/rules/compat/no-declare-g.d.ts +12 -0
- package/dist/rules/compat/no-declare-g.js +59 -0
- package/dist/rules/compat/no-double-bracket.d.ts +12 -0
- package/dist/rules/compat/no-double-bracket.js +52 -0
- package/dist/rules/compat/no-epoch-variables.d.ts +12 -0
- package/dist/rules/compat/no-epoch-variables.js +57 -0
- package/dist/rules/compat/no-fd-variable.d.ts +12 -0
- package/dist/rules/compat/no-fd-variable.js +48 -0
- package/dist/rules/compat/no-globstar.d.ts +12 -0
- package/dist/rules/compat/no-globstar.js +39 -0
- package/dist/rules/compat/no-lastpipe-semantics.d.ts +12 -0
- package/dist/rules/compat/no-lastpipe-semantics.js +53 -0
- package/dist/rules/compat/no-let.d.ts +12 -0
- package/dist/rules/compat/no-let.js +62 -0
- package/dist/rules/compat/no-mapfile.d.ts +13 -0
- package/dist/rules/compat/no-mapfile.js +122 -0
- package/dist/rules/compat/no-nameref.d.ts +12 -0
- package/dist/rules/compat/no-nameref.js +44 -0
- package/dist/rules/compat/no-negative-array-index.d.ts +13 -0
- package/dist/rules/compat/no-negative-array-index.js +123 -0
- package/dist/rules/compat/no-nounset-empty-array.d.ts +13 -0
- package/dist/rules/compat/no-nounset-empty-array.js +158 -0
- package/dist/rules/compat/no-parameter-transformation.d.ts +13 -0
- package/dist/rules/compat/no-parameter-transformation.js +123 -0
- package/dist/rules/compat/no-pipe-all.d.ts +13 -0
- package/dist/rules/compat/no-pipe-all.js +68 -0
- package/dist/rules/compat/no-printf-time-format.d.ts +12 -0
- package/dist/rules/compat/no-printf-time-format.js +99 -0
- package/dist/rules/compat/no-source.d.ts +13 -0
- package/dist/rules/compat/no-source.js +103 -0
- package/dist/rules/compat/no-test-v.d.ts +12 -0
- package/dist/rules/compat/no-test-v.js +280 -0
- package/dist/rules/compat/no-unicode-escape.d.ts +14 -0
- package/dist/rules/compat/no-unicode-escape.js +120 -0
- package/dist/rules/compat/no-wait-n.d.ts +12 -0
- package/dist/rules/compat/no-wait-n.js +73 -0
- package/dist/rules/compat/operator-range.d.ts +10 -0
- package/dist/rules/compat/operator-range.js +15 -0
- package/dist/rules/compat/posix-absent-command.d.ts +12 -0
- package/dist/rules/compat/posix-absent-command.js +134 -0
- package/dist/rules/compat/shopt-options.d.ts +9 -0
- package/dist/rules/compat/shopt-options.js +63 -0
- package/dist/rules/compat/version-gates.d.ts +76 -0
- package/dist/rules/compat/version-gates.js +197 -0
- package/dist/rules/create-rule.d.ts +91 -0
- package/dist/rules/create-rule.js +51 -0
- package/dist/rules/create-wrapper-rule.d.ts +166 -0
- package/dist/rules/create-wrapper-rule.js +407 -0
- package/dist/rules/define-shell-rule.d.ts +184 -0
- package/dist/rules/define-shell-rule.js +56 -0
- package/dist/rules/index.d.ts +26 -0
- package/dist/rules/index.js +88 -0
- package/dist/rules/max-script-complexity.d.ts +28 -0
- package/dist/rules/max-script-complexity.js +130 -0
- package/dist/rules/no-dynamic-invocation.d.ts +35 -0
- package/dist/rules/no-dynamic-invocation.js +117 -0
- package/dist/rules/no-eval.d.ts +13 -0
- package/dist/rules/no-eval.js +89 -0
- package/dist/rules/no-masked-exit-status.d.ts +21 -0
- package/dist/rules/no-masked-exit-status.js +667 -0
- package/dist/rules/no-secret-env-reads.d.ts +24 -0
- package/dist/rules/no-secret-env-reads.js +133 -0
- package/dist/rules/no-secret-flags.d.ts +32 -0
- package/dist/rules/no-secret-flags.js +162 -0
- package/dist/rules/no-unchecked-cd.d.ts +44 -0
- package/dist/rules/no-unchecked-cd.js +250 -0
- package/dist/rules/no-undeclared-commands.d.ts +20 -0
- package/dist/rules/no-undeclared-commands.js +115 -0
- package/dist/rules/no-unused-declarations.d.ts +23 -0
- package/dist/rules/no-unused-declarations.js +259 -0
- package/dist/rules/prefer-local.d.ts +16 -0
- package/dist/rules/prefer-local.js +249 -0
- package/dist/rules/quote-expansions.d.ts +13 -0
- package/dist/rules/quote-expansions.js +229 -0
- package/dist/rules/require-network-wrapper.d.ts +40 -0
- package/dist/rules/require-network-wrapper.js +244 -0
- package/dist/rules/require-shebang.d.ts +29 -0
- package/dist/rules/require-shebang.js +101 -0
- package/dist/rules/require-strict-mode.d.ts +52 -0
- package/dist/rules/require-strict-mode.js +177 -0
- package/dist/rules/require-tty-guard.d.ts +13 -0
- package/dist/rules/require-tty-guard.js +289 -0
- package/dist/rules/resolve-declaration-convention.d.ts +96 -0
- package/dist/rules/resolve-declaration-convention.js +109 -0
- package/dist/rules/resolve-network-wrapper.d.ts +19 -0
- package/dist/rules/resolve-network-wrapper.js +116 -0
- package/dist/rules/set-options.d.ts +109 -0
- package/dist/rules/set-options.js +201 -0
- package/dist/rules/shebang-interpreter.d.ts +18 -0
- package/dist/rules/shebang-interpreter.js +81 -0
- package/dist/rules/shebang-matches-dialect.d.ts +12 -0
- package/dist/rules/shebang-matches-dialect.js +69 -0
- package/dist/rules/transparent-wrappers.d.ts +52 -0
- package/dist/rules/transparent-wrappers.js +99 -0
- package/dist/rules/user-pattern.d.ts +63 -0
- package/dist/rules/user-pattern.js +184 -0
- package/dist/syntax/analyze.d.ts +19 -0
- package/dist/syntax/analyze.js +21 -0
- package/dist/syntax/index.d.ts +23 -0
- package/dist/syntax/index.js +25 -0
- package/dist/version.d.ts +0 -1
- package/dist/version.js +0 -1
- package/docs/compat.md +347 -0
- package/docs/custom-rule-tutorial.md +780 -34
- package/docs/declaration-conventions.md +478 -0
- package/docs/examples/audit-wrapper-pack.mjs +15 -0
- package/docs/examples/dep-line-convention.mjs +22 -0
- package/docs/examples/eslint.config.mjs +3 -2
- package/docs/examples/no-and-or-as-if-rule.mjs +61 -0
- package/docs/examples/no-backticks-rule-tester.mjs +106 -0
- package/docs/examples/no-backticks-rule.mjs +60 -0
- package/docs/examples/no-curl-in-functions-rule.mjs +53 -0
- package/docs/examples/no-function-keyword-rule.mjs +59 -0
- package/docs/examples/no-listed-commands-rule.ts +63 -0
- package/docs/examples/no-rm-rf-rule.mjs +26 -15
- package/docs/examples/no-unquoted-rm-variable-rule.ts +54 -0
- package/docs/examples/sidecar-convention.mjs +38 -0
- package/docs/examples/sidecar-snapshot-convention.mjs +26 -0
- package/docs/examples/sorted-declarations-rule.mjs +52 -0
- package/docs/reading-nodes.md +159 -0
- package/docs/rules/README.md +78 -0
- package/docs/rules/_template.md +83 -0
- package/docs/rules/compat/no-append-all-redirect.md +87 -0
- package/docs/rules/compat/no-associative-arrays.md +95 -0
- package/docs/rules/compat/no-case-attributes.md +82 -0
- package/docs/rules/compat/no-case-fallthrough.md +90 -0
- package/docs/rules/compat/no-case-modification.md +74 -0
- package/docs/rules/compat/no-coproc.md +76 -0
- package/docs/rules/compat/no-declare-g.md +80 -0
- package/docs/rules/compat/no-double-bracket.md +72 -0
- package/docs/rules/compat/no-epoch-variables.md +79 -0
- package/docs/rules/compat/no-fd-variable.md +78 -0
- package/docs/rules/compat/no-globstar.md +74 -0
- package/docs/rules/compat/no-lastpipe-semantics.md +73 -0
- package/docs/rules/compat/no-let.md +78 -0
- package/docs/rules/compat/no-mapfile.md +118 -0
- package/docs/rules/compat/no-nameref.md +81 -0
- package/docs/rules/compat/no-negative-array-index.md +87 -0
- package/docs/rules/compat/no-nounset-empty-array.md +103 -0
- package/docs/rules/compat/no-parameter-transformation.md +94 -0
- package/docs/rules/compat/no-pipe-all.md +85 -0
- package/docs/rules/compat/no-printf-time-format.md +75 -0
- package/docs/rules/compat/no-source.md +98 -0
- package/docs/rules/compat/no-test-v.md +82 -0
- package/docs/rules/compat/no-unicode-escape.md +91 -0
- package/docs/rules/compat/no-wait-n.md +85 -0
- package/docs/rules/max-script-complexity.md +109 -0
- package/docs/rules/no-dynamic-invocation.md +139 -0
- package/docs/rules/no-eval.md +106 -0
- package/docs/rules/no-masked-exit-status.md +239 -0
- package/docs/rules/no-secret-env-reads.md +173 -0
- package/docs/rules/no-secret-flags.md +169 -0
- package/docs/rules/no-unchecked-cd.md +146 -0
- package/docs/rules/no-undeclared-commands.md +141 -0
- package/docs/rules/no-unused-declarations.md +216 -0
- package/docs/rules/prefer-local.md +221 -0
- package/docs/rules/quote-expansions.md +157 -0
- package/docs/rules/require-network-wrapper.md +171 -0
- package/docs/rules/require-shebang.md +157 -0
- package/docs/rules/require-strict-mode.md +253 -0
- package/docs/rules/require-tty-guard.md +128 -0
- package/docs/rules/shebang-matches-dialect.md +120 -0
- package/docs/selectors.md +2 -2
- package/docs/shellcheck.md +86 -0
- package/docs/wrapper-rules.md +206 -0
- package/package.json +26 -20
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/language/selector-classes.d.ts.map +0 -1
- package/dist/language/selector-classes.js.map +0 -1
- package/dist/language/shell-language-options.d.ts.map +0 -1
- package/dist/language/shell-language-options.js.map +0 -1
- package/dist/language/shell-language.d.ts.map +0 -1
- package/dist/language/shell-language.js.map +0 -1
- package/dist/language/shell-source-code.d.ts.map +0 -1
- package/dist/language/shell-source-code.js.map +0 -1
- package/dist/language/source-coordinates.d.ts.map +0 -1
- package/dist/language/source-coordinates.js.map +0 -1
- package/dist/language/syntax-view.d.ts.map +0 -1
- package/dist/language/syntax-view.js.map +0 -1
- package/dist/language/word-resolution.d.ts.map +0 -1
- package/dist/language/word-resolution.js.map +0 -1
- package/dist/version.d.ts.map +0 -1
- package/dist/version.js.map +0 -1
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The static types of the lint view's nodes, derived from the faithful syntax
|
|
3
|
+
* interfaces `sh-ast` generates from mvdan/sh.
|
|
4
|
+
*
|
|
5
|
+
* Nothing here restates a field list. Each lint-view node type is computed from
|
|
6
|
+
* the matching `sh-ast` interface by the same rules the runtime projection in
|
|
7
|
+
* `syntax-view.ts` applies to `SYNTAX_SCHEMA`, so an mvdan/sh upgrade that
|
|
8
|
+
* adds, removes, renames or retypes a field changes these types in the same
|
|
9
|
+
* `pnpm install`, and any rule (built-in or third-party) that reads a field
|
|
10
|
+
* that no longer exists stops compiling. The remaining way for types and
|
|
11
|
+
* runtime to part is a change to the rules themselves; `test/lint-view-types-drift.test.ts`
|
|
12
|
+
* compares both against the runtime schema to catch that.
|
|
13
|
+
*
|
|
14
|
+
* The projection rules mirrored here:
|
|
15
|
+
* - `Type` becomes the discriminator `type`; every other field is renamed to
|
|
16
|
+
* its lowercase spelling (`Cmd` → `cmd`, `RsrvWord` → `rsrvword`).
|
|
17
|
+
* - Position fields (`SyntaxPos`) have no counterpart: the view carries
|
|
18
|
+
* `range` and `loc` instead.
|
|
19
|
+
* - A nil child is an absent key (an optional property); a nil slice is an
|
|
20
|
+
* empty array and nil slice elements are dropped.
|
|
21
|
+
* - Go strings are decoded to JavaScript strings; booleans and operator
|
|
22
|
+
* values are copied unchanged.
|
|
23
|
+
*/
|
|
24
|
+
import type { Expansion, Node, Replace, Slice, SyntaxPos } from 'sh-ast';
|
|
25
|
+
/** An ESLint location counts UTF-16 units, with both line and column starting at one. @public */
|
|
26
|
+
export interface ShellPosition {
|
|
27
|
+
/** One-based source line, with CRLF treated as one line break. */
|
|
28
|
+
readonly line: number;
|
|
29
|
+
/** One-based UTF-16 column; tabs and grapheme widths are not expanded. */
|
|
30
|
+
readonly column: number;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* The lint-view type of one upstream field value `Value`: a record becomes the
|
|
34
|
+
* node type of that record (a union of records, a union of node types), a slice
|
|
35
|
+
* becomes a read-only array of such nodes with its nil elements removed, a Go
|
|
36
|
+
* string becomes a JavaScript `string`, and booleans and operator values stay
|
|
37
|
+
* as they are. Upstream nil (`null`) and typed nils never reach a view, so
|
|
38
|
+
* neither appears in the result.
|
|
39
|
+
*
|
|
40
|
+
* It is the type of every field of every {@link ShellNodeMap} entry, exported
|
|
41
|
+
* so a helper that takes or returns a node's field value can name it.
|
|
42
|
+
*
|
|
43
|
+
* @public
|
|
44
|
+
*/
|
|
45
|
+
export type ShellNodeField<Value> = Exclude<Value, null | {
|
|
46
|
+
readonly Type: 'TypedNil';
|
|
47
|
+
}> extends infer Present ? Present extends readonly (infer Element)[] ? readonly ShellNodeField<Element>[] : Present extends {
|
|
48
|
+
readonly Type: infer Type extends ShellNodeType;
|
|
49
|
+
} ? ShellNodeMap[Type] : Present extends string | Uint8Array ? string : Present : never;
|
|
50
|
+
/**
|
|
51
|
+
* Lint-view node types by their `type` discriminator, one entry per upstream
|
|
52
|
+
* syntax record. It is computed from `sh-ast`'s generated interfaces, so it
|
|
53
|
+
* follows the pinned mvdan/sh version.
|
|
54
|
+
*
|
|
55
|
+
* A node has `type`, `range` and `loc`, plus a lowercase field for each
|
|
56
|
+
* non-position upstream field. A field that is a single child that may be nil
|
|
57
|
+
* upstream is optional, because the view omits the key when the child is
|
|
58
|
+
* absent; a slice is always present.
|
|
59
|
+
*
|
|
60
|
+
* `range` is a half-open UTF-16 span of the source text and `loc` its ESLint
|
|
61
|
+
* line/column form; both describe the view, not the upstream byte positions. A
|
|
62
|
+
* node whose line ends in CRLF ends before the `\r` (and a text field such as
|
|
63
|
+
* `value` loses that one trailing `\r`), so a CRLF file reads like its LF
|
|
64
|
+
* twin. Auxiliary records (`Expansion`, `Replace`, `Slice`) take their span
|
|
65
|
+
* from their children.
|
|
66
|
+
*
|
|
67
|
+
* @public
|
|
68
|
+
*/
|
|
69
|
+
export type ShellNodeMap = {
|
|
70
|
+
[Record in Exclude<Node, {
|
|
71
|
+
readonly Type: 'TypedNil';
|
|
72
|
+
}> | Expansion | Replace | Slice as Record['Type']]: {
|
|
73
|
+
/** Syntax kind of this node; the discriminator to narrow a ShellNode by. */
|
|
74
|
+
readonly type: Record['Type'];
|
|
75
|
+
/**
|
|
76
|
+
* Half-open UTF-16 span of the node in the source text. Its type is the
|
|
77
|
+
* mutable tuple ESLint's `fixer` methods take, so `node.range` can be passed
|
|
78
|
+
* to `fixer.insertTextBeforeRange` and the others without a cast; treat it
|
|
79
|
+
* as read-only.
|
|
80
|
+
*/
|
|
81
|
+
range: [number, number];
|
|
82
|
+
/** ESLint line and column of the span's two ends. */
|
|
83
|
+
readonly loc: {
|
|
84
|
+
readonly start: ShellPosition;
|
|
85
|
+
readonly end: ShellPosition;
|
|
86
|
+
};
|
|
87
|
+
} & {
|
|
88
|
+
readonly [Name in keyof Record & string as Name extends 'Type' ? never : [Record[Name]] extends [SyntaxPos] ? never : null extends Record[Name] ? Exclude<Record[Name], null | {
|
|
89
|
+
readonly Type: 'TypedNil';
|
|
90
|
+
}> extends {
|
|
91
|
+
readonly Type: string;
|
|
92
|
+
} ? never : Lowercase<Name> : Lowercase<Name>]: ShellNodeField<Record[Name]>;
|
|
93
|
+
} & {
|
|
94
|
+
readonly [Name in keyof Record & string as null extends Record[Name] ? Exclude<Record[Name], null | {
|
|
95
|
+
readonly Type: 'TypedNil';
|
|
96
|
+
}> extends {
|
|
97
|
+
readonly Type: string;
|
|
98
|
+
} ? Lowercase<Name> : never : never]?: ShellNodeField<Record[Name]>;
|
|
99
|
+
};
|
|
100
|
+
};
|
|
101
|
+
/** Every value a lint-view node's `type` can hold. @public */
|
|
102
|
+
export type ShellNodeType = keyof ShellNodeMap;
|
|
103
|
+
/** The lint-view node whose `type` is `Type`, for example `ShellNodeOf<'Stmt'>`. @public */
|
|
104
|
+
export type ShellNodeOf<Type extends ShellNodeType> = ShellNodeMap[Type];
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Recognizes files that are certainly not shell scripts but reached
|
|
3
|
+
* `ShellLanguage.parse` anyway, and explains why.
|
|
4
|
+
*
|
|
5
|
+
* `sh.configs.recommended` deliberately carries no `files` key: the user's own
|
|
6
|
+
* globs decide which paths are shell. A config entry that selects
|
|
7
|
+
* `language: 'sh/sh'` without `files` (most often the preset dropped bare into
|
|
8
|
+
* the config array) applies to every file some other entry matches,
|
|
9
|
+
* including ESLint's default JavaScript globs. Without this guard each such
|
|
10
|
+
* file gets an upstream shell syntax error that says nothing about the
|
|
11
|
+
* misconfiguration. Here this plugin's own code is running, so it can name
|
|
12
|
+
* the real cause instead.
|
|
13
|
+
*
|
|
14
|
+
* Scope boundaries:
|
|
15
|
+
*
|
|
16
|
+
* - Detection is by file extension only, against a fixed list of common
|
|
17
|
+
* non-shell source types. Content is never sniffed: an extensionless file,
|
|
18
|
+
* a shell extension, or any extension not on the list goes through the
|
|
19
|
+
* normal shell parse.
|
|
20
|
+
* - Matching is exact and case-sensitive, the same way ESLint's own `files`
|
|
21
|
+
* globs match extensions.
|
|
22
|
+
* - The guard is not configurable.
|
|
23
|
+
*/
|
|
24
|
+
import type { FileError } from '@eslint/core';
|
|
25
|
+
/**
|
|
26
|
+
* Returns the diagnostic for a file whose extension marks it as a known
|
|
27
|
+
* non-shell source type, or `undefined` when the file should be parsed as
|
|
28
|
+
* shell. The diagnostic names the file type, says the file is being linted
|
|
29
|
+
* as shell, and shows the fix as a one-line config entry. It sits at line 1,
|
|
30
|
+
* column 1, since it describes the whole file rather than any position in
|
|
31
|
+
* it.
|
|
32
|
+
*
|
|
33
|
+
* @internal
|
|
34
|
+
*/
|
|
35
|
+
export declare function nonShellFileError(filePath: string): FileError | undefined;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Known non-shell source extensions, each mapped to the human name used in
|
|
3
|
+
* the diagnostic. Lookup is by the exact extension, leading dot included.
|
|
4
|
+
*/
|
|
5
|
+
const NON_SHELL_FILE_TYPES = new Map([
|
|
6
|
+
['.js', 'JavaScript'],
|
|
7
|
+
['.mjs', 'JavaScript'],
|
|
8
|
+
['.cjs', 'JavaScript'],
|
|
9
|
+
['.jsx', 'JavaScript (JSX)'],
|
|
10
|
+
['.ts', 'TypeScript'],
|
|
11
|
+
['.mts', 'TypeScript'],
|
|
12
|
+
['.cts', 'TypeScript'],
|
|
13
|
+
['.tsx', 'TypeScript (TSX)'],
|
|
14
|
+
['.json', 'JSON'],
|
|
15
|
+
['.jsonc', 'JSONC'],
|
|
16
|
+
['.json5', 'JSON5'],
|
|
17
|
+
['.md', 'Markdown'],
|
|
18
|
+
['.css', 'CSS'],
|
|
19
|
+
['.html', 'HTML'],
|
|
20
|
+
['.vue', 'Vue'],
|
|
21
|
+
['.svelte', 'Svelte'],
|
|
22
|
+
['.yml', 'YAML'],
|
|
23
|
+
['.yaml', 'YAML'],
|
|
24
|
+
]);
|
|
25
|
+
/**
|
|
26
|
+
* The extension of the last path segment, leading dot included, or `''`
|
|
27
|
+
* when there is none. A leading dot alone (a dotfile such as `.bashrc`) is
|
|
28
|
+
* not an extension. Both `/` and `\` separate segments so Windows paths
|
|
29
|
+
* resolve the same way.
|
|
30
|
+
*/
|
|
31
|
+
function extensionOf(filePath) {
|
|
32
|
+
const baseName = filePath.slice(Math.max(filePath.lastIndexOf('/'), filePath.lastIndexOf('\\')) + 1);
|
|
33
|
+
const dot = baseName.lastIndexOf('.');
|
|
34
|
+
return dot > 0 ? baseName.slice(dot) : '';
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Returns the diagnostic for a file whose extension marks it as a known
|
|
38
|
+
* non-shell source type, or `undefined` when the file should be parsed as
|
|
39
|
+
* shell. The diagnostic names the file type, says the file is being linted
|
|
40
|
+
* as shell, and shows the fix as a one-line config entry. It sits at line 1,
|
|
41
|
+
* column 1, since it describes the whole file rather than any position in
|
|
42
|
+
* it.
|
|
43
|
+
*
|
|
44
|
+
* @internal
|
|
45
|
+
*/
|
|
46
|
+
export function nonShellFileError(filePath) {
|
|
47
|
+
const extension = extensionOf(filePath);
|
|
48
|
+
const fileType = NON_SHELL_FILE_TYPES.get(extension);
|
|
49
|
+
if (fileType === undefined) {
|
|
50
|
+
return undefined;
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
message: `This ${fileType} file (${extension}) is being linted as shell: a config entry selects ` +
|
|
54
|
+
`language 'sh/sh' (for example sh.configs.recommended) without a files list. ` +
|
|
55
|
+
`Wrap the preset in an entry with files, e.g. { files: ['**/*.sh'], extends: [sh.configs.recommended] }`,
|
|
56
|
+
line: 1,
|
|
57
|
+
column: 1,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
@@ -18,6 +18,13 @@ export type ShellDialect = 'bash' | 'posix' | 'mksh' | 'bats' | 'zsh';
|
|
|
18
18
|
*/
|
|
19
19
|
export interface ShellLanguageOptions extends Record<string, unknown> {
|
|
20
20
|
readonly dialect?: ShellDialect;
|
|
21
|
+
/**
|
|
22
|
+
* The Bash version (`MAJOR[.MINOR]`, such as `"3.2"`) the scripts must run
|
|
23
|
+
* on. The parser ignores it. The `compat/*` rules read it: unset means the
|
|
24
|
+
* newest version, versions compare numerically by major then minor, and
|
|
25
|
+
* under `dialect: "posix"` Bash-only features are unavailable at any
|
|
26
|
+
* version.
|
|
27
|
+
*/
|
|
21
28
|
readonly shellVersion?: string;
|
|
22
29
|
/**
|
|
23
30
|
* The prefix directive/inline-config comments must start with — e.g. the
|
|
@@ -99,4 +106,3 @@ export declare function validateShellLanguageOptions(languageOptions: Record<str
|
|
|
99
106
|
* @public
|
|
100
107
|
*/
|
|
101
108
|
export declare function normalizeShellLanguageOptions(languageOptions: Record<string, unknown>): NormalizedShellLanguageOptions;
|
|
102
|
-
//# sourceMappingURL=shell-language-options.d.ts.map
|
|
@@ -36,9 +36,10 @@ function isValidDirectivePrefix(value) {
|
|
|
36
36
|
* string — including `''` and `'not-a-version'` — with zero feedback). This
|
|
37
37
|
* validates *shape only*, not membership in a known-versions list: a
|
|
38
38
|
* shape-valid but nonexistent version like `'99.99'` is intentionally
|
|
39
|
-
* accepted, per this issue's acceptance criteria and non-goals (
|
|
40
|
-
*
|
|
41
|
-
*
|
|
39
|
+
* accepted, per this issue's acceptance criteria and non-goals (per-dialect
|
|
40
|
+
* known-version allowlists are out of scope). How rules consume
|
|
41
|
+
* `shellVersion` is defined by the `compat/*` rules' gate
|
|
42
|
+
* (`src/rules/compat/version-gates.ts`, documented in `docs/compat.md`).
|
|
42
43
|
*/
|
|
43
44
|
const SHELL_VERSION_PATTERN = /^\d+(?:\.\d+)?$/u;
|
|
44
45
|
function isValidShellVersionShape(value) {
|
|
@@ -105,4 +106,3 @@ export function normalizeShellLanguageOptions(languageOptions) {
|
|
|
105
106
|
},
|
|
106
107
|
};
|
|
107
108
|
}
|
|
108
|
-
//# sourceMappingURL=shell-language-options.js.map
|
|
@@ -60,6 +60,13 @@ export declare class ShellLanguage implements Language {
|
|
|
60
60
|
* the `{ok: true, ast}` / `{ok: false, errors}` shape `Language.parse` is
|
|
61
61
|
* required to return. Never throws — parse failures are reported as
|
|
62
62
|
* fatal lint messages, per the `Language` contract.
|
|
63
|
+
*
|
|
64
|
+
* A file whose extension marks it as a known non-shell source type (for
|
|
65
|
+
* example `.js` or `.json`) is never shell-parsed: it fails with a single
|
|
66
|
+
* diagnostic explaining that a `language: 'sh/sh'` config entry without
|
|
67
|
+
* `files` reached it, and how to scope that entry. A source in which
|
|
68
|
+
* the parser silently drops text at a U+0080 fails at that character; a
|
|
69
|
+
* U+0080 the parser reads as an ordinary character does not.
|
|
63
70
|
*/
|
|
64
71
|
parse(file: File, context: {
|
|
65
72
|
languageOptions: ShellLanguageOptions;
|
|
@@ -73,10 +80,11 @@ export declare class ShellLanguage implements Language {
|
|
|
73
80
|
* `createSourceCode` is called) — `directivePrefix` is therefore always
|
|
74
81
|
* present, but `?? 'eslint'` stays as an explicit, honest fallback for
|
|
75
82
|
* direct construction (e.g. tests that skip the `Language` normalization
|
|
76
|
-
* step), mirroring `parse()`'s `dialect` default above.
|
|
83
|
+
* step), mirroring `parse()`'s `dialect` default above. `dialect` and
|
|
84
|
+
* `shellVersion` are forwarded so rules read the same normalized values
|
|
85
|
+
* through `sourceCode.dialect` / `sourceCode.shellVersion`.
|
|
77
86
|
*/
|
|
78
87
|
createSourceCode(file: File, parseResult: OkParseResult<ShellFile>, context: {
|
|
79
88
|
languageOptions: ShellLanguageOptions;
|
|
80
89
|
}): ShellSourceCode;
|
|
81
90
|
}
|
|
82
|
-
//# sourceMappingURL=shell-language.d.ts.map
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
/** Adapts upstream parsing and diagnostics to ESLint's synchronous Language contract. */
|
|
2
2
|
import { LangBash, LangBats, LangError, LangMirBSDKorn, LangPOSIX, LangZsh, ParseError, parseWithPositionsSync, } from 'sh-ast';
|
|
3
|
+
import { firstDroppedTextIndex } from './end-of-input-marker.js';
|
|
4
|
+
import { nonShellFileError } from './non-shell-file-guard.js';
|
|
3
5
|
import { matchesSelectorClass as matchesShellSelectorClass } from './selector-classes.js';
|
|
4
6
|
import { ShellSourceCode } from './shell-source-code.js';
|
|
5
7
|
import { SourceCoordinates } from './source-coordinates.js';
|
|
@@ -24,6 +26,15 @@ function stripPositionPrefix(message, filename) {
|
|
|
24
26
|
const escaped = filename.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
25
27
|
return message.replace(new RegExp(`^${escaped}:(?:\\d+|\\?):(?:\\d+|\\?):\\s*`), '');
|
|
26
28
|
}
|
|
29
|
+
/**
|
|
30
|
+
* Starts the message of a parse failure that is not a syntax error: the parser
|
|
31
|
+
* runtime aborted or the bridge to it failed, so nothing is known about the
|
|
32
|
+
* input. A syntax error never carries it. ESLint itself puts `Parsing error: `
|
|
33
|
+
* in front of every message a language returns from `parse`; this prefix is
|
|
34
|
+
* what tells the two kinds apart after that, for users and for tooling such as
|
|
35
|
+
* the corpus soak.
|
|
36
|
+
*/
|
|
37
|
+
const PARSER_RUNTIME_FAILURE_PREFIX = 'eslint-sh parser runtime failure (not a syntax error in this file): ';
|
|
27
38
|
/** Preserve actual syntax positions while converting byte offsets into this source's UTF-16 diagnostics. */
|
|
28
39
|
function toFileError(error, source) {
|
|
29
40
|
if (error instanceof ParseError || error instanceof LangError) {
|
|
@@ -40,7 +51,26 @@ function toFileError(error, source) {
|
|
|
40
51
|
};
|
|
41
52
|
}
|
|
42
53
|
// Bridge/input failures carry no asserted syntax position. The fallback is an ESLint presentation policy.
|
|
43
|
-
|
|
54
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
55
|
+
return { message: `${PARSER_RUNTIME_FAILURE_PREFIX}${detail}`, line: 1, column: 1 };
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* The fatal parse error for a source in which the parser drops text at a U+0080, at that
|
|
59
|
+
* character, or `undefined` when nothing is dropped. Checked before parsing so a silently
|
|
60
|
+
* truncated tree never reaches the rules. A U+0080 the parser reads as an ordinary character
|
|
61
|
+
* (inside a quoted-delimiter heredoc body, after a backslash) is not an error; one the parser
|
|
62
|
+
* rejects (inside quotes or an expansion) is reported by the parser itself.
|
|
63
|
+
*/
|
|
64
|
+
function endOfInputMarkerError(source, options) {
|
|
65
|
+
const index = firstDroppedTextIndex(source, options);
|
|
66
|
+
if (index === undefined)
|
|
67
|
+
return undefined;
|
|
68
|
+
const location = new SourceCoordinates(source).atIndex(index);
|
|
69
|
+
return {
|
|
70
|
+
message: 'the character U+0080 is not supported: the parser reads it as end of input',
|
|
71
|
+
line: location.line,
|
|
72
|
+
column: location.column,
|
|
73
|
+
};
|
|
44
74
|
}
|
|
45
75
|
/**
|
|
46
76
|
* `ShellLanguage` — the `@eslint/core` `Language` implementation for shell
|
|
@@ -106,16 +136,32 @@ export class ShellLanguage {
|
|
|
106
136
|
* the `{ok: true, ast}` / `{ok: false, errors}` shape `Language.parse` is
|
|
107
137
|
* required to return. Never throws — parse failures are reported as
|
|
108
138
|
* fatal lint messages, per the `Language` contract.
|
|
139
|
+
*
|
|
140
|
+
* A file whose extension marks it as a known non-shell source type (for
|
|
141
|
+
* example `.js` or `.json`) is never shell-parsed: it fails with a single
|
|
142
|
+
* diagnostic explaining that a `language: 'sh/sh'` config entry without
|
|
143
|
+
* `files` reached it, and how to scope that entry. A source in which
|
|
144
|
+
* the parser silently drops text at a U+0080 fails at that character; a
|
|
145
|
+
* U+0080 the parser reads as an ordinary character does not.
|
|
109
146
|
*/
|
|
110
147
|
parse(file, context) {
|
|
148
|
+
const misappliedError = nonShellFileError(file.path);
|
|
149
|
+
if (misappliedError !== undefined) {
|
|
150
|
+
return { ok: false, errors: [misappliedError] };
|
|
151
|
+
}
|
|
111
152
|
const text = file.body;
|
|
112
153
|
const dialect = context.languageOptions.dialect ?? 'bash';
|
|
154
|
+
const parseOptions = {
|
|
155
|
+
Variant: DIALECT_VARIANTS[dialect],
|
|
156
|
+
Filename: file.path,
|
|
157
|
+
KeepComments: true,
|
|
158
|
+
};
|
|
159
|
+
const unsupported = endOfInputMarkerError(text, parseOptions);
|
|
160
|
+
if (unsupported !== undefined) {
|
|
161
|
+
return { ok: false, errors: [unsupported] };
|
|
162
|
+
}
|
|
113
163
|
try {
|
|
114
|
-
const parsed = parseWithPositionsSync(text,
|
|
115
|
-
Variant: DIALECT_VARIANTS[dialect],
|
|
116
|
-
Filename: file.path,
|
|
117
|
-
KeepComments: true,
|
|
118
|
-
});
|
|
164
|
+
const parsed = parseWithPositionsSync(text, parseOptions);
|
|
119
165
|
const ast = adaptSyntaxFile(parsed.File, text, parsed.Positions);
|
|
120
166
|
return { ok: true, ast };
|
|
121
167
|
}
|
|
@@ -132,14 +178,19 @@ export class ShellLanguage {
|
|
|
132
178
|
* `createSourceCode` is called) — `directivePrefix` is therefore always
|
|
133
179
|
* present, but `?? 'eslint'` stays as an explicit, honest fallback for
|
|
134
180
|
* direct construction (e.g. tests that skip the `Language` normalization
|
|
135
|
-
* step), mirroring `parse()`'s `dialect` default above.
|
|
181
|
+
* step), mirroring `parse()`'s `dialect` default above. `dialect` and
|
|
182
|
+
* `shellVersion` are forwarded so rules read the same normalized values
|
|
183
|
+
* through `sourceCode.dialect` / `sourceCode.shellVersion`.
|
|
136
184
|
*/
|
|
137
185
|
createSourceCode(file, parseResult, context) {
|
|
138
186
|
return new ShellSourceCode({
|
|
139
187
|
text: file.body,
|
|
140
188
|
ast: parseResult.ast,
|
|
141
189
|
directivePrefix: context.languageOptions.directivePrefix ?? 'eslint',
|
|
190
|
+
dialect: context.languageOptions.dialect ?? 'bash',
|
|
191
|
+
...(context.languageOptions.shellVersion === undefined
|
|
192
|
+
? {}
|
|
193
|
+
: { shellVersion: context.languageOptions.shellVersion }),
|
|
142
194
|
});
|
|
143
195
|
}
|
|
144
196
|
}
|
|
145
|
-
//# sourceMappingURL=shell-language.js.map
|