@rotki/eslint-plugin 1.3.2 → 1.5.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/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.5.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) {
@@ -129,13 +127,13 @@ function createRule({
129
127
  meta
130
128
  }) {
131
129
  return {
132
- create: ((context) => {
130
+ create: (context) => {
133
131
  const optionsWithDefault = context.options.map((options, index) => ({
134
132
  ...defaultOptions[index] || {},
135
133
  ...options || {}
136
134
  }));
137
135
  return create(context, optionsWithDefault);
138
- }),
136
+ },
139
137
  defaultOptions,
140
138
  meta
141
139
  };
@@ -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$h = "composable-input-flexibility";
170
168
  function checkParamForRef(param) {
171
169
  let annotation;
172
170
  if (param.type === AST_NODE_TYPES.Identifier && param.typeAnnotation) {
@@ -179,44 +177,73 @@ function checkParamForRef(param) {
179
177
  }
180
178
  return void 0;
181
179
  }
180
+ function getParamName(param) {
181
+ if (param.type === AST_NODE_TYPES.Identifier)
182
+ return param.name;
183
+ if (param.type === AST_NODE_TYPES.AssignmentPattern && param.left.type === AST_NODE_TYPES.Identifier)
184
+ return param.left.name;
185
+ return void 0;
186
+ }
187
+ function isWriteReference(reference) {
188
+ const { identifier } = reference;
189
+ const parent = identifier.parent;
190
+ if (parent.type === AST_NODE_TYPES.CallExpression) {
191
+ return parent.callee.type === AST_NODE_TYPES.Identifier && parent.callee.name === "set" && parent.arguments[0] === identifier;
192
+ }
193
+ if (parent.type === AST_NODE_TYPES.MemberExpression && parent.object === identifier && !parent.computed && parent.property.type === AST_NODE_TYPES.Identifier && parent.property.name === "value") {
194
+ const grandparent = parent.parent;
195
+ if (grandparent.type === AST_NODE_TYPES.AssignmentExpression)
196
+ return grandparent.left === parent;
197
+ return grandparent.type === AST_NODE_TYPES.UpdateExpression;
198
+ }
199
+ return false;
200
+ }
201
+ function isWrittenParam(scope, name) {
202
+ const variable = scope.variables.find((candidate) => candidate.name === name);
203
+ return variable?.references.some(isWriteReference) ?? false;
204
+ }
182
205
  const composableInputFlexibility = createEslintRule({
183
206
  create(context) {
184
207
  const autofix = context.options[0]?.autofix ?? false;
185
208
  return {
186
209
  FunctionDeclaration: (node) => {
187
- if (!node.id || !isComposableName(node.id.name))
210
+ if (!node.id || !isComposableName(node.id.name) || !node.body)
188
211
  return;
189
- checkParams(node.params);
212
+ checkParams(node.params, node);
190
213
  },
191
214
  VariableDeclarator: (node) => {
192
215
  if (node.id.type !== AST_NODE_TYPES.Identifier || !isComposableName(node.id.name))
193
216
  return;
194
217
  if (node.init?.type === AST_NODE_TYPES.ArrowFunctionExpression || node.init?.type === AST_NODE_TYPES.FunctionExpression) {
195
- checkParams(node.init.params);
218
+ checkParams(node.init.params, node.init);
196
219
  }
197
220
  }
198
221
  };
199
- function checkParams(params) {
222
+ function checkParams(params, fn) {
223
+ const scope = context.sourceCode.getScope(fn);
200
224
  for (const param of params) {
201
225
  const refType = checkParamForRef(param);
202
- if (refType) {
203
- context.report({
204
- ...autofix ? {
226
+ if (!refType)
227
+ continue;
228
+ const name = getParamName(param);
229
+ if (name && isWrittenParam(scope, name))
230
+ continue;
231
+ context.report({
232
+ ...autofix ? {
233
+ fix(fixer) {
234
+ return fixer.replaceText(refType.typeName, "MaybeRefOrGetter");
235
+ }
236
+ } : {
237
+ suggest: [{
205
238
  fix(fixer) {
206
239
  return fixer.replaceText(refType.typeName, "MaybeRefOrGetter");
207
- }
208
- } : {
209
- suggest: [{
210
- fix(fixer) {
211
- return fixer.replaceText(refType.typeName, "MaybeRefOrGetter");
212
- },
213
- messageId: "suggestMaybeRefOrGetter"
214
- }]
215
- },
216
- messageId: "preferMaybeRefOrGetter",
217
- node: refType
218
- });
219
- }
240
+ },
241
+ messageId: "suggestMaybeRefOrGetter"
242
+ }]
243
+ },
244
+ messageId: "preferMaybeRefOrGetter",
245
+ node: refType
246
+ });
220
247
  }
221
248
  }
222
249
  },
@@ -247,10 +274,10 @@ const composableInputFlexibility = createEslintRule({
247
274
  ],
248
275
  type: "suggestion"
249
276
  },
250
- name: RULE_NAME$f
277
+ name: RULE_NAME$h
251
278
  });
252
279
 
253
- const RULE_NAME$e = "composable-naming-convention";
280
+ const RULE_NAME$g = "composable-naming-convention";
254
281
  function getComposableFunction(node) {
255
282
  if (node.type === AST_NODE_TYPES.FunctionDeclaration) {
256
283
  if (!node.id || !isComposableName(node.id.name))
@@ -279,6 +306,9 @@ const composableNamingConvention = createEslintRule({
279
306
  checkComposable(node);
280
307
  }
281
308
  };
309
+ function isMismatchedConvention(typeName, prefix, suffix, expected) {
310
+ return !!typeName && typeName.startsWith(prefix) && typeName.endsWith(suffix) && typeName !== expected;
311
+ }
282
312
  function checkComposable(node) {
283
313
  const info = getComposableFunction(node);
284
314
  if (!info)
@@ -288,26 +318,14 @@ const composableNamingConvention = createEslintRule({
288
318
  const expectedReturn = `Use${pascalName}Return`;
289
319
  for (const param of info.params) {
290
320
  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
- }
321
+ const typeName = annotation ? getTypeReferenceName(annotation) : void 0;
322
+ if (isMismatchedConvention(typeName, "Use", "Options", expectedOptions))
323
+ context.report({ data: { expected: expectedOptions, got: typeName }, messageId: "optionsNaming", node: param });
301
324
  }
302
325
  if (info.returnType) {
303
326
  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
- }
327
+ if (isMismatchedConvention(returnTypeName, "Use", "Return", expectedReturn))
328
+ context.report({ data: { expected: expectedReturn, got: returnTypeName }, messageId: "returnNaming", node: info.returnType });
311
329
  }
312
330
  }
313
331
  function getParamTypeAnnotation(param) {
@@ -332,10 +350,10 @@ const composableNamingConvention = createEslintRule({
332
350
  schema: [],
333
351
  type: "suggestion"
334
352
  },
335
- name: RULE_NAME$e
353
+ name: RULE_NAME$g
336
354
  });
337
355
 
338
- const RULE_NAME$d = "composable-no-default-export";
356
+ const RULE_NAME$f = "composable-no-default-export";
339
357
  const composableNoDefaultExport = createEslintRule({
340
358
  create(context) {
341
359
  let hasComposable = false;
@@ -375,10 +393,10 @@ const composableNoDefaultExport = createEslintRule({
375
393
  schema: [],
376
394
  type: "problem"
377
395
  },
378
- name: RULE_NAME$d
396
+ name: RULE_NAME$f
379
397
  });
380
398
 
381
- const RULE_NAME$c = "composable-prefer-shallowref";
399
+ const RULE_NAME$e = "composable-prefer-shallowref";
382
400
  const composablePreferShallowref = createEslintRule({
383
401
  create(context) {
384
402
  const autofix = context.options[0]?.autofix ?? false;
@@ -437,10 +455,10 @@ const composablePreferShallowref = createEslintRule({
437
455
  ],
438
456
  type: "suggestion"
439
457
  },
440
- name: RULE_NAME$c
458
+ name: RULE_NAME$e
441
459
  });
442
460
 
443
- const RULE_NAME$b = "composable-require-cleanup";
461
+ const RULE_NAME$d = "composable-require-cleanup";
444
462
  const SIDE_EFFECT_CALLS = /* @__PURE__ */ new Set(["addEventListener", "setInterval", "setTimeout"]);
445
463
  const SIDE_EFFECT_CONSTRUCTORS = /* @__PURE__ */ new Set(["MutationObserver", "ResizeObserver", "IntersectionObserver"]);
446
464
  const CLEANUP_HOOKS = /* @__PURE__ */ new Set(["onUnmounted", "onBeforeUnmount", "onScopeDispose", "tryOnScopeDispose"]);
@@ -534,10 +552,11 @@ const composableRequireCleanup = createEslintRule({
534
552
  schema: [],
535
553
  type: "problem"
536
554
  },
537
- name: RULE_NAME$b
555
+ name: RULE_NAME$d
538
556
  });
539
557
 
540
- const RULE_NAME$a = "composable-return-readonly";
558
+ const RULE_NAME$c = "composable-return-readonly";
559
+ const DEFAULT_WRITABLE_PREFIXES = ["model"];
541
560
  const REACTIVE_CREATORS = /* @__PURE__ */ new Set(["ref", "shallowRef"]);
542
561
  const READONLY_CREATORS = /* @__PURE__ */ new Set(["computed"]);
543
562
  function isReadonlyCall(node) {
@@ -546,9 +565,49 @@ function isReadonlyCall(node) {
546
565
  const composableReturnReadonly = createEslintRule({
547
566
  create(context) {
548
567
  const autofix = context.options[0]?.autofix ?? false;
568
+ const writablePrefixes = context.options[0]?.writablePrefixes ?? DEFAULT_WRITABLE_PREFIXES;
549
569
  const source = getSourceCode(context);
550
570
  const reactiveVars = /* @__PURE__ */ new Set();
551
571
  const readonlyVars = /* @__PURE__ */ new Set();
572
+ function isWritableByConvention(name) {
573
+ return writablePrefixes.some((prefix) => {
574
+ if (!name.startsWith(prefix))
575
+ return false;
576
+ const next = name[prefix.length];
577
+ return next === void 0 || next >= "A" && next <= "Z";
578
+ });
579
+ }
580
+ function reportWithFixOrSuggest(prop, name, messageId, suggestMessageId, fixFn) {
581
+ context.report({
582
+ data: { name },
583
+ ...autofix ? { fix: fixFn } : { suggest: [{ data: { name }, fix: fixFn, messageId: suggestMessageId }] },
584
+ messageId,
585
+ node: prop
586
+ });
587
+ }
588
+ function checkUnnecessaryReadonly(prop) {
589
+ const valueNode = prop.shorthand ? null : prop.value;
590
+ if (!valueNode || !isReadonlyCall(valueNode))
591
+ return false;
592
+ const innerName = valueNode.arguments[0].name;
593
+ if (!readonlyVars.has(innerName))
594
+ return false;
595
+ reportWithFixOrSuggest(prop, innerName, "unnecessaryReadonly", "suggestRemoveReadonly", (fixer) => fixer.replaceText(valueNode, innerName));
596
+ return true;
597
+ }
598
+ function checkMissingReadonly(prop) {
599
+ if (prop.shorthand && prop.key.type === AST_NODE_TYPES.Identifier && reactiveVars.has(prop.key.name)) {
600
+ const keyName = prop.key.name;
601
+ if (isWritableByConvention(keyName))
602
+ return;
603
+ reportWithFixOrSuggest(prop, keyName, "wrapReadonly", "suggestWrapReadonly", (fixer) => fixer.replaceText(prop, `${keyName}: readonly(${keyName})`));
604
+ } else if (!prop.shorthand && prop.value.type === AST_NODE_TYPES.Identifier && reactiveVars.has(prop.value.name)) {
605
+ const valueName = prop.value.name;
606
+ if (isWritableByConvention(valueName))
607
+ return;
608
+ 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)})`));
609
+ }
610
+ }
552
611
  return {
553
612
  ReturnStatement: (node) => {
554
613
  if (!getEnclosingComposable(node))
@@ -558,39 +617,8 @@ const composableReturnReadonly = createEslintRule({
558
617
  for (const prop of node.argument.properties) {
559
618
  if (prop.type !== AST_NODE_TYPES.Property)
560
619
  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
- }
620
+ if (!checkUnnecessaryReadonly(prop))
621
+ checkMissingReadonly(prop);
594
622
  }
595
623
  },
596
624
  VariableDeclarator: (node) => {
@@ -609,7 +637,7 @@ const composableReturnReadonly = createEslintRule({
609
637
  }
610
638
  };
611
639
  },
612
- defaultOptions: [{ autofix: false }],
640
+ defaultOptions: [{ autofix: false, writablePrefixes: DEFAULT_WRITABLE_PREFIXES }],
613
641
  meta: {
614
642
  docs: {
615
643
  description: "Require returned refs from composables to be wrapped with readonly()",
@@ -631,6 +659,12 @@ const composableReturnReadonly = createEslintRule({
631
659
  default: false,
632
660
  description: "Enable auto-fix. When disabled, the fix is available as a suggestion.",
633
661
  type: "boolean"
662
+ },
663
+ writablePrefixes: {
664
+ default: DEFAULT_WRITABLE_PREFIXES,
665
+ 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).",
666
+ items: { type: "string" },
667
+ type: "array"
634
668
  }
635
669
  },
636
670
  type: "object"
@@ -638,10 +672,10 @@ const composableReturnReadonly = createEslintRule({
638
672
  ],
639
673
  type: "suggestion"
640
674
  },
641
- name: RULE_NAME$a
675
+ name: RULE_NAME$c
642
676
  });
643
677
 
644
- const RULE_NAME$9 = "composable-ssr-safety";
678
+ const RULE_NAME$b = "composable-ssr-safety";
645
679
  const BROWSER_GLOBALS = /* @__PURE__ */ new Set(["window", "document", "navigator"]);
646
680
  const LIFECYCLE_HOOKS = /* @__PURE__ */ new Set(["onMounted", "onBeforeMount"]);
647
681
  function isInsideLifecycleHook(node) {
@@ -668,15 +702,12 @@ function isInsideTypeofCheck(node) {
668
702
  }
669
703
  return false;
670
704
  }
705
+ function isTypeofBrowserGlobal(node) {
706
+ return node.type === AST_NODE_TYPES.UnaryExpression && node.operator === "typeof" && node.argument.type === AST_NODE_TYPES.Identifier && BROWSER_GLOBALS.has(node.argument.name);
707
+ }
671
708
  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
- }
709
+ if (node.type === AST_NODE_TYPES.BinaryExpression)
710
+ return isTypeofBrowserGlobal(node.left) || isTypeofBrowserGlobal(node.right);
680
711
  if (node.type === AST_NODE_TYPES.LogicalExpression)
681
712
  return hasTypeofCheck(node.left) || hasTypeofCheck(node.right);
682
713
  return false;
@@ -732,56 +763,51 @@ const composableSsrSafety = createEslintRule({
732
763
  schema: [],
733
764
  type: "problem"
734
765
  },
735
- name: RULE_NAME$9
766
+ name: RULE_NAME$b
736
767
  });
737
768
 
738
- const debug$6 = debugFactory("@rotki/eslint-plugin:consistent-ref-type-annotation");
739
- const RULE_NAME$8 = "consistent-ref-type-annotation";
769
+ const debug$7 = debugFactory("@rotki/eslint-plugin:consistent-ref-type-annotation");
770
+ const RULE_NAME$a = "consistent-ref-type-annotation";
740
771
  const FIXABLE_METHODS = /* @__PURE__ */ new Set(["ref", "computed"]);
741
- function checkAssignmentDeclaration(context, source, node, declaration, allowInference) {
742
- let declarationTypeArguments;
772
+ function getFixableCallExpression(declaration) {
743
773
  const init = declaration.init;
744
- if (!(init && init.type === TSESTree.AST_NODE_TYPES.CallExpression))
745
- return;
774
+ if (!init || init.type !== TSESTree.AST_NODE_TYPES.CallExpression)
775
+ return void 0;
746
776
  const callee = init.callee;
747
- if (!(callee && callee.type === TSESTree.AST_NODE_TYPES.Identifier))
748
- return;
749
- if (!FIXABLE_METHODS.has(callee.name))
777
+ if (!callee || callee.type !== TSESTree.AST_NODE_TYPES.Identifier || !FIXABLE_METHODS.has(callee.name))
778
+ return void 0;
779
+ return { callee, init };
780
+ }
781
+ function getDeclarationTypeArguments(declaration) {
782
+ const typeNode = declaration.id.typeAnnotation?.typeAnnotation;
783
+ if (typeNode?.type === TSESTree.AST_NODE_TYPES.TSTypeReference)
784
+ return typeNode.typeArguments;
785
+ return void 0;
786
+ }
787
+ function checkAssignmentDeclaration(context, source, node, declaration, allowInference) {
788
+ const call = getFixableCallExpression(declaration);
789
+ if (!call)
750
790
  return;
791
+ const { callee, init } = call;
751
792
  const name = callee.name;
752
- debug$6(`found ${name}, checking type arguments`);
753
- const initializationTypeArguments = init.typeArguments;
793
+ debug$7(`found ${name}, checking type arguments`);
794
+ const initTypeArgs = init.typeArguments;
795
+ const declTypeArgs = getDeclarationTypeArguments(declaration);
754
796
  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)
797
+ if (initTypeArgs && !declTypeArgs)
761
798
  return;
762
- 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
- }
799
+ debug$7(`generating report for ${name}`);
800
+ if (!initTypeArgs && !declTypeArgs) {
801
+ if (!allowInference)
802
+ context.report({ data: { name }, messageId: "missingType", node });
775
803
  return;
776
804
  }
777
805
  context.report({
778
- data: {
779
- name
780
- },
806
+ data: { name },
781
807
  fix(fixer) {
782
808
  const fixes = [];
783
- if (!initializationTypeArguments && callee)
784
- fixes.push(fixer.insertTextAfter(callee, source.getText(declarationTypeArguments)));
809
+ if (!initTypeArgs && callee)
810
+ fixes.push(fixer.insertTextAfter(callee, source.getText(declTypeArgs)));
785
811
  if (typeAnnotation)
786
812
  fixes.push(fixer.remove(typeAnnotation));
787
813
  return fixes;
@@ -830,10 +856,10 @@ const consistentRefTypeAnnotation = createEslintRule({
830
856
  ],
831
857
  type: "problem"
832
858
  },
833
- name: RULE_NAME$8
859
+ name: RULE_NAME$a
834
860
  });
835
861
 
836
- const RULE_NAME$7 = "max-dependencies";
862
+ const RULE_NAME$9 = "max-dependencies";
837
863
  const maxDependencies = createEslintRule({
838
864
  create(context, [options]) {
839
865
  let dependencyCount = 0;
@@ -893,11 +919,11 @@ const maxDependencies = createEslintRule({
893
919
  ],
894
920
  type: "suggestion"
895
921
  },
896
- name: RULE_NAME$7
922
+ name: RULE_NAME$9
897
923
  });
898
924
 
899
- const RULE_NAME$6 = "no-deprecated-classes";
900
- const debug$5 = debugFactory("@rotki/eslint-plugin:no-deprecated-classes");
925
+ const RULE_NAME$8 = "no-deprecated-classes";
926
+ const debug$6 = debugFactory("@rotki/eslint-plugin:no-deprecated-classes");
901
927
  const stringReplacements = /* @__PURE__ */ new Map([
902
928
  ["d-block", "block"],
903
929
  ["d-flex", "flex"],
@@ -951,7 +977,7 @@ function getRange(node) {
951
977
  return node.range;
952
978
  }
953
979
  function reportReplacement(className, replacement, node, context, source, position = 1) {
954
- debug$5(`found replacement ${replacement} for ${className}`);
980
+ debug$6(`found replacement ${replacement} for ${className}`);
955
981
  const initialRange = getRange(node);
956
982
  const range = [
957
983
  initialRange[0] + position,
@@ -973,51 +999,62 @@ function reportReplacement(className, replacement, node, context, source, positi
973
999
  messageId: "replacedWith"
974
1000
  });
975
1001
  }
1002
+ function splitClassNames(classNames, reportNode) {
1003
+ return classNames.split(/\s+/).map((className) => ({ className, position: classNames.indexOf(className) + 1, reportNode }));
1004
+ }
1005
+ function* extractFromObject(node) {
1006
+ for (const prop of node.properties) {
1007
+ if (prop.type !== "Property")
1008
+ continue;
1009
+ const classNames = getStaticPropertyName(prop);
1010
+ if (classNames)
1011
+ yield* splitClassNames(classNames, prop.key);
1012
+ }
1013
+ }
1014
+ function* extractFromArray(node) {
1015
+ for (const element of node.elements) {
1016
+ if (element == null || element.type === "SpreadElement")
1017
+ continue;
1018
+ yield* extractClassNames(element);
1019
+ }
1020
+ }
1021
+ function* extractFromTemplateLiteral(node) {
1022
+ for (const templateElement of node.quasis) {
1023
+ const classNames = templateElement.value.cooked;
1024
+ if (classNames !== null)
1025
+ yield* splitClassNames(classNames, templateElement);
1026
+ }
1027
+ for (const expr of node.expressions)
1028
+ yield* extractClassNames(expr, true);
1029
+ }
1030
+ function* extractFromBinary(node) {
1031
+ if (node.operator !== "+")
1032
+ return;
1033
+ if (node.left.type !== "PrivateIdentifier")
1034
+ yield* extractClassNames(node.left, true);
1035
+ yield* extractClassNames(node.right, true);
1036
+ }
976
1037
  function* extractClassNames(node, textOnly = false) {
977
1038
  if (node.type === "Literal") {
978
- const classNames = `${node.value}`;
979
- yield* classNames.split(/\s+/).map((className) => ({ className, position: classNames.indexOf(className) + 1, reportNode: node }));
1039
+ yield* splitClassNames(`${node.value}`, node);
980
1040
  return;
981
1041
  }
982
1042
  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);
1043
+ yield* extractFromTemplateLiteral(node);
991
1044
  return;
992
1045
  }
993
1046
  if (node.type === "BinaryExpression") {
994
- if (node.operator !== "+")
995
- return;
996
- yield* extractClassNames(node.left, true);
997
- yield* extractClassNames(node.right, true);
1047
+ yield* extractFromBinary(node);
998
1048
  return;
999
1049
  }
1000
1050
  if (textOnly)
1001
1051
  return;
1002
1052
  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
- }
1053
+ yield* extractFromObject(node);
1011
1054
  return;
1012
1055
  }
1013
1056
  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
- }
1057
+ yield* extractFromArray(node);
1021
1058
  }
1022
1059
  if (node.type === "ConditionalExpression") {
1023
1060
  yield* extractClassNames(node.consequent);
@@ -1064,11 +1101,11 @@ const noDeprecatedClasses = createEslintRule({
1064
1101
  schema: [],
1065
1102
  type: "problem"
1066
1103
  },
1067
- name: RULE_NAME$6
1104
+ name: RULE_NAME$8
1068
1105
  });
1069
1106
 
1070
- const debug$4 = debugFactory("@rotki/eslint-plugin:no-deprecated-components");
1071
- const RULE_NAME$5 = "no-deprecated-components";
1107
+ const debug$5 = debugFactory("@rotki/eslint-plugin:no-deprecated-components");
1108
+ const RULE_NAME$7 = "no-deprecated-components";
1072
1109
  const vuetify = {
1073
1110
  VApp: true,
1074
1111
  VAppBar: true,
@@ -1105,7 +1142,7 @@ const skipInLegacy = /* @__PURE__ */ new Set([
1105
1142
  "Fragment"
1106
1143
  ]);
1107
1144
  function hasReplacement$1(tag) {
1108
- return Object.prototype.hasOwnProperty.call(replacements$1, tag);
1145
+ return Object.hasOwn(replacements$1, tag);
1109
1146
  }
1110
1147
  const noDeprecatedComponents = createEslintRule({
1111
1148
  create(context, optionsWithDefault) {
@@ -1121,7 +1158,7 @@ const noDeprecatedComponents = createEslintRule({
1121
1158
  return;
1122
1159
  const replacement = replacements$1[tag];
1123
1160
  if (replacement || legacy && skipInLegacy.has(tag)) {
1124
- debug$4(`${tag} has been deprecated`);
1161
+ debug$5(`${tag} has been deprecated`);
1125
1162
  context.report({
1126
1163
  data: {
1127
1164
  name: tag
@@ -1130,7 +1167,7 @@ const noDeprecatedComponents = createEslintRule({
1130
1167
  node: element
1131
1168
  });
1132
1169
  } else {
1133
- debug$4(`${tag} has will be removed`);
1170
+ debug$5(`${tag} has will be removed`);
1134
1171
  context.report({
1135
1172
  data: {
1136
1173
  name: tag
@@ -1173,11 +1210,10 @@ const noDeprecatedComponents = createEslintRule({
1173
1210
  ],
1174
1211
  type: "problem"
1175
1212
  },
1176
- name: RULE_NAME$5
1213
+ name: RULE_NAME$7
1177
1214
  });
1178
1215
 
1179
- const debug$3 = debugFactory("@rotki/eslint-plugin:no-deprecated-props");
1180
- const RULE_NAME$4 = "no-deprecated-props";
1216
+ const RULE_NAME$6 = "no-deprecated-props";
1181
1217
  const replacements = {
1182
1218
  RuiRadio: {
1183
1219
  internalValue: "value"
@@ -1200,37 +1236,34 @@ function getPropName(node) {
1200
1236
  }
1201
1237
  return kebabCase(node.key.rawName);
1202
1238
  }
1239
+ function isNonBindDirective(node) {
1240
+ return node.directive && node.value?.type === "VExpressionContainer" && (node.key.name.name !== "bind" || !node.key.argument);
1241
+ }
1242
+ function getPropNameNode(node) {
1243
+ return node.directive ? node.key.argument : node.key;
1244
+ }
1203
1245
  const noDeprecatedProps = createEslintRule({
1204
1246
  create(context) {
1205
1247
  return defineTemplateBodyVisitor(context, {
1206
1248
  VAttribute(node) {
1207
- if (node.directive && (node.value?.type === "VExpressionContainer" && (node.key.name.name !== "bind" || !node.key.argument)))
1249
+ if (isNonBindDirective(node))
1208
1250
  return;
1209
1251
  const tag = pascalCase(node.parent.parent.rawName);
1210
1252
  if (!hasReplacement(tag))
1211
1253
  return;
1212
- debug$3(`${tag} has replacement properties`);
1213
1254
  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");
1255
+ const propNameNode = getPropNameNode(node);
1256
+ if (!propName || !propNameNode)
1217
1257
  return;
1218
- }
1219
1258
  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
- }
1259
+ if (!match)
1260
+ return;
1261
+ context.report({
1262
+ data: { prop: match.original, replacement: match.replacement },
1263
+ fix: (fixer) => fixer.replaceText(propNameNode, match.replacement),
1264
+ messageId: "replacedWith",
1265
+ node: propNameNode
1266
+ });
1234
1267
  }
1235
1268
  });
1236
1269
  },
@@ -1247,10 +1280,10 @@ const noDeprecatedProps = createEslintRule({
1247
1280
  schema: [],
1248
1281
  type: "problem"
1249
1282
  },
1250
- name: RULE_NAME$4
1283
+ name: RULE_NAME$6
1251
1284
  });
1252
1285
 
1253
- const RULE_NAME$3 = "no-dot-ts-imports";
1286
+ const RULE_NAME$5 = "no-dot-ts-imports";
1254
1287
  const noDotTsImport = createEslintRule({
1255
1288
  create(context) {
1256
1289
  return {
@@ -1285,10 +1318,10 @@ const noDotTsImport = createEslintRule({
1285
1318
  schema: [],
1286
1319
  type: "problem"
1287
1320
  },
1288
- name: RULE_NAME$3
1321
+ name: RULE_NAME$5
1289
1322
  });
1290
1323
 
1291
- const RULE_NAME$2 = "no-legacy-library-import";
1324
+ const RULE_NAME$4 = "no-legacy-library-import";
1292
1325
  const legacyLibrary = "@rotki/ui-library-compat";
1293
1326
  const newLibrary = "@rotki/ui-library";
1294
1327
  const noLegacyLibraryImport = createEslintRule({
@@ -1321,6 +1354,261 @@ const noLegacyLibraryImport = createEslintRule({
1321
1354
  schema: [],
1322
1355
  type: "problem"
1323
1356
  },
1357
+ name: RULE_NAME$4
1358
+ });
1359
+
1360
+ const RULE_NAME$3 = "no-redundant-flex-row";
1361
+ const debug$4 = debugFactory("@rotki/eslint-plugin:no-redundant-flex-row");
1362
+ function stripImportant(token) {
1363
+ return token.replace(/^!/, "").replace(/!$/, "");
1364
+ }
1365
+ function isUnconditional(token) {
1366
+ return !token.includes(":");
1367
+ }
1368
+ function isFlexDisplay(token) {
1369
+ if (!isUnconditional(token))
1370
+ return false;
1371
+ const base = stripImportant(token);
1372
+ return base === "flex" || base === "inline-flex";
1373
+ }
1374
+ function isFlexRow(token) {
1375
+ if (!isUnconditional(token))
1376
+ return false;
1377
+ return stripImportant(token) === "flex-row";
1378
+ }
1379
+ function tokenize(value) {
1380
+ const tokens = [];
1381
+ const matcher = /\S+/g;
1382
+ let match;
1383
+ while ((match = matcher.exec(value)) !== null)
1384
+ tokens.push({ start: match.index, text: match[0] });
1385
+ return tokens;
1386
+ }
1387
+ function removalRange(value, token, contentStart) {
1388
+ let startRel = token.start;
1389
+ let endRel = token.start + token.text.length;
1390
+ if (startRel > 0 && /\s/.test(value[startRel - 1])) {
1391
+ while (startRel > 0 && /\s/.test(value[startRel - 1]))
1392
+ startRel--;
1393
+ } else {
1394
+ while (endRel < value.length && /\s/.test(value[endRel]))
1395
+ endRel++;
1396
+ }
1397
+ return [contentStart + startRel, contentStart + endRel];
1398
+ }
1399
+ function reportRedundant(source, context, sourceCode) {
1400
+ const tokens = tokenize(source.value);
1401
+ if (!tokens.some((token) => isFlexDisplay(token.text)))
1402
+ return;
1403
+ for (const token of tokens) {
1404
+ if (!isFlexRow(token.text))
1405
+ continue;
1406
+ debug$4(`found redundant flex-row token '${token.text}'`);
1407
+ const tokenRange = [
1408
+ source.contentStart + token.start,
1409
+ source.contentStart + token.start + token.text.length
1410
+ ];
1411
+ const removal = removalRange(source.value, token, source.contentStart);
1412
+ context.report({
1413
+ data: { className: token.text },
1414
+ fix(fixer) {
1415
+ return fixer.removeRange(removal);
1416
+ },
1417
+ loc: {
1418
+ end: sourceCode.getLocFromIndex(tokenRange[1]),
1419
+ start: sourceCode.getLocFromIndex(tokenRange[0])
1420
+ },
1421
+ messageId: "redundantFlexRow"
1422
+ });
1423
+ }
1424
+ }
1425
+ function plainStringLists(expression) {
1426
+ if (expression.type === "Literal" && typeof expression.value === "string")
1427
+ return [{ contentStart: expression.range[0] + 1, value: expression.value }];
1428
+ if (expression.type === "TemplateLiteral" && expression.expressions.length === 0 && expression.quasis.length === 1) {
1429
+ const quasi = expression.quasis[0];
1430
+ const cooked = quasi.value.cooked;
1431
+ if (cooked !== null)
1432
+ return [{ contentStart: quasi.range[0] + 1, value: cooked }];
1433
+ }
1434
+ return [];
1435
+ }
1436
+ const noRedundantFlexRow = createEslintRule({
1437
+ create(context) {
1438
+ const sourceCode = getSourceCode(context);
1439
+ return defineTemplateBodyVisitor(context, {
1440
+ 'VAttribute[directive=false][key.name="class"]': function(node) {
1441
+ if (!node.value || !node.value.value || !node.value.range)
1442
+ return;
1443
+ reportRedundant(
1444
+ { contentStart: node.value.range[0] + 1, value: node.value.value },
1445
+ context,
1446
+ sourceCode
1447
+ );
1448
+ },
1449
+ "VAttribute[directive=true][key.name.name='bind'][key.argument.name='class'] > VExpressionContainer.value": function(node) {
1450
+ if (!node.expression)
1451
+ return;
1452
+ for (const source of plainStringLists(node.expression))
1453
+ reportRedundant(source, context, sourceCode);
1454
+ }
1455
+ });
1456
+ },
1457
+ defaultOptions: [],
1458
+ meta: {
1459
+ docs: {
1460
+ description: "disallow redundant `flex-row` since `flex` already defaults to the row direction",
1461
+ recommendation: "recommended"
1462
+ },
1463
+ fixable: "code",
1464
+ messages: {
1465
+ 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')`
1466
+ },
1467
+ schema: [],
1468
+ type: "suggestion"
1469
+ },
1470
+ name: RULE_NAME$3
1471
+ });
1472
+
1473
+ const RULE_NAME$2 = "no-shared-pinia";
1474
+ const debug$3 = debugFactory("@rotki/eslint-plugin:no-shared-pinia");
1475
+ const DESCRIBE_NAMES = /* @__PURE__ */ new Set(["describe", "fdescribe", "xdescribe", "suite"]);
1476
+ const BEFORE_EACH_NAMES = /* @__PURE__ */ new Set(["beforeEach"]);
1477
+ const DEFAULT_FACTORIES = ["createPinia", "createCustomPinia", "createTestingPinia"];
1478
+ const DEFAULT_TEST_FILE_PATTERN = "\\.(spec|test)\\.[cm]?[jt]sx?$";
1479
+ function isFunctionNode(node) {
1480
+ return node.type === "FunctionDeclaration" || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression";
1481
+ }
1482
+ function getRootCalleeName(callee) {
1483
+ if (callee.type === "Identifier")
1484
+ return callee.name;
1485
+ if (callee.type === "MemberExpression")
1486
+ return getRootCalleeName(callee.object);
1487
+ if (callee.type === "CallExpression")
1488
+ return getRootCalleeName(callee.callee);
1489
+ return null;
1490
+ }
1491
+ function callbackOwnerName(fn) {
1492
+ const parent = fn.parent;
1493
+ if (!parent || parent.type !== "CallExpression")
1494
+ return null;
1495
+ return getRootCalleeName(parent.callee);
1496
+ }
1497
+ function isBeforeEachCallback(fn) {
1498
+ const owner = callbackOwnerName(fn);
1499
+ return owner != null && BEFORE_EACH_NAMES.has(owner);
1500
+ }
1501
+ function isDescribeCallback(fn) {
1502
+ const owner = callbackOwnerName(fn);
1503
+ return owner != null && DESCRIBE_NAMES.has(owner);
1504
+ }
1505
+ function nearestFunction(node) {
1506
+ let current = node.parent;
1507
+ while (current) {
1508
+ if (isFunctionNode(current))
1509
+ return current;
1510
+ current = current.parent;
1511
+ }
1512
+ return null;
1513
+ }
1514
+ function buildTestFileMatcher(pattern) {
1515
+ const regex = new RegExp(pattern, "u");
1516
+ return (filename) => regex.test(filename);
1517
+ }
1518
+ const noSharedPinia = createEslintRule({
1519
+ create(context, optionsWithDefault) {
1520
+ const options = optionsWithDefault[0] ?? {};
1521
+ const factories = new Set(options.factories ?? DEFAULT_FACTORIES);
1522
+ const isTestFile = buildTestFileMatcher(options.testFilePattern ?? DEFAULT_TEST_FILE_PATTERN);
1523
+ const filename = getFilename(context);
1524
+ if (!isTestFile(filename)) {
1525
+ debug$3(`skipping non-test file '${filename}'`);
1526
+ return {};
1527
+ }
1528
+ let program = null;
1529
+ const candidates = [];
1530
+ const freshContainers = /* @__PURE__ */ new Set();
1531
+ return {
1532
+ CallExpression(node) {
1533
+ const name = getRootCalleeName(node.callee);
1534
+ if (name == null || !factories.has(name) || program == null)
1535
+ return;
1536
+ const enclosing = nearestFunction(node);
1537
+ if (enclosing != null && isBeforeEachCallback(enclosing)) {
1538
+ freshContainers.add(nearestFunction(enclosing) ?? program);
1539
+ return;
1540
+ }
1541
+ if (enclosing != null && !isDescribeCallback(enclosing))
1542
+ return;
1543
+ candidates.push({
1544
+ atModuleScope: enclosing == null,
1545
+ container: enclosing ?? program,
1546
+ factory: name,
1547
+ node
1548
+ });
1549
+ },
1550
+ Program(node) {
1551
+ program = node;
1552
+ },
1553
+ "Program:exit": function() {
1554
+ const isProtected = (container) => {
1555
+ let current = container;
1556
+ while (current != null) {
1557
+ if (freshContainers.has(current))
1558
+ return true;
1559
+ if (current.type === "Program")
1560
+ break;
1561
+ current = nearestFunction(current) ?? program;
1562
+ }
1563
+ return false;
1564
+ };
1565
+ for (const candidate of candidates) {
1566
+ if (isProtected(candidate.container))
1567
+ continue;
1568
+ debug$3(`found shared pinia via '${candidate.factory}' ${candidate.atModuleScope ? "at module scope" : "in describe body"}`);
1569
+ context.report({
1570
+ data: {
1571
+ factory: candidate.factory,
1572
+ location: candidate.atModuleScope ? "at module scope" : "in the `describe` body"
1573
+ },
1574
+ messageId: "sharedPinia",
1575
+ node: candidate.node
1576
+ });
1577
+ }
1578
+ }
1579
+ };
1580
+ },
1581
+ defaultOptions: [
1582
+ {
1583
+ factories: DEFAULT_FACTORIES,
1584
+ testFilePattern: DEFAULT_TEST_FILE_PATTERN
1585
+ }
1586
+ ],
1587
+ meta: {
1588
+ docs: {
1589
+ description: "disallow a Pinia instance shared across tests via `describe`-body or module scope",
1590
+ recommendation: "recommended"
1591
+ },
1592
+ messages: {
1593
+ sharedPinia: "`{{ factory }}()` is called {{ location }}, so the Pinia instance is shared across every test and store state leaks between them. Create a fresh instance per test with `beforeEach(() => setActivePinia(createPinia()))`, or add an inline disable if the shared instance is intentional."
1594
+ },
1595
+ schema: [
1596
+ {
1597
+ additionalProperties: false,
1598
+ properties: {
1599
+ factories: {
1600
+ items: { type: "string" },
1601
+ type: "array"
1602
+ },
1603
+ testFilePattern: {
1604
+ type: "string"
1605
+ }
1606
+ },
1607
+ type: "object"
1608
+ }
1609
+ ],
1610
+ type: "problem"
1611
+ },
1324
1612
  name: RULE_NAME$2
1325
1613
  });
1326
1614
 
@@ -1337,18 +1625,39 @@ function isI18nCallExpression(node) {
1337
1625
  return I18N_FUNCTION_NAMES.has(callee.name);
1338
1626
  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
1627
  }
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)
1628
+ function getQuasiText(quasi) {
1629
+ if (!quasi || typeof quasi !== "object" || !("value" in quasi))
1344
1630
  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;
1631
+ const quasiObj = quasi.value;
1349
1632
  if (!quasiObj || typeof quasiObj !== "object")
1350
1633
  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;
1634
+ if ("cooked" in quasiObj && typeof quasiObj.cooked === "string")
1635
+ return quasiObj.cooked;
1636
+ if ("raw" in quasiObj && typeof quasiObj.raw === "string")
1637
+ return quasiObj.raw;
1638
+ return void 0;
1639
+ }
1640
+ function getTemplateLiteralText(arg) {
1641
+ const { expressions, quasis } = arg;
1642
+ if (!Array.isArray(quasis) || !Array.isArray(expressions) || quasis.length === 0)
1643
+ return void 0;
1644
+ return getQuasiText(quasis[0]);
1645
+ }
1646
+ function isStaticTemplateLiteral(arg) {
1647
+ return Array.isArray(arg.expressions) && arg.expressions.length === 0 && Array.isArray(arg.quasis) && arg.quasis.length === 1;
1648
+ }
1649
+ function extractKeyFromArg(arg) {
1650
+ if (arg.type === "Literal" && typeof arg.value === "string")
1651
+ return arg.value;
1652
+ if (arg.type === "TemplateLiteral") {
1653
+ const text = getTemplateLiteralText(arg);
1654
+ if (text === void 0)
1655
+ return void 0;
1656
+ if (isStaticTemplateLiteral(arg))
1657
+ return text;
1658
+ return text ? `${text}*` : void 0;
1659
+ }
1660
+ return void 0;
1352
1661
  }
1353
1662
  function extractKeysFromCallExpression(node, keys) {
1354
1663
  if (!isI18nCallExpression(node))
@@ -1359,17 +1668,9 @@ function extractKeysFromCallExpression(node, keys) {
1359
1668
  const arg = args[0];
1360
1669
  if (!isAstNode(arg))
1361
1670
  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
- }
1671
+ const key = extractKeyFromArg(arg);
1672
+ if (key)
1673
+ keys.add(key);
1373
1674
  }
1374
1675
  const TS_AST_CHILD_KEYS = /* @__PURE__ */ new Set([
1375
1676
  "alternate",
@@ -1446,34 +1747,44 @@ function getDirectiveExpression(attr) {
1446
1747
  function extractKeysFromVueTemplate(templateBody, keys) {
1447
1748
  walkVueTemplateNode(templateBody, keys);
1448
1749
  }
1750
+ function collectI18nTKeys(element, keys) {
1751
+ if (element.rawName !== "i18n-t" && element.name !== "i18n-t")
1752
+ return;
1753
+ for (const attr of element.startTag.attributes) {
1754
+ if (isKeypathAttribute(attr))
1755
+ keys.add(attr.value.value);
1756
+ }
1757
+ }
1758
+ function collectAttributeKeys(element, keys) {
1759
+ for (const attr of element.startTag.attributes) {
1760
+ const expr = getDirectiveExpression(attr);
1761
+ if (!expr)
1762
+ continue;
1763
+ if (isVTDirective(attr) && expr.type === "Literal" && typeof expr.value === "string")
1764
+ keys.add(expr.value);
1765
+ walkTsAst(expr, keys);
1766
+ }
1767
+ }
1768
+ function walkChildren(node, keys) {
1769
+ if (!("children" in node) || !Array.isArray(node.children))
1770
+ return;
1771
+ for (const child of node.children) {
1772
+ if (child && typeof child === "object")
1773
+ walkVueTemplateNode(child, keys);
1774
+ }
1775
+ }
1449
1776
  function walkVueTemplateNode(node, keys) {
1450
1777
  if (!node || typeof node !== "object")
1451
1778
  return;
1452
1779
  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);
1780
+ collectI18nTKeys(node, keys);
1781
+ collectAttributeKeys(node, keys);
1782
+ walkChildren(node, keys);
1469
1783
  } else if (isVExpressionContainer(node)) {
1470
1784
  if (node.expression)
1471
1785
  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
- }
1786
+ } else {
1787
+ walkChildren(node, keys);
1477
1788
  }
1478
1789
  }
1479
1790
  function extractKeysFromSfcI18nBlock(content, keys) {
@@ -1527,45 +1838,42 @@ ${content}
1527
1838
  return void 0;
1528
1839
  }
1529
1840
  }
1530
- function collectKeysFromFile(filePath) {
1531
- const keys = /* @__PURE__ */ new Set();
1532
- let mtimeMs;
1841
+ function readFileWithMtime(filePath) {
1533
1842
  try {
1534
- mtimeMs = statSync(filePath).mtimeMs;
1843
+ const mtimeMs = statSync(filePath).mtimeMs;
1844
+ const content = readFileSync(filePath, "utf-8");
1845
+ return { content, mtimeMs };
1535
1846
  } 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;
1847
+ return void 0;
1553
1848
  }
1554
- const ast = parseSourceFile(content, filePath);
1555
- if (!ast)
1556
- return keys;
1849
+ }
1850
+ function extractKeysFromAst(ast, content, isVue, keys) {
1557
1851
  if (isVue) {
1558
- if (ast.templateBody) {
1852
+ if (ast.templateBody)
1559
1853
  extractKeysFromVueTemplate(ast.templateBody, keys);
1560
- }
1561
1854
  extractKeysFromSfcI18nBlock(content, keys);
1562
1855
  }
1563
1856
  if (ast.body) {
1564
- for (const node of ast.body) {
1857
+ for (const node of ast.body)
1565
1858
  walkTsAst(node, keys);
1566
- }
1567
1859
  }
1568
- fileCache.set(filePath, { keys, mtimeMs });
1860
+ }
1861
+ function collectKeysFromFile(filePath) {
1862
+ const file = readFileWithMtime(filePath);
1863
+ if (!file)
1864
+ return /* @__PURE__ */ new Set();
1865
+ const cached = fileCache.get(filePath);
1866
+ if (cached && cached.mtimeMs === file.mtimeMs)
1867
+ return cached.keys;
1868
+ const keys = /* @__PURE__ */ new Set();
1869
+ const isVue = filePath.endsWith(".vue");
1870
+ const pattern = isVue ? VUE_I18N_PATTERN : I18N_CALL_PATTERN;
1871
+ if (pattern.test(file.content)) {
1872
+ const ast = parseSourceFile(file.content, filePath);
1873
+ if (ast)
1874
+ extractKeysFromAst(ast, file.content, isVue, keys);
1875
+ }
1876
+ fileCache.set(filePath, { keys, mtimeMs: file.mtimeMs });
1569
1877
  return keys;
1570
1878
  }
1571
1879
  function collectAllUsedKeys(srcDir, extensions) {
@@ -1639,21 +1947,22 @@ const debug = debugFactory("@rotki/eslint-plugin:no-unused-i18n-keys");
1639
1947
  function isLocaleFile(filename) {
1640
1948
  return /(?:locales?|i18n|translations?|messages?|lang)\b/i.test(filename);
1641
1949
  }
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;
1950
+ function isProgramWithBody(ast) {
1951
+ return !!ast && typeof ast === "object" && "type" in ast && ast.type === "Program" && "body" in ast && Array.isArray(ast.body) && ast.body.length > 0;
1952
+ }
1953
+ function getProgramFirstBodyType(ast) {
1954
+ if (!isProgramWithBody(ast))
1955
+ return void 0;
1647
1956
  const first = ast.body[0];
1648
- return first !== null && typeof first === "object" && "type" in first && first.type === "JSONExpressionStatement";
1957
+ if (!first || typeof first !== "object" || !("type" in first) || typeof first.type !== "string")
1958
+ return void 0;
1959
+ return first.type;
1960
+ }
1961
+ function isJsonProgram(ast) {
1962
+ return getProgramFirstBodyType(ast) === "JSONExpressionStatement";
1649
1963
  }
1650
1964
  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";
1965
+ return getProgramFirstBodyType(ast) === "YAMLDocument";
1657
1966
  }
1658
1967
  function buildJsonKeyPaths(node, prefix, paths) {
1659
1968
  for (const prop of node.properties) {
@@ -1954,6 +2263,8 @@ const plugin = {
1954
2263
  "no-deprecated-props": noDeprecatedProps,
1955
2264
  "no-dot-ts-imports": noDotTsImport,
1956
2265
  "no-legacy-library-import": noLegacyLibraryImport,
2266
+ "no-redundant-flex-row": noRedundantFlexRow,
2267
+ "no-shared-pinia": noSharedPinia,
1957
2268
  "no-unused-i18n-keys": noUnusedI18nKeys,
1958
2269
  "require-jsdoc-on-composable-options": requireJsdocOnComposableOptions
1959
2270
  }