@saasmakers/eslint 1.0.19 → 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 +3 -2
- 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 +3 -2
- package/dist/index.cjs +284 -559
- package/dist/index.d.cts +11 -14
- package/dist/index.d.mts +11 -14
- package/dist/index.d.ts +11 -14
- package/dist/index.mjs +284 -559
- package/package.json +3 -2
package/dist/index.mjs
CHANGED
|
@@ -2,138 +2,62 @@ 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
|
}
|
|
@@ -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 = {
|
|
@@ -440,6 +381,7 @@ const rule$a = {
|
|
|
440
381
|
":statement": verify,
|
|
441
382
|
"BlockStatement": enterScope,
|
|
442
383
|
"BlockStatement:exit": exitScope,
|
|
384
|
+
"ConditionalExpression": (node) => checkTernary(context, node, maxCharacters),
|
|
443
385
|
"Program": enterScope,
|
|
444
386
|
"Program:exit": function() {
|
|
445
387
|
for (const comment of sourceCode.getAllComments()) {
|
|
@@ -450,7 +392,8 @@ const rule$a = {
|
|
|
450
392
|
"StaticBlock": enterScope,
|
|
451
393
|
"StaticBlock:exit": exitScope,
|
|
452
394
|
"SwitchCase": verifyThenEnterScope,
|
|
453
|
-
"SwitchCase:exit": exitScope
|
|
395
|
+
"SwitchCase:exit": exitScope,
|
|
396
|
+
"TSUnionType": (node) => checkUnion(context, node, maxLineLength, minItems)
|
|
454
397
|
};
|
|
455
398
|
}
|
|
456
399
|
};
|
|
@@ -503,7 +446,7 @@ function getTestPriority(testName) {
|
|
|
503
446
|
return 1;
|
|
504
447
|
}
|
|
505
448
|
}
|
|
506
|
-
const rule$
|
|
449
|
+
const rule$3 = {
|
|
507
450
|
defaultOptions: [],
|
|
508
451
|
meta: {
|
|
509
452
|
docs: { description: "Enforce sorted test functions grouped by method with sorted metrics, errors, exceptions and middlewares" },
|
|
@@ -570,63 +513,68 @@ const rule$9 = {
|
|
|
570
513
|
}
|
|
571
514
|
};
|
|
572
515
|
|
|
573
|
-
|
|
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) {
|
|
574
522
|
for (const locale of Object.keys(parsed)) {
|
|
575
|
-
if (
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
}
|
|
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
|
+
});
|
|
592
540
|
}
|
|
593
541
|
}
|
|
594
542
|
}
|
|
595
|
-
function checkMissingLocales(context, parsed, locales,
|
|
543
|
+
function checkMissingLocales(context, parsed, locales, content, contentOffset) {
|
|
596
544
|
for (const locale of locales) {
|
|
597
545
|
if (!parsed[locale]) {
|
|
598
546
|
context.report({
|
|
599
547
|
data: { locale },
|
|
600
548
|
loc: {
|
|
601
|
-
end: context.sourceCode.getLocFromIndex(
|
|
602
|
-
start: context.sourceCode.getLocFromIndex(
|
|
549
|
+
end: context.sourceCode.getLocFromIndex(contentOffset + content.length),
|
|
550
|
+
start: context.sourceCode.getLocFromIndex(contentOffset)
|
|
603
551
|
},
|
|
604
552
|
messageId: "missingLocale"
|
|
605
553
|
});
|
|
606
554
|
}
|
|
607
555
|
}
|
|
608
556
|
}
|
|
609
|
-
function checkMissingTranslations(context, parsed, locales,
|
|
557
|
+
function checkMissingTranslations(context, parsed, locales, content, contentOffset) {
|
|
610
558
|
const allKeys = /* @__PURE__ */ new Set();
|
|
611
559
|
for (const locale of locales) {
|
|
612
560
|
if (parsed[locale]) {
|
|
613
|
-
const
|
|
614
|
-
|
|
561
|
+
for (const key of getAllKeys(parsed[locale])) {
|
|
562
|
+
allKeys.add(key);
|
|
563
|
+
}
|
|
615
564
|
}
|
|
616
565
|
}
|
|
617
566
|
for (const locale of locales) {
|
|
618
567
|
if (!parsed[locale]) {
|
|
619
568
|
continue;
|
|
620
569
|
}
|
|
621
|
-
const localeKeys = getAllKeys
|
|
570
|
+
const localeKeys = getAllKeys(parsed[locale]);
|
|
622
571
|
const missingKeys = [...allKeys].filter((key) => !localeKeys.includes(key));
|
|
623
572
|
if (missingKeys.length === 0) {
|
|
624
573
|
continue;
|
|
625
574
|
}
|
|
626
|
-
const
|
|
627
|
-
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);
|
|
628
576
|
if (localeMatch) {
|
|
629
|
-
const localeOffset =
|
|
577
|
+
const localeOffset = contentOffset + localeMatch.index;
|
|
630
578
|
context.report({
|
|
631
579
|
data: {
|
|
632
580
|
locale,
|
|
@@ -641,152 +589,21 @@ function checkMissingTranslations(context, parsed, locales, startOffset, i18nCon
|
|
|
641
589
|
}
|
|
642
590
|
}
|
|
643
591
|
}
|
|
644
|
-
function
|
|
592
|
+
function escapeRegExp(value) {
|
|
593
|
+
return value.replaceAll(/[.*+?^${}()|[\]\\]/g, String.raw`\$&`);
|
|
594
|
+
}
|
|
595
|
+
function getAllKeys(object, prefix = "") {
|
|
645
596
|
let keys = [];
|
|
646
597
|
for (const key in object) {
|
|
647
598
|
const newPrefix = prefix ? `${prefix}.${key}` : key;
|
|
648
599
|
if (typeof object[key] === "object" && object[key] !== null) {
|
|
649
|
-
keys = [...keys, ...getAllKeys
|
|
600
|
+
keys = [...keys, ...getAllKeys(object[key], newPrefix)];
|
|
650
601
|
} else {
|
|
651
602
|
keys.push(newPrefix);
|
|
652
603
|
}
|
|
653
604
|
}
|
|
654
605
|
return keys;
|
|
655
606
|
}
|
|
656
|
-
const rule$8 = {
|
|
657
|
-
meta: {
|
|
658
|
-
docs: {
|
|
659
|
-
description: "Enforce consistent i18n locale keys across translations",
|
|
660
|
-
recommended: true
|
|
661
|
-
},
|
|
662
|
-
messages: {
|
|
663
|
-
invalidJson: "Invalid JSON in i18n block: {{error}}",
|
|
664
|
-
invalidLocale: "Invalid locale: {{locale}}. Allowed locales are: {{allowed}}",
|
|
665
|
-
missingLocale: "Missing required locale: {{locale}}",
|
|
666
|
-
missingTranslations: 'Missing translations in "{{locale}}" locale: {{missing}}'
|
|
667
|
-
},
|
|
668
|
-
schema: [
|
|
669
|
-
{
|
|
670
|
-
additionalProperties: false,
|
|
671
|
-
properties: {
|
|
672
|
-
locales: {
|
|
673
|
-
items: { type: "string" },
|
|
674
|
-
type: "array"
|
|
675
|
-
}
|
|
676
|
-
},
|
|
677
|
-
type: "object"
|
|
678
|
-
}
|
|
679
|
-
],
|
|
680
|
-
type: "problem"
|
|
681
|
-
},
|
|
682
|
-
create(context) {
|
|
683
|
-
if (!context.filename.endsWith(".vue")) {
|
|
684
|
-
return {};
|
|
685
|
-
}
|
|
686
|
-
const locales = context.options[0]?.locales || ["en", "fr"];
|
|
687
|
-
return {
|
|
688
|
-
Program() {
|
|
689
|
-
const source = context.sourceCode.getText();
|
|
690
|
-
const i18nRegex = /<i18n\s+lang=["']json["']>([\s\S]*?)<\/i18n>/i;
|
|
691
|
-
const i18nMatch = i18nRegex.exec(source);
|
|
692
|
-
if (!i18nMatch) {
|
|
693
|
-
return;
|
|
694
|
-
}
|
|
695
|
-
const tagLength = source.indexOf(">", i18nMatch.index) + 1 - i18nMatch.index;
|
|
696
|
-
const startOffset = i18nMatch.index + tagLength;
|
|
697
|
-
const i18nContent = i18nMatch[1].trim();
|
|
698
|
-
try {
|
|
699
|
-
const parsed = JSON.parse(i18nContent);
|
|
700
|
-
checkMissingLocales(context, parsed, locales, startOffset, i18nContent);
|
|
701
|
-
checkInvalidLocales(context, parsed, locales, startOffset, i18nContent);
|
|
702
|
-
checkMissingTranslations(context, parsed, locales, startOffset, i18nContent);
|
|
703
|
-
} catch (error) {
|
|
704
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
705
|
-
context.report({
|
|
706
|
-
data: { error: errorMessage },
|
|
707
|
-
loc: {
|
|
708
|
-
end: context.sourceCode.getLocFromIndex(startOffset + i18nContent.length),
|
|
709
|
-
start: context.sourceCode.getLocFromIndex(startOffset)
|
|
710
|
-
},
|
|
711
|
-
messageId: "invalidJson"
|
|
712
|
-
});
|
|
713
|
-
}
|
|
714
|
-
}
|
|
715
|
-
};
|
|
716
|
-
}
|
|
717
|
-
};
|
|
718
|
-
|
|
719
|
-
const rule$7 = {
|
|
720
|
-
meta: {
|
|
721
|
-
docs: {
|
|
722
|
-
description: "Enforce using t() instead of $t() in Vue templates",
|
|
723
|
-
recommended: true
|
|
724
|
-
},
|
|
725
|
-
fixable: "code",
|
|
726
|
-
messages: { useT: "Use t() instead of $t() in Vue templates as it does not work with <i18n> tags." },
|
|
727
|
-
schema: [],
|
|
728
|
-
type: "problem"
|
|
729
|
-
},
|
|
730
|
-
create(context) {
|
|
731
|
-
if (!context.filename.endsWith(".vue")) {
|
|
732
|
-
return {};
|
|
733
|
-
}
|
|
734
|
-
return {
|
|
735
|
-
Program() {
|
|
736
|
-
const sourceCode = context.sourceCode;
|
|
737
|
-
const source = sourceCode.getText();
|
|
738
|
-
const templateRegex = /<template>([\s\S]*)<\/template>/i;
|
|
739
|
-
const templateMatch = templateRegex.exec(source);
|
|
740
|
-
if (!templateMatch) {
|
|
741
|
-
return;
|
|
742
|
-
}
|
|
743
|
-
const templateContent = templateMatch[1];
|
|
744
|
-
const tPatterns = [
|
|
745
|
-
{
|
|
746
|
-
pattern: / \$t\(/g,
|
|
747
|
-
quote: " "
|
|
748
|
-
},
|
|
749
|
-
{
|
|
750
|
-
pattern: /"\$t\(/g,
|
|
751
|
-
quote: '"'
|
|
752
|
-
},
|
|
753
|
-
{
|
|
754
|
-
pattern: /`\$t\(/g,
|
|
755
|
-
quote: "`"
|
|
756
|
-
},
|
|
757
|
-
{
|
|
758
|
-
pattern: /\{\$t\(/g,
|
|
759
|
-
quote: "{"
|
|
760
|
-
},
|
|
761
|
-
{
|
|
762
|
-
pattern: /\[\$t\(/g,
|
|
763
|
-
quote: "["
|
|
764
|
-
}
|
|
765
|
-
];
|
|
766
|
-
for (const { pattern, quote } of tPatterns) {
|
|
767
|
-
let match = pattern.exec(templateContent);
|
|
768
|
-
while (match !== null) {
|
|
769
|
-
const templateStart = templateMatch.index + templateMatch[0].indexOf(templateContent);
|
|
770
|
-
const start = templateStart + match.index;
|
|
771
|
-
const end = start + 4;
|
|
772
|
-
context.report({
|
|
773
|
-
fix(fixer) {
|
|
774
|
-
return fixer.replaceTextRange([start, end], `${quote}t(`);
|
|
775
|
-
},
|
|
776
|
-
loc: {
|
|
777
|
-
end: sourceCode.getLocFromIndex(end),
|
|
778
|
-
start: sourceCode.getLocFromIndex(start)
|
|
779
|
-
},
|
|
780
|
-
messageId: "useT"
|
|
781
|
-
});
|
|
782
|
-
match = pattern.exec(templateContent);
|
|
783
|
-
}
|
|
784
|
-
}
|
|
785
|
-
}
|
|
786
|
-
};
|
|
787
|
-
}
|
|
788
|
-
};
|
|
789
|
-
|
|
790
607
|
function sortObjectKeys(object) {
|
|
791
608
|
if (Array.isArray(object)) {
|
|
792
609
|
return object.map((item) => sortObjectKeys(item));
|
|
@@ -794,214 +611,135 @@ function sortObjectKeys(object) {
|
|
|
794
611
|
if (object && typeof object === "object") {
|
|
795
612
|
const sortedKeys = Object.keys(object).toSorted((key1, key2) => key1.localeCompare(key2));
|
|
796
613
|
const result = {};
|
|
797
|
-
const
|
|
614
|
+
const record = object;
|
|
798
615
|
for (const key of sortedKeys) {
|
|
799
|
-
result[key] = sortObjectKeys(
|
|
616
|
+
result[key] = sortObjectKeys(record[key]);
|
|
800
617
|
}
|
|
801
618
|
return result;
|
|
802
619
|
}
|
|
803
620
|
return object;
|
|
804
621
|
}
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
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)
|
|
816
638
|
},
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
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;
|
|
823
651
|
}
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
const sortedParsed = sortObjectKeys(parsed);
|
|
836
|
-
const formattedContent = `
|
|
837
|
-
${JSON.stringify(sortedParsed, void 0, totalSpaces).replaceAll(/\n{2,}/g, "\n")}
|
|
838
|
-
`;
|
|
839
|
-
if (formattedContent.trim() !== i18nContent.trim()) {
|
|
840
|
-
const currentKeys = JSON.stringify(parsed, void 0, totalSpaces).trim();
|
|
841
|
-
const sortedKeys = JSON.stringify(sortedParsed, void 0, totalSpaces).trim();
|
|
842
|
-
context.report({
|
|
843
|
-
data: { expected: String(totalSpaces) },
|
|
844
|
-
fix(fixer) {
|
|
845
|
-
return fixer.replaceTextRange(
|
|
846
|
-
[startOffset, startOffset + i18nContent.length],
|
|
847
|
-
formattedContent
|
|
848
|
-
);
|
|
849
|
-
},
|
|
850
|
-
loc: {
|
|
851
|
-
end: context.sourceCode.getLocFromIndex(startOffset + i18nContent.length),
|
|
852
|
-
start: context.sourceCode.getLocFromIndex(startOffset)
|
|
853
|
-
},
|
|
854
|
-
messageId: currentKeys === sortedKeys ? "indentError" : "sortError"
|
|
855
|
-
});
|
|
856
|
-
}
|
|
857
|
-
} catch (error) {
|
|
858
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
859
|
-
context.report({
|
|
860
|
-
data: { error: errorMessage },
|
|
861
|
-
loc: {
|
|
862
|
-
end: context.sourceCode.getLocFromIndex(startOffset + i18nContent.length),
|
|
863
|
-
start: context.sourceCode.getLocFromIndex(startOffset)
|
|
864
|
-
},
|
|
865
|
-
messageId: "invalidJson"
|
|
866
|
-
});
|
|
867
|
-
}
|
|
868
|
-
}
|
|
869
|
-
}
|
|
870
|
-
};
|
|
871
|
-
}
|
|
872
|
-
};
|
|
873
|
-
|
|
874
|
-
function getAllKeys(object, prefix = "") {
|
|
875
|
-
let keys = [];
|
|
876
|
-
for (const key in object) {
|
|
877
|
-
const newPrefix = prefix ? `${prefix}.${key}` : key;
|
|
878
|
-
if (typeof object[key] === "object" && object[key] !== null) {
|
|
879
|
-
keys = [...keys, ...getAllKeys(object[key], newPrefix)];
|
|
880
|
-
} else {
|
|
881
|
-
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
|
+
});
|
|
882
663
|
}
|
|
883
664
|
}
|
|
884
|
-
return keys;
|
|
885
665
|
}
|
|
886
|
-
const rule$
|
|
666
|
+
const rule$2 = {
|
|
667
|
+
defaultOptions: [{ locales: defaultLocales }],
|
|
887
668
|
meta: {
|
|
888
|
-
docs: {
|
|
889
|
-
|
|
890
|
-
recommended: true
|
|
891
|
-
},
|
|
669
|
+
docs: { description: "Format Vue i18n blocks: enforce valid/consistent locales, sorted keys, and no unused strings" },
|
|
670
|
+
fixable: "code",
|
|
892
671
|
messages: {
|
|
672
|
+
indentError: "Invalid indentation for i18n content. Expected {{expected}} spaces.",
|
|
893
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.",
|
|
894
678
|
unusedString: 'Unused string in English locale: "{{key}}"'
|
|
895
679
|
},
|
|
896
|
-
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
|
+
],
|
|
897
692
|
type: "problem"
|
|
898
693
|
},
|
|
899
694
|
create(context) {
|
|
900
695
|
if (!context.filename.endsWith(".vue")) {
|
|
901
696
|
return {};
|
|
902
697
|
}
|
|
698
|
+
const locales = context.options[0]?.locales ?? defaultLocales;
|
|
903
699
|
return {
|
|
904
700
|
Program() {
|
|
905
701
|
const source = context.sourceCode.getText();
|
|
906
|
-
const i18nRegex = /(<i18n\s+lang=["']json["']>)([\s\S]*?)<\/i18n>/i;
|
|
907
702
|
const i18nMatch = i18nRegex.exec(source);
|
|
908
703
|
if (!i18nMatch) {
|
|
909
704
|
return;
|
|
910
705
|
}
|
|
911
|
-
const
|
|
912
|
-
const
|
|
706
|
+
const contentOffset = i18nMatch.index + i18nMatch[1].length;
|
|
707
|
+
const rawContent = i18nMatch[2];
|
|
708
|
+
const trimmedContent = rawContent.trim();
|
|
709
|
+
let parsed;
|
|
913
710
|
try {
|
|
914
|
-
|
|
915
|
-
if (!parsed.en) {
|
|
916
|
-
return;
|
|
917
|
-
}
|
|
918
|
-
const templateRegex = /<template>([\s\S]*)<\/template>/i;
|
|
919
|
-
const templateMatch = templateRegex.exec(source);
|
|
920
|
-
const templateContent = templateMatch ? templateMatch[1] : "";
|
|
921
|
-
const scriptRegex = /<script[^>]*>([\s\S]*?)<\/script>/i;
|
|
922
|
-
const scriptMatch = scriptRegex.exec(source);
|
|
923
|
-
const scriptContent = scriptMatch ? scriptMatch[1] : "";
|
|
924
|
-
const enKeys = getAllKeys(parsed.en);
|
|
925
|
-
for (const key of enKeys) {
|
|
926
|
-
const isUsed = templateContent.includes(key) || scriptContent.includes(key);
|
|
927
|
-
if (!isUsed) {
|
|
928
|
-
const keyMatch = new RegExp(String.raw`"${key}"\s*:`, "g").exec(i18nContent);
|
|
929
|
-
if (keyMatch) {
|
|
930
|
-
const keyOffset = startOffset + keyMatch.index;
|
|
931
|
-
context.report({
|
|
932
|
-
data: { key },
|
|
933
|
-
loc: {
|
|
934
|
-
end: context.sourceCode.getLocFromIndex(keyOffset + key.length + 2),
|
|
935
|
-
start: context.sourceCode.getLocFromIndex(keyOffset)
|
|
936
|
-
},
|
|
937
|
-
messageId: "unusedString"
|
|
938
|
-
});
|
|
939
|
-
}
|
|
940
|
-
}
|
|
941
|
-
}
|
|
711
|
+
parsed = JSON.parse(trimmedContent);
|
|
942
712
|
} catch (error) {
|
|
943
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
944
713
|
context.report({
|
|
945
|
-
data: { error:
|
|
714
|
+
data: { error: error instanceof Error ? error.message : String(error) },
|
|
946
715
|
loc: {
|
|
947
|
-
end: context.sourceCode.getLocFromIndex(
|
|
948
|
-
start: context.sourceCode.getLocFromIndex(
|
|
716
|
+
end: context.sourceCode.getLocFromIndex(contentOffset + rawContent.length),
|
|
717
|
+
start: context.sourceCode.getLocFromIndex(contentOffset)
|
|
949
718
|
},
|
|
950
719
|
messageId: "invalidJson"
|
|
951
720
|
});
|
|
721
|
+
return;
|
|
952
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);
|
|
953
728
|
}
|
|
954
729
|
};
|
|
955
730
|
}
|
|
956
731
|
};
|
|
957
732
|
|
|
958
|
-
const
|
|
733
|
+
const untypedEmitsRegex = /const\s+emit\s*=\s*defineEmits\(\[([^\]]+)\]\)/g;
|
|
734
|
+
const rule$1 = {
|
|
959
735
|
defaultOptions: [],
|
|
960
736
|
meta: {
|
|
961
|
-
docs: { description: "
|
|
737
|
+
docs: { description: "Format Vue component scripts: enforce multiline computed properties and typed emits" },
|
|
962
738
|
fixable: "code",
|
|
963
|
-
messages: {
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
},
|
|
967
|
-
create(context) {
|
|
968
|
-
return {
|
|
969
|
-
CallExpression(node) {
|
|
970
|
-
if (node.callee.type === distExports.AST_NODE_TYPES.Identifier && node.callee.name === "computed") {
|
|
971
|
-
const [argument] = node.arguments;
|
|
972
|
-
if (argument?.type === distExports.AST_NODE_TYPES.ArrowFunctionExpression) {
|
|
973
|
-
const sourceCode = context.sourceCode;
|
|
974
|
-
const functionBody = sourceCode.getText(argument.body);
|
|
975
|
-
if (!functionBody.startsWith("{")) {
|
|
976
|
-
const bodyText = functionBody.startsWith("(") && functionBody.endsWith(")") ? functionBody.slice(1, -1) : functionBody;
|
|
977
|
-
context.report({
|
|
978
|
-
fix(fixer) {
|
|
979
|
-
return fixer.replaceText(
|
|
980
|
-
argument.body,
|
|
981
|
-
`{
|
|
982
|
-
return ${bodyText}
|
|
983
|
-
}`
|
|
984
|
-
);
|
|
985
|
-
},
|
|
986
|
-
messageId: "multilineComputed",
|
|
987
|
-
node
|
|
988
|
-
});
|
|
989
|
-
}
|
|
990
|
-
}
|
|
991
|
-
}
|
|
992
|
-
}
|
|
993
|
-
};
|
|
994
|
-
}
|
|
995
|
-
};
|
|
996
|
-
|
|
997
|
-
const rule$3 = {
|
|
998
|
-
meta: {
|
|
999
|
-
docs: {
|
|
1000
|
-
description: "Enforce typed emits in Vue components",
|
|
1001
|
-
recommended: true
|
|
739
|
+
messages: {
|
|
740
|
+
multilineComputed: "Computed properties should use explicit return with blocks",
|
|
741
|
+
untypedEmits: "Use typed emits instead of untyped emits"
|
|
1002
742
|
},
|
|
1003
|
-
fixable: "code",
|
|
1004
|
-
messages: { untypedEmits: "Use typed emits instead of untyped emits" },
|
|
1005
743
|
schema: [],
|
|
1006
744
|
type: "problem"
|
|
1007
745
|
},
|
|
@@ -1010,9 +748,32 @@ const rule$3 = {
|
|
|
1010
748
|
return {};
|
|
1011
749
|
}
|
|
1012
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.
|
|
1013
774
|
Program(node) {
|
|
1014
775
|
const source = context.sourceCode.getText();
|
|
1015
|
-
|
|
776
|
+
untypedEmitsRegex.lastIndex = 0;
|
|
1016
777
|
let match = untypedEmitsRegex.exec(source);
|
|
1017
778
|
while (match !== null) {
|
|
1018
779
|
const [fullMatch, eventsString] = match;
|
|
@@ -1023,12 +784,7 @@ const rule$3 = {
|
|
|
1023
784
|
${typedEvents}
|
|
1024
785
|
}>()`;
|
|
1025
786
|
context.report({
|
|
1026
|
-
fix: (fixer) =>
|
|
1027
|
-
return fixer.replaceTextRange(
|
|
1028
|
-
[matchIndex, matchIndex + fullMatch.length],
|
|
1029
|
-
typedEmits
|
|
1030
|
-
);
|
|
1031
|
-
},
|
|
787
|
+
fix: (fixer) => fixer.replaceTextRange([matchIndex, matchIndex + fullMatch.length], typedEmits),
|
|
1032
788
|
loc: {
|
|
1033
789
|
end: context.sourceCode.getLocFromIndex(matchIndex + fullMatch.length),
|
|
1034
790
|
start: context.sourceCode.getLocFromIndex(matchIndex)
|
|
@@ -1043,38 +799,42 @@ ${typedEvents}
|
|
|
1043
799
|
}
|
|
1044
800
|
};
|
|
1045
801
|
|
|
1046
|
-
const
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
messages: {
|
|
1055
|
-
defineExposeNotTheLast: "`defineExpose` should be the last statement in `<script setup>`.",
|
|
1056
|
-
macrosNotOnTop: "{{macro}} should be the first statement in `<script setup>` (after any potential import statements or type definitions).",
|
|
1057
|
-
putExposeAtTheLast: "Put `defineExpose` as the last statement in `<script setup>`."
|
|
1058
|
-
},
|
|
1059
|
-
schema: [],
|
|
1060
|
-
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: " "
|
|
1061
810
|
},
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
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: "["
|
|
1067
826
|
}
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
827
|
+
];
|
|
828
|
+
const rule = {
|
|
829
|
+
defaultOptions: [],
|
|
1071
830
|
meta: {
|
|
1072
|
-
docs: {
|
|
1073
|
-
description: "Prevent unnecessary props. and $props. prefixes in Vue templates",
|
|
1074
|
-
recommended: true
|
|
1075
|
-
},
|
|
831
|
+
docs: { description: 'Format Vue templates: strip unnecessary props/$props prefixes, redundant ="true" attributes (except aria- attributes), and enforce t() over $t()' },
|
|
1076
832
|
fixable: "code",
|
|
1077
|
-
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
|
+
},
|
|
1078
838
|
schema: [],
|
|
1079
839
|
type: "problem"
|
|
1080
840
|
},
|
|
@@ -1083,31 +843,23 @@ const rule$1 = {
|
|
|
1083
843
|
return {};
|
|
1084
844
|
}
|
|
1085
845
|
return {
|
|
1086
|
-
|
|
1087
|
-
if (node.callee.type !== "Identifier" || node.callee.name !== "defineProps") {
|
|
1088
|
-
return;
|
|
1089
|
-
}
|
|
846
|
+
Program() {
|
|
1090
847
|
const sourceCode = context.sourceCode;
|
|
1091
848
|
const source = sourceCode.getText();
|
|
1092
|
-
const templateRegex = /<template>([\s\S]*)<\/template>/i;
|
|
1093
849
|
const templateMatch = templateRegex.exec(source);
|
|
1094
850
|
if (!templateMatch) {
|
|
1095
851
|
return;
|
|
1096
852
|
}
|
|
1097
853
|
const templateContent = templateMatch[1];
|
|
1098
|
-
const
|
|
854
|
+
const templateStartIndex = templateMatch.index + templateTagLength;
|
|
855
|
+
propsPrefixRegex.lastIndex = 0;
|
|
1099
856
|
let prefixMatch = propsPrefixRegex.exec(templateContent);
|
|
1100
857
|
while (prefixMatch !== null) {
|
|
1101
858
|
const propName = prefixMatch[1];
|
|
1102
859
|
const fullMatch = prefixMatch[0];
|
|
1103
|
-
const matchIndex =
|
|
860
|
+
const matchIndex = templateStartIndex + prefixMatch.index;
|
|
1104
861
|
context.report({
|
|
1105
|
-
fix: (fixer) =>
|
|
1106
|
-
return fixer.replaceTextRange(
|
|
1107
|
-
[matchIndex, matchIndex + fullMatch.length],
|
|
1108
|
-
propName
|
|
1109
|
-
);
|
|
1110
|
-
},
|
|
862
|
+
fix: (fixer) => fixer.replaceTextRange([matchIndex, matchIndex + fullMatch.length], propName),
|
|
1111
863
|
loc: {
|
|
1112
864
|
end: sourceCode.getLocFromIndex(matchIndex + fullMatch.length),
|
|
1113
865
|
start: sourceCode.getLocFromIndex(matchIndex)
|
|
@@ -1116,38 +868,7 @@ const rule$1 = {
|
|
|
1116
868
|
});
|
|
1117
869
|
prefixMatch = propsPrefixRegex.exec(templateContent);
|
|
1118
870
|
}
|
|
1119
|
-
|
|
1120
|
-
};
|
|
1121
|
-
}
|
|
1122
|
-
};
|
|
1123
|
-
|
|
1124
|
-
const rule = {
|
|
1125
|
-
meta: {
|
|
1126
|
-
docs: {
|
|
1127
|
-
description: 'Remove unnecessary ="true" attributes in Vue templates, except for aria- attributes',
|
|
1128
|
-
recommended: true
|
|
1129
|
-
},
|
|
1130
|
-
fixable: "code",
|
|
1131
|
-
messages: { removeTrueAttribute: 'Unnecessary ="true" attribute. Use the attribute name directly instead.' },
|
|
1132
|
-
schema: [],
|
|
1133
|
-
type: "problem"
|
|
1134
|
-
},
|
|
1135
|
-
create(context) {
|
|
1136
|
-
if (!context.filename.endsWith(".vue")) {
|
|
1137
|
-
return {};
|
|
1138
|
-
}
|
|
1139
|
-
return {
|
|
1140
|
-
Program() {
|
|
1141
|
-
const sourceCode = context.sourceCode;
|
|
1142
|
-
const source = sourceCode.getText();
|
|
1143
|
-
const templateRegex = /<template>([\s\S]*)<\/template>/i;
|
|
1144
|
-
const templateMatch = templateRegex.exec(source);
|
|
1145
|
-
if (!templateMatch) {
|
|
1146
|
-
return;
|
|
1147
|
-
}
|
|
1148
|
-
const templateContent = templateMatch[1];
|
|
1149
|
-
const templateStartIndex = templateMatch.index + 10;
|
|
1150
|
-
const trueAttributeRegex = /(?:^|\s):?(?!aria-)([a-zA-Z0-9-]+)="true"/g;
|
|
871
|
+
trueAttributeRegex.lastIndex = 0;
|
|
1151
872
|
let trueAttributeMatch = trueAttributeRegex.exec(templateContent);
|
|
1152
873
|
while (trueAttributeMatch !== null) {
|
|
1153
874
|
const fullMatch = trueAttributeMatch[0];
|
|
@@ -1159,12 +880,7 @@ const rule = {
|
|
|
1159
880
|
continue;
|
|
1160
881
|
}
|
|
1161
882
|
context.report({
|
|
1162
|
-
fix: (fixer) =>
|
|
1163
|
-
return fixer.replaceTextRange(
|
|
1164
|
-
[matchIndex, matchIndex + fullMatch.length],
|
|
1165
|
-
` ${attributeName}`
|
|
1166
|
-
);
|
|
1167
|
-
},
|
|
883
|
+
fix: (fixer) => fixer.replaceTextRange([matchIndex, matchIndex + fullMatch.length], attributeName),
|
|
1168
884
|
loc: {
|
|
1169
885
|
end: sourceCode.getLocFromIndex(matchIndex + fullMatch.length),
|
|
1170
886
|
start: sourceCode.getLocFromIndex(matchIndex)
|
|
@@ -1173,6 +889,23 @@ const rule = {
|
|
|
1173
889
|
});
|
|
1174
890
|
trueAttributeMatch = trueAttributeRegex.exec(templateContent);
|
|
1175
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
|
+
}
|
|
1176
909
|
}
|
|
1177
910
|
};
|
|
1178
911
|
}
|
|
@@ -1180,19 +913,11 @@ const rule = {
|
|
|
1180
913
|
|
|
1181
914
|
const index = {
|
|
1182
915
|
rules: {
|
|
1183
|
-
"ts-
|
|
1184
|
-
"ts-
|
|
1185
|
-
"
|
|
1186
|
-
"
|
|
1187
|
-
"vue-
|
|
1188
|
-
"vue-i18n-consistent-t": rule$7,
|
|
1189
|
-
"vue-i18n-sort-keys": rule$6,
|
|
1190
|
-
"vue-i18n-unused-strings": rule$5,
|
|
1191
|
-
"vue-script-format-computed": rule$4,
|
|
1192
|
-
"vue-script-format-emits": rule$3,
|
|
1193
|
-
"vue-script-order": rule$2,
|
|
1194
|
-
"vue-template-format-props": rule$1,
|
|
1195
|
-
"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
|
|
1196
921
|
}
|
|
1197
922
|
};
|
|
1198
923
|
|