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