@eslint-sh/eslint-plugin 0.1.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/LICENSE +21 -0
- package/README.md +78 -0
- package/dist/eslint-plugin.d.ts +317 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +33 -0
- package/dist/index.js.map +1 -0
- package/dist/language/selector-classes.d.ts +57 -0
- package/dist/language/selector-classes.d.ts.map +1 -0
- package/dist/language/selector-classes.js +55 -0
- package/dist/language/selector-classes.js.map +1 -0
- package/dist/language/shell-language-options.d.ts +104 -0
- package/dist/language/shell-language-options.d.ts.map +1 -0
- package/dist/language/shell-language-options.js +112 -0
- package/dist/language/shell-language-options.js.map +1 -0
- package/dist/language/shell-language.d.ts +84 -0
- package/dist/language/shell-language.d.ts.map +1 -0
- package/dist/language/shell-language.js +179 -0
- package/dist/language/shell-language.js.map +1 -0
- package/dist/language/shell-source-code.d.ts +121 -0
- package/dist/language/shell-source-code.d.ts.map +1 -0
- package/dist/language/shell-source-code.js +318 -0
- package/dist/language/shell-source-code.js.map +1 -0
- package/dist/version.d.ts +14 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +29 -0
- package/dist/version.js.map +1 -0
- package/docs/custom-rule-tutorial.md +169 -0
- package/docs/examples/eslint.config.mjs +23 -0
- package/docs/examples/no-rm-rf-rule.js +74 -0
- package/docs/selectors.md +89 -0
- package/package.json +80 -0
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import type { ShellDialect } from 'sh-ast';
|
|
2
|
+
/**
|
|
3
|
+
* `languageOptions` accepted by {@link ShellLanguage} (design/PACKAGES.md's
|
|
4
|
+
* `languageOptions` schema): `dialect` selects mvdan/sh's `LangVariant` by
|
|
5
|
+
* string name (default `"bash"`); `shellVersion` is consumed by the
|
|
6
|
+
* rule layer (e.g. the bash-3.2 compatibility pack), never by the parser
|
|
7
|
+
* itself (see design/ARCHITECTURE.md, "A verified nuance worth stating
|
|
8
|
+
* loudly").
|
|
9
|
+
*
|
|
10
|
+
* Extends `Record<string, unknown>` (rather than a bare interface) so this
|
|
11
|
+
* type satisfies the `@eslint/core` package's `LanguageOptions` bound
|
|
12
|
+
* (itself `Record<string, unknown>`) wherever it is used as a
|
|
13
|
+
* `Language`/`SourceCode` type parameter — ESLint's own contract for
|
|
14
|
+
* language options is that they are arbitrary user-supplied records, not a
|
|
15
|
+
* closed shape.
|
|
16
|
+
*
|
|
17
|
+
* @public
|
|
18
|
+
*/
|
|
19
|
+
export interface ShellLanguageOptions extends Record<string, unknown> {
|
|
20
|
+
readonly dialect?: ShellDialect;
|
|
21
|
+
readonly shellVersion?: string;
|
|
22
|
+
/**
|
|
23
|
+
* The prefix directive/inline-config comments must start with — e.g. the
|
|
24
|
+
* default `"eslint"` recognizes `# eslint-disable-next-line ...` and
|
|
25
|
+
* `# eslint <rule-config>`. Consumers who embed shell-linting inside a
|
|
26
|
+
* larger toolchain that already overloads `#eslint-*` comments for
|
|
27
|
+
* something else can configure a different prefix (design/ARCHITECTURE.md,
|
|
28
|
+
* "SourceCode and Language objects").
|
|
29
|
+
* @defaultValue `"eslint"`
|
|
30
|
+
*/
|
|
31
|
+
readonly directivePrefix?: string;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* The plain-data shape {@link NormalizedShellLanguageOptions.toJSON} returns:
|
|
35
|
+
* `dialect` and `directivePrefix` always present (both defaulted), `shellVersion`
|
|
36
|
+
* present only when the caller supplied one. Keys are always emitted in this
|
|
37
|
+
* fixed order so that two logically-identical inputs serialize identically
|
|
38
|
+
* regardless of the key order the caller happened to use — the property this
|
|
39
|
+
* type exists to guarantee for cache-key stability (see
|
|
40
|
+
* {@link NormalizedShellLanguageOptions}). `directivePrefix` is included
|
|
41
|
+
* unconditionally (like `dialect`, unlike `shellVersion`) because — like
|
|
42
|
+
* `dialect` and unlike `shellVersion` — it always has a concrete effect on
|
|
43
|
+
* linting results (which comments the `SourceCode` recognizes as directives)
|
|
44
|
+
* whether or not the caller supplied one, so two configs that differ only in
|
|
45
|
+
* an *implicit* default vs. an *explicit* `directivePrefix: "eslint"` must
|
|
46
|
+
* still cache-key identically.
|
|
47
|
+
*
|
|
48
|
+
* @public
|
|
49
|
+
*/
|
|
50
|
+
export interface ShellLanguageOptionsJSON {
|
|
51
|
+
readonly dialect: ShellDialect;
|
|
52
|
+
readonly directivePrefix: string;
|
|
53
|
+
readonly shellVersion?: string;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* The result of {@link ShellLanguage.normalizeLanguageOptions}: `dialect` and
|
|
57
|
+
* `directivePrefix` are always resolved to concrete values (defaulted when
|
|
58
|
+
* the caller didn't specify them), and `toJSON()` provides the "custom
|
|
59
|
+
* serialization" ESLint's languages guide recommends
|
|
60
|
+
* (https://eslint.org/docs/latest/extend/languages, "normalizeLanguageOptions")
|
|
61
|
+
* so that config caching keyed off `JSON.stringify(languageOptions)` is
|
|
62
|
+
* stable irrespective of the input object's own key order — `JSON.stringify`
|
|
63
|
+
* calls `toJSON()` automatically when present.
|
|
64
|
+
*
|
|
65
|
+
* @public
|
|
66
|
+
*/
|
|
67
|
+
export interface NormalizedShellLanguageOptions extends Record<string, unknown> {
|
|
68
|
+
readonly dialect: ShellDialect;
|
|
69
|
+
readonly directivePrefix: string;
|
|
70
|
+
readonly shellVersion?: string;
|
|
71
|
+
toJSON(): ShellLanguageOptionsJSON;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Validates `languageOptions` for {@link ShellLanguage}. Throws a
|
|
75
|
+
* `TypeError` when `dialect` is present but not one of the five supported
|
|
76
|
+
* dialects, when `shellVersion` is present but not a string or does not
|
|
77
|
+
* match the `MAJOR[.MINOR]` numeric shape (see {@link SHELL_VERSION_PATTERN};
|
|
78
|
+
* e.g. `''` and `'not-a-version'` are rejected, but a shape-valid,
|
|
79
|
+
* nonexistent version like `'99.99'` is accepted — shape is validated, not
|
|
80
|
+
* membership in a known-versions list), or when `directivePrefix` is present
|
|
81
|
+
* but not a valid directive-comment label (see {@link DIRECTIVE_PREFIX_PATTERN}).
|
|
82
|
+
* Unknown keys are silently ignored, per the ESLint `Language` contract
|
|
83
|
+
* (design/ARCHITECTURE.md's `languageOptions` schema comment).
|
|
84
|
+
*
|
|
85
|
+
* The parameter is treated as `Record<string, unknown>` internally (rather
|
|
86
|
+
* than trusting {@link ShellLanguageOptions}'s declared field types) because
|
|
87
|
+
* this function's entire job is validating an untrusted boundary: the
|
|
88
|
+
* caller-supplied object may not actually match the declared shape.
|
|
89
|
+
*
|
|
90
|
+
* @public
|
|
91
|
+
*/
|
|
92
|
+
export declare function validateShellLanguageOptions(languageOptions: Record<string, unknown>): void;
|
|
93
|
+
/**
|
|
94
|
+
* Normalizes `languageOptions` for {@link ShellLanguage}: resolves `dialect`
|
|
95
|
+
* and `directivePrefix` to their defaults (`"bash"` / `"eslint"`) when
|
|
96
|
+
* unset, and attaches a `toJSON()` method whose key order never depends on
|
|
97
|
+
* the input object's own key order (see {@link NormalizedShellLanguageOptions}).
|
|
98
|
+
* Callers are expected to have already run {@link validateShellLanguageOptions}
|
|
99
|
+
* — this function does not re-validate.
|
|
100
|
+
*
|
|
101
|
+
* @public
|
|
102
|
+
*/
|
|
103
|
+
export declare function normalizeShellLanguageOptions(languageOptions: Record<string, unknown>): NormalizedShellLanguageOptions;
|
|
104
|
+
//# sourceMappingURL=shell-language-options.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shell-language-options.d.ts","sourceRoot":"","sources":["../../src/language/shell-language-options.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AA4D3C;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,WAAW,oBAAqB,SAAQ,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IACnE,QAAQ,CAAC,OAAO,CAAC,EAAE,YAAY,CAAC;IAChC,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAE/B;;;;;;;;OAQG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC;CACnC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,OAAO,EAAE,YAAY,CAAC;IAC/B,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;CAChC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,8BAA+B,SAAQ,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAC7E,QAAQ,CAAC,OAAO,EAAE,YAAY,CAAC;IAC/B,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,MAAM,IAAI,wBAAwB,CAAC;CACpC;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,4BAA4B,CAAC,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CA8B3F;AAED;;;;;;;;;GASG;AACH,wBAAgB,6BAA6B,CAC3C,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACvC,8BAA8B,CAkBhC"}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The full set of dialects {@link ShellLanguage.validateLanguageOptions}
|
|
3
|
+
* accepts, in the order surfaced by its error message. Mirrors
|
|
4
|
+
* `sh-ast`'s `SUPPORTED_SHELL_DIALECTS` (see
|
|
5
|
+
* design/ARCHITECTURE.md, "Parse errors and dialects", and
|
|
6
|
+
* design/PACKAGES.md's `languageOptions` schema) — kept as an independent
|
|
7
|
+
* constant here (rather than imported) because the bridge does not export
|
|
8
|
+
* its internal list; both lists are exercised by tests that pin the same
|
|
9
|
+
* five dialect names.
|
|
10
|
+
*/
|
|
11
|
+
const SUPPORTED_SHELL_DIALECTS = ['bash', 'posix', 'mksh', 'bats', 'zsh'];
|
|
12
|
+
function isShellDialect(value) {
|
|
13
|
+
return (typeof value === 'string' && SUPPORTED_SHELL_DIALECTS.includes(value));
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Default for {@link ShellLanguageOptions.directivePrefix} — the
|
|
17
|
+
* ESLint-conventional spelling (design/ARCHITECTURE.md, "SourceCode and
|
|
18
|
+
* Language objects": "The directive prefix is configurable but defaults to
|
|
19
|
+
* the ESLint-conventional spelling").
|
|
20
|
+
*/
|
|
21
|
+
const DEFAULT_DIRECTIVE_PREFIX = 'eslint';
|
|
22
|
+
/**
|
|
23
|
+
* Matches `@eslint/plugin-kit`'s `ConfigCommentParser` internal
|
|
24
|
+
* `directivesPattern` (`/^([a-z]+(?:-[a-z]+)*)(?:\s|$)/u`) for the label
|
|
25
|
+
* portion alone: a `directivePrefix` that doesn't fit this shape could never
|
|
26
|
+
* combine with a `-disable`/`-enable`/`-disable-line`/`-disable-next-line`
|
|
27
|
+
* suffix into a label that parser can ever recognize, so rejecting it here
|
|
28
|
+
* (rather than silently accepting a prefix that can never match anything)
|
|
29
|
+
* is a strict improvement.
|
|
30
|
+
*/
|
|
31
|
+
const DIRECTIVE_PREFIX_PATTERN = /^[a-z]+(?:-[a-z]+)*$/u;
|
|
32
|
+
function isValidDirectivePrefix(value) {
|
|
33
|
+
return typeof value === 'string' && DIRECTIVE_PREFIX_PATTERN.test(value);
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Matches the `MAJOR[.MINOR]` numeric shape {@link validateShellLanguageOptions}
|
|
37
|
+
* requires `shellVersion` to have (issue #34: the sibling `dialect` option throws
|
|
38
|
+
* an exemplary enum-listing TypeError, while `shellVersion` accepted any
|
|
39
|
+
* string — including `''` and `'not-a-version'` — with zero feedback). This
|
|
40
|
+
* validates *shape only*, not membership in a known-versions list: a
|
|
41
|
+
* shape-valid but nonexistent version like `'99.99'` is intentionally
|
|
42
|
+
* accepted, per this issue's acceptance criteria and non-goals (deciding
|
|
43
|
+
* `shellVersion`'s consumption semantics, and per-dialect known-version
|
|
44
|
+
* allowlists, are separate concerns left to the rule layer).
|
|
45
|
+
*/
|
|
46
|
+
const SHELL_VERSION_PATTERN = /^\d+(?:\.\d+)?$/u;
|
|
47
|
+
function isValidShellVersionShape(value) {
|
|
48
|
+
return SHELL_VERSION_PATTERN.test(value);
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Validates `languageOptions` for {@link ShellLanguage}. Throws a
|
|
52
|
+
* `TypeError` when `dialect` is present but not one of the five supported
|
|
53
|
+
* dialects, when `shellVersion` is present but not a string or does not
|
|
54
|
+
* match the `MAJOR[.MINOR]` numeric shape (see {@link SHELL_VERSION_PATTERN};
|
|
55
|
+
* e.g. `''` and `'not-a-version'` are rejected, but a shape-valid,
|
|
56
|
+
* nonexistent version like `'99.99'` is accepted — shape is validated, not
|
|
57
|
+
* membership in a known-versions list), or when `directivePrefix` is present
|
|
58
|
+
* but not a valid directive-comment label (see {@link DIRECTIVE_PREFIX_PATTERN}).
|
|
59
|
+
* Unknown keys are silently ignored, per the ESLint `Language` contract
|
|
60
|
+
* (design/ARCHITECTURE.md's `languageOptions` schema comment).
|
|
61
|
+
*
|
|
62
|
+
* The parameter is treated as `Record<string, unknown>` internally (rather
|
|
63
|
+
* than trusting {@link ShellLanguageOptions}'s declared field types) because
|
|
64
|
+
* this function's entire job is validating an untrusted boundary: the
|
|
65
|
+
* caller-supplied object may not actually match the declared shape.
|
|
66
|
+
*
|
|
67
|
+
* @public
|
|
68
|
+
*/
|
|
69
|
+
export function validateShellLanguageOptions(languageOptions) {
|
|
70
|
+
const { dialect, shellVersion, directivePrefix } = languageOptions;
|
|
71
|
+
if (dialect !== undefined && !isShellDialect(dialect)) {
|
|
72
|
+
throw new TypeError(`@eslint-sh/eslint-plugin: languageOptions.dialect must be one of ${SUPPORTED_SHELL_DIALECTS.join(', ')}; received ${JSON.stringify(dialect)}.`);
|
|
73
|
+
}
|
|
74
|
+
if (shellVersion !== undefined && typeof shellVersion !== 'string') {
|
|
75
|
+
throw new TypeError(`@eslint-sh/eslint-plugin: languageOptions.shellVersion must be a string (e.g. "3.2"); received ${JSON.stringify(shellVersion)}.`);
|
|
76
|
+
}
|
|
77
|
+
if (typeof shellVersion === 'string' && !isValidShellVersionShape(shellVersion)) {
|
|
78
|
+
throw new TypeError(`@eslint-sh/eslint-plugin: languageOptions.shellVersion must match the shape MAJOR[.MINOR] (e.g. "3.2", "5"); received ${JSON.stringify(shellVersion)}.`);
|
|
79
|
+
}
|
|
80
|
+
if (directivePrefix !== undefined && !isValidDirectivePrefix(directivePrefix)) {
|
|
81
|
+
throw new TypeError(`@eslint-sh/eslint-plugin: languageOptions.directivePrefix must be one or more lowercase, hyphen-separated words (e.g. "eslint", "shellcheck-sh"); received ${JSON.stringify(directivePrefix)}.`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Normalizes `languageOptions` for {@link ShellLanguage}: resolves `dialect`
|
|
86
|
+
* and `directivePrefix` to their defaults (`"bash"` / `"eslint"`) when
|
|
87
|
+
* unset, and attaches a `toJSON()` method whose key order never depends on
|
|
88
|
+
* the input object's own key order (see {@link NormalizedShellLanguageOptions}).
|
|
89
|
+
* Callers are expected to have already run {@link validateShellLanguageOptions}
|
|
90
|
+
* — this function does not re-validate.
|
|
91
|
+
*
|
|
92
|
+
* @public
|
|
93
|
+
*/
|
|
94
|
+
export function normalizeShellLanguageOptions(languageOptions) {
|
|
95
|
+
const dialect = languageOptions.dialect ?? 'bash';
|
|
96
|
+
const directivePrefix = languageOptions.directivePrefix ?? DEFAULT_DIRECTIVE_PREFIX;
|
|
97
|
+
const shellVersion = languageOptions.shellVersion;
|
|
98
|
+
return {
|
|
99
|
+
dialect,
|
|
100
|
+
directivePrefix,
|
|
101
|
+
// `exactOptionalPropertyTypes` forbids assigning `undefined` to an
|
|
102
|
+
// optional property directly — omit the key entirely rather than set it
|
|
103
|
+
// to `undefined` when the caller didn't supply a shellVersion.
|
|
104
|
+
...(shellVersion === undefined ? {} : { shellVersion }),
|
|
105
|
+
toJSON() {
|
|
106
|
+
return shellVersion === undefined
|
|
107
|
+
? { dialect, directivePrefix }
|
|
108
|
+
: { dialect, directivePrefix, shellVersion };
|
|
109
|
+
},
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
//# sourceMappingURL=shell-language-options.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shell-language-options.js","sourceRoot":"","sources":["../../src/language/shell-language-options.ts"],"names":[],"mappings":"AAEA;;;;;;;;;GASG;AACH,MAAM,wBAAwB,GAA4B,CAAC,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC;AAEnG,SAAS,cAAc,CAAC,KAAc;IACpC,OAAO,CACL,OAAO,KAAK,KAAK,QAAQ,IAAK,wBAA8C,CAAC,QAAQ,CAAC,KAAK,CAAC,CAC7F,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,wBAAwB,GAAG,QAAQ,CAAC;AAE1C;;;;;;;;GAQG;AACH,MAAM,wBAAwB,GAAG,uBAAuB,CAAC;AAEzD,SAAS,sBAAsB,CAAC,KAAc;IAC5C,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,wBAAwB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AAC3E,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,qBAAqB,GAAG,kBAAkB,CAAC;AAEjD,SAAS,wBAAwB,CAAC,KAAa;IAC7C,OAAO,qBAAqB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AAC3C,CAAC;AA6ED;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,UAAU,4BAA4B,CAAC,eAAwC;IACnF,MAAM,EAAE,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,GAAG,eAAe,CAAC;IACnE,IAAI,OAAO,KAAK,SAAS,IAAI,CAAC,cAAc,CAAC,OAAO,CAAC,EAAE,CAAC;QACtD,MAAM,IAAI,SAAS,CACjB,oEAAoE,wBAAwB,CAAC,IAAI,CAC/F,IAAI,CACL,cAAc,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,GAAG,CAC1C,CAAC;IACJ,CAAC;IACD,IAAI,YAAY,KAAK,SAAS,IAAI,OAAO,YAAY,KAAK,QAAQ,EAAE,CAAC;QACnE,MAAM,IAAI,SAAS,CACjB,kGAAkG,IAAI,CAAC,SAAS,CAC9G,YAAY,CACb,GAAG,CACL,CAAC;IACJ,CAAC;IACD,IAAI,OAAO,YAAY,KAAK,QAAQ,IAAI,CAAC,wBAAwB,CAAC,YAAY,CAAC,EAAE,CAAC;QAChF,MAAM,IAAI,SAAS,CACjB,yHAAyH,IAAI,CAAC,SAAS,CACrI,YAAY,CACb,GAAG,CACL,CAAC;IACJ,CAAC;IACD,IAAI,eAAe,KAAK,SAAS,IAAI,CAAC,sBAAsB,CAAC,eAAe,CAAC,EAAE,CAAC;QAC9E,MAAM,IAAI,SAAS,CACjB,8JAA8J,IAAI,CAAC,SAAS,CAC1K,eAAe,CAChB,GAAG,CACL,CAAC;IACJ,CAAC;AACH,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,6BAA6B,CAC3C,eAAwC;IAExC,MAAM,OAAO,GAAI,eAAe,CAAC,OAAoC,IAAI,MAAM,CAAC;IAChF,MAAM,eAAe,GAClB,eAAe,CAAC,eAAsC,IAAI,wBAAwB,CAAC;IACtF,MAAM,YAAY,GAAG,eAAe,CAAC,YAAkC,CAAC;IACxE,OAAO;QACL,OAAO;QACP,eAAe;QACf,mEAAmE;QACnE,wEAAwE;QACxE,+DAA+D;QAC/D,GAAG,CAAC,YAAY,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,CAAC;QACvD,MAAM;YACJ,OAAO,YAAY,KAAK,SAAS;gBAC/B,CAAC,CAAC,EAAE,OAAO,EAAE,eAAe,EAAE;gBAC9B,CAAC,CAAC,EAAE,OAAO,EAAE,eAAe,EAAE,YAAY,EAAE,CAAC;QACjD,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import type { ShFile, ShNode } from 'sh-ast';
|
|
2
|
+
import type { File, Language, OkParseResult, ParseResult } from '@eslint/core';
|
|
3
|
+
import { ShellSourceCode } from './shell-source-code.js';
|
|
4
|
+
import { type NormalizedShellLanguageOptions, type ShellLanguageOptions } from './shell-language-options.js';
|
|
5
|
+
/**
|
|
6
|
+
* `ShellLanguage` — the `@eslint/core` `Language` implementation for shell
|
|
7
|
+
* scripts (design/ARCHITECTURE.md, "SourceCode and Language objects";
|
|
8
|
+
* design/PACKAGES.md, `@eslint-sh/eslint-plugin`). Registered by the plugin
|
|
9
|
+
* as `languages.sh`, so consumers select it via `language: "sh/sh"`.
|
|
10
|
+
*
|
|
11
|
+
* Implements `matchesSelectorClass` (the six launch esquery selector
|
|
12
|
+
* classes — see `./selector-classes.js`), so rules and end users can write
|
|
13
|
+
* class-based selectors (`:command`, `:loop`, `:conditional`, `:expansion`,
|
|
14
|
+
* `:quoted`, `:function`) against shell trees. The `SourceCode` directive
|
|
15
|
+
* members (`getDisableDirectives`, `getInlineConfigNodes`,
|
|
16
|
+
* `applyInlineConfig`) are implemented on `ShellSourceCode` itself; see
|
|
17
|
+
* shell-source-code.ts.
|
|
18
|
+
*
|
|
19
|
+
* @public
|
|
20
|
+
*/
|
|
21
|
+
export declare class ShellLanguage implements Language {
|
|
22
|
+
readonly fileType: "text";
|
|
23
|
+
readonly lineStart: 1;
|
|
24
|
+
readonly columnStart: 1;
|
|
25
|
+
readonly nodeTypeKey: string;
|
|
26
|
+
readonly visitorKeys: Record<string, string[]>;
|
|
27
|
+
readonly defaultLanguageOptions: ShellLanguageOptions;
|
|
28
|
+
/**
|
|
29
|
+
* Validates `languageOptions.dialect` (must be one of the five supported
|
|
30
|
+
* dialects, if present), `languageOptions.shellVersion` (must be a
|
|
31
|
+
* string matching the `MAJOR[.MINOR]` numeric shape, if present), and
|
|
32
|
+
* `languageOptions.directivePrefix` (must be a valid directive-comment
|
|
33
|
+
* label, if present). Unknown keys are silently ignored, per the
|
|
34
|
+
* `Language` contract. See `shell-language-options.ts`'s
|
|
35
|
+
* `validateShellLanguageOptions` for the full rationale.
|
|
36
|
+
*/
|
|
37
|
+
validateLanguageOptions(languageOptions: ShellLanguageOptions): void;
|
|
38
|
+
/**
|
|
39
|
+
* Resolves `dialect` to its default (`"bash"`) when unset and attaches a
|
|
40
|
+
* `toJSON()` method with a fixed key order, so that ESLint's config
|
|
41
|
+
* caching (keyed off serializing `languageOptions`) is stable regardless
|
|
42
|
+
* of the key order a user's config happened to use. See
|
|
43
|
+
* `shell-language-options.ts`'s `normalizeShellLanguageOptions`.
|
|
44
|
+
*/
|
|
45
|
+
normalizeLanguageOptions(languageOptions: ShellLanguageOptions): NormalizedShellLanguageOptions;
|
|
46
|
+
/**
|
|
47
|
+
* `@eslint/core`'s esquery hook: lets a selector's leading `:` "class"
|
|
48
|
+
* (e.g. `:command` in `CallExpr[args.0.parts.0.value='jq']` isn't even
|
|
49
|
+
* needed there, but `:loop :command` is) match a whole family of node
|
|
50
|
+
* types instead of one literal type name. Delegates to
|
|
51
|
+
* `./selector-classes.js`'s `SELECTOR_CLASS_NODE_TYPES` table for the six
|
|
52
|
+
* launch classes (design/ARCHITECTURE.md, "SourceCode and Language
|
|
53
|
+
* objects"); `ancestry` is unused today because none of the six launch
|
|
54
|
+
* classes need ancestor context, but the parameter is accepted to match
|
|
55
|
+
* `Language.matchesSelectorClass`'s signature exactly.
|
|
56
|
+
*/
|
|
57
|
+
matchesSelectorClass(className: string, node: ShNode, _ancestry: ShNode[]): boolean;
|
|
58
|
+
/**
|
|
59
|
+
* Parses `file.body` (guaranteed to be a `string`, since `fileType` is
|
|
60
|
+
* `"text"`) via `sh-ast`'s `parseSync`, mapping the result to
|
|
61
|
+
* the `{ok: true, ast}` / `{ok: false, errors}` shape `Language.parse` is
|
|
62
|
+
* required to return. Never throws — parse failures are reported as
|
|
63
|
+
* fatal lint messages, per the `Language` contract
|
|
64
|
+
* (design/ARCHITECTURE.md, "Parse errors and dialects").
|
|
65
|
+
*/
|
|
66
|
+
parse(file: File, context: {
|
|
67
|
+
languageOptions: ShellLanguageOptions;
|
|
68
|
+
}): ParseResult<ShFile>;
|
|
69
|
+
/**
|
|
70
|
+
* Creates the `ShellSourceCode` ESLint uses to run rules against `ast`.
|
|
71
|
+
* `context.languageOptions` is ESLint's already-normalized copy (see
|
|
72
|
+
* `packages/eslint-plugin/test/language-options.test.ts` and
|
|
73
|
+
* `eslint`'s `Config#finalize`, which replaces `languageOptions` with
|
|
74
|
+
* `normalizeLanguageOptions`'s return value before either `parse` or
|
|
75
|
+
* `createSourceCode` is called) — `directivePrefix` is therefore always
|
|
76
|
+
* present, but `?? 'eslint'` stays as an explicit, honest fallback for
|
|
77
|
+
* direct construction (e.g. tests that skip the `Language` normalization
|
|
78
|
+
* step), mirroring `parse()`'s `dialect` default above.
|
|
79
|
+
*/
|
|
80
|
+
createSourceCode(file: File, parseResult: OkParseResult<ShFile>, context: {
|
|
81
|
+
languageOptions: ShellLanguageOptions;
|
|
82
|
+
}): ShellSourceCode;
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=shell-language.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shell-language.d.ts","sourceRoot":"","sources":["../../src/language/shell-language.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAgB,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC3D,OAAO,KAAK,EAAE,IAAI,EAAa,QAAQ,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAE1F,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,EAGL,KAAK,8BAA8B,EACnC,KAAK,oBAAoB,EAC1B,MAAM,6BAA6B,CAAC;AAsFrC;;;;;;;;;;;;;;;GAeG;AACH,qBAAa,aAAc,YAAW,QAAQ;IAC5C,QAAQ,CAAC,QAAQ,EAAG,MAAM,CAAU;IACpC,QAAQ,CAAC,SAAS,EAAG,CAAC,CAAU;IAChC,QAAQ,CAAC,WAAW,EAAG,CAAC,CAAU;IAClC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAU;IACtC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAA2B;IACzE,QAAQ,CAAC,sBAAsB,EAAE,oBAAoB,CAAuB;IAE5E;;;;;;;;OAQG;IACH,uBAAuB,CAAC,eAAe,EAAE,oBAAoB,GAAG,IAAI;IAIpE;;;;;;OAMG;IACH,wBAAwB,CAAC,eAAe,EAAE,oBAAoB,GAAG,8BAA8B;IAI/F;;;;;;;;;;OAUG;IACH,oBAAoB,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,OAAO;IAInF;;;;;;;OAOG;IACH,KAAK,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE;QAAE,eAAe,EAAE,oBAAoB,CAAA;KAAE,GAAG,WAAW,CAAC,MAAM,CAAC;IAY1F;;;;;;;;;;OAUG;IACH,gBAAgB,CACd,IAAI,EAAE,IAAI,EACV,WAAW,EAAE,aAAa,CAAC,MAAM,CAAC,EAClC,OAAO,EAAE;QAAE,eAAe,EAAE,oBAAoB,CAAA;KAAE,GACjD,eAAe;CAOnB"}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { ShBridgeError, ShParseError, parseSync, visitorKeys as shellVisitorKeys } from 'sh-ast';
|
|
2
|
+
import { matchesSelectorClass as matchesShellSelectorClass } from './selector-classes.js';
|
|
3
|
+
import { ShellSourceCode } from './shell-source-code.js';
|
|
4
|
+
import { normalizeShellLanguageOptions, validateShellLanguageOptions, } from './shell-language-options.js';
|
|
5
|
+
/**
|
|
6
|
+
* `sh-ast`'s `visitorKeys` is deep-frozen and typed with
|
|
7
|
+
* `readonly string[]` arrays (a shared, immutable table safe to hand to any
|
|
8
|
+
* number of consumers). `@eslint/core`'s `Language.visitorKeys` types this
|
|
9
|
+
* property as mutable `Record<string, string[]>` for historical reasons,
|
|
10
|
+
* even though ESLint itself only ever reads it. Rather than cast away the
|
|
11
|
+
* mismatch, build one genuinely mutable (if still logically frozen-in-spirit)
|
|
12
|
+
* copy at module load — a one-time, small-object cost.
|
|
13
|
+
*/
|
|
14
|
+
const mutableShellVisitorKeys = Object.fromEntries(Object.entries(shellVisitorKeys).map(([type, keys]) => [type, [...keys]]));
|
|
15
|
+
/**
|
|
16
|
+
* Escapes every regex-special character in `value` so it can be embedded
|
|
17
|
+
* literally inside a `RegExp` source string. Used by
|
|
18
|
+
* {@link stripPositionPrefix} to build a filename-specific prefix pattern
|
|
19
|
+
* without the filename's own characters (e.g. `.` in `b.sh`, or any
|
|
20
|
+
* sandbox-path characters) being interpreted as regex metacharacters.
|
|
21
|
+
*/
|
|
22
|
+
function escapeRegExp(value) {
|
|
23
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Strips mvdan/sh's own `<filename>:<line>:<col>: ` prefix from a
|
|
27
|
+
* `ShParseError.message`, if present. mvdan/sh's `.message` is its own
|
|
28
|
+
* formatted error string (`sh-ast`'s `parse.ts` passes
|
|
29
|
+
* `result.parseError.message` to `ShParseError` verbatim — see that file's
|
|
30
|
+
* doc comment), which already embeds the same `filename:line:col` ESLint's
|
|
31
|
+
* own fatal-message header prints from `FileError.line`/`.column` — see
|
|
32
|
+
* issue #36. Left un-stripped, ESLint's console formatter (and any other
|
|
33
|
+
* consumer that reads `message.message`) shows the position twice and
|
|
34
|
+
* exposes the sandbox-absolute `file.path` a second time inside the message
|
|
35
|
+
* body.
|
|
36
|
+
*
|
|
37
|
+
* Only strips a prefix that names *this exact* `filename` (escaped, so the
|
|
38
|
+
* filename's own characters can't be misread as regex syntax), matched
|
|
39
|
+
* against `\d+:\d+` for line/column — mvdan/sh's message uses its own raw
|
|
40
|
+
* byte column, which is not always numerically identical to
|
|
41
|
+
* `ShParseError.column` (UTF-16-converted; see `ShParseErrorInfo.column`'s
|
|
42
|
+
* doc comment). A message that doesn't start with that exact prefix (e.g.
|
|
43
|
+
* mvdan/sh changes its format, or `filename` doesn't appear verbatim) is
|
|
44
|
+
* returned unchanged rather than mangled.
|
|
45
|
+
*/
|
|
46
|
+
function stripPositionPrefix(message, filename) {
|
|
47
|
+
const prefixPattern = new RegExp(`^${escapeRegExp(filename)}:\\d+:\\d+:\\s*`);
|
|
48
|
+
return message.replace(prefixPattern, '');
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Converts a caught `parseSync` failure into the `FileError` ESLint expects
|
|
52
|
+
* from a `NotOkParseResult`. A `ShParseError` carries mvdan/sh's *real*
|
|
53
|
+
* reported position (`.line`/`.column`, already UTF-16 — see
|
|
54
|
+
* `sh-ast`'s `ShParseError` doc comment) and is used verbatim;
|
|
55
|
+
* this is the fix for the retired spike's known flaw (`spike/language.mjs`
|
|
56
|
+
* hardcoded `{ line: 1, column: 1 }` for every parse failure, discarding the
|
|
57
|
+
* position mvdan/sh actually reported). The message text has its duplicated
|
|
58
|
+
* `filename:line:col:` prefix stripped (see {@link stripPositionPrefix}) —
|
|
59
|
+
* ESLint already renders that position from `.line`/`.column` in its own
|
|
60
|
+
* fatal-message header, so embedding it again in the message body is both
|
|
61
|
+
* noisy and, for absolute `file.path` values, an unwanted disclosure of the
|
|
62
|
+
* sandbox-absolute path (issue #36).
|
|
63
|
+
*
|
|
64
|
+
* Any other error reaching here (`ShInvalidDialectError` — unreachable in
|
|
65
|
+
* practice, since `validateLanguageOptions` rejects a bad dialect before
|
|
66
|
+
* ESLint ever calls `parse()`; or `ShBridgeInternalError` — a shim contract
|
|
67
|
+
* violation) has no real source position to report, so `1:1` is used as an
|
|
68
|
+
* explicit, honest fallback for the "no position available" case — not a
|
|
69
|
+
* stand-in for an actual parse error's position.
|
|
70
|
+
*/
|
|
71
|
+
function toFileError(error) {
|
|
72
|
+
if (error instanceof ShParseError) {
|
|
73
|
+
return {
|
|
74
|
+
message: stripPositionPrefix(error.message, error.filename),
|
|
75
|
+
line: error.line,
|
|
76
|
+
column: error.column,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
const message = error instanceof ShBridgeError || error instanceof Error ? error.message : String(error);
|
|
80
|
+
return { message, line: 1, column: 1 };
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* `ShellLanguage` — the `@eslint/core` `Language` implementation for shell
|
|
84
|
+
* scripts (design/ARCHITECTURE.md, "SourceCode and Language objects";
|
|
85
|
+
* design/PACKAGES.md, `@eslint-sh/eslint-plugin`). Registered by the plugin
|
|
86
|
+
* as `languages.sh`, so consumers select it via `language: "sh/sh"`.
|
|
87
|
+
*
|
|
88
|
+
* Implements `matchesSelectorClass` (the six launch esquery selector
|
|
89
|
+
* classes — see `./selector-classes.js`), so rules and end users can write
|
|
90
|
+
* class-based selectors (`:command`, `:loop`, `:conditional`, `:expansion`,
|
|
91
|
+
* `:quoted`, `:function`) against shell trees. The `SourceCode` directive
|
|
92
|
+
* members (`getDisableDirectives`, `getInlineConfigNodes`,
|
|
93
|
+
* `applyInlineConfig`) are implemented on `ShellSourceCode` itself; see
|
|
94
|
+
* shell-source-code.ts.
|
|
95
|
+
*
|
|
96
|
+
* @public
|
|
97
|
+
*/
|
|
98
|
+
export class ShellLanguage {
|
|
99
|
+
fileType = 'text';
|
|
100
|
+
lineStart = 1;
|
|
101
|
+
columnStart = 1;
|
|
102
|
+
nodeTypeKey = 'type';
|
|
103
|
+
visitorKeys = mutableShellVisitorKeys;
|
|
104
|
+
defaultLanguageOptions = { dialect: 'bash' };
|
|
105
|
+
/**
|
|
106
|
+
* Validates `languageOptions.dialect` (must be one of the five supported
|
|
107
|
+
* dialects, if present), `languageOptions.shellVersion` (must be a
|
|
108
|
+
* string matching the `MAJOR[.MINOR]` numeric shape, if present), and
|
|
109
|
+
* `languageOptions.directivePrefix` (must be a valid directive-comment
|
|
110
|
+
* label, if present). Unknown keys are silently ignored, per the
|
|
111
|
+
* `Language` contract. See `shell-language-options.ts`'s
|
|
112
|
+
* `validateShellLanguageOptions` for the full rationale.
|
|
113
|
+
*/
|
|
114
|
+
validateLanguageOptions(languageOptions) {
|
|
115
|
+
validateShellLanguageOptions(languageOptions);
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Resolves `dialect` to its default (`"bash"`) when unset and attaches a
|
|
119
|
+
* `toJSON()` method with a fixed key order, so that ESLint's config
|
|
120
|
+
* caching (keyed off serializing `languageOptions`) is stable regardless
|
|
121
|
+
* of the key order a user's config happened to use. See
|
|
122
|
+
* `shell-language-options.ts`'s `normalizeShellLanguageOptions`.
|
|
123
|
+
*/
|
|
124
|
+
normalizeLanguageOptions(languageOptions) {
|
|
125
|
+
return normalizeShellLanguageOptions(languageOptions);
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* `@eslint/core`'s esquery hook: lets a selector's leading `:` "class"
|
|
129
|
+
* (e.g. `:command` in `CallExpr[args.0.parts.0.value='jq']` isn't even
|
|
130
|
+
* needed there, but `:loop :command` is) match a whole family of node
|
|
131
|
+
* types instead of one literal type name. Delegates to
|
|
132
|
+
* `./selector-classes.js`'s `SELECTOR_CLASS_NODE_TYPES` table for the six
|
|
133
|
+
* launch classes (design/ARCHITECTURE.md, "SourceCode and Language
|
|
134
|
+
* objects"); `ancestry` is unused today because none of the six launch
|
|
135
|
+
* classes need ancestor context, but the parameter is accepted to match
|
|
136
|
+
* `Language.matchesSelectorClass`'s signature exactly.
|
|
137
|
+
*/
|
|
138
|
+
matchesSelectorClass(className, node, _ancestry) {
|
|
139
|
+
return matchesShellSelectorClass(className, node);
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Parses `file.body` (guaranteed to be a `string`, since `fileType` is
|
|
143
|
+
* `"text"`) via `sh-ast`'s `parseSync`, mapping the result to
|
|
144
|
+
* the `{ok: true, ast}` / `{ok: false, errors}` shape `Language.parse` is
|
|
145
|
+
* required to return. Never throws — parse failures are reported as
|
|
146
|
+
* fatal lint messages, per the `Language` contract
|
|
147
|
+
* (design/ARCHITECTURE.md, "Parse errors and dialects").
|
|
148
|
+
*/
|
|
149
|
+
parse(file, context) {
|
|
150
|
+
const text = file.body;
|
|
151
|
+
const dialect = context.languageOptions.dialect ?? 'bash';
|
|
152
|
+
try {
|
|
153
|
+
const ast = parseSync(text, { dialect, filename: file.path });
|
|
154
|
+
return { ok: true, ast };
|
|
155
|
+
}
|
|
156
|
+
catch (error) {
|
|
157
|
+
return { ok: false, errors: [toFileError(error)] };
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Creates the `ShellSourceCode` ESLint uses to run rules against `ast`.
|
|
162
|
+
* `context.languageOptions` is ESLint's already-normalized copy (see
|
|
163
|
+
* `packages/eslint-plugin/test/language-options.test.ts` and
|
|
164
|
+
* `eslint`'s `Config#finalize`, which replaces `languageOptions` with
|
|
165
|
+
* `normalizeLanguageOptions`'s return value before either `parse` or
|
|
166
|
+
* `createSourceCode` is called) — `directivePrefix` is therefore always
|
|
167
|
+
* present, but `?? 'eslint'` stays as an explicit, honest fallback for
|
|
168
|
+
* direct construction (e.g. tests that skip the `Language` normalization
|
|
169
|
+
* step), mirroring `parse()`'s `dialect` default above.
|
|
170
|
+
*/
|
|
171
|
+
createSourceCode(file, parseResult, context) {
|
|
172
|
+
return new ShellSourceCode({
|
|
173
|
+
text: file.body,
|
|
174
|
+
ast: parseResult.ast,
|
|
175
|
+
directivePrefix: context.languageOptions.directivePrefix ?? 'eslint',
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
//# sourceMappingURL=shell-language.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shell-language.js","sourceRoot":"","sources":["../../src/language/shell-language.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { TextSourceCodeBase } from '@eslint/plugin-kit';
|
|
2
|
+
import type { ShFile, ShNode, ShNodes } from 'sh-ast';
|
|
3
|
+
import type { Directive as IDirective, FileProblem, RulesConfig, SourceLocation, TraversalStep } from '@eslint/core';
|
|
4
|
+
import type { ShellLanguageOptions } from './shell-language-options.js';
|
|
5
|
+
/**
|
|
6
|
+
* `ShellSourceCode` — the `SourceCode` object ESLint uses to work with a
|
|
7
|
+
* parsed shell file (design/ARCHITECTURE.md, "SourceCode and Language
|
|
8
|
+
* objects"). Extends `@eslint/plugin-kit`'s `TextSourceCodeBase`, which
|
|
9
|
+
* already implements `getLoc`/`getRange`/`getText`/`lines` (both by reading
|
|
10
|
+
* the normalized node's own `loc`/`range` fields — see
|
|
11
|
+
* `sh-ast`'s "Normalized node shape"), `getLocFromIndex`, and
|
|
12
|
+
* `getIndexFromLoc`; this class adds the two members the base class
|
|
13
|
+
* deliberately leaves unimplemented: `traverse()` (base throws "Not
|
|
14
|
+
* implemented") and `getParent()` (base throws "Not implemented", and
|
|
15
|
+
* `getAncestors()` — already implemented generically in terms of
|
|
16
|
+
* `getParent()` — falls out for free).
|
|
17
|
+
*
|
|
18
|
+
* Directive support (`getDisableDirectives`, `getInlineConfigNodes`,
|
|
19
|
+
* `applyInlineConfig`) recognizes all four disable-comment forms
|
|
20
|
+
* (`<prefix>-disable`, `<prefix>-enable`, `<prefix>-disable-line`,
|
|
21
|
+
* `<prefix>-disable-next-line`) plus whole-file inline rule config
|
|
22
|
+
* (bare `<prefix> <rule-config>`), sourced from `Comment` nodes gathered via
|
|
23
|
+
* `sh-ast`'s structural `walk()` and parsed with
|
|
24
|
+
* `@eslint/plugin-kit`'s `ConfigCommentParser` — the same building blocks
|
|
25
|
+
* `@eslint/json`'s `JSONSourceCode` uses, generalized so the `<prefix>` is
|
|
26
|
+
* configurable (default `"eslint"`; see `ShellLanguageOptions.directivePrefix`)
|
|
27
|
+
* rather than hardcoded to `"eslint"`.
|
|
28
|
+
*
|
|
29
|
+
* Selector-class support (`matchesSelectorClass`, the six launch esquery
|
|
30
|
+
* classes) is implemented on `ShellLanguage` — see `shell-language.ts` and
|
|
31
|
+
* `selector-classes.ts`.
|
|
32
|
+
*
|
|
33
|
+
* @public
|
|
34
|
+
*/
|
|
35
|
+
export declare class ShellSourceCode extends TextSourceCodeBase<{
|
|
36
|
+
LangOptions: ShellLanguageOptions;
|
|
37
|
+
RootNode: ShFile;
|
|
38
|
+
SyntaxElementWithLoc: ShNode;
|
|
39
|
+
ConfigNode: ShNodes.ShCommentNode;
|
|
40
|
+
}> {
|
|
41
|
+
#private;
|
|
42
|
+
constructor(options: {
|
|
43
|
+
text: string;
|
|
44
|
+
ast: ShFile;
|
|
45
|
+
directivePrefix?: string;
|
|
46
|
+
});
|
|
47
|
+
/**
|
|
48
|
+
* Returns the parent of `node`, or `undefined` for the root `File` node
|
|
49
|
+
* (or any node visited before `traverse()` has run once). ESLint always
|
|
50
|
+
* calls `traverse()` before any rule can call `getParent`/`getAncestors`,
|
|
51
|
+
* so in practice the map is always populated by the time a rule observes
|
|
52
|
+
* it.
|
|
53
|
+
*/
|
|
54
|
+
getParent(node: ShNode): ShNode | undefined;
|
|
55
|
+
/**
|
|
56
|
+
* Traverses the AST pre-order, yielding a `VisitNodeStep` on node enter
|
|
57
|
+
* (`phase: 1`) and another on node exit (`phase: 2`) for every node —
|
|
58
|
+
* exactly the two-phase shape `@eslint/json`/`@eslint/css` produce, which
|
|
59
|
+
* is what lets rule listeners use `NodeType` and `NodeType:exit` esquery
|
|
60
|
+
* selectors. Children are discovered via `sh-ast`'s generated,
|
|
61
|
+
* frozen `visitorKeys` table (per node `type`) — **not** structurally
|
|
62
|
+
* (contrast `sh-ast`'s own `walk()`, which deliberately walks
|
|
63
|
+
* structurally so its correctness never depends on that table) — because
|
|
64
|
+
* ESLint's traversal contract is defined in terms of a language's
|
|
65
|
+
* declared `visitorKeys`, and this class's whole job is to honor that
|
|
66
|
+
* contract for shell ASTs.
|
|
67
|
+
*/
|
|
68
|
+
traverse(): Iterable<TraversalStep>;
|
|
69
|
+
/**
|
|
70
|
+
* Returns every `Comment` node whose text is a directive or inline-config
|
|
71
|
+
* candidate for this source code's `directivePrefix` — the pool both
|
|
72
|
+
* {@link ShellSourceCode.getDisableDirectives} and {@link ShellSourceCode.applyInlineConfig} draw from,
|
|
73
|
+
* and the array ESLint core itself asks for directly (to warn "has no
|
|
74
|
+
* effect" when `linterOptions.noInlineConfig` is set — see
|
|
75
|
+
* `eslint`'s `lib/linter/linter.js`, the `warnInlineConfig` branch).
|
|
76
|
+
*/
|
|
77
|
+
getInlineConfigNodes(): ShNodes.ShCommentNode[];
|
|
78
|
+
/**
|
|
79
|
+
* Returns the disable/enable directives found among
|
|
80
|
+
* {@link ShellSourceCode.getInlineConfigNodes}'s comments, recognizing all four forms —
|
|
81
|
+
* `<prefix>-disable`, `<prefix>-enable`, `<prefix>-disable-line`,
|
|
82
|
+
* `<prefix>-disable-next-line` — with `-- justification` capture, per the
|
|
83
|
+
* `@eslint/core` `Directive` contract. Mirrors `@eslint/json`'s
|
|
84
|
+
* `JSONSourceCode.getDisableDirectives` (verified against its published
|
|
85
|
+
* source as the named ecosystem precedent), generalized to a configurable
|
|
86
|
+
* prefix instead of the hardcoded `"eslint"` label.
|
|
87
|
+
*
|
|
88
|
+
* Unlike `JSONSourceCode` (whose block comments can span multiple
|
|
89
|
+
* physical lines, making `<prefix>-disable-line` spanning lines a real,
|
|
90
|
+
* reportable problem), mvdan/sh's `#` line comments cannot contain an
|
|
91
|
+
* embedded newline — the parser always terminates a comment at end of
|
|
92
|
+
* line — so that particular problem can never arise here and is not
|
|
93
|
+
* checked for.
|
|
94
|
+
*/
|
|
95
|
+
getDisableDirectives(): {
|
|
96
|
+
directives: IDirective[];
|
|
97
|
+
problems: FileProblem[];
|
|
98
|
+
};
|
|
99
|
+
/**
|
|
100
|
+
* Returns the whole-file inline rule configuration found among
|
|
101
|
+
* {@link ShellSourceCode.getInlineConfigNodes}'s comments — a bare `<prefix> <rule-config>`
|
|
102
|
+
* comment (e.g. `# eslint sh/rule-a: "error"`), parsed with
|
|
103
|
+
* `ConfigCommentParser.parseJSONLikeConfig`. Mirrors `@eslint/json`'s
|
|
104
|
+
* `JSONSourceCode.applyInlineConfig`. This is the surface
|
|
105
|
+
* `linterOptions.noInlineConfig: true` suppresses (ESLint core skips
|
|
106
|
+
* calling this method — and `getDisableDirectives`, together, since both
|
|
107
|
+
* are gated by the same flag in `eslint`'s own `linter.js` — and instead
|
|
108
|
+
* calls `getInlineConfigNodes` to emit a "has no effect" warning per
|
|
109
|
+
* comment; see design/ARCHITECTURE.md, "SourceCode and Language objects").
|
|
110
|
+
*/
|
|
111
|
+
applyInlineConfig(): {
|
|
112
|
+
configs: {
|
|
113
|
+
config: {
|
|
114
|
+
rules: RulesConfig;
|
|
115
|
+
};
|
|
116
|
+
loc: SourceLocation;
|
|
117
|
+
}[];
|
|
118
|
+
problems: FileProblem[];
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
//# sourceMappingURL=shell-source-code.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shell-source-code.d.ts","sourceRoot":"","sources":["../../src/language/shell-source-code.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,kBAAkB,EAEnB,MAAM,oBAAoB,CAAC;AAE5B,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AACtD,OAAO,KAAK,EACV,SAAS,IAAI,UAAU,EAEvB,WAAW,EACX,WAAW,EACX,cAAc,EACd,aAAa,EACd,MAAM,cAAc,CAAC;AACtB,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AAuExE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,qBAAa,eAAgB,SAAQ,kBAAkB,CAAC;IACtD,WAAW,EAAE,oBAAoB,CAAC;IAClC,QAAQ,EAAE,MAAM,CAAC;IACjB,oBAAoB,EAAE,MAAM,CAAC;IAC7B,UAAU,EAAE,OAAO,CAAC,aAAa,CAAC;CACnC,CAAC;;gBAwDY,OAAO,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,eAAe,CAAC,EAAE,MAAM,CAAA;KAAE;IAU5E;;;;;;OAMG;IACH,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAI3C;;;;;;;;;;;;OAYG;IACH,QAAQ,IAAI,QAAQ,CAAC,aAAa,CAAC;IAoEnC;;;;;;;OAOG;IACH,oBAAoB,IAAI,OAAO,CAAC,aAAa,EAAE;IAO/C;;;;;;;;;;;;;;;;OAgBG;IACH,oBAAoB,IAAI;QAAE,UAAU,EAAE,UAAU,EAAE,CAAC;QAAC,QAAQ,EAAE,WAAW,EAAE,CAAA;KAAE;IA0B7E;;;;;;;;;;;OAWG;IACH,iBAAiB,IAAI;QACnB,OAAO,EAAE;YAAE,MAAM,EAAE;gBAAE,KAAK,EAAE,WAAW,CAAA;aAAE,CAAC;YAAC,GAAG,EAAE,cAAc,CAAA;SAAE,EAAE,CAAC;QACnE,QAAQ,EAAE,WAAW,EAAE,CAAC;KACzB;CAqBF"}
|