eslint-plugin-lube 0.6.0 → 0.8.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/CHANGELOG.md +39 -0
- package/README.md +6 -1
- package/docs/prefer-param-tag.md +25 -0
- package/docs/prefer-type-tag.md +45 -0
- package/package.json +3 -3
- package/src/index.js +14 -2
- package/src/rules/ascii-order.js +165 -78
- package/src/rules/prefer-param-tag.js +252 -0
- package/src/rules/prefer-type-tag.js +165 -0
- package/src/rules/pretty-imports.js +21 -12
- package/src/rules/pretty-jsdoc-casting.js +76 -37
- package/src/rules/pretty-sequence.js +110 -42
- package/src/rules/pretty-ternary.js +43 -23
- package/src/rules/svelte-naming-convention.js +287 -134
- package/src/utils/keep_body_comments.js +53 -0
- package/src/utils/keep_comments.js +6 -2
- package/src/utils/keep_line_breaks.js +3 -1
- package/src/utils/skip_hole_commas.js +4 -4
- package/src/utils/skip_optional_patterns.js +3 -1
- package/src/utils/skip_unchanged_fixes.js +3 -1
- package/src/utils/source_text.js +57 -23
- package/types/index.d.ts.map +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,44 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.8.0
|
|
4
|
+
|
|
5
|
+
### Breaking
|
|
6
|
+
|
|
7
|
+
- **The strict config moves inline parameter types into `@param` tags** with the new
|
|
8
|
+
`prefer-param-tag`: `function f(/** @type {T} */ a) {}` becomes `@param {T} a` in the JSDoc block of
|
|
9
|
+
the declaration. It has a fix, so run `eslint --fix` once; a destructured parameter is left to you.
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- `prefer-param-tag`, in the strict config: reports a function declaration whose parameters are typed
|
|
14
|
+
with inline comments, `function f(/** @type {T} */ a) {}`, and fixes it into `@param {T} a` tags in the
|
|
15
|
+
JSDoc block of the declaration, creating the block if there is none, each tag at the position of its
|
|
16
|
+
parameter among the existing `@param` tags. Arrow functions and function expressions stay, and a
|
|
17
|
+
destructured parameter is reported without a fix.
|
|
18
|
+
|
|
19
|
+
## 0.7.0
|
|
20
|
+
|
|
21
|
+
### Breaking
|
|
22
|
+
|
|
23
|
+
- **The strict config requires braces on every block** with `lube/curly` (`all`): `if`, `else`, `for`,
|
|
24
|
+
`while` and `do` bodies are wrapped in `{ }`. It has a fix, so run `eslint --fix` once.
|
|
25
|
+
- **The strict config no longer reports `await` in a loop:** `no-await-in-loop` is gone from it.
|
|
26
|
+
- **The strict config moves the type cast of a declaration's value into a `@type` tag** with the new
|
|
27
|
+
`prefer-type-tag`, in a project linted with type information: `const a = /** @type {T} */(b)/**/`
|
|
28
|
+
becomes `/** @type {T} */` on the line before `const a = b`. Run `eslint --fix` once.
|
|
29
|
+
|
|
30
|
+
### Added
|
|
31
|
+
|
|
32
|
+
- `prefer-type-tag`, in the strict config: reports the cast of the whole value of a declaration,
|
|
33
|
+
`const a = /** @type {T} */(b)/**/`, when TypeScript would accept a `/** @type {T} */` tag on the
|
|
34
|
+
declaration instead, and fixes it into one. A cast that narrows, such as `/** @type {T} */(list[0])/**/`
|
|
35
|
+
under `noUncheckedIndexedAccess`, one that widens to a union, such as `/** @type {T | null} */(null)/**/`,
|
|
36
|
+
one through `unknown` and `/** @type {const} */` stay. It reads types from the program of
|
|
37
|
+
`@typescript-eslint/parser` with `projectService` and checks nothing without one.
|
|
38
|
+
- `curly`, ESLint's own rule with the same options, whose fix puts the braces around a comment in
|
|
39
|
+
front of the body: `if (a) /** @type {A} */(b).c = 1` becomes `if (a) {/** @type {A} */(b).c = 1}`,
|
|
40
|
+
where ESLint's fix turned the cast into a comment before the brace, `if (a) /** @type {A} */{(b).c = 1}`.
|
|
41
|
+
|
|
3
42
|
## 0.6.0
|
|
4
43
|
|
|
5
44
|
### Breaking
|
package/README.md
CHANGED
|
@@ -89,10 +89,12 @@ Both configs are flat config objects with `plugins` and `rules`, and differ in h
|
|
|
89
89
|
|
|
90
90
|
**`configs.strict`** is the house style of this repository, and several of its rules cannot be fixed. It is everything in `recommended` plus:
|
|
91
91
|
- `svelte-naming-convention`, which renames declarations to snake_case.
|
|
92
|
+
- `prefer-param-tag`, which moves the inline `/** @type {T} */` comments of a function declaration's parameters into `@param` tags on the declaration ([prefer-param-tag](https://github.com/artxe/lube-series/blob/master/packages/eslint-plugin-lube/docs/prefer-param-tag.md)).
|
|
93
|
+
- `prefer-type-tag`, which moves the cast of a declaration's value into a `@type` tag on the declaration, in a project linted with type information ([prefer-type-tag](https://github.com/artxe/lube-series/blob/master/packages/eslint-plugin-lube/docs/prefer-type-tag.md) shows the parser options).
|
|
92
94
|
- `ascii-order` for import statements, object keys, function declarations and test cases too (see below).
|
|
93
95
|
- `pretty-sequence` and `pretty-imports` at 30 characters.
|
|
94
96
|
- No blank lines, no line break at the end of a file and one statement per line.
|
|
95
|
-
-
|
|
97
|
+
- `curly` (braces on every block) and core rules such as `func-style` (declarations), `no-shadow`, `prefer-const`, `one-var` (never) and `no-console`.
|
|
96
98
|
|
|
97
99
|
In `strict`, `ascii-order` fixes import order assuming modules can run in any order, and key order assuming nothing reads it: sorted keys change `Object.keys`, `JSON.stringify` and `for...in`.
|
|
98
100
|
- Import a module that has to run first without names, `import "./setup.js"`, which is never moved.
|
|
@@ -109,6 +111,9 @@ Override a rule after the spread: `rules: { ...lube.configs.recommended.rules, "
|
|
|
109
111
|
| Rule | Enforces | `recommended` |
|
|
110
112
|
| --- | --- | --- |
|
|
111
113
|
| [ascii-order](https://github.com/artxe/lube-series/blob/master/packages/eslint-plugin-lube/docs/ascii-order.md) | ASCII order of import statements, import and export names, object keys, function declarations and test cases | names only |
|
|
114
|
+
| [curly](https://eslint.org/docs/latest/rules/curly) | Braces around the bodies of `if`, `else`, `for`, `while` and `do`, as ESLint's own rule does, with a fix that keeps a comment in front of the body, such as a JSDoc cast, inside the braces | no |
|
|
115
|
+
| [prefer-param-tag](https://github.com/artxe/lube-series/blob/master/packages/eslint-plugin-lube/docs/prefer-param-tag.md) | JSDoc `@param` tags on a function declaration instead of inline `/** @type {T} */` comments in its parameter list | no |
|
|
116
|
+
| [prefer-type-tag](https://github.com/artxe/lube-series/blob/master/packages/eslint-plugin-lube/docs/prefer-type-tag.md) | A JSDoc `@type` tag on a declaration instead of a cast of its value, wherever the tag would type check (needs type information) | no |
|
|
112
117
|
| [pretty-imports](https://github.com/artxe/lube-series/blob/master/packages/eslint-plugin-lube/docs/pretty-imports.md) | Import and export names on one line when they fit, or one per line | yes |
|
|
113
118
|
| [pretty-jsdoc-casting](https://github.com/artxe/lube-series/blob/master/packages/eslint-plugin-lube/docs/pretty-jsdoc-casting.md) | JSDoc type casts written as `/** @type {T} */(value)/**/` | yes |
|
|
114
119
|
| [pretty-sequence](https://github.com/artxe/lube-series/blob/master/packages/eslint-plugin-lube/docs/pretty-sequence.md) | Items of arrays, objects, calls and parameters on one line when they fit, or one per line | yes |
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# prefer-param-tag
|
|
2
|
+
Enforces JSDoc `@param` tags on a function declaration, instead of inline `/** @type {T} */` comments inside its parameter list.
|
|
3
|
+
|
|
4
|
+
- Only function declarations are checked, `export` and `export default` ones too. Arrow functions and function expressions, such as `items.map((/** @type {string} */ x) => x)`, have no declaration to carry a tag and are left alone, and so are casts in a body.
|
|
5
|
+
- The fix removes the inline comments and adds one `@param {T} name` line per parameter to the JSDoc block above the declaration, in parameter order, after the existing `@param` tags and before `@returns` and the like. It creates the block when there is none, and keeps the indentation of the declaration. A `@param` tag that already names the parameter is not repeated.
|
|
6
|
+
- A default value, `/** @type {number} */ n = 1`, is typed by `@param {number} n` too.
|
|
7
|
+
- A destructured or rest parameter has no simple name to write in a tag and is reported without a fix. So is a declaration that does not start its line, where a block cannot be added.
|
|
8
|
+
|
|
9
|
+
## options
|
|
10
|
+
None.
|
|
11
|
+
|
|
12
|
+
## Valid
|
|
13
|
+
```js
|
|
14
|
+
/**
|
|
15
|
+
* @param {string} tx
|
|
16
|
+
*/
|
|
17
|
+
function use(tx) {}
|
|
18
|
+
items.map((/** @type {string} */ x) => x)
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Invalid
|
|
22
|
+
```js
|
|
23
|
+
function use(/** @type {string} */ tx) {}
|
|
24
|
+
async function source(/** @type {unknown} */ n, /** @type {number} */ m = 1) {}
|
|
25
|
+
```
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# prefer-type-tag
|
|
2
|
+
Enforces a JSDoc type tag on a declaration, `/** @type {T} */` on the line before it, instead of a type cast of its whole value, `const v = /** @type {T} */(value)/**/`, wherever the value is assignable to `T` and the tag says the same.
|
|
3
|
+
|
|
4
|
+
The rule needs type information: it reads the types of the value and of the cast from the TypeScript program of `@typescript-eslint/parser`, and checks nothing without one. Turn it on with `projectService`, for files that a `tsconfig.json` with `checkJs` includes:
|
|
5
|
+
```js
|
|
6
|
+
// eslint.config.js
|
|
7
|
+
import tsParser from "@typescript-eslint/parser"
|
|
8
|
+
import lube from "eslint-plugin-lube"
|
|
9
|
+
|
|
10
|
+
export default [
|
|
11
|
+
{
|
|
12
|
+
...lube.configs.strict,
|
|
13
|
+
files: ["**/*.js"],
|
|
14
|
+
languageOptions: {
|
|
15
|
+
parser: tsParser,
|
|
16
|
+
parserOptions: { projectService: true, tsconfigRootDir: import.meta.dirname }
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
]
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
- TypeScript files, `.ts`, `.tsx`, `.mts` and `.cts`, are not checked, since TypeScript reads no type from a JSDoc cast there.
|
|
23
|
+
- A cast the value is not assignable to is kept: it narrows, as `/** @type {T} */(list[0])/**/` drops the `undefined` that `noUncheckedIndexedAccess` adds, and a tag would fail type checking. So is a cast through `unknown`, `/** @type {T} */(/** @type {unknown} */(value))/**/`, and an object literal with a property the type lacks.
|
|
24
|
+
- `/** @type {const} */` is kept, since a tag cannot make a value constant.
|
|
25
|
+
- A cast that widens the value to a union, `let found = /** @type {Item | null} */(null)/**/`, is kept: TypeScript narrows a variable typed by a union tag to the type of its value, so `found` would read as `null` even after a callback assigned an item, and a comparison with another member of the union would fail type checking.
|
|
26
|
+
- Only a cast of the whole value of a declaration with one variable is checked, and not one in the head of a `for` loop, where a tag has nowhere to go. A declaration that already has a `@type` tag is left alone.
|
|
27
|
+
- The tag goes on its own line before the declaration and any `eslint-disable-next-line`, `@ts-expect-error` or `@ts-ignore` comment above it. A declaration with a JSDoc comment of its own is reported without a fix: add the type to that comment by hand.
|
|
28
|
+
|
|
29
|
+
## options
|
|
30
|
+
None.
|
|
31
|
+
|
|
32
|
+
## Valid
|
|
33
|
+
```js
|
|
34
|
+
/** @type {string[]} */
|
|
35
|
+
const names = JSON.parse(text)
|
|
36
|
+
const first = /** @type {string} */(names[0])/**/
|
|
37
|
+
const units = /** @type {const} */([ "s", "m" ])/**/
|
|
38
|
+
let found = /** @type {Item | null} */(null)/**/
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Invalid
|
|
42
|
+
```js
|
|
43
|
+
const names = /** @type {string[]} */(JSON.parse(text))/**/
|
|
44
|
+
const empty = /** @type {string[]} */([])/**/
|
|
45
|
+
```
|
package/package.json
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"devDependencies": {
|
|
11
11
|
"@types/estree": "^1.0.9",
|
|
12
12
|
"@typescript/native": "npm:typescript@^7.0.2",
|
|
13
|
-
"eslint": "^10.
|
|
13
|
+
"eslint": "^10.12.0"
|
|
14
14
|
},
|
|
15
15
|
"engines": {
|
|
16
16
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
|
@@ -53,10 +53,10 @@
|
|
|
53
53
|
},
|
|
54
54
|
"type": "module",
|
|
55
55
|
"types": "types/index.d.ts",
|
|
56
|
-
"version": "0.
|
|
56
|
+
"version": "0.8.0",
|
|
57
57
|
"scripts": {
|
|
58
58
|
"dts": "node ../../scripts/tsc_with_clean.js eslint-plugin-lube",
|
|
59
59
|
"lint": "pnpm -w lint",
|
|
60
|
-
"test": "pnpm -w exec vitest run --coverage test/eslint-plugin-lube"
|
|
60
|
+
"test": "pnpm -w exec vitest run --coverage --coverage.include=packages/eslint-plugin-lube/src/** test/eslint-plugin-lube"
|
|
61
61
|
}
|
|
62
62
|
}
|
package/src/index.js
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import ascii_order from "./rules/ascii-order.js"
|
|
2
|
+
import prefer_param_tag from "./rules/prefer-param-tag.js"
|
|
3
|
+
import prefer_type_tag from "./rules/prefer-type-tag.js"
|
|
2
4
|
import pretty_imports from "./rules/pretty-imports.js"
|
|
3
5
|
import pretty_jsdoc_casting from "./rules/pretty-jsdoc-casting.js"
|
|
4
6
|
import pretty_sequence from "./rules/pretty-sequence.js"
|
|
5
7
|
import pretty_ternary from "./rules/pretty-ternary.js"
|
|
6
8
|
import svelte_naming_convention from "./rules/svelte-naming-convention.js"
|
|
9
|
+
import { keep_body_comments } from "./utils/keep_body_comments.js"
|
|
7
10
|
import {
|
|
8
11
|
keep_comment_order,
|
|
9
12
|
keep_comments
|
|
@@ -14,8 +17,10 @@ import { skip_optional_patterns } from "./utils/skip_optional_patterns.js"
|
|
|
14
17
|
import { skip_unchanged_fixes } from "./utils/skip_unchanged_fixes.js"
|
|
15
18
|
import { split_declarations } from "./utils/split_declarations.js"
|
|
16
19
|
import stylistic_plugin from "@stylistic/eslint-plugin"
|
|
20
|
+
import { builtinRules } from "eslint/use-at-your-own-risk"
|
|
17
21
|
import { readFileSync } from "node:fs"
|
|
18
|
-
|
|
22
|
+
/** @type {import("eslint").ESLint.Plugin} */
|
|
23
|
+
const stylistic = stylistic_plugin
|
|
19
24
|
/** @type {{ version: string }} */
|
|
20
25
|
const manifest = JSON.parse(
|
|
21
26
|
readFileSync(
|
|
@@ -147,12 +152,14 @@ const strict_rules = {
|
|
|
147
152
|
checkTests: true
|
|
148
153
|
}
|
|
149
154
|
],
|
|
155
|
+
"lube/curly": [ "error", "all" ],
|
|
156
|
+
"lube/prefer-param-tag": "error",
|
|
157
|
+
"lube/prefer-type-tag": "error",
|
|
150
158
|
"lube/pretty-imports": "error",
|
|
151
159
|
"lube/pretty-jsdoc-casting": "error",
|
|
152
160
|
"lube/pretty-sequence": "error",
|
|
153
161
|
"lube/pretty-ternary": "error",
|
|
154
162
|
"lube/svelte-naming-convention": "error",
|
|
155
|
-
"no-await-in-loop": "warn",
|
|
156
163
|
"no-case-declarations": "off",
|
|
157
164
|
"no-console": "warn",
|
|
158
165
|
"no-duplicate-imports": [
|
|
@@ -238,6 +245,11 @@ const plugin = {
|
|
|
238
245
|
},
|
|
239
246
|
rules: {
|
|
240
247
|
"ascii-order": ascii_order,
|
|
248
|
+
"curly": keep_body_comments(
|
|
249
|
+
/** @type {import("eslint").Rule.RuleModule} */(builtinRules.get("curly"))/**/
|
|
250
|
+
),
|
|
251
|
+
"prefer-param-tag": prefer_param_tag,
|
|
252
|
+
"prefer-type-tag": prefer_type_tag,
|
|
241
253
|
"pretty-imports": pretty_imports,
|
|
242
254
|
"pretty-jsdoc-casting": pretty_jsdoc_casting,
|
|
243
255
|
"pretty-sequence": pretty_sequence,
|