@rotki/eslint-plugin 1.3.2 → 1.4.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/README.md CHANGED
@@ -38,11 +38,9 @@ export default [
38
38
 
39
39
  Or configure individual rules:
40
40
 
41
- <!-- eslint-disable perfectionist/sort-imports -->
42
-
43
41
  ```js
44
- import * as jsoncParser from 'jsonc-eslint-parser';
45
42
  import rotkiPlugin from '@rotki/eslint-plugin';
43
+ import * as jsoncParser from 'jsonc-eslint-parser';
46
44
 
47
45
  export default [
48
46
  {
@@ -62,20 +60,10 @@ export default [
62
60
  ];
63
61
  ```
64
62
 
65
- <!-- eslint-enable perfectionist/sort-imports -->
66
-
67
63
  ## Rules
68
64
 
69
- | Rule | Description | Fixable |
70
- | ------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------- | ----------- |
71
- | [consistent-ref-type-annotation](https://rotki.github.io/eslint-plugin/rules/consistent-ref-type-annotation) | Ensures consistent type annotation position for ref, computed assignments | :black_nib: |
72
- | [max-dependencies](https://rotki.github.io/eslint-plugin/rules/max-dependencies) | Enforce a maximum number of dependencies per file | |
73
- | [no-deprecated-classes](https://rotki.github.io/eslint-plugin/rules/no-deprecated-classes) | Disallow deprecated vuetify css classes | :black_nib: |
74
- | [no-deprecated-components](https://rotki.github.io/eslint-plugin/rules/no-deprecated-components) | Disallow deprecated components | :black_nib: |
75
- | [no-deprecated-props](https://rotki.github.io/eslint-plugin/rules/no-deprecated-props) | Replace deprecated props with their replacements | :black_nib: |
76
- | [no-dot-ts-imports](https://rotki.github.io/eslint-plugin/rules/no-dot-ts-imports) | Disallow .ts extension in import statements | :black_nib: |
77
- | [no-legacy-library-import](https://rotki.github.io/eslint-plugin/rules/no-legacy-library-import) | Disallow imports from @rotki/ui-library-compat | :black_nib: |
78
- | [no-unused-i18n-keys](https://rotki.github.io/eslint-plugin/rules/no-unused-i18n-keys) | Disallow unused i18n keys in locale files | :black_nib: |
65
+ See the [full list of available rules](https://rotki.github.io/eslint-plugin/rules/),
66
+ including which are enabled by the `recommended` preset and which are auto-fixable.
79
67
 
80
68
  ## Documentation
81
69
 
package/dist/index.d.mts CHANGED
@@ -27,6 +27,7 @@ type Options$3 = [{
27
27
 
28
28
  type Options$2 = [{
29
29
  autofix?: boolean;
30
+ writablePrefixes?: string[];
30
31
  }];
31
32
 
32
33
  type Options$1 = [{
@@ -57,6 +58,7 @@ declare const plugin: {
57
58
  'no-deprecated-props': PluginRuleModule<[]>;
58
59
  'no-dot-ts-imports': PluginRuleModule<[]>;
59
60
  'no-legacy-library-import': PluginRuleModule<[]>;
61
+ 'no-redundant-flex-row': PluginRuleModule<[]>;
60
62
  'no-unused-i18n-keys': PluginRuleModule<Options$6>;
61
63
  'require-jsdoc-on-composable-options': PluginRuleModule<[]>;
62
64
  };
@@ -82,6 +84,7 @@ declare const _default: {
82
84
  'no-deprecated-props': PluginRuleModule<[]>;
83
85
  'no-dot-ts-imports': PluginRuleModule<[]>;
84
86
  'no-legacy-library-import': PluginRuleModule<[]>;
87
+ 'no-redundant-flex-row': PluginRuleModule<[]>;
85
88
  'no-unused-i18n-keys': PluginRuleModule<Options$6>;
86
89
  'require-jsdoc-on-composable-options': PluginRuleModule<[]>;
87
90
  };
@@ -109,6 +112,7 @@ declare const _default: {
109
112
  'no-deprecated-props': PluginRuleModule<[]>;
110
113
  'no-dot-ts-imports': PluginRuleModule<[]>;
111
114
  'no-legacy-library-import': PluginRuleModule<[]>;
115
+ 'no-redundant-flex-row': PluginRuleModule<[]>;
112
116
  'no-unused-i18n-keys': PluginRuleModule<Options$6>;
113
117
  'require-jsdoc-on-composable-options': PluginRuleModule<[]>;
114
118
  };
@@ -145,6 +149,7 @@ declare const _default: {
145
149
  'no-deprecated-props': PluginRuleModule<[]>;
146
150
  'no-dot-ts-imports': PluginRuleModule<[]>;
147
151
  'no-legacy-library-import': PluginRuleModule<[]>;
152
+ 'no-redundant-flex-row': PluginRuleModule<[]>;
148
153
  'no-unused-i18n-keys': PluginRuleModule<Options$6>;
149
154
  'require-jsdoc-on-composable-options': PluginRuleModule<[]>;
150
155
  };
@@ -181,6 +186,7 @@ declare const _default: {
181
186
  'no-deprecated-props': PluginRuleModule<[]>;
182
187
  'no-dot-ts-imports': PluginRuleModule<[]>;
183
188
  'no-legacy-library-import': PluginRuleModule<[]>;
189
+ 'no-redundant-flex-row': PluginRuleModule<[]>;
184
190
  'no-unused-i18n-keys': PluginRuleModule<Options$6>;
185
191
  'require-jsdoc-on-composable-options': PluginRuleModule<[]>;
186
192
  };
@@ -217,6 +223,7 @@ declare const _default: {
217
223
  'no-deprecated-props': PluginRuleModule<[]>;
218
224
  'no-dot-ts-imports': PluginRuleModule<[]>;
219
225
  'no-legacy-library-import': PluginRuleModule<[]>;
226
+ 'no-redundant-flex-row': PluginRuleModule<[]>;
220
227
  'no-unused-i18n-keys': PluginRuleModule<Options$6>;
221
228
  'require-jsdoc-on-composable-options': PluginRuleModule<[]>;
222
229
  };
@@ -253,6 +260,7 @@ declare const _default: {
253
260
  'no-deprecated-props': PluginRuleModule<[]>;
254
261
  'no-dot-ts-imports': PluginRuleModule<[]>;
255
262
  'no-legacy-library-import': PluginRuleModule<[]>;
263
+ 'no-redundant-flex-row': PluginRuleModule<[]>;
256
264
  'no-unused-i18n-keys': PluginRuleModule<Options$6>;
257
265
  'require-jsdoc-on-composable-options': PluginRuleModule<[]>;
258
266
  };
@@ -289,6 +297,7 @@ declare const _default: {
289
297
  'no-deprecated-props': PluginRuleModule<[]>;
290
298
  'no-dot-ts-imports': PluginRuleModule<[]>;
291
299
  'no-legacy-library-import': PluginRuleModule<[]>;
300
+ 'no-redundant-flex-row': PluginRuleModule<[]>;
292
301
  'no-unused-i18n-keys': PluginRuleModule<Options$6>;
293
302
  'require-jsdoc-on-composable-options': PluginRuleModule<[]>;
294
303
  };
package/dist/index.d.ts CHANGED
@@ -27,6 +27,7 @@ type Options$3 = [{
27
27
 
28
28
  type Options$2 = [{
29
29
  autofix?: boolean;
30
+ writablePrefixes?: string[];
30
31
  }];
31
32
 
32
33
  type Options$1 = [{
@@ -57,6 +58,7 @@ declare const plugin: {
57
58
  'no-deprecated-props': PluginRuleModule<[]>;
58
59
  'no-dot-ts-imports': PluginRuleModule<[]>;
59
60
  'no-legacy-library-import': PluginRuleModule<[]>;
61
+ 'no-redundant-flex-row': PluginRuleModule<[]>;
60
62
  'no-unused-i18n-keys': PluginRuleModule<Options$6>;
61
63
  'require-jsdoc-on-composable-options': PluginRuleModule<[]>;
62
64
  };
@@ -82,6 +84,7 @@ declare const _default: {
82
84
  'no-deprecated-props': PluginRuleModule<[]>;
83
85
  'no-dot-ts-imports': PluginRuleModule<[]>;
84
86
  'no-legacy-library-import': PluginRuleModule<[]>;
87
+ 'no-redundant-flex-row': PluginRuleModule<[]>;
85
88
  'no-unused-i18n-keys': PluginRuleModule<Options$6>;
86
89
  'require-jsdoc-on-composable-options': PluginRuleModule<[]>;
87
90
  };
@@ -109,6 +112,7 @@ declare const _default: {
109
112
  'no-deprecated-props': PluginRuleModule<[]>;
110
113
  'no-dot-ts-imports': PluginRuleModule<[]>;
111
114
  'no-legacy-library-import': PluginRuleModule<[]>;
115
+ 'no-redundant-flex-row': PluginRuleModule<[]>;
112
116
  'no-unused-i18n-keys': PluginRuleModule<Options$6>;
113
117
  'require-jsdoc-on-composable-options': PluginRuleModule<[]>;
114
118
  };
@@ -145,6 +149,7 @@ declare const _default: {
145
149
  'no-deprecated-props': PluginRuleModule<[]>;
146
150
  'no-dot-ts-imports': PluginRuleModule<[]>;
147
151
  'no-legacy-library-import': PluginRuleModule<[]>;
152
+ 'no-redundant-flex-row': PluginRuleModule<[]>;
148
153
  'no-unused-i18n-keys': PluginRuleModule<Options$6>;
149
154
  'require-jsdoc-on-composable-options': PluginRuleModule<[]>;
150
155
  };
@@ -181,6 +186,7 @@ declare const _default: {
181
186
  'no-deprecated-props': PluginRuleModule<[]>;
182
187
  'no-dot-ts-imports': PluginRuleModule<[]>;
183
188
  'no-legacy-library-import': PluginRuleModule<[]>;
189
+ 'no-redundant-flex-row': PluginRuleModule<[]>;
184
190
  'no-unused-i18n-keys': PluginRuleModule<Options$6>;
185
191
  'require-jsdoc-on-composable-options': PluginRuleModule<[]>;
186
192
  };
@@ -217,6 +223,7 @@ declare const _default: {
217
223
  'no-deprecated-props': PluginRuleModule<[]>;
218
224
  'no-dot-ts-imports': PluginRuleModule<[]>;
219
225
  'no-legacy-library-import': PluginRuleModule<[]>;
226
+ 'no-redundant-flex-row': PluginRuleModule<[]>;
220
227
  'no-unused-i18n-keys': PluginRuleModule<Options$6>;
221
228
  'require-jsdoc-on-composable-options': PluginRuleModule<[]>;
222
229
  };
@@ -253,6 +260,7 @@ declare const _default: {
253
260
  'no-deprecated-props': PluginRuleModule<[]>;
254
261
  'no-dot-ts-imports': PluginRuleModule<[]>;
255
262
  'no-legacy-library-import': PluginRuleModule<[]>;
263
+ 'no-redundant-flex-row': PluginRuleModule<[]>;
256
264
  'no-unused-i18n-keys': PluginRuleModule<Options$6>;
257
265
  'require-jsdoc-on-composable-options': PluginRuleModule<[]>;
258
266
  };
@@ -289,6 +297,7 @@ declare const _default: {
289
297
  'no-deprecated-props': PluginRuleModule<[]>;
290
298
  'no-dot-ts-imports': PluginRuleModule<[]>;
291
299
  'no-legacy-library-import': PluginRuleModule<[]>;
300
+ 'no-redundant-flex-row': PluginRuleModule<[]>;
292
301
  'no-unused-i18n-keys': PluginRuleModule<Options$6>;
293
302
  'require-jsdoc-on-composable-options': PluginRuleModule<[]>;
294
303
  };
package/dist/index.mjs CHANGED
@@ -1,5 +1,4 @@
1
1
  import { AST_NODE_TYPES, TSESTree } from '@typescript-eslint/utils';
2
- import * as compat from 'eslint-compat-utils';
3
2
  import debugFactory from 'debug';
4
3
  import { extname, resolve } from 'node:path';
5
4
  import { pascalCase, kebabCase } from 'scule';
@@ -7,32 +6,35 @@ import { statSync, readFileSync } from 'node:fs';
7
6
  import { globSync } from 'tinyglobby';
8
7
  import { parse } from 'vue-eslint-parser';
9
8
 
10
- const version = "1.3.2";
9
+ const version = "1.4.0";
11
10
  const pkg = {
12
11
  version: version};
13
12
 
14
13
  function getFilename(context) {
15
- return compat.getFilename(context);
14
+ return context.filename;
16
15
  }
17
16
  function getSourceCode(context) {
18
- return compat.getSourceCode(context);
17
+ return context.sourceCode;
19
18
  }
20
19
 
21
20
  const COMPOSABLE_PATTERN = /^use[A-Z]/;
22
21
  function isComposableName(name) {
23
22
  return COMPOSABLE_PATTERN.test(name);
24
23
  }
24
+ function isComposableNode(node) {
25
+ if (node.type === AST_NODE_TYPES.FunctionDeclaration)
26
+ return !!node.id && isComposableName(node.id.name);
27
+ if (node.type === AST_NODE_TYPES.ArrowFunctionExpression || node.type === AST_NODE_TYPES.FunctionExpression) {
28
+ const parent = node.parent;
29
+ return parent?.type === AST_NODE_TYPES.VariableDeclarator && parent.id.type === AST_NODE_TYPES.Identifier && isComposableName(parent.id.name);
30
+ }
31
+ return false;
32
+ }
25
33
  function getEnclosingComposable(node) {
26
34
  let current = node.parent;
27
35
  while (current) {
28
- if (current.type === AST_NODE_TYPES.FunctionDeclaration && current.id && isComposableName(current.id.name))
36
+ if (isComposableNode(current))
29
37
  return current;
30
- if (current.type === AST_NODE_TYPES.ArrowFunctionExpression || current.type === AST_NODE_TYPES.FunctionExpression) {
31
- const parent = current.parent;
32
- if (parent?.type === AST_NODE_TYPES.VariableDeclarator && parent.id.type === AST_NODE_TYPES.Identifier && isComposableName(parent.id.name)) {
33
- return current;
34
- }
35
- }
36
38
  current = current.parent;
37
39
  }
38
40
  return void 0;
@@ -64,22 +66,18 @@ function createRecommended(plugin, name, flat) {
64
66
  return createConfig(plugin, name, flat, "recommended");
65
67
  }
66
68
 
69
+ function getLiteralValue(node, stringOnly) {
70
+ if (node.value == null)
71
+ return !stringOnly && node.bigint != null ? node.bigint : null;
72
+ if (typeof node.value === "string")
73
+ return node.value;
74
+ return stringOnly ? null : String(node.value);
75
+ }
67
76
  function getStringLiteralValue(node, stringOnly = false) {
68
- if (node.type === "Literal") {
69
- if (node.value == null) {
70
- if (!stringOnly && node.bigint != null)
71
- return node.bigint;
72
- return null;
73
- }
74
- if (typeof node.value === "string")
75
- return node.value;
76
- if (!stringOnly)
77
- return String(node.value);
78
- return null;
79
- }
80
- if (node.type === "TemplateLiteral" && node.expressions.length === 0 && node.quasis.length === 1) {
77
+ if (node.type === "Literal")
78
+ return getLiteralValue(node, stringOnly);
79
+ if (node.type === "TemplateLiteral" && node.expressions.length === 0 && node.quasis.length === 1)
81
80
  return node.quasis[0].value.cooked;
82
- }
83
81
  return null;
84
82
  }
85
83
  function getStaticPropertyName(node) {
@@ -166,7 +164,7 @@ function defineTemplateBodyVisitor(context, templateBodyVisitor, scriptVisitor,
166
164
  );
167
165
  }
168
166
 
169
- const RULE_NAME$f = "composable-input-flexibility";
167
+ const RULE_NAME$g = "composable-input-flexibility";
170
168
  function checkParamForRef(param) {
171
169
  let annotation;
172
170
  if (param.type === AST_NODE_TYPES.Identifier && param.typeAnnotation) {
@@ -247,10 +245,10 @@ const composableInputFlexibility = createEslintRule({
247
245
  ],
248
246
  type: "suggestion"
249
247
  },
250
- name: RULE_NAME$f
248
+ name: RULE_NAME$g
251
249
  });
252
250
 
253
- const RULE_NAME$e = "composable-naming-convention";
251
+ const RULE_NAME$f = "composable-naming-convention";
254
252
  function getComposableFunction(node) {
255
253
  if (node.type === AST_NODE_TYPES.FunctionDeclaration) {
256
254
  if (!node.id || !isComposableName(node.id.name))
@@ -279,6 +277,9 @@ const composableNamingConvention = createEslintRule({
279
277
  checkComposable(node);
280
278
  }
281
279
  };
280
+ function isMismatchedConvention(typeName, prefix, suffix, expected) {
281
+ return !!typeName && typeName.startsWith(prefix) && typeName.endsWith(suffix) && typeName !== expected;
282
+ }
282
283
  function checkComposable(node) {
283
284
  const info = getComposableFunction(node);
284
285
  if (!info)
@@ -288,26 +289,14 @@ const composableNamingConvention = createEslintRule({
288
289
  const expectedReturn = `Use${pascalName}Return`;
289
290
  for (const param of info.params) {
290
291
  const annotation = getParamTypeAnnotation(param);
291
- if (!annotation)
292
- continue;
293
- const typeName = getTypeReferenceName(annotation);
294
- if (typeName && typeName.startsWith("Use") && typeName.endsWith("Options") && typeName !== expectedOptions) {
295
- context.report({
296
- data: { expected: expectedOptions, got: typeName },
297
- messageId: "optionsNaming",
298
- node: param
299
- });
300
- }
292
+ const typeName = annotation ? getTypeReferenceName(annotation) : void 0;
293
+ if (isMismatchedConvention(typeName, "Use", "Options", expectedOptions))
294
+ context.report({ data: { expected: expectedOptions, got: typeName }, messageId: "optionsNaming", node: param });
301
295
  }
302
296
  if (info.returnType) {
303
297
  const returnTypeName = getTypeReferenceName(info.returnType.typeAnnotation);
304
- if (returnTypeName && returnTypeName.startsWith("Use") && returnTypeName.endsWith("Return") && returnTypeName !== expectedReturn) {
305
- context.report({
306
- data: { expected: expectedReturn, got: returnTypeName },
307
- messageId: "returnNaming",
308
- node: info.returnType
309
- });
310
- }
298
+ if (isMismatchedConvention(returnTypeName, "Use", "Return", expectedReturn))
299
+ context.report({ data: { expected: expectedReturn, got: returnTypeName }, messageId: "returnNaming", node: info.returnType });
311
300
  }
312
301
  }
313
302
  function getParamTypeAnnotation(param) {
@@ -332,10 +321,10 @@ const composableNamingConvention = createEslintRule({
332
321
  schema: [],
333
322
  type: "suggestion"
334
323
  },
335
- name: RULE_NAME$e
324
+ name: RULE_NAME$f
336
325
  });
337
326
 
338
- const RULE_NAME$d = "composable-no-default-export";
327
+ const RULE_NAME$e = "composable-no-default-export";
339
328
  const composableNoDefaultExport = createEslintRule({
340
329
  create(context) {
341
330
  let hasComposable = false;
@@ -375,10 +364,10 @@ const composableNoDefaultExport = createEslintRule({
375
364
  schema: [],
376
365
  type: "problem"
377
366
  },
378
- name: RULE_NAME$d
367
+ name: RULE_NAME$e
379
368
  });
380
369
 
381
- const RULE_NAME$c = "composable-prefer-shallowref";
370
+ const RULE_NAME$d = "composable-prefer-shallowref";
382
371
  const composablePreferShallowref = createEslintRule({
383
372
  create(context) {
384
373
  const autofix = context.options[0]?.autofix ?? false;
@@ -437,10 +426,10 @@ const composablePreferShallowref = createEslintRule({
437
426
  ],
438
427
  type: "suggestion"
439
428
  },
440
- name: RULE_NAME$c
429
+ name: RULE_NAME$d
441
430
  });
442
431
 
443
- const RULE_NAME$b = "composable-require-cleanup";
432
+ const RULE_NAME$c = "composable-require-cleanup";
444
433
  const SIDE_EFFECT_CALLS = /* @__PURE__ */ new Set(["addEventListener", "setInterval", "setTimeout"]);
445
434
  const SIDE_EFFECT_CONSTRUCTORS = /* @__PURE__ */ new Set(["MutationObserver", "ResizeObserver", "IntersectionObserver"]);
446
435
  const CLEANUP_HOOKS = /* @__PURE__ */ new Set(["onUnmounted", "onBeforeUnmount", "onScopeDispose", "tryOnScopeDispose"]);
@@ -534,10 +523,11 @@ const composableRequireCleanup = createEslintRule({
534
523
  schema: [],
535
524
  type: "problem"
536
525
  },
537
- name: RULE_NAME$b
526
+ name: RULE_NAME$c
538
527
  });
539
528
 
540
- const RULE_NAME$a = "composable-return-readonly";
529
+ const RULE_NAME$b = "composable-return-readonly";
530
+ const DEFAULT_WRITABLE_PREFIXES = ["model"];
541
531
  const REACTIVE_CREATORS = /* @__PURE__ */ new Set(["ref", "shallowRef"]);
542
532
  const READONLY_CREATORS = /* @__PURE__ */ new Set(["computed"]);
543
533
  function isReadonlyCall(node) {
@@ -546,9 +536,49 @@ function isReadonlyCall(node) {
546
536
  const composableReturnReadonly = createEslintRule({
547
537
  create(context) {
548
538
  const autofix = context.options[0]?.autofix ?? false;
539
+ const writablePrefixes = context.options[0]?.writablePrefixes ?? DEFAULT_WRITABLE_PREFIXES;
549
540
  const source = getSourceCode(context);
550
541
  const reactiveVars = /* @__PURE__ */ new Set();
551
542
  const readonlyVars = /* @__PURE__ */ new Set();
543
+ function isWritableByConvention(name) {
544
+ return writablePrefixes.some((prefix) => {
545
+ if (!name.startsWith(prefix))
546
+ return false;
547
+ const next = name[prefix.length];
548
+ return next === void 0 || next >= "A" && next <= "Z";
549
+ });
550
+ }
551
+ function reportWithFixOrSuggest(prop, name, messageId, suggestMessageId, fixFn) {
552
+ context.report({
553
+ data: { name },
554
+ ...autofix ? { fix: fixFn } : { suggest: [{ data: { name }, fix: fixFn, messageId: suggestMessageId }] },
555
+ messageId,
556
+ node: prop
557
+ });
558
+ }
559
+ function checkUnnecessaryReadonly(prop) {
560
+ const valueNode = prop.shorthand ? null : prop.value;
561
+ if (!valueNode || !isReadonlyCall(valueNode))
562
+ return false;
563
+ const innerName = valueNode.arguments[0].name;
564
+ if (!readonlyVars.has(innerName))
565
+ return false;
566
+ reportWithFixOrSuggest(prop, innerName, "unnecessaryReadonly", "suggestRemoveReadonly", (fixer) => fixer.replaceText(valueNode, innerName));
567
+ return true;
568
+ }
569
+ function checkMissingReadonly(prop) {
570
+ if (prop.shorthand && prop.key.type === AST_NODE_TYPES.Identifier && reactiveVars.has(prop.key.name)) {
571
+ const keyName = prop.key.name;
572
+ if (isWritableByConvention(keyName))
573
+ return;
574
+ reportWithFixOrSuggest(prop, keyName, "wrapReadonly", "suggestWrapReadonly", (fixer) => fixer.replaceText(prop, `${keyName}: readonly(${keyName})`));
575
+ } else if (!prop.shorthand && prop.value.type === AST_NODE_TYPES.Identifier && reactiveVars.has(prop.value.name)) {
576
+ const valueName = prop.value.name;
577
+ if (isWritableByConvention(valueName))
578
+ return;
579
+ reportWithFixOrSuggest(prop, valueName, "wrapReadonly", "suggestWrapReadonly", (fixer) => fixer.replaceText(prop.value, `readonly(${prop.value.type === AST_NODE_TYPES.Identifier ? prop.value.name : source.getText(prop.value)})`));
580
+ }
581
+ }
552
582
  return {
553
583
  ReturnStatement: (node) => {
554
584
  if (!getEnclosingComposable(node))
@@ -558,39 +588,8 @@ const composableReturnReadonly = createEslintRule({
558
588
  for (const prop of node.argument.properties) {
559
589
  if (prop.type !== AST_NODE_TYPES.Property)
560
590
  continue;
561
- const valueNode = prop.shorthand ? null : prop.value;
562
- if (valueNode && isReadonlyCall(valueNode)) {
563
- const innerName = valueNode.arguments[0].name;
564
- if (readonlyVars.has(innerName)) {
565
- const fixFn = (fixer) => fixer.replaceText(valueNode, innerName);
566
- context.report({
567
- data: { name: innerName },
568
- ...autofix ? { fix: fixFn } : { suggest: [{ data: { name: innerName }, fix: fixFn, messageId: "suggestRemoveReadonly" }] },
569
- messageId: "unnecessaryReadonly",
570
- node: prop
571
- });
572
- continue;
573
- }
574
- }
575
- if (prop.shorthand && prop.key.type === AST_NODE_TYPES.Identifier && reactiveVars.has(prop.key.name)) {
576
- const keyName = prop.key.name;
577
- const fixFn = (fixer) => fixer.replaceText(prop, `${keyName}: readonly(${keyName})`);
578
- context.report({
579
- data: { name: keyName },
580
- ...autofix ? { fix: fixFn } : { suggest: [{ data: { name: keyName }, fix: fixFn, messageId: "suggestWrapReadonly" }] },
581
- messageId: "wrapReadonly",
582
- node: prop
583
- });
584
- } else if (!prop.shorthand && prop.value.type === AST_NODE_TYPES.Identifier && reactiveVars.has(prop.value.name)) {
585
- const valueName = prop.value.name;
586
- const fixFn = (fixer) => fixer.replaceText(prop.value, `readonly(${prop.value.type === AST_NODE_TYPES.Identifier ? prop.value.name : source.getText(prop.value)})`);
587
- context.report({
588
- data: { name: valueName },
589
- ...autofix ? { fix: fixFn } : { suggest: [{ data: { name: valueName }, fix: fixFn, messageId: "suggestWrapReadonly" }] },
590
- messageId: "wrapReadonly",
591
- node: prop
592
- });
593
- }
591
+ if (!checkUnnecessaryReadonly(prop))
592
+ checkMissingReadonly(prop);
594
593
  }
595
594
  },
596
595
  VariableDeclarator: (node) => {
@@ -609,7 +608,7 @@ const composableReturnReadonly = createEslintRule({
609
608
  }
610
609
  };
611
610
  },
612
- defaultOptions: [{ autofix: false }],
611
+ defaultOptions: [{ autofix: false, writablePrefixes: DEFAULT_WRITABLE_PREFIXES }],
613
612
  meta: {
614
613
  docs: {
615
614
  description: "Require returned refs from composables to be wrapped with readonly()",
@@ -631,6 +630,12 @@ const composableReturnReadonly = createEslintRule({
631
630
  default: false,
632
631
  description: "Enable auto-fix. When disabled, the fix is available as a suggestion.",
633
632
  type: "boolean"
633
+ },
634
+ writablePrefixes: {
635
+ default: DEFAULT_WRITABLE_PREFIXES,
636
+ description: "Returned variables whose name starts with one of these prefixes are exempt from the readonly() requirement (e.g. refs intended for v-model binding).",
637
+ items: { type: "string" },
638
+ type: "array"
634
639
  }
635
640
  },
636
641
  type: "object"
@@ -638,10 +643,10 @@ const composableReturnReadonly = createEslintRule({
638
643
  ],
639
644
  type: "suggestion"
640
645
  },
641
- name: RULE_NAME$a
646
+ name: RULE_NAME$b
642
647
  });
643
648
 
644
- const RULE_NAME$9 = "composable-ssr-safety";
649
+ const RULE_NAME$a = "composable-ssr-safety";
645
650
  const BROWSER_GLOBALS = /* @__PURE__ */ new Set(["window", "document", "navigator"]);
646
651
  const LIFECYCLE_HOOKS = /* @__PURE__ */ new Set(["onMounted", "onBeforeMount"]);
647
652
  function isInsideLifecycleHook(node) {
@@ -668,15 +673,12 @@ function isInsideTypeofCheck(node) {
668
673
  }
669
674
  return false;
670
675
  }
676
+ function isTypeofBrowserGlobal(node) {
677
+ return node.type === AST_NODE_TYPES.UnaryExpression && node.operator === "typeof" && node.argument.type === AST_NODE_TYPES.Identifier && BROWSER_GLOBALS.has(node.argument.name);
678
+ }
671
679
  function hasTypeofCheck(node) {
672
- if (node.type === AST_NODE_TYPES.BinaryExpression) {
673
- if (node.left.type === AST_NODE_TYPES.UnaryExpression && node.left.operator === "typeof" && node.left.argument.type === AST_NODE_TYPES.Identifier && BROWSER_GLOBALS.has(node.left.argument.name)) {
674
- return true;
675
- }
676
- if (node.right.type === AST_NODE_TYPES.UnaryExpression && node.right.operator === "typeof" && node.right.argument.type === AST_NODE_TYPES.Identifier && BROWSER_GLOBALS.has(node.right.argument.name)) {
677
- return true;
678
- }
679
- }
680
+ if (node.type === AST_NODE_TYPES.BinaryExpression)
681
+ return isTypeofBrowserGlobal(node.left) || isTypeofBrowserGlobal(node.right);
680
682
  if (node.type === AST_NODE_TYPES.LogicalExpression)
681
683
  return hasTypeofCheck(node.left) || hasTypeofCheck(node.right);
682
684
  return false;
@@ -732,56 +734,51 @@ const composableSsrSafety = createEslintRule({
732
734
  schema: [],
733
735
  type: "problem"
734
736
  },
735
- name: RULE_NAME$9
737
+ name: RULE_NAME$a
736
738
  });
737
739
 
738
740
  const debug$6 = debugFactory("@rotki/eslint-plugin:consistent-ref-type-annotation");
739
- const RULE_NAME$8 = "consistent-ref-type-annotation";
741
+ const RULE_NAME$9 = "consistent-ref-type-annotation";
740
742
  const FIXABLE_METHODS = /* @__PURE__ */ new Set(["ref", "computed"]);
741
- function checkAssignmentDeclaration(context, source, node, declaration, allowInference) {
742
- let declarationTypeArguments;
743
+ function getFixableCallExpression(declaration) {
743
744
  const init = declaration.init;
744
- if (!(init && init.type === TSESTree.AST_NODE_TYPES.CallExpression))
745
- return;
745
+ if (!init || init.type !== TSESTree.AST_NODE_TYPES.CallExpression)
746
+ return void 0;
746
747
  const callee = init.callee;
747
- if (!(callee && callee.type === TSESTree.AST_NODE_TYPES.Identifier))
748
- return;
749
- if (!FIXABLE_METHODS.has(callee.name))
748
+ if (!callee || callee.type !== TSESTree.AST_NODE_TYPES.Identifier || !FIXABLE_METHODS.has(callee.name))
749
+ return void 0;
750
+ return { callee, init };
751
+ }
752
+ function getDeclarationTypeArguments(declaration) {
753
+ const typeNode = declaration.id.typeAnnotation?.typeAnnotation;
754
+ if (typeNode?.type === TSESTree.AST_NODE_TYPES.TSTypeReference)
755
+ return typeNode.typeArguments;
756
+ return void 0;
757
+ }
758
+ function checkAssignmentDeclaration(context, source, node, declaration, allowInference) {
759
+ const call = getFixableCallExpression(declaration);
760
+ if (!call)
750
761
  return;
762
+ const { callee, init } = call;
751
763
  const name = callee.name;
752
764
  debug$6(`found ${name}, checking type arguments`);
753
- const initializationTypeArguments = init.typeArguments;
765
+ const initTypeArgs = init.typeArguments;
766
+ const declTypeArgs = getDeclarationTypeArguments(declaration);
754
767
  const typeAnnotation = declaration.id.typeAnnotation;
755
- if (typeAnnotation) {
756
- const typeNode = typeAnnotation.typeAnnotation;
757
- if (typeNode && typeNode.type === TSESTree.AST_NODE_TYPES.TSTypeReference)
758
- declarationTypeArguments = typeNode.typeArguments;
759
- }
760
- if (initializationTypeArguments && !declarationTypeArguments)
768
+ if (initTypeArgs && !declTypeArgs)
761
769
  return;
762
770
  debug$6(`generating report for ${name}`);
763
- if (!initializationTypeArguments && !declarationTypeArguments) {
764
- if (allowInference) {
765
- debug$6("type inference is allowed");
766
- } else {
767
- context.report({
768
- data: {
769
- name
770
- },
771
- messageId: "missingType",
772
- node
773
- });
774
- }
771
+ if (!initTypeArgs && !declTypeArgs) {
772
+ if (!allowInference)
773
+ context.report({ data: { name }, messageId: "missingType", node });
775
774
  return;
776
775
  }
777
776
  context.report({
778
- data: {
779
- name
780
- },
777
+ data: { name },
781
778
  fix(fixer) {
782
779
  const fixes = [];
783
- if (!initializationTypeArguments && callee)
784
- fixes.push(fixer.insertTextAfter(callee, source.getText(declarationTypeArguments)));
780
+ if (!initTypeArgs && callee)
781
+ fixes.push(fixer.insertTextAfter(callee, source.getText(declTypeArgs)));
785
782
  if (typeAnnotation)
786
783
  fixes.push(fixer.remove(typeAnnotation));
787
784
  return fixes;
@@ -830,10 +827,10 @@ const consistentRefTypeAnnotation = createEslintRule({
830
827
  ],
831
828
  type: "problem"
832
829
  },
833
- name: RULE_NAME$8
830
+ name: RULE_NAME$9
834
831
  });
835
832
 
836
- const RULE_NAME$7 = "max-dependencies";
833
+ const RULE_NAME$8 = "max-dependencies";
837
834
  const maxDependencies = createEslintRule({
838
835
  create(context, [options]) {
839
836
  let dependencyCount = 0;
@@ -893,10 +890,10 @@ const maxDependencies = createEslintRule({
893
890
  ],
894
891
  type: "suggestion"
895
892
  },
896
- name: RULE_NAME$7
893
+ name: RULE_NAME$8
897
894
  });
898
895
 
899
- const RULE_NAME$6 = "no-deprecated-classes";
896
+ const RULE_NAME$7 = "no-deprecated-classes";
900
897
  const debug$5 = debugFactory("@rotki/eslint-plugin:no-deprecated-classes");
901
898
  const stringReplacements = /* @__PURE__ */ new Map([
902
899
  ["d-block", "block"],
@@ -973,51 +970,62 @@ function reportReplacement(className, replacement, node, context, source, positi
973
970
  messageId: "replacedWith"
974
971
  });
975
972
  }
973
+ function splitClassNames(classNames, reportNode) {
974
+ return classNames.split(/\s+/).map((className) => ({ className, position: classNames.indexOf(className) + 1, reportNode }));
975
+ }
976
+ function* extractFromObject(node) {
977
+ for (const prop of node.properties) {
978
+ if (prop.type !== "Property")
979
+ continue;
980
+ const classNames = getStaticPropertyName(prop);
981
+ if (classNames)
982
+ yield* splitClassNames(classNames, prop.key);
983
+ }
984
+ }
985
+ function* extractFromArray(node) {
986
+ for (const element of node.elements) {
987
+ if (element == null || element.type === "SpreadElement")
988
+ continue;
989
+ yield* extractClassNames(element);
990
+ }
991
+ }
992
+ function* extractFromTemplateLiteral(node) {
993
+ for (const templateElement of node.quasis) {
994
+ const classNames = templateElement.value.cooked;
995
+ if (classNames !== null)
996
+ yield* splitClassNames(classNames, templateElement);
997
+ }
998
+ for (const expr of node.expressions)
999
+ yield* extractClassNames(expr, true);
1000
+ }
1001
+ function* extractFromBinary(node) {
1002
+ if (node.operator !== "+")
1003
+ return;
1004
+ if (node.left.type !== "PrivateIdentifier")
1005
+ yield* extractClassNames(node.left, true);
1006
+ yield* extractClassNames(node.right, true);
1007
+ }
976
1008
  function* extractClassNames(node, textOnly = false) {
977
1009
  if (node.type === "Literal") {
978
- const classNames = `${node.value}`;
979
- yield* classNames.split(/\s+/).map((className) => ({ className, position: classNames.indexOf(className) + 1, reportNode: node }));
1010
+ yield* splitClassNames(`${node.value}`, node);
980
1011
  return;
981
1012
  }
982
1013
  if (node.type === "TemplateLiteral") {
983
- for (const templateElement of node.quasis) {
984
- const classNames = templateElement.value.cooked;
985
- if (classNames === null)
986
- continue;
987
- yield* classNames.split(/\s+/).map((className) => ({ className, position: classNames.indexOf(className) + 1, reportNode: templateElement }));
988
- }
989
- for (const expr of node.expressions)
990
- yield* extractClassNames(expr, true);
1014
+ yield* extractFromTemplateLiteral(node);
991
1015
  return;
992
1016
  }
993
1017
  if (node.type === "BinaryExpression") {
994
- if (node.operator !== "+")
995
- return;
996
- yield* extractClassNames(node.left, true);
997
- yield* extractClassNames(node.right, true);
1018
+ yield* extractFromBinary(node);
998
1019
  return;
999
1020
  }
1000
1021
  if (textOnly)
1001
1022
  return;
1002
1023
  if (node.type === "ObjectExpression") {
1003
- for (const prop of node.properties) {
1004
- if (prop.type !== "Property")
1005
- continue;
1006
- const classNames = getStaticPropertyName(prop);
1007
- if (!classNames)
1008
- continue;
1009
- yield* classNames.split(/\s+/).map((className) => ({ className, position: classNames.indexOf(className) + 1, reportNode: prop.key }));
1010
- }
1024
+ yield* extractFromObject(node);
1011
1025
  return;
1012
1026
  }
1013
1027
  if (node.type === "ArrayExpression") {
1014
- for (const element of node.elements) {
1015
- if (element == null)
1016
- continue;
1017
- if (element.type === "SpreadElement")
1018
- continue;
1019
- yield* extractClassNames(element);
1020
- }
1028
+ yield* extractFromArray(node);
1021
1029
  }
1022
1030
  if (node.type === "ConditionalExpression") {
1023
1031
  yield* extractClassNames(node.consequent);
@@ -1064,11 +1072,11 @@ const noDeprecatedClasses = createEslintRule({
1064
1072
  schema: [],
1065
1073
  type: "problem"
1066
1074
  },
1067
- name: RULE_NAME$6
1075
+ name: RULE_NAME$7
1068
1076
  });
1069
1077
 
1070
1078
  const debug$4 = debugFactory("@rotki/eslint-plugin:no-deprecated-components");
1071
- const RULE_NAME$5 = "no-deprecated-components";
1079
+ const RULE_NAME$6 = "no-deprecated-components";
1072
1080
  const vuetify = {
1073
1081
  VApp: true,
1074
1082
  VAppBar: true,
@@ -1105,7 +1113,7 @@ const skipInLegacy = /* @__PURE__ */ new Set([
1105
1113
  "Fragment"
1106
1114
  ]);
1107
1115
  function hasReplacement$1(tag) {
1108
- return Object.prototype.hasOwnProperty.call(replacements$1, tag);
1116
+ return Object.hasOwn(replacements$1, tag);
1109
1117
  }
1110
1118
  const noDeprecatedComponents = createEslintRule({
1111
1119
  create(context, optionsWithDefault) {
@@ -1173,11 +1181,10 @@ const noDeprecatedComponents = createEslintRule({
1173
1181
  ],
1174
1182
  type: "problem"
1175
1183
  },
1176
- name: RULE_NAME$5
1184
+ name: RULE_NAME$6
1177
1185
  });
1178
1186
 
1179
- const debug$3 = debugFactory("@rotki/eslint-plugin:no-deprecated-props");
1180
- const RULE_NAME$4 = "no-deprecated-props";
1187
+ const RULE_NAME$5 = "no-deprecated-props";
1181
1188
  const replacements = {
1182
1189
  RuiRadio: {
1183
1190
  internalValue: "value"
@@ -1200,37 +1207,34 @@ function getPropName(node) {
1200
1207
  }
1201
1208
  return kebabCase(node.key.rawName);
1202
1209
  }
1210
+ function isNonBindDirective(node) {
1211
+ return node.directive && node.value?.type === "VExpressionContainer" && (node.key.name.name !== "bind" || !node.key.argument);
1212
+ }
1213
+ function getPropNameNode(node) {
1214
+ return node.directive ? node.key.argument : node.key;
1215
+ }
1203
1216
  const noDeprecatedProps = createEslintRule({
1204
1217
  create(context) {
1205
1218
  return defineTemplateBodyVisitor(context, {
1206
1219
  VAttribute(node) {
1207
- if (node.directive && (node.value?.type === "VExpressionContainer" && (node.key.name.name !== "bind" || !node.key.argument)))
1220
+ if (isNonBindDirective(node))
1208
1221
  return;
1209
1222
  const tag = pascalCase(node.parent.parent.rawName);
1210
1223
  if (!hasReplacement(tag))
1211
1224
  return;
1212
- debug$3(`${tag} has replacement properties`);
1213
1225
  const propName = getPropName(node);
1214
- const propNameNode = node.directive ? node.key.argument : node.key;
1215
- if (!propName || !propNameNode) {
1216
- debug$3("could not get prop name and/or node");
1226
+ const propNameNode = getPropNameNode(node);
1227
+ if (!propName || !propNameNode)
1217
1228
  return;
1218
- }
1219
1229
  const match = replacementMaps.get(tag)?.get(propName);
1220
- if (match) {
1221
- debug$3(`preparing a replacement for ${tag}:${propName} -> ${match.replacement}`);
1222
- context.report({
1223
- data: {
1224
- prop: match.original,
1225
- replacement: match.replacement
1226
- },
1227
- fix(fixer) {
1228
- return fixer.replaceText(propNameNode, match.replacement);
1229
- },
1230
- messageId: "replacedWith",
1231
- node: propNameNode
1232
- });
1233
- }
1230
+ if (!match)
1231
+ return;
1232
+ context.report({
1233
+ data: { prop: match.original, replacement: match.replacement },
1234
+ fix: (fixer) => fixer.replaceText(propNameNode, match.replacement),
1235
+ messageId: "replacedWith",
1236
+ node: propNameNode
1237
+ });
1234
1238
  }
1235
1239
  });
1236
1240
  },
@@ -1247,10 +1251,10 @@ const noDeprecatedProps = createEslintRule({
1247
1251
  schema: [],
1248
1252
  type: "problem"
1249
1253
  },
1250
- name: RULE_NAME$4
1254
+ name: RULE_NAME$5
1251
1255
  });
1252
1256
 
1253
- const RULE_NAME$3 = "no-dot-ts-imports";
1257
+ const RULE_NAME$4 = "no-dot-ts-imports";
1254
1258
  const noDotTsImport = createEslintRule({
1255
1259
  create(context) {
1256
1260
  return {
@@ -1285,10 +1289,10 @@ const noDotTsImport = createEslintRule({
1285
1289
  schema: [],
1286
1290
  type: "problem"
1287
1291
  },
1288
- name: RULE_NAME$3
1292
+ name: RULE_NAME$4
1289
1293
  });
1290
1294
 
1291
- const RULE_NAME$2 = "no-legacy-library-import";
1295
+ const RULE_NAME$3 = "no-legacy-library-import";
1292
1296
  const legacyLibrary = "@rotki/ui-library-compat";
1293
1297
  const newLibrary = "@rotki/ui-library";
1294
1298
  const noLegacyLibraryImport = createEslintRule({
@@ -1321,6 +1325,119 @@ const noLegacyLibraryImport = createEslintRule({
1321
1325
  schema: [],
1322
1326
  type: "problem"
1323
1327
  },
1328
+ name: RULE_NAME$3
1329
+ });
1330
+
1331
+ const RULE_NAME$2 = "no-redundant-flex-row";
1332
+ const debug$3 = debugFactory("@rotki/eslint-plugin:no-redundant-flex-row");
1333
+ function stripImportant(token) {
1334
+ return token.replace(/^!/, "").replace(/!$/, "");
1335
+ }
1336
+ function isUnconditional(token) {
1337
+ return !token.includes(":");
1338
+ }
1339
+ function isFlexDisplay(token) {
1340
+ if (!isUnconditional(token))
1341
+ return false;
1342
+ const base = stripImportant(token);
1343
+ return base === "flex" || base === "inline-flex";
1344
+ }
1345
+ function isFlexRow(token) {
1346
+ if (!isUnconditional(token))
1347
+ return false;
1348
+ return stripImportant(token) === "flex-row";
1349
+ }
1350
+ function tokenize(value) {
1351
+ const tokens = [];
1352
+ const matcher = /\S+/g;
1353
+ let match;
1354
+ while ((match = matcher.exec(value)) !== null)
1355
+ tokens.push({ start: match.index, text: match[0] });
1356
+ return tokens;
1357
+ }
1358
+ function removalRange(value, token, contentStart) {
1359
+ let startRel = token.start;
1360
+ let endRel = token.start + token.text.length;
1361
+ if (startRel > 0 && /\s/.test(value[startRel - 1])) {
1362
+ while (startRel > 0 && /\s/.test(value[startRel - 1]))
1363
+ startRel--;
1364
+ } else {
1365
+ while (endRel < value.length && /\s/.test(value[endRel]))
1366
+ endRel++;
1367
+ }
1368
+ return [contentStart + startRel, contentStart + endRel];
1369
+ }
1370
+ function reportRedundant(source, context, sourceCode) {
1371
+ const tokens = tokenize(source.value);
1372
+ if (!tokens.some((token) => isFlexDisplay(token.text)))
1373
+ return;
1374
+ for (const token of tokens) {
1375
+ if (!isFlexRow(token.text))
1376
+ continue;
1377
+ debug$3(`found redundant flex-row token '${token.text}'`);
1378
+ const tokenRange = [
1379
+ source.contentStart + token.start,
1380
+ source.contentStart + token.start + token.text.length
1381
+ ];
1382
+ const removal = removalRange(source.value, token, source.contentStart);
1383
+ context.report({
1384
+ data: { className: token.text },
1385
+ fix(fixer) {
1386
+ return fixer.removeRange(removal);
1387
+ },
1388
+ loc: {
1389
+ end: sourceCode.getLocFromIndex(tokenRange[1]),
1390
+ start: sourceCode.getLocFromIndex(tokenRange[0])
1391
+ },
1392
+ messageId: "redundantFlexRow"
1393
+ });
1394
+ }
1395
+ }
1396
+ function plainStringLists(expression) {
1397
+ if (expression.type === "Literal" && typeof expression.value === "string")
1398
+ return [{ contentStart: expression.range[0] + 1, value: expression.value }];
1399
+ if (expression.type === "TemplateLiteral" && expression.expressions.length === 0 && expression.quasis.length === 1) {
1400
+ const quasi = expression.quasis[0];
1401
+ const cooked = quasi.value.cooked;
1402
+ if (cooked !== null)
1403
+ return [{ contentStart: quasi.range[0] + 1, value: cooked }];
1404
+ }
1405
+ return [];
1406
+ }
1407
+ const noRedundantFlexRow = createEslintRule({
1408
+ create(context) {
1409
+ const sourceCode = getSourceCode(context);
1410
+ return defineTemplateBodyVisitor(context, {
1411
+ 'VAttribute[directive=false][key.name="class"]': function(node) {
1412
+ if (!node.value || !node.value.value || !node.value.range)
1413
+ return;
1414
+ reportRedundant(
1415
+ { contentStart: node.value.range[0] + 1, value: node.value.value },
1416
+ context,
1417
+ sourceCode
1418
+ );
1419
+ },
1420
+ "VAttribute[directive=true][key.name.name='bind'][key.argument.name='class'] > VExpressionContainer.value": function(node) {
1421
+ if (!node.expression)
1422
+ return;
1423
+ for (const source of plainStringLists(node.expression))
1424
+ reportRedundant(source, context, sourceCode);
1425
+ }
1426
+ });
1427
+ },
1428
+ defaultOptions: [],
1429
+ meta: {
1430
+ docs: {
1431
+ description: "disallow redundant `flex-row` since `flex` already defaults to the row direction",
1432
+ recommendation: "recommended"
1433
+ },
1434
+ fixable: "code",
1435
+ messages: {
1436
+ redundantFlexRow: `'{{ className }}' is redundant because 'flex' already lays out in the row direction; remove it, or keep it only behind a conditional variant (e.g. 'sm:flex-row')`
1437
+ },
1438
+ schema: [],
1439
+ type: "suggestion"
1440
+ },
1324
1441
  name: RULE_NAME$2
1325
1442
  });
1326
1443
 
@@ -1337,18 +1454,39 @@ function isI18nCallExpression(node) {
1337
1454
  return I18N_FUNCTION_NAMES.has(callee.name);
1338
1455
  return callee.type === "MemberExpression" && !callee.computed && isAstNode(callee.property) && callee.property.type === "Identifier" && typeof callee.property.name === "string" && I18N_MEMBER_NAMES.has(callee.property.name);
1339
1456
  }
1340
- function getTemplateLiteralText(arg) {
1341
- const quasis = arg.quasis;
1342
- const expressions = arg.expressions;
1343
- if (!Array.isArray(quasis) || !Array.isArray(expressions) || quasis.length === 0)
1457
+ function getQuasiText(quasi) {
1458
+ if (!quasi || typeof quasi !== "object" || !("value" in quasi))
1344
1459
  return void 0;
1345
- const firstQuasi = quasis[0];
1346
- if (!firstQuasi || typeof firstQuasi !== "object" || !("value" in firstQuasi))
1347
- return void 0;
1348
- const quasiObj = firstQuasi.value;
1460
+ const quasiObj = quasi.value;
1349
1461
  if (!quasiObj || typeof quasiObj !== "object")
1350
1462
  return void 0;
1351
- return "cooked" in quasiObj && typeof quasiObj.cooked === "string" ? quasiObj.cooked : "raw" in quasiObj && typeof quasiObj.raw === "string" ? quasiObj.raw : void 0;
1463
+ if ("cooked" in quasiObj && typeof quasiObj.cooked === "string")
1464
+ return quasiObj.cooked;
1465
+ if ("raw" in quasiObj && typeof quasiObj.raw === "string")
1466
+ return quasiObj.raw;
1467
+ return void 0;
1468
+ }
1469
+ function getTemplateLiteralText(arg) {
1470
+ const { expressions, quasis } = arg;
1471
+ if (!Array.isArray(quasis) || !Array.isArray(expressions) || quasis.length === 0)
1472
+ return void 0;
1473
+ return getQuasiText(quasis[0]);
1474
+ }
1475
+ function isStaticTemplateLiteral(arg) {
1476
+ return Array.isArray(arg.expressions) && arg.expressions.length === 0 && Array.isArray(arg.quasis) && arg.quasis.length === 1;
1477
+ }
1478
+ function extractKeyFromArg(arg) {
1479
+ if (arg.type === "Literal" && typeof arg.value === "string")
1480
+ return arg.value;
1481
+ if (arg.type === "TemplateLiteral") {
1482
+ const text = getTemplateLiteralText(arg);
1483
+ if (text === void 0)
1484
+ return void 0;
1485
+ if (isStaticTemplateLiteral(arg))
1486
+ return text;
1487
+ return text ? `${text}*` : void 0;
1488
+ }
1489
+ return void 0;
1352
1490
  }
1353
1491
  function extractKeysFromCallExpression(node, keys) {
1354
1492
  if (!isI18nCallExpression(node))
@@ -1359,17 +1497,9 @@ function extractKeysFromCallExpression(node, keys) {
1359
1497
  const arg = args[0];
1360
1498
  if (!isAstNode(arg))
1361
1499
  return;
1362
- if (arg.type === "Literal" && typeof arg.value === "string") {
1363
- keys.add(arg.value);
1364
- } else if (arg.type === "TemplateLiteral") {
1365
- const text = getTemplateLiteralText(arg);
1366
- if (text === void 0)
1367
- return;
1368
- if (arg.expressions && Array.isArray(arg.expressions) && arg.expressions.length === 0 && Array.isArray(arg.quasis) && arg.quasis.length === 1)
1369
- keys.add(text);
1370
- else if (text)
1371
- keys.add(`${text}*`);
1372
- }
1500
+ const key = extractKeyFromArg(arg);
1501
+ if (key)
1502
+ keys.add(key);
1373
1503
  }
1374
1504
  const TS_AST_CHILD_KEYS = /* @__PURE__ */ new Set([
1375
1505
  "alternate",
@@ -1446,34 +1576,44 @@ function getDirectiveExpression(attr) {
1446
1576
  function extractKeysFromVueTemplate(templateBody, keys) {
1447
1577
  walkVueTemplateNode(templateBody, keys);
1448
1578
  }
1579
+ function collectI18nTKeys(element, keys) {
1580
+ if (element.rawName !== "i18n-t" && element.name !== "i18n-t")
1581
+ return;
1582
+ for (const attr of element.startTag.attributes) {
1583
+ if (isKeypathAttribute(attr))
1584
+ keys.add(attr.value.value);
1585
+ }
1586
+ }
1587
+ function collectAttributeKeys(element, keys) {
1588
+ for (const attr of element.startTag.attributes) {
1589
+ const expr = getDirectiveExpression(attr);
1590
+ if (!expr)
1591
+ continue;
1592
+ if (isVTDirective(attr) && expr.type === "Literal" && typeof expr.value === "string")
1593
+ keys.add(expr.value);
1594
+ walkTsAst(expr, keys);
1595
+ }
1596
+ }
1597
+ function walkChildren(node, keys) {
1598
+ if (!("children" in node) || !Array.isArray(node.children))
1599
+ return;
1600
+ for (const child of node.children) {
1601
+ if (child && typeof child === "object")
1602
+ walkVueTemplateNode(child, keys);
1603
+ }
1604
+ }
1449
1605
  function walkVueTemplateNode(node, keys) {
1450
1606
  if (!node || typeof node !== "object")
1451
1607
  return;
1452
1608
  if (isVElement(node)) {
1453
- if (node.rawName === "i18n-t" || node.name === "i18n-t") {
1454
- for (const attr of node.startTag.attributes) {
1455
- if (isKeypathAttribute(attr))
1456
- keys.add(attr.value.value);
1457
- }
1458
- }
1459
- for (const attr of node.startTag.attributes) {
1460
- const expr = getDirectiveExpression(attr);
1461
- if (!expr)
1462
- continue;
1463
- if (isVTDirective(attr) && expr.type === "Literal" && typeof expr.value === "string")
1464
- keys.add(expr.value);
1465
- walkTsAst(expr, keys);
1466
- }
1467
- for (const child of node.children)
1468
- walkVueTemplateNode(child, keys);
1609
+ collectI18nTKeys(node, keys);
1610
+ collectAttributeKeys(node, keys);
1611
+ walkChildren(node, keys);
1469
1612
  } else if (isVExpressionContainer(node)) {
1470
1613
  if (node.expression)
1471
1614
  walkTsAst(node.expression, keys);
1472
- } else if ("children" in node && Array.isArray(node.children)) {
1473
- for (const child of node.children) {
1474
- if (child && typeof child === "object")
1475
- walkVueTemplateNode(child, keys);
1476
- }
1615
+ } else {
1616
+ walkChildren(node, keys);
1477
1617
  }
1478
1618
  }
1479
1619
  function extractKeysFromSfcI18nBlock(content, keys) {
@@ -1527,45 +1667,42 @@ ${content}
1527
1667
  return void 0;
1528
1668
  }
1529
1669
  }
1530
- function collectKeysFromFile(filePath) {
1531
- const keys = /* @__PURE__ */ new Set();
1532
- let mtimeMs;
1670
+ function readFileWithMtime(filePath) {
1533
1671
  try {
1534
- mtimeMs = statSync(filePath).mtimeMs;
1672
+ const mtimeMs = statSync(filePath).mtimeMs;
1673
+ const content = readFileSync(filePath, "utf-8");
1674
+ return { content, mtimeMs };
1535
1675
  } catch {
1536
- return keys;
1537
- }
1538
- const cached = fileCache.get(filePath);
1539
- if (cached && cached.mtimeMs === mtimeMs) {
1540
- return cached.keys;
1541
- }
1542
- let content;
1543
- try {
1544
- content = readFileSync(filePath, "utf-8");
1545
- } catch {
1546
- return keys;
1547
- }
1548
- const isVue = filePath.endsWith(".vue");
1549
- const pattern = isVue ? VUE_I18N_PATTERN : I18N_CALL_PATTERN;
1550
- if (!pattern.test(content)) {
1551
- fileCache.set(filePath, { keys, mtimeMs });
1552
- return keys;
1676
+ return void 0;
1553
1677
  }
1554
- const ast = parseSourceFile(content, filePath);
1555
- if (!ast)
1556
- return keys;
1678
+ }
1679
+ function extractKeysFromAst(ast, content, isVue, keys) {
1557
1680
  if (isVue) {
1558
- if (ast.templateBody) {
1681
+ if (ast.templateBody)
1559
1682
  extractKeysFromVueTemplate(ast.templateBody, keys);
1560
- }
1561
1683
  extractKeysFromSfcI18nBlock(content, keys);
1562
1684
  }
1563
1685
  if (ast.body) {
1564
- for (const node of ast.body) {
1686
+ for (const node of ast.body)
1565
1687
  walkTsAst(node, keys);
1566
- }
1567
1688
  }
1568
- fileCache.set(filePath, { keys, mtimeMs });
1689
+ }
1690
+ function collectKeysFromFile(filePath) {
1691
+ const file = readFileWithMtime(filePath);
1692
+ if (!file)
1693
+ return /* @__PURE__ */ new Set();
1694
+ const cached = fileCache.get(filePath);
1695
+ if (cached && cached.mtimeMs === file.mtimeMs)
1696
+ return cached.keys;
1697
+ const keys = /* @__PURE__ */ new Set();
1698
+ const isVue = filePath.endsWith(".vue");
1699
+ const pattern = isVue ? VUE_I18N_PATTERN : I18N_CALL_PATTERN;
1700
+ if (pattern.test(file.content)) {
1701
+ const ast = parseSourceFile(file.content, filePath);
1702
+ if (ast)
1703
+ extractKeysFromAst(ast, file.content, isVue, keys);
1704
+ }
1705
+ fileCache.set(filePath, { keys, mtimeMs: file.mtimeMs });
1569
1706
  return keys;
1570
1707
  }
1571
1708
  function collectAllUsedKeys(srcDir, extensions) {
@@ -1639,21 +1776,22 @@ const debug = debugFactory("@rotki/eslint-plugin:no-unused-i18n-keys");
1639
1776
  function isLocaleFile(filename) {
1640
1777
  return /(?:locales?|i18n|translations?|messages?|lang)\b/i.test(filename);
1641
1778
  }
1642
- function isJsonProgram(ast) {
1643
- if (!ast || typeof ast !== "object" || !("type" in ast) || ast.type !== "Program")
1644
- return false;
1645
- if (!("body" in ast) || !Array.isArray(ast.body) || ast.body.length === 0)
1646
- return false;
1779
+ function isProgramWithBody(ast) {
1780
+ return !!ast && typeof ast === "object" && "type" in ast && ast.type === "Program" && "body" in ast && Array.isArray(ast.body) && ast.body.length > 0;
1781
+ }
1782
+ function getProgramFirstBodyType(ast) {
1783
+ if (!isProgramWithBody(ast))
1784
+ return void 0;
1647
1785
  const first = ast.body[0];
1648
- return first !== null && typeof first === "object" && "type" in first && first.type === "JSONExpressionStatement";
1786
+ if (!first || typeof first !== "object" || !("type" in first) || typeof first.type !== "string")
1787
+ return void 0;
1788
+ return first.type;
1789
+ }
1790
+ function isJsonProgram(ast) {
1791
+ return getProgramFirstBodyType(ast) === "JSONExpressionStatement";
1649
1792
  }
1650
1793
  function isYamlProgram(ast) {
1651
- if (!ast || typeof ast !== "object" || !("type" in ast) || ast.type !== "Program")
1652
- return false;
1653
- if (!("body" in ast) || !Array.isArray(ast.body) || ast.body.length === 0)
1654
- return false;
1655
- const first = ast.body[0];
1656
- return first !== null && typeof first === "object" && "type" in first && first.type === "YAMLDocument";
1794
+ return getProgramFirstBodyType(ast) === "YAMLDocument";
1657
1795
  }
1658
1796
  function buildJsonKeyPaths(node, prefix, paths) {
1659
1797
  for (const prop of node.properties) {
@@ -1954,6 +2092,7 @@ const plugin = {
1954
2092
  "no-deprecated-props": noDeprecatedProps,
1955
2093
  "no-dot-ts-imports": noDotTsImport,
1956
2094
  "no-legacy-library-import": noLegacyLibraryImport,
2095
+ "no-redundant-flex-row": noRedundantFlexRow,
1957
2096
  "no-unused-i18n-keys": noUnusedI18nKeys,
1958
2097
  "require-jsdoc-on-composable-options": requireJsdocOnComposableOptions
1959
2098
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rotki/eslint-plugin",
3
- "version": "1.3.2",
3
+ "version": "1.4.0",
4
4
  "type": "module",
5
5
  "license": "AGPL-3.0",
6
6
  "bugs": {
@@ -25,43 +25,44 @@
25
25
  },
26
26
  "sideEffects": false,
27
27
  "peerDependencies": {
28
- "eslint": "^9.20.0"
28
+ "eslint": "^9.20.0 || ^10.0.0"
29
29
  },
30
30
  "dependencies": {
31
- "@typescript-eslint/utils": "8.55.0",
31
+ "@typescript-eslint/utils": "8.60.1",
32
32
  "debug": "4.4.3",
33
- "eslint-compat-utils": "0.6.5",
34
- "jsonc-eslint-parser": "2.4.2",
33
+ "jsonc-eslint-parser": "3.1.0",
35
34
  "scule": "1.3.0",
36
- "tinyglobby": "0.2.15",
37
- "vue-eslint-parser": "10.4.0",
35
+ "tinyglobby": "0.2.17",
36
+ "vue-eslint-parser": "10.4.1",
38
37
  "yaml-eslint-parser": "2.0.0"
39
38
  },
40
39
  "devDependencies": {
41
- "@commitlint/cli": "20.4.1",
42
- "@commitlint/config-conventional": "20.4.1",
43
- "@rotki/eslint-config": "4.5.0",
44
- "@rotki/eslint-plugin": "1.2.0",
45
- "@types/debug": "4.1.12",
46
- "@types/node": "24.10.13",
47
- "@typescript-eslint/eslint-plugin": "8.55.0",
48
- "@typescript-eslint/parser": "8.55.0",
49
- "@typescript-eslint/rule-tester": "8.55.0",
50
- "@vitest/coverage-v8": "4.0.18",
51
- "bumpp": "10.4.1",
52
- "eslint": "9.39.2",
40
+ "@commitlint/cli": "21.0.2",
41
+ "@commitlint/config-conventional": "21.0.2",
42
+ "@rotki/eslint-config": "6.1.0",
43
+ "@rotki/eslint-plugin": "1.3.2",
44
+ "@types/debug": "4.1.13",
45
+ "@types/node": "24.13.1",
46
+ "@typescript-eslint/eslint-plugin": "8.60.1",
47
+ "@typescript-eslint/parser": "8.60.1",
48
+ "@typescript-eslint/rule-tester": "8.60.1",
49
+ "@vitest/coverage-v8": "4.1.8",
50
+ "bumpp": "11.1.0",
51
+ "eslint": "10.4.1",
53
52
  "husky": "9.1.7",
54
- "lint-staged": "16.2.7",
53
+ "lint-staged": "17.0.7",
54
+ "oxc-minify": "0.135.0",
55
+ "prettier": "3.8.1",
55
56
  "rimraf": "6.1.3",
56
- "tsx": "4.21.0",
57
- "typescript": "5.9.3",
57
+ "tsx": "4.22.4",
58
+ "typescript": "6.0.3",
58
59
  "unbuild": "3.6.1",
59
- "vitepress": "1.6.4",
60
- "vitest": "4.0.18"
60
+ "vitepress": "2.0.0-alpha.17",
61
+ "vitest": "4.1.8"
61
62
  },
62
63
  "engines": {
63
64
  "node": ">=24 <25",
64
- "pnpm": ">=10 <11"
65
+ "pnpm": ">=10 <12"
65
66
  },
66
67
  "lint-staged": {
67
68
  "*.{js,cjs,ts,vue,yml,json,md}": "eslint"