eslint 10.0.0-alpha.1 → 10.0.0-rc.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/lib/api.js +2 -2
- package/lib/cli-engine/formatters/stylish.js +65 -34
- package/lib/cli.js +18 -8
- package/lib/eslint/eslint.js +4 -2
- package/lib/languages/js/index.js +1 -1
- package/lib/linter/linter.js +15 -22
- package/lib/rule-tester/rule-tester.js +646 -307
- package/lib/rules/max-params.js +29 -10
- package/lib/rules/no-restricted-imports.js +56 -14
- package/lib/rules/no-useless-assignment.js +8 -5
- package/lib/rules/utils/ast-utils.js +39 -36
- package/lib/types/index.d.ts +110 -27
- package/lib/types/rules.d.ts +5 -0
- package/lib/types/use-at-your-own-risk.d.ts +1 -1
- package/package.json +7 -8
- package/lib/linter/rules.js +0 -71
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
//------------------------------------------------------------------------------
|
|
12
12
|
|
|
13
13
|
const assert = require("node:assert"),
|
|
14
|
+
{ existsSync, readFileSync } = require("node:fs"),
|
|
14
15
|
util = require("node:util"),
|
|
15
16
|
path = require("node:path"),
|
|
16
17
|
equal = require("fast-deep-equal"),
|
|
@@ -387,9 +388,10 @@ function normalizeTestCase(item) {
|
|
|
387
388
|
* Asserts that the `errors` property of an invalid test case is valid.
|
|
388
389
|
* @param {number | string[]} errors The `errors` property of the invalid test case.
|
|
389
390
|
* @param {string} ruleName The name of the rule being tested.
|
|
391
|
+
* @param {Object} [assertionOptions] The assertion options for the test case.
|
|
390
392
|
* @returns {void}
|
|
391
393
|
*/
|
|
392
|
-
function assertErrorsProperty(errors, ruleName) {
|
|
394
|
+
function assertErrorsProperty(errors, ruleName, assertionOptions = {}) {
|
|
393
395
|
const isNumber = typeof errors === "number";
|
|
394
396
|
const isArray = Array.isArray(errors);
|
|
395
397
|
|
|
@@ -407,12 +409,75 @@ function assertErrorsProperty(errors, ruleName) {
|
|
|
407
409
|
}
|
|
408
410
|
}
|
|
409
411
|
|
|
412
|
+
const { requireMessage = false, requireLocation = false } =
|
|
413
|
+
assertionOptions;
|
|
414
|
+
|
|
410
415
|
if (isArray) {
|
|
411
416
|
assert.ok(
|
|
412
417
|
errors.length !== 0,
|
|
413
418
|
"Invalid cases must have at least one error",
|
|
414
419
|
);
|
|
420
|
+
|
|
421
|
+
for (const [number, error] of errors.entries()) {
|
|
422
|
+
if (typeof error === "string" || error instanceof RegExp) {
|
|
423
|
+
// Just an error message.
|
|
424
|
+
assert.ok(
|
|
425
|
+
requireMessage !== "messageId" && !requireLocation,
|
|
426
|
+
`errors[${number}] should be an object when 'assertionOptions.requireMessage' is 'messageId' or 'assertionOptions.requireLocation' is true.`,
|
|
427
|
+
);
|
|
428
|
+
} else if (typeof error === "object" && error !== null) {
|
|
429
|
+
/*
|
|
430
|
+
* Error object.
|
|
431
|
+
* This may have a message, messageId, data, line, and/or column.
|
|
432
|
+
*/
|
|
433
|
+
|
|
434
|
+
for (const propertyName of Object.keys(error)) {
|
|
435
|
+
assert.ok(
|
|
436
|
+
errorObjectParameters.has(propertyName),
|
|
437
|
+
`Invalid error property name '${propertyName}'. Expected one of ${friendlyErrorObjectParameterList}.`,
|
|
438
|
+
);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
if (requireMessage === "message") {
|
|
442
|
+
assert.ok(
|
|
443
|
+
!hasOwnProperty(error, "messageId") &&
|
|
444
|
+
hasOwnProperty(error, "message"),
|
|
445
|
+
`errors[${number}] should specify 'message' (and not 'messageId') when 'assertionOptions.requireMessage' is 'message'.`,
|
|
446
|
+
);
|
|
447
|
+
} else if (requireMessage === "messageId") {
|
|
448
|
+
assert.ok(
|
|
449
|
+
!hasOwnProperty(error, "message") &&
|
|
450
|
+
hasOwnProperty(error, "messageId"),
|
|
451
|
+
`errors[${number}] should specify 'messageId' (and not 'message') when 'assertionOptions.requireMessage' is 'messageId'.`,
|
|
452
|
+
);
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
if (hasOwnProperty(error, "message")) {
|
|
456
|
+
assert.ok(
|
|
457
|
+
!hasOwnProperty(error, "messageId"),
|
|
458
|
+
`errors[${number}] should not specify both 'message' and 'messageId'.`,
|
|
459
|
+
);
|
|
460
|
+
assert.ok(
|
|
461
|
+
!hasOwnProperty(error, "data"),
|
|
462
|
+
`errors[${number}] should not specify both 'data' and 'message'.`,
|
|
463
|
+
);
|
|
464
|
+
} else {
|
|
465
|
+
assert.ok(
|
|
466
|
+
hasOwnProperty(error, "messageId"),
|
|
467
|
+
`errors[${number}] must specify either 'messageId' or 'message'.`,
|
|
468
|
+
);
|
|
469
|
+
}
|
|
470
|
+
} else {
|
|
471
|
+
assert.fail(
|
|
472
|
+
`errors[${number}] must be a string, RegExp, or an object.`,
|
|
473
|
+
);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
415
476
|
} else {
|
|
477
|
+
assert.ok(
|
|
478
|
+
!requireMessage && !requireLocation,
|
|
479
|
+
"Invalid cases must have 'errors' value as an array",
|
|
480
|
+
);
|
|
416
481
|
assert.ok(
|
|
417
482
|
errors > 0,
|
|
418
483
|
"Invalid cases must have 'error' value greater than 0",
|
|
@@ -564,13 +629,19 @@ function assertValidTestCase(item, seenTestCases) {
|
|
|
564
629
|
* @param {Object} item The invalid test case object to check.
|
|
565
630
|
* @param {Set<string>} seenTestCases Set of serialized test cases to check for duplicates.
|
|
566
631
|
* @param {string} ruleName The name of the rule being tested.
|
|
632
|
+
* @param {Object} [assertionOptions] The assertion options for the test case.
|
|
567
633
|
* @returns {void}
|
|
568
634
|
* @throws {AssertionError} If the test case is not valid.
|
|
569
635
|
*/
|
|
570
|
-
function assertInvalidTestCase(
|
|
636
|
+
function assertInvalidTestCase(
|
|
637
|
+
item,
|
|
638
|
+
seenTestCases,
|
|
639
|
+
ruleName,
|
|
640
|
+
assertionOptions = {},
|
|
641
|
+
) {
|
|
571
642
|
assertTestCommonProperties(item);
|
|
572
643
|
|
|
573
|
-
assertErrorsProperty(item.errors, ruleName);
|
|
644
|
+
assertErrorsProperty(item.errors, ruleName, assertionOptions);
|
|
574
645
|
|
|
575
646
|
// 'output' is optional, but if it exists it must be a string or null
|
|
576
647
|
if (hasOwnProperty(item, "output")) {
|
|
@@ -583,6 +654,190 @@ function assertInvalidTestCase(item, seenTestCases, ruleName) {
|
|
|
583
654
|
checkDuplicateTestCase(item, seenTestCases);
|
|
584
655
|
}
|
|
585
656
|
|
|
657
|
+
/**
|
|
658
|
+
* Gets the invocation location from the stack trace for later use.
|
|
659
|
+
* @param {Function} relative The function before the invocation point.
|
|
660
|
+
* @returns {string} The invocation location.
|
|
661
|
+
*/
|
|
662
|
+
function getInvocationLocation(relative = getInvocationLocation) {
|
|
663
|
+
const dummyObject = {};
|
|
664
|
+
Error.captureStackTrace(dummyObject, relative);
|
|
665
|
+
const { stack } = dummyObject;
|
|
666
|
+
|
|
667
|
+
return stack.split("\n")[1].replace(/.*?\(/u, "").replace(/\)$/u, "");
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
/**
|
|
671
|
+
* Estimates the location of the test case in the source file.
|
|
672
|
+
* @param {Function} invoker The method that runs the tests.
|
|
673
|
+
* @returns {(key: string) => string} The lazy resolver for the estimated location of the test case.
|
|
674
|
+
*/
|
|
675
|
+
function buildLazyTestLocationEstimator(invoker) {
|
|
676
|
+
const invocationLocation = getInvocationLocation(invoker);
|
|
677
|
+
let testLocations = null;
|
|
678
|
+
return key => {
|
|
679
|
+
if (testLocations === null) {
|
|
680
|
+
let [sourceFile, sourceLine = "1", sourceColumn = "1"] =
|
|
681
|
+
invocationLocation
|
|
682
|
+
.replace(/:\\/u, "\\\\") // Windows workaround for C:\\
|
|
683
|
+
.split(":");
|
|
684
|
+
sourceFile = sourceFile.replace(/\\\\/u, ":\\");
|
|
685
|
+
sourceLine = Number(sourceLine);
|
|
686
|
+
sourceColumn = Number(sourceColumn);
|
|
687
|
+
testLocations = { root: invocationLocation };
|
|
688
|
+
|
|
689
|
+
if (existsSync(sourceFile)) {
|
|
690
|
+
let content = readFileSync(sourceFile, "utf8")
|
|
691
|
+
.split("\n")
|
|
692
|
+
.slice(sourceLine - 1);
|
|
693
|
+
content[0] = content[0].slice(Math.max(0, sourceColumn - 1));
|
|
694
|
+
content = content.map(
|
|
695
|
+
l =>
|
|
696
|
+
l
|
|
697
|
+
.trim() // Remove whitespace
|
|
698
|
+
.replace(/\s*\/\/.*$(?<!,)/u, ""), // and trailing in-line comments that aren't part of the test `code`
|
|
699
|
+
);
|
|
700
|
+
|
|
701
|
+
// Roots
|
|
702
|
+
const validStartIndex = content.findIndex(line =>
|
|
703
|
+
/\bvalid:/u.test(line),
|
|
704
|
+
);
|
|
705
|
+
const invalidStartIndex = content.findIndex(line =>
|
|
706
|
+
/\binvalid:/u.test(line),
|
|
707
|
+
);
|
|
708
|
+
|
|
709
|
+
testLocations.valid = `${sourceFile}:${
|
|
710
|
+
sourceLine + validStartIndex
|
|
711
|
+
}`;
|
|
712
|
+
testLocations.invalid = `${sourceFile}:${
|
|
713
|
+
sourceLine + invalidStartIndex
|
|
714
|
+
}`;
|
|
715
|
+
|
|
716
|
+
// Scenario basics
|
|
717
|
+
const validEndIndex =
|
|
718
|
+
validStartIndex < invalidStartIndex
|
|
719
|
+
? invalidStartIndex
|
|
720
|
+
: content.length;
|
|
721
|
+
const invalidEndIndex =
|
|
722
|
+
validStartIndex < invalidStartIndex
|
|
723
|
+
? content.length
|
|
724
|
+
: validStartIndex;
|
|
725
|
+
|
|
726
|
+
const validLines = content.slice(
|
|
727
|
+
validStartIndex,
|
|
728
|
+
validEndIndex,
|
|
729
|
+
);
|
|
730
|
+
const invalidLines = content.slice(
|
|
731
|
+
invalidStartIndex,
|
|
732
|
+
invalidEndIndex,
|
|
733
|
+
);
|
|
734
|
+
|
|
735
|
+
let objectDepth = 0;
|
|
736
|
+
const validLineIndexes = validLines
|
|
737
|
+
.map((l, i) => {
|
|
738
|
+
// matches `key: {` and `{`
|
|
739
|
+
if (/^(?:\w+\s*:\s*)?\{/u.test(l)) {
|
|
740
|
+
objectDepth++;
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
if (objectDepth > 0) {
|
|
744
|
+
if (l.endsWith("}") || l.endsWith("},")) {
|
|
745
|
+
objectDepth--;
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
return objectDepth <= 1 && l.includes("code:")
|
|
749
|
+
? i
|
|
750
|
+
: null;
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
return l.endsWith(",") ? i : null;
|
|
754
|
+
})
|
|
755
|
+
.filter(Boolean);
|
|
756
|
+
const invalidLineIndexes = invalidLines
|
|
757
|
+
.map((l, i) =>
|
|
758
|
+
l.trimStart().startsWith("errors:") ? i : null,
|
|
759
|
+
)
|
|
760
|
+
.filter(Boolean);
|
|
761
|
+
|
|
762
|
+
Object.assign(
|
|
763
|
+
testLocations,
|
|
764
|
+
{
|
|
765
|
+
[`valid[0]`]: `${sourceFile}:${
|
|
766
|
+
sourceLine + validStartIndex
|
|
767
|
+
}`,
|
|
768
|
+
},
|
|
769
|
+
Object.fromEntries(
|
|
770
|
+
validLineIndexes.map((location, validIndex) => [
|
|
771
|
+
`valid[${validIndex}]`,
|
|
772
|
+
`${sourceFile}:${
|
|
773
|
+
sourceLine + validStartIndex + location
|
|
774
|
+
}`,
|
|
775
|
+
]),
|
|
776
|
+
),
|
|
777
|
+
Object.fromEntries(
|
|
778
|
+
invalidLineIndexes.map((location, invalidIndex) => [
|
|
779
|
+
`invalid[${invalidIndex}]`,
|
|
780
|
+
`${sourceFile}:${
|
|
781
|
+
sourceLine + invalidStartIndex + location
|
|
782
|
+
}`,
|
|
783
|
+
]),
|
|
784
|
+
),
|
|
785
|
+
);
|
|
786
|
+
|
|
787
|
+
// Indexes for errors inside each invalid test case
|
|
788
|
+
invalidLineIndexes.push(invalidLines.length);
|
|
789
|
+
|
|
790
|
+
for (let i = 0; i < invalidLineIndexes.length - 1; i++) {
|
|
791
|
+
const start = invalidLineIndexes[i];
|
|
792
|
+
const end = invalidLineIndexes[i + 1];
|
|
793
|
+
const errorLines = invalidLines.slice(start, end);
|
|
794
|
+
let errorObjectDepth = 0;
|
|
795
|
+
const errorLineIndexes = errorLines
|
|
796
|
+
.map((l, j) => {
|
|
797
|
+
if (l.startsWith("{") || l.endsWith("{")) {
|
|
798
|
+
errorObjectDepth++;
|
|
799
|
+
|
|
800
|
+
if (l.endsWith("}") || l.endsWith("},")) {
|
|
801
|
+
errorObjectDepth--;
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
return errorObjectDepth <= 1 ? j : null;
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
if (errorObjectDepth > 0) {
|
|
808
|
+
if (l.endsWith("}") || l.endsWith("},")) {
|
|
809
|
+
errorObjectDepth--;
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
return null;
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
return l.endsWith(",") ? j : null;
|
|
816
|
+
})
|
|
817
|
+
.filter(Boolean);
|
|
818
|
+
|
|
819
|
+
Object.assign(
|
|
820
|
+
testLocations,
|
|
821
|
+
Object.fromEntries(
|
|
822
|
+
errorLineIndexes.map((line, errorIndex) => [
|
|
823
|
+
`invalid[${i}].errors[${errorIndex}]`,
|
|
824
|
+
`${sourceFile}:${
|
|
825
|
+
sourceLine +
|
|
826
|
+
invalidStartIndex +
|
|
827
|
+
start +
|
|
828
|
+
line
|
|
829
|
+
}`,
|
|
830
|
+
]),
|
|
831
|
+
),
|
|
832
|
+
);
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
return testLocations[key] || "unknown source";
|
|
838
|
+
};
|
|
839
|
+
}
|
|
840
|
+
|
|
586
841
|
//------------------------------------------------------------------------------
|
|
587
842
|
// Public Interface
|
|
588
843
|
//------------------------------------------------------------------------------
|
|
@@ -763,6 +1018,11 @@ class RuleTester {
|
|
|
763
1018
|
* @param {string} ruleName The name of the rule to run.
|
|
764
1019
|
* @param {RuleDefinition} rule The rule to test.
|
|
765
1020
|
* @param {{
|
|
1021
|
+
* assertionOptions?: {
|
|
1022
|
+
* requireMessage?: boolean | "message" | "messageId",
|
|
1023
|
+
* requireLocation?: boolean
|
|
1024
|
+
* requireData?: boolean | "error" | "suggestion"
|
|
1025
|
+
* },
|
|
766
1026
|
* valid: (ValidTestCase | string)[],
|
|
767
1027
|
* invalid: InvalidTestCase[]
|
|
768
1028
|
* }} test The collection of tests to run.
|
|
@@ -778,6 +1038,8 @@ class RuleTester {
|
|
|
778
1038
|
assertRule(rule, ruleName);
|
|
779
1039
|
assertTest(test, ruleName);
|
|
780
1040
|
|
|
1041
|
+
const estimateTestLocation = buildLazyTestLocationEstimator(this.run);
|
|
1042
|
+
|
|
781
1043
|
const baseConfig = [
|
|
782
1044
|
{
|
|
783
1045
|
plugins: {
|
|
@@ -1141,8 +1403,15 @@ class RuleTester {
|
|
|
1141
1403
|
* @param {Object} item Item to run the rule against
|
|
1142
1404
|
* @returns {void}
|
|
1143
1405
|
* @private
|
|
1406
|
+
* @throws {Error} If the test case is invalid or has an invalid error.
|
|
1144
1407
|
*/
|
|
1145
1408
|
function testInvalidTemplate(item) {
|
|
1409
|
+
const {
|
|
1410
|
+
requireMessage = false,
|
|
1411
|
+
requireLocation = false,
|
|
1412
|
+
requireData = false,
|
|
1413
|
+
} = test.assertionOptions ?? {};
|
|
1414
|
+
|
|
1146
1415
|
const ruleHasMetaMessages =
|
|
1147
1416
|
hasOwnProperty(rule, "meta") &&
|
|
1148
1417
|
hasOwnProperty(rule.meta, "messages");
|
|
@@ -1152,6 +1421,11 @@ class RuleTester {
|
|
|
1152
1421
|
.join(", ")}]`
|
|
1153
1422
|
: null;
|
|
1154
1423
|
|
|
1424
|
+
assert.ok(
|
|
1425
|
+
ruleHasMetaMessages || requireMessage !== "messageId",
|
|
1426
|
+
`Assertion options can not use 'requireMessage: "messageId"' if rule under test doesn't define 'meta.messages'.`,
|
|
1427
|
+
);
|
|
1428
|
+
|
|
1155
1429
|
const result = runRuleForItem(item);
|
|
1156
1430
|
const messages = result.messages;
|
|
1157
1431
|
|
|
@@ -1204,357 +1478,384 @@ class RuleTester {
|
|
|
1204
1478
|
);
|
|
1205
1479
|
|
|
1206
1480
|
for (let i = 0, l = item.errors.length; i < l; i++) {
|
|
1207
|
-
|
|
1208
|
-
|
|
1481
|
+
try {
|
|
1482
|
+
const error = item.errors[i];
|
|
1483
|
+
const message = messages[i];
|
|
1209
1484
|
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
);
|
|
1214
|
-
|
|
1215
|
-
if (typeof error === "string" || error instanceof RegExp) {
|
|
1216
|
-
// Just an error message.
|
|
1217
|
-
assertMessageMatches(message.message, error);
|
|
1218
|
-
assert.ok(
|
|
1219
|
-
message.suggestions === void 0,
|
|
1220
|
-
`Error at index ${i} has suggestions. Please convert the test error into an object and specify 'suggestions' property on it to test suggestions.`,
|
|
1485
|
+
assert(
|
|
1486
|
+
hasMessageOfThisRule,
|
|
1487
|
+
"Error rule name should be the same as the name of the rule being tested",
|
|
1221
1488
|
);
|
|
1222
|
-
} else if (typeof error === "object" && error !== null) {
|
|
1223
|
-
/*
|
|
1224
|
-
* Error object.
|
|
1225
|
-
* This may have a message, messageId, data, line, and/or column.
|
|
1226
|
-
*/
|
|
1227
|
-
|
|
1228
|
-
Object.keys(error).forEach(propertyName => {
|
|
1229
|
-
assert.ok(
|
|
1230
|
-
errorObjectParameters.has(propertyName),
|
|
1231
|
-
`Invalid error property name '${propertyName}'. Expected one of ${friendlyErrorObjectParameterList}.`,
|
|
1232
|
-
);
|
|
1233
|
-
});
|
|
1234
1489
|
|
|
1235
|
-
if (
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1490
|
+
if (
|
|
1491
|
+
typeof error === "string" ||
|
|
1492
|
+
error instanceof RegExp
|
|
1493
|
+
) {
|
|
1494
|
+
// Just an error message.
|
|
1495
|
+
assertMessageMatches(message.message, error);
|
|
1240
1496
|
assert.ok(
|
|
1241
|
-
|
|
1242
|
-
|
|
1497
|
+
message.suggestions === void 0,
|
|
1498
|
+
`Error at index ${i} has suggestions. Please convert the test error into an object and specify 'suggestions' property on it to test suggestions.`,
|
|
1243
1499
|
);
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
if (
|
|
1254
|
-
|
|
1255
|
-
rule.meta.messages,
|
|
1256
|
-
error.messageId,
|
|
1257
|
-
)
|
|
1258
|
-
) {
|
|
1259
|
-
assert(
|
|
1260
|
-
false,
|
|
1261
|
-
`Invalid messageId '${error.messageId}'. Expected one of ${friendlyIDList}.`,
|
|
1262
|
-
);
|
|
1263
|
-
}
|
|
1264
|
-
assert.strictEqual(
|
|
1265
|
-
message.messageId,
|
|
1266
|
-
error.messageId,
|
|
1267
|
-
`messageId '${message.messageId}' does not match expected messageId '${error.messageId}'.`,
|
|
1268
|
-
);
|
|
1269
|
-
|
|
1270
|
-
const unsubstitutedPlaceholders =
|
|
1271
|
-
getUnsubstitutedMessagePlaceholders(
|
|
1500
|
+
} else if (
|
|
1501
|
+
typeof error === "object" &&
|
|
1502
|
+
error !== null
|
|
1503
|
+
) {
|
|
1504
|
+
/*
|
|
1505
|
+
* Error object.
|
|
1506
|
+
* This may have a message, messageId, data, line, and/or column.
|
|
1507
|
+
*/
|
|
1508
|
+
|
|
1509
|
+
if (hasOwnProperty(error, "message")) {
|
|
1510
|
+
assertMessageMatches(
|
|
1272
1511
|
message.message,
|
|
1273
|
-
|
|
1274
|
-
error.data,
|
|
1512
|
+
error.message,
|
|
1275
1513
|
);
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
);
|
|
1281
|
-
|
|
1282
|
-
if (hasOwnProperty(error, "data")) {
|
|
1283
|
-
/*
|
|
1284
|
-
* if data was provided, then directly compare the returned message to a synthetic
|
|
1285
|
-
* interpolated message using the same message ID and data provided in the test.
|
|
1286
|
-
* See https://github.com/eslint/eslint/issues/9890 for context.
|
|
1287
|
-
*/
|
|
1288
|
-
const unformattedOriginalMessage =
|
|
1289
|
-
rule.meta.messages[error.messageId];
|
|
1290
|
-
const rehydratedMessage = interpolate(
|
|
1291
|
-
unformattedOriginalMessage,
|
|
1292
|
-
error.data,
|
|
1514
|
+
} else if (hasOwnProperty(error, "messageId")) {
|
|
1515
|
+
assert.ok(
|
|
1516
|
+
ruleHasMetaMessages,
|
|
1517
|
+
"Error can not use 'messageId' if rule under test doesn't define 'meta.messages'.",
|
|
1293
1518
|
);
|
|
1294
|
-
|
|
1519
|
+
if (
|
|
1520
|
+
!hasOwnProperty(
|
|
1521
|
+
rule.meta.messages,
|
|
1522
|
+
error.messageId,
|
|
1523
|
+
)
|
|
1524
|
+
) {
|
|
1525
|
+
assert(
|
|
1526
|
+
false,
|
|
1527
|
+
`Invalid messageId '${error.messageId}'. Expected one of ${friendlyIDList}.`,
|
|
1528
|
+
);
|
|
1529
|
+
}
|
|
1295
1530
|
assert.strictEqual(
|
|
1296
|
-
message.
|
|
1297
|
-
|
|
1298
|
-
`
|
|
1531
|
+
message.messageId,
|
|
1532
|
+
error.messageId,
|
|
1533
|
+
`messageId '${message.messageId}' does not match expected messageId '${error.messageId}'.`,
|
|
1299
1534
|
);
|
|
1300
|
-
}
|
|
1301
|
-
} else {
|
|
1302
|
-
assert.fail(
|
|
1303
|
-
"Test error must specify either a 'messageId' or 'message'.",
|
|
1304
|
-
);
|
|
1305
|
-
}
|
|
1306
|
-
|
|
1307
|
-
const actualLocation = {};
|
|
1308
|
-
const expectedLocation = {};
|
|
1309
1535
|
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1536
|
+
const unsubstitutedPlaceholders =
|
|
1537
|
+
getUnsubstitutedMessagePlaceholders(
|
|
1538
|
+
message.message,
|
|
1539
|
+
rule.meta.messages[message.messageId],
|
|
1540
|
+
error.data,
|
|
1541
|
+
);
|
|
1314
1542
|
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1543
|
+
assert.ok(
|
|
1544
|
+
unsubstitutedPlaceholders.length === 0,
|
|
1545
|
+
`The reported message has ${unsubstitutedPlaceholders.length > 1 ? `unsubstituted placeholders: ${unsubstitutedPlaceholders.map(name => `'${name}'`).join(", ")}` : `an unsubstituted placeholder '${unsubstitutedPlaceholders[0]}'`}. Please provide the missing ${unsubstitutedPlaceholders.length > 1 ? "values" : "value"} via the 'data' property in the context.report() call.`,
|
|
1546
|
+
);
|
|
1319
1547
|
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1548
|
+
if (hasOwnProperty(error, "data")) {
|
|
1549
|
+
/*
|
|
1550
|
+
* if data was provided, then directly compare the returned message to a synthetic
|
|
1551
|
+
* interpolated message using the same message ID and data provided in the test.
|
|
1552
|
+
* See https://github.com/eslint/eslint/issues/9890 for context.
|
|
1553
|
+
*/
|
|
1554
|
+
const unformattedOriginalMessage =
|
|
1555
|
+
rule.meta.messages[error.messageId];
|
|
1556
|
+
const rehydratedMessage = interpolate(
|
|
1557
|
+
unformattedOriginalMessage,
|
|
1558
|
+
error.data,
|
|
1559
|
+
);
|
|
1324
1560
|
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1561
|
+
assert.strictEqual(
|
|
1562
|
+
message.message,
|
|
1563
|
+
rehydratedMessage,
|
|
1564
|
+
`Hydrated message "${rehydratedMessage}" does not match "${message.message}"`,
|
|
1565
|
+
);
|
|
1566
|
+
} else {
|
|
1567
|
+
const requiresDataProperty =
|
|
1568
|
+
requireData === true ||
|
|
1569
|
+
requireData === "error";
|
|
1570
|
+
const hasPlaceholders =
|
|
1571
|
+
getMessagePlaceholders(
|
|
1572
|
+
rule.meta.messages[error.messageId],
|
|
1573
|
+
).length > 0;
|
|
1574
|
+
assert.ok(
|
|
1575
|
+
!requiresDataProperty ||
|
|
1576
|
+
!hasPlaceholders,
|
|
1577
|
+
`Error should specify the 'data' property as the referenced message has placeholders.`,
|
|
1578
|
+
);
|
|
1579
|
+
}
|
|
1580
|
+
}
|
|
1329
1581
|
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
"
|
|
1335
|
-
|
|
1336
|
-
|
|
1582
|
+
const locationProperties = [
|
|
1583
|
+
"line",
|
|
1584
|
+
"column",
|
|
1585
|
+
"endLine",
|
|
1586
|
+
"endColumn",
|
|
1587
|
+
];
|
|
1588
|
+
const actualLocation = {};
|
|
1589
|
+
const expectedLocation = {};
|
|
1590
|
+
|
|
1591
|
+
for (const key of locationProperties) {
|
|
1592
|
+
if (hasOwnProperty(error, key)) {
|
|
1593
|
+
actualLocation[key] = message[key];
|
|
1594
|
+
expectedLocation[key] = error[key];
|
|
1595
|
+
}
|
|
1596
|
+
}
|
|
1337
1597
|
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
if (hasOwnProperty(error, "suggestions")) {
|
|
1344
|
-
// Support asserting there are no suggestions
|
|
1345
|
-
const expectsSuggestions = Array.isArray(
|
|
1346
|
-
error.suggestions,
|
|
1347
|
-
)
|
|
1348
|
-
? error.suggestions.length > 0
|
|
1349
|
-
: Boolean(error.suggestions);
|
|
1350
|
-
const hasSuggestions =
|
|
1351
|
-
message.suggestions !== void 0;
|
|
1352
|
-
|
|
1353
|
-
if (!hasSuggestions && expectsSuggestions) {
|
|
1354
|
-
assert.ok(
|
|
1355
|
-
!error.suggestions,
|
|
1356
|
-
`Error should have suggestions on error with message: "${message.message}"`,
|
|
1598
|
+
if (requireLocation) {
|
|
1599
|
+
const missingKeys = locationProperties.filter(
|
|
1600
|
+
key =>
|
|
1601
|
+
!hasOwnProperty(error, key) &&
|
|
1602
|
+
hasOwnProperty(message, key),
|
|
1357
1603
|
);
|
|
1358
|
-
} else if (hasSuggestions) {
|
|
1359
1604
|
assert.ok(
|
|
1360
|
-
|
|
1361
|
-
`Error
|
|
1605
|
+
missingKeys.length === 0,
|
|
1606
|
+
`Error is missing expected location properties: ${missingKeys.join(", ")}`,
|
|
1362
1607
|
);
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1608
|
+
}
|
|
1609
|
+
|
|
1610
|
+
if (Object.keys(expectedLocation).length > 0) {
|
|
1611
|
+
assert.deepStrictEqual(
|
|
1612
|
+
actualLocation,
|
|
1613
|
+
expectedLocation,
|
|
1614
|
+
"Actual error location does not match expected error location.",
|
|
1615
|
+
);
|
|
1616
|
+
}
|
|
1617
|
+
|
|
1618
|
+
assert.ok(
|
|
1619
|
+
!message.suggestions ||
|
|
1620
|
+
hasOwnProperty(error, "suggestions"),
|
|
1621
|
+
`Error at index ${i} has suggestions. Please specify 'suggestions' property on the test error object.`,
|
|
1622
|
+
);
|
|
1623
|
+
if (hasOwnProperty(error, "suggestions")) {
|
|
1624
|
+
// Support asserting there are no suggestions
|
|
1625
|
+
const expectsSuggestions = Array.isArray(
|
|
1626
|
+
error.suggestions,
|
|
1627
|
+
)
|
|
1628
|
+
? error.suggestions.length > 0
|
|
1629
|
+
: Boolean(error.suggestions);
|
|
1630
|
+
const hasSuggestions =
|
|
1631
|
+
message.suggestions !== void 0;
|
|
1632
|
+
|
|
1633
|
+
if (!hasSuggestions && expectsSuggestions) {
|
|
1634
|
+
assert.ok(
|
|
1635
|
+
!error.suggestions,
|
|
1636
|
+
`Error should have suggestions on error with message: "${message.message}"`,
|
|
1368
1637
|
);
|
|
1369
|
-
} else if (
|
|
1370
|
-
assert.
|
|
1371
|
-
|
|
1372
|
-
error.
|
|
1373
|
-
`Error should have ${error.suggestions.length} suggestions. Instead found ${message.suggestions.length} suggestions`,
|
|
1638
|
+
} else if (hasSuggestions) {
|
|
1639
|
+
assert.ok(
|
|
1640
|
+
expectsSuggestions,
|
|
1641
|
+
`Error should have no suggestions on error with message: "${message.message}"`,
|
|
1374
1642
|
);
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1643
|
+
if (typeof error.suggestions === "number") {
|
|
1644
|
+
assert.strictEqual(
|
|
1645
|
+
message.suggestions.length,
|
|
1646
|
+
error.suggestions,
|
|
1647
|
+
`Error should have ${error.suggestions} suggestions. Instead found ${message.suggestions.length} suggestions`,
|
|
1648
|
+
);
|
|
1649
|
+
} else if (
|
|
1650
|
+
Array.isArray(error.suggestions)
|
|
1651
|
+
) {
|
|
1652
|
+
assert.strictEqual(
|
|
1653
|
+
message.suggestions.length,
|
|
1654
|
+
error.suggestions.length,
|
|
1655
|
+
`Error should have ${error.suggestions.length} suggestions. Instead found ${message.suggestions.length} suggestions`,
|
|
1656
|
+
);
|
|
1657
|
+
|
|
1658
|
+
error.suggestions.forEach(
|
|
1659
|
+
(expectedSuggestion, index) => {
|
|
1387
1660
|
assert.ok(
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1661
|
+
typeof expectedSuggestion ===
|
|
1662
|
+
"object" &&
|
|
1663
|
+
expectedSuggestion !==
|
|
1664
|
+
null,
|
|
1665
|
+
"Test suggestion in 'suggestions' array must be an object.",
|
|
1392
1666
|
);
|
|
1393
|
-
|
|
1667
|
+
Object.keys(
|
|
1668
|
+
expectedSuggestion,
|
|
1669
|
+
).forEach(propertyName => {
|
|
1670
|
+
assert.ok(
|
|
1671
|
+
suggestionObjectParameters.has(
|
|
1672
|
+
propertyName,
|
|
1673
|
+
),
|
|
1674
|
+
`Invalid suggestion property name '${propertyName}'. Expected one of ${friendlySuggestionObjectParameterList}.`,
|
|
1675
|
+
);
|
|
1676
|
+
});
|
|
1394
1677
|
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1678
|
+
const actualSuggestion =
|
|
1679
|
+
message.suggestions[index];
|
|
1680
|
+
const suggestionPrefix = `Error Suggestion at index ${index}:`;
|
|
1398
1681
|
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
expectedSuggestion,
|
|
1402
|
-
"desc",
|
|
1403
|
-
)
|
|
1404
|
-
) {
|
|
1405
|
-
assert.ok(
|
|
1406
|
-
!hasOwnProperty(
|
|
1682
|
+
if (
|
|
1683
|
+
hasOwnProperty(
|
|
1407
1684
|
expectedSuggestion,
|
|
1408
|
-
"
|
|
1409
|
-
)
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1685
|
+
"desc",
|
|
1686
|
+
)
|
|
1687
|
+
) {
|
|
1688
|
+
assert.ok(
|
|
1689
|
+
!hasOwnProperty(
|
|
1690
|
+
expectedSuggestion,
|
|
1691
|
+
"data",
|
|
1692
|
+
),
|
|
1693
|
+
`${suggestionPrefix} Test should not specify both 'desc' and 'data'.`,
|
|
1694
|
+
);
|
|
1695
|
+
assert.ok(
|
|
1696
|
+
!hasOwnProperty(
|
|
1697
|
+
expectedSuggestion,
|
|
1698
|
+
"messageId",
|
|
1699
|
+
),
|
|
1700
|
+
`${suggestionPrefix} Test should not specify both 'desc' and 'messageId'.`,
|
|
1701
|
+
);
|
|
1702
|
+
assert.strictEqual(
|
|
1703
|
+
actualSuggestion.desc,
|
|
1704
|
+
expectedSuggestion.desc,
|
|
1705
|
+
`${suggestionPrefix} desc should be "${expectedSuggestion.desc}" but got "${actualSuggestion.desc}" instead.`,
|
|
1706
|
+
);
|
|
1707
|
+
} else if (
|
|
1708
|
+
hasOwnProperty(
|
|
1414
1709
|
expectedSuggestion,
|
|
1415
1710
|
"messageId",
|
|
1416
|
-
)
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
ruleHasMetaMessages,
|
|
1432
|
-
`${suggestionPrefix} Test can not use 'messageId' if rule under test doesn't define 'meta.messages'.`,
|
|
1433
|
-
);
|
|
1434
|
-
assert.ok(
|
|
1435
|
-
hasOwnProperty(
|
|
1436
|
-
rule.meta.messages,
|
|
1711
|
+
)
|
|
1712
|
+
) {
|
|
1713
|
+
assert.ok(
|
|
1714
|
+
ruleHasMetaMessages,
|
|
1715
|
+
`${suggestionPrefix} Test can not use 'messageId' if rule under test doesn't define 'meta.messages'.`,
|
|
1716
|
+
);
|
|
1717
|
+
assert.ok(
|
|
1718
|
+
hasOwnProperty(
|
|
1719
|
+
rule.meta.messages,
|
|
1720
|
+
expectedSuggestion.messageId,
|
|
1721
|
+
),
|
|
1722
|
+
`${suggestionPrefix} Test has invalid messageId '${expectedSuggestion.messageId}', the rule under test allows only one of ${friendlyIDList}.`,
|
|
1723
|
+
);
|
|
1724
|
+
assert.strictEqual(
|
|
1725
|
+
actualSuggestion.messageId,
|
|
1437
1726
|
expectedSuggestion.messageId,
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
);
|
|
1441
|
-
assert.strictEqual(
|
|
1442
|
-
actualSuggestion.messageId,
|
|
1443
|
-
expectedSuggestion.messageId,
|
|
1444
|
-
`${suggestionPrefix} messageId should be '${expectedSuggestion.messageId}' but got '${actualSuggestion.messageId}' instead.`,
|
|
1445
|
-
);
|
|
1727
|
+
`${suggestionPrefix} messageId should be '${expectedSuggestion.messageId}' but got '${actualSuggestion.messageId}' instead.`,
|
|
1728
|
+
);
|
|
1446
1729
|
|
|
1447
|
-
|
|
1448
|
-
getUnsubstitutedMessagePlaceholders(
|
|
1449
|
-
actualSuggestion.desc,
|
|
1730
|
+
const rawSuggestionMessage =
|
|
1450
1731
|
rule.meta.messages[
|
|
1451
1732
|
expectedSuggestion
|
|
1452
1733
|
.messageId
|
|
1453
|
-
]
|
|
1454
|
-
|
|
1455
|
-
|
|
1734
|
+
];
|
|
1735
|
+
const unsubstitutedPlaceholders =
|
|
1736
|
+
getUnsubstitutedMessagePlaceholders(
|
|
1737
|
+
actualSuggestion.desc,
|
|
1738
|
+
rawSuggestionMessage,
|
|
1739
|
+
expectedSuggestion.data,
|
|
1740
|
+
);
|
|
1456
1741
|
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1742
|
+
assert.ok(
|
|
1743
|
+
unsubstitutedPlaceholders.length ===
|
|
1744
|
+
0,
|
|
1745
|
+
`The message of the suggestion has ${unsubstitutedPlaceholders.length > 1 ? `unsubstituted placeholders: ${unsubstitutedPlaceholders.map(name => `'${name}'`).join(", ")}` : `an unsubstituted placeholder '${unsubstitutedPlaceholders[0]}'`}. Please provide the missing ${unsubstitutedPlaceholders.length > 1 ? "values" : "value"} via the 'data' property for the suggestion in the context.report() call.`,
|
|
1746
|
+
);
|
|
1462
1747
|
|
|
1463
|
-
|
|
1748
|
+
if (
|
|
1749
|
+
hasOwnProperty(
|
|
1750
|
+
expectedSuggestion,
|
|
1751
|
+
"data",
|
|
1752
|
+
)
|
|
1753
|
+
) {
|
|
1754
|
+
const unformattedMetaMessage =
|
|
1755
|
+
rule.meta.messages[
|
|
1756
|
+
expectedSuggestion
|
|
1757
|
+
.messageId
|
|
1758
|
+
];
|
|
1759
|
+
const rehydratedDesc =
|
|
1760
|
+
interpolate(
|
|
1761
|
+
unformattedMetaMessage,
|
|
1762
|
+
expectedSuggestion.data,
|
|
1763
|
+
);
|
|
1764
|
+
|
|
1765
|
+
assert.strictEqual(
|
|
1766
|
+
actualSuggestion.desc,
|
|
1767
|
+
rehydratedDesc,
|
|
1768
|
+
`${suggestionPrefix} Hydrated test desc "${rehydratedDesc}" does not match received desc "${actualSuggestion.desc}".`,
|
|
1769
|
+
);
|
|
1770
|
+
} else {
|
|
1771
|
+
const requiresDataProperty =
|
|
1772
|
+
requireData ===
|
|
1773
|
+
true ||
|
|
1774
|
+
requireData ===
|
|
1775
|
+
"suggestion";
|
|
1776
|
+
const hasPlaceholders =
|
|
1777
|
+
getMessagePlaceholders(
|
|
1778
|
+
rawSuggestionMessage,
|
|
1779
|
+
).length > 0;
|
|
1780
|
+
assert.ok(
|
|
1781
|
+
!requiresDataProperty ||
|
|
1782
|
+
!hasPlaceholders,
|
|
1783
|
+
`${suggestionPrefix} Suggestion should specify the 'data' property as the referenced message has placeholders.`,
|
|
1784
|
+
);
|
|
1785
|
+
}
|
|
1786
|
+
} else if (
|
|
1464
1787
|
hasOwnProperty(
|
|
1465
1788
|
expectedSuggestion,
|
|
1466
1789
|
"data",
|
|
1467
1790
|
)
|
|
1468
1791
|
) {
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
interpolate(
|
|
1476
|
-
unformattedMetaMessage,
|
|
1477
|
-
expectedSuggestion.data,
|
|
1478
|
-
);
|
|
1479
|
-
|
|
1480
|
-
assert.strictEqual(
|
|
1481
|
-
actualSuggestion.desc,
|
|
1482
|
-
rehydratedDesc,
|
|
1483
|
-
`${suggestionPrefix} Hydrated test desc "${rehydratedDesc}" does not match received desc "${actualSuggestion.desc}".`,
|
|
1792
|
+
assert.fail(
|
|
1793
|
+
`${suggestionPrefix} Test must specify 'messageId' if 'data' is used.`,
|
|
1794
|
+
);
|
|
1795
|
+
} else {
|
|
1796
|
+
assert.fail(
|
|
1797
|
+
`${suggestionPrefix} Test must specify either 'messageId' or 'desc'.`,
|
|
1484
1798
|
);
|
|
1485
1799
|
}
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
`${suggestionPrefix} Test must specify 'messageId' if 'data' is used.`,
|
|
1800
|
+
|
|
1801
|
+
assert.ok(
|
|
1802
|
+
hasOwnProperty(
|
|
1803
|
+
expectedSuggestion,
|
|
1804
|
+
"output",
|
|
1805
|
+
),
|
|
1806
|
+
`${suggestionPrefix} The "output" property is required.`,
|
|
1494
1807
|
);
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1808
|
+
const codeWithAppliedSuggestion =
|
|
1809
|
+
SourceCodeFixer.applyFixes(
|
|
1810
|
+
item.code,
|
|
1811
|
+
[actualSuggestion],
|
|
1812
|
+
).output;
|
|
1813
|
+
|
|
1814
|
+
// Verify if suggestion fix makes a syntax error or not.
|
|
1815
|
+
const errorMessageInSuggestion =
|
|
1816
|
+
linter
|
|
1817
|
+
.verify(
|
|
1818
|
+
codeWithAppliedSuggestion,
|
|
1819
|
+
result.configs,
|
|
1820
|
+
result.filename,
|
|
1821
|
+
)
|
|
1822
|
+
.find(m => m.fatal);
|
|
1823
|
+
|
|
1824
|
+
assert(
|
|
1825
|
+
!errorMessageInSuggestion,
|
|
1826
|
+
[
|
|
1827
|
+
"A fatal parsing error occurred in suggestion fix.",
|
|
1828
|
+
`Error: ${errorMessageInSuggestion && errorMessageInSuggestion.message}`,
|
|
1829
|
+
"Suggestion output:",
|
|
1830
|
+
codeWithAppliedSuggestion,
|
|
1831
|
+
].join("\n"),
|
|
1498
1832
|
);
|
|
1499
|
-
}
|
|
1500
1833
|
|
|
1501
|
-
|
|
1502
|
-
hasOwnProperty(
|
|
1503
|
-
expectedSuggestion,
|
|
1504
|
-
"output",
|
|
1505
|
-
),
|
|
1506
|
-
`${suggestionPrefix} The "output" property is required.`,
|
|
1507
|
-
);
|
|
1508
|
-
const codeWithAppliedSuggestion =
|
|
1509
|
-
SourceCodeFixer.applyFixes(
|
|
1510
|
-
item.code,
|
|
1511
|
-
[actualSuggestion],
|
|
1512
|
-
).output;
|
|
1513
|
-
|
|
1514
|
-
// Verify if suggestion fix makes a syntax error or not.
|
|
1515
|
-
const errorMessageInSuggestion =
|
|
1516
|
-
linter
|
|
1517
|
-
.verify(
|
|
1518
|
-
codeWithAppliedSuggestion,
|
|
1519
|
-
result.configs,
|
|
1520
|
-
result.filename,
|
|
1521
|
-
)
|
|
1522
|
-
.find(m => m.fatal);
|
|
1523
|
-
|
|
1524
|
-
assert(
|
|
1525
|
-
!errorMessageInSuggestion,
|
|
1526
|
-
[
|
|
1527
|
-
"A fatal parsing error occurred in suggestion fix.",
|
|
1528
|
-
`Error: ${errorMessageInSuggestion && errorMessageInSuggestion.message}`,
|
|
1529
|
-
"Suggestion output:",
|
|
1834
|
+
assert.strictEqual(
|
|
1530
1835
|
codeWithAppliedSuggestion,
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
} else {
|
|
1547
|
-
assert.fail(
|
|
1548
|
-
"Test error object property 'suggestions' should be an array or a number",
|
|
1549
|
-
);
|
|
1836
|
+
expectedSuggestion.output,
|
|
1837
|
+
`Expected the applied suggestion fix to match the test suggestion output for suggestion at index: ${index} on error with message: "${message.message}"`,
|
|
1838
|
+
);
|
|
1839
|
+
assert.notStrictEqual(
|
|
1840
|
+
expectedSuggestion.output,
|
|
1841
|
+
item.code,
|
|
1842
|
+
`The output of a suggestion should differ from the original source code for suggestion at index: ${index} on error with message: "${message.message}"`,
|
|
1843
|
+
);
|
|
1844
|
+
},
|
|
1845
|
+
);
|
|
1846
|
+
} else {
|
|
1847
|
+
assert.fail(
|
|
1848
|
+
"Test error object property 'suggestions' should be an array or a number",
|
|
1849
|
+
);
|
|
1850
|
+
}
|
|
1550
1851
|
}
|
|
1551
1852
|
}
|
|
1552
1853
|
}
|
|
1553
|
-
}
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1854
|
+
} catch (error) {
|
|
1855
|
+
if (error instanceof Error) {
|
|
1856
|
+
error.errorIndex = i;
|
|
1857
|
+
}
|
|
1858
|
+
throw error;
|
|
1558
1859
|
}
|
|
1559
1860
|
}
|
|
1560
1861
|
}
|
|
@@ -1599,7 +1900,7 @@ class RuleTester {
|
|
|
1599
1900
|
if (test.valid.length > 0) {
|
|
1600
1901
|
this.constructor.describe("valid", () => {
|
|
1601
1902
|
const seenTestCases = new Set();
|
|
1602
|
-
test.valid.forEach(valid => {
|
|
1903
|
+
test.valid.forEach((valid, index) => {
|
|
1603
1904
|
const item = normalizeTestCase(valid);
|
|
1604
1905
|
this.constructor[valid.only ? "itOnly" : "it"](
|
|
1605
1906
|
sanitize(item.name || item.code),
|
|
@@ -1608,6 +1909,21 @@ class RuleTester {
|
|
|
1608
1909
|
runHook(item, "before");
|
|
1609
1910
|
assertValidTestCase(item, seenTestCases);
|
|
1610
1911
|
testValidTemplate(item);
|
|
1912
|
+
} catch (error) {
|
|
1913
|
+
if (error instanceof Error) {
|
|
1914
|
+
error.scenarioType = "valid";
|
|
1915
|
+
error.scenarioIndex = index;
|
|
1916
|
+
error.stack = error.stack.replace(
|
|
1917
|
+
/^ +at /mu,
|
|
1918
|
+
[
|
|
1919
|
+
` roughly at RuleTester.run.valid[${index}] (${estimateTestLocation(`valid[${index}]`)})`,
|
|
1920
|
+
` roughly at RuleTester.run.valid (${estimateTestLocation("valid")})`,
|
|
1921
|
+
` at RuleTester.run (${estimateTestLocation("root")})`,
|
|
1922
|
+
" at ",
|
|
1923
|
+
].join("\n"),
|
|
1924
|
+
);
|
|
1925
|
+
}
|
|
1926
|
+
throw error;
|
|
1611
1927
|
} finally {
|
|
1612
1928
|
runHook(item, "after");
|
|
1613
1929
|
}
|
|
@@ -1620,7 +1936,7 @@ class RuleTester {
|
|
|
1620
1936
|
if (test.invalid.length > 0) {
|
|
1621
1937
|
this.constructor.describe("invalid", () => {
|
|
1622
1938
|
const seenTestCases = new Set();
|
|
1623
|
-
test.invalid.forEach(invalid => {
|
|
1939
|
+
test.invalid.forEach((invalid, index) => {
|
|
1624
1940
|
const item = normalizeTestCase(invalid);
|
|
1625
1941
|
this.constructor[item.only ? "itOnly" : "it"](
|
|
1626
1942
|
sanitize(item.name || item.code),
|
|
@@ -1631,8 +1947,31 @@ class RuleTester {
|
|
|
1631
1947
|
item,
|
|
1632
1948
|
seenTestCases,
|
|
1633
1949
|
ruleName,
|
|
1950
|
+
test.assertionOptions,
|
|
1634
1951
|
);
|
|
1635
1952
|
testInvalidTemplate(item);
|
|
1953
|
+
} catch (error) {
|
|
1954
|
+
if (error instanceof Error) {
|
|
1955
|
+
error.scenarioType = "invalid";
|
|
1956
|
+
error.scenarioIndex = index;
|
|
1957
|
+
const errorIndex = error.errorIndex;
|
|
1958
|
+
error.stack = error.stack.replace(
|
|
1959
|
+
/^ +at /mu,
|
|
1960
|
+
[
|
|
1961
|
+
...(typeof errorIndex ===
|
|
1962
|
+
"number"
|
|
1963
|
+
? [
|
|
1964
|
+
` roughly at RuleTester.run.invalid[${index}].error[${errorIndex}] (${estimateTestLocation(`invalid[${index}].errors[${errorIndex}]`)})`,
|
|
1965
|
+
]
|
|
1966
|
+
: []),
|
|
1967
|
+
` roughly at RuleTester.run.invalid[${index}] (${estimateTestLocation(`invalid[${index}]`)})`,
|
|
1968
|
+
` roughly at RuleTester.run.invalid (${estimateTestLocation("invalid")})`,
|
|
1969
|
+
` at RuleTester.run (${estimateTestLocation("root")})`,
|
|
1970
|
+
" at ",
|
|
1971
|
+
].join("\n"),
|
|
1972
|
+
);
|
|
1973
|
+
}
|
|
1974
|
+
throw error;
|
|
1636
1975
|
} finally {
|
|
1637
1976
|
runHook(item, "after");
|
|
1638
1977
|
}
|