@saasmakers/eslint 1.0.18 → 1.0.20
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/eslint.config.cjs +4 -3
- package/dist/eslint.config.d.cts +3 -2
- package/dist/eslint.config.d.mts +3 -2
- package/dist/eslint.config.d.ts +3 -2
- package/dist/eslint.config.mjs +4 -3
- package/dist/index.cjs +301 -573
- package/dist/index.d.cts +545 -2878
- package/dist/index.d.mts +545 -2878
- package/dist/index.d.ts +545 -2878
- package/dist/index.mjs +301 -573
- package/dist/shared/{eslint.DhFjwkxh.cjs → eslint.BqRQ4tAN.cjs} +2901 -2464
- package/dist/shared/{eslint.CohBuu1-.mjs → eslint.DOaqyhfZ.mjs} +2901 -2464
- package/package.json +7 -2
package/dist/index.mjs
CHANGED
|
@@ -1,139 +1,63 @@
|
|
|
1
|
-
import { d as distExports } from './shared/eslint.
|
|
1
|
+
import { d as distExports } from './shared/eslint.DOaqyhfZ.mjs';
|
|
2
2
|
import 'eslint';
|
|
3
3
|
import 'eslint/use-at-your-own-risk';
|
|
4
4
|
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
additionalProperties: false,
|
|
19
|
-
properties: { maxCharacters: { type: "number" } },
|
|
20
|
-
type: "object"
|
|
21
|
-
}
|
|
22
|
-
],
|
|
23
|
-
type: "layout"
|
|
24
|
-
},
|
|
25
|
-
create(context) {
|
|
26
|
-
const maxCharacters = context.options[0]?.maxCharacters ?? 100;
|
|
27
|
-
return {
|
|
28
|
-
ConditionalExpression(node) {
|
|
29
|
-
if (node.type !== distExports.AST_NODE_TYPES.ConditionalExpression) {
|
|
30
|
-
return;
|
|
31
|
-
}
|
|
32
|
-
const sourceCode = context.sourceCode;
|
|
33
|
-
const ternaryText = sourceCode.getText(node);
|
|
34
|
-
const isMultiline = ternaryText.includes("\n");
|
|
35
|
-
const isOverMaxLength = ternaryText.length > maxCharacters;
|
|
36
|
-
if (isOverMaxLength && !isMultiline) {
|
|
37
|
-
context.report({
|
|
38
|
-
fix: (fixer) => {
|
|
39
|
-
const [
|
|
40
|
-
test,
|
|
41
|
-
consequent,
|
|
42
|
-
alternate
|
|
43
|
-
] = [
|
|
44
|
-
node.test,
|
|
45
|
-
node.consequent,
|
|
46
|
-
node.alternate
|
|
47
|
-
].map((part) => sourceCode.getText(part));
|
|
48
|
-
return fixer.replaceText(node, `${test} ?
|
|
5
|
+
const defaultMaxCharacters = 100;
|
|
6
|
+
const defaultMaxLineLength = 100;
|
|
7
|
+
const defaultMinItems = 5;
|
|
8
|
+
function checkTernary(context, node, maxCharacters) {
|
|
9
|
+
const sourceCode = context.sourceCode;
|
|
10
|
+
const ternaryText = sourceCode.getText(node);
|
|
11
|
+
const isMultiline = ternaryText.includes("\n");
|
|
12
|
+
const isOverMaxLength = ternaryText.length > maxCharacters;
|
|
13
|
+
if (isOverMaxLength && !isMultiline) {
|
|
14
|
+
context.report({
|
|
15
|
+
fix: (fixer) => {
|
|
16
|
+
const [test, consequent, alternate] = [node.test, node.consequent, node.alternate].map((part) => sourceCode.getText(part));
|
|
17
|
+
return fixer.replaceText(node, `${test} ?
|
|
49
18
|
${consequent} :
|
|
50
19
|
${alternate}`);
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
};
|
|
20
|
+
},
|
|
21
|
+
messageId: "ternaryMultiline",
|
|
22
|
+
node
|
|
23
|
+
});
|
|
24
|
+
} else if (!isOverMaxLength && isMultiline) {
|
|
25
|
+
context.report({
|
|
26
|
+
fix: (fixer) => fixer.replaceText(node, ternaryText.replaceAll(/\s+/g, " ")),
|
|
27
|
+
messageId: "ternarySingleLine",
|
|
28
|
+
node
|
|
29
|
+
});
|
|
64
30
|
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
},
|
|
78
|
-
schema: [
|
|
79
|
-
{
|
|
80
|
-
additionalProperties: false,
|
|
81
|
-
properties: {
|
|
82
|
-
maxLineLength: { type: "number" },
|
|
83
|
-
minItems: { type: "number" }
|
|
84
|
-
},
|
|
85
|
-
type: "object"
|
|
86
|
-
}
|
|
87
|
-
],
|
|
88
|
-
type: "layout"
|
|
89
|
-
},
|
|
90
|
-
create(context) {
|
|
91
|
-
const minItems = context.options[0]?.minItems ?? 5;
|
|
92
|
-
const maxLineLength = context.options[0]?.maxLineLength ?? 100;
|
|
93
|
-
function reportAndFix(node, messageId, fixFunction) {
|
|
94
|
-
context.report({
|
|
95
|
-
fix: fixFunction,
|
|
96
|
-
messageId,
|
|
97
|
-
node
|
|
98
|
-
});
|
|
99
|
-
}
|
|
100
|
-
return {
|
|
101
|
-
// @ts-expect-error TSUnionType is not the right type
|
|
102
|
-
TSUnionType(node) {
|
|
103
|
-
const sourceCode = context.sourceCode;
|
|
104
|
-
const unionText = sourceCode.getText(node);
|
|
105
|
-
const isMultiline = unionText.includes("\n");
|
|
106
|
-
const isOverMaxLength = unionText.length > maxLineLength;
|
|
107
|
-
const hasEnoughItems = node.types.length >= minItems;
|
|
108
|
-
if ((isOverMaxLength || hasEnoughItems) && !isMultiline) {
|
|
109
|
-
reportAndFix(
|
|
110
|
-
node,
|
|
111
|
-
"multiline",
|
|
112
|
-
(fixer) => {
|
|
113
|
-
const types = node.types.map((type) => {
|
|
114
|
-
return sourceCode.getText(type);
|
|
115
|
-
});
|
|
116
|
-
return fixer.replaceText(node, `
|
|
31
|
+
}
|
|
32
|
+
function checkUnion(context, node, maxLineLength, minItems) {
|
|
33
|
+
const sourceCode = context.sourceCode;
|
|
34
|
+
const unionText = sourceCode.getText(node);
|
|
35
|
+
const isMultiline = unionText.includes("\n");
|
|
36
|
+
const isOverMaxLength = unionText.length > maxLineLength;
|
|
37
|
+
const hasEnoughItems = node.types.length >= minItems;
|
|
38
|
+
if ((isOverMaxLength || hasEnoughItems) && !isMultiline) {
|
|
39
|
+
context.report({
|
|
40
|
+
fix: (fixer) => {
|
|
41
|
+
const types = node.types.map((type) => sourceCode.getText(type));
|
|
42
|
+
return fixer.replaceText(node, `
|
|
117
43
|
| ${types.join("\n | ")}`);
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
};
|
|
44
|
+
},
|
|
45
|
+
messageId: "unionMultiline",
|
|
46
|
+
node
|
|
47
|
+
});
|
|
48
|
+
} else if (!isOverMaxLength && !hasEnoughItems && isMultiline) {
|
|
49
|
+
context.report({
|
|
50
|
+
fix: (fixer) => {
|
|
51
|
+
let singleLineText = unionText.replaceAll(/[ \t\n]+/g, " ");
|
|
52
|
+
singleLineText = singleLineText.replaceAll(" |", "|").replaceAll("|", " |");
|
|
53
|
+
singleLineText = singleLineText.replaceAll(" ", " ");
|
|
54
|
+
return fixer.replaceText(node, singleLineText);
|
|
55
|
+
},
|
|
56
|
+
messageId: "unionSingleLine",
|
|
57
|
+
node
|
|
58
|
+
});
|
|
134
59
|
}
|
|
135
|
-
}
|
|
136
|
-
|
|
60
|
+
}
|
|
137
61
|
function collapseBlankLine(text) {
|
|
138
62
|
return text.replace(/\n[^\S\n]*\n[^\S\n]*/, "\n");
|
|
139
63
|
}
|
|
@@ -191,7 +115,7 @@ function getBlankLineRequirement(prevNode, nextNode, sourceCode) {
|
|
|
191
115
|
return null;
|
|
192
116
|
}
|
|
193
117
|
if (hasLineCommentBetween(prevNode, nextNode, sourceCode)) {
|
|
194
|
-
return
|
|
118
|
+
return null;
|
|
195
119
|
}
|
|
196
120
|
const nextKind = getStatementKind(nextNode);
|
|
197
121
|
if (!nextKind) {
|
|
@@ -327,21 +251,38 @@ function isThisReceiver(node) {
|
|
|
327
251
|
function isVoidExpression(expression) {
|
|
328
252
|
return expression.type === distExports.AST_NODE_TYPES.UnaryExpression && expression.operator === "void";
|
|
329
253
|
}
|
|
330
|
-
const rule$
|
|
331
|
-
defaultOptions: [],
|
|
254
|
+
const rule$4 = {
|
|
255
|
+
defaultOptions: [{}],
|
|
332
256
|
meta: {
|
|
333
|
-
docs: { description: "
|
|
257
|
+
docs: { description: "Format TypeScript layout: single/multiline ternaries and union types, and blank lines between statements" },
|
|
334
258
|
fixable: "whitespace",
|
|
335
259
|
messages: {
|
|
336
260
|
expectedBlankLine: "Expected blank line before this statement.",
|
|
337
261
|
expectedBlankLineBeforeComment: "Expected blank line before this comment.",
|
|
338
|
-
|
|
262
|
+
ternaryMultiline: "Ternary expression should be multiline when it exceeds max characters.",
|
|
263
|
+
ternarySingleLine: "Ternary expression should be single-line when it is under max characters.",
|
|
264
|
+
unexpectedBlankLine: "Unexpected blank line before this statement.",
|
|
265
|
+
unionMultiline: "Union type should be multiline when it exceeds max length or has too many items.",
|
|
266
|
+
unionSingleLine: "Union type should be single-line when it is under max length and has few items."
|
|
339
267
|
},
|
|
340
|
-
schema: [
|
|
268
|
+
schema: [
|
|
269
|
+
{
|
|
270
|
+
additionalProperties: false,
|
|
271
|
+
properties: {
|
|
272
|
+
maxCharacters: { type: "number" },
|
|
273
|
+
maxLineLength: { type: "number" },
|
|
274
|
+
minItems: { type: "number" }
|
|
275
|
+
},
|
|
276
|
+
type: "object"
|
|
277
|
+
}
|
|
278
|
+
],
|
|
341
279
|
type: "layout"
|
|
342
280
|
},
|
|
343
281
|
create(context) {
|
|
344
282
|
const sourceCode = context.sourceCode;
|
|
283
|
+
const maxCharacters = context.options[0]?.maxCharacters ?? defaultMaxCharacters;
|
|
284
|
+
const maxLineLength = context.options[0]?.maxLineLength ?? defaultMaxLineLength;
|
|
285
|
+
const minItems = context.options[0]?.minItems ?? defaultMinItems;
|
|
345
286
|
let scopeInfo = null;
|
|
346
287
|
function enterScope() {
|
|
347
288
|
scopeInfo = {
|
|
@@ -393,7 +334,7 @@ const rule$a = {
|
|
|
393
334
|
return;
|
|
394
335
|
}
|
|
395
336
|
const previousLine = sourceCode.lines[commentLine - 2] ?? "";
|
|
396
|
-
if (previousLine.trim() === "" || previousLine.trimEnd().endsWith("{")) {
|
|
337
|
+
if (previousLine.trim() === "" || previousLine.trimEnd().endsWith("{") || previousLine.trimStart().startsWith("//")) {
|
|
397
338
|
return;
|
|
398
339
|
}
|
|
399
340
|
context.report({
|
|
@@ -440,13 +381,19 @@ const rule$a = {
|
|
|
440
381
|
":statement": verify,
|
|
441
382
|
"BlockStatement": enterScope,
|
|
442
383
|
"BlockStatement:exit": exitScope,
|
|
443
|
-
"
|
|
384
|
+
"ConditionalExpression": (node) => checkTernary(context, node, maxCharacters),
|
|
444
385
|
"Program": enterScope,
|
|
445
|
-
"Program:exit":
|
|
386
|
+
"Program:exit": function() {
|
|
387
|
+
for (const comment of sourceCode.getAllComments()) {
|
|
388
|
+
verifyComment(comment);
|
|
389
|
+
}
|
|
390
|
+
exitScope();
|
|
391
|
+
},
|
|
446
392
|
"StaticBlock": enterScope,
|
|
447
393
|
"StaticBlock:exit": exitScope,
|
|
448
394
|
"SwitchCase": verifyThenEnterScope,
|
|
449
|
-
"SwitchCase:exit": exitScope
|
|
395
|
+
"SwitchCase:exit": exitScope,
|
|
396
|
+
"TSUnionType": (node) => checkUnion(context, node, maxLineLength, minItems)
|
|
450
397
|
};
|
|
451
398
|
}
|
|
452
399
|
};
|
|
@@ -480,8 +427,9 @@ function getSpecificName(testName) {
|
|
|
480
427
|
return parts.slice(specialIndex + 1).join(".");
|
|
481
428
|
}
|
|
482
429
|
function getTestName(node) {
|
|
483
|
-
|
|
484
|
-
|
|
430
|
+
const firstArgument = node.arguments[0];
|
|
431
|
+
if (firstArgument?.type === distExports.AST_NODE_TYPES.Literal && typeof firstArgument.value === "string") {
|
|
432
|
+
return firstArgument.value;
|
|
485
433
|
}
|
|
486
434
|
return "";
|
|
487
435
|
}
|
|
@@ -498,12 +446,10 @@ function getTestPriority(testName) {
|
|
|
498
446
|
return 1;
|
|
499
447
|
}
|
|
500
448
|
}
|
|
501
|
-
const rule$
|
|
449
|
+
const rule$3 = {
|
|
450
|
+
defaultOptions: [],
|
|
502
451
|
meta: {
|
|
503
|
-
docs: {
|
|
504
|
-
description: "Enforce sorted test functions grouped by method with sorted metrics, errors, exceptions and middlewares",
|
|
505
|
-
recommended: true
|
|
506
|
-
},
|
|
452
|
+
docs: { description: "Enforce sorted test functions grouped by method with sorted metrics, errors, exceptions and middlewares" },
|
|
507
453
|
fixable: "code",
|
|
508
454
|
messages: { sortError: "Test functions should be grouped by method with sorted metrics, errors, exceptions and middlewares." },
|
|
509
455
|
schema: [],
|
|
@@ -519,15 +465,15 @@ const rule$9 = {
|
|
|
519
465
|
return;
|
|
520
466
|
}
|
|
521
467
|
const groupCallback = node.arguments[1];
|
|
522
|
-
if (groupCallback.type !==
|
|
468
|
+
if (groupCallback.type !== distExports.AST_NODE_TYPES.FunctionExpression && groupCallback.type !== distExports.AST_NODE_TYPES.ArrowFunctionExpression) {
|
|
523
469
|
return;
|
|
524
470
|
}
|
|
525
471
|
const callbackBody = groupCallback.body;
|
|
526
|
-
if (callbackBody.type !==
|
|
472
|
+
if (callbackBody.type !== distExports.AST_NODE_TYPES.BlockStatement || !callbackBody.body) {
|
|
527
473
|
return;
|
|
528
474
|
}
|
|
529
475
|
const tests = callbackBody.body.filter((statement) => {
|
|
530
|
-
return statement.type ===
|
|
476
|
+
return statement.type === distExports.AST_NODE_TYPES.ExpressionStatement && statement.expression.type === distExports.AST_NODE_TYPES.CallExpression && statement.expression.callee.type === distExports.AST_NODE_TYPES.Identifier && (statement.expression.callee.name === "test" || statement.expression.callee.name === "it");
|
|
531
477
|
});
|
|
532
478
|
const testNames = tests.map((test) => getTestName(test.expression));
|
|
533
479
|
const sortedTestNames = [...testNames].toSorted(compareTests);
|
|
@@ -567,63 +513,68 @@ const rule$9 = {
|
|
|
567
513
|
}
|
|
568
514
|
};
|
|
569
515
|
|
|
570
|
-
|
|
516
|
+
const defaultLocales = ["en", "fr"];
|
|
517
|
+
const indentSpaces = 2;
|
|
518
|
+
const i18nRegex = /(<i18n\s+lang=["']json["']>)([\s\S]*?)<\/i18n>/i;
|
|
519
|
+
const templateRegex$1 = /<template>([\s\S]*)<\/template>/i;
|
|
520
|
+
const scriptRegex = /<script[^>]*>([\s\S]*?)<\/script>/i;
|
|
521
|
+
function checkInvalidLocales(context, parsed, locales, content, contentOffset) {
|
|
571
522
|
for (const locale of Object.keys(parsed)) {
|
|
572
|
-
if (
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
}
|
|
523
|
+
if (locales.includes(locale)) {
|
|
524
|
+
continue;
|
|
525
|
+
}
|
|
526
|
+
const localeMatch = new RegExp(String.raw`"${escapeRegExp(locale)}"\s*:`, "g").exec(content);
|
|
527
|
+
if (localeMatch) {
|
|
528
|
+
const localeOffset = contentOffset + localeMatch.index;
|
|
529
|
+
context.report({
|
|
530
|
+
data: {
|
|
531
|
+
allowed: locales.join(", "),
|
|
532
|
+
locale
|
|
533
|
+
},
|
|
534
|
+
loc: {
|
|
535
|
+
end: context.sourceCode.getLocFromIndex(localeOffset + locale.length + 2),
|
|
536
|
+
start: context.sourceCode.getLocFromIndex(localeOffset)
|
|
537
|
+
},
|
|
538
|
+
messageId: "invalidLocale"
|
|
539
|
+
});
|
|
589
540
|
}
|
|
590
541
|
}
|
|
591
542
|
}
|
|
592
|
-
function checkMissingLocales(context, parsed, locales,
|
|
543
|
+
function checkMissingLocales(context, parsed, locales, content, contentOffset) {
|
|
593
544
|
for (const locale of locales) {
|
|
594
545
|
if (!parsed[locale]) {
|
|
595
546
|
context.report({
|
|
596
547
|
data: { locale },
|
|
597
548
|
loc: {
|
|
598
|
-
end: context.sourceCode.getLocFromIndex(
|
|
599
|
-
start: context.sourceCode.getLocFromIndex(
|
|
549
|
+
end: context.sourceCode.getLocFromIndex(contentOffset + content.length),
|
|
550
|
+
start: context.sourceCode.getLocFromIndex(contentOffset)
|
|
600
551
|
},
|
|
601
552
|
messageId: "missingLocale"
|
|
602
553
|
});
|
|
603
554
|
}
|
|
604
555
|
}
|
|
605
556
|
}
|
|
606
|
-
function checkMissingTranslations(context, parsed, locales,
|
|
557
|
+
function checkMissingTranslations(context, parsed, locales, content, contentOffset) {
|
|
607
558
|
const allKeys = /* @__PURE__ */ new Set();
|
|
608
559
|
for (const locale of locales) {
|
|
609
560
|
if (parsed[locale]) {
|
|
610
|
-
const
|
|
611
|
-
|
|
561
|
+
for (const key of getAllKeys(parsed[locale])) {
|
|
562
|
+
allKeys.add(key);
|
|
563
|
+
}
|
|
612
564
|
}
|
|
613
565
|
}
|
|
614
566
|
for (const locale of locales) {
|
|
615
567
|
if (!parsed[locale]) {
|
|
616
568
|
continue;
|
|
617
569
|
}
|
|
618
|
-
const localeKeys = getAllKeys
|
|
570
|
+
const localeKeys = getAllKeys(parsed[locale]);
|
|
619
571
|
const missingKeys = [...allKeys].filter((key) => !localeKeys.includes(key));
|
|
620
572
|
if (missingKeys.length === 0) {
|
|
621
573
|
continue;
|
|
622
574
|
}
|
|
623
|
-
const
|
|
624
|
-
const localeMatch = new RegExp(String.raw`"${escapedLocale}"\s*:\s*\{`, "g").exec(i18nContent);
|
|
575
|
+
const localeMatch = new RegExp(String.raw`"${escapeRegExp(locale)}"\s*:\s*\{`, "g").exec(content);
|
|
625
576
|
if (localeMatch) {
|
|
626
|
-
const localeOffset =
|
|
577
|
+
const localeOffset = contentOffset + localeMatch.index;
|
|
627
578
|
context.report({
|
|
628
579
|
data: {
|
|
629
580
|
locale,
|
|
@@ -638,152 +589,21 @@ function checkMissingTranslations(context, parsed, locales, startOffset, i18nCon
|
|
|
638
589
|
}
|
|
639
590
|
}
|
|
640
591
|
}
|
|
641
|
-
function
|
|
592
|
+
function escapeRegExp(value) {
|
|
593
|
+
return value.replaceAll(/[.*+?^${}()|[\]\\]/g, String.raw`\$&`);
|
|
594
|
+
}
|
|
595
|
+
function getAllKeys(object, prefix = "") {
|
|
642
596
|
let keys = [];
|
|
643
597
|
for (const key in object) {
|
|
644
598
|
const newPrefix = prefix ? `${prefix}.${key}` : key;
|
|
645
599
|
if (typeof object[key] === "object" && object[key] !== null) {
|
|
646
|
-
keys = [...keys, ...getAllKeys
|
|
600
|
+
keys = [...keys, ...getAllKeys(object[key], newPrefix)];
|
|
647
601
|
} else {
|
|
648
602
|
keys.push(newPrefix);
|
|
649
603
|
}
|
|
650
604
|
}
|
|
651
605
|
return keys;
|
|
652
606
|
}
|
|
653
|
-
const rule$8 = {
|
|
654
|
-
meta: {
|
|
655
|
-
docs: {
|
|
656
|
-
description: "Enforce consistent i18n locale keys across translations",
|
|
657
|
-
recommended: true
|
|
658
|
-
},
|
|
659
|
-
messages: {
|
|
660
|
-
invalidJson: "Invalid JSON in i18n block: {{error}}",
|
|
661
|
-
invalidLocale: "Invalid locale: {{locale}}. Allowed locales are: {{allowed}}",
|
|
662
|
-
missingLocale: "Missing required locale: {{locale}}",
|
|
663
|
-
missingTranslations: 'Missing translations in "{{locale}}" locale: {{missing}}'
|
|
664
|
-
},
|
|
665
|
-
schema: [
|
|
666
|
-
{
|
|
667
|
-
additionalProperties: false,
|
|
668
|
-
properties: {
|
|
669
|
-
locales: {
|
|
670
|
-
items: { type: "string" },
|
|
671
|
-
type: "array"
|
|
672
|
-
}
|
|
673
|
-
},
|
|
674
|
-
type: "object"
|
|
675
|
-
}
|
|
676
|
-
],
|
|
677
|
-
type: "problem"
|
|
678
|
-
},
|
|
679
|
-
create(context) {
|
|
680
|
-
if (!context.filename.endsWith(".vue")) {
|
|
681
|
-
return {};
|
|
682
|
-
}
|
|
683
|
-
const locales = context.options[0]?.locales || ["en", "fr"];
|
|
684
|
-
return {
|
|
685
|
-
Program() {
|
|
686
|
-
const source = context.sourceCode.getText();
|
|
687
|
-
const i18nRegex = /<i18n\s+lang=["']json["']>([\s\S]*?)<\/i18n>/i;
|
|
688
|
-
const i18nMatch = i18nRegex.exec(source);
|
|
689
|
-
if (!i18nMatch) {
|
|
690
|
-
return;
|
|
691
|
-
}
|
|
692
|
-
const tagLength = source.indexOf(">", i18nMatch.index) + 1 - i18nMatch.index;
|
|
693
|
-
const startOffset = i18nMatch.index + tagLength;
|
|
694
|
-
const i18nContent = i18nMatch[1].trim();
|
|
695
|
-
try {
|
|
696
|
-
const parsed = JSON.parse(i18nContent);
|
|
697
|
-
checkMissingLocales(context, parsed, locales, startOffset, i18nContent);
|
|
698
|
-
checkInvalidLocales(context, parsed, locales, startOffset, i18nContent);
|
|
699
|
-
checkMissingTranslations(context, parsed, locales, startOffset, i18nContent);
|
|
700
|
-
} catch (error) {
|
|
701
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
702
|
-
context.report({
|
|
703
|
-
data: { error: errorMessage },
|
|
704
|
-
loc: {
|
|
705
|
-
end: context.sourceCode.getLocFromIndex(startOffset + i18nContent.length),
|
|
706
|
-
start: context.sourceCode.getLocFromIndex(startOffset)
|
|
707
|
-
},
|
|
708
|
-
messageId: "invalidJson"
|
|
709
|
-
});
|
|
710
|
-
}
|
|
711
|
-
}
|
|
712
|
-
};
|
|
713
|
-
}
|
|
714
|
-
};
|
|
715
|
-
|
|
716
|
-
const rule$7 = {
|
|
717
|
-
meta: {
|
|
718
|
-
docs: {
|
|
719
|
-
description: "Enforce using t() instead of $t() in Vue templates",
|
|
720
|
-
recommended: true
|
|
721
|
-
},
|
|
722
|
-
fixable: "code",
|
|
723
|
-
messages: { useT: "Use t() instead of $t() in Vue templates as it does not work with <i18n> tags." },
|
|
724
|
-
schema: [],
|
|
725
|
-
type: "problem"
|
|
726
|
-
},
|
|
727
|
-
create(context) {
|
|
728
|
-
if (!context.filename.endsWith(".vue")) {
|
|
729
|
-
return {};
|
|
730
|
-
}
|
|
731
|
-
return {
|
|
732
|
-
Program() {
|
|
733
|
-
const sourceCode = context.sourceCode;
|
|
734
|
-
const source = sourceCode.getText();
|
|
735
|
-
const templateRegex = /<template>([\s\S]*)<\/template>/i;
|
|
736
|
-
const templateMatch = templateRegex.exec(source);
|
|
737
|
-
if (!templateMatch) {
|
|
738
|
-
return;
|
|
739
|
-
}
|
|
740
|
-
const templateContent = templateMatch[1];
|
|
741
|
-
const tPatterns = [
|
|
742
|
-
{
|
|
743
|
-
pattern: / \$t\(/g,
|
|
744
|
-
quote: " "
|
|
745
|
-
},
|
|
746
|
-
{
|
|
747
|
-
pattern: /"\$t\(/g,
|
|
748
|
-
quote: '"'
|
|
749
|
-
},
|
|
750
|
-
{
|
|
751
|
-
pattern: /`\$t\(/g,
|
|
752
|
-
quote: "`"
|
|
753
|
-
},
|
|
754
|
-
{
|
|
755
|
-
pattern: /\{\$t\(/g,
|
|
756
|
-
quote: "{"
|
|
757
|
-
},
|
|
758
|
-
{
|
|
759
|
-
pattern: /\[\$t\(/g,
|
|
760
|
-
quote: "["
|
|
761
|
-
}
|
|
762
|
-
];
|
|
763
|
-
for (const { pattern, quote } of tPatterns) {
|
|
764
|
-
let match = pattern.exec(templateContent);
|
|
765
|
-
while (match !== null) {
|
|
766
|
-
const templateStart = templateMatch.index + templateMatch[0].indexOf(templateContent);
|
|
767
|
-
const start = templateStart + match.index;
|
|
768
|
-
const end = start + 4;
|
|
769
|
-
context.report({
|
|
770
|
-
fix(fixer) {
|
|
771
|
-
return fixer.replaceTextRange([start, end], `${quote}t(`);
|
|
772
|
-
},
|
|
773
|
-
loc: {
|
|
774
|
-
end: sourceCode.getLocFromIndex(end),
|
|
775
|
-
start: sourceCode.getLocFromIndex(start)
|
|
776
|
-
},
|
|
777
|
-
messageId: "useT"
|
|
778
|
-
});
|
|
779
|
-
match = pattern.exec(templateContent);
|
|
780
|
-
}
|
|
781
|
-
}
|
|
782
|
-
}
|
|
783
|
-
};
|
|
784
|
-
}
|
|
785
|
-
};
|
|
786
|
-
|
|
787
607
|
function sortObjectKeys(object) {
|
|
788
608
|
if (Array.isArray(object)) {
|
|
789
609
|
return object.map((item) => sortObjectKeys(item));
|
|
@@ -791,214 +611,135 @@ function sortObjectKeys(object) {
|
|
|
791
611
|
if (object && typeof object === "object") {
|
|
792
612
|
const sortedKeys = Object.keys(object).toSorted((key1, key2) => key1.localeCompare(key2));
|
|
793
613
|
const result = {};
|
|
794
|
-
const
|
|
614
|
+
const record = object;
|
|
795
615
|
for (const key of sortedKeys) {
|
|
796
|
-
result[key] = sortObjectKeys(
|
|
616
|
+
result[key] = sortObjectKeys(record[key]);
|
|
797
617
|
}
|
|
798
618
|
return result;
|
|
799
619
|
}
|
|
800
620
|
return object;
|
|
801
621
|
}
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
622
|
+
function checkSortAndIndent(context, parsed, rawContent, contentOffset) {
|
|
623
|
+
const sortedParsed = sortObjectKeys(parsed);
|
|
624
|
+
const formattedContent = `
|
|
625
|
+
${JSON.stringify(sortedParsed, void 0, indentSpaces).replaceAll(/\n{2,}/g, "\n")}
|
|
626
|
+
`;
|
|
627
|
+
if (formattedContent.trim() === rawContent.trim()) {
|
|
628
|
+
return;
|
|
629
|
+
}
|
|
630
|
+
const currentKeys = JSON.stringify(parsed, void 0, indentSpaces).trim();
|
|
631
|
+
const sortedKeys = JSON.stringify(sortedParsed, void 0, indentSpaces).trim();
|
|
632
|
+
context.report({
|
|
633
|
+
data: { expected: String(indentSpaces) },
|
|
634
|
+
fix: (fixer) => fixer.replaceTextRange([contentOffset, contentOffset + rawContent.length], formattedContent),
|
|
635
|
+
loc: {
|
|
636
|
+
end: context.sourceCode.getLocFromIndex(contentOffset + rawContent.length),
|
|
637
|
+
start: context.sourceCode.getLocFromIndex(contentOffset)
|
|
813
638
|
},
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
639
|
+
messageId: currentKeys === sortedKeys ? "indentError" : "sortError"
|
|
640
|
+
});
|
|
641
|
+
}
|
|
642
|
+
function checkUnusedStrings(context, parsed, source, content, contentOffset) {
|
|
643
|
+
if (!parsed.en) {
|
|
644
|
+
return;
|
|
645
|
+
}
|
|
646
|
+
const templateContent = templateRegex$1.exec(source)?.[1] ?? "";
|
|
647
|
+
const scriptContent = scriptRegex.exec(source)?.[1] ?? "";
|
|
648
|
+
for (const key of getAllKeys(parsed.en)) {
|
|
649
|
+
if (templateContent.includes(key) || scriptContent.includes(key)) {
|
|
650
|
+
continue;
|
|
820
651
|
}
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
const sortedParsed = sortObjectKeys(parsed);
|
|
833
|
-
const formattedContent = `
|
|
834
|
-
${JSON.stringify(sortedParsed, void 0, totalSpaces).replaceAll(/\n{2,}/g, "\n")}
|
|
835
|
-
`;
|
|
836
|
-
if (formattedContent.trim() !== i18nContent.trim()) {
|
|
837
|
-
const currentKeys = JSON.stringify(parsed, void 0, totalSpaces).trim();
|
|
838
|
-
const sortedKeys = JSON.stringify(sortedParsed, void 0, totalSpaces).trim();
|
|
839
|
-
context.report({
|
|
840
|
-
data: { expected: String(totalSpaces) },
|
|
841
|
-
fix(fixer) {
|
|
842
|
-
return fixer.replaceTextRange(
|
|
843
|
-
[startOffset, startOffset + i18nContent.length],
|
|
844
|
-
formattedContent
|
|
845
|
-
);
|
|
846
|
-
},
|
|
847
|
-
loc: {
|
|
848
|
-
end: context.sourceCode.getLocFromIndex(startOffset + i18nContent.length),
|
|
849
|
-
start: context.sourceCode.getLocFromIndex(startOffset)
|
|
850
|
-
},
|
|
851
|
-
messageId: currentKeys === sortedKeys ? "indentError" : "sortError"
|
|
852
|
-
});
|
|
853
|
-
}
|
|
854
|
-
} catch (error) {
|
|
855
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
856
|
-
context.report({
|
|
857
|
-
data: { error: errorMessage },
|
|
858
|
-
loc: {
|
|
859
|
-
end: context.sourceCode.getLocFromIndex(startOffset + i18nContent.length),
|
|
860
|
-
start: context.sourceCode.getLocFromIndex(startOffset)
|
|
861
|
-
},
|
|
862
|
-
messageId: "invalidJson"
|
|
863
|
-
});
|
|
864
|
-
}
|
|
865
|
-
}
|
|
866
|
-
}
|
|
867
|
-
};
|
|
868
|
-
}
|
|
869
|
-
};
|
|
870
|
-
|
|
871
|
-
function getAllKeys(object, prefix = "") {
|
|
872
|
-
let keys = [];
|
|
873
|
-
for (const key in object) {
|
|
874
|
-
const newPrefix = prefix ? `${prefix}.${key}` : key;
|
|
875
|
-
if (typeof object[key] === "object" && object[key] !== null) {
|
|
876
|
-
keys = [...keys, ...getAllKeys(object[key], newPrefix)];
|
|
877
|
-
} else {
|
|
878
|
-
keys.push(newPrefix);
|
|
652
|
+
const keyMatch = new RegExp(String.raw`"${escapeRegExp(key)}"\s*:`, "g").exec(content);
|
|
653
|
+
if (keyMatch) {
|
|
654
|
+
const keyOffset = contentOffset + keyMatch.index;
|
|
655
|
+
context.report({
|
|
656
|
+
data: { key },
|
|
657
|
+
loc: {
|
|
658
|
+
end: context.sourceCode.getLocFromIndex(keyOffset + key.length + 2),
|
|
659
|
+
start: context.sourceCode.getLocFromIndex(keyOffset)
|
|
660
|
+
},
|
|
661
|
+
messageId: "unusedString"
|
|
662
|
+
});
|
|
879
663
|
}
|
|
880
664
|
}
|
|
881
|
-
return keys;
|
|
882
665
|
}
|
|
883
|
-
const rule$
|
|
666
|
+
const rule$2 = {
|
|
667
|
+
defaultOptions: [{ locales: defaultLocales }],
|
|
884
668
|
meta: {
|
|
885
|
-
docs: {
|
|
886
|
-
|
|
887
|
-
recommended: true
|
|
888
|
-
},
|
|
669
|
+
docs: { description: "Format Vue i18n blocks: enforce valid/consistent locales, sorted keys, and no unused strings" },
|
|
670
|
+
fixable: "code",
|
|
889
671
|
messages: {
|
|
672
|
+
indentError: "Invalid indentation for i18n content. Expected {{expected}} spaces.",
|
|
890
673
|
invalidJson: "Invalid JSON in i18n block: {{error}}",
|
|
674
|
+
invalidLocale: "Invalid locale: {{locale}}. Allowed locales are: {{allowed}}",
|
|
675
|
+
missingLocale: "Missing required locale: {{locale}}",
|
|
676
|
+
missingTranslations: 'Missing translations in "{{locale}}" locale: {{missing}}',
|
|
677
|
+
sortError: "Keys in i18n block should be sorted alphabetically.",
|
|
891
678
|
unusedString: 'Unused string in English locale: "{{key}}"'
|
|
892
679
|
},
|
|
893
|
-
schema: [
|
|
680
|
+
schema: [
|
|
681
|
+
{
|
|
682
|
+
additionalProperties: false,
|
|
683
|
+
properties: {
|
|
684
|
+
locales: {
|
|
685
|
+
items: { type: "string" },
|
|
686
|
+
type: "array"
|
|
687
|
+
}
|
|
688
|
+
},
|
|
689
|
+
type: "object"
|
|
690
|
+
}
|
|
691
|
+
],
|
|
894
692
|
type: "problem"
|
|
895
693
|
},
|
|
896
694
|
create(context) {
|
|
897
695
|
if (!context.filename.endsWith(".vue")) {
|
|
898
696
|
return {};
|
|
899
697
|
}
|
|
698
|
+
const locales = context.options[0]?.locales ?? defaultLocales;
|
|
900
699
|
return {
|
|
901
700
|
Program() {
|
|
902
701
|
const source = context.sourceCode.getText();
|
|
903
|
-
const i18nRegex = /(<i18n\s+lang=["']json["']>)([\s\S]*?)<\/i18n>/i;
|
|
904
702
|
const i18nMatch = i18nRegex.exec(source);
|
|
905
703
|
if (!i18nMatch) {
|
|
906
704
|
return;
|
|
907
705
|
}
|
|
908
|
-
const
|
|
909
|
-
const
|
|
706
|
+
const contentOffset = i18nMatch.index + i18nMatch[1].length;
|
|
707
|
+
const rawContent = i18nMatch[2];
|
|
708
|
+
const trimmedContent = rawContent.trim();
|
|
709
|
+
let parsed;
|
|
910
710
|
try {
|
|
911
|
-
|
|
912
|
-
if (!parsed.en) {
|
|
913
|
-
return;
|
|
914
|
-
}
|
|
915
|
-
const templateRegex = /<template>([\s\S]*)<\/template>/i;
|
|
916
|
-
const templateMatch = templateRegex.exec(source);
|
|
917
|
-
const templateContent = templateMatch ? templateMatch[1] : "";
|
|
918
|
-
const scriptRegex = /<script[^>]*>([\s\S]*?)<\/script>/i;
|
|
919
|
-
const scriptMatch = scriptRegex.exec(source);
|
|
920
|
-
const scriptContent = scriptMatch ? scriptMatch[1] : "";
|
|
921
|
-
const enKeys = getAllKeys(parsed.en);
|
|
922
|
-
for (const key of enKeys) {
|
|
923
|
-
const isUsed = templateContent.includes(key) || scriptContent.includes(key);
|
|
924
|
-
if (!isUsed) {
|
|
925
|
-
const keyMatch = new RegExp(String.raw`"${key}"\s*:`, "g").exec(i18nContent);
|
|
926
|
-
if (keyMatch) {
|
|
927
|
-
const keyOffset = startOffset + keyMatch.index;
|
|
928
|
-
context.report({
|
|
929
|
-
data: { key },
|
|
930
|
-
loc: {
|
|
931
|
-
end: context.sourceCode.getLocFromIndex(keyOffset + key.length + 2),
|
|
932
|
-
start: context.sourceCode.getLocFromIndex(keyOffset)
|
|
933
|
-
},
|
|
934
|
-
messageId: "unusedString"
|
|
935
|
-
});
|
|
936
|
-
}
|
|
937
|
-
}
|
|
938
|
-
}
|
|
711
|
+
parsed = JSON.parse(trimmedContent);
|
|
939
712
|
} catch (error) {
|
|
940
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
941
713
|
context.report({
|
|
942
|
-
data: { error:
|
|
714
|
+
data: { error: error instanceof Error ? error.message : String(error) },
|
|
943
715
|
loc: {
|
|
944
|
-
end: context.sourceCode.getLocFromIndex(
|
|
945
|
-
start: context.sourceCode.getLocFromIndex(
|
|
716
|
+
end: context.sourceCode.getLocFromIndex(contentOffset + rawContent.length),
|
|
717
|
+
start: context.sourceCode.getLocFromIndex(contentOffset)
|
|
946
718
|
},
|
|
947
719
|
messageId: "invalidJson"
|
|
948
720
|
});
|
|
721
|
+
return;
|
|
949
722
|
}
|
|
723
|
+
checkMissingLocales(context, parsed, locales, rawContent, contentOffset);
|
|
724
|
+
checkInvalidLocales(context, parsed, locales, rawContent, contentOffset);
|
|
725
|
+
checkMissingTranslations(context, parsed, locales, rawContent, contentOffset);
|
|
726
|
+
checkSortAndIndent(context, parsed, rawContent, contentOffset);
|
|
727
|
+
checkUnusedStrings(context, parsed, source, rawContent, contentOffset);
|
|
950
728
|
}
|
|
951
729
|
};
|
|
952
730
|
}
|
|
953
731
|
};
|
|
954
732
|
|
|
955
|
-
const
|
|
733
|
+
const untypedEmitsRegex = /const\s+emit\s*=\s*defineEmits\(\[([^\]]+)\]\)/g;
|
|
734
|
+
const rule$1 = {
|
|
956
735
|
defaultOptions: [],
|
|
957
736
|
meta: {
|
|
958
|
-
docs: { description: "
|
|
737
|
+
docs: { description: "Format Vue component scripts: enforce multiline computed properties and typed emits" },
|
|
959
738
|
fixable: "code",
|
|
960
|
-
messages: {
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
},
|
|
964
|
-
create(context) {
|
|
965
|
-
return {
|
|
966
|
-
CallExpression(node) {
|
|
967
|
-
if (node.callee.type === distExports.AST_NODE_TYPES.Identifier && node.callee.name === "computed") {
|
|
968
|
-
const [argument] = node.arguments;
|
|
969
|
-
if (argument?.type === distExports.AST_NODE_TYPES.ArrowFunctionExpression) {
|
|
970
|
-
const sourceCode = context.sourceCode;
|
|
971
|
-
const functionBody = sourceCode.getText(argument.body);
|
|
972
|
-
if (!functionBody.startsWith("{")) {
|
|
973
|
-
const bodyText = functionBody.startsWith("(") && functionBody.endsWith(")") ? functionBody.slice(1, -1) : functionBody;
|
|
974
|
-
context.report({
|
|
975
|
-
fix(fixer) {
|
|
976
|
-
return fixer.replaceText(
|
|
977
|
-
argument.body,
|
|
978
|
-
`{
|
|
979
|
-
return ${bodyText}
|
|
980
|
-
}`
|
|
981
|
-
);
|
|
982
|
-
},
|
|
983
|
-
messageId: "multilineComputed",
|
|
984
|
-
node
|
|
985
|
-
});
|
|
986
|
-
}
|
|
987
|
-
}
|
|
988
|
-
}
|
|
989
|
-
}
|
|
990
|
-
};
|
|
991
|
-
}
|
|
992
|
-
};
|
|
993
|
-
|
|
994
|
-
const rule$3 = {
|
|
995
|
-
meta: {
|
|
996
|
-
docs: {
|
|
997
|
-
description: "Enforce typed emits in Vue components",
|
|
998
|
-
recommended: true
|
|
739
|
+
messages: {
|
|
740
|
+
multilineComputed: "Computed properties should use explicit return with blocks",
|
|
741
|
+
untypedEmits: "Use typed emits instead of untyped emits"
|
|
999
742
|
},
|
|
1000
|
-
fixable: "code",
|
|
1001
|
-
messages: { untypedEmits: "Use typed emits instead of untyped emits" },
|
|
1002
743
|
schema: [],
|
|
1003
744
|
type: "problem"
|
|
1004
745
|
},
|
|
@@ -1007,9 +748,32 @@ const rule$3 = {
|
|
|
1007
748
|
return {};
|
|
1008
749
|
}
|
|
1009
750
|
return {
|
|
751
|
+
// Rewrite computed(() => expr) into an explicit return block.
|
|
752
|
+
CallExpression(node) {
|
|
753
|
+
if (node.callee.type !== distExports.AST_NODE_TYPES.Identifier || node.callee.name !== "computed") {
|
|
754
|
+
return;
|
|
755
|
+
}
|
|
756
|
+
const [argument] = node.arguments;
|
|
757
|
+
if (argument?.type !== distExports.AST_NODE_TYPES.ArrowFunctionExpression) {
|
|
758
|
+
return;
|
|
759
|
+
}
|
|
760
|
+
const functionBody = context.sourceCode.getText(argument.body);
|
|
761
|
+
if (functionBody.startsWith("{")) {
|
|
762
|
+
return;
|
|
763
|
+
}
|
|
764
|
+
const bodyText = functionBody.startsWith("(") && functionBody.endsWith(")") ? functionBody.slice(1, -1) : functionBody;
|
|
765
|
+
context.report({
|
|
766
|
+
fix: (fixer) => fixer.replaceText(argument.body, `{
|
|
767
|
+
return ${bodyText}
|
|
768
|
+
}`),
|
|
769
|
+
messageId: "multilineComputed",
|
|
770
|
+
node
|
|
771
|
+
});
|
|
772
|
+
},
|
|
773
|
+
// Rewrite array-syntax defineEmits([...]) into the typed generic form.
|
|
1010
774
|
Program(node) {
|
|
1011
775
|
const source = context.sourceCode.getText();
|
|
1012
|
-
|
|
776
|
+
untypedEmitsRegex.lastIndex = 0;
|
|
1013
777
|
let match = untypedEmitsRegex.exec(source);
|
|
1014
778
|
while (match !== null) {
|
|
1015
779
|
const [fullMatch, eventsString] = match;
|
|
@@ -1020,12 +784,7 @@ const rule$3 = {
|
|
|
1020
784
|
${typedEvents}
|
|
1021
785
|
}>()`;
|
|
1022
786
|
context.report({
|
|
1023
|
-
fix: (fixer) =>
|
|
1024
|
-
return fixer.replaceTextRange(
|
|
1025
|
-
[matchIndex, matchIndex + fullMatch.length],
|
|
1026
|
-
typedEmits
|
|
1027
|
-
);
|
|
1028
|
-
},
|
|
787
|
+
fix: (fixer) => fixer.replaceTextRange([matchIndex, matchIndex + fullMatch.length], typedEmits),
|
|
1029
788
|
loc: {
|
|
1030
789
|
end: context.sourceCode.getLocFromIndex(matchIndex + fullMatch.length),
|
|
1031
790
|
start: context.sourceCode.getLocFromIndex(matchIndex)
|
|
@@ -1040,38 +799,42 @@ ${typedEvents}
|
|
|
1040
799
|
}
|
|
1041
800
|
};
|
|
1042
801
|
|
|
1043
|
-
const
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
messages: {
|
|
1052
|
-
defineExposeNotTheLast: "`defineExpose` should be the last statement in `<script setup>`.",
|
|
1053
|
-
macrosNotOnTop: "{{macro}} should be the first statement in `<script setup>` (after any potential import statements or type definitions).",
|
|
1054
|
-
putExposeAtTheLast: "Put `defineExpose` as the last statement in `<script setup>`."
|
|
1055
|
-
},
|
|
1056
|
-
schema: [],
|
|
1057
|
-
type: "problem"
|
|
802
|
+
const templateRegex = /<template>([\s\S]*)<\/template>/i;
|
|
803
|
+
const templateTagLength = "<template>".length;
|
|
804
|
+
const propsPrefixRegex = /\$?props\.(\w+)/g;
|
|
805
|
+
const trueAttributeRegex = /(?<![\w-]):?(?!aria-)([a-z0-9-]+)="true"/gi;
|
|
806
|
+
const dollarTPatterns = [
|
|
807
|
+
{
|
|
808
|
+
pattern: / \$t\(/g,
|
|
809
|
+
prefix: " "
|
|
1058
810
|
},
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
811
|
+
{
|
|
812
|
+
pattern: /"\$t\(/g,
|
|
813
|
+
prefix: '"'
|
|
814
|
+
},
|
|
815
|
+
{
|
|
816
|
+
pattern: /`\$t\(/g,
|
|
817
|
+
prefix: "`"
|
|
818
|
+
},
|
|
819
|
+
{
|
|
820
|
+
pattern: /\{\$t\(/g,
|
|
821
|
+
prefix: "{"
|
|
822
|
+
},
|
|
823
|
+
{
|
|
824
|
+
pattern: /\[\$t\(/g,
|
|
825
|
+
prefix: "["
|
|
1064
826
|
}
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
827
|
+
];
|
|
828
|
+
const rule = {
|
|
829
|
+
defaultOptions: [],
|
|
1068
830
|
meta: {
|
|
1069
|
-
docs: {
|
|
1070
|
-
description: "Prevent unnecessary props. and $props. prefixes in Vue templates",
|
|
1071
|
-
recommended: true
|
|
1072
|
-
},
|
|
831
|
+
docs: { description: 'Format Vue templates: strip unnecessary props/$props prefixes, redundant ="true" attributes (except aria- attributes), and enforce t() over $t()' },
|
|
1073
832
|
fixable: "code",
|
|
1074
|
-
messages: {
|
|
833
|
+
messages: {
|
|
834
|
+
noPropsPrefix: "Unnecessary props/$props prefix in template. Props are automatically available in template scope.",
|
|
835
|
+
removeTrueAttribute: 'Unnecessary ="true" attribute. Use the attribute name directly instead.',
|
|
836
|
+
useT: "Use t() instead of $t() in Vue templates as it does not work with <i18n> tags."
|
|
837
|
+
},
|
|
1075
838
|
schema: [],
|
|
1076
839
|
type: "problem"
|
|
1077
840
|
},
|
|
@@ -1080,31 +843,23 @@ const rule$1 = {
|
|
|
1080
843
|
return {};
|
|
1081
844
|
}
|
|
1082
845
|
return {
|
|
1083
|
-
|
|
1084
|
-
if (node.callee.type !== "Identifier" || node.callee.name !== "defineProps") {
|
|
1085
|
-
return;
|
|
1086
|
-
}
|
|
846
|
+
Program() {
|
|
1087
847
|
const sourceCode = context.sourceCode;
|
|
1088
848
|
const source = sourceCode.getText();
|
|
1089
|
-
const templateRegex = /<template>([\s\S]*)<\/template>/i;
|
|
1090
849
|
const templateMatch = templateRegex.exec(source);
|
|
1091
850
|
if (!templateMatch) {
|
|
1092
851
|
return;
|
|
1093
852
|
}
|
|
1094
853
|
const templateContent = templateMatch[1];
|
|
1095
|
-
const
|
|
854
|
+
const templateStartIndex = templateMatch.index + templateTagLength;
|
|
855
|
+
propsPrefixRegex.lastIndex = 0;
|
|
1096
856
|
let prefixMatch = propsPrefixRegex.exec(templateContent);
|
|
1097
857
|
while (prefixMatch !== null) {
|
|
1098
858
|
const propName = prefixMatch[1];
|
|
1099
859
|
const fullMatch = prefixMatch[0];
|
|
1100
|
-
const matchIndex =
|
|
860
|
+
const matchIndex = templateStartIndex + prefixMatch.index;
|
|
1101
861
|
context.report({
|
|
1102
|
-
fix: (fixer) =>
|
|
1103
|
-
return fixer.replaceTextRange(
|
|
1104
|
-
[matchIndex, matchIndex + fullMatch.length],
|
|
1105
|
-
propName
|
|
1106
|
-
);
|
|
1107
|
-
},
|
|
862
|
+
fix: (fixer) => fixer.replaceTextRange([matchIndex, matchIndex + fullMatch.length], propName),
|
|
1108
863
|
loc: {
|
|
1109
864
|
end: sourceCode.getLocFromIndex(matchIndex + fullMatch.length),
|
|
1110
865
|
start: sourceCode.getLocFromIndex(matchIndex)
|
|
@@ -1113,38 +868,7 @@ const rule$1 = {
|
|
|
1113
868
|
});
|
|
1114
869
|
prefixMatch = propsPrefixRegex.exec(templateContent);
|
|
1115
870
|
}
|
|
1116
|
-
|
|
1117
|
-
};
|
|
1118
|
-
}
|
|
1119
|
-
};
|
|
1120
|
-
|
|
1121
|
-
const rule = {
|
|
1122
|
-
meta: {
|
|
1123
|
-
docs: {
|
|
1124
|
-
description: 'Remove unnecessary ="true" attributes in Vue templates, except for aria- attributes',
|
|
1125
|
-
recommended: true
|
|
1126
|
-
},
|
|
1127
|
-
fixable: "code",
|
|
1128
|
-
messages: { removeTrueAttribute: 'Unnecessary ="true" attribute. Use the attribute name directly instead.' },
|
|
1129
|
-
schema: [],
|
|
1130
|
-
type: "problem"
|
|
1131
|
-
},
|
|
1132
|
-
create(context) {
|
|
1133
|
-
if (!context.filename.endsWith(".vue")) {
|
|
1134
|
-
return {};
|
|
1135
|
-
}
|
|
1136
|
-
return {
|
|
1137
|
-
Program() {
|
|
1138
|
-
const sourceCode = context.sourceCode;
|
|
1139
|
-
const source = sourceCode.getText();
|
|
1140
|
-
const templateRegex = /<template>([\s\S]*)<\/template>/i;
|
|
1141
|
-
const templateMatch = templateRegex.exec(source);
|
|
1142
|
-
if (!templateMatch) {
|
|
1143
|
-
return;
|
|
1144
|
-
}
|
|
1145
|
-
const templateContent = templateMatch[1];
|
|
1146
|
-
const templateStartIndex = templateMatch.index + 10;
|
|
1147
|
-
const trueAttributeRegex = /(?:^|\s):?(?!aria-)([a-zA-Z0-9-]+)="true"/g;
|
|
871
|
+
trueAttributeRegex.lastIndex = 0;
|
|
1148
872
|
let trueAttributeMatch = trueAttributeRegex.exec(templateContent);
|
|
1149
873
|
while (trueAttributeMatch !== null) {
|
|
1150
874
|
const fullMatch = trueAttributeMatch[0];
|
|
@@ -1156,12 +880,7 @@ const rule = {
|
|
|
1156
880
|
continue;
|
|
1157
881
|
}
|
|
1158
882
|
context.report({
|
|
1159
|
-
fix: (fixer) =>
|
|
1160
|
-
return fixer.replaceTextRange(
|
|
1161
|
-
[matchIndex, matchIndex + fullMatch.length],
|
|
1162
|
-
` ${attributeName}`
|
|
1163
|
-
);
|
|
1164
|
-
},
|
|
883
|
+
fix: (fixer) => fixer.replaceTextRange([matchIndex, matchIndex + fullMatch.length], attributeName),
|
|
1165
884
|
loc: {
|
|
1166
885
|
end: sourceCode.getLocFromIndex(matchIndex + fullMatch.length),
|
|
1167
886
|
start: sourceCode.getLocFromIndex(matchIndex)
|
|
@@ -1170,6 +889,23 @@ const rule = {
|
|
|
1170
889
|
});
|
|
1171
890
|
trueAttributeMatch = trueAttributeRegex.exec(templateContent);
|
|
1172
891
|
}
|
|
892
|
+
for (const { pattern, prefix } of dollarTPatterns) {
|
|
893
|
+
pattern.lastIndex = 0;
|
|
894
|
+
let dollarTMatch = pattern.exec(templateContent);
|
|
895
|
+
while (dollarTMatch !== null) {
|
|
896
|
+
const start = templateStartIndex + dollarTMatch.index;
|
|
897
|
+
const end = start + 4;
|
|
898
|
+
context.report({
|
|
899
|
+
fix: (fixer) => fixer.replaceTextRange([start, end], `${prefix}t(`),
|
|
900
|
+
loc: {
|
|
901
|
+
end: sourceCode.getLocFromIndex(end),
|
|
902
|
+
start: sourceCode.getLocFromIndex(start)
|
|
903
|
+
},
|
|
904
|
+
messageId: "useT"
|
|
905
|
+
});
|
|
906
|
+
dollarTMatch = pattern.exec(templateContent);
|
|
907
|
+
}
|
|
908
|
+
}
|
|
1173
909
|
}
|
|
1174
910
|
};
|
|
1175
911
|
}
|
|
@@ -1177,19 +913,11 @@ const rule = {
|
|
|
1177
913
|
|
|
1178
914
|
const index = {
|
|
1179
915
|
rules: {
|
|
1180
|
-
"ts-
|
|
1181
|
-
"ts-
|
|
1182
|
-
"
|
|
1183
|
-
"
|
|
1184
|
-
"vue-
|
|
1185
|
-
"vue-i18n-consistent-t": rule$7,
|
|
1186
|
-
"vue-i18n-sort-keys": rule$6,
|
|
1187
|
-
"vue-i18n-unused-strings": rule$5,
|
|
1188
|
-
"vue-script-format-computed": rule$4,
|
|
1189
|
-
"vue-script-format-emits": rule$3,
|
|
1190
|
-
"vue-script-order": rule$2,
|
|
1191
|
-
"vue-template-format-props": rule$1,
|
|
1192
|
-
"vue-template-remove-true-attributes": rule
|
|
916
|
+
"ts-format-layout": rule$4,
|
|
917
|
+
"ts-format-tests": rule$3,
|
|
918
|
+
"vue-format-i18n": rule$2,
|
|
919
|
+
"vue-format-script": rule$1,
|
|
920
|
+
"vue-format-template": rule
|
|
1193
921
|
}
|
|
1194
922
|
};
|
|
1195
923
|
|