@eslint/shell 0.0.0-stage → 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.
Files changed (37) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +157 -2
  3. package/dist/index.d.ts +79 -0
  4. package/dist/index.js +74 -0
  5. package/dist/languages/shell-language.d.ts +36 -0
  6. package/dist/languages/shell-language.js +72 -0
  7. package/dist/languages/shell-source-code.d.ts +55 -0
  8. package/dist/languages/shell-source-code.js +184 -0
  9. package/dist/parser/parse.d.ts +32 -0
  10. package/dist/parser/parse.js +758 -0
  11. package/dist/rules/no-backticks.d.ts +9 -0
  12. package/dist/rules/no-backticks.js +47 -0
  13. package/dist/rules/no-expansions-in-single-quotes.d.ts +9 -0
  14. package/dist/rules/no-expansions-in-single-quotes.js +43 -0
  15. package/dist/rules/no-ls-iteration.d.ts +9 -0
  16. package/dist/rules/no-ls-iteration.js +43 -0
  17. package/dist/rules/no-unquoted-expansions.d.ts +9 -0
  18. package/dist/rules/no-unquoted-expansions.js +91 -0
  19. package/dist/rules/no-unused-vars.d.ts +12 -0
  20. package/dist/rules/no-unused-vars.js +383 -0
  21. package/dist/rules/no-useless-cat.d.ts +9 -0
  22. package/dist/rules/no-useless-cat.js +77 -0
  23. package/dist/rules/no-useless-echo.d.ts +9 -0
  24. package/dist/rules/no-useless-echo.js +43 -0
  25. package/dist/rules/no-variables-in-printf-format.d.ts +9 -0
  26. package/dist/rules/no-variables-in-printf-format.js +61 -0
  27. package/dist/rules/require-cd-guard.d.ts +9 -0
  28. package/dist/rules/require-cd-guard.js +75 -0
  29. package/dist/rules/require-read-r.d.ts +9 -0
  30. package/dist/rules/require-read-r.js +84 -0
  31. package/dist/rules/utils.d.ts +35 -0
  32. package/dist/rules/utils.js +100 -0
  33. package/dist/types.d.ts +343 -0
  34. package/dist/types.js +7 -0
  35. package/dist/visitor-keys.d.ts +5 -0
  36. package/dist/visitor-keys.js +59 -0
  37. package/package.json +74 -4
@@ -0,0 +1,55 @@
1
+ /**
2
+ * @fileoverview The ShellSourceCode class, the SourceCode implementation that
3
+ * ESLint uses to interact with a parsed shell script.
4
+ */
5
+ import { Directive, TextSourceCodeBase } from "@eslint/plugin-kit";
6
+ import type { FileProblem, RulesConfig, SourceLocation, SourceRange, TraversalStep } from "@eslint/core";
7
+ import type { ShellLanguageOptions, ShellNode, CommentNode, ProgramNode } from "../types.js";
8
+ export interface ShellSourceCodeOptions {
9
+ text: string;
10
+ ast: ProgramNode;
11
+ }
12
+ /**
13
+ * SourceCode implementation for shell scripts. Nodes carry `start`/`end`
14
+ * character offsets; `getLoc()` and `getRange()` derive positions from
15
+ * those offsets, so nodes have no `loc` or `range` properties.
16
+ */
17
+ export declare class ShellSourceCode extends TextSourceCodeBase<{
18
+ LangOptions: ShellLanguageOptions;
19
+ RootNode: ProgramNode;
20
+ SyntaxElementWithLoc: ShellNode;
21
+ ConfigNode: CommentNode;
22
+ }> {
23
+ #private;
24
+ /** All comments found in the file, in source order. */
25
+ comments: CommentNode[];
26
+ constructor({ text, ast }: ShellSourceCodeOptions);
27
+ getLoc(node: ShellNode): SourceLocation;
28
+ getRange(node: ShellNode): SourceRange;
29
+ getParent(node: ShellNode): ShellNode | undefined;
30
+ traverse(): Iterable<TraversalStep>;
31
+ /**
32
+ * Returns all comments that look like inline ESLint configuration.
33
+ */
34
+ getInlineConfigNodes(): CommentNode[];
35
+ /**
36
+ * Returns directives for disabling/enabling rules found in comments,
37
+ * such as `# eslint-disable-next-line shell/no-backticks`.
38
+ */
39
+ getDisableDirectives(): {
40
+ directives: Directive[];
41
+ problems: FileProblem[];
42
+ };
43
+ /**
44
+ * Applies `# eslint rule: severity` configuration comments.
45
+ */
46
+ applyInlineConfig(): {
47
+ configs: {
48
+ config: {
49
+ rules: RulesConfig;
50
+ };
51
+ loc: SourceLocation;
52
+ }[];
53
+ problems: FileProblem[];
54
+ };
55
+ }
@@ -0,0 +1,184 @@
1
+ /**
2
+ * @fileoverview The ShellSourceCode class, the SourceCode implementation that
3
+ * ESLint uses to interact with a parsed shell script.
4
+ */
5
+ import { ConfigCommentParser, Directive, TextSourceCodeBase, VisitNodeStep, } from "@eslint/plugin-kit";
6
+ import { visitorKeys } from "../visitor-keys.js";
7
+ const commentParser = new ConfigCommentParser();
8
+ const INLINE_CONFIG = /^\s*eslint(?:-enable|-disable(?:(?:-next)?-line)?)?(?:\s|$)/u;
9
+ /**
10
+ * The directive labels ESLint understands, mapped to directive types.
11
+ */
12
+ const DIRECTIVE_TYPES = new Map([
13
+ ["eslint-disable", "disable"],
14
+ ["eslint-enable", "enable"],
15
+ ["eslint-disable-line", "disable-line"],
16
+ ["eslint-disable-next-line", "disable-next-line"],
17
+ ]);
18
+ /**
19
+ * SourceCode implementation for shell scripts. Nodes carry `start`/`end`
20
+ * character offsets; `getLoc()` and `getRange()` derive positions from
21
+ * those offsets, so nodes have no `loc` or `range` properties.
22
+ */
23
+ export class ShellSourceCode extends TextSourceCodeBase {
24
+ /** All comments found in the file, in source order. */
25
+ comments;
26
+ #parents = new Map();
27
+ #steps = null;
28
+ #lineOffsets = null;
29
+ #inlineConfigComments = null;
30
+ constructor({ text, ast }) {
31
+ super({ text, ast });
32
+ this.comments = ast.comments;
33
+ }
34
+ /** Character offsets at which each line starts. */
35
+ get #lineStarts() {
36
+ if (!this.#lineOffsets) {
37
+ const offsets = [0];
38
+ for (let i = 0; i < this.text.length; i++) {
39
+ if (this.text[i] === "\n") {
40
+ offsets.push(i + 1);
41
+ }
42
+ }
43
+ this.#lineOffsets = offsets;
44
+ }
45
+ return this.#lineOffsets;
46
+ }
47
+ /**
48
+ * Converts a character offset into a 1-based line/column pair.
49
+ */
50
+ #locFromIndex(index) {
51
+ const lineStarts = this.#lineStarts;
52
+ let low = 0;
53
+ let high = lineStarts.length - 1;
54
+ while (low < high) {
55
+ const mid = (low + high + 1) >> 1;
56
+ if (lineStarts[mid] <= index) {
57
+ low = mid;
58
+ }
59
+ else {
60
+ high = mid - 1;
61
+ }
62
+ }
63
+ return {
64
+ line: low + 1,
65
+ column: index - lineStarts[low] + 1,
66
+ };
67
+ }
68
+ getLoc(node) {
69
+ return {
70
+ start: this.#locFromIndex(node.start),
71
+ end: this.#locFromIndex(node.end),
72
+ };
73
+ }
74
+ getRange(node) {
75
+ return [node.start, node.end];
76
+ }
77
+ getParent(node) {
78
+ this.#ensureTraversed();
79
+ return this.#parents.get(node);
80
+ }
81
+ #ensureTraversed() {
82
+ if (!this.#steps) {
83
+ void [...this.traverse()];
84
+ }
85
+ }
86
+ traverse() {
87
+ if (this.#steps) {
88
+ return this.#steps.values();
89
+ }
90
+ const steps = (this.#steps = []);
91
+ const visit = (node, parent) => {
92
+ if (parent) {
93
+ this.#parents.set(node, parent);
94
+ }
95
+ steps.push(new VisitNodeStep({
96
+ target: node,
97
+ phase: 1,
98
+ args: [node, parent],
99
+ }));
100
+ for (const key of visitorKeys[node.type] ?? []) {
101
+ const child = node[key];
102
+ if (Array.isArray(child)) {
103
+ for (const element of child) {
104
+ if (element) {
105
+ visit(element, node);
106
+ }
107
+ }
108
+ }
109
+ else if (child) {
110
+ visit(child, node);
111
+ }
112
+ }
113
+ steps.push(new VisitNodeStep({
114
+ target: node,
115
+ phase: 2,
116
+ args: [node, parent],
117
+ }));
118
+ };
119
+ visit(this.ast, undefined);
120
+ return steps.values();
121
+ }
122
+ /**
123
+ * Returns all comments that look like inline ESLint configuration.
124
+ */
125
+ getInlineConfigNodes() {
126
+ if (!this.#inlineConfigComments) {
127
+ this.#inlineConfigComments = this.comments.filter(comment => INLINE_CONFIG.test(comment.text));
128
+ }
129
+ return this.#inlineConfigComments;
130
+ }
131
+ /**
132
+ * Returns directives for disabling/enabling rules found in comments,
133
+ * such as `# eslint-disable-next-line shell/no-backticks`.
134
+ */
135
+ getDisableDirectives() {
136
+ const directives = [];
137
+ const problems = [];
138
+ for (const comment of this.getInlineConfigNodes()) {
139
+ const parsed = commentParser.parseDirective(comment.text.trim());
140
+ if (!parsed) {
141
+ continue;
142
+ }
143
+ const directiveType = DIRECTIVE_TYPES.get(parsed.label);
144
+ if (!directiveType) {
145
+ continue;
146
+ }
147
+ directives.push(new Directive({
148
+ type: directiveType,
149
+ node: comment,
150
+ value: parsed.value,
151
+ justification: parsed.justification,
152
+ }));
153
+ }
154
+ return { directives, problems };
155
+ }
156
+ /**
157
+ * Applies `# eslint rule: severity` configuration comments.
158
+ */
159
+ applyInlineConfig() {
160
+ const configs = [];
161
+ const problems = [];
162
+ for (const comment of this.getInlineConfigNodes()) {
163
+ const parsed = commentParser.parseDirective(comment.text.trim());
164
+ if (!parsed || parsed.label !== "eslint") {
165
+ continue;
166
+ }
167
+ const parseResult = commentParser.parseJSONLikeConfig(parsed.value);
168
+ if (parseResult.ok) {
169
+ configs.push({
170
+ config: { rules: parseResult.config },
171
+ loc: this.getLoc(comment),
172
+ });
173
+ }
174
+ else {
175
+ problems.push({
176
+ ruleId: null,
177
+ message: parseResult.error.message,
178
+ loc: this.getLoc(comment),
179
+ });
180
+ }
181
+ }
182
+ return { configs, problems };
183
+ }
184
+ }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * @fileoverview Parses shell source code into an ESTree-style syntax tree by
3
+ * wrapping the mvdan-sh parser (a GopherJS build of mvdan.cc/sh) and
4
+ * translating its tree. See docs/syntax-tree.md for the tree format.
5
+ * @author Claude Opus 5.5
6
+ */
7
+ import type { ShellVariant, CommentNode, ProgramNode } from "../types.js";
8
+ /**
9
+ * The error thrown when shell source code cannot be parsed.
10
+ */
11
+ export declare class ShellSyntaxError extends SyntaxError {
12
+ line: number;
13
+ column: number;
14
+ /** The file path passed to the parser, when there was one. */
15
+ path: string | undefined;
16
+ constructor(message: string, line: number, column: number, path?: string);
17
+ }
18
+ export interface ShellParseOptions {
19
+ /** The shell dialect to parse. Defaults to `"bash"`. */
20
+ variant?: ShellVariant;
21
+ /** The file path reported as `ShellSyntaxError#path` on syntax errors. */
22
+ path?: string;
23
+ }
24
+ export interface ShellParseResult {
25
+ ast: ProgramNode;
26
+ comments: CommentNode[];
27
+ }
28
+ /**
29
+ * Parses shell source code into an ESTree-style syntax tree.
30
+ * @throws {ShellSyntaxError} When the source code contains a syntax error.
31
+ */
32
+ export declare function parseShell(text: string, options?: ShellParseOptions): ShellParseResult;