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