@rotki/eslint-plugin 1.2.0 → 1.3.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,10 +1,13 @@
1
- import { TSESTree } from '@typescript-eslint/utils';
2
- import createDebug from 'debug';
1
+ import { AST_NODE_TYPES, TSESTree } from '@typescript-eslint/utils';
3
2
  import * as compat from 'eslint-compat-utils';
4
- import { extname } from 'node:path';
3
+ import debugFactory from 'debug';
4
+ import { extname, resolve } from 'node:path';
5
5
  import { pascalCase, kebabCase } from 'scule';
6
+ import { statSync, readFileSync } from 'node:fs';
7
+ import { globSync } from 'tinyglobby';
8
+ import { parse } from 'vue-eslint-parser';
6
9
 
7
- const version = "1.2.0";
10
+ const version = "1.3.0";
8
11
  const pkg = {
9
12
  version: version};
10
13
 
@@ -15,8 +18,34 @@ function getSourceCode(context) {
15
18
  return compat.getSourceCode(context);
16
19
  }
17
20
 
18
- function createRecommended(plugin, name, flat) {
19
- const rules = Object.fromEntries(Object.entries(plugin.rules).filter(([_key, rule]) => rule.meta.recommended === "recommended" && !rule.meta.deprecated).map(([key]) => [`${name}/${key}`, 2]));
21
+ const COMPOSABLE_PATTERN = /^use[A-Z]/;
22
+ function isComposableName(name) {
23
+ return COMPOSABLE_PATTERN.test(name);
24
+ }
25
+ function getEnclosingComposable(node) {
26
+ let current = node.parent;
27
+ while (current) {
28
+ if (current.type === AST_NODE_TYPES.FunctionDeclaration && current.id && isComposableName(current.id.name))
29
+ 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
+ current = current.parent;
37
+ }
38
+ return void 0;
39
+ }
40
+ function isInsideComposable(node) {
41
+ return getEnclosingComposable(node) !== void 0;
42
+ }
43
+ function composableNameToPascal(name) {
44
+ return name.slice(3);
45
+ }
46
+
47
+ function createConfig(plugin, name, flat, category) {
48
+ const rules = Object.fromEntries(Object.entries(plugin.rules).filter(([_key, rule]) => rule.meta.docs?.recommendation === category && !rule.meta.deprecated).map(([key]) => [`${name}/${key}`, 2]));
20
49
  if (flat) {
21
50
  return {
22
51
  plugins: {
@@ -31,6 +60,9 @@ function createRecommended(plugin, name, flat) {
31
60
  };
32
61
  }
33
62
  }
63
+ function createRecommended(plugin, name, flat) {
64
+ return createConfig(plugin, name, flat, "recommended");
65
+ }
34
66
 
35
67
  function getStringLiteralValue(node, stringOnly = false) {
36
68
  if (node.type === "Literal") {
@@ -134,12 +166,491 @@ function defineTemplateBodyVisitor(context, templateBodyVisitor, scriptVisitor,
134
166
  );
135
167
  }
136
168
 
137
- const debug$3 = createDebug("@rotki/eslint-plugin:consistent-ref-type-annotation");
138
- const RULE_NAME$6 = "consistent-ref-type-annotation";
139
- const FIXABLE_METHODS = ["ref", "computed"];
140
- function checkAssignmentDeclaration(context, node, declaration, options) {
141
- const source = getSourceCode(context);
142
- const { allowInference } = options;
169
+ const RULE_NAME$f = "composable-input-flexibility";
170
+ function checkParamForRef(param) {
171
+ let annotation;
172
+ if (param.type === AST_NODE_TYPES.Identifier && param.typeAnnotation) {
173
+ annotation = param.typeAnnotation.typeAnnotation;
174
+ } else if (param.type === AST_NODE_TYPES.AssignmentPattern && param.left.type === AST_NODE_TYPES.Identifier && param.left.typeAnnotation) {
175
+ annotation = param.left.typeAnnotation.typeAnnotation;
176
+ }
177
+ if (annotation?.type === AST_NODE_TYPES.TSTypeReference && annotation.typeName.type === AST_NODE_TYPES.Identifier && annotation.typeName.name === "Ref") {
178
+ return annotation;
179
+ }
180
+ return void 0;
181
+ }
182
+ const composableInputFlexibility = createEslintRule({
183
+ create(context) {
184
+ return {
185
+ FunctionDeclaration: (node) => {
186
+ if (!node.id || !isComposableName(node.id.name))
187
+ return;
188
+ checkParams(node.params);
189
+ },
190
+ VariableDeclarator: (node) => {
191
+ if (node.id.type !== AST_NODE_TYPES.Identifier || !isComposableName(node.id.name))
192
+ return;
193
+ if (node.init?.type === AST_NODE_TYPES.ArrowFunctionExpression || node.init?.type === AST_NODE_TYPES.FunctionExpression) {
194
+ checkParams(node.init.params);
195
+ }
196
+ }
197
+ };
198
+ function checkParams(params) {
199
+ for (const param of params) {
200
+ const refType = checkParamForRef(param);
201
+ if (refType) {
202
+ context.report({
203
+ fix(fixer) {
204
+ return fixer.replaceText(refType.typeName, "MaybeRefOrGetter");
205
+ },
206
+ messageId: "preferMaybeRefOrGetter",
207
+ node: refType
208
+ });
209
+ }
210
+ }
211
+ }
212
+ },
213
+ defaultOptions: [],
214
+ meta: {
215
+ docs: {
216
+ description: "Prefer MaybeRefOrGetter over Ref for composable parameters",
217
+ recommendation: "stylistic"
218
+ },
219
+ fixable: "code",
220
+ messages: {
221
+ preferMaybeRefOrGetter: "Use MaybeRefOrGetter<T> instead of Ref<T> for composable parameters to increase input flexibility."
222
+ },
223
+ schema: [],
224
+ type: "suggestion"
225
+ },
226
+ name: RULE_NAME$f
227
+ });
228
+
229
+ const RULE_NAME$e = "composable-naming-convention";
230
+ function getComposableFunction(node) {
231
+ if (node.type === AST_NODE_TYPES.FunctionDeclaration) {
232
+ if (!node.id || !isComposableName(node.id.name))
233
+ return void 0;
234
+ return { fn: node, name: node.id.name, params: node.params, returnType: node.returnType };
235
+ }
236
+ if (node.type === AST_NODE_TYPES.VariableDeclarator && node.id.type === AST_NODE_TYPES.Identifier && isComposableName(node.id.name) && node.init && (node.init.type === AST_NODE_TYPES.ArrowFunctionExpression || node.init.type === AST_NODE_TYPES.FunctionExpression)) {
237
+ const fn = node.init;
238
+ return { fn, name: node.id.name, params: fn.params, returnType: fn.returnType };
239
+ }
240
+ return void 0;
241
+ }
242
+ function getTypeReferenceName(annotation) {
243
+ if (annotation.type === AST_NODE_TYPES.TSTypeReference && annotation.typeName.type === AST_NODE_TYPES.Identifier) {
244
+ return annotation.typeName.name;
245
+ }
246
+ return void 0;
247
+ }
248
+ const composableNamingConvention = createEslintRule({
249
+ create(context) {
250
+ return {
251
+ FunctionDeclaration: (node) => {
252
+ checkComposable(node);
253
+ },
254
+ VariableDeclarator: (node) => {
255
+ checkComposable(node);
256
+ }
257
+ };
258
+ function checkComposable(node) {
259
+ const info = getComposableFunction(node);
260
+ if (!info)
261
+ return;
262
+ const pascalName = composableNameToPascal(info.name);
263
+ const expectedOptions = `Use${pascalName}Options`;
264
+ const expectedReturn = `Use${pascalName}Return`;
265
+ for (const param of info.params) {
266
+ const annotation = getParamTypeAnnotation(param);
267
+ if (!annotation)
268
+ continue;
269
+ const typeName = getTypeReferenceName(annotation);
270
+ if (typeName && typeName.startsWith("Use") && typeName.endsWith("Options") && typeName !== expectedOptions) {
271
+ context.report({
272
+ data: { expected: expectedOptions, got: typeName },
273
+ messageId: "optionsNaming",
274
+ node: param
275
+ });
276
+ }
277
+ }
278
+ if (info.returnType) {
279
+ const returnTypeName = getTypeReferenceName(info.returnType.typeAnnotation);
280
+ if (returnTypeName && returnTypeName.startsWith("Use") && returnTypeName.endsWith("Return") && returnTypeName !== expectedReturn) {
281
+ context.report({
282
+ data: { expected: expectedReturn, got: returnTypeName },
283
+ messageId: "returnNaming",
284
+ node: info.returnType
285
+ });
286
+ }
287
+ }
288
+ }
289
+ function getParamTypeAnnotation(param) {
290
+ if (param.type === AST_NODE_TYPES.Identifier && param.typeAnnotation)
291
+ return param.typeAnnotation.typeAnnotation;
292
+ if (param.type === AST_NODE_TYPES.AssignmentPattern && param.left.type === AST_NODE_TYPES.Identifier && param.left.typeAnnotation) {
293
+ return param.left.typeAnnotation.typeAnnotation;
294
+ }
295
+ return void 0;
296
+ }
297
+ },
298
+ defaultOptions: [],
299
+ meta: {
300
+ docs: {
301
+ description: "Enforce consistent naming for composable options and return types",
302
+ recommendation: "stylistic"
303
+ },
304
+ messages: {
305
+ optionsNaming: "Options type should be named '{{ expected }}' instead of '{{ got }}'.",
306
+ returnNaming: "Return type should be named '{{ expected }}' instead of '{{ got }}'."
307
+ },
308
+ schema: [],
309
+ type: "suggestion"
310
+ },
311
+ name: RULE_NAME$e
312
+ });
313
+
314
+ const RULE_NAME$d = "composable-no-default-export";
315
+ const composableNoDefaultExport = createEslintRule({
316
+ create(context) {
317
+ let hasComposable = false;
318
+ let defaultExportNode = null;
319
+ return {
320
+ "ExportDefaultDeclaration": (node) => {
321
+ defaultExportNode = node;
322
+ },
323
+ "FunctionDeclaration": (node) => {
324
+ if (node.id && isComposableName(node.id.name))
325
+ hasComposable = true;
326
+ },
327
+ "Program:exit": () => {
328
+ if (hasComposable && defaultExportNode) {
329
+ context.report({
330
+ messageId: "noDefaultExport",
331
+ node: defaultExportNode
332
+ });
333
+ }
334
+ },
335
+ "VariableDeclarator": (node) => {
336
+ if (node.id.type === AST_NODE_TYPES.Identifier && isComposableName(node.id.name) && node.init && (node.init.type === AST_NODE_TYPES.ArrowFunctionExpression || node.init.type === AST_NODE_TYPES.FunctionExpression)) {
337
+ hasComposable = true;
338
+ }
339
+ }
340
+ };
341
+ },
342
+ defaultOptions: [],
343
+ meta: {
344
+ docs: {
345
+ description: "Forbid default exports in files containing composables",
346
+ recommendation: "strict"
347
+ },
348
+ messages: {
349
+ noDefaultExport: "Files containing composables should use named exports instead of default exports."
350
+ },
351
+ schema: [],
352
+ type: "problem"
353
+ },
354
+ name: RULE_NAME$d
355
+ });
356
+
357
+ const RULE_NAME$c = "composable-prefer-shallowref";
358
+ const composablePreferShallowref = createEslintRule({
359
+ create(context) {
360
+ return {
361
+ CallExpression(node) {
362
+ if (node.callee.type !== AST_NODE_TYPES.Identifier || node.callee.name !== "ref")
363
+ return;
364
+ if (!isInsideComposable(node))
365
+ return;
366
+ const arg = node.arguments[0];
367
+ if (!arg || arg.type !== AST_NODE_TYPES.Literal)
368
+ return;
369
+ context.report({
370
+ fix(fixer) {
371
+ return fixer.replaceText(node.callee, "shallowRef");
372
+ },
373
+ messageId: "preferShallowRef",
374
+ node
375
+ });
376
+ }
377
+ };
378
+ },
379
+ defaultOptions: [],
380
+ meta: {
381
+ docs: {
382
+ description: "Prefer shallowRef() over ref() for primitive values in composables",
383
+ recommendation: "strict"
384
+ },
385
+ fixable: "code",
386
+ messages: {
387
+ preferShallowRef: "Use shallowRef() instead of ref() for primitive values in composables."
388
+ },
389
+ schema: [],
390
+ type: "suggestion"
391
+ },
392
+ name: RULE_NAME$c
393
+ });
394
+
395
+ const RULE_NAME$b = "composable-require-cleanup";
396
+ const SIDE_EFFECT_CALLS = /* @__PURE__ */ new Set(["addEventListener", "setInterval", "setTimeout"]);
397
+ const SIDE_EFFECT_CONSTRUCTORS = /* @__PURE__ */ new Set(["MutationObserver", "ResizeObserver", "IntersectionObserver"]);
398
+ const CLEANUP_HOOKS = /* @__PURE__ */ new Set(["onUnmounted", "onBeforeUnmount", "onScopeDispose", "tryOnScopeDispose"]);
399
+ const composableRequireCleanup = createEslintRule({
400
+ create(context) {
401
+ const scopeStack = [];
402
+ function enterComposable() {
403
+ scopeStack.push({ hasCleanup: false, sideEffects: [] });
404
+ }
405
+ function exitComposable() {
406
+ const scope = scopeStack.pop();
407
+ if (scope && scope.sideEffects.length > 0 && !scope.hasCleanup) {
408
+ for (const node of scope.sideEffects) {
409
+ context.report({
410
+ messageId: "requireCleanup",
411
+ node
412
+ });
413
+ }
414
+ }
415
+ }
416
+ function currentScope() {
417
+ return scopeStack.at(-1);
418
+ }
419
+ return {
420
+ "ArrowFunctionExpression": (node) => {
421
+ const parent = node.parent;
422
+ if (parent?.type === AST_NODE_TYPES.VariableDeclarator && parent.id.type === AST_NODE_TYPES.Identifier && isComposableName(parent.id.name)) {
423
+ enterComposable();
424
+ }
425
+ },
426
+ "ArrowFunctionExpression:exit": (node) => {
427
+ const parent = node.parent;
428
+ if (parent?.type === AST_NODE_TYPES.VariableDeclarator && parent.id.type === AST_NODE_TYPES.Identifier && isComposableName(parent.id.name)) {
429
+ exitComposable();
430
+ }
431
+ },
432
+ "CallExpression": (node) => {
433
+ const scope = currentScope();
434
+ if (!scope)
435
+ return;
436
+ if (node.callee.type === AST_NODE_TYPES.Identifier && CLEANUP_HOOKS.has(node.callee.name)) {
437
+ scope.hasCleanup = true;
438
+ return;
439
+ }
440
+ if (node.callee.type === AST_NODE_TYPES.MemberExpression && node.callee.property.type === AST_NODE_TYPES.Identifier && SIDE_EFFECT_CALLS.has(node.callee.property.name)) {
441
+ scope.sideEffects.push(node);
442
+ return;
443
+ }
444
+ if (node.callee.type === AST_NODE_TYPES.Identifier && SIDE_EFFECT_CALLS.has(node.callee.name)) {
445
+ scope.sideEffects.push(node);
446
+ }
447
+ },
448
+ "FunctionDeclaration": (node) => {
449
+ if (node.id && isComposableName(node.id.name))
450
+ enterComposable();
451
+ },
452
+ "FunctionDeclaration:exit": (node) => {
453
+ if (node.id && isComposableName(node.id.name))
454
+ exitComposable();
455
+ },
456
+ "FunctionExpression": (node) => {
457
+ const parent = node.parent;
458
+ if (parent?.type === AST_NODE_TYPES.VariableDeclarator && parent.id.type === AST_NODE_TYPES.Identifier && isComposableName(parent.id.name)) {
459
+ enterComposable();
460
+ }
461
+ },
462
+ "FunctionExpression:exit": (node) => {
463
+ const parent = node.parent;
464
+ if (parent?.type === AST_NODE_TYPES.VariableDeclarator && parent.id.type === AST_NODE_TYPES.Identifier && isComposableName(parent.id.name)) {
465
+ exitComposable();
466
+ }
467
+ },
468
+ "NewExpression": (node) => {
469
+ const scope = currentScope();
470
+ if (!scope)
471
+ return;
472
+ if (node.callee.type === AST_NODE_TYPES.Identifier && SIDE_EFFECT_CONSTRUCTORS.has(node.callee.name))
473
+ scope.sideEffects.push(node);
474
+ }
475
+ };
476
+ },
477
+ defaultOptions: [],
478
+ meta: {
479
+ docs: {
480
+ description: "Require cleanup hooks when composables use side effects",
481
+ recommendation: "strict"
482
+ },
483
+ messages: {
484
+ requireCleanup: "Side effect requires a cleanup hook (onUnmounted, onBeforeUnmount, or onScopeDispose)."
485
+ },
486
+ schema: [],
487
+ type: "problem"
488
+ },
489
+ name: RULE_NAME$b
490
+ });
491
+
492
+ const RULE_NAME$a = "composable-return-readonly";
493
+ const REACTIVE_CREATORS = /* @__PURE__ */ new Set(["ref", "shallowRef", "computed"]);
494
+ const composableReturnReadonly = createEslintRule({
495
+ create(context) {
496
+ const source = getSourceCode(context);
497
+ const reactiveVars = /* @__PURE__ */ new Set();
498
+ return {
499
+ ReturnStatement: (node) => {
500
+ if (!getEnclosingComposable(node))
501
+ return;
502
+ if (!node.argument || node.argument.type !== AST_NODE_TYPES.ObjectExpression)
503
+ return;
504
+ for (const prop of node.argument.properties) {
505
+ if (prop.type !== AST_NODE_TYPES.Property)
506
+ continue;
507
+ if (prop.shorthand && prop.key.type === AST_NODE_TYPES.Identifier && reactiveVars.has(prop.key.name)) {
508
+ const keyName = prop.key.name;
509
+ context.report({
510
+ data: { name: keyName },
511
+ fix(fixer) {
512
+ return fixer.replaceText(prop, `${keyName}: readonly(${keyName})`);
513
+ },
514
+ messageId: "wrapReadonly",
515
+ node: prop
516
+ });
517
+ } else if (!prop.shorthand && prop.value.type === AST_NODE_TYPES.Identifier && reactiveVars.has(prop.value.name)) {
518
+ context.report({
519
+ data: { name: prop.value.name },
520
+ fix(fixer) {
521
+ return fixer.replaceText(prop.value, `readonly(${prop.value.type === AST_NODE_TYPES.Identifier ? prop.value.name : source.getText(prop.value)})`);
522
+ },
523
+ messageId: "wrapReadonly",
524
+ node: prop
525
+ });
526
+ }
527
+ }
528
+ },
529
+ VariableDeclarator: (node) => {
530
+ if (!getEnclosingComposable(node))
531
+ return;
532
+ if (node.id.type !== AST_NODE_TYPES.Identifier)
533
+ return;
534
+ if (node.init?.type === AST_NODE_TYPES.CallExpression && node.init.callee.type === AST_NODE_TYPES.Identifier && REACTIVE_CREATORS.has(node.init.callee.name)) {
535
+ reactiveVars.add(node.id.name);
536
+ }
537
+ }
538
+ };
539
+ },
540
+ defaultOptions: [],
541
+ meta: {
542
+ docs: {
543
+ description: "Require returned refs from composables to be wrapped with readonly()",
544
+ recommendation: "strict"
545
+ },
546
+ fixable: "code",
547
+ messages: {
548
+ wrapReadonly: "Returned ref '{{ name }}' should be wrapped with readonly()."
549
+ },
550
+ schema: [],
551
+ type: "suggestion"
552
+ },
553
+ name: RULE_NAME$a
554
+ });
555
+
556
+ const RULE_NAME$9 = "composable-ssr-safety";
557
+ const BROWSER_GLOBALS = /* @__PURE__ */ new Set(["window", "document", "navigator"]);
558
+ const LIFECYCLE_HOOKS = /* @__PURE__ */ new Set(["onMounted", "onBeforeMount"]);
559
+ function isInsideLifecycleHook(node) {
560
+ let current = node.parent;
561
+ while (current) {
562
+ if ((current.type === AST_NODE_TYPES.ArrowFunctionExpression || current.type === AST_NODE_TYPES.FunctionExpression) && current.parent?.type === AST_NODE_TYPES.CallExpression && current.parent.callee.type === AST_NODE_TYPES.Identifier && LIFECYCLE_HOOKS.has(current.parent.callee.name)) {
563
+ return true;
564
+ }
565
+ current = current.parent;
566
+ }
567
+ return false;
568
+ }
569
+ function isInsideTypeofCheck(node) {
570
+ let current = node.parent;
571
+ while (current) {
572
+ if (current.type === AST_NODE_TYPES.UnaryExpression && current.operator === "typeof")
573
+ return true;
574
+ if (current.type === AST_NODE_TYPES.IfStatement) {
575
+ const test = current.test;
576
+ if (hasTypeofCheck(test))
577
+ return true;
578
+ }
579
+ current = current.parent;
580
+ }
581
+ return false;
582
+ }
583
+ function hasTypeofCheck(node) {
584
+ if (node.type === AST_NODE_TYPES.BinaryExpression) {
585
+ 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)) {
586
+ return true;
587
+ }
588
+ 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)) {
589
+ return true;
590
+ }
591
+ }
592
+ if (node.type === AST_NODE_TYPES.LogicalExpression)
593
+ return hasTypeofCheck(node.left) || hasTypeofCheck(node.right);
594
+ return false;
595
+ }
596
+ function isGuardedByIfTypeofOrLifecycle(node) {
597
+ if (isInsideLifecycleHook(node))
598
+ return true;
599
+ let current = node.parent;
600
+ while (current) {
601
+ if (current.type === AST_NODE_TYPES.IfStatement && hasTypeofCheck(current.test)) {
602
+ return true;
603
+ }
604
+ if (current.type === AST_NODE_TYPES.ConditionalExpression && hasTypeofCheck(current.test))
605
+ return true;
606
+ if (current.type === AST_NODE_TYPES.LogicalExpression && current.operator === "&&" && hasTypeofCheck(current.left)) {
607
+ return true;
608
+ }
609
+ current = current.parent;
610
+ }
611
+ return false;
612
+ }
613
+ const composableSsrSafety = createEslintRule({
614
+ create(context) {
615
+ return {
616
+ MemberExpression(node) {
617
+ if (node.object.type !== AST_NODE_TYPES.Identifier)
618
+ return;
619
+ if (!BROWSER_GLOBALS.has(node.object.name))
620
+ return;
621
+ if (!getEnclosingComposable(node))
622
+ return;
623
+ if (isInsideTypeofCheck(node))
624
+ return;
625
+ if (isGuardedByIfTypeofOrLifecycle(node))
626
+ return;
627
+ context.report({
628
+ data: { name: node.object.name },
629
+ messageId: "ssrUnsafe",
630
+ node
631
+ });
632
+ }
633
+ };
634
+ },
635
+ defaultOptions: [],
636
+ meta: {
637
+ docs: {
638
+ description: "Require browser global access in composables to be SSR-safe",
639
+ recommendation: "strict"
640
+ },
641
+ messages: {
642
+ ssrUnsafe: "Access to '{{ name }}' in a composable must be guarded with a typeof check or placed inside onMounted/onBeforeMount."
643
+ },
644
+ schema: [],
645
+ type: "problem"
646
+ },
647
+ name: RULE_NAME$9
648
+ });
649
+
650
+ const debug$6 = debugFactory("@rotki/eslint-plugin:consistent-ref-type-annotation");
651
+ const RULE_NAME$8 = "consistent-ref-type-annotation";
652
+ const FIXABLE_METHODS = /* @__PURE__ */ new Set(["ref", "computed"]);
653
+ function checkAssignmentDeclaration(context, source, node, declaration, allowInference) {
143
654
  let declarationTypeArguments;
144
655
  const init = declaration.init;
145
656
  if (!(init && init.type === TSESTree.AST_NODE_TYPES.CallExpression))
@@ -147,10 +658,10 @@ function checkAssignmentDeclaration(context, node, declaration, options) {
147
658
  const callee = init.callee;
148
659
  if (!(callee && callee.type === TSESTree.AST_NODE_TYPES.Identifier))
149
660
  return;
150
- if (!Array.prototype.includes.call(FIXABLE_METHODS, callee.name))
661
+ if (!FIXABLE_METHODS.has(callee.name))
151
662
  return;
152
663
  const name = callee.name;
153
- debug$3(`found ${name}, checking type arguments`);
664
+ debug$6(`found ${name}, checking type arguments`);
154
665
  const initializationTypeArguments = init.typeArguments;
155
666
  const typeAnnotation = declaration.id.typeAnnotation;
156
667
  if (typeAnnotation) {
@@ -160,10 +671,10 @@ function checkAssignmentDeclaration(context, node, declaration, options) {
160
671
  }
161
672
  if (initializationTypeArguments && !declarationTypeArguments)
162
673
  return;
163
- debug$3(`generating report for ${name}`);
674
+ debug$6(`generating report for ${name}`);
164
675
  if (!initializationTypeArguments && !declarationTypeArguments) {
165
676
  if (allowInference) {
166
- debug$3("type inference is allowed");
677
+ debug$6("type inference is allowed");
167
678
  } else {
168
679
  context.report({
169
680
  data: {
@@ -195,13 +706,14 @@ const consistentRefTypeAnnotation = createEslintRule({
195
706
  create(context, optionsWithDefault) {
196
707
  const options = optionsWithDefault[0] || {};
197
708
  const allowInference = options.allowInference;
709
+ const source = getSourceCode(context);
198
710
  return {
199
711
  VariableDeclaration: (node) => {
200
712
  const declarations = node.declarations;
201
713
  for (const declaration of declarations) {
202
714
  if (declaration.type !== TSESTree.AST_NODE_TYPES.VariableDeclarator)
203
715
  continue;
204
- checkAssignmentDeclaration(context, node, declaration, { allowInference });
716
+ checkAssignmentDeclaration(context, source, node, declaration, allowInference);
205
717
  }
206
718
  }
207
719
  };
@@ -230,10 +742,10 @@ const consistentRefTypeAnnotation = createEslintRule({
230
742
  ],
231
743
  type: "problem"
232
744
  },
233
- name: RULE_NAME$6
745
+ name: RULE_NAME$8
234
746
  });
235
747
 
236
- const RULE_NAME$5 = "max-dependencies";
748
+ const RULE_NAME$7 = "max-dependencies";
237
749
  const maxDependencies = createEslintRule({
238
750
  create(context, [options]) {
239
751
  let dependencyCount = 0;
@@ -293,12 +805,12 @@ const maxDependencies = createEslintRule({
293
805
  ],
294
806
  type: "suggestion"
295
807
  },
296
- name: RULE_NAME$5
808
+ name: RULE_NAME$7
297
809
  });
298
810
 
299
- const RULE_NAME$4 = "no-deprecated-classes";
300
- const debug$2 = createDebug("@rotki/eslint-plugin:no-deprecated-classes");
301
- const replacements$2 = [
811
+ const RULE_NAME$6 = "no-deprecated-classes";
812
+ const debug$5 = debugFactory("@rotki/eslint-plugin:no-deprecated-classes");
813
+ const stringReplacements = /* @__PURE__ */ new Map([
302
814
  ["d-block", "block"],
303
815
  ["d-flex", "flex"],
304
816
  ["flex-column", "flex-col"],
@@ -306,6 +818,11 @@ const replacements$2 = [
306
818
  ["flex-grow-0", "grow-0"],
307
819
  ["flex-shrink-1", "shrink"],
308
820
  ["flex-shrink-0", "shrink-0"],
821
+ ["text--secondary", "text-rui-text-secondary"],
822
+ ["white--text", "text-white"],
823
+ ["primary--text", "text-rui-primary"]
824
+ ]);
825
+ const regexReplacements = [
309
826
  [
310
827
  /^align-(start|end|center|baseline|stretch)$/,
311
828
  ([align]) => `items-${align}`
@@ -320,9 +837,6 @@ const replacements$2 = [
320
837
  ([weight]) => `font-${weight}`
321
838
  ],
322
839
  [/^text-(capitalize|uppercase|lowercase)$/, ([casing]) => casing],
323
- ["text--secondary", "text-rui-text-secondary"],
324
- ["white--text", "text-white"],
325
- ["primary--text", "text-rui-primary"],
326
840
  [
327
841
  /^([mp])([abelr-txy]?)-n(\d)$/,
328
842
  ([type, position, size]) => `-${type}${position === "a" ? "" : position}-${size}`
@@ -332,22 +846,14 @@ const replacements$2 = [
332
846
  ([type, size]) => `${type}-${size}`
333
847
  ]
334
848
  ];
335
- function isString(replacement) {
336
- return typeof replacement[0] === "string";
337
- }
338
- function isRegex(replacement) {
339
- return replacement[0] instanceof RegExp;
340
- }
341
849
  function findReplacement(className) {
342
- for (const replacement of replacements$2) {
343
- if (isString(replacement) && replacement[0] === className)
344
- return replacement[1];
345
- if (isRegex(replacement)) {
346
- const matches = (replacement[0].exec(className) || []).slice(1);
347
- const replace = replacement[1];
348
- if (matches.length > 0 && typeof replace === "function")
349
- return replace(matches);
350
- }
850
+ const direct = stringReplacements.get(className);
851
+ if (direct !== void 0)
852
+ return direct;
853
+ for (const [regex, replace] of regexReplacements) {
854
+ const matches = regex.exec(className);
855
+ if (matches)
856
+ return replace(matches.slice(1));
351
857
  }
352
858
  return void 0;
353
859
  }
@@ -356,9 +862,8 @@ function getRange(node) {
356
862
  return node.value.range;
357
863
  return node.range;
358
864
  }
359
- function reportReplacement(className, replacement, node, context, position = 1) {
360
- debug$2(`found replacement ${replacement} for ${className}`);
361
- const source = getSourceCode(context);
865
+ function reportReplacement(className, replacement, node, context, source, position = 1) {
866
+ debug$5(`found replacement ${replacement} for ${className}`);
362
867
  const initialRange = getRange(node);
363
868
  const range = [
364
869
  initialRange[0] + position,
@@ -433,6 +938,7 @@ function* extractClassNames(node, textOnly = false) {
433
938
  }
434
939
  const noDeprecatedClasses = createEslintRule({
435
940
  create(context) {
941
+ const source = getSourceCode(context);
436
942
  return defineTemplateBodyVisitor(context, {
437
943
  'VAttribute[directive=false][key.name="class"]': function(node) {
438
944
  if (!node.value || !node.value.value)
@@ -442,7 +948,7 @@ const noDeprecatedClasses = createEslintRule({
442
948
  const position = node.value.value.indexOf(className) + 1;
443
949
  if (!replacement)
444
950
  continue;
445
- reportReplacement(className, replacement, node, context, position);
951
+ reportReplacement(className, replacement, node, context, source, position);
446
952
  }
447
953
  },
448
954
  "VAttribute[directive=true][key.name.name='bind'][key.argument.name='class'] > VExpressionContainer.value": function(node) {
@@ -452,7 +958,7 @@ const noDeprecatedClasses = createEslintRule({
452
958
  const replacement = findReplacement(className);
453
959
  if (!replacement)
454
960
  continue;
455
- reportReplacement(className, replacement, reportNode, context, position);
961
+ reportReplacement(className, replacement, reportNode, context, source, position);
456
962
  }
457
963
  }
458
964
  });
@@ -470,11 +976,11 @@ const noDeprecatedClasses = createEslintRule({
470
976
  schema: [],
471
977
  type: "problem"
472
978
  },
473
- name: RULE_NAME$4
979
+ name: RULE_NAME$6
474
980
  });
475
981
 
476
- const debug$1 = createDebug("@rotki/eslint-plugin:no-deprecated-components");
477
- const RULE_NAME$3 = "no-deprecated-components";
982
+ const debug$4 = debugFactory("@rotki/eslint-plugin:no-deprecated-components");
983
+ const RULE_NAME$5 = "no-deprecated-components";
478
984
  const vuetify = {
479
985
  VApp: true,
480
986
  VAppBar: true,
@@ -507,9 +1013,9 @@ const replacements$1 = {
507
1013
  Fragment: false,
508
1014
  ...vuetify
509
1015
  };
510
- const skipInLegacy = [
1016
+ const skipInLegacy = /* @__PURE__ */ new Set([
511
1017
  "Fragment"
512
- ];
1018
+ ]);
513
1019
  function hasReplacement$1(tag) {
514
1020
  return Object.prototype.hasOwnProperty.call(replacements$1, tag);
515
1021
  }
@@ -517,17 +1023,17 @@ const noDeprecatedComponents = createEslintRule({
517
1023
  create(context, optionsWithDefault) {
518
1024
  const options = optionsWithDefault[0] || {};
519
1025
  const legacy = options.legacy;
1026
+ const sourceCode = getSourceCode(context);
1027
+ if (sourceCode?.parserServices && !("getTemplateBodyTokenStore" in sourceCode.parserServices))
1028
+ throw new Error("cannot find getTemplateBodyTokenStore in parserServices");
520
1029
  return defineTemplateBodyVisitor(context, {
521
1030
  VElement(element) {
522
1031
  const tag = pascalCase(element.rawName);
523
- const sourceCode = getSourceCode(context);
524
- if (sourceCode?.parserServices && !("getTemplateBodyTokenStore" in sourceCode.parserServices))
525
- throw new Error("cannot find getTemplateBodyTokenStore in parserServices");
526
1032
  if (!hasReplacement$1(tag))
527
1033
  return;
528
1034
  const replacement = replacements$1[tag];
529
- if (replacement || legacy && skipInLegacy.includes(tag)) {
530
- debug$1(`${tag} has been deprecated`);
1035
+ if (replacement || legacy && skipInLegacy.has(tag)) {
1036
+ debug$4(`${tag} has been deprecated`);
531
1037
  context.report({
532
1038
  data: {
533
1039
  name: tag
@@ -536,7 +1042,7 @@ const noDeprecatedComponents = createEslintRule({
536
1042
  node: element
537
1043
  });
538
1044
  } else {
539
- debug$1(`${tag} has will be removed`);
1045
+ debug$4(`${tag} has will be removed`);
540
1046
  context.report({
541
1047
  data: {
542
1048
  name: tag
@@ -579,18 +1085,24 @@ const noDeprecatedComponents = createEslintRule({
579
1085
  ],
580
1086
  type: "problem"
581
1087
  },
582
- name: RULE_NAME$3
1088
+ name: RULE_NAME$5
583
1089
  });
584
1090
 
585
- const debug = createDebug("@rotki/eslint-plugin:no-deprecated-props");
586
- const RULE_NAME$2 = "no-deprecated-props";
1091
+ const debug$3 = debugFactory("@rotki/eslint-plugin:no-deprecated-props");
1092
+ const RULE_NAME$4 = "no-deprecated-props";
587
1093
  const replacements = {
588
1094
  RuiRadio: {
589
1095
  internalValue: "value"
590
1096
  }
591
1097
  };
1098
+ const replacementMaps = new Map(
1099
+ Object.entries(replacements).map(([tag, props]) => [
1100
+ tag,
1101
+ new Map(Object.entries(props).map(([prop, repl]) => [kebabCase(prop), { original: prop, replacement: repl }]))
1102
+ ])
1103
+ );
592
1104
  function hasReplacement(tag) {
593
- return Object.prototype.hasOwnProperty.call(replacements, tag);
1105
+ return replacementMaps.has(tag);
594
1106
  }
595
1107
  function getPropName(node) {
596
1108
  if (node.directive) {
@@ -609,29 +1121,28 @@ const noDeprecatedProps = createEslintRule({
609
1121
  const tag = pascalCase(node.parent.parent.rawName);
610
1122
  if (!hasReplacement(tag))
611
1123
  return;
612
- debug(`${tag} has replacement properties`);
1124
+ debug$3(`${tag} has replacement properties`);
613
1125
  const propName = getPropName(node);
614
1126
  const propNameNode = node.directive ? node.key.argument : node.key;
615
1127
  if (!propName || !propNameNode) {
616
- debug("could not get prop name and/or node");
1128
+ debug$3("could not get prop name and/or node");
617
1129
  return;
618
1130
  }
619
- Object.entries(replacements[tag]).forEach(([prop, replacement]) => {
620
- if (kebabCase(prop) === propName) {
621
- debug(`preparing a replacement for ${tag}:${propName} -> ${replacement}`);
622
- context.report({
623
- data: {
624
- prop,
625
- replacement
626
- },
627
- fix(fixer) {
628
- return fixer.replaceText(propNameNode, replacement);
629
- },
630
- messageId: "replacedWith",
631
- node: propNameNode
632
- });
633
- }
634
- });
1131
+ const match = replacementMaps.get(tag)?.get(propName);
1132
+ if (match) {
1133
+ debug$3(`preparing a replacement for ${tag}:${propName} -> ${match.replacement}`);
1134
+ context.report({
1135
+ data: {
1136
+ prop: match.original,
1137
+ replacement: match.replacement
1138
+ },
1139
+ fix(fixer) {
1140
+ return fixer.replaceText(propNameNode, match.replacement);
1141
+ },
1142
+ messageId: "replacedWith",
1143
+ node: propNameNode
1144
+ });
1145
+ }
635
1146
  }
636
1147
  });
637
1148
  },
@@ -648,10 +1159,10 @@ const noDeprecatedProps = createEslintRule({
648
1159
  schema: [],
649
1160
  type: "problem"
650
1161
  },
651
- name: RULE_NAME$2
1162
+ name: RULE_NAME$4
652
1163
  });
653
1164
 
654
- const RULE_NAME$1 = "no-dot-ts-imports";
1165
+ const RULE_NAME$3 = "no-dot-ts-imports";
655
1166
  const noDotTsImport = createEslintRule({
656
1167
  create(context) {
657
1168
  return {
@@ -659,8 +1170,7 @@ const noDotTsImport = createEslintRule({
659
1170
  const importDeclaration = node.source.value;
660
1171
  if (!importDeclaration.endsWith(".ts"))
661
1172
  return;
662
- const lastIndexOfExtension = importDeclaration.lastIndexOf(".ts");
663
- const replacement = importDeclaration.substring(0, lastIndexOfExtension);
1173
+ const replacement = importDeclaration.slice(0, -3);
664
1174
  context.report({
665
1175
  data: {
666
1176
  import: importDeclaration
@@ -687,10 +1197,10 @@ const noDotTsImport = createEslintRule({
687
1197
  schema: [],
688
1198
  type: "problem"
689
1199
  },
690
- name: RULE_NAME$1
1200
+ name: RULE_NAME$3
691
1201
  });
692
1202
 
693
- const RULE_NAME = "no-legacy-library-import";
1203
+ const RULE_NAME$2 = "no-legacy-library-import";
694
1204
  const legacyLibrary = "@rotki/ui-library-compat";
695
1205
  const newLibrary = "@rotki/ui-library";
696
1206
  const noLegacyLibraryImport = createEslintRule({
@@ -723,6 +1233,616 @@ const noLegacyLibraryImport = createEslintRule({
723
1233
  schema: [],
724
1234
  type: "problem"
725
1235
  },
1236
+ name: RULE_NAME$2
1237
+ });
1238
+
1239
+ const I18N_FUNCTION_NAMES = /* @__PURE__ */ new Set(["t", "te", "tc", "$t", "$te", "$tc"]);
1240
+ const I18N_MEMBER_NAMES = /* @__PURE__ */ new Set(["$t", "$te", "$tc"]);
1241
+ function isAstNode(value) {
1242
+ return value !== null && typeof value === "object" && "type" in value && typeof value.type === "string";
1243
+ }
1244
+ function isI18nCallExpression(node) {
1245
+ const callee = node.callee;
1246
+ if (!isAstNode(callee))
1247
+ return false;
1248
+ if (callee.type === "Identifier" && typeof callee.name === "string")
1249
+ return I18N_FUNCTION_NAMES.has(callee.name);
1250
+ 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);
1251
+ }
1252
+ function getTemplateLiteralText(arg) {
1253
+ const quasis = arg.quasis;
1254
+ const expressions = arg.expressions;
1255
+ if (!Array.isArray(quasis) || !Array.isArray(expressions) || quasis.length === 0)
1256
+ return void 0;
1257
+ const firstQuasi = quasis[0];
1258
+ if (!firstQuasi || typeof firstQuasi !== "object" || !("value" in firstQuasi))
1259
+ return void 0;
1260
+ const quasiObj = firstQuasi.value;
1261
+ if (!quasiObj || typeof quasiObj !== "object")
1262
+ return void 0;
1263
+ return "cooked" in quasiObj && typeof quasiObj.cooked === "string" ? quasiObj.cooked : "raw" in quasiObj && typeof quasiObj.raw === "string" ? quasiObj.raw : void 0;
1264
+ }
1265
+ function extractKeysFromCallExpression(node, keys) {
1266
+ if (!isI18nCallExpression(node))
1267
+ return;
1268
+ const args = node.arguments;
1269
+ if (!Array.isArray(args) || args.length === 0)
1270
+ return;
1271
+ const arg = args[0];
1272
+ if (!isAstNode(arg))
1273
+ return;
1274
+ if (arg.type === "Literal" && typeof arg.value === "string") {
1275
+ keys.add(arg.value);
1276
+ } else if (arg.type === "TemplateLiteral") {
1277
+ const text = getTemplateLiteralText(arg);
1278
+ if (text === void 0)
1279
+ return;
1280
+ if (arg.expressions && Array.isArray(arg.expressions) && arg.expressions.length === 0 && Array.isArray(arg.quasis) && arg.quasis.length === 1)
1281
+ keys.add(text);
1282
+ else if (text)
1283
+ keys.add(`${text}*`);
1284
+ }
1285
+ }
1286
+ const TS_AST_CHILD_KEYS = /* @__PURE__ */ new Set([
1287
+ "alternate",
1288
+ "argument",
1289
+ "arguments",
1290
+ "block",
1291
+ "body",
1292
+ "callee",
1293
+ "cases",
1294
+ "consequent",
1295
+ "declaration",
1296
+ "declarations",
1297
+ "discriminant",
1298
+ "elements",
1299
+ "expression",
1300
+ "expressions",
1301
+ "handler",
1302
+ "init",
1303
+ "left",
1304
+ "object",
1305
+ "params",
1306
+ "properties",
1307
+ "property",
1308
+ "right",
1309
+ "source",
1310
+ "specifiers",
1311
+ "tag",
1312
+ "test",
1313
+ "update",
1314
+ "value"
1315
+ ]);
1316
+ function walkTsAst(node, keys) {
1317
+ if (!isAstNode(node))
1318
+ return;
1319
+ if (node.type === "CallExpression") {
1320
+ extractKeysFromCallExpression(node, keys);
1321
+ }
1322
+ for (const key of TS_AST_CHILD_KEYS) {
1323
+ if (!(key in node))
1324
+ continue;
1325
+ const child = node[key];
1326
+ if (!child || typeof child !== "object")
1327
+ continue;
1328
+ if (Array.isArray(child)) {
1329
+ for (const item of child) {
1330
+ walkTsAst(item, keys);
1331
+ }
1332
+ } else {
1333
+ walkTsAst(child, keys);
1334
+ }
1335
+ }
1336
+ }
1337
+
1338
+ const debug$2 = debugFactory("@rotki/eslint-plugin:i18n-vue-template");
1339
+ function isVElement(node) {
1340
+ return "type" in node && node.type === "VElement";
1341
+ }
1342
+ function isVExpressionContainer(node) {
1343
+ return "type" in node && node.type === "VExpressionContainer";
1344
+ }
1345
+ function isKeypathAttribute(attr) {
1346
+ return attr.type === "VAttribute" && !attr.directive && (attr.key.name === "keypath" || attr.key.name === "path") && attr.value !== null && attr.value.type === "VLiteral";
1347
+ }
1348
+ function isVTDirective(attr) {
1349
+ return attr.type === "VAttribute" && attr.directive && attr.key.name.type === "VIdentifier" && attr.key.name.rawName === "t";
1350
+ }
1351
+ function getDirectiveExpression(attr) {
1352
+ if (attr.type !== "VAttribute" || !attr.directive)
1353
+ return null;
1354
+ if (!attr.value || attr.value.type !== "VExpressionContainer")
1355
+ return null;
1356
+ return attr.value.expression;
1357
+ }
1358
+ function extractKeysFromVueTemplate(templateBody, keys) {
1359
+ walkVueTemplateNode(templateBody, keys);
1360
+ }
1361
+ function walkVueTemplateNode(node, keys) {
1362
+ if (!node || typeof node !== "object")
1363
+ return;
1364
+ if (isVElement(node)) {
1365
+ if (node.rawName === "i18n-t" || node.name === "i18n-t") {
1366
+ for (const attr of node.startTag.attributes) {
1367
+ if (isKeypathAttribute(attr))
1368
+ keys.add(attr.value.value);
1369
+ }
1370
+ }
1371
+ for (const attr of node.startTag.attributes) {
1372
+ const expr = getDirectiveExpression(attr);
1373
+ if (!expr)
1374
+ continue;
1375
+ if (isVTDirective(attr) && expr.type === "Literal" && typeof expr.value === "string")
1376
+ keys.add(expr.value);
1377
+ walkTsAst(expr, keys);
1378
+ }
1379
+ for (const child of node.children)
1380
+ walkVueTemplateNode(child, keys);
1381
+ } else if (isVExpressionContainer(node)) {
1382
+ if (node.expression)
1383
+ walkTsAst(node.expression, keys);
1384
+ } else if ("children" in node && Array.isArray(node.children)) {
1385
+ for (const child of node.children) {
1386
+ if (child && typeof child === "object")
1387
+ walkVueTemplateNode(child, keys);
1388
+ }
1389
+ }
1390
+ }
1391
+ function extractKeysFromSfcI18nBlock(content, keys) {
1392
+ const i18nBlockRegex = /<i18n(?:\s[^>]*)?>([^]*?)<\/i18n>/g;
1393
+ let match = i18nBlockRegex.exec(content);
1394
+ while (match !== null) {
1395
+ const blockContent = match[1].trim();
1396
+ if (blockContent) {
1397
+ try {
1398
+ const parsed = JSON.parse(blockContent);
1399
+ collectJsonKeys(parsed, "", keys);
1400
+ } catch (error) {
1401
+ const message = error instanceof Error ? error.message : String(error);
1402
+ debug$2("Failed to parse <i18n> block as JSON: %s", message);
1403
+ console.warn(`[@rotki/eslint-plugin] Failed to parse <i18n> block as JSON. If you are using JSON5, it is not supported. Error: ${message}`);
1404
+ }
1405
+ }
1406
+ match = i18nBlockRegex.exec(content);
1407
+ }
1408
+ }
1409
+ function collectJsonKeys(obj, prefix, keys) {
1410
+ if (!obj || typeof obj !== "object" || Array.isArray(obj))
1411
+ return;
1412
+ for (const [key, value] of Object.entries(obj)) {
1413
+ const fullKey = prefix ? `${prefix}.${key}` : key;
1414
+ if (value && typeof value === "object" && !Array.isArray(value)) {
1415
+ collectJsonKeys(value, fullKey, keys);
1416
+ } else {
1417
+ keys.add(fullKey);
1418
+ }
1419
+ }
1420
+ }
1421
+
1422
+ const debug$1 = debugFactory("@rotki/eslint-plugin:i18n-key-collector");
1423
+ const fileCache = /* @__PURE__ */ new Map();
1424
+ let cachedUsedKeys;
1425
+ const I18N_CALL_PATTERN = /\bt\s*\(|\bte\s*\(|\btc\s*\(|\$t\s*\(|\$te\s*\(|\$tc\s*\(/;
1426
+ const VUE_I18N_PATTERN = /\bt\s*\(|\bte\s*\(|\btc\s*\(|\$t\s*\(|\$te\s*\(|\$tc\s*\(|<i18n[\s>-]|v-t\b/;
1427
+ function parseSourceFile(content, filePath) {
1428
+ const isVue = filePath.endsWith(".vue");
1429
+ const source = isVue ? content : `<script lang="ts">
1430
+ ${content}
1431
+ <\/script>`;
1432
+ try {
1433
+ return parse(source, {
1434
+ parser: "@typescript-eslint/parser",
1435
+ sourceType: "module"
1436
+ });
1437
+ } catch (error) {
1438
+ debug$1(`Failed to parse file ${filePath}: ${String(error)}`);
1439
+ return void 0;
1440
+ }
1441
+ }
1442
+ function collectKeysFromFile(filePath) {
1443
+ const keys = /* @__PURE__ */ new Set();
1444
+ let mtimeMs;
1445
+ try {
1446
+ mtimeMs = statSync(filePath).mtimeMs;
1447
+ } catch {
1448
+ return keys;
1449
+ }
1450
+ const cached = fileCache.get(filePath);
1451
+ if (cached && cached.mtimeMs === mtimeMs) {
1452
+ return cached.keys;
1453
+ }
1454
+ let content;
1455
+ try {
1456
+ content = readFileSync(filePath, "utf-8");
1457
+ } catch {
1458
+ return keys;
1459
+ }
1460
+ const isVue = filePath.endsWith(".vue");
1461
+ const pattern = isVue ? VUE_I18N_PATTERN : I18N_CALL_PATTERN;
1462
+ if (!pattern.test(content)) {
1463
+ fileCache.set(filePath, { keys, mtimeMs });
1464
+ return keys;
1465
+ }
1466
+ const ast = parseSourceFile(content, filePath);
1467
+ if (!ast)
1468
+ return keys;
1469
+ if (isVue) {
1470
+ if (ast.templateBody) {
1471
+ extractKeysFromVueTemplate(ast.templateBody, keys);
1472
+ }
1473
+ extractKeysFromSfcI18nBlock(content, keys);
1474
+ }
1475
+ if (ast.body) {
1476
+ for (const node of ast.body) {
1477
+ walkTsAst(node, keys);
1478
+ }
1479
+ }
1480
+ fileCache.set(filePath, { keys, mtimeMs });
1481
+ return keys;
1482
+ }
1483
+ function collectAllUsedKeys(srcDir, extensions) {
1484
+ const resolvedSrc = resolve(srcDir);
1485
+ if (cachedUsedKeys && cachedUsedKeys.srcDir === resolvedSrc && cachedUsedKeys.extensions.join(",") === extensions.join(",")) {
1486
+ return cachedUsedKeys.keys;
1487
+ }
1488
+ const patterns = extensions.map((ext) => `**/*${ext}`);
1489
+ const files = globSync(patterns, { absolute: true, cwd: resolvedSrc });
1490
+ debug$1(`Found ${files.length} source files in ${resolvedSrc}`);
1491
+ const allKeys = /* @__PURE__ */ new Set();
1492
+ for (const file of files) {
1493
+ const keys = collectKeysFromFile(file);
1494
+ for (const key of keys) {
1495
+ allKeys.add(key);
1496
+ }
1497
+ }
1498
+ cachedUsedKeys = { extensions, keys: allKeys, srcDir: resolvedSrc };
1499
+ return allKeys;
1500
+ }
1501
+
1502
+ function extractLinkedKeys(value) {
1503
+ const linkedKeyRegex = /@(?:\.\w+)?:(?:{([^}]+)}|(\w+(?:\.\w+)*))/g;
1504
+ const keys = [];
1505
+ let match = linkedKeyRegex.exec(value);
1506
+ while (match !== null) {
1507
+ const key = match[1] || match[2];
1508
+ if (key)
1509
+ keys.push(key);
1510
+ match = linkedKeyRegex.exec(value);
1511
+ }
1512
+ return keys;
1513
+ }
1514
+ function prepareUsedKeys(usedKeys, ignorePatterns) {
1515
+ const directKeys = /* @__PURE__ */ new Set();
1516
+ const wildcardPrefixes = [];
1517
+ for (const key of usedKeys) {
1518
+ if (key.endsWith("*")) {
1519
+ wildcardPrefixes.push(key.slice(0, -1));
1520
+ } else {
1521
+ directKeys.add(key);
1522
+ }
1523
+ }
1524
+ const ignoreRegexps = ignorePatterns.map(
1525
+ (pattern) => new RegExp(`^${pattern.replace(/[$()+?[\\\]^{|}]/g, "\\$&").replace(/\./g, "\\.").replace(/\*/g, ".*")}$`)
1526
+ );
1527
+ return { directKeys, ignoreRegexps, wildcardPrefixes };
1528
+ }
1529
+ function isKeyUsed(keyPath, prepared) {
1530
+ if (prepared.directKeys.has(keyPath))
1531
+ return true;
1532
+ let dotIndex = keyPath.indexOf(".");
1533
+ while (dotIndex !== -1) {
1534
+ if (prepared.directKeys.has(keyPath.slice(0, dotIndex)))
1535
+ return true;
1536
+ dotIndex = keyPath.indexOf(".", dotIndex + 1);
1537
+ }
1538
+ for (const prefix of prepared.wildcardPrefixes) {
1539
+ if (keyPath.startsWith(prefix))
1540
+ return true;
1541
+ }
1542
+ for (const regex of prepared.ignoreRegexps) {
1543
+ if (regex.test(keyPath))
1544
+ return true;
1545
+ }
1546
+ return false;
1547
+ }
1548
+
1549
+ const RULE_NAME$1 = "no-unused-i18n-keys";
1550
+ const debug = debugFactory("@rotki/eslint-plugin:no-unused-i18n-keys");
1551
+ function isLocaleFile(filename) {
1552
+ return /(?:locales?|i18n|translations?|messages?|lang)\b/i.test(filename);
1553
+ }
1554
+ function isJsonProgram(ast) {
1555
+ if (!ast || typeof ast !== "object" || !("type" in ast) || ast.type !== "Program")
1556
+ return false;
1557
+ if (!("body" in ast) || !Array.isArray(ast.body) || ast.body.length === 0)
1558
+ return false;
1559
+ const first = ast.body[0];
1560
+ return first !== null && typeof first === "object" && "type" in first && first.type === "JSONExpressionStatement";
1561
+ }
1562
+ function isYamlProgram(ast) {
1563
+ if (!ast || typeof ast !== "object" || !("type" in ast) || ast.type !== "Program")
1564
+ return false;
1565
+ if (!("body" in ast) || !Array.isArray(ast.body) || ast.body.length === 0)
1566
+ return false;
1567
+ const first = ast.body[0];
1568
+ return first !== null && typeof first === "object" && "type" in first && first.type === "YAMLDocument";
1569
+ }
1570
+ function buildJsonKeyPaths(node, prefix, paths) {
1571
+ for (const prop of node.properties) {
1572
+ const keyName = prop.key.type === "JSONLiteral" ? String(prop.key.value) : prop.key.name;
1573
+ const fullKey = prefix ? `${prefix}.${keyName}` : keyName;
1574
+ if (prop.value.type === "JSONObjectExpression") {
1575
+ buildJsonKeyPaths(prop.value, fullKey, paths);
1576
+ } else {
1577
+ paths.push({ key: fullKey, node: prop });
1578
+ }
1579
+ }
1580
+ }
1581
+ function collectJsonLinkedKeys(node, linkedKeys) {
1582
+ for (const prop of node.properties) {
1583
+ if (prop.value.type === "JSONLiteral" && typeof prop.value.value === "string") {
1584
+ for (const key of extractLinkedKeys(prop.value.value)) {
1585
+ linkedKeys.add(key);
1586
+ }
1587
+ } else if (prop.value.type === "JSONObjectExpression") {
1588
+ collectJsonLinkedKeys(prop.value, linkedKeys);
1589
+ }
1590
+ }
1591
+ }
1592
+ function getYamlScalarValue(node) {
1593
+ if (!node)
1594
+ return void 0;
1595
+ if (node.type === "YAMLWithMeta")
1596
+ return getYamlScalarValue(node.value);
1597
+ if (node.type === "YAMLScalar" && typeof node.value === "string")
1598
+ return node.value;
1599
+ return void 0;
1600
+ }
1601
+ function getYamlKeyName(node) {
1602
+ if (!node)
1603
+ return void 0;
1604
+ if (node.type === "YAMLWithMeta")
1605
+ return getYamlKeyName(node.value);
1606
+ if (node.type === "YAMLScalar")
1607
+ return String(node.value);
1608
+ return void 0;
1609
+ }
1610
+ function isYamlMapping(node) {
1611
+ if (!node)
1612
+ return false;
1613
+ if (node.type === "YAMLMapping")
1614
+ return true;
1615
+ if (node.type === "YAMLWithMeta" && node.value?.type === "YAMLMapping")
1616
+ return true;
1617
+ return false;
1618
+ }
1619
+ function getYamlMapping(node) {
1620
+ if (!node)
1621
+ return null;
1622
+ if (node.type === "YAMLMapping")
1623
+ return node;
1624
+ if (node.type === "YAMLWithMeta" && node.value?.type === "YAMLMapping")
1625
+ return node.value;
1626
+ return null;
1627
+ }
1628
+ function buildYamlKeyPaths(node, prefix, paths) {
1629
+ for (const pair of node.pairs) {
1630
+ const keyName = getYamlKeyName(pair.key);
1631
+ if (keyName === void 0)
1632
+ continue;
1633
+ const fullKey = prefix ? `${prefix}.${keyName}` : keyName;
1634
+ if (isYamlMapping(pair.value)) {
1635
+ const mapping = getYamlMapping(pair.value);
1636
+ if (mapping) {
1637
+ buildYamlKeyPaths(mapping, fullKey, paths);
1638
+ }
1639
+ } else {
1640
+ paths.push({ key: fullKey, node: pair });
1641
+ }
1642
+ }
1643
+ }
1644
+ function collectYamlLinkedKeys(node, linkedKeys) {
1645
+ for (const pair of node.pairs) {
1646
+ const value = getYamlScalarValue(pair.value);
1647
+ if (value) {
1648
+ for (const key of extractLinkedKeys(value)) {
1649
+ linkedKeys.add(key);
1650
+ }
1651
+ } else {
1652
+ const mapping = getYamlMapping(pair.value);
1653
+ if (mapping) {
1654
+ collectYamlLinkedKeys(mapping, linkedKeys);
1655
+ }
1656
+ }
1657
+ }
1658
+ }
1659
+ function removeJsonProperty(prop) {
1660
+ const parent = prop.parent;
1661
+ const index = parent.properties.indexOf(prop);
1662
+ const isLast = index === parent.properties.length - 1;
1663
+ const isOnly = parent.properties.length === 1;
1664
+ let start = prop.range[0];
1665
+ let end = prop.range[1];
1666
+ if (isOnly) {
1667
+ return { end, start };
1668
+ }
1669
+ if (isLast) {
1670
+ const prev = parent.properties[index - 1];
1671
+ start = prev.range[1];
1672
+ } else {
1673
+ end = parent.properties[index + 1].range[0];
1674
+ }
1675
+ return { end, start };
1676
+ }
1677
+ function removeYamlPair(pair) {
1678
+ const parent = pair.parent;
1679
+ const index = parent.pairs.indexOf(pair);
1680
+ const isLast = index === parent.pairs.length - 1;
1681
+ const isOnly = parent.pairs.length === 1;
1682
+ let start = pair.range[0];
1683
+ let end = pair.range[1];
1684
+ if (isOnly) {
1685
+ return { end, start };
1686
+ }
1687
+ if (isLast) {
1688
+ const prev = parent.pairs[index - 1];
1689
+ start = prev.range[1];
1690
+ } else {
1691
+ end = parent.pairs[index + 1].range[0];
1692
+ }
1693
+ return { end, start };
1694
+ }
1695
+ const noUnusedI18nKeys = createEslintRule({
1696
+ create(context, optionsWithDefault) {
1697
+ const options = optionsWithDefault[0];
1698
+ const filename = getFilename(context);
1699
+ if (!isLocaleFile(filename)) {
1700
+ return {};
1701
+ }
1702
+ const sourceCode = getSourceCode(context);
1703
+ const ast = sourceCode.ast;
1704
+ if (isJsonProgram(ast)) {
1705
+ debug(`Processing JSON locale file: ${filename}`);
1706
+ const rootExpr = ast.body[0].expression;
1707
+ if (rootExpr.type !== "JSONObjectExpression")
1708
+ return {};
1709
+ const usedKeys = collectAllUsedKeys(options.src, options.extensions);
1710
+ const linkedKeys = /* @__PURE__ */ new Set();
1711
+ collectJsonLinkedKeys(rootExpr, linkedKeys);
1712
+ const allUsedKeys = /* @__PURE__ */ new Set([...usedKeys, ...linkedKeys]);
1713
+ const prepared = prepareUsedKeys(allUsedKeys, options.ignoreKeys);
1714
+ const paths = [];
1715
+ buildJsonKeyPaths(rootExpr, "", paths);
1716
+ return {
1717
+ "Program:exit": function() {
1718
+ for (const { key, node } of paths) {
1719
+ if (!isKeyUsed(key, prepared)) {
1720
+ const loc = node.loc;
1721
+ context.report({
1722
+ data: { key },
1723
+ fix(fixer) {
1724
+ const { end, start } = removeJsonProperty(node);
1725
+ return fixer.removeRange([start, end]);
1726
+ },
1727
+ loc,
1728
+ messageId: "unused"
1729
+ });
1730
+ }
1731
+ }
1732
+ }
1733
+ };
1734
+ }
1735
+ if (isYamlProgram(ast)) {
1736
+ debug(`Processing YAML locale file: ${filename}`);
1737
+ const doc = ast.body[0];
1738
+ const content = doc.content;
1739
+ const mapping = getYamlMapping(content);
1740
+ if (!mapping)
1741
+ return {};
1742
+ const usedKeys = collectAllUsedKeys(options.src, options.extensions);
1743
+ const linkedKeys = /* @__PURE__ */ new Set();
1744
+ collectYamlLinkedKeys(mapping, linkedKeys);
1745
+ const allUsedKeys = /* @__PURE__ */ new Set([...usedKeys, ...linkedKeys]);
1746
+ const prepared = prepareUsedKeys(allUsedKeys, options.ignoreKeys);
1747
+ const paths = [];
1748
+ buildYamlKeyPaths(mapping, "", paths);
1749
+ return {
1750
+ "Program:exit": function() {
1751
+ for (const { key, node } of paths) {
1752
+ if (!isKeyUsed(key, prepared)) {
1753
+ const loc = node.loc;
1754
+ context.report({
1755
+ data: { key },
1756
+ fix(fixer) {
1757
+ const { end, start } = removeYamlPair(node);
1758
+ return fixer.removeRange([start, end]);
1759
+ },
1760
+ loc,
1761
+ messageId: "unused"
1762
+ });
1763
+ }
1764
+ }
1765
+ }
1766
+ };
1767
+ }
1768
+ return {};
1769
+ },
1770
+ defaultOptions: [{
1771
+ extensions: [".vue", ".ts"],
1772
+ ignoreKeys: [],
1773
+ src: "src"
1774
+ }],
1775
+ meta: {
1776
+ docs: {
1777
+ description: "disallow unused i18n keys in locale files",
1778
+ recommendation: "recommended"
1779
+ },
1780
+ fixable: "code",
1781
+ messages: {
1782
+ unused: `The i18n key '{{ key }}' is unused`
1783
+ },
1784
+ schema: [
1785
+ {
1786
+ additionalProperties: false,
1787
+ properties: {
1788
+ extensions: {
1789
+ items: { type: "string" },
1790
+ type: "array"
1791
+ },
1792
+ ignoreKeys: {
1793
+ items: { type: "string" },
1794
+ type: "array"
1795
+ },
1796
+ src: {
1797
+ type: "string"
1798
+ }
1799
+ },
1800
+ type: "object"
1801
+ }
1802
+ ],
1803
+ type: "suggestion"
1804
+ },
1805
+ name: RULE_NAME$1
1806
+ });
1807
+
1808
+ const RULE_NAME = "require-jsdoc-on-composable-options";
1809
+ const OPTIONS_PATTERN = /^Use\w+Options$/;
1810
+ const requireJsdocOnComposableOptions = createEslintRule({
1811
+ create(context) {
1812
+ const source = getSourceCode(context);
1813
+ return {
1814
+ TSInterfaceDeclaration(node) {
1815
+ if (!OPTIONS_PATTERN.test(node.id.name))
1816
+ return;
1817
+ for (const member of node.body.body) {
1818
+ if (member.type !== AST_NODE_TYPES.TSPropertySignature)
1819
+ continue;
1820
+ const comments = source.getCommentsBefore(member);
1821
+ const hasJsdoc = comments.some((comment) => comment.type === "Block" && comment.value.startsWith("*"));
1822
+ if (!hasJsdoc) {
1823
+ const propertyName = member.key.type === AST_NODE_TYPES.Identifier ? member.key.name : source.getText(member.key);
1824
+ context.report({
1825
+ data: { interfaceName: node.id.name, property: propertyName },
1826
+ messageId: "missingJsdoc",
1827
+ node: member
1828
+ });
1829
+ }
1830
+ }
1831
+ }
1832
+ };
1833
+ },
1834
+ defaultOptions: [],
1835
+ meta: {
1836
+ docs: {
1837
+ description: "Require JSDoc comments on composable options interface properties",
1838
+ recommendation: "stylistic"
1839
+ },
1840
+ messages: {
1841
+ missingJsdoc: "Property '{{ property }}' in '{{ interfaceName }}' should have a JSDoc comment."
1842
+ },
1843
+ schema: [],
1844
+ type: "suggestion"
1845
+ },
726
1846
  name: RULE_NAME
727
1847
  });
728
1848
 
@@ -732,19 +1852,32 @@ const plugin = {
732
1852
  version: pkg.version
733
1853
  },
734
1854
  rules: {
1855
+ "composable-input-flexibility": composableInputFlexibility,
1856
+ "composable-naming-convention": composableNamingConvention,
1857
+ "composable-no-default-export": composableNoDefaultExport,
1858
+ "composable-prefer-shallowref": composablePreferShallowref,
1859
+ "composable-require-cleanup": composableRequireCleanup,
1860
+ "composable-return-readonly": composableReturnReadonly,
1861
+ "composable-ssr-safety": composableSsrSafety,
735
1862
  "consistent-ref-type-annotation": consistentRefTypeAnnotation,
736
1863
  "max-dependencies": maxDependencies,
737
1864
  "no-deprecated-classes": noDeprecatedClasses,
738
1865
  "no-deprecated-components": noDeprecatedComponents,
739
1866
  "no-deprecated-props": noDeprecatedProps,
740
1867
  "no-dot-ts-imports": noDotTsImport,
741
- "no-legacy-library-import": noLegacyLibraryImport
1868
+ "no-legacy-library-import": noLegacyLibraryImport,
1869
+ "no-unused-i18n-keys": noUnusedI18nKeys,
1870
+ "require-jsdoc-on-composable-options": requireJsdocOnComposableOptions
742
1871
  }
743
1872
  };
744
1873
 
745
1874
  const configs = {
746
1875
  "recommended": createRecommended(plugin, "@rotki", false),
747
- "recommended-flat": createRecommended(plugin, "@rotki", true)
1876
+ "recommended-flat": createRecommended(plugin, "@rotki", true),
1877
+ "strict": createConfig(plugin, "@rotki", false, "strict"),
1878
+ "strict-flat": createConfig(plugin, "@rotki", true, "strict"),
1879
+ "stylistic": createConfig(plugin, "@rotki", false, "stylistic"),
1880
+ "stylistic-flat": createConfig(plugin, "@rotki", true, "stylistic")
748
1881
  };
749
1882
 
750
1883
  const index = Object.assign(plugin, { configs });