@rotki/eslint-plugin 0.1.0 → 0.2.1

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.cjs CHANGED
@@ -1,8 +1,8 @@
1
1
  'use strict';
2
2
 
3
+ const createDebug = require('debug');
3
4
  const node_path = require('node:path');
4
5
  const compat = require('eslint-compat-utils');
5
- const createDebug = require('debug');
6
6
  const scule = require('scule');
7
7
 
8
8
  function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e; }
@@ -19,12 +19,12 @@ function _interopNamespaceCompat(e) {
19
19
  return n;
20
20
  }
21
21
 
22
- const compat__namespace = /*#__PURE__*/_interopNamespaceCompat(compat);
23
22
  const createDebug__default = /*#__PURE__*/_interopDefaultCompat(createDebug);
23
+ const compat__namespace = /*#__PURE__*/_interopNamespaceCompat(compat);
24
24
 
25
25
  const name = "@rotki/eslint-plugin";
26
- const version = "0.1.0";
27
- const packageManager = "pnpm@8.14.1";
26
+ const version = "0.2.1";
27
+ const packageManager = "pnpm@8.14.3";
28
28
  const type = "module";
29
29
  const license = "AGPL-3.0";
30
30
  const bugs = {
@@ -70,25 +70,26 @@ const peerDependencies = {
70
70
  eslint: "^8.0.0 || ^9.0.0"
71
71
  };
72
72
  const dependencies = {
73
- "@typescript-eslint/utils": "6.19.0",
73
+ "@typescript-eslint/utils": "6.19.1",
74
74
  debug: "4.3.4",
75
75
  "eslint-compat-utils": "0.4.1",
76
76
  "jsonc-eslint-parser": "2.4.0",
77
77
  scule: "1.2.0",
78
- "vue-eslint-parser": "9.4.1",
78
+ "vue-eslint-parser": "9.4.2",
79
79
  "yaml-eslint-parser": "1.2.2"
80
80
  };
81
81
  const devDependencies = {
82
82
  "@commitlint/cli": "18.5.0",
83
83
  "@commitlint/config-conventional": "18.5.0",
84
- "@rotki/eslint-config": "2.3.0",
84
+ "@rotki/eslint-config": "2.4.1",
85
85
  "@types/debug": "4.1.12",
86
86
  "@types/eslint": "8.56.2",
87
87
  "@types/node": "20",
88
- "@typescript-eslint/eslint-plugin": "6.19.0",
89
- "@typescript-eslint/parser": "6.19.0",
90
- "@typescript-eslint/rule-tester": "6.19.0",
88
+ "@typescript-eslint/eslint-plugin": "6.19.1",
89
+ "@typescript-eslint/parser": "6.19.1",
90
+ "@typescript-eslint/rule-tester": "6.19.1",
91
91
  bumpp: "9.3.0",
92
+ debug: "4.3.4",
92
93
  eslint: "8.56.0",
93
94
  husky: "8.0.3",
94
95
  "lint-staged": "15.2.0",
@@ -195,8 +196,65 @@ function defineTemplateBodyVisitor(context, templateBodyVisitor, scriptVisitor,
195
196
  );
196
197
  }
197
198
 
198
- const RULE_NAME$1 = "no-deprecated-classes";
199
- const replacements$1 = [
199
+ function getStringLiteralValue(node, stringOnly = false) {
200
+ if (node.type === "Literal") {
201
+ if (node.value == null) {
202
+ if (!stringOnly && node.bigint != null)
203
+ return node.bigint;
204
+ return null;
205
+ }
206
+ if (typeof node.value === "string")
207
+ return node.value;
208
+ if (!stringOnly)
209
+ return String(node.value);
210
+ return null;
211
+ }
212
+ if (node.type === "TemplateLiteral" && node.expressions.length === 0 && node.quasis.length === 1)
213
+ return node.quasis[0].value.cooked;
214
+ return null;
215
+ }
216
+ function getStaticPropertyName(node) {
217
+ if (node.type === "Property" || node.type === "MethodDefinition") {
218
+ if (!node.computed) {
219
+ const key2 = node.key;
220
+ if (key2.type === "Identifier")
221
+ return key2.name;
222
+ }
223
+ const key = node.key;
224
+ return getStringLiteralValue(key);
225
+ } else if (node.type === "MemberExpression") {
226
+ if (!node.computed) {
227
+ const property2 = node.property;
228
+ if (property2.type === "Identifier")
229
+ return property2.name;
230
+ return null;
231
+ }
232
+ const property = node.property;
233
+ return getStringLiteralValue(property);
234
+ }
235
+ return null;
236
+ }
237
+
238
+ function createRecommended(plugin, name, flat) {
239
+ const rules = Object.fromEntries(Object.entries(plugin.rules).filter(([_key, rule]) => rule.meta.recommended === "recommended" && !rule.meta.deprecated).map(([key]) => [`${name}/${key}`, 2]));
240
+ if (flat) {
241
+ return {
242
+ plugins: {
243
+ [name]: plugin
244
+ },
245
+ rules
246
+ };
247
+ } else {
248
+ return {
249
+ plugins: [name],
250
+ rules
251
+ };
252
+ }
253
+ }
254
+
255
+ const RULE_NAME$3 = "no-deprecated-classes";
256
+ const debug$2 = createDebug__default("@rotki/eslint-plugin:no-deprecated-classes");
257
+ const replacements$2 = [
200
258
  ["d-block", "block"],
201
259
  ["d-flex", "flex"],
202
260
  ["flex-column", "flex-col"],
@@ -225,51 +283,122 @@ function isString(replacement) {
225
283
  function isRegex(replacement) {
226
284
  return replacement[0] instanceof RegExp;
227
285
  }
286
+ function findReplacement(className) {
287
+ for (const replacement of replacements$2) {
288
+ if (isString(replacement) && replacement[0] === className)
289
+ return replacement[1];
290
+ if (isRegex(replacement)) {
291
+ const matches = (replacement[0].exec(className) || []).slice(1);
292
+ const replace = replacement[1];
293
+ if (matches.length > 0 && typeof replace === "function")
294
+ return replace(matches);
295
+ }
296
+ }
297
+ return void 0;
298
+ }
299
+ function getRange(node) {
300
+ if (node.type === "VAttribute" && node.value && node.value.range)
301
+ return node.value.range;
302
+ return node.range;
303
+ }
304
+ function reportReplacement(className, replacement, node, context, position = 1) {
305
+ debug$2(`found replacement ${replacement} for ${className}`);
306
+ const source = getSourceCode(context);
307
+ const initialRange = getRange(node);
308
+ const range = [
309
+ initialRange[0] + position,
310
+ initialRange[0] + position + className.length
311
+ ];
312
+ const loc = {
313
+ end: source.getLocFromIndex(range[1]),
314
+ start: source.getLocFromIndex(range[0])
315
+ };
316
+ context.report({
317
+ data: {
318
+ className,
319
+ replacement
320
+ },
321
+ fix(fixer) {
322
+ return fixer.replaceTextRange(range, replacement);
323
+ },
324
+ loc,
325
+ messageId: "replacedWith"
326
+ });
327
+ }
328
+ function* extractClassNames(node, textOnly = false) {
329
+ if (node.type === "Literal") {
330
+ const classNames = `${node.value}`;
331
+ yield* classNames.split(/\s+/).map((className) => ({ className, position: classNames.indexOf(className) + 1, reportNode: node }));
332
+ return;
333
+ }
334
+ if (node.type === "TemplateLiteral") {
335
+ for (const templateElement of node.quasis) {
336
+ const classNames = templateElement.value.cooked;
337
+ if (classNames === null)
338
+ continue;
339
+ yield* classNames.split(/\s+/).map((className) => ({ className, position: classNames.indexOf(className) + 1, reportNode: templateElement }));
340
+ }
341
+ for (const expr of node.expressions)
342
+ yield* extractClassNames(expr, true);
343
+ return;
344
+ }
345
+ if (node.type === "BinaryExpression") {
346
+ if (node.operator !== "+")
347
+ return;
348
+ yield* extractClassNames(node.left, true);
349
+ yield* extractClassNames(node.right, true);
350
+ return;
351
+ }
352
+ if (textOnly)
353
+ return;
354
+ if (node.type === "ObjectExpression") {
355
+ for (const prop of node.properties) {
356
+ if (prop.type !== "Property")
357
+ continue;
358
+ const classNames = getStaticPropertyName(prop);
359
+ if (!classNames)
360
+ continue;
361
+ yield* classNames.split(/\s+/).map((className) => ({ className, position: classNames.indexOf(className) + 1, reportNode: prop.key }));
362
+ }
363
+ return;
364
+ }
365
+ if (node.type === "ArrayExpression") {
366
+ for (const element of node.elements) {
367
+ if (element == null)
368
+ continue;
369
+ if (element.type === "SpreadElement")
370
+ continue;
371
+ yield* extractClassNames(element);
372
+ }
373
+ }
374
+ if (node.type === "ConditionalExpression") {
375
+ yield* extractClassNames(node.consequent);
376
+ yield* extractClassNames(node.alternate);
377
+ }
378
+ }
228
379
  const noDeprecatedClasses = createEslintRule({
229
380
  create(context) {
230
381
  return defineTemplateBodyVisitor(context, {
231
- 'VAttribute[key.name="class"]': function(node) {
382
+ 'VAttribute[directive=false][key.name="class"]': function(node) {
232
383
  if (!node.value || !node.value.value)
233
384
  return;
234
- const classes = node.value.value.split(/\s+/).filter((s) => !!s);
235
- const source = getSourceCode(context);
236
- const replaced = [];
237
- classes.forEach((className) => {
238
- for (const replacement of replacements$1) {
239
- if (isString(replacement) && replacement[0] === className)
240
- replaced.push([className, replacement[1]]);
241
- if (isRegex(replacement)) {
242
- const matches = (replacement[0].exec(className) || []).slice(1);
243
- const replace = replacement[1];
244
- if (matches.length > 0 && typeof replace === "function")
245
- return replaced.push([className, replace(matches)]);
246
- }
247
- }
248
- });
249
- replaced.forEach((replacement) => {
250
- if (!node.value)
251
- return;
252
- const idx = node.value.value.indexOf(replacement[0]) + 1;
253
- const range = [
254
- node.value.range[0] + idx,
255
- node.value.range[0] + idx + replacement[0].length
256
- ];
257
- const loc = {
258
- end: source.getLocFromIndex(range[1]),
259
- start: source.getLocFromIndex(range[0])
260
- };
261
- context.report({
262
- data: {
263
- a: replacement[0],
264
- b: replacement[1]
265
- },
266
- fix(fixer) {
267
- return fixer.replaceTextRange(range, replacement[1]);
268
- },
269
- loc,
270
- messageId: "replacedWith"
271
- });
272
- });
385
+ for (const className of node.value.value.split(/\s+/).filter((s) => !!s)) {
386
+ const replacement = findReplacement(className);
387
+ const position = node.value.value.indexOf(className) + 1;
388
+ if (!replacement)
389
+ continue;
390
+ reportReplacement(className, replacement, node, context, position);
391
+ }
392
+ },
393
+ "VAttribute[directive=true][key.name.name='bind'][key.argument.name='class'] > VExpressionContainer.value": function(node) {
394
+ if (!node.expression)
395
+ return;
396
+ for (const { className, position, reportNode } of extractClassNames(node.expression)) {
397
+ const replacement = findReplacement(className);
398
+ if (!replacement)
399
+ continue;
400
+ reportReplacement(className, replacement, reportNode, context, position);
401
+ }
273
402
  }
274
403
  });
275
404
  },
@@ -281,25 +410,25 @@ const noDeprecatedClasses = createEslintRule({
281
410
  },
282
411
  fixable: "code",
283
412
  messages: {
284
- replacedWith: `'{{ a }}' has been replaced with '{{ b }}'`
413
+ replacedWith: `'{{ className }}' has been replaced with '{{ replacement }}'`
285
414
  },
286
415
  schema: [],
287
416
  type: "problem"
288
417
  },
289
- name: RULE_NAME$1
418
+ name: RULE_NAME$3
290
419
  });
291
420
 
292
- const debug = createDebug__default("@rotki/eslint-plugin:no-deprecated-components");
293
- const RULE_NAME = "no-deprecated-components";
294
- const replacements = {
421
+ const debug$1 = createDebug__default("@rotki/eslint-plugin:no-deprecated-components");
422
+ const RULE_NAME$2 = "no-deprecated-components";
423
+ const replacements$1 = {
295
424
  DataTable: true,
296
425
  Fragment: false
297
426
  };
298
427
  const skipInLegacy = [
299
428
  "Fragment"
300
429
  ];
301
- function hasReplacement(tag) {
302
- return Object.prototype.hasOwnProperty.call(replacements, tag);
430
+ function hasReplacement$1(tag) {
431
+ return Object.prototype.hasOwnProperty.call(replacements$1, tag);
303
432
  }
304
433
  const noDeprecatedComponents = createEslintRule({
305
434
  create(context, optionsWithDefault) {
@@ -311,11 +440,11 @@ const noDeprecatedComponents = createEslintRule({
311
440
  const sourceCode = getSourceCode(context);
312
441
  if (!("getTemplateBodyTokenStore" in sourceCode.parserServices))
313
442
  throw new Error("cannot find getTemplateBodyTokenStore in parserServices");
314
- if (!hasReplacement(tag))
443
+ if (!hasReplacement$1(tag))
315
444
  return;
316
- const replacement = replacements[tag];
445
+ const replacement = replacements$1[tag];
317
446
  if (replacement || legacy && skipInLegacy.includes(tag)) {
318
- debug(`${tag} has been deprecated`);
447
+ debug$1(`${tag} has been deprecated`);
319
448
  context.report({
320
449
  data: {
321
450
  name: scule.snakeCase(tag)
@@ -324,7 +453,7 @@ const noDeprecatedComponents = createEslintRule({
324
453
  node: element
325
454
  });
326
455
  } else {
327
- debug(`${tag} has will be removed`);
456
+ debug$1(`${tag} has will be removed`);
328
457
  context.report({
329
458
  data: {
330
459
  name: scule.snakeCase(tag)
@@ -367,6 +496,109 @@ const noDeprecatedComponents = createEslintRule({
367
496
  ],
368
497
  type: "problem"
369
498
  },
499
+ name: RULE_NAME$2
500
+ });
501
+
502
+ const debug = createDebug__default("@rotki/eslint-plugin:no-deprecated-props");
503
+ const RULE_NAME$1 = "no-deprecated-props";
504
+ const replacements = {
505
+ RuiRadio: {
506
+ internalValue: "value"
507
+ }
508
+ };
509
+ function hasReplacement(tag) {
510
+ return Object.prototype.hasOwnProperty.call(replacements, tag);
511
+ }
512
+ function getPropName(node) {
513
+ if (node.directive) {
514
+ if (node.key.argument?.type !== "VIdentifier")
515
+ return void 0;
516
+ return scule.kebabCase(node.key.argument.rawName);
517
+ }
518
+ return scule.kebabCase(node.key.rawName);
519
+ }
520
+ const noDeprecatedProps = createEslintRule({
521
+ create(context) {
522
+ return defineTemplateBodyVisitor(context, {
523
+ VAttribute(node) {
524
+ if (node.directive && (node.value?.type === "VExpressionContainer" && (node.key.name.name !== "bind" || !node.key.argument)))
525
+ return;
526
+ const tag = scule.pascalCase(node.parent.parent.rawName);
527
+ if (!hasReplacement(tag))
528
+ return;
529
+ debug(`${tag} has replacement properties`);
530
+ const propName = getPropName(node);
531
+ const propNameNode = node.directive ? node.key.argument : node.key;
532
+ if (!propName || !propNameNode) {
533
+ debug("could not get prop name and/or node");
534
+ return;
535
+ }
536
+ Object.entries(replacements[tag]).forEach(([prop, replacement]) => {
537
+ if (scule.kebabCase(prop) === propName) {
538
+ debug(`preparing a replacement for ${tag}:${propName} -> ${replacement}`);
539
+ context.report({
540
+ data: {
541
+ prop,
542
+ replacement
543
+ },
544
+ fix(fixer) {
545
+ return fixer.replaceText(propNameNode, replacement);
546
+ },
547
+ messageId: "replacedWith",
548
+ node: propNameNode
549
+ });
550
+ }
551
+ });
552
+ }
553
+ });
554
+ },
555
+ defaultOptions: [],
556
+ meta: {
557
+ docs: {
558
+ description: "..."
559
+ },
560
+ fixable: "code",
561
+ messages: {
562
+ replacedWith: `'{{ prop }}' has been replaced with '{{ replacement }}'`
563
+ },
564
+ schema: [],
565
+ type: "problem"
566
+ },
567
+ name: RULE_NAME$1
568
+ });
569
+
570
+ const RULE_NAME = "no-legacy-library-import";
571
+ const legacyLibrary = "@rotki/ui-library-compat";
572
+ const newLibrary = "@rotki/ui-library";
573
+ const noLegacyLibraryImport = createEslintRule({
574
+ create(context) {
575
+ return {
576
+ ImportDeclaration(node) {
577
+ if (!node.source.value.startsWith(legacyLibrary))
578
+ return;
579
+ const replacement = node.source.value.replace(legacyLibrary, newLibrary);
580
+ context.report({
581
+ fix(fixer) {
582
+ return fixer.replaceText(node.source, `'${replacement}'`);
583
+ },
584
+ messageId: "replacedWith",
585
+ node: node.source
586
+ });
587
+ }
588
+ };
589
+ },
590
+ defaultOptions: [],
591
+ meta: {
592
+ docs: {
593
+ description: `Reports and replaces imports of ${legacyLibrary} with ${newLibrary}`
594
+ },
595
+ fixable: "code",
596
+ messages: {
597
+ replacedWith: `${legacyLibrary} has been replaced by ${newLibrary}`
598
+ },
599
+ schema: [],
600
+ type: "problem"
601
+ },
370
602
  name: RULE_NAME
371
603
  });
372
604
 
@@ -377,28 +609,13 @@ const plugin = {
377
609
  },
378
610
  rules: {
379
611
  "no-deprecated-classes": noDeprecatedClasses,
380
- "no-deprecated-components": noDeprecatedComponents
612
+ "no-deprecated-components": noDeprecatedComponents,
613
+ "no-deprecated-props": noDeprecatedProps,
614
+ "no-legacy-library-import": noLegacyLibraryImport
381
615
  }
382
616
  };
383
617
  const plugin$1 = plugin;
384
618
 
385
- function createRecommended(plugin, name, flat) {
386
- const rules = Object.fromEntries(Object.entries(plugin.rules).filter(([_key, rule]) => rule.meta.recommended === "recommended" && !rule.meta.deprecated).map(([key]) => [`${name}/${key}`, 2]));
387
- if (flat) {
388
- return {
389
- plugins: {
390
- [name]: plugin
391
- },
392
- rules
393
- };
394
- } else {
395
- return {
396
- plugins: [name],
397
- rules
398
- };
399
- }
400
- }
401
-
402
619
  const configs = {
403
620
  "recommended": createRecommended(plugin$1, "@rotki", false),
404
621
  "recommended-flat": createRecommended(plugin$1, "@rotki", true)
package/dist/index.d.cts CHANGED
@@ -16,6 +16,8 @@ declare const plugin: {
16
16
  rules: {
17
17
  'no-deprecated-classes': PluginRuleModule<[]>;
18
18
  'no-deprecated-components': PluginRuleModule<Options>;
19
+ 'no-deprecated-props': PluginRuleModule<[]>;
20
+ 'no-legacy-library-import': PluginRuleModule<[]>;
19
21
  };
20
22
  };
21
23
 
@@ -27,6 +29,8 @@ declare const _default: {
27
29
  rules: {
28
30
  'no-deprecated-classes': PluginRuleModule<[]>;
29
31
  'no-deprecated-components': PluginRuleModule<Options>;
32
+ 'no-deprecated-props': PluginRuleModule<[]>;
33
+ 'no-legacy-library-import': PluginRuleModule<[]>;
30
34
  };
31
35
  } & {
32
36
  configs: {
@@ -40,6 +44,8 @@ declare const _default: {
40
44
  rules: {
41
45
  'no-deprecated-classes': PluginRuleModule<[]>;
42
46
  'no-deprecated-components': PluginRuleModule<Options>;
47
+ 'no-deprecated-props': PluginRuleModule<[]>;
48
+ 'no-legacy-library-import': PluginRuleModule<[]>;
43
49
  };
44
50
  };
45
51
  };
@@ -62,6 +68,8 @@ declare const _default: {
62
68
  rules: {
63
69
  'no-deprecated-classes': PluginRuleModule<[]>;
64
70
  'no-deprecated-components': PluginRuleModule<Options>;
71
+ 'no-deprecated-props': PluginRuleModule<[]>;
72
+ 'no-legacy-library-import': PluginRuleModule<[]>;
65
73
  };
66
74
  };
67
75
  };
package/dist/index.d.mts CHANGED
@@ -16,6 +16,8 @@ declare const plugin: {
16
16
  rules: {
17
17
  'no-deprecated-classes': PluginRuleModule<[]>;
18
18
  'no-deprecated-components': PluginRuleModule<Options>;
19
+ 'no-deprecated-props': PluginRuleModule<[]>;
20
+ 'no-legacy-library-import': PluginRuleModule<[]>;
19
21
  };
20
22
  };
21
23
 
@@ -27,6 +29,8 @@ declare const _default: {
27
29
  rules: {
28
30
  'no-deprecated-classes': PluginRuleModule<[]>;
29
31
  'no-deprecated-components': PluginRuleModule<Options>;
32
+ 'no-deprecated-props': PluginRuleModule<[]>;
33
+ 'no-legacy-library-import': PluginRuleModule<[]>;
30
34
  };
31
35
  } & {
32
36
  configs: {
@@ -40,6 +44,8 @@ declare const _default: {
40
44
  rules: {
41
45
  'no-deprecated-classes': PluginRuleModule<[]>;
42
46
  'no-deprecated-components': PluginRuleModule<Options>;
47
+ 'no-deprecated-props': PluginRuleModule<[]>;
48
+ 'no-legacy-library-import': PluginRuleModule<[]>;
43
49
  };
44
50
  };
45
51
  };
@@ -62,6 +68,8 @@ declare const _default: {
62
68
  rules: {
63
69
  'no-deprecated-classes': PluginRuleModule<[]>;
64
70
  'no-deprecated-components': PluginRuleModule<Options>;
71
+ 'no-deprecated-props': PluginRuleModule<[]>;
72
+ 'no-legacy-library-import': PluginRuleModule<[]>;
65
73
  };
66
74
  };
67
75
  };
package/dist/index.d.ts CHANGED
@@ -16,6 +16,8 @@ declare const plugin: {
16
16
  rules: {
17
17
  'no-deprecated-classes': PluginRuleModule<[]>;
18
18
  'no-deprecated-components': PluginRuleModule<Options>;
19
+ 'no-deprecated-props': PluginRuleModule<[]>;
20
+ 'no-legacy-library-import': PluginRuleModule<[]>;
19
21
  };
20
22
  };
21
23
 
@@ -27,6 +29,8 @@ declare const _default: {
27
29
  rules: {
28
30
  'no-deprecated-classes': PluginRuleModule<[]>;
29
31
  'no-deprecated-components': PluginRuleModule<Options>;
32
+ 'no-deprecated-props': PluginRuleModule<[]>;
33
+ 'no-legacy-library-import': PluginRuleModule<[]>;
30
34
  };
31
35
  } & {
32
36
  configs: {
@@ -40,6 +44,8 @@ declare const _default: {
40
44
  rules: {
41
45
  'no-deprecated-classes': PluginRuleModule<[]>;
42
46
  'no-deprecated-components': PluginRuleModule<Options>;
47
+ 'no-deprecated-props': PluginRuleModule<[]>;
48
+ 'no-legacy-library-import': PluginRuleModule<[]>;
43
49
  };
44
50
  };
45
51
  };
@@ -62,6 +68,8 @@ declare const _default: {
62
68
  rules: {
63
69
  'no-deprecated-classes': PluginRuleModule<[]>;
64
70
  'no-deprecated-components': PluginRuleModule<Options>;
71
+ 'no-deprecated-props': PluginRuleModule<[]>;
72
+ 'no-legacy-library-import': PluginRuleModule<[]>;
65
73
  };
66
74
  };
67
75
  };
package/dist/index.mjs CHANGED
@@ -1,11 +1,11 @@
1
+ import createDebug from 'debug';
1
2
  import { extname } from 'node:path';
2
3
  import * as compat from 'eslint-compat-utils';
3
- import createDebug from 'debug';
4
- import { pascalCase, snakeCase } from 'scule';
4
+ import { pascalCase, snakeCase, kebabCase } from 'scule';
5
5
 
6
6
  const name = "@rotki/eslint-plugin";
7
- const version = "0.1.0";
8
- const packageManager = "pnpm@8.14.1";
7
+ const version = "0.2.1";
8
+ const packageManager = "pnpm@8.14.3";
9
9
  const type = "module";
10
10
  const license = "AGPL-3.0";
11
11
  const bugs = {
@@ -51,25 +51,26 @@ const peerDependencies = {
51
51
  eslint: "^8.0.0 || ^9.0.0"
52
52
  };
53
53
  const dependencies = {
54
- "@typescript-eslint/utils": "6.19.0",
54
+ "@typescript-eslint/utils": "6.19.1",
55
55
  debug: "4.3.4",
56
56
  "eslint-compat-utils": "0.4.1",
57
57
  "jsonc-eslint-parser": "2.4.0",
58
58
  scule: "1.2.0",
59
- "vue-eslint-parser": "9.4.1",
59
+ "vue-eslint-parser": "9.4.2",
60
60
  "yaml-eslint-parser": "1.2.2"
61
61
  };
62
62
  const devDependencies = {
63
63
  "@commitlint/cli": "18.5.0",
64
64
  "@commitlint/config-conventional": "18.5.0",
65
- "@rotki/eslint-config": "2.3.0",
65
+ "@rotki/eslint-config": "2.4.1",
66
66
  "@types/debug": "4.1.12",
67
67
  "@types/eslint": "8.56.2",
68
68
  "@types/node": "20",
69
- "@typescript-eslint/eslint-plugin": "6.19.0",
70
- "@typescript-eslint/parser": "6.19.0",
71
- "@typescript-eslint/rule-tester": "6.19.0",
69
+ "@typescript-eslint/eslint-plugin": "6.19.1",
70
+ "@typescript-eslint/parser": "6.19.1",
71
+ "@typescript-eslint/rule-tester": "6.19.1",
72
72
  bumpp: "9.3.0",
73
+ debug: "4.3.4",
73
74
  eslint: "8.56.0",
74
75
  husky: "8.0.3",
75
76
  "lint-staged": "15.2.0",
@@ -176,8 +177,65 @@ function defineTemplateBodyVisitor(context, templateBodyVisitor, scriptVisitor,
176
177
  );
177
178
  }
178
179
 
179
- const RULE_NAME$1 = "no-deprecated-classes";
180
- const replacements$1 = [
180
+ function getStringLiteralValue(node, stringOnly = false) {
181
+ if (node.type === "Literal") {
182
+ if (node.value == null) {
183
+ if (!stringOnly && node.bigint != null)
184
+ return node.bigint;
185
+ return null;
186
+ }
187
+ if (typeof node.value === "string")
188
+ return node.value;
189
+ if (!stringOnly)
190
+ return String(node.value);
191
+ return null;
192
+ }
193
+ if (node.type === "TemplateLiteral" && node.expressions.length === 0 && node.quasis.length === 1)
194
+ return node.quasis[0].value.cooked;
195
+ return null;
196
+ }
197
+ function getStaticPropertyName(node) {
198
+ if (node.type === "Property" || node.type === "MethodDefinition") {
199
+ if (!node.computed) {
200
+ const key2 = node.key;
201
+ if (key2.type === "Identifier")
202
+ return key2.name;
203
+ }
204
+ const key = node.key;
205
+ return getStringLiteralValue(key);
206
+ } else if (node.type === "MemberExpression") {
207
+ if (!node.computed) {
208
+ const property2 = node.property;
209
+ if (property2.type === "Identifier")
210
+ return property2.name;
211
+ return null;
212
+ }
213
+ const property = node.property;
214
+ return getStringLiteralValue(property);
215
+ }
216
+ return null;
217
+ }
218
+
219
+ function createRecommended(plugin, name, flat) {
220
+ const rules = Object.fromEntries(Object.entries(plugin.rules).filter(([_key, rule]) => rule.meta.recommended === "recommended" && !rule.meta.deprecated).map(([key]) => [`${name}/${key}`, 2]));
221
+ if (flat) {
222
+ return {
223
+ plugins: {
224
+ [name]: plugin
225
+ },
226
+ rules
227
+ };
228
+ } else {
229
+ return {
230
+ plugins: [name],
231
+ rules
232
+ };
233
+ }
234
+ }
235
+
236
+ const RULE_NAME$3 = "no-deprecated-classes";
237
+ const debug$2 = createDebug("@rotki/eslint-plugin:no-deprecated-classes");
238
+ const replacements$2 = [
181
239
  ["d-block", "block"],
182
240
  ["d-flex", "flex"],
183
241
  ["flex-column", "flex-col"],
@@ -206,51 +264,122 @@ function isString(replacement) {
206
264
  function isRegex(replacement) {
207
265
  return replacement[0] instanceof RegExp;
208
266
  }
267
+ function findReplacement(className) {
268
+ for (const replacement of replacements$2) {
269
+ if (isString(replacement) && replacement[0] === className)
270
+ return replacement[1];
271
+ if (isRegex(replacement)) {
272
+ const matches = (replacement[0].exec(className) || []).slice(1);
273
+ const replace = replacement[1];
274
+ if (matches.length > 0 && typeof replace === "function")
275
+ return replace(matches);
276
+ }
277
+ }
278
+ return void 0;
279
+ }
280
+ function getRange(node) {
281
+ if (node.type === "VAttribute" && node.value && node.value.range)
282
+ return node.value.range;
283
+ return node.range;
284
+ }
285
+ function reportReplacement(className, replacement, node, context, position = 1) {
286
+ debug$2(`found replacement ${replacement} for ${className}`);
287
+ const source = getSourceCode(context);
288
+ const initialRange = getRange(node);
289
+ const range = [
290
+ initialRange[0] + position,
291
+ initialRange[0] + position + className.length
292
+ ];
293
+ const loc = {
294
+ end: source.getLocFromIndex(range[1]),
295
+ start: source.getLocFromIndex(range[0])
296
+ };
297
+ context.report({
298
+ data: {
299
+ className,
300
+ replacement
301
+ },
302
+ fix(fixer) {
303
+ return fixer.replaceTextRange(range, replacement);
304
+ },
305
+ loc,
306
+ messageId: "replacedWith"
307
+ });
308
+ }
309
+ function* extractClassNames(node, textOnly = false) {
310
+ if (node.type === "Literal") {
311
+ const classNames = `${node.value}`;
312
+ yield* classNames.split(/\s+/).map((className) => ({ className, position: classNames.indexOf(className) + 1, reportNode: node }));
313
+ return;
314
+ }
315
+ if (node.type === "TemplateLiteral") {
316
+ for (const templateElement of node.quasis) {
317
+ const classNames = templateElement.value.cooked;
318
+ if (classNames === null)
319
+ continue;
320
+ yield* classNames.split(/\s+/).map((className) => ({ className, position: classNames.indexOf(className) + 1, reportNode: templateElement }));
321
+ }
322
+ for (const expr of node.expressions)
323
+ yield* extractClassNames(expr, true);
324
+ return;
325
+ }
326
+ if (node.type === "BinaryExpression") {
327
+ if (node.operator !== "+")
328
+ return;
329
+ yield* extractClassNames(node.left, true);
330
+ yield* extractClassNames(node.right, true);
331
+ return;
332
+ }
333
+ if (textOnly)
334
+ return;
335
+ if (node.type === "ObjectExpression") {
336
+ for (const prop of node.properties) {
337
+ if (prop.type !== "Property")
338
+ continue;
339
+ const classNames = getStaticPropertyName(prop);
340
+ if (!classNames)
341
+ continue;
342
+ yield* classNames.split(/\s+/).map((className) => ({ className, position: classNames.indexOf(className) + 1, reportNode: prop.key }));
343
+ }
344
+ return;
345
+ }
346
+ if (node.type === "ArrayExpression") {
347
+ for (const element of node.elements) {
348
+ if (element == null)
349
+ continue;
350
+ if (element.type === "SpreadElement")
351
+ continue;
352
+ yield* extractClassNames(element);
353
+ }
354
+ }
355
+ if (node.type === "ConditionalExpression") {
356
+ yield* extractClassNames(node.consequent);
357
+ yield* extractClassNames(node.alternate);
358
+ }
359
+ }
209
360
  const noDeprecatedClasses = createEslintRule({
210
361
  create(context) {
211
362
  return defineTemplateBodyVisitor(context, {
212
- 'VAttribute[key.name="class"]': function(node) {
363
+ 'VAttribute[directive=false][key.name="class"]': function(node) {
213
364
  if (!node.value || !node.value.value)
214
365
  return;
215
- const classes = node.value.value.split(/\s+/).filter((s) => !!s);
216
- const source = getSourceCode(context);
217
- const replaced = [];
218
- classes.forEach((className) => {
219
- for (const replacement of replacements$1) {
220
- if (isString(replacement) && replacement[0] === className)
221
- replaced.push([className, replacement[1]]);
222
- if (isRegex(replacement)) {
223
- const matches = (replacement[0].exec(className) || []).slice(1);
224
- const replace = replacement[1];
225
- if (matches.length > 0 && typeof replace === "function")
226
- return replaced.push([className, replace(matches)]);
227
- }
228
- }
229
- });
230
- replaced.forEach((replacement) => {
231
- if (!node.value)
232
- return;
233
- const idx = node.value.value.indexOf(replacement[0]) + 1;
234
- const range = [
235
- node.value.range[0] + idx,
236
- node.value.range[0] + idx + replacement[0].length
237
- ];
238
- const loc = {
239
- end: source.getLocFromIndex(range[1]),
240
- start: source.getLocFromIndex(range[0])
241
- };
242
- context.report({
243
- data: {
244
- a: replacement[0],
245
- b: replacement[1]
246
- },
247
- fix(fixer) {
248
- return fixer.replaceTextRange(range, replacement[1]);
249
- },
250
- loc,
251
- messageId: "replacedWith"
252
- });
253
- });
366
+ for (const className of node.value.value.split(/\s+/).filter((s) => !!s)) {
367
+ const replacement = findReplacement(className);
368
+ const position = node.value.value.indexOf(className) + 1;
369
+ if (!replacement)
370
+ continue;
371
+ reportReplacement(className, replacement, node, context, position);
372
+ }
373
+ },
374
+ "VAttribute[directive=true][key.name.name='bind'][key.argument.name='class'] > VExpressionContainer.value": function(node) {
375
+ if (!node.expression)
376
+ return;
377
+ for (const { className, position, reportNode } of extractClassNames(node.expression)) {
378
+ const replacement = findReplacement(className);
379
+ if (!replacement)
380
+ continue;
381
+ reportReplacement(className, replacement, reportNode, context, position);
382
+ }
254
383
  }
255
384
  });
256
385
  },
@@ -262,25 +391,25 @@ const noDeprecatedClasses = createEslintRule({
262
391
  },
263
392
  fixable: "code",
264
393
  messages: {
265
- replacedWith: `'{{ a }}' has been replaced with '{{ b }}'`
394
+ replacedWith: `'{{ className }}' has been replaced with '{{ replacement }}'`
266
395
  },
267
396
  schema: [],
268
397
  type: "problem"
269
398
  },
270
- name: RULE_NAME$1
399
+ name: RULE_NAME$3
271
400
  });
272
401
 
273
- const debug = createDebug("@rotki/eslint-plugin:no-deprecated-components");
274
- const RULE_NAME = "no-deprecated-components";
275
- const replacements = {
402
+ const debug$1 = createDebug("@rotki/eslint-plugin:no-deprecated-components");
403
+ const RULE_NAME$2 = "no-deprecated-components";
404
+ const replacements$1 = {
276
405
  DataTable: true,
277
406
  Fragment: false
278
407
  };
279
408
  const skipInLegacy = [
280
409
  "Fragment"
281
410
  ];
282
- function hasReplacement(tag) {
283
- return Object.prototype.hasOwnProperty.call(replacements, tag);
411
+ function hasReplacement$1(tag) {
412
+ return Object.prototype.hasOwnProperty.call(replacements$1, tag);
284
413
  }
285
414
  const noDeprecatedComponents = createEslintRule({
286
415
  create(context, optionsWithDefault) {
@@ -292,11 +421,11 @@ const noDeprecatedComponents = createEslintRule({
292
421
  const sourceCode = getSourceCode(context);
293
422
  if (!("getTemplateBodyTokenStore" in sourceCode.parserServices))
294
423
  throw new Error("cannot find getTemplateBodyTokenStore in parserServices");
295
- if (!hasReplacement(tag))
424
+ if (!hasReplacement$1(tag))
296
425
  return;
297
- const replacement = replacements[tag];
426
+ const replacement = replacements$1[tag];
298
427
  if (replacement || legacy && skipInLegacy.includes(tag)) {
299
- debug(`${tag} has been deprecated`);
428
+ debug$1(`${tag} has been deprecated`);
300
429
  context.report({
301
430
  data: {
302
431
  name: snakeCase(tag)
@@ -305,7 +434,7 @@ const noDeprecatedComponents = createEslintRule({
305
434
  node: element
306
435
  });
307
436
  } else {
308
- debug(`${tag} has will be removed`);
437
+ debug$1(`${tag} has will be removed`);
309
438
  context.report({
310
439
  data: {
311
440
  name: snakeCase(tag)
@@ -348,6 +477,109 @@ const noDeprecatedComponents = createEslintRule({
348
477
  ],
349
478
  type: "problem"
350
479
  },
480
+ name: RULE_NAME$2
481
+ });
482
+
483
+ const debug = createDebug("@rotki/eslint-plugin:no-deprecated-props");
484
+ const RULE_NAME$1 = "no-deprecated-props";
485
+ const replacements = {
486
+ RuiRadio: {
487
+ internalValue: "value"
488
+ }
489
+ };
490
+ function hasReplacement(tag) {
491
+ return Object.prototype.hasOwnProperty.call(replacements, tag);
492
+ }
493
+ function getPropName(node) {
494
+ if (node.directive) {
495
+ if (node.key.argument?.type !== "VIdentifier")
496
+ return void 0;
497
+ return kebabCase(node.key.argument.rawName);
498
+ }
499
+ return kebabCase(node.key.rawName);
500
+ }
501
+ const noDeprecatedProps = createEslintRule({
502
+ create(context) {
503
+ return defineTemplateBodyVisitor(context, {
504
+ VAttribute(node) {
505
+ if (node.directive && (node.value?.type === "VExpressionContainer" && (node.key.name.name !== "bind" || !node.key.argument)))
506
+ return;
507
+ const tag = pascalCase(node.parent.parent.rawName);
508
+ if (!hasReplacement(tag))
509
+ return;
510
+ debug(`${tag} has replacement properties`);
511
+ const propName = getPropName(node);
512
+ const propNameNode = node.directive ? node.key.argument : node.key;
513
+ if (!propName || !propNameNode) {
514
+ debug("could not get prop name and/or node");
515
+ return;
516
+ }
517
+ Object.entries(replacements[tag]).forEach(([prop, replacement]) => {
518
+ if (kebabCase(prop) === propName) {
519
+ debug(`preparing a replacement for ${tag}:${propName} -> ${replacement}`);
520
+ context.report({
521
+ data: {
522
+ prop,
523
+ replacement
524
+ },
525
+ fix(fixer) {
526
+ return fixer.replaceText(propNameNode, replacement);
527
+ },
528
+ messageId: "replacedWith",
529
+ node: propNameNode
530
+ });
531
+ }
532
+ });
533
+ }
534
+ });
535
+ },
536
+ defaultOptions: [],
537
+ meta: {
538
+ docs: {
539
+ description: "..."
540
+ },
541
+ fixable: "code",
542
+ messages: {
543
+ replacedWith: `'{{ prop }}' has been replaced with '{{ replacement }}'`
544
+ },
545
+ schema: [],
546
+ type: "problem"
547
+ },
548
+ name: RULE_NAME$1
549
+ });
550
+
551
+ const RULE_NAME = "no-legacy-library-import";
552
+ const legacyLibrary = "@rotki/ui-library-compat";
553
+ const newLibrary = "@rotki/ui-library";
554
+ const noLegacyLibraryImport = createEslintRule({
555
+ create(context) {
556
+ return {
557
+ ImportDeclaration(node) {
558
+ if (!node.source.value.startsWith(legacyLibrary))
559
+ return;
560
+ const replacement = node.source.value.replace(legacyLibrary, newLibrary);
561
+ context.report({
562
+ fix(fixer) {
563
+ return fixer.replaceText(node.source, `'${replacement}'`);
564
+ },
565
+ messageId: "replacedWith",
566
+ node: node.source
567
+ });
568
+ }
569
+ };
570
+ },
571
+ defaultOptions: [],
572
+ meta: {
573
+ docs: {
574
+ description: `Reports and replaces imports of ${legacyLibrary} with ${newLibrary}`
575
+ },
576
+ fixable: "code",
577
+ messages: {
578
+ replacedWith: `${legacyLibrary} has been replaced by ${newLibrary}`
579
+ },
580
+ schema: [],
581
+ type: "problem"
582
+ },
351
583
  name: RULE_NAME
352
584
  });
353
585
 
@@ -358,28 +590,13 @@ const plugin = {
358
590
  },
359
591
  rules: {
360
592
  "no-deprecated-classes": noDeprecatedClasses,
361
- "no-deprecated-components": noDeprecatedComponents
593
+ "no-deprecated-components": noDeprecatedComponents,
594
+ "no-deprecated-props": noDeprecatedProps,
595
+ "no-legacy-library-import": noLegacyLibraryImport
362
596
  }
363
597
  };
364
598
  const plugin$1 = plugin;
365
599
 
366
- function createRecommended(plugin, name, flat) {
367
- const rules = Object.fromEntries(Object.entries(plugin.rules).filter(([_key, rule]) => rule.meta.recommended === "recommended" && !rule.meta.deprecated).map(([key]) => [`${name}/${key}`, 2]));
368
- if (flat) {
369
- return {
370
- plugins: {
371
- [name]: plugin
372
- },
373
- rules
374
- };
375
- } else {
376
- return {
377
- plugins: [name],
378
- rules
379
- };
380
- }
381
- }
382
-
383
600
  const configs = {
384
601
  "recommended": createRecommended(plugin$1, "@rotki", false),
385
602
  "recommended-flat": createRecommended(plugin$1, "@rotki", true)
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@rotki/eslint-plugin",
3
- "version": "0.1.0",
4
- "packageManager": "pnpm@8.14.1",
3
+ "version": "0.2.1",
4
+ "packageManager": "pnpm@8.14.3",
5
5
  "type": "module",
6
6
  "license": "AGPL-3.0",
7
7
  "bugs": {
@@ -30,25 +30,26 @@
30
30
  "eslint": "^8.0.0 || ^9.0.0"
31
31
  },
32
32
  "dependencies": {
33
- "@typescript-eslint/utils": "6.19.0",
33
+ "@typescript-eslint/utils": "6.19.1",
34
34
  "debug": "4.3.4",
35
35
  "eslint-compat-utils": "0.4.1",
36
36
  "jsonc-eslint-parser": "2.4.0",
37
37
  "scule": "1.2.0",
38
- "vue-eslint-parser": "9.4.1",
38
+ "vue-eslint-parser": "9.4.2",
39
39
  "yaml-eslint-parser": "1.2.2"
40
40
  },
41
41
  "devDependencies": {
42
42
  "@commitlint/cli": "18.5.0",
43
43
  "@commitlint/config-conventional": "18.5.0",
44
- "@rotki/eslint-config": "2.3.0",
44
+ "@rotki/eslint-config": "2.4.1",
45
45
  "@types/debug": "4.1.12",
46
46
  "@types/eslint": "8.56.2",
47
47
  "@types/node": "20",
48
- "@typescript-eslint/eslint-plugin": "6.19.0",
49
- "@typescript-eslint/parser": "6.19.0",
50
- "@typescript-eslint/rule-tester": "6.19.0",
48
+ "@typescript-eslint/eslint-plugin": "6.19.1",
49
+ "@typescript-eslint/parser": "6.19.1",
50
+ "@typescript-eslint/rule-tester": "6.19.1",
51
51
  "bumpp": "9.3.0",
52
+ "debug": "4.3.4",
52
53
  "eslint": "8.56.0",
53
54
  "husky": "8.0.3",
54
55
  "lint-staged": "15.2.0",