@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
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Mike North
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# @eslint-sh/eslint-plugin
|
|
2
|
+
|
|
3
|
+
Shell script parsing and syntax-error reporting for ESLint, with custom-rule authoring over a
|
|
4
|
+
typed shell AST. Provides the `ShellLanguage` / `ShellSourceCode` core that lets ESLint parse and
|
|
5
|
+
traverse `.sh`/`.bash` files, backed by `sh-ast`.
|
|
6
|
+
|
|
7
|
+
**Roadmap:** built-in lint rules and shared configs are planned but not implemented yet.
|
|
8
|
+
|
|
9
|
+
## `languageOptions` reference
|
|
10
|
+
|
|
11
|
+
| Option | Allowed values | Default | Effect |
|
|
12
|
+
| ----------------- | ----------------------------------------------------------------------- | ---------- | --------------------------------------------------------------------------------------------------------------------------------- |
|
|
13
|
+
| `dialect` | `"bash"`, `"posix"`, `"mksh"`, `"bats"`, `"zsh"` | `"bash"` | Selects which shell grammar the parser accepts; parsing a construct outside the chosen dialect produces a parse error. |
|
|
14
|
+
| `shellVersion` | A numeric `MAJOR` or `MAJOR.MINOR` string, e.g. `"5"` or `"3.2"` | unset | Reserved for the rule layer (e.g. a future bash-3.2 compatibility check); has no effect on parsing today since no rules ship yet. |
|
|
15
|
+
| `directivePrefix` | Lowercase, hyphen-separated words, e.g. `"eslint"` or `"shellcheck-sh"` | `"eslint"` | The prefix directive/inline-config comments must start with — e.g. the default recognizes `# eslint-disable-next-line ...`. |
|
|
16
|
+
|
|
17
|
+
This release reports shell syntax (parse) errors only — no lint rules ship yet, so a "0 problems"
|
|
18
|
+
result means the script parsed without a syntax error, not that it is problem-free.
|
|
19
|
+
|
|
20
|
+
```js
|
|
21
|
+
// eslint.config.mjs
|
|
22
|
+
import { defineConfig } from 'eslint/config';
|
|
23
|
+
import sh from '@eslint-sh/eslint-plugin'; // or the umbrella: import sh from "eslint-sh"
|
|
24
|
+
|
|
25
|
+
export default defineConfig([
|
|
26
|
+
{
|
|
27
|
+
files: ['**/*.sh', '**/*.bash'],
|
|
28
|
+
plugins: { sh },
|
|
29
|
+
language: 'sh/sh',
|
|
30
|
+
languageOptions: { dialect: 'bash', shellVersion: '3.2' },
|
|
31
|
+
},
|
|
32
|
+
]);
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Status
|
|
36
|
+
|
|
37
|
+
`ShellLanguage`/`ShellSourceCode` core, plus the six launch esquery selector classes — see the
|
|
38
|
+
[selector cookbook](./docs/selectors.md) (shipped with this package) for ready-made selector
|
|
39
|
+
recipes. Rules and shared `configs` are not implemented yet — `plugin.rules` is intentionally
|
|
40
|
+
empty here. Disable directives / inline config are implemented and live: all four
|
|
41
|
+
`# eslint-disable*` comment forms, unused-directive detection, and `linterOptions.noInlineConfig`
|
|
42
|
+
interplay work through ESLint's native machinery. No lint rules ship with this package yet, so the
|
|
43
|
+
rule id below is illustrative — but once you register a custom rule of your own, the directives
|
|
44
|
+
suppress it: for a rule you've configured as `sh/your-rule`,
|
|
45
|
+
`# eslint-disable-next-line sh/your-rule -- justification` suppresses it on the following line.
|
|
46
|
+
|
|
47
|
+
## Public API
|
|
48
|
+
|
|
49
|
+
- `plugin` (default export) — `{ meta: { name, version, namespace: "sh" }, languages: { sh:
|
|
50
|
+
ShellLanguage }, rules: {} }`. `meta.version` always matches this package's `package.json`.
|
|
51
|
+
- `ShellLanguage` — the `@eslint/core` `Language` implementation: `fileType: "text"`,
|
|
52
|
+
`lineStart`/`columnStart: 1`, `nodeTypeKey: "type"`, the bridge's generated `visitorKeys`,
|
|
53
|
+
`validateLanguageOptions`/`normalizeLanguageOptions` for the
|
|
54
|
+
`{dialect, shellVersion, directivePrefix}` schema, `parse`/`createSourceCode`, and
|
|
55
|
+
`matchesSelectorClass` for the six launch esquery selector classes (`:command`, `:loop`,
|
|
56
|
+
`:conditional`, `:expansion`, `:quoted`, `:function` — see the
|
|
57
|
+
[selector cookbook](./docs/selectors.md)).
|
|
58
|
+
- `ShellSourceCode` — extends `@eslint/plugin-kit`'s `TextSourceCodeBase`; implements `traverse()`
|
|
59
|
+
(driven by the bridge's `visitorKeys`, yielding two-phase `VisitNodeStep`s), `getParent()`, and
|
|
60
|
+
the disable-directive surface (`getDisableDirectives()`, `getInlineConfigNodes()`,
|
|
61
|
+
`applyInlineConfig()`).
|
|
62
|
+
- `ShellLanguageOptions` / `NormalizedShellLanguageOptions` / `ShellLanguageOptionsJSON` — the
|
|
63
|
+
`languageOptions` types (`dialect`, `shellVersion`, `directivePrefix`). `directivePrefix` is the
|
|
64
|
+
prefix directive/inline-config comments must start with — e.g. the default `"eslint"` recognizes
|
|
65
|
+
`# eslint-disable-next-line ...`; consumers who already overload `# eslint-*` comments for
|
|
66
|
+
something else can configure a different prefix (e.g. `directivePrefix: 'shellcheck-sh'`).
|
|
67
|
+
|
|
68
|
+
Parse failures surface as fatal lint messages at mvdan/sh's real reported position (never a 1:1
|
|
69
|
+
placeholder) — see `ShParseError` in `sh-ast`.
|
|
70
|
+
|
|
71
|
+
## Writing your own rules
|
|
72
|
+
|
|
73
|
+
Want to write a custom rule against the shell AST? See the [custom-rule
|
|
74
|
+
tutorial](./docs/custom-rule-tutorial.md) for a complete, CI-tested walkthrough: a selector from
|
|
75
|
+
the cookbook, a full `{ meta, create(context) }` rule module with `context.report`, and the
|
|
76
|
+
flat-config wiring to run it. `docs/` ships with this package (see `package.json#files`), so the
|
|
77
|
+
tutorial and its example files (`docs/examples/`) are available in every installed copy — not just
|
|
78
|
+
on GitHub.
|
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
import type { Directive } from '@eslint/core';
|
|
2
|
+
import type { File as File_2 } from '@eslint/core';
|
|
3
|
+
import type { FileProblem } from '@eslint/core';
|
|
4
|
+
import type { Language } from '@eslint/core';
|
|
5
|
+
import type { OkParseResult } from '@eslint/core';
|
|
6
|
+
import type { ParseResult } from '@eslint/core';
|
|
7
|
+
import type { RulesConfig } from '@eslint/core';
|
|
8
|
+
import type { ShellDialect } from 'sh-ast';
|
|
9
|
+
import type { ShFile } from 'sh-ast';
|
|
10
|
+
import type { ShNode } from 'sh-ast';
|
|
11
|
+
import type { ShNodes } from 'sh-ast';
|
|
12
|
+
import type { SourceLocation } from '@eslint/core';
|
|
13
|
+
import { TextSourceCodeBase } from '@eslint/plugin-kit';
|
|
14
|
+
import type { TraversalStep } from '@eslint/core';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* The `@eslint-sh/eslint-plugin` plugin object (design/PACKAGES.md,
|
|
18
|
+
* `@eslint-sh/eslint-plugin`): `languages.sh` provides `ShellLanguage` /
|
|
19
|
+
* `ShellSourceCode` (design/ARCHITECTURE.md, "SourceCode and Language
|
|
20
|
+
* objects"), selected in a flat config via `language: "sh/sh"`
|
|
21
|
+
* (`namespace: "sh"`). `meta.version` is read from this package's own
|
|
22
|
+
* `package.json` at runtime — never hardcoded (see `version.ts`).
|
|
23
|
+
*
|
|
24
|
+
* `rules` is empty at this milestone (M2 — `ShellLanguage`/`ShellSourceCode`
|
|
25
|
+
* core, plus the six launch esquery selector classes); rules and shared
|
|
26
|
+
* `configs` are Milestone 3 (design/MILESTONES.md). Adding them later is
|
|
27
|
+
* additive, not a breaking change to this object's shape.
|
|
28
|
+
*
|
|
29
|
+
* Re-exported, unchanged, as the default export of the `eslint-sh` umbrella
|
|
30
|
+
* package (design/PACKAGES.md's Naming decision): importing this package or
|
|
31
|
+
* the umbrella package both yield the same plugin object.
|
|
32
|
+
*/
|
|
33
|
+
declare const _default: {
|
|
34
|
+
meta: {
|
|
35
|
+
name: string;
|
|
36
|
+
version: string;
|
|
37
|
+
namespace: string;
|
|
38
|
+
};
|
|
39
|
+
languages: {
|
|
40
|
+
sh: ShellLanguage;
|
|
41
|
+
};
|
|
42
|
+
rules: {};
|
|
43
|
+
};
|
|
44
|
+
export default _default;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* The result of {@link ShellLanguage.normalizeLanguageOptions}: `dialect` and
|
|
48
|
+
* `directivePrefix` are always resolved to concrete values (defaulted when
|
|
49
|
+
* the caller didn't specify them), and `toJSON()` provides the "custom
|
|
50
|
+
* serialization" ESLint's languages guide recommends
|
|
51
|
+
* (https://eslint.org/docs/latest/extend/languages, "normalizeLanguageOptions")
|
|
52
|
+
* so that config caching keyed off `JSON.stringify(languageOptions)` is
|
|
53
|
+
* stable irrespective of the input object's own key order — `JSON.stringify`
|
|
54
|
+
* calls `toJSON()` automatically when present.
|
|
55
|
+
*
|
|
56
|
+
* @public
|
|
57
|
+
*/
|
|
58
|
+
export declare interface NormalizedShellLanguageOptions extends Record<string, unknown> {
|
|
59
|
+
readonly dialect: ShellDialect;
|
|
60
|
+
readonly directivePrefix: string;
|
|
61
|
+
readonly shellVersion?: string;
|
|
62
|
+
toJSON(): ShellLanguageOptionsJSON;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* `ShellLanguage` — the `@eslint/core` `Language` implementation for shell
|
|
67
|
+
* scripts (design/ARCHITECTURE.md, "SourceCode and Language objects";
|
|
68
|
+
* design/PACKAGES.md, `@eslint-sh/eslint-plugin`). Registered by the plugin
|
|
69
|
+
* as `languages.sh`, so consumers select it via `language: "sh/sh"`.
|
|
70
|
+
*
|
|
71
|
+
* Implements `matchesSelectorClass` (the six launch esquery selector
|
|
72
|
+
* classes — see `./selector-classes.js`), so rules and end users can write
|
|
73
|
+
* class-based selectors (`:command`, `:loop`, `:conditional`, `:expansion`,
|
|
74
|
+
* `:quoted`, `:function`) against shell trees. The `SourceCode` directive
|
|
75
|
+
* members (`getDisableDirectives`, `getInlineConfigNodes`,
|
|
76
|
+
* `applyInlineConfig`) are implemented on `ShellSourceCode` itself; see
|
|
77
|
+
* shell-source-code.ts.
|
|
78
|
+
*
|
|
79
|
+
* @public
|
|
80
|
+
*/
|
|
81
|
+
export declare class ShellLanguage implements Language {
|
|
82
|
+
readonly fileType: "text";
|
|
83
|
+
readonly lineStart: 1;
|
|
84
|
+
readonly columnStart: 1;
|
|
85
|
+
readonly nodeTypeKey: string;
|
|
86
|
+
readonly visitorKeys: Record<string, string[]>;
|
|
87
|
+
readonly defaultLanguageOptions: ShellLanguageOptions;
|
|
88
|
+
/**
|
|
89
|
+
* Validates `languageOptions.dialect` (must be one of the five supported
|
|
90
|
+
* dialects, if present), `languageOptions.shellVersion` (must be a
|
|
91
|
+
* string matching the `MAJOR[.MINOR]` numeric shape, if present), and
|
|
92
|
+
* `languageOptions.directivePrefix` (must be a valid directive-comment
|
|
93
|
+
* label, if present). Unknown keys are silently ignored, per the
|
|
94
|
+
* `Language` contract. See `shell-language-options.ts`'s
|
|
95
|
+
* `validateShellLanguageOptions` for the full rationale.
|
|
96
|
+
*/
|
|
97
|
+
validateLanguageOptions(languageOptions: ShellLanguageOptions): void;
|
|
98
|
+
/**
|
|
99
|
+
* Resolves `dialect` to its default (`"bash"`) when unset and attaches a
|
|
100
|
+
* `toJSON()` method with a fixed key order, so that ESLint's config
|
|
101
|
+
* caching (keyed off serializing `languageOptions`) is stable regardless
|
|
102
|
+
* of the key order a user's config happened to use. See
|
|
103
|
+
* `shell-language-options.ts`'s `normalizeShellLanguageOptions`.
|
|
104
|
+
*/
|
|
105
|
+
normalizeLanguageOptions(languageOptions: ShellLanguageOptions): NormalizedShellLanguageOptions;
|
|
106
|
+
/**
|
|
107
|
+
* `@eslint/core`'s esquery hook: lets a selector's leading `:` "class"
|
|
108
|
+
* (e.g. `:command` in `CallExpr[args.0.parts.0.value='jq']` isn't even
|
|
109
|
+
* needed there, but `:loop :command` is) match a whole family of node
|
|
110
|
+
* types instead of one literal type name. Delegates to
|
|
111
|
+
* `./selector-classes.js`'s `SELECTOR_CLASS_NODE_TYPES` table for the six
|
|
112
|
+
* launch classes (design/ARCHITECTURE.md, "SourceCode and Language
|
|
113
|
+
* objects"); `ancestry` is unused today because none of the six launch
|
|
114
|
+
* classes need ancestor context, but the parameter is accepted to match
|
|
115
|
+
* `Language.matchesSelectorClass`'s signature exactly.
|
|
116
|
+
*/
|
|
117
|
+
matchesSelectorClass(className: string, node: ShNode, _ancestry: ShNode[]): boolean;
|
|
118
|
+
/**
|
|
119
|
+
* Parses `file.body` (guaranteed to be a `string`, since `fileType` is
|
|
120
|
+
* `"text"`) via `sh-ast`'s `parseSync`, mapping the result to
|
|
121
|
+
* the `{ok: true, ast}` / `{ok: false, errors}` shape `Language.parse` is
|
|
122
|
+
* required to return. Never throws — parse failures are reported as
|
|
123
|
+
* fatal lint messages, per the `Language` contract
|
|
124
|
+
* (design/ARCHITECTURE.md, "Parse errors and dialects").
|
|
125
|
+
*/
|
|
126
|
+
parse(file: File_2, context: {
|
|
127
|
+
languageOptions: ShellLanguageOptions;
|
|
128
|
+
}): ParseResult<ShFile>;
|
|
129
|
+
/**
|
|
130
|
+
* Creates the `ShellSourceCode` ESLint uses to run rules against `ast`.
|
|
131
|
+
* `context.languageOptions` is ESLint's already-normalized copy (see
|
|
132
|
+
* `packages/eslint-plugin/test/language-options.test.ts` and
|
|
133
|
+
* `eslint`'s `Config#finalize`, which replaces `languageOptions` with
|
|
134
|
+
* `normalizeLanguageOptions`'s return value before either `parse` or
|
|
135
|
+
* `createSourceCode` is called) — `directivePrefix` is therefore always
|
|
136
|
+
* present, but `?? 'eslint'` stays as an explicit, honest fallback for
|
|
137
|
+
* direct construction (e.g. tests that skip the `Language` normalization
|
|
138
|
+
* step), mirroring `parse()`'s `dialect` default above.
|
|
139
|
+
*/
|
|
140
|
+
createSourceCode(file: File_2, parseResult: OkParseResult<ShFile>, context: {
|
|
141
|
+
languageOptions: ShellLanguageOptions;
|
|
142
|
+
}): ShellSourceCode;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* `languageOptions` accepted by {@link ShellLanguage} (design/PACKAGES.md's
|
|
147
|
+
* `languageOptions` schema): `dialect` selects mvdan/sh's `LangVariant` by
|
|
148
|
+
* string name (default `"bash"`); `shellVersion` is consumed by the
|
|
149
|
+
* rule layer (e.g. the bash-3.2 compatibility pack), never by the parser
|
|
150
|
+
* itself (see design/ARCHITECTURE.md, "A verified nuance worth stating
|
|
151
|
+
* loudly").
|
|
152
|
+
*
|
|
153
|
+
* Extends `Record<string, unknown>` (rather than a bare interface) so this
|
|
154
|
+
* type satisfies the `@eslint/core` package's `LanguageOptions` bound
|
|
155
|
+
* (itself `Record<string, unknown>`) wherever it is used as a
|
|
156
|
+
* `Language`/`SourceCode` type parameter — ESLint's own contract for
|
|
157
|
+
* language options is that they are arbitrary user-supplied records, not a
|
|
158
|
+
* closed shape.
|
|
159
|
+
*
|
|
160
|
+
* @public
|
|
161
|
+
*/
|
|
162
|
+
export declare interface ShellLanguageOptions extends Record<string, unknown> {
|
|
163
|
+
readonly dialect?: ShellDialect;
|
|
164
|
+
readonly shellVersion?: string;
|
|
165
|
+
/**
|
|
166
|
+
* The prefix directive/inline-config comments must start with — e.g. the
|
|
167
|
+
* default `"eslint"` recognizes `# eslint-disable-next-line ...` and
|
|
168
|
+
* `# eslint <rule-config>`. Consumers who embed shell-linting inside a
|
|
169
|
+
* larger toolchain that already overloads `#eslint-*` comments for
|
|
170
|
+
* something else can configure a different prefix (design/ARCHITECTURE.md,
|
|
171
|
+
* "SourceCode and Language objects").
|
|
172
|
+
* @defaultValue `"eslint"`
|
|
173
|
+
*/
|
|
174
|
+
readonly directivePrefix?: string;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* The plain-data shape {@link NormalizedShellLanguageOptions.toJSON} returns:
|
|
179
|
+
* `dialect` and `directivePrefix` always present (both defaulted), `shellVersion`
|
|
180
|
+
* present only when the caller supplied one. Keys are always emitted in this
|
|
181
|
+
* fixed order so that two logically-identical inputs serialize identically
|
|
182
|
+
* regardless of the key order the caller happened to use — the property this
|
|
183
|
+
* type exists to guarantee for cache-key stability (see
|
|
184
|
+
* {@link NormalizedShellLanguageOptions}). `directivePrefix` is included
|
|
185
|
+
* unconditionally (like `dialect`, unlike `shellVersion`) because — like
|
|
186
|
+
* `dialect` and unlike `shellVersion` — it always has a concrete effect on
|
|
187
|
+
* linting results (which comments the `SourceCode` recognizes as directives)
|
|
188
|
+
* whether or not the caller supplied one, so two configs that differ only in
|
|
189
|
+
* an *implicit* default vs. an *explicit* `directivePrefix: "eslint"` must
|
|
190
|
+
* still cache-key identically.
|
|
191
|
+
*
|
|
192
|
+
* @public
|
|
193
|
+
*/
|
|
194
|
+
export declare interface ShellLanguageOptionsJSON {
|
|
195
|
+
readonly dialect: ShellDialect;
|
|
196
|
+
readonly directivePrefix: string;
|
|
197
|
+
readonly shellVersion?: string;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* `ShellSourceCode` — the `SourceCode` object ESLint uses to work with a
|
|
202
|
+
* parsed shell file (design/ARCHITECTURE.md, "SourceCode and Language
|
|
203
|
+
* objects"). Extends `@eslint/plugin-kit`'s `TextSourceCodeBase`, which
|
|
204
|
+
* already implements `getLoc`/`getRange`/`getText`/`lines` (both by reading
|
|
205
|
+
* the normalized node's own `loc`/`range` fields — see
|
|
206
|
+
* `sh-ast`'s "Normalized node shape"), `getLocFromIndex`, and
|
|
207
|
+
* `getIndexFromLoc`; this class adds the two members the base class
|
|
208
|
+
* deliberately leaves unimplemented: `traverse()` (base throws "Not
|
|
209
|
+
* implemented") and `getParent()` (base throws "Not implemented", and
|
|
210
|
+
* `getAncestors()` — already implemented generically in terms of
|
|
211
|
+
* `getParent()` — falls out for free).
|
|
212
|
+
*
|
|
213
|
+
* Directive support (`getDisableDirectives`, `getInlineConfigNodes`,
|
|
214
|
+
* `applyInlineConfig`) recognizes all four disable-comment forms
|
|
215
|
+
* (`<prefix>-disable`, `<prefix>-enable`, `<prefix>-disable-line`,
|
|
216
|
+
* `<prefix>-disable-next-line`) plus whole-file inline rule config
|
|
217
|
+
* (bare `<prefix> <rule-config>`), sourced from `Comment` nodes gathered via
|
|
218
|
+
* `sh-ast`'s structural `walk()` and parsed with
|
|
219
|
+
* `@eslint/plugin-kit`'s `ConfigCommentParser` — the same building blocks
|
|
220
|
+
* `@eslint/json`'s `JSONSourceCode` uses, generalized so the `<prefix>` is
|
|
221
|
+
* configurable (default `"eslint"`; see `ShellLanguageOptions.directivePrefix`)
|
|
222
|
+
* rather than hardcoded to `"eslint"`.
|
|
223
|
+
*
|
|
224
|
+
* Selector-class support (`matchesSelectorClass`, the six launch esquery
|
|
225
|
+
* classes) is implemented on `ShellLanguage` — see `shell-language.ts` and
|
|
226
|
+
* `selector-classes.ts`.
|
|
227
|
+
*
|
|
228
|
+
* @public
|
|
229
|
+
*/
|
|
230
|
+
export declare class ShellSourceCode extends TextSourceCodeBase<{
|
|
231
|
+
LangOptions: ShellLanguageOptions;
|
|
232
|
+
RootNode: ShFile;
|
|
233
|
+
SyntaxElementWithLoc: ShNode;
|
|
234
|
+
ConfigNode: ShNodes.ShCommentNode;
|
|
235
|
+
}> {
|
|
236
|
+
#private;
|
|
237
|
+
constructor(options: {
|
|
238
|
+
text: string;
|
|
239
|
+
ast: ShFile;
|
|
240
|
+
directivePrefix?: string;
|
|
241
|
+
});
|
|
242
|
+
/**
|
|
243
|
+
* Returns the parent of `node`, or `undefined` for the root `File` node
|
|
244
|
+
* (or any node visited before `traverse()` has run once). ESLint always
|
|
245
|
+
* calls `traverse()` before any rule can call `getParent`/`getAncestors`,
|
|
246
|
+
* so in practice the map is always populated by the time a rule observes
|
|
247
|
+
* it.
|
|
248
|
+
*/
|
|
249
|
+
getParent(node: ShNode): ShNode | undefined;
|
|
250
|
+
/**
|
|
251
|
+
* Traverses the AST pre-order, yielding a `VisitNodeStep` on node enter
|
|
252
|
+
* (`phase: 1`) and another on node exit (`phase: 2`) for every node —
|
|
253
|
+
* exactly the two-phase shape `@eslint/json`/`@eslint/css` produce, which
|
|
254
|
+
* is what lets rule listeners use `NodeType` and `NodeType:exit` esquery
|
|
255
|
+
* selectors. Children are discovered via `sh-ast`'s generated,
|
|
256
|
+
* frozen `visitorKeys` table (per node `type`) — **not** structurally
|
|
257
|
+
* (contrast `sh-ast`'s own `walk()`, which deliberately walks
|
|
258
|
+
* structurally so its correctness never depends on that table) — because
|
|
259
|
+
* ESLint's traversal contract is defined in terms of a language's
|
|
260
|
+
* declared `visitorKeys`, and this class's whole job is to honor that
|
|
261
|
+
* contract for shell ASTs.
|
|
262
|
+
*/
|
|
263
|
+
traverse(): Iterable<TraversalStep>;
|
|
264
|
+
/**
|
|
265
|
+
* Returns every `Comment` node whose text is a directive or inline-config
|
|
266
|
+
* candidate for this source code's `directivePrefix` — the pool both
|
|
267
|
+
* {@link ShellSourceCode.getDisableDirectives} and {@link ShellSourceCode.applyInlineConfig} draw from,
|
|
268
|
+
* and the array ESLint core itself asks for directly (to warn "has no
|
|
269
|
+
* effect" when `linterOptions.noInlineConfig` is set — see
|
|
270
|
+
* `eslint`'s `lib/linter/linter.js`, the `warnInlineConfig` branch).
|
|
271
|
+
*/
|
|
272
|
+
getInlineConfigNodes(): ShNodes.ShCommentNode[];
|
|
273
|
+
/**
|
|
274
|
+
* Returns the disable/enable directives found among
|
|
275
|
+
* {@link ShellSourceCode.getInlineConfigNodes}'s comments, recognizing all four forms —
|
|
276
|
+
* `<prefix>-disable`, `<prefix>-enable`, `<prefix>-disable-line`,
|
|
277
|
+
* `<prefix>-disable-next-line` — with `-- justification` capture, per the
|
|
278
|
+
* `@eslint/core` `Directive` contract. Mirrors `@eslint/json`'s
|
|
279
|
+
* `JSONSourceCode.getDisableDirectives` (verified against its published
|
|
280
|
+
* source as the named ecosystem precedent), generalized to a configurable
|
|
281
|
+
* prefix instead of the hardcoded `"eslint"` label.
|
|
282
|
+
*
|
|
283
|
+
* Unlike `JSONSourceCode` (whose block comments can span multiple
|
|
284
|
+
* physical lines, making `<prefix>-disable-line` spanning lines a real,
|
|
285
|
+
* reportable problem), mvdan/sh's `#` line comments cannot contain an
|
|
286
|
+
* embedded newline — the parser always terminates a comment at end of
|
|
287
|
+
* line — so that particular problem can never arise here and is not
|
|
288
|
+
* checked for.
|
|
289
|
+
*/
|
|
290
|
+
getDisableDirectives(): {
|
|
291
|
+
directives: Directive[];
|
|
292
|
+
problems: FileProblem[];
|
|
293
|
+
};
|
|
294
|
+
/**
|
|
295
|
+
* Returns the whole-file inline rule configuration found among
|
|
296
|
+
* {@link ShellSourceCode.getInlineConfigNodes}'s comments — a bare `<prefix> <rule-config>`
|
|
297
|
+
* comment (e.g. `# eslint sh/rule-a: "error"`), parsed with
|
|
298
|
+
* `ConfigCommentParser.parseJSONLikeConfig`. Mirrors `@eslint/json`'s
|
|
299
|
+
* `JSONSourceCode.applyInlineConfig`. This is the surface
|
|
300
|
+
* `linterOptions.noInlineConfig: true` suppresses (ESLint core skips
|
|
301
|
+
* calling this method — and `getDisableDirectives`, together, since both
|
|
302
|
+
* are gated by the same flag in `eslint`'s own `linter.js` — and instead
|
|
303
|
+
* calls `getInlineConfigNodes` to emit a "has no effect" warning per
|
|
304
|
+
* comment; see design/ARCHITECTURE.md, "SourceCode and Language objects").
|
|
305
|
+
*/
|
|
306
|
+
applyInlineConfig(): {
|
|
307
|
+
configs: {
|
|
308
|
+
config: {
|
|
309
|
+
rules: RulesConfig;
|
|
310
|
+
};
|
|
311
|
+
loc: SourceLocation;
|
|
312
|
+
}[];
|
|
313
|
+
problems: FileProblem[];
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
export { }
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { ShellLanguage } from './language/shell-language.js';
|
|
2
|
+
export { ShellLanguage } from './language/shell-language.js';
|
|
3
|
+
export { ShellSourceCode } from './language/shell-source-code.js';
|
|
4
|
+
export type { NormalizedShellLanguageOptions, ShellLanguageOptions, ShellLanguageOptionsJSON, } from './language/shell-language-options.js';
|
|
5
|
+
/**
|
|
6
|
+
* The `@eslint-sh/eslint-plugin` plugin object (design/PACKAGES.md,
|
|
7
|
+
* `@eslint-sh/eslint-plugin`): `languages.sh` provides `ShellLanguage` /
|
|
8
|
+
* `ShellSourceCode` (design/ARCHITECTURE.md, "SourceCode and Language
|
|
9
|
+
* objects"), selected in a flat config via `language: "sh/sh"`
|
|
10
|
+
* (`namespace: "sh"`). `meta.version` is read from this package's own
|
|
11
|
+
* `package.json` at runtime — never hardcoded (see `version.ts`).
|
|
12
|
+
*
|
|
13
|
+
* `rules` is empty at this milestone (M2 — `ShellLanguage`/`ShellSourceCode`
|
|
14
|
+
* core, plus the six launch esquery selector classes); rules and shared
|
|
15
|
+
* `configs` are Milestone 3 (design/MILESTONES.md). Adding them later is
|
|
16
|
+
* additive, not a breaking change to this object's shape.
|
|
17
|
+
*
|
|
18
|
+
* Re-exported, unchanged, as the default export of the `eslint-sh` umbrella
|
|
19
|
+
* package (design/PACKAGES.md's Naming decision): importing this package or
|
|
20
|
+
* the umbrella package both yield the same plugin object.
|
|
21
|
+
*/
|
|
22
|
+
declare const _default: {
|
|
23
|
+
meta: {
|
|
24
|
+
name: string;
|
|
25
|
+
version: string;
|
|
26
|
+
namespace: string;
|
|
27
|
+
};
|
|
28
|
+
languages: {
|
|
29
|
+
sh: ShellLanguage;
|
|
30
|
+
};
|
|
31
|
+
rules: {};
|
|
32
|
+
};
|
|
33
|
+
export default _default;
|
|
34
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAG7D,OAAO,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAC7D,OAAO,EAAE,eAAe,EAAE,MAAM,iCAAiC,CAAC;AAClE,YAAY,EACV,8BAA8B,EAC9B,oBAAoB,EACpB,wBAAwB,GACzB,MAAM,sCAAsC,CAAC;AAE9C;;;;;;;;;;;;;;;;GAgBG;;;;;;;;;;;;AACH,wBAUmB"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { ShellLanguage } from './language/shell-language.js';
|
|
2
|
+
import { readPackageVersion } from './version.js';
|
|
3
|
+
export { ShellLanguage } from './language/shell-language.js';
|
|
4
|
+
export { ShellSourceCode } from './language/shell-source-code.js';
|
|
5
|
+
/**
|
|
6
|
+
* The `@eslint-sh/eslint-plugin` plugin object (design/PACKAGES.md,
|
|
7
|
+
* `@eslint-sh/eslint-plugin`): `languages.sh` provides `ShellLanguage` /
|
|
8
|
+
* `ShellSourceCode` (design/ARCHITECTURE.md, "SourceCode and Language
|
|
9
|
+
* objects"), selected in a flat config via `language: "sh/sh"`
|
|
10
|
+
* (`namespace: "sh"`). `meta.version` is read from this package's own
|
|
11
|
+
* `package.json` at runtime — never hardcoded (see `version.ts`).
|
|
12
|
+
*
|
|
13
|
+
* `rules` is empty at this milestone (M2 — `ShellLanguage`/`ShellSourceCode`
|
|
14
|
+
* core, plus the six launch esquery selector classes); rules and shared
|
|
15
|
+
* `configs` are Milestone 3 (design/MILESTONES.md). Adding them later is
|
|
16
|
+
* additive, not a breaking change to this object's shape.
|
|
17
|
+
*
|
|
18
|
+
* Re-exported, unchanged, as the default export of the `eslint-sh` umbrella
|
|
19
|
+
* package (design/PACKAGES.md's Naming decision): importing this package or
|
|
20
|
+
* the umbrella package both yield the same plugin object.
|
|
21
|
+
*/
|
|
22
|
+
export default {
|
|
23
|
+
meta: {
|
|
24
|
+
name: '@eslint-sh/eslint-plugin',
|
|
25
|
+
version: readPackageVersion(),
|
|
26
|
+
namespace: 'sh',
|
|
27
|
+
},
|
|
28
|
+
languages: {
|
|
29
|
+
sh: new ShellLanguage(),
|
|
30
|
+
},
|
|
31
|
+
rules: {},
|
|
32
|
+
};
|
|
33
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAC7D,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAElD,OAAO,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAC7D,OAAO,EAAE,eAAe,EAAE,MAAM,iCAAiC,CAAC;AAOlE;;;;;;;;;;;;;;;;GAgBG;AACH,eAAe;IACb,IAAI,EAAE;QACJ,IAAI,EAAE,0BAA0B;QAChC,OAAO,EAAE,kBAAkB,EAAE;QAC7B,SAAS,EAAE,IAAI;KAChB;IACD,SAAS,EAAE;QACT,EAAE,EAAE,IAAI,aAAa,EAAE;KACxB;IACD,KAAK,EAAE,EAAE;CACO,CAAC"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { ShNode } from 'sh-ast';
|
|
2
|
+
/**
|
|
3
|
+
* The six launch esquery selector classes `ShellLanguage.matchesSelectorClass`
|
|
4
|
+
* implements (design/ARCHITECTURE.md, "SourceCode and Language objects";
|
|
5
|
+
* the issue that introduced this module). Keys are the *bare* class name —
|
|
6
|
+
* esquery strips the leading `:` before invoking a language's
|
|
7
|
+
* `matchesSelectorClass` (confirmed against `esquery`'s own `case 'class'`
|
|
8
|
+
* matcher, which calls `options.matchClass(selector.name, node, ancestry)`
|
|
9
|
+
* with `selector.name` already colon-free) — so a selector written as
|
|
10
|
+
* `:command` calls this table with `"command"`, not `":command"`.
|
|
11
|
+
*
|
|
12
|
+
* `:loop` intentionally matches only `ForClause` and `WhileClause` — the
|
|
13
|
+
* two loop *statement* nodes, exactly as the bridge's generated schema
|
|
14
|
+
* names them (`packages/bridge/generated/visitor-keys.js`), even though
|
|
15
|
+
* design/ARCHITECTURE.md's prose shorthand says "For/While". `CStyleLoop`
|
|
16
|
+
* is deliberately excluded from `:loop`: it is `ForClause.loop`'s nested
|
|
17
|
+
* C-style arithmetic header (`init`/`cond`/`post` — see
|
|
18
|
+
* `packages/bridge/generated/node-types.d.ts`'s `ShCStyleLoopNode`), not a
|
|
19
|
+
* loop statement in its own right. A `for ((i=0;i<3;i++))` loop's outer
|
|
20
|
+
* node is still `ForClause`, so `:loop` already reaches every C-style loop
|
|
21
|
+
* without needing to include `CStyleLoop` itself.
|
|
22
|
+
*
|
|
23
|
+
* `as const satisfies Record<string, readonly ShNodes.ShNodeTypeName[]>`
|
|
24
|
+
* pins every listed node type to a real member of the bridge's generated
|
|
25
|
+
* type union — a typo or a since-renamed node type here is a compile error,
|
|
26
|
+
* not a silent no-op match.
|
|
27
|
+
*
|
|
28
|
+
* @public
|
|
29
|
+
*/
|
|
30
|
+
export declare const SELECTOR_CLASS_NODE_TYPES: {
|
|
31
|
+
readonly command: readonly ["CallExpr"];
|
|
32
|
+
readonly loop: readonly ["ForClause", "WhileClause"];
|
|
33
|
+
readonly conditional: readonly ["IfClause", "CaseClause"];
|
|
34
|
+
readonly expansion: readonly ["ParamExp", "CmdSubst", "ArithmExp", "ProcSubst"];
|
|
35
|
+
readonly quoted: readonly ["SglQuoted", "DblQuoted"];
|
|
36
|
+
readonly function: readonly ["FuncDecl"];
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* The six launch class names, bare (no leading `:`) — see
|
|
40
|
+
* {@link SELECTOR_CLASS_NODE_TYPES}.
|
|
41
|
+
*
|
|
42
|
+
* @public
|
|
43
|
+
*/
|
|
44
|
+
export type SelectorClassName = keyof typeof SELECTOR_CLASS_NODE_TYPES;
|
|
45
|
+
/**
|
|
46
|
+
* Implements the matching behavior behind `ShellLanguage.matchesSelectorClass`
|
|
47
|
+
* (`@eslint/core`'s `Language.matchesSelectorClass`): `true` when `node`'s
|
|
48
|
+
* `type` belongs to the named launch class, `false` for every other node
|
|
49
|
+
* type (including the five other launch classes' node types) and for any
|
|
50
|
+
* unrecognized `className` (a class name this language does not define —
|
|
51
|
+
* per the `Language` contract, an unknown class simply never matches,
|
|
52
|
+
* rather than throwing).
|
|
53
|
+
*
|
|
54
|
+
* @public
|
|
55
|
+
*/
|
|
56
|
+
export declare function matchesSelectorClass(className: string, node: ShNode): boolean;
|
|
57
|
+
//# sourceMappingURL=selector-classes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"selector-classes.d.ts","sourceRoot":"","sources":["../../src/language/selector-classes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAW,MAAM,QAAQ,CAAC;AAE9C;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,eAAO,MAAM,yBAAyB;;;;;;;CAOgC,CAAC;AAEvE;;;;;GAKG;AACH,MAAM,MAAM,iBAAiB,GAAG,MAAM,OAAO,yBAAyB,CAAC;AASvE;;;;;;;;;;GAUG;AACH,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAE7E"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The six launch esquery selector classes `ShellLanguage.matchesSelectorClass`
|
|
3
|
+
* implements (design/ARCHITECTURE.md, "SourceCode and Language objects";
|
|
4
|
+
* the issue that introduced this module). Keys are the *bare* class name —
|
|
5
|
+
* esquery strips the leading `:` before invoking a language's
|
|
6
|
+
* `matchesSelectorClass` (confirmed against `esquery`'s own `case 'class'`
|
|
7
|
+
* matcher, which calls `options.matchClass(selector.name, node, ancestry)`
|
|
8
|
+
* with `selector.name` already colon-free) — so a selector written as
|
|
9
|
+
* `:command` calls this table with `"command"`, not `":command"`.
|
|
10
|
+
*
|
|
11
|
+
* `:loop` intentionally matches only `ForClause` and `WhileClause` — the
|
|
12
|
+
* two loop *statement* nodes, exactly as the bridge's generated schema
|
|
13
|
+
* names them (`packages/bridge/generated/visitor-keys.js`), even though
|
|
14
|
+
* design/ARCHITECTURE.md's prose shorthand says "For/While". `CStyleLoop`
|
|
15
|
+
* is deliberately excluded from `:loop`: it is `ForClause.loop`'s nested
|
|
16
|
+
* C-style arithmetic header (`init`/`cond`/`post` — see
|
|
17
|
+
* `packages/bridge/generated/node-types.d.ts`'s `ShCStyleLoopNode`), not a
|
|
18
|
+
* loop statement in its own right. A `for ((i=0;i<3;i++))` loop's outer
|
|
19
|
+
* node is still `ForClause`, so `:loop` already reaches every C-style loop
|
|
20
|
+
* without needing to include `CStyleLoop` itself.
|
|
21
|
+
*
|
|
22
|
+
* `as const satisfies Record<string, readonly ShNodes.ShNodeTypeName[]>`
|
|
23
|
+
* pins every listed node type to a real member of the bridge's generated
|
|
24
|
+
* type union — a typo or a since-renamed node type here is a compile error,
|
|
25
|
+
* not a silent no-op match.
|
|
26
|
+
*
|
|
27
|
+
* @public
|
|
28
|
+
*/
|
|
29
|
+
export const SELECTOR_CLASS_NODE_TYPES = {
|
|
30
|
+
command: ['CallExpr'],
|
|
31
|
+
loop: ['ForClause', 'WhileClause'],
|
|
32
|
+
conditional: ['IfClause', 'CaseClause'],
|
|
33
|
+
expansion: ['ParamExp', 'CmdSubst', 'ArithmExp', 'ProcSubst'],
|
|
34
|
+
quoted: ['SglQuoted', 'DblQuoted'],
|
|
35
|
+
function: ['FuncDecl'],
|
|
36
|
+
};
|
|
37
|
+
const nodeTypesByClassName = new Map(Object.entries(SELECTOR_CLASS_NODE_TYPES).map(([className, nodeTypes]) => [
|
|
38
|
+
className,
|
|
39
|
+
new Set(nodeTypes),
|
|
40
|
+
]));
|
|
41
|
+
/**
|
|
42
|
+
* Implements the matching behavior behind `ShellLanguage.matchesSelectorClass`
|
|
43
|
+
* (`@eslint/core`'s `Language.matchesSelectorClass`): `true` when `node`'s
|
|
44
|
+
* `type` belongs to the named launch class, `false` for every other node
|
|
45
|
+
* type (including the five other launch classes' node types) and for any
|
|
46
|
+
* unrecognized `className` (a class name this language does not define —
|
|
47
|
+
* per the `Language` contract, an unknown class simply never matches,
|
|
48
|
+
* rather than throwing).
|
|
49
|
+
*
|
|
50
|
+
* @public
|
|
51
|
+
*/
|
|
52
|
+
export function matchesSelectorClass(className, node) {
|
|
53
|
+
return nodeTypesByClassName.get(className)?.has(node.type) ?? false;
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=selector-classes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"selector-classes.js","sourceRoot":"","sources":["../../src/language/selector-classes.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,MAAM,CAAC,MAAM,yBAAyB,GAAG;IACvC,OAAO,EAAE,CAAC,UAAU,CAAC;IACrB,IAAI,EAAE,CAAC,WAAW,EAAE,aAAa,CAAC;IAClC,WAAW,EAAE,CAAC,UAAU,EAAE,YAAY,CAAC;IACvC,SAAS,EAAE,CAAC,UAAU,EAAE,UAAU,EAAE,WAAW,EAAE,WAAW,CAAC;IAC7D,MAAM,EAAE,CAAC,WAAW,EAAE,WAAW,CAAC;IAClC,QAAQ,EAAE,CAAC,UAAU,CAAC;CAC8C,CAAC;AAUvE,MAAM,oBAAoB,GAA6C,IAAI,GAAG,CAC5E,MAAM,CAAC,OAAO,CAAC,yBAAyB,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,EAAE,SAAS,CAAC,EAAE,EAAE,CAAC;IACxE,SAAS;IACT,IAAI,GAAG,CAAS,SAAS,CAAC;CAC3B,CAAC,CACH,CAAC;AAEF;;;;;;;;;;GAUG;AACH,MAAM,UAAU,oBAAoB,CAAC,SAAiB,EAAE,IAAY;IAClE,OAAO,oBAAoB,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,KAAK,CAAC;AACtE,CAAC"}
|