@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/README.md +98 -1
- package/dist/index.d.mts +188 -0
- package/dist/index.d.ts +188 -0
- package/dist/index.mjs +1220 -87
- package/package.json +23 -23
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
|
|
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.
|
|
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
|
-
|
|
19
|
-
|
|
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
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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 (!
|
|
661
|
+
if (!FIXABLE_METHODS.has(callee.name))
|
|
151
662
|
return;
|
|
152
663
|
const name = callee.name;
|
|
153
|
-
debug$
|
|
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$
|
|
674
|
+
debug$6(`generating report for ${name}`);
|
|
164
675
|
if (!initializationTypeArguments && !declarationTypeArguments) {
|
|
165
676
|
if (allowInference) {
|
|
166
|
-
debug$
|
|
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,
|
|
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$
|
|
745
|
+
name: RULE_NAME$8
|
|
234
746
|
});
|
|
235
747
|
|
|
236
|
-
const RULE_NAME$
|
|
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$
|
|
808
|
+
name: RULE_NAME$7
|
|
297
809
|
});
|
|
298
810
|
|
|
299
|
-
const RULE_NAME$
|
|
300
|
-
const debug$
|
|
301
|
-
const
|
|
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
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
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$
|
|
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$
|
|
979
|
+
name: RULE_NAME$6
|
|
474
980
|
});
|
|
475
981
|
|
|
476
|
-
const debug$
|
|
477
|
-
const RULE_NAME$
|
|
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.
|
|
530
|
-
debug$
|
|
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$
|
|
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$
|
|
1088
|
+
name: RULE_NAME$5
|
|
583
1089
|
});
|
|
584
1090
|
|
|
585
|
-
const debug =
|
|
586
|
-
const RULE_NAME$
|
|
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
|
|
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
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
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$
|
|
1162
|
+
name: RULE_NAME$4
|
|
652
1163
|
});
|
|
653
1164
|
|
|
654
|
-
const RULE_NAME$
|
|
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
|
|
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$
|
|
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 });
|