@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,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
|
+
}
|