@typescript-eslint/rule-tester 8.59.5-alpha.5 → 8.59.5-alpha.6

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.
@@ -30,6 +30,11 @@ export declare class RuleTester extends TestFramework {
30
30
  */
31
31
  static only<MessageIds extends string, Options extends readonly unknown[]>(item: InvalidTestCase<MessageIds, Options>): InvalidTestCase<MessageIds, Options>;
32
32
  defineRule(name: string, rule: AnyRuleModule): void;
33
+ /**
34
+ * Verifies that the assertion options are valid
35
+ * @throws {Error} if the options aren't the correct type
36
+ */
37
+ private verifyAssertionOptions;
33
38
  /**
34
39
  * Adds a new rule test to execute.
35
40
  */
@@ -346,10 +346,31 @@ class RuleTester extends TestFramework_1.TestFramework {
346
346
  item[prop]();
347
347
  }
348
348
  }
349
+ /**
350
+ * Verifies that the assertion options are valid
351
+ * @throws {Error} if the options aren't the correct type
352
+ */
353
+ verifyAssertionOptions(assertionOption) {
354
+ if (assertionOption == null) {
355
+ return;
356
+ }
357
+ const dataOption = assertionOption.requireData;
358
+ if (dataOption != null &&
359
+ typeof dataOption !== 'boolean' &&
360
+ dataOption !== 'error' &&
361
+ dataOption !== 'suggestion') {
362
+ throw new Error("The assertion option `requireData` should be of type boolean | 'error' | 'suggestion'");
363
+ }
364
+ const locationOption = assertionOption.requireLocation;
365
+ if (locationOption != null && typeof locationOption !== 'boolean') {
366
+ throw new Error('The assertion option `requireLocation` should be of type boolean');
367
+ }
368
+ }
349
369
  /**
350
370
  * Adds a new rule test to execute.
351
371
  */
352
372
  run(ruleName, rule, test) {
373
+ this.verifyAssertionOptions(test.assertionOptions);
353
374
  const constructor = this.constructor;
354
375
  if (this.#testerConfig.dependencyConstraints &&
355
376
  !(0, dependencyConstraints_1.satisfiesAllDependencyConstraints)(this.#testerConfig.dependencyConstraints)) {
@@ -431,7 +452,7 @@ class RuleTester extends TestFramework_1.TestFramework {
431
452
  this.#runHook(invalid, 'before');
432
453
  this.#testInvalidTemplate(ruleName, rule,
433
454
  // no need to pass no infer type parameter down to private methods
434
- invalid, seenInvalidTestCases);
455
+ invalid, seenInvalidTestCases, test.assertionOptions);
435
456
  }
436
457
  finally {
437
458
  this.#runHook(invalid, 'after');
@@ -644,7 +665,7 @@ class RuleTester extends TestFramework_1.TestFramework {
644
665
  * Check if the template is invalid or not
645
666
  * all invalid cases go through this.
646
667
  */
647
- #testInvalidTemplate(ruleName, rule, item, seenInvalidTestCases) {
668
+ #testInvalidTemplate(ruleName, rule, item, seenInvalidTestCases, assertionOptions = {}) {
648
669
  node_assert_1.default.ok(typeof item.code === 'string', "Test case must specify a string value for 'code'");
649
670
  if (item.name) {
650
671
  node_assert_1.default.ok(typeof item.name === 'string', "Optional test case property 'name' must be a string");
@@ -689,6 +710,7 @@ class RuleTester extends TestFramework_1.TestFramework {
689
710
  else {
690
711
  node_assert_1.default.strictEqual(messages.length, item.errors.length, node_util_1.default.format('Should have %d error%s but had %d: %s', item.errors.length, item.errors.length === 1 ? '' : 's', messages.length, node_util_1.default.inspect(messages)));
691
712
  const hasMessageOfThisRule = messages.some(m => m.ruleId === ruleName);
713
+ const { requireData = false, requireLocation = false } = assertionOptions;
692
714
  // console.log({ messages });
693
715
  for (let i = 0, l = item.errors.length; i < l; i++) {
694
716
  const error = item.errors[i];
@@ -734,6 +756,12 @@ class RuleTester extends TestFramework_1.TestFramework {
734
756
  const rehydratedMessage = (0, interpolate_1.interpolate)(unformattedOriginalMessage, error.data);
735
757
  node_assert_1.default.strictEqual(message.message, rehydratedMessage, `Hydrated message "${rehydratedMessage}" does not match "${message.message}"`);
736
758
  }
759
+ else {
760
+ const requiresDataProperty = requireData === true || requireData === 'error';
761
+ const hasPlaceholders = getMessagePlaceholders(rule.meta.messages[error.messageId])
762
+ .length > 0;
763
+ node_assert_1.default.ok(!requiresDataProperty || !hasPlaceholders, `Error should specify the 'data' property as the referenced message has placeholders.`);
764
+ }
737
765
  }
738
766
  else {
739
767
  node_assert_1.default.fail("Test error must specify either a 'messageId' or 'message'.");
@@ -750,6 +778,11 @@ class RuleTester extends TestFramework_1.TestFramework {
750
778
  if ((0, hasOwnProperty_1.hasOwnProperty)(error, 'endColumn')) {
751
779
  node_assert_1.default.strictEqual(message.endColumn, error.endColumn, `Error endColumn should be ${error.endColumn}`);
752
780
  }
781
+ if (requireLocation) {
782
+ const locationProperties = ['line', 'column', 'endLine', 'endColumn'];
783
+ const missingKeys = locationProperties.filter(key => !(0, hasOwnProperty_1.hasOwnProperty)(error, key) && (0, hasOwnProperty_1.hasOwnProperty)(message, key));
784
+ node_assert_1.default.ok(missingKeys.length === 0, `Error is missing expected location properties: ${missingKeys.join(', ')}`);
785
+ }
753
786
  node_assert_1.default.ok(!message.suggestions || (0, hasOwnProperty_1.hasOwnProperty)(error, 'suggestions'), `Error at index ${i} has suggestions. Please specify 'suggestions' property on the test error object.`);
754
787
  if ((0, hasOwnProperty_1.hasOwnProperty)(error, 'suggestions')) {
755
788
  // Support asserting there are no suggestions
@@ -792,13 +825,20 @@ class RuleTester extends TestFramework_1.TestFramework {
792
825
  node_assert_1.default.ok(ruleHasMetaMessages, `${suggestionPrefix} Test can not use 'messageId' if rule under test doesn't define 'meta.messages'.`);
793
826
  node_assert_1.default.ok((0, hasOwnProperty_1.hasOwnProperty)(rule.meta.messages, expectedSuggestion.messageId), `${suggestionPrefix} Test has invalid messageId '${expectedSuggestion.messageId}', the rule under test allows only one of ${friendlyIDList}.`);
794
827
  node_assert_1.default.strictEqual(actualSuggestion.messageId, expectedSuggestion.messageId, `${suggestionPrefix} messageId should be '${expectedSuggestion.messageId}' but got '${actualSuggestion.messageId}' instead.`);
795
- const unsubstitutedPlaceholders = getUnsubstitutedMessagePlaceholders(actualSuggestion.desc, rule.meta.messages[expectedSuggestion.messageId], expectedSuggestion.data);
828
+ const rawSuggestionMessage = rule.meta.messages[expectedSuggestion.messageId];
829
+ const unsubstitutedPlaceholders = getUnsubstitutedMessagePlaceholders(actualSuggestion.desc, rawSuggestionMessage, expectedSuggestion.data);
796
830
  node_assert_1.default.ok(unsubstitutedPlaceholders.length === 0, `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.`);
797
831
  if ((0, hasOwnProperty_1.hasOwnProperty)(expectedSuggestion, 'data')) {
798
832
  const unformattedMetaMessage = rule.meta.messages[expectedSuggestion.messageId];
799
833
  const rehydratedDesc = (0, interpolate_1.interpolate)(unformattedMetaMessage, expectedSuggestion.data);
800
834
  node_assert_1.default.strictEqual(actualSuggestion.desc, rehydratedDesc, `${suggestionPrefix} Hydrated test desc "${rehydratedDesc}" does not match received desc "${actualSuggestion.desc}".`);
801
835
  }
836
+ else {
837
+ const requiresDataProperty = requireData === true || requireData === 'suggestion';
838
+ const hasPlaceholders = getMessagePlaceholders(rawSuggestionMessage).length >
839
+ 0;
840
+ node_assert_1.default.ok(!requiresDataProperty || !hasPlaceholders, `${suggestionPrefix} Suggestion should specify the 'data' property as the referenced message has placeholders.`);
841
+ }
802
842
  }
803
843
  else if ((0, hasOwnProperty_1.hasOwnProperty)(expectedSuggestion, 'data')) {
804
844
  node_assert_1.default.fail(`${suggestionPrefix} Test must specify 'messageId' if 'data' is used.`);
@@ -0,0 +1,14 @@
1
+ export interface AssertionOptions {
2
+ /**
3
+ * - If `true`, each error object and each suggestion object must also specify `data` if the message referenced by `messageId` has placeholders.
4
+ * - If `'error'`, each error object must also specify `data` if the message referenced by `messageId` has placeholders.
5
+ * - If `'suggestion'`, each suggestion object must also specify `data` if the message referenced by `messageId` has placeholders.
6
+ * @default `false`
7
+ */
8
+ readonly requireData?: boolean | 'error' | 'suggestion';
9
+ /**
10
+ * If `true`, each `errors` block must contain location properties `line`, `column`, `endLine`, and `endColumn`. Properties `endLine` and `endColumn` may be omitted if the actual error does not contain them.
11
+ * @default `false`
12
+ */
13
+ readonly requireLocation?: boolean;
14
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -1,3 +1,4 @@
1
+ import type { AssertionOptions } from './AssertionOptions';
1
2
  import type { InvalidTestCase } from './InvalidTestCase';
2
3
  import type { RuleTesterConfig } from './RuleTesterConfig';
3
4
  import type { ValidTestCase } from './ValidTestCase';
@@ -6,10 +7,12 @@ type Mutable<T> = {
6
7
  };
7
8
  export type TesterConfigWithDefaults = Mutable<Required<Pick<RuleTesterConfig, 'defaultFilenames' | 'languageOptions' | 'rules'>> & RuleTesterConfig>;
8
9
  export interface RunTests<MessageIds extends string, Options extends readonly unknown[]> {
10
+ readonly assertionOptions?: AssertionOptions;
9
11
  readonly invalid: readonly InvalidTestCase<MessageIds, Options>[];
10
12
  readonly valid: readonly (string | ValidTestCase<Options>)[];
11
13
  }
12
14
  export interface NormalizedRunTests<MessageIds extends string, Options extends readonly unknown[]> {
15
+ readonly assertionOptions?: AssertionOptions;
13
16
  readonly invalid: readonly InvalidTestCase<MessageIds, Options>[];
14
17
  readonly valid: readonly ValidTestCase<Options>[];
15
18
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@typescript-eslint/rule-tester",
3
- "version": "8.59.5-alpha.5",
3
+ "version": "8.59.5-alpha.6",
4
4
  "description": "Tooling to test ESLint rules",
5
5
  "files": [
6
6
  "dist",
@@ -40,9 +40,9 @@
40
40
  "json-stable-stringify-without-jsonify": "^1.0.1",
41
41
  "lodash.merge": "4.6.2",
42
42
  "semver": "^7.7.3",
43
- "@typescript-eslint/parser": "8.59.5-alpha.5",
44
- "@typescript-eslint/utils": "8.59.5-alpha.5",
45
- "@typescript-eslint/typescript-estree": "8.59.5-alpha.5"
43
+ "@typescript-eslint/parser": "8.59.5-alpha.6",
44
+ "@typescript-eslint/typescript-estree": "8.59.5-alpha.6",
45
+ "@typescript-eslint/utils": "8.59.5-alpha.6"
46
46
  },
47
47
  "peerDependencies": {
48
48
  "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",