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 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
- - Core rules such as `func-style` (declarations), `no-shadow`, `prefer-const`, `one-var` (never), `no-console` and `no-await-in-loop`.
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
@@ -53,7 +53,7 @@
53
53
  },
54
54
  "type": "module",
55
55
  "types": "types/index.d.ts",
56
- "version": "0.6.0",
56
+ "version": "0.7.0",
57
57
  "scripts": {
58
58
  "dts": "node ../../scripts/tsc_with_clean.js eslint-plugin-lube",
59
59
  "lint": "pnpm -w lint",
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
- const stylistic = /** @type {import("eslint").ESLint.Plugin} */(stylistic_plugin)/**/
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,
@@ -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) return 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) break
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 == ",") separator = next
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
- )) return undefined
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") next++
158
+ while (text[next] == " " || text[next] == "\t") {
159
+ next++
160
+ }
151
161
  let previous = start
152
- while (text[previous - 1] == " " || text[previous - 1] == "\t") previous--
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) return null
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) return 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
- ) return null
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) result += " " + source.render(item.tail_start, item.after)
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) return null
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) return
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) return false
345
- if (flag_text && !portable_flags_regex.test(flag_text) || source && regexp_modifier_regex.test(source)) return false
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)) return false
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") return 2
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") default_exports.add(get_name(specifier.local))
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) collect_targets(element, names)
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) collect_targets(property, names)
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)) return not_static
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
- ) return not_static
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") return undefined
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") return get_name(node.imported)
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") return undefined
618
+ if (call?.type != "CallExpression") {
619
+ return undefined
620
+ }
573
621
  let callee = call.callee
574
- while (callee.type == "MemberExpression" && !callee.computed) callee = callee.object
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)) return undefined
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) return constant_globals.has(node.name) && is_global(node)
591
- if (variable.defs.length > 1) return false
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)) scope = scope.upper
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
- ) return false
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") return false
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)) return false
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") return true
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)) return true
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) return not_static
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) return 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) defer(
765
- entries,
766
- message_id,
767
- layout,
768
- get_level
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) return
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)) id = undefined
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) defer(entries, kind, "statement")
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) verify_nodes(
825
- node.specifiers,
826
- get_specifier_name,
827
- "names",
828
- "sequence"
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) verify_nodes(
833
- node.specifiers,
834
- get_specifier_name,
835
- "names",
836
- "sequence"
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) return
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)) repeated.add(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) verify_nodes(
892
- node.body,
893
- get_key_name,
894
- "keys",
895
- "member"
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) verify_nodes(
901
- node.members,
902
- get_key_name,
903
- "keys",
904
- "member"
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))/**/