eslint 8.57.0 → 9.2.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.
Files changed (156) hide show
  1. package/README.md +31 -28
  2. package/bin/eslint.js +4 -3
  3. package/conf/ecma-version.js +16 -0
  4. package/conf/globals.js +1 -0
  5. package/conf/rule-type-list.json +3 -1
  6. package/lib/api.js +7 -11
  7. package/lib/cli-engine/cli-engine.js +14 -3
  8. package/lib/cli-engine/formatters/formatters-meta.json +1 -29
  9. package/lib/cli-engine/lint-result-cache.js +2 -2
  10. package/lib/cli.js +115 -36
  11. package/lib/config/default-config.js +3 -0
  12. package/lib/config/flat-config-array.js +110 -24
  13. package/lib/config/flat-config-helpers.js +41 -20
  14. package/lib/config/flat-config-schema.js +1 -7
  15. package/lib/config/rule-validator.js +42 -6
  16. package/lib/eslint/eslint-helpers.js +116 -58
  17. package/lib/eslint/eslint.js +892 -377
  18. package/lib/eslint/index.js +2 -2
  19. package/lib/eslint/legacy-eslint.js +728 -0
  20. package/lib/linter/apply-disable-directives.js +59 -31
  21. package/lib/linter/code-path-analysis/code-path-analyzer.js +0 -1
  22. package/lib/linter/code-path-analysis/code-path.js +32 -30
  23. package/lib/linter/code-path-analysis/fork-context.js +1 -1
  24. package/lib/linter/config-comment-parser.js +8 -11
  25. package/lib/linter/index.js +1 -3
  26. package/lib/linter/interpolate.js +24 -2
  27. package/lib/linter/linter.js +428 -207
  28. package/lib/linter/report-translator.js +3 -3
  29. package/lib/linter/rules.js +6 -15
  30. package/lib/linter/source-code-fixer.js +1 -1
  31. package/lib/linter/timing.js +16 -8
  32. package/lib/options.js +35 -3
  33. package/lib/rule-tester/index.js +3 -1
  34. package/lib/rule-tester/rule-tester.js +424 -347
  35. package/lib/rules/array-bracket-newline.js +1 -1
  36. package/lib/rules/array-bracket-spacing.js +1 -1
  37. package/lib/rules/block-scoped-var.js +1 -1
  38. package/lib/rules/callback-return.js +2 -2
  39. package/lib/rules/camelcase.js +3 -5
  40. package/lib/rules/capitalized-comments.js +10 -7
  41. package/lib/rules/comma-dangle.js +1 -1
  42. package/lib/rules/comma-style.js +2 -2
  43. package/lib/rules/complexity.js +14 -1
  44. package/lib/rules/constructor-super.js +99 -100
  45. package/lib/rules/default-case.js +1 -1
  46. package/lib/rules/eol-last.js +2 -2
  47. package/lib/rules/function-paren-newline.js +2 -2
  48. package/lib/rules/indent-legacy.js +5 -5
  49. package/lib/rules/indent.js +5 -5
  50. package/lib/rules/index.js +1 -2
  51. package/lib/rules/key-spacing.js +2 -2
  52. package/lib/rules/line-comment-position.js +1 -1
  53. package/lib/rules/lines-around-directive.js +2 -2
  54. package/lib/rules/max-depth.js +1 -1
  55. package/lib/rules/max-len.js +3 -3
  56. package/lib/rules/max-lines.js +3 -3
  57. package/lib/rules/max-nested-callbacks.js +1 -1
  58. package/lib/rules/max-params.js +1 -1
  59. package/lib/rules/max-statements.js +1 -1
  60. package/lib/rules/multiline-comment-style.js +7 -7
  61. package/lib/rules/new-cap.js +1 -1
  62. package/lib/rules/newline-after-var.js +1 -1
  63. package/lib/rules/newline-before-return.js +1 -1
  64. package/lib/rules/no-case-declarations.js +13 -1
  65. package/lib/rules/no-constant-binary-expression.js +7 -8
  66. package/lib/rules/no-constant-condition.js +18 -7
  67. package/lib/rules/no-constructor-return.js +2 -2
  68. package/lib/rules/no-dupe-class-members.js +2 -2
  69. package/lib/rules/no-else-return.js +1 -1
  70. package/lib/rules/no-empty-function.js +2 -2
  71. package/lib/rules/no-empty-static-block.js +1 -1
  72. package/lib/rules/no-extend-native.js +1 -2
  73. package/lib/rules/no-extra-semi.js +1 -1
  74. package/lib/rules/no-fallthrough.js +41 -16
  75. package/lib/rules/no-implicit-coercion.js +66 -24
  76. package/lib/rules/no-inner-declarations.js +23 -2
  77. package/lib/rules/no-invalid-regexp.js +1 -1
  78. package/lib/rules/no-invalid-this.js +1 -1
  79. package/lib/rules/no-lone-blocks.js +3 -3
  80. package/lib/rules/no-loss-of-precision.js +1 -1
  81. package/lib/rules/no-misleading-character-class.js +225 -69
  82. package/lib/rules/no-mixed-spaces-and-tabs.js +1 -1
  83. package/lib/rules/no-multiple-empty-lines.js +1 -1
  84. package/lib/rules/no-new-native-nonconstructor.js +1 -1
  85. package/lib/rules/no-new-symbol.js +8 -1
  86. package/lib/rules/no-restricted-globals.js +1 -1
  87. package/lib/rules/no-restricted-imports.js +186 -40
  88. package/lib/rules/no-restricted-modules.js +2 -2
  89. package/lib/rules/no-return-await.js +1 -1
  90. package/lib/rules/no-sequences.js +1 -0
  91. package/lib/rules/no-this-before-super.js +45 -13
  92. package/lib/rules/no-trailing-spaces.js +2 -3
  93. package/lib/rules/no-unneeded-ternary.js +1 -1
  94. package/lib/rules/no-unsafe-optional-chaining.js +1 -1
  95. package/lib/rules/no-unused-private-class-members.js +1 -1
  96. package/lib/rules/no-unused-vars.js +197 -36
  97. package/lib/rules/no-useless-assignment.js +566 -0
  98. package/lib/rules/no-useless-backreference.js +1 -1
  99. package/lib/rules/no-useless-computed-key.js +2 -2
  100. package/lib/rules/no-useless-return.js +7 -2
  101. package/lib/rules/object-curly-spacing.js +3 -3
  102. package/lib/rules/object-property-newline.js +1 -1
  103. package/lib/rules/one-var.js +5 -5
  104. package/lib/rules/padded-blocks.js +7 -7
  105. package/lib/rules/prefer-arrow-callback.js +3 -3
  106. package/lib/rules/prefer-reflect.js +1 -1
  107. package/lib/rules/prefer-regex-literals.js +1 -1
  108. package/lib/rules/prefer-template.js +1 -1
  109. package/lib/rules/radix.js +2 -2
  110. package/lib/rules/semi-style.js +1 -1
  111. package/lib/rules/sort-imports.js +1 -1
  112. package/lib/rules/sort-keys.js +1 -1
  113. package/lib/rules/sort-vars.js +1 -1
  114. package/lib/rules/space-unary-ops.js +1 -1
  115. package/lib/rules/strict.js +1 -1
  116. package/lib/rules/use-isnan.js +101 -7
  117. package/lib/rules/utils/ast-utils.js +16 -7
  118. package/lib/rules/utils/char-source.js +240 -0
  119. package/lib/rules/utils/lazy-loading-rule-map.js +1 -1
  120. package/lib/rules/utils/unicode/index.js +9 -4
  121. package/lib/rules/yield-star-spacing.js +1 -1
  122. package/lib/shared/runtime-info.js +1 -0
  123. package/lib/shared/serialization.js +55 -0
  124. package/lib/shared/stats.js +30 -0
  125. package/lib/shared/string-utils.js +9 -11
  126. package/lib/shared/types.js +35 -1
  127. package/lib/source-code/index.js +3 -1
  128. package/lib/source-code/source-code.js +299 -85
  129. package/lib/source-code/token-store/backward-token-cursor.js +3 -3
  130. package/lib/source-code/token-store/cursors.js +4 -2
  131. package/lib/source-code/token-store/forward-token-comment-cursor.js +3 -3
  132. package/lib/source-code/token-store/forward-token-cursor.js +3 -3
  133. package/lib/source-code/token-store/index.js +2 -2
  134. package/lib/unsupported-api.js +3 -5
  135. package/messages/no-config-found.js +1 -1
  136. package/messages/plugin-conflict.js +1 -1
  137. package/messages/plugin-invalid.js +1 -1
  138. package/messages/plugin-missing.js +1 -1
  139. package/package.json +32 -29
  140. package/conf/config-schema.js +0 -93
  141. package/lib/cli-engine/formatters/checkstyle.js +0 -60
  142. package/lib/cli-engine/formatters/compact.js +0 -60
  143. package/lib/cli-engine/formatters/jslint-xml.js +0 -41
  144. package/lib/cli-engine/formatters/junit.js +0 -82
  145. package/lib/cli-engine/formatters/tap.js +0 -95
  146. package/lib/cli-engine/formatters/unix.js +0 -58
  147. package/lib/cli-engine/formatters/visualstudio.js +0 -63
  148. package/lib/cli-engine/xml-escape.js +0 -34
  149. package/lib/eslint/flat-eslint.js +0 -1155
  150. package/lib/rule-tester/flat-rule-tester.js +0 -1131
  151. package/lib/rules/require-jsdoc.js +0 -122
  152. package/lib/rules/utils/patterns/letters.js +0 -36
  153. package/lib/rules/valid-jsdoc.js +0 -516
  154. package/lib/shared/config-validator.js +0 -347
  155. package/lib/shared/deprecation-warnings.js +0 -58
  156. package/lib/shared/relative-module-resolver.js +0 -50
@@ -1,1131 +0,0 @@
1
- /**
2
- * @fileoverview Mocha/Jest test wrapper
3
- * @author Ilya Volodin
4
- */
5
- "use strict";
6
-
7
- /* globals describe, it -- Mocha globals */
8
-
9
- //------------------------------------------------------------------------------
10
- // Requirements
11
- //------------------------------------------------------------------------------
12
-
13
- const
14
- assert = require("assert"),
15
- util = require("util"),
16
- path = require("path"),
17
- equal = require("fast-deep-equal"),
18
- Traverser = require("../shared/traverser"),
19
- { getRuleOptionsSchema } = require("../config/flat-config-helpers"),
20
- { Linter, SourceCodeFixer, interpolate } = require("../linter"),
21
- CodePath = require("../linter/code-path-analysis/code-path");
22
-
23
- const { FlatConfigArray } = require("../config/flat-config-array");
24
- const { defaultConfig } = require("../config/default-config");
25
-
26
- const ajv = require("../shared/ajv")({ strictDefaults: true });
27
-
28
- const parserSymbol = Symbol.for("eslint.RuleTester.parser");
29
- const { SourceCode } = require("../source-code");
30
- const { ConfigArraySymbol } = require("@humanwhocodes/config-array");
31
-
32
- //------------------------------------------------------------------------------
33
- // Typedefs
34
- //------------------------------------------------------------------------------
35
-
36
- /** @typedef {import("../shared/types").Parser} Parser */
37
- /** @typedef {import("../shared/types").LanguageOptions} LanguageOptions */
38
- /** @typedef {import("../shared/types").Rule} Rule */
39
-
40
-
41
- /**
42
- * A test case that is expected to pass lint.
43
- * @typedef {Object} ValidTestCase
44
- * @property {string} [name] Name for the test case.
45
- * @property {string} code Code for the test case.
46
- * @property {any[]} [options] Options for the test case.
47
- * @property {LanguageOptions} [languageOptions] The language options to use in the test case.
48
- * @property {{ [name: string]: any }} [settings] Settings for the test case.
49
- * @property {string} [filename] The fake filename for the test case. Useful for rules that make assertion about filenames.
50
- * @property {boolean} [only] Run only this test case or the subset of test cases with this property.
51
- */
52
-
53
- /**
54
- * A test case that is expected to fail lint.
55
- * @typedef {Object} InvalidTestCase
56
- * @property {string} [name] Name for the test case.
57
- * @property {string} code Code for the test case.
58
- * @property {number | Array<TestCaseError | string | RegExp>} errors Expected errors.
59
- * @property {string | null} [output] The expected code after autofixes are applied. If set to `null`, the test runner will assert that no autofix is suggested.
60
- * @property {any[]} [options] Options for the test case.
61
- * @property {{ [name: string]: any }} [settings] Settings for the test case.
62
- * @property {string} [filename] The fake filename for the test case. Useful for rules that make assertion about filenames.
63
- * @property {LanguageOptions} [languageOptions] The language options to use in the test case.
64
- * @property {boolean} [only] Run only this test case or the subset of test cases with this property.
65
- */
66
-
67
- /**
68
- * A description of a reported error used in a rule tester test.
69
- * @typedef {Object} TestCaseError
70
- * @property {string | RegExp} [message] Message.
71
- * @property {string} [messageId] Message ID.
72
- * @property {string} [type] The type of the reported AST node.
73
- * @property {{ [name: string]: string }} [data] The data used to fill the message template.
74
- * @property {number} [line] The 1-based line number of the reported start location.
75
- * @property {number} [column] The 1-based column number of the reported start location.
76
- * @property {number} [endLine] The 1-based line number of the reported end location.
77
- * @property {number} [endColumn] The 1-based column number of the reported end location.
78
- */
79
-
80
- //------------------------------------------------------------------------------
81
- // Private Members
82
- //------------------------------------------------------------------------------
83
-
84
- /*
85
- * testerDefaultConfig must not be modified as it allows to reset the tester to
86
- * the initial default configuration
87
- */
88
- const testerDefaultConfig = { rules: {} };
89
-
90
- /*
91
- * RuleTester uses this config as its default. This can be overwritten via
92
- * setDefaultConfig().
93
- */
94
- let sharedDefaultConfig = { rules: {} };
95
-
96
- /*
97
- * List every parameters possible on a test case that are not related to eslint
98
- * configuration
99
- */
100
- const RuleTesterParameters = [
101
- "name",
102
- "code",
103
- "filename",
104
- "options",
105
- "errors",
106
- "output",
107
- "only"
108
- ];
109
-
110
- /*
111
- * All allowed property names in error objects.
112
- */
113
- const errorObjectParameters = new Set([
114
- "message",
115
- "messageId",
116
- "data",
117
- "type",
118
- "line",
119
- "column",
120
- "endLine",
121
- "endColumn",
122
- "suggestions"
123
- ]);
124
- const friendlyErrorObjectParameterList = `[${[...errorObjectParameters].map(key => `'${key}'`).join(", ")}]`;
125
-
126
- /*
127
- * All allowed property names in suggestion objects.
128
- */
129
- const suggestionObjectParameters = new Set([
130
- "desc",
131
- "messageId",
132
- "data",
133
- "output"
134
- ]);
135
- const friendlySuggestionObjectParameterList = `[${[...suggestionObjectParameters].map(key => `'${key}'`).join(", ")}]`;
136
-
137
- const forbiddenMethods = [
138
- "applyInlineConfig",
139
- "applyLanguageOptions",
140
- "finalize"
141
- ];
142
-
143
- /** @type {Map<string,WeakSet>} */
144
- const forbiddenMethodCalls = new Map(forbiddenMethods.map(methodName => ([methodName, new WeakSet()])));
145
-
146
- const hasOwnProperty = Function.call.bind(Object.hasOwnProperty);
147
-
148
- /**
149
- * Clones a given value deeply.
150
- * Note: This ignores `parent` property.
151
- * @param {any} x A value to clone.
152
- * @returns {any} A cloned value.
153
- */
154
- function cloneDeeplyExcludesParent(x) {
155
- if (typeof x === "object" && x !== null) {
156
- if (Array.isArray(x)) {
157
- return x.map(cloneDeeplyExcludesParent);
158
- }
159
-
160
- const retv = {};
161
-
162
- for (const key in x) {
163
- if (key !== "parent" && hasOwnProperty(x, key)) {
164
- retv[key] = cloneDeeplyExcludesParent(x[key]);
165
- }
166
- }
167
-
168
- return retv;
169
- }
170
-
171
- return x;
172
- }
173
-
174
- /**
175
- * Freezes a given value deeply.
176
- * @param {any} x A value to freeze.
177
- * @returns {void}
178
- */
179
- function freezeDeeply(x) {
180
- if (typeof x === "object" && x !== null) {
181
- if (Array.isArray(x)) {
182
- x.forEach(freezeDeeply);
183
- } else {
184
- for (const key in x) {
185
- if (key !== "parent" && hasOwnProperty(x, key)) {
186
- freezeDeeply(x[key]);
187
- }
188
- }
189
- }
190
- Object.freeze(x);
191
- }
192
- }
193
-
194
- /**
195
- * Replace control characters by `\u00xx` form.
196
- * @param {string} text The text to sanitize.
197
- * @returns {string} The sanitized text.
198
- */
199
- function sanitize(text) {
200
- if (typeof text !== "string") {
201
- return "";
202
- }
203
- return text.replace(
204
- /[\u0000-\u0009\u000b-\u001a]/gu, // eslint-disable-line no-control-regex -- Escaping controls
205
- c => `\\u${c.codePointAt(0).toString(16).padStart(4, "0")}`
206
- );
207
- }
208
-
209
- /**
210
- * Define `start`/`end` properties as throwing error.
211
- * @param {string} objName Object name used for error messages.
212
- * @param {ASTNode} node The node to define.
213
- * @returns {void}
214
- */
215
- function defineStartEndAsError(objName, node) {
216
- Object.defineProperties(node, {
217
- start: {
218
- get() {
219
- throw new Error(`Use ${objName}.range[0] instead of ${objName}.start`);
220
- },
221
- configurable: true,
222
- enumerable: false
223
- },
224
- end: {
225
- get() {
226
- throw new Error(`Use ${objName}.range[1] instead of ${objName}.end`);
227
- },
228
- configurable: true,
229
- enumerable: false
230
- }
231
- });
232
- }
233
-
234
-
235
- /**
236
- * Define `start`/`end` properties of all nodes of the given AST as throwing error.
237
- * @param {ASTNode} ast The root node to errorize `start`/`end` properties.
238
- * @param {Object} [visitorKeys] Visitor keys to be used for traversing the given ast.
239
- * @returns {void}
240
- */
241
- function defineStartEndAsErrorInTree(ast, visitorKeys) {
242
- Traverser.traverse(ast, { visitorKeys, enter: defineStartEndAsError.bind(null, "node") });
243
- ast.tokens.forEach(defineStartEndAsError.bind(null, "token"));
244
- ast.comments.forEach(defineStartEndAsError.bind(null, "token"));
245
- }
246
-
247
- /**
248
- * Wraps the given parser in order to intercept and modify return values from the `parse` and `parseForESLint` methods, for test purposes.
249
- * In particular, to modify ast nodes, tokens and comments to throw on access to their `start` and `end` properties.
250
- * @param {Parser} parser Parser object.
251
- * @returns {Parser} Wrapped parser object.
252
- */
253
- function wrapParser(parser) {
254
-
255
- if (typeof parser.parseForESLint === "function") {
256
- return {
257
- [parserSymbol]: parser,
258
- parseForESLint(...args) {
259
- const ret = parser.parseForESLint(...args);
260
-
261
- defineStartEndAsErrorInTree(ret.ast, ret.visitorKeys);
262
- return ret;
263
- }
264
- };
265
- }
266
-
267
- return {
268
- [parserSymbol]: parser,
269
- parse(...args) {
270
- const ast = parser.parse(...args);
271
-
272
- defineStartEndAsErrorInTree(ast);
273
- return ast;
274
- }
275
- };
276
- }
277
-
278
- /**
279
- * Function to replace `SourceCode.prototype.getComments`.
280
- * @returns {void}
281
- * @throws {Error} Deprecation message.
282
- */
283
- function getCommentsDeprecation() {
284
- throw new Error(
285
- "`SourceCode#getComments()` is deprecated and will be removed in a future major version. Use `getCommentsBefore()`, `getCommentsAfter()`, and `getCommentsInside()` instead."
286
- );
287
- }
288
-
289
- /**
290
- * Emit a deprecation warning if rule uses CodePath#currentSegments.
291
- * @param {string} ruleName Name of the rule.
292
- * @returns {void}
293
- */
294
- function emitCodePathCurrentSegmentsWarning(ruleName) {
295
- if (!emitCodePathCurrentSegmentsWarning[`warned-${ruleName}`]) {
296
- emitCodePathCurrentSegmentsWarning[`warned-${ruleName}`] = true;
297
- process.emitWarning(
298
- `"${ruleName}" rule uses CodePath#currentSegments and will stop working in ESLint v9. Please read the documentation for how to update your code: https://eslint.org/docs/latest/extend/code-path-analysis#usage-examples`,
299
- "DeprecationWarning"
300
- );
301
- }
302
- }
303
-
304
- /**
305
- * Function to replace forbidden `SourceCode` methods. Allows just one call per method.
306
- * @param {string} methodName The name of the method to forbid.
307
- * @param {Function} prototype The prototype with the original method to call.
308
- * @returns {Function} The function that throws the error.
309
- */
310
- function throwForbiddenMethodError(methodName, prototype) {
311
-
312
- const original = prototype[methodName];
313
-
314
- return function(...args) {
315
-
316
- const called = forbiddenMethodCalls.get(methodName);
317
-
318
- /* eslint-disable no-invalid-this -- needed to operate as a method. */
319
- if (!called.has(this)) {
320
- called.add(this);
321
-
322
- return original.apply(this, args);
323
- }
324
- /* eslint-enable no-invalid-this -- not needed past this point */
325
-
326
- throw new Error(
327
- `\`SourceCode#${methodName}()\` cannot be called inside a rule.`
328
- );
329
- };
330
- }
331
-
332
- //------------------------------------------------------------------------------
333
- // Public Interface
334
- //------------------------------------------------------------------------------
335
-
336
- // default separators for testing
337
- const DESCRIBE = Symbol("describe");
338
- const IT = Symbol("it");
339
- const IT_ONLY = Symbol("itOnly");
340
-
341
- /**
342
- * This is `it` default handler if `it` don't exist.
343
- * @this {Mocha}
344
- * @param {string} text The description of the test case.
345
- * @param {Function} method The logic of the test case.
346
- * @throws {Error} Any error upon execution of `method`.
347
- * @returns {any} Returned value of `method`.
348
- */
349
- function itDefaultHandler(text, method) {
350
- try {
351
- return method.call(this);
352
- } catch (err) {
353
- if (err instanceof assert.AssertionError) {
354
- err.message += ` (${util.inspect(err.actual)} ${err.operator} ${util.inspect(err.expected)})`;
355
- }
356
- throw err;
357
- }
358
- }
359
-
360
- /**
361
- * This is `describe` default handler if `describe` don't exist.
362
- * @this {Mocha}
363
- * @param {string} text The description of the test case.
364
- * @param {Function} method The logic of the test case.
365
- * @returns {any} Returned value of `method`.
366
- */
367
- function describeDefaultHandler(text, method) {
368
- return method.call(this);
369
- }
370
-
371
- /**
372
- * Mocha test wrapper.
373
- */
374
- class FlatRuleTester {
375
-
376
- /**
377
- * Creates a new instance of RuleTester.
378
- * @param {Object} [testerConfig] Optional, extra configuration for the tester
379
- */
380
- constructor(testerConfig = {}) {
381
-
382
- /**
383
- * The configuration to use for this tester. Combination of the tester
384
- * configuration and the default configuration.
385
- * @type {Object}
386
- */
387
- this.testerConfig = [
388
- sharedDefaultConfig,
389
- testerConfig,
390
- { rules: { "rule-tester/validate-ast": "error" } }
391
- ];
392
-
393
- this.linter = new Linter({ configType: "flat" });
394
- }
395
-
396
- /**
397
- * Set the configuration to use for all future tests
398
- * @param {Object} config the configuration to use.
399
- * @throws {TypeError} If non-object config.
400
- * @returns {void}
401
- */
402
- static setDefaultConfig(config) {
403
- if (typeof config !== "object" || config === null) {
404
- throw new TypeError("FlatRuleTester.setDefaultConfig: config must be an object");
405
- }
406
- sharedDefaultConfig = config;
407
-
408
- // Make sure the rules object exists since it is assumed to exist later
409
- sharedDefaultConfig.rules = sharedDefaultConfig.rules || {};
410
- }
411
-
412
- /**
413
- * Get the current configuration used for all tests
414
- * @returns {Object} the current configuration
415
- */
416
- static getDefaultConfig() {
417
- return sharedDefaultConfig;
418
- }
419
-
420
- /**
421
- * Reset the configuration to the initial configuration of the tester removing
422
- * any changes made until now.
423
- * @returns {void}
424
- */
425
- static resetDefaultConfig() {
426
- sharedDefaultConfig = {
427
- rules: {
428
- ...testerDefaultConfig.rules
429
- }
430
- };
431
- }
432
-
433
-
434
- /*
435
- * If people use `mocha test.js --watch` command, `describe` and `it` function
436
- * instances are different for each execution. So `describe` and `it` should get fresh instance
437
- * always.
438
- */
439
- static get describe() {
440
- return (
441
- this[DESCRIBE] ||
442
- (typeof describe === "function" ? describe : describeDefaultHandler)
443
- );
444
- }
445
-
446
- static set describe(value) {
447
- this[DESCRIBE] = value;
448
- }
449
-
450
- static get it() {
451
- return (
452
- this[IT] ||
453
- (typeof it === "function" ? it : itDefaultHandler)
454
- );
455
- }
456
-
457
- static set it(value) {
458
- this[IT] = value;
459
- }
460
-
461
- /**
462
- * Adds the `only` property to a test to run it in isolation.
463
- * @param {string | ValidTestCase | InvalidTestCase} item A single test to run by itself.
464
- * @returns {ValidTestCase | InvalidTestCase} The test with `only` set.
465
- */
466
- static only(item) {
467
- if (typeof item === "string") {
468
- return { code: item, only: true };
469
- }
470
-
471
- return { ...item, only: true };
472
- }
473
-
474
- static get itOnly() {
475
- if (typeof this[IT_ONLY] === "function") {
476
- return this[IT_ONLY];
477
- }
478
- if (typeof this[IT] === "function" && typeof this[IT].only === "function") {
479
- return Function.bind.call(this[IT].only, this[IT]);
480
- }
481
- if (typeof it === "function" && typeof it.only === "function") {
482
- return Function.bind.call(it.only, it);
483
- }
484
-
485
- if (typeof this[DESCRIBE] === "function" || typeof this[IT] === "function") {
486
- throw new Error(
487
- "Set `RuleTester.itOnly` to use `only` with a custom test framework.\n" +
488
- "See https://eslint.org/docs/latest/integrate/nodejs-api#customizing-ruletester for more."
489
- );
490
- }
491
- if (typeof it === "function") {
492
- throw new Error("The current test framework does not support exclusive tests with `only`.");
493
- }
494
- throw new Error("To use `only`, use RuleTester with a test framework that provides `it.only()` like Mocha.");
495
- }
496
-
497
- static set itOnly(value) {
498
- this[IT_ONLY] = value;
499
- }
500
-
501
-
502
- /**
503
- * Adds a new rule test to execute.
504
- * @param {string} ruleName The name of the rule to run.
505
- * @param {Function | Rule} rule The rule to test.
506
- * @param {{
507
- * valid: (ValidTestCase | string)[],
508
- * invalid: InvalidTestCase[]
509
- * }} test The collection of tests to run.
510
- * @throws {TypeError|Error} If non-object `test`, or if a required
511
- * scenario of the given type is missing.
512
- * @returns {void}
513
- */
514
- run(ruleName, rule, test) {
515
-
516
- const testerConfig = this.testerConfig,
517
- requiredScenarios = ["valid", "invalid"],
518
- scenarioErrors = [],
519
- linter = this.linter,
520
- ruleId = `rule-to-test/${ruleName}`;
521
-
522
- if (!test || typeof test !== "object") {
523
- throw new TypeError(`Test Scenarios for rule ${ruleName} : Could not find test scenario object`);
524
- }
525
-
526
- requiredScenarios.forEach(scenarioType => {
527
- if (!test[scenarioType]) {
528
- scenarioErrors.push(`Could not find any ${scenarioType} test scenarios`);
529
- }
530
- });
531
-
532
- if (scenarioErrors.length > 0) {
533
- throw new Error([
534
- `Test Scenarios for rule ${ruleName} is invalid:`
535
- ].concat(scenarioErrors).join("\n"));
536
- }
537
-
538
- const baseConfig = [
539
- { files: ["**"] }, // Make sure the default config matches for all files
540
- {
541
- plugins: {
542
-
543
- // copy root plugin over
544
- "@": {
545
-
546
- /*
547
- * Parsers are wrapped to detect more errors, so this needs
548
- * to be a new object for each call to run(), otherwise the
549
- * parsers will be wrapped multiple times.
550
- */
551
- parsers: {
552
- ...defaultConfig[0].plugins["@"].parsers
553
- },
554
-
555
- /*
556
- * The rules key on the default plugin is a proxy to lazy-load
557
- * just the rules that are needed. So, don't create a new object
558
- * here, just use the default one to keep that performance
559
- * enhancement.
560
- */
561
- rules: defaultConfig[0].plugins["@"].rules
562
- },
563
- "rule-to-test": {
564
- rules: {
565
- [ruleName]: Object.assign({}, rule, {
566
-
567
- // Create a wrapper rule that freezes the `context` properties.
568
- create(context) {
569
- freezeDeeply(context.options);
570
- freezeDeeply(context.settings);
571
- freezeDeeply(context.parserOptions);
572
-
573
- // freezeDeeply(context.languageOptions);
574
-
575
- return (typeof rule === "function" ? rule : rule.create)(context);
576
- }
577
- })
578
- }
579
- }
580
- },
581
- languageOptions: {
582
- ...defaultConfig[0].languageOptions
583
- }
584
- },
585
- ...defaultConfig.slice(1)
586
- ];
587
-
588
- /**
589
- * Run the rule for the given item
590
- * @param {string|Object} item Item to run the rule against
591
- * @throws {Error} If an invalid schema.
592
- * @returns {Object} Eslint run result
593
- * @private
594
- */
595
- function runRuleForItem(item) {
596
- const flatConfigArrayOptions = {
597
- baseConfig
598
- };
599
-
600
- if (item.filename) {
601
- flatConfigArrayOptions.basePath = path.parse(item.filename).root;
602
- }
603
-
604
- const configs = new FlatConfigArray(testerConfig, flatConfigArrayOptions);
605
-
606
- /*
607
- * Modify the returned config so that the parser is wrapped to catch
608
- * access of the start/end properties. This method is called just
609
- * once per code snippet being tested, so each test case gets a clean
610
- * parser.
611
- */
612
- configs[ConfigArraySymbol.finalizeConfig] = function(...args) {
613
-
614
- // can't do super here :(
615
- const proto = Object.getPrototypeOf(this);
616
- const calculatedConfig = proto[ConfigArraySymbol.finalizeConfig].apply(this, args);
617
-
618
- // wrap the parser to catch start/end property access
619
- calculatedConfig.languageOptions.parser = wrapParser(calculatedConfig.languageOptions.parser);
620
- return calculatedConfig;
621
- };
622
-
623
- let code, filename, output, beforeAST, afterAST;
624
-
625
- if (typeof item === "string") {
626
- code = item;
627
- } else {
628
- code = item.code;
629
-
630
- /*
631
- * Assumes everything on the item is a config except for the
632
- * parameters used by this tester
633
- */
634
- const itemConfig = { ...item };
635
-
636
- for (const parameter of RuleTesterParameters) {
637
- delete itemConfig[parameter];
638
- }
639
-
640
- // wrap any parsers
641
- if (itemConfig.languageOptions && itemConfig.languageOptions.parser) {
642
-
643
- const parser = itemConfig.languageOptions.parser;
644
-
645
- if (parser && typeof parser !== "object") {
646
- throw new Error("Parser must be an object with a parse() or parseForESLint() method.");
647
- }
648
-
649
- }
650
-
651
- /*
652
- * Create the config object from the tester config and this item
653
- * specific configurations.
654
- */
655
- configs.push(itemConfig);
656
- }
657
-
658
- if (item.filename) {
659
- filename = item.filename;
660
- }
661
-
662
- let ruleConfig = 1;
663
-
664
- if (hasOwnProperty(item, "options")) {
665
- assert(Array.isArray(item.options), "options must be an array");
666
- ruleConfig = [1, ...item.options];
667
- }
668
-
669
- configs.push({
670
- rules: {
671
- [ruleId]: ruleConfig
672
- }
673
- });
674
-
675
- const schema = getRuleOptionsSchema(rule);
676
-
677
- /*
678
- * Setup AST getters.
679
- * The goal is to check whether or not AST was modified when
680
- * running the rule under test.
681
- */
682
- configs.push({
683
- plugins: {
684
- "rule-tester": {
685
- rules: {
686
- "validate-ast": {
687
- create() {
688
- return {
689
- Program(node) {
690
- beforeAST = cloneDeeplyExcludesParent(node);
691
- },
692
- "Program:exit"(node) {
693
- afterAST = node;
694
- }
695
- };
696
- }
697
- }
698
- }
699
- }
700
- }
701
- });
702
-
703
- if (schema) {
704
- ajv.validateSchema(schema);
705
-
706
- if (ajv.errors) {
707
- const errors = ajv.errors.map(error => {
708
- const field = error.dataPath[0] === "." ? error.dataPath.slice(1) : error.dataPath;
709
-
710
- return `\t${field}: ${error.message}`;
711
- }).join("\n");
712
-
713
- throw new Error([`Schema for rule ${ruleName} is invalid:`, errors]);
714
- }
715
-
716
- /*
717
- * `ajv.validateSchema` checks for errors in the structure of the schema (by comparing the schema against a "meta-schema"),
718
- * and it reports those errors individually. However, there are other types of schema errors that only occur when compiling
719
- * the schema (e.g. using invalid defaults in a schema), and only one of these errors can be reported at a time. As a result,
720
- * the schema is compiled here separately from checking for `validateSchema` errors.
721
- */
722
- try {
723
- ajv.compile(schema);
724
- } catch (err) {
725
- throw new Error(`Schema for rule ${ruleName} is invalid: ${err.message}`);
726
- }
727
- }
728
-
729
- // check for validation errors
730
- try {
731
- configs.normalizeSync();
732
- configs.getConfig("test.js");
733
- } catch (error) {
734
- error.message = `ESLint configuration in rule-tester is invalid: ${error.message}`;
735
- throw error;
736
- }
737
-
738
- // Verify the code.
739
- const { getComments, applyLanguageOptions, applyInlineConfig, finalize } = SourceCode.prototype;
740
- const originalCurrentSegments = Object.getOwnPropertyDescriptor(CodePath.prototype, "currentSegments");
741
- let messages;
742
-
743
- try {
744
- SourceCode.prototype.getComments = getCommentsDeprecation;
745
- Object.defineProperty(CodePath.prototype, "currentSegments", {
746
- get() {
747
- emitCodePathCurrentSegmentsWarning(ruleName);
748
- return originalCurrentSegments.get.call(this);
749
- }
750
- });
751
-
752
- forbiddenMethods.forEach(methodName => {
753
- SourceCode.prototype[methodName] = throwForbiddenMethodError(methodName, SourceCode.prototype);
754
- });
755
-
756
- messages = linter.verify(code, configs, filename);
757
- } finally {
758
- SourceCode.prototype.getComments = getComments;
759
- Object.defineProperty(CodePath.prototype, "currentSegments", originalCurrentSegments);
760
- SourceCode.prototype.applyInlineConfig = applyInlineConfig;
761
- SourceCode.prototype.applyLanguageOptions = applyLanguageOptions;
762
- SourceCode.prototype.finalize = finalize;
763
- }
764
-
765
-
766
- const fatalErrorMessage = messages.find(m => m.fatal);
767
-
768
- assert(!fatalErrorMessage, `A fatal parsing error occurred: ${fatalErrorMessage && fatalErrorMessage.message}`);
769
-
770
- // Verify if autofix makes a syntax error or not.
771
- if (messages.some(m => m.fix)) {
772
- output = SourceCodeFixer.applyFixes(code, messages).output;
773
- const errorMessageInFix = linter.verify(output, configs, filename).find(m => m.fatal);
774
-
775
- assert(!errorMessageInFix, [
776
- "A fatal parsing error occurred in autofix.",
777
- `Error: ${errorMessageInFix && errorMessageInFix.message}`,
778
- "Autofix output:",
779
- output
780
- ].join("\n"));
781
- } else {
782
- output = code;
783
- }
784
-
785
- return {
786
- messages,
787
- output,
788
- beforeAST,
789
- afterAST: cloneDeeplyExcludesParent(afterAST)
790
- };
791
- }
792
-
793
- /**
794
- * Check if the AST was changed
795
- * @param {ASTNode} beforeAST AST node before running
796
- * @param {ASTNode} afterAST AST node after running
797
- * @returns {void}
798
- * @private
799
- */
800
- function assertASTDidntChange(beforeAST, afterAST) {
801
- if (!equal(beforeAST, afterAST)) {
802
- assert.fail("Rule should not modify AST.");
803
- }
804
- }
805
-
806
- /**
807
- * Check if the template is valid or not
808
- * all valid cases go through this
809
- * @param {string|Object} item Item to run the rule against
810
- * @returns {void}
811
- * @private
812
- */
813
- function testValidTemplate(item) {
814
- const code = typeof item === "object" ? item.code : item;
815
-
816
- assert.ok(typeof code === "string", "Test case must specify a string value for 'code'");
817
- if (item.name) {
818
- assert.ok(typeof item.name === "string", "Optional test case property 'name' must be a string");
819
- }
820
-
821
- const result = runRuleForItem(item);
822
- const messages = result.messages;
823
-
824
- assert.strictEqual(messages.length, 0, util.format("Should have no errors but had %d: %s",
825
- messages.length,
826
- util.inspect(messages)));
827
-
828
- assertASTDidntChange(result.beforeAST, result.afterAST);
829
- }
830
-
831
- /**
832
- * Asserts that the message matches its expected value. If the expected
833
- * value is a regular expression, it is checked against the actual
834
- * value.
835
- * @param {string} actual Actual value
836
- * @param {string|RegExp} expected Expected value
837
- * @returns {void}
838
- * @private
839
- */
840
- function assertMessageMatches(actual, expected) {
841
- if (expected instanceof RegExp) {
842
-
843
- // assert.js doesn't have a built-in RegExp match function
844
- assert.ok(
845
- expected.test(actual),
846
- `Expected '${actual}' to match ${expected}`
847
- );
848
- } else {
849
- assert.strictEqual(actual, expected);
850
- }
851
- }
852
-
853
- /**
854
- * Check if the template is invalid or not
855
- * all invalid cases go through this.
856
- * @param {string|Object} item Item to run the rule against
857
- * @returns {void}
858
- * @private
859
- */
860
- function testInvalidTemplate(item) {
861
- assert.ok(typeof item.code === "string", "Test case must specify a string value for 'code'");
862
- if (item.name) {
863
- assert.ok(typeof item.name === "string", "Optional test case property 'name' must be a string");
864
- }
865
- assert.ok(item.errors || item.errors === 0,
866
- `Did not specify errors for an invalid test of ${ruleName}`);
867
-
868
- if (Array.isArray(item.errors) && item.errors.length === 0) {
869
- assert.fail("Invalid cases must have at least one error");
870
- }
871
-
872
- const ruleHasMetaMessages = hasOwnProperty(rule, "meta") && hasOwnProperty(rule.meta, "messages");
873
- const friendlyIDList = ruleHasMetaMessages ? `[${Object.keys(rule.meta.messages).map(key => `'${key}'`).join(", ")}]` : null;
874
-
875
- const result = runRuleForItem(item);
876
- const messages = result.messages;
877
-
878
- if (typeof item.errors === "number") {
879
-
880
- if (item.errors === 0) {
881
- assert.fail("Invalid cases must have 'error' value greater than 0");
882
- }
883
-
884
- assert.strictEqual(messages.length, item.errors, util.format("Should have %d error%s but had %d: %s",
885
- item.errors,
886
- item.errors === 1 ? "" : "s",
887
- messages.length,
888
- util.inspect(messages)));
889
- } else {
890
- assert.strictEqual(
891
- messages.length, item.errors.length, util.format(
892
- "Should have %d error%s but had %d: %s",
893
- item.errors.length,
894
- item.errors.length === 1 ? "" : "s",
895
- messages.length,
896
- util.inspect(messages)
897
- )
898
- );
899
-
900
- const hasMessageOfThisRule = messages.some(m => m.ruleId === ruleId);
901
-
902
- for (let i = 0, l = item.errors.length; i < l; i++) {
903
- const error = item.errors[i];
904
- const message = messages[i];
905
-
906
- assert(hasMessageOfThisRule, "Error rule name should be the same as the name of the rule being tested");
907
-
908
- if (typeof error === "string" || error instanceof RegExp) {
909
-
910
- // Just an error message.
911
- assertMessageMatches(message.message, error);
912
- } else if (typeof error === "object" && error !== null) {
913
-
914
- /*
915
- * Error object.
916
- * This may have a message, messageId, data, node type, line, and/or
917
- * column.
918
- */
919
-
920
- Object.keys(error).forEach(propertyName => {
921
- assert.ok(
922
- errorObjectParameters.has(propertyName),
923
- `Invalid error property name '${propertyName}'. Expected one of ${friendlyErrorObjectParameterList}.`
924
- );
925
- });
926
-
927
- if (hasOwnProperty(error, "message")) {
928
- assert.ok(!hasOwnProperty(error, "messageId"), "Error should not specify both 'message' and a 'messageId'.");
929
- assert.ok(!hasOwnProperty(error, "data"), "Error should not specify both 'data' and 'message'.");
930
- assertMessageMatches(message.message, error.message);
931
- } else if (hasOwnProperty(error, "messageId")) {
932
- assert.ok(
933
- ruleHasMetaMessages,
934
- "Error can not use 'messageId' if rule under test doesn't define 'meta.messages'."
935
- );
936
- if (!hasOwnProperty(rule.meta.messages, error.messageId)) {
937
- assert(false, `Invalid messageId '${error.messageId}'. Expected one of ${friendlyIDList}.`);
938
- }
939
- assert.strictEqual(
940
- message.messageId,
941
- error.messageId,
942
- `messageId '${message.messageId}' does not match expected messageId '${error.messageId}'.`
943
- );
944
- if (hasOwnProperty(error, "data")) {
945
-
946
- /*
947
- * if data was provided, then directly compare the returned message to a synthetic
948
- * interpolated message using the same message ID and data provided in the test.
949
- * See https://github.com/eslint/eslint/issues/9890 for context.
950
- */
951
- const unformattedOriginalMessage = rule.meta.messages[error.messageId];
952
- const rehydratedMessage = interpolate(unformattedOriginalMessage, error.data);
953
-
954
- assert.strictEqual(
955
- message.message,
956
- rehydratedMessage,
957
- `Hydrated message "${rehydratedMessage}" does not match "${message.message}"`
958
- );
959
- }
960
- }
961
-
962
- assert.ok(
963
- hasOwnProperty(error, "data") ? hasOwnProperty(error, "messageId") : true,
964
- "Error must specify 'messageId' if 'data' is used."
965
- );
966
-
967
- if (error.type) {
968
- assert.strictEqual(message.nodeType, error.type, `Error type should be ${error.type}, found ${message.nodeType}`);
969
- }
970
-
971
- if (hasOwnProperty(error, "line")) {
972
- assert.strictEqual(message.line, error.line, `Error line should be ${error.line}`);
973
- }
974
-
975
- if (hasOwnProperty(error, "column")) {
976
- assert.strictEqual(message.column, error.column, `Error column should be ${error.column}`);
977
- }
978
-
979
- if (hasOwnProperty(error, "endLine")) {
980
- assert.strictEqual(message.endLine, error.endLine, `Error endLine should be ${error.endLine}`);
981
- }
982
-
983
- if (hasOwnProperty(error, "endColumn")) {
984
- assert.strictEqual(message.endColumn, error.endColumn, `Error endColumn should be ${error.endColumn}`);
985
- }
986
-
987
- if (hasOwnProperty(error, "suggestions")) {
988
-
989
- // Support asserting there are no suggestions
990
- if (!error.suggestions || (Array.isArray(error.suggestions) && error.suggestions.length === 0)) {
991
- if (Array.isArray(message.suggestions) && message.suggestions.length > 0) {
992
- assert.fail(`Error should have no suggestions on error with message: "${message.message}"`);
993
- }
994
- } else {
995
- assert.strictEqual(Array.isArray(message.suggestions), true, `Error should have an array of suggestions. Instead received "${message.suggestions}" on error with message: "${message.message}"`);
996
- assert.strictEqual(message.suggestions.length, error.suggestions.length, `Error should have ${error.suggestions.length} suggestions. Instead found ${message.suggestions.length} suggestions`);
997
-
998
- error.suggestions.forEach((expectedSuggestion, index) => {
999
- assert.ok(
1000
- typeof expectedSuggestion === "object" && expectedSuggestion !== null,
1001
- "Test suggestion in 'suggestions' array must be an object."
1002
- );
1003
- Object.keys(expectedSuggestion).forEach(propertyName => {
1004
- assert.ok(
1005
- suggestionObjectParameters.has(propertyName),
1006
- `Invalid suggestion property name '${propertyName}'. Expected one of ${friendlySuggestionObjectParameterList}.`
1007
- );
1008
- });
1009
-
1010
- const actualSuggestion = message.suggestions[index];
1011
- const suggestionPrefix = `Error Suggestion at index ${index} :`;
1012
-
1013
- if (hasOwnProperty(expectedSuggestion, "desc")) {
1014
- assert.ok(
1015
- !hasOwnProperty(expectedSuggestion, "data"),
1016
- `${suggestionPrefix} Test should not specify both 'desc' and 'data'.`
1017
- );
1018
- assert.strictEqual(
1019
- actualSuggestion.desc,
1020
- expectedSuggestion.desc,
1021
- `${suggestionPrefix} desc should be "${expectedSuggestion.desc}" but got "${actualSuggestion.desc}" instead.`
1022
- );
1023
- }
1024
-
1025
- if (hasOwnProperty(expectedSuggestion, "messageId")) {
1026
- assert.ok(
1027
- ruleHasMetaMessages,
1028
- `${suggestionPrefix} Test can not use 'messageId' if rule under test doesn't define 'meta.messages'.`
1029
- );
1030
- assert.ok(
1031
- hasOwnProperty(rule.meta.messages, expectedSuggestion.messageId),
1032
- `${suggestionPrefix} Test has invalid messageId '${expectedSuggestion.messageId}', the rule under test allows only one of ${friendlyIDList}.`
1033
- );
1034
- assert.strictEqual(
1035
- actualSuggestion.messageId,
1036
- expectedSuggestion.messageId,
1037
- `${suggestionPrefix} messageId should be '${expectedSuggestion.messageId}' but got '${actualSuggestion.messageId}' instead.`
1038
- );
1039
- if (hasOwnProperty(expectedSuggestion, "data")) {
1040
- const unformattedMetaMessage = rule.meta.messages[expectedSuggestion.messageId];
1041
- const rehydratedDesc = interpolate(unformattedMetaMessage, expectedSuggestion.data);
1042
-
1043
- assert.strictEqual(
1044
- actualSuggestion.desc,
1045
- rehydratedDesc,
1046
- `${suggestionPrefix} Hydrated test desc "${rehydratedDesc}" does not match received desc "${actualSuggestion.desc}".`
1047
- );
1048
- }
1049
- } else {
1050
- assert.ok(
1051
- !hasOwnProperty(expectedSuggestion, "data"),
1052
- `${suggestionPrefix} Test must specify 'messageId' if 'data' is used.`
1053
- );
1054
- }
1055
-
1056
- if (hasOwnProperty(expectedSuggestion, "output")) {
1057
- const codeWithAppliedSuggestion = SourceCodeFixer.applyFixes(item.code, [actualSuggestion]).output;
1058
-
1059
- assert.strictEqual(codeWithAppliedSuggestion, expectedSuggestion.output, `Expected the applied suggestion fix to match the test suggestion output for suggestion at index: ${index} on error with message: "${message.message}"`);
1060
- }
1061
- });
1062
- }
1063
- }
1064
- } else {
1065
-
1066
- // Message was an unexpected type
1067
- assert.fail(`Error should be a string, object, or RegExp, but found (${util.inspect(message)})`);
1068
- }
1069
- }
1070
- }
1071
-
1072
- if (hasOwnProperty(item, "output")) {
1073
- if (item.output === null) {
1074
- assert.strictEqual(
1075
- result.output,
1076
- item.code,
1077
- "Expected no autofixes to be suggested"
1078
- );
1079
- } else {
1080
- assert.strictEqual(result.output, item.output, "Output is incorrect.");
1081
- }
1082
- } else {
1083
- assert.strictEqual(
1084
- result.output,
1085
- item.code,
1086
- "The rule fixed the code. Please add 'output' property."
1087
- );
1088
- }
1089
-
1090
- assertASTDidntChange(result.beforeAST, result.afterAST);
1091
- }
1092
-
1093
- /*
1094
- * This creates a mocha test suite and pipes all supplied info through
1095
- * one of the templates above.
1096
- * The test suites for valid/invalid are created conditionally as
1097
- * test runners (eg. vitest) fail for empty test suites.
1098
- */
1099
- this.constructor.describe(ruleName, () => {
1100
- if (test.valid.length > 0) {
1101
- this.constructor.describe("valid", () => {
1102
- test.valid.forEach(valid => {
1103
- this.constructor[valid.only ? "itOnly" : "it"](
1104
- sanitize(typeof valid === "object" ? valid.name || valid.code : valid),
1105
- () => {
1106
- testValidTemplate(valid);
1107
- }
1108
- );
1109
- });
1110
- });
1111
- }
1112
-
1113
- if (test.invalid.length > 0) {
1114
- this.constructor.describe("invalid", () => {
1115
- test.invalid.forEach(invalid => {
1116
- this.constructor[invalid.only ? "itOnly" : "it"](
1117
- sanitize(invalid.name || invalid.code),
1118
- () => {
1119
- testInvalidTemplate(invalid);
1120
- }
1121
- );
1122
- });
1123
- });
1124
- }
1125
- });
1126
- }
1127
- }
1128
-
1129
- FlatRuleTester[DESCRIBE] = FlatRuleTester[IT] = FlatRuleTester[IT_ONLY] = null;
1130
-
1131
- module.exports = FlatRuleTester;