eslint 7.23.0 → 7.27.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 (62) hide show
  1. package/CHANGELOG.md +54 -0
  2. package/README.md +7 -2
  3. package/bin/eslint.js +2 -12
  4. package/lib/cli-engine/cli-engine.js +2 -7
  5. package/lib/cli-engine/file-enumerator.js +1 -1
  6. package/lib/cli-engine/formatters/html.js +193 -9
  7. package/lib/init/autoconfig.js +2 -2
  8. package/lib/init/config-file.js +1 -0
  9. package/lib/init/config-initializer.js +14 -1
  10. package/lib/init/npm-utils.js +1 -0
  11. package/lib/linter/apply-disable-directives.js +15 -3
  12. package/lib/linter/linter.js +9 -7
  13. package/lib/linter/node-event-generator.js +43 -6
  14. package/lib/rule-tester/rule-tester.js +14 -10
  15. package/lib/rules/comma-dangle.js +16 -7
  16. package/lib/rules/comma-spacing.js +1 -1
  17. package/lib/rules/complexity.js +2 -3
  18. package/lib/rules/consistent-return.js +2 -2
  19. package/lib/rules/eol-last.js +2 -7
  20. package/lib/rules/indent.js +8 -9
  21. package/lib/rules/max-lines-per-function.js +2 -3
  22. package/lib/rules/max-lines.js +32 -7
  23. package/lib/rules/max-params.js +2 -3
  24. package/lib/rules/max-statements.js +2 -3
  25. package/lib/rules/no-fallthrough.js +2 -8
  26. package/lib/rules/no-implicit-coercion.js +37 -0
  27. package/lib/rules/no-multi-assign.js +15 -2
  28. package/lib/rules/no-restricted-imports.js +61 -24
  29. package/lib/rules/no-unused-vars.js +53 -16
  30. package/lib/rules/no-useless-backreference.js +1 -2
  31. package/lib/rules/no-useless-computed-key.js +8 -2
  32. package/lib/rules/no-warning-comments.js +1 -1
  33. package/lib/rules/object-curly-newline.js +19 -4
  34. package/lib/rules/radix.js +19 -3
  35. package/lib/rules/require-atomic-updates.js +23 -20
  36. package/lib/rules/spaced-comment.js +2 -2
  37. package/lib/rules/utils/ast-utils.js +2 -2
  38. package/lib/shared/deprecation-warnings.js +12 -3
  39. package/lib/shared/string-utils.js +22 -0
  40. package/lib/source-code/source-code.js +6 -5
  41. package/lib/source-code/token-store/utils.js +4 -12
  42. package/messages/{all-files-ignored.txt → all-files-ignored.js} +10 -2
  43. package/messages/extend-config-missing.js +13 -0
  44. package/messages/failed-to-read-json.js +11 -0
  45. package/messages/file-not-found.js +10 -0
  46. package/messages/{no-config-found.txt → no-config-found.js} +9 -1
  47. package/messages/plugin-conflict.js +22 -0
  48. package/messages/plugin-invalid.js +16 -0
  49. package/messages/plugin-missing.js +19 -0
  50. package/messages/{print-config-with-directory-path.txt → print-config-with-directory-path.js} +6 -0
  51. package/messages/whitespace-found.js +11 -0
  52. package/package.json +10 -13
  53. package/lib/cli-engine/formatters/html-template-message.html +0 -8
  54. package/lib/cli-engine/formatters/html-template-page.html +0 -115
  55. package/lib/cli-engine/formatters/html-template-result.html +0 -6
  56. package/messages/extend-config-missing.txt +0 -5
  57. package/messages/failed-to-read-json.txt +0 -3
  58. package/messages/file-not-found.txt +0 -2
  59. package/messages/plugin-conflict.txt +0 -7
  60. package/messages/plugin-invalid.txt +0 -8
  61. package/messages/plugin-missing.txt +0 -11
  62. package/messages/whitespace-found.txt +0 -3
@@ -44,7 +44,8 @@ const
44
44
  assert = require("assert"),
45
45
  path = require("path"),
46
46
  util = require("util"),
47
- lodash = require("lodash"),
47
+ merge = require("lodash.merge"),
48
+ equal = require("fast-deep-equal"),
48
49
  Traverser = require("../../lib/shared/traverser"),
49
50
  { getRuleOptionsSchema, validate } = require("../shared/config-validator"),
50
51
  { Linter, SourceCodeFixer, interpolate } = require("../linter");
@@ -324,10 +325,9 @@ class RuleTester {
324
325
  * configuration and the default configuration.
325
326
  * @type {Object}
326
327
  */
327
- this.testerConfig = lodash.merge(
328
-
329
- // we have to clone because merge uses the first argument for recipient
330
- lodash.cloneDeep(defaultConfig),
328
+ this.testerConfig = merge(
329
+ {},
330
+ defaultConfig,
331
331
  testerConfig,
332
332
  { rules: { "rule-tester/validate-ast": "error" } }
333
333
  );
@@ -369,7 +369,7 @@ class RuleTester {
369
369
  * @returns {void}
370
370
  */
371
371
  static resetDefaultConfig() {
372
- defaultConfig = lodash.cloneDeep(testerDefaultConfig);
372
+ defaultConfig = merge({}, testerDefaultConfig);
373
373
  }
374
374
 
375
375
 
@@ -465,7 +465,7 @@ class RuleTester {
465
465
  * @private
466
466
  */
467
467
  function runRuleForItem(item) {
468
- let config = lodash.cloneDeep(testerConfig),
468
+ let config = merge({}, testerConfig),
469
469
  code, filename, output, beforeAST, afterAST;
470
470
 
471
471
  if (typeof item === "string") {
@@ -477,13 +477,17 @@ class RuleTester {
477
477
  * Assumes everything on the item is a config except for the
478
478
  * parameters used by this tester
479
479
  */
480
- const itemConfig = lodash.omit(item, RuleTesterParameters);
480
+ const itemConfig = { ...item };
481
+
482
+ for (const parameter of RuleTesterParameters) {
483
+ delete itemConfig[parameter];
484
+ }
481
485
 
482
486
  /*
483
487
  * Create the config object from the tester config and this item
484
488
  * specific configurations.
485
489
  */
486
- config = lodash.merge(
490
+ config = merge(
487
491
  config,
488
492
  itemConfig
489
493
  );
@@ -589,7 +593,7 @@ class RuleTester {
589
593
  * @private
590
594
  */
591
595
  function assertASTDidntChange(beforeAST, afterAST) {
592
- if (!lodash.isEqual(beforeAST, afterAST)) {
596
+ if (!equal(beforeAST, afterAST)) {
593
597
  assert.fail("Rule should not modify AST.");
594
598
  }
595
599
  }
@@ -9,7 +9,6 @@
9
9
  // Requirements
10
10
  //------------------------------------------------------------------------------
11
11
 
12
- const lodash = require("lodash");
13
12
  const astUtils = require("./utils/ast-utils");
14
13
 
15
14
  //------------------------------------------------------------------------------
@@ -144,23 +143,33 @@ module.exports = {
144
143
  * @returns {ASTNode|null} The last node or null.
145
144
  */
146
145
  function getLastItem(node) {
146
+
147
+ /**
148
+ * Returns the last element of an array
149
+ * @param {any[]} array The input array
150
+ * @returns {any} The last element
151
+ */
152
+ function last(array) {
153
+ return array[array.length - 1];
154
+ }
155
+
147
156
  switch (node.type) {
148
157
  case "ObjectExpression":
149
158
  case "ObjectPattern":
150
- return lodash.last(node.properties);
159
+ return last(node.properties);
151
160
  case "ArrayExpression":
152
161
  case "ArrayPattern":
153
- return lodash.last(node.elements);
162
+ return last(node.elements);
154
163
  case "ImportDeclaration":
155
164
  case "ExportNamedDeclaration":
156
- return lodash.last(node.specifiers);
165
+ return last(node.specifiers);
157
166
  case "FunctionDeclaration":
158
167
  case "FunctionExpression":
159
168
  case "ArrowFunctionExpression":
160
- return lodash.last(node.params);
169
+ return last(node.params);
161
170
  case "CallExpression":
162
171
  case "NewExpression":
163
- return lodash.last(node.arguments);
172
+ return last(node.arguments);
164
173
  default:
165
174
  return null;
166
175
  }
@@ -316,7 +325,7 @@ module.exports = {
316
325
  "always-multiline": forceTrailingCommaIfMultiline,
317
326
  "only-multiline": allowTrailingCommaIfMultiline,
318
327
  never: forbidTrailingComma,
319
- ignore: lodash.noop
328
+ ignore: () => {}
320
329
  };
321
330
 
322
331
  return {
@@ -181,7 +181,7 @@ module.exports = {
181
181
 
182
182
  validateCommaItemSpacing({
183
183
  comma: token,
184
- left: astUtils.isCommaToken(previousToken) || commaTokensToIgnore.indexOf(token) > -1 ? null : previousToken,
184
+ left: astUtils.isCommaToken(previousToken) || commaTokensToIgnore.includes(token) ? null : previousToken,
185
185
  right: astUtils.isCommaToken(nextToken) ? null : nextToken
186
186
  }, token);
187
187
  });
@@ -10,9 +10,8 @@
10
10
  // Requirements
11
11
  //------------------------------------------------------------------------------
12
12
 
13
- const lodash = require("lodash");
14
-
15
13
  const astUtils = require("./utils/ast-utils");
14
+ const { upperCaseFirst } = require("../shared/string-utils");
16
15
 
17
16
  //------------------------------------------------------------------------------
18
17
  // Rule Definition
@@ -95,7 +94,7 @@ module.exports = {
95
94
  * @private
96
95
  */
97
96
  function endFunction(node) {
98
- const name = lodash.upperFirst(astUtils.getFunctionNameWithKind(node));
97
+ const name = upperCaseFirst(astUtils.getFunctionNameWithKind(node));
99
98
  const complexity = fns.pop();
100
99
 
101
100
  if (complexity > THRESHOLD) {
@@ -8,8 +8,8 @@
8
8
  // Requirements
9
9
  //------------------------------------------------------------------------------
10
10
 
11
- const lodash = require("lodash");
12
11
  const astUtils = require("./utils/ast-utils");
12
+ const { upperCaseFirst } = require("../shared/string-utils");
13
13
 
14
14
  //------------------------------------------------------------------------------
15
15
  // Helpers
@@ -164,7 +164,7 @@ module.exports = {
164
164
  funcInfo.data = {
165
165
  name: funcInfo.node.type === "Program"
166
166
  ? "Program"
167
- : lodash.upperFirst(astUtils.getFunctionNameWithKind(funcInfo.node))
167
+ : upperCaseFirst(astUtils.getFunctionNameWithKind(funcInfo.node))
168
168
  };
169
169
  } else if (funcInfo.hasReturnValue !== hasReturnValue) {
170
170
  context.report({
@@ -4,12 +4,6 @@
4
4
  */
5
5
  "use strict";
6
6
 
7
- //------------------------------------------------------------------------------
8
- // Requirements
9
- //------------------------------------------------------------------------------
10
-
11
- const lodash = require("lodash");
12
-
13
7
  //------------------------------------------------------------------------------
14
8
  // Rule Definition
15
9
  //------------------------------------------------------------------------------
@@ -48,8 +42,9 @@ module.exports = {
48
42
  Program: function checkBadEOF(node) {
49
43
  const sourceCode = context.getSourceCode(),
50
44
  src = sourceCode.getText(),
45
+ lastLine = sourceCode.lines[sourceCode.lines.length - 1],
51
46
  location = {
52
- column: lodash.last(sourceCode.lines).length,
47
+ column: lastLine.length,
53
48
  line: sourceCode.lines.length
54
49
  },
55
50
  LF = "\n",
@@ -12,10 +12,10 @@
12
12
  // Requirements
13
13
  //------------------------------------------------------------------------------
14
14
 
15
- const lodash = require("lodash");
16
- const astUtils = require("./utils/ast-utils");
17
15
  const createTree = require("functional-red-black-tree");
18
16
 
17
+ const astUtils = require("./utils/ast-utils");
18
+
19
19
  //------------------------------------------------------------------------------
20
20
  // Rule Definition
21
21
  //------------------------------------------------------------------------------
@@ -1068,7 +1068,7 @@ module.exports = {
1068
1068
  const baseOffsetListeners = {
1069
1069
  "ArrayExpression, ArrayPattern"(node) {
1070
1070
  const openingBracket = sourceCode.getFirstToken(node);
1071
- const closingBracket = sourceCode.getTokenAfter(lodash.findLast(node.elements) || openingBracket, astUtils.isClosingBracketToken);
1071
+ const closingBracket = sourceCode.getTokenAfter([...node.elements].reverse().find(_ => _) || openingBracket, astUtils.isClosingBracketToken);
1072
1072
 
1073
1073
  addElementListIndent(node.elements, openingBracket, closingBracket, options.ArrayExpression);
1074
1074
  },
@@ -1560,8 +1560,9 @@ module.exports = {
1560
1560
  * 2. Don't set any offsets against the first token of the node.
1561
1561
  * 3. Call `ignoreNode` on the node sometime after exiting it and before validating offsets.
1562
1562
  */
1563
- const offsetListeners = lodash.mapValues(
1564
- baseOffsetListeners,
1563
+ const offsetListeners = {};
1564
+
1565
+ for (const [selector, listener] of Object.entries(baseOffsetListeners)) {
1565
1566
 
1566
1567
  /*
1567
1568
  * Offset listener calls are deferred until traversal is finished, and are called as
@@ -1579,10 +1580,8 @@ module.exports = {
1579
1580
  * To avoid this, the `Identifier` listener isn't called until traversal finishes and all
1580
1581
  * ignored nodes are known.
1581
1582
  */
1582
- listener =>
1583
- node =>
1584
- listenerCallQueue.push({ listener, node })
1585
- );
1583
+ offsetListeners[selector] = node => listenerCallQueue.push({ listener, node });
1584
+ }
1586
1585
 
1587
1586
  // For each ignored node selector, set up a listener to collect it into the `ignoredNodes` set.
1588
1587
  const ignoredNodes = new Set();
@@ -9,8 +9,7 @@
9
9
  //------------------------------------------------------------------------------
10
10
 
11
11
  const astUtils = require("./utils/ast-utils");
12
-
13
- const lodash = require("lodash");
12
+ const { upperCaseFirst } = require("../shared/string-utils");
14
13
 
15
14
  //------------------------------------------------------------------------------
16
15
  // Constants
@@ -191,7 +190,7 @@ module.exports = {
191
190
  }
192
191
 
193
192
  if (lineCount > maxLines) {
194
- const name = lodash.upperFirst(astUtils.getFunctionNameWithKind(funcNode));
193
+ const name = upperCaseFirst(astUtils.getFunctionNameWithKind(funcNode));
195
194
 
196
195
  context.report({
197
196
  node,
@@ -8,9 +8,22 @@
8
8
  // Requirements
9
9
  //------------------------------------------------------------------------------
10
10
 
11
- const lodash = require("lodash");
12
11
  const astUtils = require("./utils/ast-utils");
13
12
 
13
+ //------------------------------------------------------------------------------
14
+ // Helpers
15
+ //------------------------------------------------------------------------------
16
+
17
+ /**
18
+ * Creates an array of numbers from `start` up to, but not including, `end`
19
+ * @param {number} start The start of the range
20
+ * @param {number} end The end of the range
21
+ * @returns {number[]} The range of numbers
22
+ */
23
+ function range(start, end) {
24
+ return [...Array(end - start).keys()].map(x => x + start);
25
+ }
26
+
14
27
  //------------------------------------------------------------------------------
15
28
  // Rule Definition
16
29
  //------------------------------------------------------------------------------
@@ -119,11 +132,25 @@ module.exports = {
119
132
  }
120
133
 
121
134
  if (start <= end) {
122
- return lodash.range(start, end + 1);
135
+ return range(start, end + 1);
123
136
  }
124
137
  return [];
125
138
  }
126
139
 
140
+ /**
141
+ * Returns a new array formed by applying a given callback function to each element of the array, and then flattening the result by one level.
142
+ * TODO(stephenwade): Replace this with array.flatMap when we drop support for Node v10
143
+ * @param {any[]} array The array to process
144
+ * @param {Function} fn The function to use
145
+ * @returns {any[]} The result array
146
+ */
147
+ function flatMap(array, fn) {
148
+ const mapped = array.map(fn);
149
+ const flattened = [].concat(...mapped);
150
+
151
+ return flattened;
152
+ }
153
+
127
154
  return {
128
155
  "Program:exit"() {
129
156
  let lines = sourceCode.lines.map((text, i) => ({
@@ -135,7 +162,7 @@ module.exports = {
135
162
  * If file ends with a linebreak, `sourceCode.lines` will have one extra empty line at the end.
136
163
  * That isn't a real line, so we shouldn't count it.
137
164
  */
138
- if (lines.length > 1 && lodash.last(lines).text === "") {
165
+ if (lines.length > 1 && lines[lines.length - 1].text === "") {
139
166
  lines.pop();
140
167
  }
141
168
 
@@ -146,9 +173,7 @@ module.exports = {
146
173
  if (skipComments) {
147
174
  const comments = sourceCode.getAllComments();
148
175
 
149
- const commentLines = lodash.flatten(
150
- comments.map(comment => getLinesWithoutCode(comment))
151
- );
176
+ const commentLines = flatMap(comments, comment => getLinesWithoutCode(comment));
152
177
 
153
178
  lines = lines.filter(
154
179
  l => !commentLines.includes(l.lineNumber)
@@ -163,7 +188,7 @@ module.exports = {
163
188
  },
164
189
  end: {
165
190
  line: sourceCode.lines.length,
166
- column: lodash.last(sourceCode.lines).length
191
+ column: sourceCode.lines[sourceCode.lines.length - 1].length
167
192
  }
168
193
  };
169
194
 
@@ -9,9 +9,8 @@
9
9
  // Requirements
10
10
  //------------------------------------------------------------------------------
11
11
 
12
- const lodash = require("lodash");
13
-
14
12
  const astUtils = require("./utils/ast-utils");
13
+ const { upperCaseFirst } = require("../shared/string-utils");
15
14
 
16
15
  //------------------------------------------------------------------------------
17
16
  // Rule Definition
@@ -85,7 +84,7 @@ module.exports = {
85
84
  node,
86
85
  messageId: "exceed",
87
86
  data: {
88
- name: lodash.upperFirst(astUtils.getFunctionNameWithKind(node)),
87
+ name: upperCaseFirst(astUtils.getFunctionNameWithKind(node)),
89
88
  count: node.params.length,
90
89
  max: numParams
91
90
  }
@@ -9,9 +9,8 @@
9
9
  // Requirements
10
10
  //------------------------------------------------------------------------------
11
11
 
12
- const lodash = require("lodash");
13
-
14
12
  const astUtils = require("./utils/ast-utils");
13
+ const { upperCaseFirst } = require("../shared/string-utils");
15
14
 
16
15
  //------------------------------------------------------------------------------
17
16
  // Rule Definition
@@ -97,7 +96,7 @@ module.exports = {
97
96
  */
98
97
  function reportIfTooManyStatements(node, count, max) {
99
98
  if (count > max) {
100
- const name = lodash.upperFirst(astUtils.getFunctionNameWithKind(node));
99
+ const name = upperCaseFirst(astUtils.getFunctionNameWithKind(node));
101
100
 
102
101
  context.report({
103
102
  node,
@@ -4,12 +4,6 @@
4
4
  */
5
5
  "use strict";
6
6
 
7
- //------------------------------------------------------------------------------
8
- // Requirements
9
- //------------------------------------------------------------------------------
10
-
11
- const lodash = require("lodash");
12
-
13
7
  //------------------------------------------------------------------------------
14
8
  // Helpers
15
9
  //------------------------------------------------------------------------------
@@ -25,7 +19,7 @@ const DEFAULT_FALLTHROUGH_COMMENT = /falls?\s?through/iu;
25
19
  */
26
20
  function hasFallthroughComment(node, context, fallthroughCommentPattern) {
27
21
  const sourceCode = context.getSourceCode();
28
- const comment = lodash.last(sourceCode.getCommentsBefore(node));
22
+ const comment = sourceCode.getCommentsBefore(node).pop();
29
23
 
30
24
  return Boolean(comment && fallthroughCommentPattern.test(comment.value));
31
25
  }
@@ -133,7 +127,7 @@ module.exports = {
133
127
  */
134
128
  if (currentCodePath.currentSegments.some(isReachable) &&
135
129
  (node.consequent.length > 0 || hasBlankLinesBetween(node, nextToken)) &&
136
- lodash.last(node.parent.cases) !== node) {
130
+ node.parent.cases[node.parent.cases.length - 1] !== node) {
137
131
  fallthroughCase = node;
138
132
  }
139
133
  }
@@ -24,6 +24,7 @@ function parseOptions(options) {
24
24
  boolean: "boolean" in options ? options.boolean : true,
25
25
  number: "number" in options ? options.number : true,
26
26
  string: "string" in options ? options.string : true,
27
+ disallowTemplateShorthand: "disallowTemplateShorthand" in options ? options.disallowTemplateShorthand : false,
27
28
  allow: options.allow || []
28
29
  };
29
30
  }
@@ -180,6 +181,10 @@ module.exports = {
180
181
  type: "boolean",
181
182
  default: true
182
183
  },
184
+ disallowTemplateShorthand: {
185
+ type: "boolean",
186
+ default: false
187
+ },
183
188
  allow: {
184
189
  type: "array",
185
190
  items: {
@@ -299,6 +304,38 @@ module.exports = {
299
304
 
300
305
  report(node, recommendation, true);
301
306
  }
307
+ },
308
+
309
+ TemplateLiteral(node) {
310
+ if (!options.disallowTemplateShorthand) {
311
+ return;
312
+ }
313
+
314
+ // tag`${foo}`
315
+ if (node.parent.type === "TaggedTemplateExpression") {
316
+ return;
317
+ }
318
+
319
+ // `` or `${foo}${bar}`
320
+ if (node.expressions.length !== 1) {
321
+ return;
322
+ }
323
+
324
+
325
+ // `prefix${foo}`
326
+ if (node.quasis[0].value.cooked !== "") {
327
+ return;
328
+ }
329
+
330
+ // `${foo}postfix`
331
+ if (node.quasis[1].value.cooked !== "") {
332
+ return;
333
+ }
334
+
335
+ const code = sourceCode.getText(node.expressions[0]);
336
+ const recommendation = `String(${code})`;
337
+
338
+ report(node, recommendation, true);
302
339
  }
303
340
  };
304
341
  }
@@ -21,7 +21,16 @@ module.exports = {
21
21
  url: "https://eslint.org/docs/rules/no-multi-assign"
22
22
  },
23
23
 
24
- schema: [],
24
+ schema: [{
25
+ type: "object",
26
+ properties: {
27
+ ignoreNonDeclaration: {
28
+ type: "boolean",
29
+ default: false
30
+ }
31
+ },
32
+ additionalProperties: false
33
+ }],
25
34
 
26
35
  messages: {
27
36
  unexpectedChain: "Unexpected chained assignment."
@@ -33,10 +42,14 @@ module.exports = {
33
42
  //--------------------------------------------------------------------------
34
43
  // Public
35
44
  //--------------------------------------------------------------------------
45
+ const options = context.options[0] || {
46
+ ignoreNonDeclaration: false
47
+ };
48
+ const targetParent = options.ignoreNonDeclaration ? ["VariableDeclarator"] : ["AssignmentExpression", "VariableDeclarator"];
36
49
 
37
50
  return {
38
51
  AssignmentExpression(node) {
39
- if (["AssignmentExpression", "VariableDeclarator"].indexOf(node.parent.type) !== -1) {
52
+ if (targetParent.indexOf(node.parent.type) !== -1) {
40
53
  context.report({
41
54
  node,
42
55
  messageId: "unexpectedChain"
@@ -10,12 +10,6 @@
10
10
 
11
11
  const ignore = require("ignore");
12
12
 
13
- const arrayOfStrings = {
14
- type: "array",
15
- items: { type: "string" },
16
- uniqueItems: true
17
- };
18
-
19
13
  const arrayOfStringsOrObjects = {
20
14
  type: "array",
21
15
  items: {
@@ -44,6 +38,41 @@ const arrayOfStringsOrObjects = {
44
38
  uniqueItems: true
45
39
  };
46
40
 
41
+ const arrayOfStringsOrObjectPatterns = {
42
+ anyOf: [
43
+ {
44
+ type: "array",
45
+ items: {
46
+ type: "string"
47
+ },
48
+ uniqueItems: true
49
+ },
50
+ {
51
+ type: "array",
52
+ items: {
53
+ type: "object",
54
+ properties: {
55
+ group: {
56
+ type: "array",
57
+ items: {
58
+ type: "string"
59
+ },
60
+ minItems: 1,
61
+ uniqueItems: true
62
+ },
63
+ message: {
64
+ type: "string",
65
+ minLength: 1
66
+ }
67
+ },
68
+ additionalProperties: false,
69
+ required: ["group"]
70
+ },
71
+ uniqueItems: true
72
+ }
73
+ ]
74
+ };
75
+
47
76
  module.exports = {
48
77
  meta: {
49
78
  type: "suggestion",
@@ -61,6 +90,8 @@ module.exports = {
61
90
  pathWithCustomMessage: "'{{importSource}}' import is restricted from being used. {{customMessage}}",
62
91
 
63
92
  patterns: "'{{importSource}}' import is restricted from being used by a pattern.",
93
+ // eslint-disable-next-line eslint-plugin/report-message-format
94
+ patternWithCustomMessage: "'{{importSource}}' import is restricted from being used by a pattern. {{customMessage}}",
64
95
 
65
96
  everything: "* import is invalid because '{{importNames}}' from '{{importSource}}' is restricted.",
66
97
  // eslint-disable-next-line eslint-plugin/report-message-format
@@ -80,7 +111,7 @@ module.exports = {
80
111
  type: "object",
81
112
  properties: {
82
113
  paths: arrayOfStringsOrObjects,
83
- patterns: arrayOfStrings
114
+ patterns: arrayOfStringsOrObjectPatterns
84
115
  },
85
116
  additionalProperties: false
86
117
  }],
@@ -98,13 +129,6 @@ module.exports = {
98
129
  (Object.prototype.hasOwnProperty.call(options[0], "paths") || Object.prototype.hasOwnProperty.call(options[0], "patterns"));
99
130
 
100
131
  const restrictedPaths = (isPathAndPatternsObject ? options[0].paths : context.options) || [];
101
- const restrictedPatterns = (isPathAndPatternsObject ? options[0].patterns : []) || [];
102
-
103
- // if no imports are restricted we don"t need to check
104
- if (Object.keys(restrictedPaths).length === 0 && restrictedPatterns.length === 0) {
105
- return {};
106
- }
107
-
108
132
  const restrictedPathMessages = restrictedPaths.reduce((memo, importSource) => {
109
133
  if (typeof importSource === "string") {
110
134
  memo[importSource] = { message: null };
@@ -117,7 +141,16 @@ module.exports = {
117
141
  return memo;
118
142
  }, {});
119
143
 
120
- const restrictedPatternsMatcher = ignore().add(restrictedPatterns);
144
+ // Handle patterns too, either as strings or groups
145
+ const restrictedPatterns = (isPathAndPatternsObject ? options[0].patterns : []) || [];
146
+ const restrictedPatternGroups = restrictedPatterns.length > 0 && typeof restrictedPatterns[0] === "string"
147
+ ? [{ matcher: ignore().add(restrictedPatterns) }]
148
+ : restrictedPatterns.map(({ group, message }) => ({ matcher: ignore().add(group), customMessage: message }));
149
+
150
+ // if no imports are restricted we don"t need to check
151
+ if (Object.keys(restrictedPaths).length === 0 && restrictedPatternGroups.length === 0) {
152
+ return {};
153
+ }
121
154
 
122
155
  /**
123
156
  * Report a restricted path.
@@ -184,17 +217,19 @@ module.exports = {
184
217
  /**
185
218
  * Report a restricted path specifically for patterns.
186
219
  * @param {node} node representing the restricted path reference
220
+ * @param {Object} group contains a Ignore instance for paths, and the customMessage to show if it fails
187
221
  * @returns {void}
188
222
  * @private
189
223
  */
190
- function reportPathForPatterns(node) {
224
+ function reportPathForPatterns(node, group) {
191
225
  const importSource = node.source.value.trim();
192
226
 
193
227
  context.report({
194
228
  node,
195
- messageId: "patterns",
229
+ messageId: group.customMessage ? "patternWithCustomMessage" : "patterns",
196
230
  data: {
197
- importSource
231
+ importSource,
232
+ customMessage: group.customMessage
198
233
  }
199
234
  });
200
235
  }
@@ -202,11 +237,12 @@ module.exports = {
202
237
  /**
203
238
  * Check if the given importSource is restricted by a pattern.
204
239
  * @param {string} importSource path of the import
240
+ * @param {Object} group contains a Ignore instance for paths, and the customMessage to show if it fails
205
241
  * @returns {boolean} whether the variable is a restricted pattern or not
206
242
  * @private
207
243
  */
208
- function isRestrictedPattern(importSource) {
209
- return restrictedPatterns.length > 0 && restrictedPatternsMatcher.ignores(importSource);
244
+ function isRestrictedPattern(importSource, group) {
245
+ return group.matcher.ignores(importSource);
210
246
  }
211
247
 
212
248
  /**
@@ -249,10 +285,11 @@ module.exports = {
249
285
  }
250
286
 
251
287
  checkRestrictedPathAndReport(importSource, importNames, node);
252
-
253
- if (isRestrictedPattern(importSource)) {
254
- reportPathForPatterns(node);
255
- }
288
+ restrictedPatternGroups.forEach(group => {
289
+ if (isRestrictedPattern(importSource, group)) {
290
+ reportPathForPatterns(node, group);
291
+ }
292
+ });
256
293
  }
257
294
 
258
295
  return {