eslint-plugin-lube 0.5.1 → 0.7.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 (88) hide show
  1. package/CHANGELOG.md +283 -0
  2. package/LICENSE +21 -0
  3. package/README.md +123 -9
  4. package/docs/ascii-order.md +81 -0
  5. package/docs/prefer-type-tag.md +45 -0
  6. package/docs/pretty-imports.md +70 -59
  7. package/docs/pretty-jsdoc-casting.md +31 -23
  8. package/docs/pretty-sequence.md +129 -117
  9. package/docs/pretty-ternary.md +55 -0
  10. package/docs/svelte-naming-convention.md +59 -46
  11. package/package.json +60 -38
  12. package/src/index.js +308 -0
  13. package/src/rules/ascii-order.js +1032 -0
  14. package/src/rules/prefer-type-tag.js +165 -0
  15. package/src/rules/pretty-imports.js +188 -0
  16. package/src/rules/pretty-jsdoc-casting.js +325 -0
  17. package/src/rules/pretty-sequence.js +562 -0
  18. package/src/rules/pretty-ternary.js +255 -0
  19. package/src/rules/svelte-naming-convention.js +1062 -0
  20. package/src/utils/fix_recorder.js +50 -0
  21. package/src/utils/keep_body_comments.js +53 -0
  22. package/src/utils/keep_comments.js +103 -0
  23. package/src/utils/keep_line_breaks.js +58 -0
  24. package/src/utils/skip_hole_commas.js +34 -0
  25. package/src/utils/skip_optional_patterns.js +29 -0
  26. package/src/utils/skip_unchanged_fixes.js +37 -0
  27. package/src/utils/source_text.js +476 -0
  28. package/src/utils/split_declarations.js +75 -0
  29. package/src/utils/text_width.js +9 -0
  30. package/types/index.d.ts +27 -13
  31. package/types/index.d.ts.map +1 -1
  32. package/index.js +0 -126
  33. package/private.d.ts +0 -168
  34. package/rules/pretty-imports.js +0 -147
  35. package/rules/pretty-jsdoc-casting.js +0 -236
  36. package/rules/pretty-sequence.js +0 -441
  37. package/rules/svelte-naming-convention.js +0 -361
  38. package/test/pretty-imports/ExportNamedDeclaration.js +0 -123
  39. package/test/pretty-imports/ImportDeclaration.js +0 -125
  40. package/test/pretty-imports.js +0 -20
  41. package/test/pretty-jsdoc-casting/case_by_expression.js +0 -289
  42. package/test/pretty-jsdoc-casting/various_situations.js +0 -111
  43. package/test/pretty-jsdoc-casting.js +0 -22
  44. package/test/pretty-sequence/case_by_node_type.js +0 -360
  45. package/test/pretty-sequence/case_by_option.js +0 -142
  46. package/test/pretty-sequence/various_situations.js +0 -204
  47. package/test/pretty-sequence.js +0 -23
  48. package/test/svelte-naming-convention/basic_snake_case.js +0 -67
  49. package/test/svelte-naming-convention/case_by_identifier.js +0 -1394
  50. package/test/svelte-naming-convention/snake_case_with_symbols.js +0 -67
  51. package/test/svelte-naming-convention.js +0 -25
  52. package/tsconfig.json +0 -21
  53. package/types/rules/pretty-imports.d.ts +0 -3
  54. package/types/rules/pretty-imports.d.ts.map +0 -1
  55. package/types/rules/pretty-jsdoc-casting.d.ts +0 -3
  56. package/types/rules/pretty-jsdoc-casting.d.ts.map +0 -1
  57. package/types/rules/pretty-sequence.d.ts +0 -3
  58. package/types/rules/pretty-sequence.d.ts.map +0 -1
  59. package/types/rules/svelte-naming-convention.d.ts +0 -3
  60. package/types/rules/svelte-naming-convention.d.ts.map +0 -1
  61. package/types/test/pretty-imports/ExportNamedDeclaration.d.ts +0 -2
  62. package/types/test/pretty-imports/ExportNamedDeclaration.d.ts.map +0 -1
  63. package/types/test/pretty-imports/ImportDeclaration.d.ts +0 -2
  64. package/types/test/pretty-imports/ImportDeclaration.d.ts.map +0 -1
  65. package/types/test/pretty-imports.d.ts +0 -2
  66. package/types/test/pretty-imports.d.ts.map +0 -1
  67. package/types/test/pretty-jsdoc-casting/case_by_expression.d.ts +0 -2
  68. package/types/test/pretty-jsdoc-casting/case_by_expression.d.ts.map +0 -1
  69. package/types/test/pretty-jsdoc-casting/various_situations.d.ts +0 -2
  70. package/types/test/pretty-jsdoc-casting/various_situations.d.ts.map +0 -1
  71. package/types/test/pretty-jsdoc-casting.d.ts +0 -2
  72. package/types/test/pretty-jsdoc-casting.d.ts.map +0 -1
  73. package/types/test/pretty-sequence/case_by_node_type.d.ts +0 -2
  74. package/types/test/pretty-sequence/case_by_node_type.d.ts.map +0 -1
  75. package/types/test/pretty-sequence/case_by_option.d.ts +0 -2
  76. package/types/test/pretty-sequence/case_by_option.d.ts.map +0 -1
  77. package/types/test/pretty-sequence/various_situations.d.ts +0 -2
  78. package/types/test/pretty-sequence/various_situations.d.ts.map +0 -1
  79. package/types/test/pretty-sequence.d.ts +0 -2
  80. package/types/test/pretty-sequence.d.ts.map +0 -1
  81. package/types/test/svelte-naming-convention/basic_snake_case.d.ts +0 -2
  82. package/types/test/svelte-naming-convention/basic_snake_case.d.ts.map +0 -1
  83. package/types/test/svelte-naming-convention/case_by_identifier.d.ts +0 -2
  84. package/types/test/svelte-naming-convention/case_by_identifier.d.ts.map +0 -1
  85. package/types/test/svelte-naming-convention/snake_case_with_symbols.d.ts +0 -2
  86. package/types/test/svelte-naming-convention/snake_case_with_symbols.d.ts.map +0 -1
  87. package/types/test/svelte-naming-convention.d.ts +0 -2
  88. package/types/test/svelte-naming-convention.d.ts.map +0 -1
package/CHANGELOG.md ADDED
@@ -0,0 +1,283 @@
1
+ # Changelog
2
+
3
+ ## 0.7.0
4
+
5
+ ### Breaking
6
+
7
+ - **The strict config requires braces on every block** with `lube/curly` (`all`): `if`, `else`, `for`,
8
+ `while` and `do` bodies are wrapped in `{ }`. It has a fix, so run `eslint --fix` once.
9
+ - **The strict config no longer reports `await` in a loop:** `no-await-in-loop` is gone from it.
10
+ - **The strict config moves the type cast of a declaration's value into a `@type` tag** with the new
11
+ `prefer-type-tag`, in a project linted with type information: `const a = /** @type {T} */(b)/**/`
12
+ becomes `/** @type {T} */` on the line before `const a = b`. Run `eslint --fix` once.
13
+
14
+ ### Added
15
+
16
+ - `prefer-type-tag`, in the strict config: reports the cast of the whole value of a declaration,
17
+ `const a = /** @type {T} */(b)/**/`, when TypeScript would accept a `/** @type {T} */` tag on the
18
+ declaration instead, and fixes it into one. A cast that narrows, such as `/** @type {T} */(list[0])/**/`
19
+ under `noUncheckedIndexedAccess`, one that widens to a union, such as `/** @type {T | null} */(null)/**/`,
20
+ one through `unknown` and `/** @type {const} */` stay. It reads types from the program of
21
+ `@typescript-eslint/parser` with `projectService` and checks nothing without one.
22
+ - `curly`, ESLint's own rule with the same options, whose fix puts the braces around a comment in
23
+ front of the body: `if (a) /** @type {A} */(b).c = 1` becomes `if (a) {/** @type {A} */(b).c = 1}`,
24
+ where ESLint's fix turned the cast into a comment before the brace, `if (a) /** @type {A} */{(b).c = 1}`.
25
+
26
+ ## 0.6.0
27
+
28
+ ### Breaking
29
+
30
+ - **Two configs replace `configs.plugins` and `configs.rules`.** `configs.strict` holds the rules
31
+ `configs.rules` held, the house style with renaming, sorting and core rules, and
32
+ `configs.recommended` is a lighter set that only formats and orders, with rules that all have a
33
+ fix. Each is a flat config object with `plugins` and `rules`: replace
34
+ `plugins: lube.configs.plugins, rules: { ...lube.configs.rules }` with
35
+ `plugins: lube.configs.strict.plugins, rules: { ...lube.configs.strict.rules }`, or spread
36
+ `...lube.configs.strict` into the config.
37
+ - **Every rule rejects options it does not know.** A misspelled option such as `maxLenght` is a
38
+ configuration error instead of being ignored, and `pretty-jsdoc-casting` takes no options.
39
+ - **`svelte-naming-convention` is a `suggestion` rule, no longer `layout`,** since its fix renames
40
+ identifiers: `eslint --fix --fix-type layout` leaves names alone now.
41
+ - **The package is an ES module only.** `import lube from "eslint-plugin-lube"` works as before on
42
+ Node.js 18.18+, 20.9+ and 21.1+, the versions `@stylistic/eslint-plugin` supports. A CommonJS
43
+ `eslint.config.cjs` still gets the plugin itself from `require("eslint-plugin-lube")` on Node.js
44
+ 20.19+ and 22.12+, which ESLint 10 requires anyway. Deep paths such as `eslint-plugin-lube/rules/ascii-order` are gone; the rules are
45
+ `lube.rules`.
46
+ - **Both configs use `@stylistic/eslint-plugin` instead of the deprecated core formatting
47
+ rules,** which ESLint 11 removes. Rename overrides of those rules:
48
+ ```js
49
+ // before
50
+ rules: { ...lube.configs.rules, "indent": ["error", 4] }
51
+ // after
52
+ rules: { ...lube.configs.strict.rules, "@stylistic/indent": ["error", 4] }
53
+ ```
54
+ `func-call-spacing` is `@stylistic/function-call-spacing`. `@stylistic/indent` also checks
55
+ TypeScript syntax, and `@stylistic/operator-linebreak` checks `=` of type aliases, which stays at the
56
+ end of the line: `type Name =` followed by `| "a"`.
57
+ - **The strict config reports blank lines** with `@stylistic/no-multiple-empty-lines` (`max: 0`)
58
+ and fixes `arrow-body-style`, `new-parens`, `no-extra-boolean-cast`, `no-extra-semi`,
59
+ `no-unneeded-ternary`, `no-useless-computed-key`, `no-useless-rename`, `no-useless-return`,
60
+ `prefer-exponentiation-operator`, `prefer-object-spread` and `yoda`. Run `eslint --fix` once.
61
+ - **The strict config reports functions assigned to variables** with `func-style`
62
+ (`declaration`): write `function name() {}` instead of `const name = () => {}`. Callbacks and
63
+ arrow functions that use `this` are not reported. It cannot be fixed.
64
+ - **The strict config sorts import statements, import and export names, object keys, function
65
+ declarations and test cases** with the new `ascii-order`, all of its options on. Run
66
+ `eslint --fix` once, then reorder by hand what it reports without a fix, or disable it where the
67
+ order matters: `// eslint-disable-next-line lube/ascii-order`. A report without a fix says why,
68
+ ``Move key "a" before "b" to keep keys in ASCII order; it is not fixed automatically because moving
69
+ `f()` could change the order of side effects, so reorder it by hand``, or that a comment or statement
70
+ next to the item would end up in the wrong place. Keys whose values construct a
71
+ built-in from literals, such as `{ updatedAt: new Date(), createdAt: new Date() }` and
72
+ `{ s: new Set([ 1 ]), m: new Map() }`, are fixed, and so are keys whose values are a `const`, a
73
+ namespace import or a function or class declaration that is never assigned, which no call can
74
+ change: `{ shadow: shadowed(), helper }`. A `const` or class counts only where the value comes
75
+ after its declaration and outside a function declaration, which could run before it, so a read
76
+ that throws in the temporal dead zone still throws after the calls written before it. A
77
+ construction that always throws, such as `new RegExp("(")`, `new Array(-1)` or `new Map([ 1 ])`,
78
+ is not moved, nor one that another runtime may reject or that allocates a lot while linting:
79
+ `new RegExp` with a flag other than `gimsuy` or with syntax newer than ES2018, such as
80
+ `new RegExp("a", "v")` or `(?i:a)`, and `new Array`, a buffer, a typed array or a `DataView` with a
81
+ number over 65536 in its arguments, such as `new Set(new Array(1e8))`. Its fix assumes modules can run
82
+ in any order and nothing reads key order: sorted keys change `Object.keys`, `JSON.stringify` and
83
+ `for...in`, and an import without names, such as `import "./setup.js"`, is never moved and no
84
+ import moves across it. Under `exports` and `imports` of a
85
+ `package.json` it puts `types` first and `default` last, as TypeScript and Node.js read them.
86
+ - **Both configs break nested and long ternaries** with the new `pretty-ternary`: a
87
+ ternary with a ternary branch, or longer than 80 characters, puts `?` and `:` at the start of their
88
+ lines, one indent deeper per level. Run `eslint --fix` once.
89
+ - **`pretty-imports` checks imports with a default import** such as `import def, {a,b} from "m"`,
90
+ which it skipped before.
91
+ - **`pretty-sequence` and `pretty-imports` leave the indentation of code that is already written over
92
+ several lines to `@stylistic/indent`,** and write it only for the line breaks they insert themselves.
93
+ The two rules owned the same lines before, so wherever the indent rule measures a line differently,
94
+ `eslint --fix` never finished. Turn `fixIndent` on in a config without an indent rule.
95
+ - **Both configs wrap the `@stylistic` rules whose fixes break code or never settle:**
96
+ `comma-style` leaves the comma of an array hole on its own line, `operator-linebreak` leaves an
97
+ operator a comment follows, `object-curly-newline` and `object-curly-spacing` skip optional
98
+ destructured parameters, `key-spacing`, `semi-spacing`, `space-unary-ops`, `generator-star-spacing`,
99
+ `rest-spread-spacing` and `yield-star-spacing` keep the comments in the space they remove, every fix
100
+ writes the line break of the file, and a fix that would change nothing is dropped.
101
+ - **`max-len` is in neither config,** since it cannot be fixed.
102
+ - **`svelte-naming-convention` reports more declarations.** Function expression names and
103
+ parameters, `catch` parameters, parameters of exported camelCase functions, non-shorthand
104
+ destructuring such as `const { key: camelCase } = value` and `import { "name" as camelCase }`
105
+ are reported now. Expect new errors on existing code.
106
+ - **`svelte-naming-convention` treats `for...in` and `for...of` targets as usages.** They are fixed
107
+ together with their declaration, like assignments, instead of being renamed on their own.
108
+ - **`pretty-jsdoc-casting` puts one empty comment after nested castings that end together,** after
109
+ the outermost one: `/** @type {A} */(/** @type {unknown} */(a))/**/` instead of
110
+ `/** @type {A} */(/** @type {unknown} */(a)/**/)/**/`. Run `eslint --fix` once.
111
+ - **Every rule message says what to change and why:** `Rename 'isV' to 'is_v'`,
112
+ ``Write these items on one line as `[ a, b ]`, since they fit in 30 characters``,
113
+ `Put each item and the closing bracket on a line of its own, since the items are longer than 30
114
+ characters together`, ``Break this ternary before `?` and `:`, since a branch is another ternary``,
115
+ `Move key "a" before "b" to keep keys in ASCII order`. The message IDs are new:
116
+ `pretty-sequence` reports `multiline`, `single_line` and `indent`; `pretty-imports` `multiline`,
117
+ `single_line`, `indent`, `semicolon` and `no_semicolon` for imports and exports alike;
118
+ `pretty-ternary` `multiline` and `spacing`; `pretty-jsdoc-casting` `marker` and `format`; and
119
+ `svelte-naming-convention` `rename` and `case`.
120
+
121
+ ### Fixed
122
+
123
+ - The `maxLength` of `pretty-sequence` and `pretty-imports` counts a character that takes two
124
+ columns, such as a Korean, Chinese or Japanese one, as two. A line of Korean strings and a line of
125
+ Latin ones that take the same width on screen are now broken or joined alike, where the Korean one
126
+ used to stay on one line at twice the width.
127
+ - The message that asks for one line said the items "fit in" the `maxLength`, although it measures
128
+ the items alone, without the code around them, so the line it wrote could be much longer. It now
129
+ names the width it measured: "since the items are 75 characters together, within the 80 allowed".
130
+ - `pretty-jsdoc-casting` added `/**/` after casts in TypeScript files, where a JSDoc cast means
131
+ nothing. `.ts`, `.tsx`, `.mts` and `.cts` files are not checked now.
132
+ - The `one-var` fix of the strict config left `const a = 1; const b = 2` on one line, which
133
+ `@stylistic/max-statements-per-line` reported without a fix. `eslint --fix` puts each of the
134
+ declarations on a line of its own now.
135
+ - `no-duplicate-imports` of the strict config reported `import type { A } from "m"` next to
136
+ `import { b } from "m"`. A separate type import is allowed now.
137
+ - The `@stylistic/space-unary-ops`, `rest-spread-spacing`, `generator-star-spacing`,
138
+ `yield-star-spacing`, `semi-spacing` and `key-spacing` fixes of both configs deleted a
139
+ comment in the space they remove, or moved a colon into one: `!/** @type {A} */(a)/**/` lost its
140
+ type cast, `.../* @__PURE__ */ f()` its comment, and `a // note` followed by `: 1` became
141
+ `a // note: 1`. Such a space is left unreported now.
142
+ - In a file whose line breaks are CRLF, CR, U+2028 or U+2029, `pretty-sequence`, `pretty-imports`
143
+ and `pretty-ternary` counted only LF as a line break, so they indented from the wrong line, wrote
144
+ LF line breaks into the file and never stopped fixing it; `pretty-sequence` also indented the lines
145
+ inside a string or a template that spans such lines, changing its value, `` f(`a `` + U+2028 +
146
+ `` b`, c) `` grew a tab in front of `b`, and `pretty-ternary` and `pretty-jsdoc-casting` read those
147
+ line breaks as none. Every rule and the `@stylistic` fixes of both configs follow the
148
+ line break of the file now.
149
+ - `ascii-order` sorted only the outermost of nested objects, patterns or import braces per
150
+ `eslint --fix` pass, one pass per nesting level, so deeply nested files needed more than the 10
151
+ passes ESLint runs. Nested ones are sorted in the same pass now.
152
+ - `ascii-order` swapped destructuring targets whose order matters: `var { b: x, a: x } = o` changed
153
+ the value of `x`, and `({ b: this.b, a: this.a } = o)` the order of the setter calls. They are
154
+ reported without a fix now.
155
+ - `pretty-imports` removed `type` from `import type` and `export type`, import attributes such as
156
+ `with { type: "json" }`, and comments between the braces.
157
+ - `pretty-imports` removed a semicolon another statement needs: the one before a statement on the
158
+ same line, `import { a } from "m";var b = 1` became `import { a } from "m"var b = 1`, and the one
159
+ that starts the next line, `;[a].map(f)`. It also added a semicolon before the `}` that closes a
160
+ namespace, which `@stylistic/semi` of both configs removed again on every fix.
161
+ - `pretty-jsdoc-casting` changed what a cast means: it moved the cast of an inner expression out of
162
+ the parentheses, `/** @type {X} */(/** @type {A} */(a).b)` became
163
+ `/** @type {A} *//** @type {X} */((a).b)`, deleted the text after the type of a type comment,
164
+ `/** @type {A} @deprecated */ (a)` lost `@deprecated`, turned the plain comment
165
+ `/* @type {A} */ (a)` into a type cast, and removed the empty comment after a type comment that
166
+ spans lines. A type comment is kept as written now unless it holds only the type. It also mangled
167
+ `/** @type {A} */(a)/**//**/`, left a double space for `/** @type {A} */ (a)` and moved a cast onto
168
+ the next line when a line break followed the type comment.
169
+ - `pretty-jsdoc-casting` moved what stood between the cast parenthesis and the expression in front
170
+ of the type comment: a line break, so `return /** @type {A} */(` followed by `b)/**/` returned
171
+ `undefined`; the JSDoc of a function inside the cast, which landed between `export default` and
172
+ the cast with broken indentation; and the white space of a cast written over several lines, which
173
+ put the cast on a new line and left the old one empty, so with either config every
174
+ `eslint --fix` added one more line and one more indent level. That white space is dropped now,
175
+ unless a line comment ends there, and a cast is left unfixed when another comment stands there or
176
+ when a line break after `return`, `throw` or `yield` would stay.
177
+ - `pretty-jsdoc-casting` read the argument parentheses of a call as cast parentheses when a type
178
+ comment stood before them, so `var x = 1` followed by `/** @type {T} */(f(a))` (a call, because no
179
+ semicolon ends the first line) got a `/**/` marker and never stopped fighting `pretty-sequence`.
180
+ The parentheses of a call, `new` or `import()` are left alone now.
181
+ - `pretty-sequence` and `pretty-jsdoc-casting` took time quadratic in the file size, and
182
+ `pretty-sequence` in the line length: a 258 KB file took about 40 seconds per rule and takes about
183
+ 0.1 seconds, and a 1.4 MB single-line file took 33 seconds and takes about 2 seconds, most of it
184
+ parsing.
185
+ - `pretty-sequence` broke TypeScript: `({ a, b }: Props) => a` became `({ a, b s) => a`, the
186
+ annotation of an array pattern was deleted, and `f<(x: number) => void>( a )` became `f<(a)`.
187
+ - `pretty-sequence` lost or moved comments and holes: a line comment after the last item commented
188
+ out the closing bracket, `[ b, c // note` followed by `]` became `[ b, c // note ]`; comments after
189
+ a trailing comma or inside empty brackets and the trailing hole of `[a, ,]` were deleted; and a
190
+ line comment after an item's comma moved onto the next item's line. It stays with its item now.
191
+ - `pretty-sequence` changed the value of a multiline template literal, a string with a line
192
+ continuation or a multiline JSX attribute string, `f(a, <div title="one` followed by ` two">)`,
193
+ by re-indenting its lines. Such sequences are left unfixed.
194
+ - `pretty-sequence`: with `funcCallSpacing`, `a => b` gained more spaces on every fix.
195
+ - `pretty-sequence` wrote indentation onto the empty lines of a function it moved to a new line.
196
+ - `pretty-sequence` and `@stylistic/indent` fixed each other endlessly ("Circular fixes detected")
197
+ for a sequence opening on the first line of a declaration whose last declarator starts on a later
198
+ line, `let i = [` … `], a = 1`, for a line starting with `;` as `@stylistic/semi-style: first`
199
+ writes it, for the arguments of a call that follows a multiline parenthesized expression on the
200
+ same line, `(a` `|| b).catch(c, d)` or `(a ? b : "") + f(c, d)`, and for a sequence on a line that
201
+ starts inside a block comment, `*/ const a = [b, c]`. `pretty-sequence` indents them like
202
+ `@stylistic/indent` now.
203
+ - In `.svelte` files, `pretty-sequence` and `pretty-ternary` indented a template expression such as
204
+ `{await f(` from the column where the markup before it started, and `@stylistic/indent` undid it
205
+ on every fix.
206
+ - The `pretty-sequence` rule description was copied from `pretty-jsdoc-casting`.
207
+ - `svelte-naming-convention` renamed usages as if they were declarations, arrow function bodies and
208
+ `extends` clauses among them, so references to imported or global names broke, and it left other
209
+ usages with the old name: `do...while` conditions, computed keys, class field values, `import()`,
210
+ JSX, decorators, TypeScript expressions such as `camelCase as T`, `camelCase!` and
211
+ `typeof camelCase`, `export = camelCase`, enum member values, type predicates, the overloads of a
212
+ function (only the implementation was renamed), the object of a JSX member tag such as
213
+ `<routeModule.Layout>`, and the markup of a `.svelte` file, where every node was read as a name
214
+ the rule must not touch, so `{camelCase}` in a mustache tag, an attribute, a directive or a block
215
+ expression stopped the rename. Every usage that scope analysis reaches is renamed with its
216
+ declaration now, a shorthand attribute becomes `camelCase={camel_case}`, what the markup declares,
217
+ such as the context of `{#each}` or a `let:` property, is left alone, and a name is reported
218
+ without a fix where the rule cannot tell a reference from a type or a property, such as
219
+ `let a: camelCase`, or where it is a component tag, `<My_Comp />`, which a lowercase name would
220
+ turn into an HTML tag.
221
+ - `svelte-naming-convention` broke destructuring and exports: `{ camelCase: camelCase }` was read as
222
+ a shorthand property and became `{ camelCase: camelCase: camel_case }`, a shorthand with a default,
223
+ `{ camelCase = 1 }` in a pattern, was never renamed while every other use of the name was, and
224
+ `export { camelCase }` lost its binding. They become `{ camelCase: camel_case }`,
225
+ `{ camelCase: camel_case = 1 }` and `export { camel_case as camelCase }` now, the declaration and
226
+ its export specifier in one fix, since separate fixes in one pass could leave `export` pointing at
227
+ a name that no longer exists, a syntax error.
228
+ - `svelte-naming-convention` matched usages to declarations by name only, so its fixes broke code
229
+ that the Svelte code base holds: a global or an exported camelCase function used where a parameter
230
+ of the same name was renamed elsewhere was renamed too, e.g. `requestAnimationFrame()` or
231
+ `flushSync()` became undefined; the usages of an import, `import { fetchModule }`, were renamed
232
+ with a same-named declaration elsewhere but not the import; a name whose snake_case form is already
233
+ a variable was renamed into it, e.g. `itemCount` next to `item_count`; the type annotation and `?`
234
+ of a TypeScript name were deleted, `itemCount?: number` became `item_count`; a parameter mentioned
235
+ in the prose of a JSDoc comment was renamed while `@param {T} callbackFn`, `[superClass]`,
236
+ `{callbackFn is F}` and `typeof callbackFn` kept the old name and failed type checking; and a store
237
+ read as `$userStore` lost its declaration. Usages follow scope analysis now, an import becomes
238
+ `import { fetchModule as fetch_module }` and `import { foo_bar as fooBar }` becomes
239
+ `import { foo_bar }`, a colliding rename is reported without a fix, only `typeof`, `asserts` and
240
+ `is` inside `{ }` of a JSDoc comment are renamed with the parameter, and in a `.svelte` file the
241
+ `$camelCase` subscriptions are renamed with the store in one fix, while outside one `$camelCase`
242
+ stays a name of its own and nothing is renamed.
243
+ - `svelte-naming-convention` renamed only part of a name and broke the code when another rule's fix
244
+ overlapped a usage in the same `eslint --fix` pass, such as one inside an argument list
245
+ `pretty-sequence` reformats, or when a declaration of the name is one the rule does not rename: a
246
+ function merged with a TypeScript namespace of the same name, as in `.d.ts` files,
247
+ `declare namespace pLimit` and `declare function pLimit(): pLimit.Limit`, a declaration exported
248
+ with `export var` next to a `var` of the same name, a parameter that shadows a function of the same
249
+ name, and a declaration turned off with `// eslint-disable-next-line`, `// eslint-disable-line` or
250
+ `/* eslint-disable */`, whose usages were renamed anyway. With `fixSameNames`, usages are renamed
251
+ first and the declaration in a later pass, once no usage of the old name is left, together with the
252
+ assignments to it and an `export default` of it, so `prefer-const` of the strict config never
253
+ turns `let lastBid = 1` into a `const` whose `lastBid += 1` was renamed in an earlier pass, and
254
+ `ascii-order` never moves a default-exported function while the export names it differently; a
255
+ name whose snake_case form is declared elsewhere is renamed in one fix with its usages; with
256
+ `fixSameNames: false` a declaration is renamed with its usages instead of without them; and the
257
+ other names are reported without a fix.
258
+
259
+ ### Added
260
+
261
+ - `pretty-sequence` and `pretty-imports` take `fixIndent`. With it on, the rule reports a sequence or a
262
+ list of specifiers whose lines differ from it only in their indentation, as it always did.
263
+ - `ascii-order`, in the strict config: sorts import statements by module path, names in import
264
+ and export braces, object keys, function declarations next to each other and `describe`, `it` and
265
+ `test` calls in ASCII order, each turned off with `checkImports`, `checkNames`, `checkKeys`,
266
+ `checkDeclarations` or `checkTests`. Side-effect imports and a function exported with
267
+ `export default name` keep their place. When moving an item could change what the code does, such
268
+ as swapping two object values that call functions, it reports without a fix.
269
+ - `pretty-ternary`, in both configs: breaks a ternary or TypeScript conditional type
270
+ before `?` and `:` when a branch is another ternary, when it is longer than `maxLength` (80) or
271
+ when it already spans lines, indenting each nested level once more. Only the spaces around `?`
272
+ and `:` change.
273
+ - `configs.recommended`, for format on save: the `@stylistic` formatting rules, `pretty-imports`,
274
+ `pretty-jsdoc-casting`, `pretty-sequence` and `pretty-ternary` with items on one line up to 80
275
+ characters, and `ascii-order` for the names inside import and export braces only. It ends a file
276
+ with a line break, allows one blank line, never renames or moves statements or keys, and every
277
+ rule in it has a fix: `{ ...lube.configs.recommended, files: ["**/*.js"] }`, or
278
+ `extends: ["lube/recommended"]` with `defineConfig`.
279
+ - `configs.strict`, the house style: everything above plus `svelte-naming-convention`, every
280
+ `ascii-order` check, `pretty-sequence` and `pretty-imports` at 30 characters, no blank lines, no
281
+ line break at the end of a file, one statement per line and core rules such as `func-style`,
282
+ `no-shadow` and `prefer-const`.
283
+ - `meta.version`, so ESLint can cache results per plugin version.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2023 artxe
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 CHANGED
@@ -1,9 +1,123 @@
1
- # Eslint plugin lube
2
- ## Full fixable rules for auto-formatting with ESLint only without Prettier
3
-
4
- | Rule | Docs | Code | Test |
5
- |---|---|---|---|
6
- | pretty-imports | [docs](https://github.com/Artxe2/lube-series/blob/master/packages/eslint-plugin-lube/docs/pretty-imports.md) | [code](https://github.com/Artxe2/lube-series/blob/master/packages/eslint-plugin-lube/rules/pretty-imports.js) | [test](https://github.com/Artxe2/lube-series/blob/master/packages/eslint-plugin-lube/test/pretty-imports) |
7
- | pretty-jsdoc-casting | [docs](https://github.com/Artxe2/lube-series/blob/master/packages/eslint-plugin-lube/docs/pretty-jsdoc-casting.md) | [code](https://github.com/Artxe2/lube-series/blob/master/packages/eslint-plugin-lube/rules/pretty-jsdoc-casting.js) | [test](https://github.com/Artxe2/lube-series/blob/master/packages/eslint-plugin-lube/test/pretty-jsdoc-casting) |
8
- | pretty-sequence | [docs](https://github.com/Artxe2/lube-series/blob/master/packages/eslint-plugin-lube/docs/pretty-sequence.md) | [code](https://github.com/Artxe2/lube-series/blob/master/packages/eslint-plugin-lube/rules/pretty-sequence.js) | [test](https://github.com/Artxe2/lube-series/blob/master/packages/eslint-plugin-lube/test/pretty-sequence) |
9
- | svelte-naming-convention | [docs](https://github.com/Artxe2/lube-series/blob/master/packages/eslint-plugin-lube/docs/svelte-naming-convention.md) | [code](https://github.com/Artxe2/lube-series/blob/master/packages/eslint-plugin-lube/rules/svelte-naming-convention.js) | [test](https://github.com/Artxe2/lube-series/blob/master/packages/eslint-plugin-lube/test/svelte-naming-convention) |
1
+ # ESLint Plugin Lube
2
+ Fixable formatting and ordering rules, so `eslint --fix` formats a project without Prettier.
3
+
4
+ Instead of Prettier next to ESLint, with a second config and a plugin to keep the two from fighting, formatting is a set of ESLint rules: one `eslint --fix` formats and lints, and each rule can be tuned or turned off like any other.
5
+
6
+ ## Installation
7
+ ```bash
8
+ npm i -D eslint eslint-plugin-lube
9
+ ```
10
+ Requires ESLint 9 or later and Node.js 18.18+, 20.9+ or 21.1+; a CommonJS `eslint.config.cjs` needs Node.js 20.19+ or 22.12+ to `require` it. `@stylistic/eslint-plugin` is installed with the plugin.
11
+
12
+ ## Usage
13
+ ```js
14
+ // eslint.config.js
15
+ import lube from "eslint-plugin-lube"
16
+
17
+ export default [
18
+ {
19
+ ...lube.configs.recommended,
20
+ files: ["**/*.js", "**/*.ts"]
21
+ }
22
+ ]
23
+ ```
24
+ `eslint --fix` with `recommended` turns this:
25
+ ```js
26
+ import {writeFile,readFile} from 'node:fs/promises';
27
+
28
+ export async function copy(from,to){
29
+ const text=await readFile(from,'utf8');
30
+ await writeFile(to,text.replace(/\r\n/g,'\n'),{encoding:'utf8',flag:'wx',mode:0o644,flush:true});
31
+ return text.length>1000?text.length>100000?'large':'medium':'small';
32
+ }
33
+ ```
34
+ into this:
35
+ ```js
36
+ import { readFile, writeFile } from "node:fs/promises"
37
+
38
+ export async function copy(from, to) {
39
+ const text = await readFile(from, "utf8")
40
+ await writeFile(
41
+ to,
42
+ text.replace(/\r\n/g, "\n"),
43
+ { encoding: "utf8", flag: "wx", mode: 0o644, flush: true }
44
+ )
45
+ return text.length > 1000
46
+ ? text.length > 100000
47
+ ? "large"
48
+ : "medium"
49
+ : "small"
50
+ }
51
+ ```
52
+
53
+ For TypeScript files, add the parser from `@typescript-eslint/parser`:
54
+ ```js
55
+ // eslint.config.js
56
+ import tsParser from "@typescript-eslint/parser"
57
+ import lube from "eslint-plugin-lube"
58
+
59
+ export default [
60
+ {
61
+ ...lube.configs.recommended,
62
+ files: ["**/*.js", "**/*.ts"],
63
+ languageOptions: { parser: tsParser }
64
+ }
65
+ ]
66
+ ```
67
+ With `defineConfig`, extend the config by name:
68
+ ```js
69
+ // eslint.config.js
70
+ import { defineConfig } from "eslint/config"
71
+ import lube from "eslint-plugin-lube"
72
+
73
+ export default defineConfig({
74
+ extends: ["lube/recommended"],
75
+ files: ["**/*.js"],
76
+ plugins: { lube }
77
+ })
78
+ ```
79
+
80
+ ### Choosing a config
81
+ Both configs are flat config objects with `plugins` and `rules`, and differ in how much they decide for you. Start with `recommended`; take `strict` for a code base that follows the Svelte code conventions or wants every choice made for it.
82
+
83
+ **`configs.recommended`** only formats and orders, and every rule in it has a fix, so it suits format on save.
84
+ - The `@stylistic` formatting rules: tab indentation, double quotes, no semicolons, spacing and line breaks.
85
+ - `pretty-imports`, `pretty-jsdoc-casting`, `pretty-sequence` and `pretty-ternary`, with items on one line up to 80 characters.
86
+ - `ascii-order` for the names inside import and export braces only.
87
+ - A line break at the end of a file and at most one blank line, as editors write them.
88
+ - It never renames an identifier, never moves an import, key, function or test, and reports nothing about what the code does.
89
+
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
+ - `svelte-naming-convention`, which renames declarations to snake_case.
92
+ - `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).
93
+ - `ascii-order` for import statements, object keys, function declarations and test cases too (see below).
94
+ - `pretty-sequence` and `pretty-imports` at 30 characters.
95
+ - No blank lines, no line break at the end of a file and one statement per line.
96
+ - `curly` (braces on every block) and core rules such as `func-style` (declarations), `no-shadow`, `prefer-const`, `one-var` (never) and `no-console`.
97
+
98
+ 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`.
99
+ - Import a module that has to run first without names, `import "./setup.js"`, which is never moved.
100
+ - Disable the rule where key or import order matters; [ascii-order](https://github.com/artxe/lube-series/blob/master/packages/eslint-plugin-lube/docs/ascii-order.md) lists what it does not fix.
101
+
102
+ ### Changing a rule
103
+ Override a rule after the spread: `rules: { ...lube.configs.recommended.rules, "@stylistic/indent": ["error", 4] }`.
104
+ - Formatting rules go by their `@stylistic/` name.
105
+ - An indentation of your own also goes to the `indent` option of `pretty-imports`, `pretty-sequence` and `pretty-ternary`, or those rules and `@stylistic/indent` keep rewriting each other's fixes.
106
+ - `pretty-sequence` writes the items of an array, object or call one per line once they are longer than its `maxLength` together, 80 in `recommended` and 30 in `strict`: `"lube/pretty-sequence": ["error", { maxLength: 100 }]`.
107
+ - Every option of a rule is checked, so a misspelled option is a configuration error.
108
+
109
+ ## Rules
110
+ | Rule | Enforces | `recommended` |
111
+ | --- | --- | --- |
112
+ | [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 |
113
+ | [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 |
114
+ | [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 |
115
+ | [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 |
116
+ | [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 |
117
+ | [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 |
118
+ | [pretty-ternary](https://github.com/artxe/lube-series/blob/master/packages/eslint-plugin-lube/docs/pretty-ternary.md) | Nested and long ternaries broken before `?` and `:`, one indent deeper per level | yes |
119
+ | [svelte-naming-convention](https://github.com/artxe/lube-series/blob/master/packages/eslint-plugin-lube/docs/svelte-naming-convention.md) | snake_case declarations, following the Svelte code conventions | no |
120
+
121
+ ## What it is not
122
+ - **Not a formatter you run on its own.** There is no CLI and no config file: the rules run inside `eslint --fix`, and a file ESLint does not lint is not formatted.
123
+ - **Not a whole style guide.** `recommended` only formats and orders, and every rule it turns on has a fix. The opinions, such as renaming and the core rules, are in `strict`, which is the house style of this repository rather than a recommendation.
@@ -0,0 +1,81 @@
1
+ # ascii-order
2
+ Sorts import statements, import and export names, object keys, function declarations and test cases in ASCII order, so uppercase before lowercase. The strict config turns all five on; the recommended config only sorts names inside import and export braces, which never changes what the code does.
3
+
4
+ - **Import statements** next to each other, by module path: `"../a.js"`, `"./b.js"`, `"c"`, `"node:d"`. Side-effect imports such as `import "polyfill"` keep their place and split the imports into groups.
5
+ - **Names** in import and export braces, by the name written first: `import def, { B, a as c }`, `export { a as z, b }`.
6
+ - **Keys** of object literals, destructuring patterns, TypeScript type literals and interfaces. Spread, rest, computed keys and index signatures split the keys into groups sorted on their own. Under `exports` and `imports` of a `package.json`, where conditions match in order, `types` goes first and `default` last, and the conditions between them are in ASCII order.
7
+ - **Function declarations** next to each other in the same block, TypeScript overloads included. A function exported with `export default name` keeps its place.
8
+ - **Test cases**: `describe`, `it` and `test` calls next to each other with a string title, `it.skip` and `test.only` included.
9
+
10
+ Comments on their own lines above an item and comments after it on the same line move with it.
11
+ Nested objects, patterns and braces are sorted in the same pass.
12
+
13
+ The fix assumes three things it cannot see, and changes behavior where they do not hold:
14
+ - **Modules evaluate in any order.** Moving an import statement changes the order in which its module and the modules it imports run their top-level code. A module whose top-level code has to run first, such as a polyfill or a setup file, is imported without names, `import "./setup.js"`, which the rule never moves and never moves another import across. Disable the rule for the other imports whose order matters.
15
+ - **Key order is not observed.** Sorting keys changes the order of `Object.keys`, `Object.entries`, `JSON.stringify` and `for...in`, so an object literal serialized or iterated in a written order changes its output, and a destructuring pattern reads the properties, running their getters, in the new order. Integer-like keys such as `2` and `10` are listed in numeric order by JavaScript anyway. A key written twice keeps its last value.
16
+ - **Tests do not depend on each other.**
17
+
18
+ Where the order matters, disable the rule for that statement, `// eslint-disable-next-line lube/ascii-order`, or turn off one kind with the options below.
19
+
20
+ Beyond that, only the error is reported, without a fix, when moving would change what the code evaluates or where comments end up. The message then says why and names the value that keeps it in place, as in ``Move key "a" before "b" to keep keys in ASCII order; it is not fixed automatically because moving `f()` could change the order of side effects, so reorder it by hand``:
21
+ - An object value that calls a function, constructs an object, assigns, updates, deletes, awaits or yields would change places with another value that is not a constant or a function. A variable is a constant when it is a `const`, a namespace import (`import * as x`), or a function or class declaration that is never assigned, and the value is the variable itself, also inside an array, `!x`, `typeof x`, `x === y`, `a ? x : y` or `x ?? y`; `x.y`, `` `${x}` ``, `-x`, `x + 1` and `...x` can run code of `x`. A `let`, `var`, parameter, global or named import, which the exporting module can reassign, still counts as a variable a call may change, so `{ b: f(), a: x }` is fixed only for such a constant `x`. A `const` or class counts only where the value comes after its declaration and not inside a function declaration, which can run before it: reading it earlier throws in its temporal dead zone, and moving the read before `f()` would throw before `f()` runs. Constructing a built-in counts as reading a variable: it reads the global of the constructor, which a call may replace, so it never moves across a call, while `{ updatedAt: new Date(), createdAt: new Date() }` and `{ s: new Set([ 1 ]), m: new Map() }` are fixed: `new` of `Array`, `ArrayBuffer`, `Date`, `Map`, `RegExp`, `Set`, `WeakMap`, `WeakSet`, the typed arrays such as `Uint8Array`, and `Error` and its subclasses except `AggregateError`, when the name is not declared in the file and every argument is built only from literals, `undefined`, `NaN`, `Infinity`, templates without expressions, negative numbers, array and object literals and such constructions, and when the construction does not throw with those arguments, which the rule tries while linting: `new RegExp("(")`, `new Array(-1)`, `new Set(1)`, `new Map([ 1 ])`, `new WeakSet([ 1 ])` and `new URL("/a")` would throw before the values moved in front of them run, and are not fixed. The runtime that lints is not the one that runs the code, so `new RegExp` is fixed only with the flags `g`, `i`, `m`, `s`, `u` and `y` and the syntax of ES2018: `new RegExp("a", "v")`, the `d` flag, modifiers such as `(?i:a)` and a group name used twice may throw in an older runtime, and are not fixed. The same goes for `DataView` and `URL`, as in `new DataView(new ArrayBuffer(8))` and `new URL("a", "https://example.com")`. An `Array`, `ArrayBuffer`, typed array or `DataView` with a number, a numeric string or an object property over 65536 in its arguments, as in `new Array(1e8)`, `new ArrayBuffer(8, { maxByteLength: 1e9 })` or `new Uint8Array({ length: 1e9 })`, is not tried and not fixed, since trying it would allocate that much while linting. Two `new Date()` values may then read the clock in the other order. `new Date(x)` and `new Set(items)` can run code of `x` and `items`, and are not fixed.
22
+ - A destructuring default that is not a constant would change places with another key.
23
+ - A destructuring target is assigned by two keys, or a member such as `this.a` would change places with another key.
24
+ - A comment shares a line with two items, or a line comment would end up before a closing bracket.
25
+ - The first statement of a file has comments before it and would move.
26
+ - A statement without a semicolon would end up on a line with the next one, as in `import b from "b"; import a from "a"`.
27
+
28
+ ## options
29
+ ```js
30
+ schema: [
31
+ {
32
+ additionalProperties: false,
33
+ properties: {
34
+ checkDeclarations: { default: true, type: "boolean" },
35
+ checkImports: { default: true, type: "boolean" },
36
+ checkKeys: { default: true, type: "boolean" },
37
+ checkNames: { default: true, type: "boolean" },
38
+ checkTests: { default: true, type: "boolean" }
39
+ },
40
+ type: "object"
41
+ }
42
+ ]
43
+ ```
44
+ ### checkDeclarations
45
+ If it is `true`, it sorts function declarations.
46
+ ### checkImports
47
+ If it is `true`, it sorts import statements.
48
+ ### checkKeys
49
+ If it is `true`, it sorts object keys.
50
+ ### checkNames
51
+ If it is `true`, it sorts the names in import and export braces.
52
+ ### checkTests
53
+ If it is `true`, it sorts test cases.
54
+
55
+ ## Valid
56
+ ```js
57
+ import { A, B, a } from "a-module"
58
+ import value from "b-module"
59
+ const object = { a: 1, b: 2, ...rest, a_b: 3 }
60
+ function main() {}
61
+ function b() {}
62
+ function c() {}
63
+ export default main
64
+ it("a", () => {})
65
+ it("b", () => {})
66
+ ```
67
+
68
+ ## Invalid
69
+ ```js
70
+ import other from "b-module"
71
+ import { y, x } from "a-module"
72
+ const value = { b: 1, a: 2 }
73
+ function b() {}
74
+ function a() {}
75
+ it("b", () => {})
76
+ it("a", () => {})
77
+ // reported without a fix: g() would run before f()
78
+ const later = { b: f(), a: g() }
79
+ // fixed: constructing a built-in from literals runs no code of the file
80
+ const empty = { s: new Set([ 1 ]), m: new Map() }
81
+ ```
@@ -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
+ ```