eslint-plugin-lube 0.6.0 → 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.
- package/CHANGELOG.md +23 -0
- package/README.md +4 -1
- package/docs/prefer-type-tag.md +45 -0
- package/package.json +1 -1
- package/src/index.js +11 -2
- package/src/rules/ascii-order.js +163 -71
- package/src/rules/prefer-type-tag.js +165 -0
- package/src/rules/pretty-imports.js +24 -12
- package/src/rules/pretty-jsdoc-casting.js +76 -29
- package/src/rules/pretty-sequence.js +110 -37
- package/src/rules/pretty-ternary.js +42 -14
- package/src/rules/svelte-naming-convention.js +281 -122
- 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 +3 -1
- 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,28 @@
|
|
|
1
1
|
# Changelog
|
|
2
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
|
+
|
|
3
26
|
## 0.6.0
|
|
4
27
|
|
|
5
28
|
### Breaking
|
package/README.md
CHANGED
|
@@ -89,10 +89,11 @@ 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-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
93
|
- `ascii-order` for import statements, object keys, function declarations and test cases too (see below).
|
|
93
94
|
- `pretty-sequence` and `pretty-imports` at 30 characters.
|
|
94
95
|
- No blank lines, no line break at the end of a file and one statement per line.
|
|
95
|
-
-
|
|
96
|
+
- `curly` (braces on every block) and core rules such as `func-style` (declarations), `no-shadow`, `prefer-const`, `one-var` (never) and `no-console`.
|
|
96
97
|
|
|
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`.
|
|
98
99
|
- Import a module that has to run first without names, `import "./setup.js"`, which is never moved.
|
|
@@ -109,6 +110,8 @@ Override a rule after the spread: `rules: { ...lube.configs.recommended.rules, "
|
|
|
109
110
|
| Rule | Enforces | `recommended` |
|
|
110
111
|
| --- | --- | --- |
|
|
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 |
|
|
112
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 |
|
|
113
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 |
|
|
114
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 |
|
|
@@ -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
package/src/index.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import ascii_order from "./rules/ascii-order.js"
|
|
2
|
+
import prefer_type_tag from "./rules/prefer-type-tag.js"
|
|
2
3
|
import pretty_imports from "./rules/pretty-imports.js"
|
|
3
4
|
import pretty_jsdoc_casting from "./rules/pretty-jsdoc-casting.js"
|
|
4
5
|
import pretty_sequence from "./rules/pretty-sequence.js"
|
|
5
6
|
import pretty_ternary from "./rules/pretty-ternary.js"
|
|
6
7
|
import svelte_naming_convention from "./rules/svelte-naming-convention.js"
|
|
8
|
+
import { keep_body_comments } from "./utils/keep_body_comments.js"
|
|
7
9
|
import {
|
|
8
10
|
keep_comment_order,
|
|
9
11
|
keep_comments
|
|
@@ -14,8 +16,10 @@ import { skip_optional_patterns } from "./utils/skip_optional_patterns.js"
|
|
|
14
16
|
import { skip_unchanged_fixes } from "./utils/skip_unchanged_fixes.js"
|
|
15
17
|
import { split_declarations } from "./utils/split_declarations.js"
|
|
16
18
|
import stylistic_plugin from "@stylistic/eslint-plugin"
|
|
19
|
+
import { builtinRules } from "eslint/use-at-your-own-risk"
|
|
17
20
|
import { readFileSync } from "node:fs"
|
|
18
|
-
|
|
21
|
+
/** @type {import("eslint").ESLint.Plugin} */
|
|
22
|
+
const stylistic = stylistic_plugin
|
|
19
23
|
/** @type {{ version: string }} */
|
|
20
24
|
const manifest = JSON.parse(
|
|
21
25
|
readFileSync(
|
|
@@ -147,12 +151,13 @@ const strict_rules = {
|
|
|
147
151
|
checkTests: true
|
|
148
152
|
}
|
|
149
153
|
],
|
|
154
|
+
"lube/curly": [ "error", "all" ],
|
|
155
|
+
"lube/prefer-type-tag": "error",
|
|
150
156
|
"lube/pretty-imports": "error",
|
|
151
157
|
"lube/pretty-jsdoc-casting": "error",
|
|
152
158
|
"lube/pretty-sequence": "error",
|
|
153
159
|
"lube/pretty-ternary": "error",
|
|
154
160
|
"lube/svelte-naming-convention": "error",
|
|
155
|
-
"no-await-in-loop": "warn",
|
|
156
161
|
"no-case-declarations": "off",
|
|
157
162
|
"no-console": "warn",
|
|
158
163
|
"no-duplicate-imports": [
|
|
@@ -238,6 +243,10 @@ const plugin = {
|
|
|
238
243
|
},
|
|
239
244
|
rules: {
|
|
240
245
|
"ascii-order": ascii_order,
|
|
246
|
+
"curly": keep_body_comments(
|
|
247
|
+
/** @type {import("eslint").Rule.RuleModule} */(builtinRules.get("curly"))/**/
|
|
248
|
+
),
|
|
249
|
+
"prefer-type-tag": prefer_type_tag,
|
|
241
250
|
"pretty-imports": pretty_imports,
|
|
242
251
|
"pretty-jsdoc-casting": pretty_jsdoc_casting,
|
|
243
252
|
"pretty-sequence": pretty_sequence,
|
package/src/rules/ascii-order.js
CHANGED
|
@@ -66,7 +66,9 @@ const shebang_types = new Set([ "Hashbang", "Shebang" ])
|
|
|
66
66
|
*/
|
|
67
67
|
function compare(a, b) {
|
|
68
68
|
const rank = (a.rank ?? 1) - (b.rank ?? 1)
|
|
69
|
-
if (rank)
|
|
69
|
+
if (rank) {
|
|
70
|
+
return rank
|
|
71
|
+
}
|
|
70
72
|
return a.name < b.name
|
|
71
73
|
? -1
|
|
72
74
|
: a.name > b.name
|
|
@@ -81,7 +83,9 @@ function compare(a, b) {
|
|
|
81
83
|
function count_same_line(comments, line) {
|
|
82
84
|
let count = 0
|
|
83
85
|
for (const comment of comments) {
|
|
84
|
-
if (comment.loc.start.line != line)
|
|
86
|
+
if (comment.loc.start.line != line) {
|
|
87
|
+
break
|
|
88
|
+
}
|
|
85
89
|
line = comment.loc.end.line
|
|
86
90
|
count++
|
|
87
91
|
}
|
|
@@ -120,7 +124,9 @@ function create_reorder(context) {
|
|
|
120
124
|
last_token = /** @type {import("eslint").AST.Token} */(source_code.getTokenBefore(last_token))/**/
|
|
121
125
|
} else if (layout == "sequence") {
|
|
122
126
|
const next = source_code.getTokenAfter(last_token)
|
|
123
|
-
if (next?.value == ",")
|
|
127
|
+
if (next?.value == ",") {
|
|
128
|
+
separator = next
|
|
129
|
+
}
|
|
124
130
|
}
|
|
125
131
|
const inner = /** @type {import("../../private.js").Comment[]} */(source_code.getCommentsAfter(last_token))/**/
|
|
126
132
|
let code_end = last_token.range[1]
|
|
@@ -130,7 +136,9 @@ function create_reorder(context) {
|
|
|
130
136
|
if (separator) {
|
|
131
137
|
if (inner.some(
|
|
132
138
|
comment => comment.type == "Line"
|
|
133
|
-
))
|
|
139
|
+
)) {
|
|
140
|
+
return undefined
|
|
141
|
+
}
|
|
134
142
|
code_end = inner[inner.length - 1]?.range[1] ?? code_end
|
|
135
143
|
line = separator.loc.end.line
|
|
136
144
|
tail = /** @type {import("../../private.js").Comment[]} */(source_code.getCommentsAfter(separator))/**/
|
|
@@ -147,9 +155,13 @@ function create_reorder(context) {
|
|
|
147
155
|
const last_comment = tail[tail_count - 1]
|
|
148
156
|
const after = last_comment ? last_comment.range[1] : sep_end
|
|
149
157
|
let next = after
|
|
150
|
-
while (text[next] == " " || text[next] == "\t")
|
|
158
|
+
while (text[next] == " " || text[next] == "\t") {
|
|
159
|
+
next++
|
|
160
|
+
}
|
|
151
161
|
let previous = start
|
|
152
|
-
while (text[previous - 1] == " " || text[previous - 1] == "\t")
|
|
162
|
+
while (text[previous - 1] == " " || text[previous - 1] == "\t") {
|
|
163
|
+
previous--
|
|
164
|
+
}
|
|
153
165
|
const declaration = entry.last.type == "ExportNamedDeclaration"
|
|
154
166
|
? entry.last.declaration
|
|
155
167
|
: entry.last
|
|
@@ -184,14 +196,18 @@ function create_reorder(context) {
|
|
|
184
196
|
const items = []
|
|
185
197
|
for (const entry of entries) {
|
|
186
198
|
const item = get_item(entry, layout, get_level)
|
|
187
|
-
if (!item)
|
|
199
|
+
if (!item) {
|
|
200
|
+
return null
|
|
201
|
+
}
|
|
188
202
|
items.push(item)
|
|
189
203
|
}
|
|
190
204
|
const sorted = items.slice().sort(
|
|
191
205
|
(a, b) => compare(a.entry, b.entry)
|
|
192
206
|
)
|
|
193
207
|
const blocker = find_blocker(items, sorted, reported)
|
|
194
|
-
if (blocker)
|
|
208
|
+
if (blocker) {
|
|
209
|
+
return blocker
|
|
210
|
+
}
|
|
195
211
|
const last_index = items.length - 1
|
|
196
212
|
let result = ""
|
|
197
213
|
for (let i = 0; i <= last_index; i++) {
|
|
@@ -205,9 +221,13 @@ function create_reorder(context) {
|
|
|
205
221
|
|| slot.tail_start < slot.after && !slot.break_after && (i < last_index || !slot.closes)
|
|
206
222
|
|| has_tail && !slot.break_after && (i < last_index || item.tail_line)
|
|
207
223
|
|| layout == "statement" && i < last_index && !item.terminated && !slot.break_after
|
|
208
|
-
)
|
|
224
|
+
) {
|
|
225
|
+
return null
|
|
226
|
+
}
|
|
209
227
|
result += source.render(item.start, item.code_end) + source.render(slot.code_end, slot.sep_end)
|
|
210
|
-
if (has_tail)
|
|
228
|
+
if (has_tail) {
|
|
229
|
+
result += " " + source.render(item.tail_start, item.after)
|
|
230
|
+
}
|
|
211
231
|
if (i < last_index) {
|
|
212
232
|
result += source.render(
|
|
213
233
|
slot.after,
|
|
@@ -217,7 +237,9 @@ function create_reorder(context) {
|
|
|
217
237
|
}
|
|
218
238
|
const first = /** @type {import("../../private.js").OrderItem} */(items[0])/**/
|
|
219
239
|
const last = /** @type {import("../../private.js").OrderItem} */(items[last_index])/**/
|
|
220
|
-
if (last.semicolon && !(/** @type {import("../../private.js").OrderItem} */(sorted[last_index])/**/).semicolon)
|
|
240
|
+
if (last.semicolon && !(/** @type {import("../../private.js").OrderItem} */(sorted[last_index])/**/).semicolon) {
|
|
241
|
+
return null
|
|
242
|
+
}
|
|
221
243
|
source.edit(first.start, last.after, result)
|
|
222
244
|
return {
|
|
223
245
|
range: [ first.start, last.after ],
|
|
@@ -260,7 +282,9 @@ function create_reorder(context) {
|
|
|
260
282
|
)
|
|
261
283
|
const entry = entries[index]
|
|
262
284
|
const previous = entries[index - 1]
|
|
263
|
-
if (!entry || !previous)
|
|
285
|
+
if (!entry || !previous) {
|
|
286
|
+
return
|
|
287
|
+
}
|
|
264
288
|
const replacement = get_replacement(entries, layout, entry, get_level)
|
|
265
289
|
const fix = replacement && "range" in replacement ? replacement : null
|
|
266
290
|
const blocker = replacement && "entry" in replacement ? replacement : null
|
|
@@ -341,13 +365,19 @@ function is_portable_regexp(args) {
|
|
|
341
365
|
const [ pattern, flags ] = args
|
|
342
366
|
const source = pattern instanceof RegExp ? pattern.source : pattern
|
|
343
367
|
const flag_text = flags === undefined && pattern instanceof RegExp ? pattern.flags : flags
|
|
344
|
-
if (typeof source != "string" && source !== undefined || typeof flag_text != "string" && flag_text !== undefined)
|
|
345
|
-
|
|
368
|
+
if (typeof source != "string" && source !== undefined || typeof flag_text != "string" && flag_text !== undefined) {
|
|
369
|
+
return false
|
|
370
|
+
}
|
|
371
|
+
if (flag_text && !portable_flags_regex.test(flag_text) || source && regexp_modifier_regex.test(source)) {
|
|
372
|
+
return false
|
|
373
|
+
}
|
|
346
374
|
/** @type {Set<string>} */
|
|
347
375
|
const names = new Set()
|
|
348
376
|
for (const match of source?.matchAll(regexp_group_name_regex) ?? []) {
|
|
349
377
|
const name = /** @type {string} */(match[1])/**/
|
|
350
|
-
if (names.has(name))
|
|
378
|
+
if (names.has(name)) {
|
|
379
|
+
return false
|
|
380
|
+
}
|
|
351
381
|
names.add(name)
|
|
352
382
|
}
|
|
353
383
|
return true
|
|
@@ -421,7 +451,9 @@ export default {
|
|
|
421
451
|
case "TSTypeAssertion":
|
|
422
452
|
return classify(node.expression)
|
|
423
453
|
case "UnaryExpression":
|
|
424
|
-
if (node.operator == "delete")
|
|
454
|
+
if (node.operator == "delete") {
|
|
455
|
+
return 2
|
|
456
|
+
}
|
|
425
457
|
break
|
|
426
458
|
}
|
|
427
459
|
let level = 0
|
|
@@ -456,7 +488,9 @@ export default {
|
|
|
456
488
|
default_exports.add(statement.declaration.name)
|
|
457
489
|
} else if (statement.type == "ExportNamedDeclaration" && !statement.source) {
|
|
458
490
|
for (const specifier of statement.specifiers) {
|
|
459
|
-
if (get_name(specifier.exported) == "default")
|
|
491
|
+
if (get_name(specifier.exported) == "default") {
|
|
492
|
+
default_exports.add(get_name(specifier.local))
|
|
493
|
+
}
|
|
460
494
|
}
|
|
461
495
|
}
|
|
462
496
|
}
|
|
@@ -469,7 +503,9 @@ export default {
|
|
|
469
503
|
function collect_targets(node, names) {
|
|
470
504
|
switch (node?.type) {
|
|
471
505
|
case "ArrayPattern":
|
|
472
|
-
for (const element of node.elements)
|
|
506
|
+
for (const element of node.elements) {
|
|
507
|
+
collect_targets(element, names)
|
|
508
|
+
}
|
|
473
509
|
break
|
|
474
510
|
case "AssignmentPattern":
|
|
475
511
|
collect_targets(node.left, names)
|
|
@@ -478,7 +514,9 @@ export default {
|
|
|
478
514
|
names.push(node.name)
|
|
479
515
|
break
|
|
480
516
|
case "ObjectPattern":
|
|
481
|
-
for (const property of node.properties)
|
|
517
|
+
for (const property of node.properties) {
|
|
518
|
+
collect_targets(property, names)
|
|
519
|
+
}
|
|
482
520
|
break
|
|
483
521
|
case "Property":
|
|
484
522
|
collect_targets(node.value, names)
|
|
@@ -495,13 +533,17 @@ export default {
|
|
|
495
533
|
*/
|
|
496
534
|
function construct(node) {
|
|
497
535
|
const callee = node.callee
|
|
498
|
-
if (callee.type != "Identifier" || !safe_constructors.has(callee.name) || !is_global(callee))
|
|
536
|
+
if (callee.type != "Identifier" || !safe_constructors.has(callee.name) || !is_global(callee)) {
|
|
537
|
+
return not_static
|
|
538
|
+
}
|
|
499
539
|
const args = node.arguments.map(static_value)
|
|
500
540
|
if (
|
|
501
541
|
args.includes(not_static)
|
|
502
542
|
|| (callee.name.includes("Array") || callee.name == "DataView") && args.some(is_large)
|
|
503
543
|
|| callee.name == "RegExp" && !is_portable_regexp(args)
|
|
504
|
-
)
|
|
544
|
+
) {
|
|
545
|
+
return not_static
|
|
546
|
+
}
|
|
505
547
|
try {
|
|
506
548
|
return new (/** @type {new (...values: unknown[]) => unknown} */(/** @type {Record<string, unknown>} */(/** @type {unknown} */(globalThis))/**/[callee.name])/**/)(...args)
|
|
507
549
|
} catch {
|
|
@@ -552,7 +594,9 @@ export default {
|
|
|
552
594
|
*/
|
|
553
595
|
function get_key_name(node) {
|
|
554
596
|
const type = node.type
|
|
555
|
-
if (type != "Property" && type != "TSMethodSignature" && type != "TSPropertySignature")
|
|
597
|
+
if (type != "Property" && type != "TSMethodSignature" && type != "TSPropertySignature") {
|
|
598
|
+
return undefined
|
|
599
|
+
}
|
|
556
600
|
return node.computed && node.key.type != "Literal" ? undefined : get_name(node.key)
|
|
557
601
|
}
|
|
558
602
|
/**
|
|
@@ -560,7 +604,9 @@ export default {
|
|
|
560
604
|
* @returns {string | undefined}
|
|
561
605
|
*/
|
|
562
606
|
function get_specifier_name(node) {
|
|
563
|
-
if (node.type == "ImportSpecifier")
|
|
607
|
+
if (node.type == "ImportSpecifier") {
|
|
608
|
+
return get_name(node.imported)
|
|
609
|
+
}
|
|
564
610
|
return node.type == "ExportSpecifier" ? get_name(node.local) : undefined
|
|
565
611
|
}
|
|
566
612
|
/**
|
|
@@ -569,11 +615,17 @@ export default {
|
|
|
569
615
|
*/
|
|
570
616
|
function get_test_title(statement) {
|
|
571
617
|
const call = statement.type == "ExpressionStatement" ? statement.expression : undefined
|
|
572
|
-
if (call?.type != "CallExpression")
|
|
618
|
+
if (call?.type != "CallExpression") {
|
|
619
|
+
return undefined
|
|
620
|
+
}
|
|
573
621
|
let callee = call.callee
|
|
574
|
-
while (callee.type == "MemberExpression" && !callee.computed)
|
|
622
|
+
while (callee.type == "MemberExpression" && !callee.computed) {
|
|
623
|
+
callee = callee.object
|
|
624
|
+
}
|
|
575
625
|
const title = call.arguments[0]
|
|
576
|
-
if (callee.type != "Identifier" || !test_functions.has(callee.name))
|
|
626
|
+
if (callee.type != "Identifier" || !test_functions.has(callee.name)) {
|
|
627
|
+
return undefined
|
|
628
|
+
}
|
|
577
629
|
return title?.type == "Literal" && typeof title.value == "string" || title?.type == "TemplateLiteral"
|
|
578
630
|
? title
|
|
579
631
|
: undefined
|
|
@@ -587,8 +639,12 @@ export default {
|
|
|
587
639
|
const variable = scope.references.find(
|
|
588
640
|
reference => reference.identifier == node
|
|
589
641
|
)?.resolved
|
|
590
|
-
if (!variable?.defs.length)
|
|
591
|
-
|
|
642
|
+
if (!variable?.defs.length) {
|
|
643
|
+
return constant_globals.has(node.name) && is_global(node)
|
|
644
|
+
}
|
|
645
|
+
if (variable.defs.length > 1) {
|
|
646
|
+
return false
|
|
647
|
+
}
|
|
592
648
|
const def = /** @type {import("eslint").Scope.Definition} */(variable.defs[0])/**/
|
|
593
649
|
switch (def.type) {
|
|
594
650
|
case "ClassName":
|
|
@@ -627,7 +683,9 @@ export default {
|
|
|
627
683
|
*/
|
|
628
684
|
function is_global(node) {
|
|
629
685
|
let scope = context.sourceCode.getScope(node)
|
|
630
|
-
while (scope.upper && !scope.set.has(node.name))
|
|
686
|
+
while (scope.upper && !scope.set.has(node.name)) {
|
|
687
|
+
scope = scope.upper
|
|
688
|
+
}
|
|
631
689
|
return !scope.set.get(node.name)?.defs.length
|
|
632
690
|
}
|
|
633
691
|
/**
|
|
@@ -643,9 +701,13 @@ export default {
|
|
|
643
701
|
parent?.type == "SwitchCase"
|
|
644
702
|
|| parent?.parent?.type == "SwitchCase"
|
|
645
703
|
|| /** @type {[number, number]} */(node.range)/**/[0] < /** @type {[number, number]} */(declaration.range)/**/[1]
|
|
646
|
-
)
|
|
704
|
+
) {
|
|
705
|
+
return false
|
|
706
|
+
}
|
|
647
707
|
for (let current = /** @type {import("eslint").Scope.Scope | null} */(scope)/**/; current && current != variable.scope; current = current.upper) {
|
|
648
|
-
if (current.block.type == "FunctionDeclaration")
|
|
708
|
+
if (current.block.type == "FunctionDeclaration") {
|
|
709
|
+
return false
|
|
710
|
+
}
|
|
649
711
|
}
|
|
650
712
|
return true
|
|
651
713
|
}
|
|
@@ -654,11 +716,15 @@ export default {
|
|
|
654
716
|
* @returns {boolean}
|
|
655
717
|
*/
|
|
656
718
|
function is_package_condition(node) {
|
|
657
|
-
if (!/(?:^|[/\\])package\.json$/.test(context.filename))
|
|
719
|
+
if (!/(?:^|[/\\])package\.json$/.test(context.filename)) {
|
|
720
|
+
return false
|
|
721
|
+
}
|
|
658
722
|
let parent = /** @type {import("eslint").Rule.Node} */(node)/**/.parent
|
|
659
723
|
while (parent && parent.type != "Program") {
|
|
660
724
|
const name = get_key_name(parent)
|
|
661
|
-
if (name == "exports" || name == "imports")
|
|
725
|
+
if (name == "exports" || name == "imports") {
|
|
726
|
+
return true
|
|
727
|
+
}
|
|
662
728
|
parent = parent.parent
|
|
663
729
|
}
|
|
664
730
|
return false
|
|
@@ -679,7 +745,9 @@ export default {
|
|
|
679
745
|
for (const key of visitor_keys[node.type] ?? []) {
|
|
680
746
|
const value = /** @type {Record<string, import("../../private.js").OrderNode | import("../../private.js").OrderNode[] | null | undefined>} */(/** @type {unknown} */(node))/**/[key]
|
|
681
747
|
for (const child of Array.isArray(value) ? value : [ value ]) {
|
|
682
|
-
if (child?.type && predicate(child))
|
|
748
|
+
if (child?.type && predicate(child)) {
|
|
749
|
+
return true
|
|
750
|
+
}
|
|
683
751
|
}
|
|
684
752
|
}
|
|
685
753
|
return false
|
|
@@ -708,9 +776,13 @@ export default {
|
|
|
708
776
|
/** @type {Record<string, unknown>} */
|
|
709
777
|
const object = {}
|
|
710
778
|
for (const property of node.properties) {
|
|
711
|
-
if (property.type != "Property" || property.kind != "init" || property.computed || property.shorthand)
|
|
779
|
+
if (property.type != "Property" || property.kind != "init" || property.computed || property.shorthand) {
|
|
780
|
+
return not_static
|
|
781
|
+
}
|
|
712
782
|
const value = static_value(property.value)
|
|
713
|
-
if (value === not_static)
|
|
783
|
+
if (value === not_static) {
|
|
784
|
+
return not_static
|
|
785
|
+
}
|
|
714
786
|
object[String(get_name(property.key))] = value
|
|
715
787
|
}
|
|
716
788
|
return object
|
|
@@ -761,12 +833,14 @@ export default {
|
|
|
761
833
|
}
|
|
762
834
|
)
|
|
763
835
|
} else {
|
|
764
|
-
if (entries.length > 1)
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
836
|
+
if (entries.length > 1) {
|
|
837
|
+
defer(
|
|
838
|
+
entries,
|
|
839
|
+
message_id,
|
|
840
|
+
layout,
|
|
841
|
+
get_level
|
|
842
|
+
)
|
|
843
|
+
}
|
|
770
844
|
entries = []
|
|
771
845
|
}
|
|
772
846
|
}
|
|
@@ -776,14 +850,18 @@ export default {
|
|
|
776
850
|
* @returns {void}
|
|
777
851
|
*/
|
|
778
852
|
function verify_statements(node) {
|
|
779
|
-
if (!check_declarations && !check_imports && !check_tests)
|
|
853
|
+
if (!check_declarations && !check_imports && !check_tests) {
|
|
854
|
+
return
|
|
855
|
+
}
|
|
780
856
|
/** @type {import("../../private.js").OrderEntry[]} */
|
|
781
857
|
let entries = []
|
|
782
858
|
/** @type {"declarations" | "imports" | "tests" | undefined} */
|
|
783
859
|
let kind
|
|
784
860
|
for (const statement of [ ...node.body, null ]) {
|
|
785
861
|
let id = statement && check_declarations ? get_function_id(statement) : undefined
|
|
786
|
-
if (node.type == "Program" && default_exports.has(id?.name))
|
|
862
|
+
if (node.type == "Program" && default_exports.has(id?.name)) {
|
|
863
|
+
id = undefined
|
|
864
|
+
}
|
|
787
865
|
const source = statement && check_imports ? get_import_source(statement) : undefined
|
|
788
866
|
const title = statement && check_tests && !id ? get_test_title(statement) : undefined
|
|
789
867
|
const report = id ?? source ?? title
|
|
@@ -801,7 +879,9 @@ export default {
|
|
|
801
879
|
continue
|
|
802
880
|
}
|
|
803
881
|
if (kind != next_kind) {
|
|
804
|
-
if (kind && entries.length > 1)
|
|
882
|
+
if (kind && entries.length > 1) {
|
|
883
|
+
defer(entries, kind, "statement")
|
|
884
|
+
}
|
|
805
885
|
entries = []
|
|
806
886
|
kind = next_kind
|
|
807
887
|
}
|
|
@@ -821,23 +901,29 @@ export default {
|
|
|
821
901
|
const listener = {
|
|
822
902
|
BlockStatement: verify_statements,
|
|
823
903
|
ExportNamedDeclaration: node => {
|
|
824
|
-
if (check_names)
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
904
|
+
if (check_names) {
|
|
905
|
+
verify_nodes(
|
|
906
|
+
node.specifiers,
|
|
907
|
+
get_specifier_name,
|
|
908
|
+
"names",
|
|
909
|
+
"sequence"
|
|
910
|
+
)
|
|
911
|
+
}
|
|
830
912
|
},
|
|
831
913
|
ImportDeclaration: node => {
|
|
832
|
-
if (check_names)
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
914
|
+
if (check_names) {
|
|
915
|
+
verify_nodes(
|
|
916
|
+
node.specifiers,
|
|
917
|
+
get_specifier_name,
|
|
918
|
+
"names",
|
|
919
|
+
"sequence"
|
|
920
|
+
)
|
|
921
|
+
}
|
|
838
922
|
},
|
|
839
923
|
ObjectExpression: node => {
|
|
840
|
-
if (!check_keys)
|
|
924
|
+
if (!check_keys) {
|
|
925
|
+
return
|
|
926
|
+
}
|
|
841
927
|
const condition = is_package_condition(node)
|
|
842
928
|
verify_nodes(
|
|
843
929
|
node.properties,
|
|
@@ -859,7 +945,9 @@ export default {
|
|
|
859
945
|
for (const property of node.properties) {
|
|
860
946
|
const names = collect_targets(property, [])
|
|
861
947
|
for (const name of new Set(names)) {
|
|
862
|
-
if (seen.has(name))
|
|
948
|
+
if (seen.has(name)) {
|
|
949
|
+
repeated.add(name)
|
|
950
|
+
}
|
|
863
951
|
seen.add(name)
|
|
864
952
|
}
|
|
865
953
|
targets.set(property, names)
|
|
@@ -888,21 +976,25 @@ export default {
|
|
|
888
976
|
},
|
|
889
977
|
StaticBlock: verify_statements,
|
|
890
978
|
TSInterfaceBody: node => {
|
|
891
|
-
if (check_keys)
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
979
|
+
if (check_keys) {
|
|
980
|
+
verify_nodes(
|
|
981
|
+
node.body,
|
|
982
|
+
get_key_name,
|
|
983
|
+
"keys",
|
|
984
|
+
"member"
|
|
985
|
+
)
|
|
986
|
+
}
|
|
897
987
|
},
|
|
898
988
|
TSModuleBlock: verify_statements,
|
|
899
989
|
TSTypeLiteral: node => {
|
|
900
|
-
if (check_keys)
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
990
|
+
if (check_keys) {
|
|
991
|
+
verify_nodes(
|
|
992
|
+
node.members,
|
|
993
|
+
get_key_name,
|
|
994
|
+
"keys",
|
|
995
|
+
"member"
|
|
996
|
+
)
|
|
997
|
+
}
|
|
906
998
|
}
|
|
907
999
|
}
|
|
908
1000
|
return /** @type {import("eslint").Rule.RuleListener} */(/** @type {unknown} */(listener))/**/
|