@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.
@@ -0,0 +1,89 @@
1
+ # Selector cookbook
2
+
3
+ `@eslint-sh/eslint-plugin` registers `ShellLanguage.matchesSelectorClass`, which lets ESLint's
4
+ [esquery](https://github.com/estools/esquery)-based rule selectors use six shorthand **launch
5
+ classes** against shell ASTs, in addition to matching bare node type names
6
+ (`CallExpr`, `IfClause`, ...). Selector-first rule authoring — writing a rule as a selector string
7
+ rather than a `create()` full of manual type checks — is a headline feature of this plugin; this
8
+ page is a copy-paste cookbook of real selectors, each backed by a passing test (see
9
+ `test/selectors-cookbook.test.ts`) so the examples can't silently rot as the plugin evolves.
10
+
11
+ Looking for a complete, runnable `create()` that uses one of these selectors — the full
12
+ `{ meta, create(context) }` rule module, `context.report`, and the `eslint.config.mjs`
13
+ `plugins`/`rules`/`language` wiring? See the [custom-rule tutorial](./custom-rule-tutorial.md).
14
+
15
+ ## The six launch classes
16
+
17
+ | Class | Matches |
18
+ | -------------- | ---------------------------------------------------- |
19
+ | `:command` | `CallExpr` |
20
+ | `:loop` | `ForClause`, `WhileClause` |
21
+ | `:conditional` | `IfClause`, `CaseClause` |
22
+ | `:expansion` | `ParamExp`, `CmdSubst`, `ArithmExp`, `ProcSubst` |
23
+ | `:quoted` | `SglQuoted`, `DblQuoted` |
24
+ | `:function` | `FuncDecl` |
25
+
26
+ `:loop` deliberately does **not** include `CStyleLoop` — that node is `ForClause.loop`'s nested
27
+ C-style arithmetic header (`init`/`cond`/`post`), not a loop statement in its own right. A
28
+ `for ((i=0;i<3;i++))` loop's outer node is still `ForClause`, so `:loop` already reaches it.
29
+
30
+ ## Recipes
31
+
32
+ ### Match a specific command by name
33
+
34
+ ```
35
+ CallExpr[args.0.parts.0.value='jq']
36
+ ```
37
+
38
+ `args.0` is the command word (the first argument of a `CallExpr` is the command name itself, not
39
+ a separate field); `.parts.0.value` reaches that word's first literal part. This is the pattern
40
+ behind rules like "forbid calling `curl` directly, use the wrapped helper instead."
41
+
42
+ ### Match every command nested inside a function
43
+
44
+ ```
45
+ :function :command
46
+ ```
47
+
48
+ A descendant combinator: any `CallExpr` that has a `FuncDecl` ancestor. Useful for rules that only
49
+ apply to commands run from within a shell function (e.g. "functions must not shell out to `sudo`").
50
+
51
+ ### Match every command nested inside a conditional
52
+
53
+ ```
54
+ :conditional :command
55
+ ```
56
+
57
+ Same shape as above, but for `IfClause`/`CaseClause` ancestors — matches commands in both the
58
+ condition and the branch bodies of an `if`/`case`.
59
+
60
+ ### Match unquoted expansions
61
+
62
+ ```
63
+ :expansion:not(DblQuoted :expansion)
64
+ ```
65
+
66
+ A compound selector: match any of the four expansion node types, but reject the ones that also
67
+ have a `DblQuoted` ancestor. `echo $foo "$bar"` — this matches `$foo` (unquoted) but not the
68
+ `$bar` inside the double-quoted word, which is exactly the risky, word-splitting/glob-expanding
69
+ case most "quote your variables" lint rules care about.
70
+
71
+ ### Match a function declaration by name
72
+
73
+ ```
74
+ FuncDecl[name.value='deploy']
75
+ ```
76
+
77
+ `FuncDecl.name` is the declared function's identifier (a `Lit` node); this pattern targets a rule
78
+ at one specific, by-convention-named function (e.g. a project's `deploy` entrypoint) rather than
79
+ every function.
80
+
81
+ ### Match any of the six launch classes at once
82
+
83
+ ```
84
+ :command, :loop, :conditional, :expansion, :quoted, :function
85
+ ```
86
+
87
+ esquery's top-level comma is an OR across selectors — useful for a single rule that reports on
88
+ "anything selector-class-worthy" (for example, a rule collecting a script's shape/complexity
89
+ metrics across all six categories in one listener instead of six).
package/package.json ADDED
@@ -0,0 +1,80 @@
1
+ {
2
+ "name": "@eslint-sh/eslint-plugin",
3
+ "description": "Shell script parsing and syntax-error reporting for ESLint, with custom-rule authoring over a typed shell AST, backed by sh-ast.",
4
+ "version": "0.1.0",
5
+ "author": "Mike North <michael.l.north@gmail.com>",
6
+ "dependencies": {
7
+ "@eslint/core": "^1.2.1",
8
+ "@eslint/plugin-kit": "^0.7.2",
9
+ "sh-ast": "^0.1.0"
10
+ },
11
+ "devDependencies": {
12
+ "eslint": "^10.7.0"
13
+ },
14
+ "peerDependencies": {
15
+ "eslint": "^10.0.0"
16
+ },
17
+ "exports": {
18
+ ".": {
19
+ "types": "./dist/eslint-plugin.d.ts",
20
+ "default": "./dist/index.js"
21
+ }
22
+ },
23
+ "files": [
24
+ "dist",
25
+ "docs"
26
+ ],
27
+ "license": "MIT",
28
+ "main": "./dist/index.js",
29
+ "nx": {
30
+ "targets": {
31
+ "build": {
32
+ "outputs": [
33
+ "{projectRoot}/dist/**/*.js",
34
+ "{projectRoot}/dist/**/*.js.map",
35
+ "{projectRoot}/dist/**/*.d.ts",
36
+ "{projectRoot}/dist/**/*.d.ts.map",
37
+ "!{projectRoot}/dist/eslint-plugin.d.ts"
38
+ ]
39
+ },
40
+ "check:api-report": {
41
+ "outputs": [
42
+ "{projectRoot}/api-report",
43
+ "{projectRoot}/temp",
44
+ "{projectRoot}/dist/eslint-plugin.d.ts"
45
+ ]
46
+ },
47
+ "uplevel-api-reports": {
48
+ "dependsOn": [
49
+ "check:api-report"
50
+ ],
51
+ "cache": true,
52
+ "inputs": [
53
+ "{projectRoot}/api-report/**",
54
+ "{projectRoot}/temp/**"
55
+ ],
56
+ "outputs": [
57
+ "{workspaceRoot}/api-report-combined/eslint-plugin.api.md",
58
+ "{workspaceRoot}/api-docs-temp/eslint-plugin.api.json"
59
+ ]
60
+ }
61
+ }
62
+ },
63
+ "publishConfig": {
64
+ "access": "public"
65
+ },
66
+ "type": "module",
67
+ "types": "./dist/eslint-plugin.d.ts",
68
+ "scripts": {
69
+ "build": "tsc -p tsconfig.json",
70
+ "check": "concurrently -n lint,types,api-report \"pnpm run check:lint-ts\" \"pnpm run check:typecheck\" \"pnpm run check:api-report\"",
71
+ "check:api-report": "api-extractor run",
72
+ "check:lint-ts": "eslint .",
73
+ "check:typecheck": "tsc --noEmit",
74
+ "clean": "rimraf --glob dist \"temp/*\" \"*.tsbuildinfo\"",
75
+ "fix:api-report": "api-extractor run --local",
76
+ "fix:lint-ts": "eslint . --fix",
77
+ "test": "vitest run",
78
+ "uplevel-api-reports": "mkdir -p ../../api-report-combined ../../api-docs-temp && cp api-report/eslint-plugin.api.md ../../api-report-combined/ && cp temp/eslint-plugin.api.json ../../api-docs-temp/"
79
+ }
80
+ }