eslint 3.9.0 → 3.10.2

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 (112) hide show
  1. package/CHANGELOG.md +60 -1
  2. package/LICENSE +1 -1
  3. package/README.md +1 -1
  4. package/bin/eslint.js +5 -5
  5. package/conf/eslint.json +1 -0
  6. package/lib/ast-utils.js +1 -3
  7. package/lib/cli-engine.js +9 -11
  8. package/lib/code-path-analysis/debug-helpers.js +4 -4
  9. package/lib/config/autoconfig.js +23 -35
  10. package/lib/config/config-file.js +1 -1
  11. package/lib/config/config-initializer.js +12 -20
  12. package/lib/config/config-ops.js +7 -9
  13. package/lib/config/config-rule.js +14 -18
  14. package/lib/config/config-validator.js +3 -3
  15. package/lib/config/environments.js +1 -1
  16. package/lib/config.js +2 -2
  17. package/lib/eslint.js +21 -23
  18. package/lib/file-finder.js +1 -1
  19. package/lib/formatters/checkstyle.js +2 -2
  20. package/lib/formatters/compact.js +2 -2
  21. package/lib/formatters/html.js +9 -11
  22. package/lib/formatters/jslint-xml.js +2 -2
  23. package/lib/formatters/junit.js +2 -2
  24. package/lib/formatters/stylish.js +3 -7
  25. package/lib/formatters/table.js +4 -6
  26. package/lib/formatters/tap.js +2 -2
  27. package/lib/formatters/unix.js +2 -2
  28. package/lib/formatters/visualstudio.js +2 -2
  29. package/lib/load-rules.js +1 -1
  30. package/lib/rules/arrow-body-style.js +1 -1
  31. package/lib/rules/arrow-parens.js +9 -2
  32. package/lib/rules/brace-style.js +2 -2
  33. package/lib/rules/comma-spacing.js +2 -2
  34. package/lib/rules/comma-style.js +51 -4
  35. package/lib/rules/consistent-this.js +5 -9
  36. package/lib/rules/constructor-super.js +1 -1
  37. package/lib/rules/curly.js +10 -7
  38. package/lib/rules/default-case.js +1 -3
  39. package/lib/rules/eqeqeq.js +19 -7
  40. package/lib/rules/func-names.js +23 -4
  41. package/lib/rules/global-require.js +3 -7
  42. package/lib/rules/handle-callback-err.js +1 -3
  43. package/lib/rules/id-length.js +1 -1
  44. package/lib/rules/indent.js +10 -25
  45. package/lib/rules/key-spacing.js +4 -4
  46. package/lib/rules/keyword-spacing.js +1 -1
  47. package/lib/rules/lines-around-comment.js +5 -11
  48. package/lib/rules/lines-around-directive.js +23 -4
  49. package/lib/rules/max-len.js +5 -11
  50. package/lib/rules/max-lines.js +4 -12
  51. package/lib/rules/max-statements.js +11 -2
  52. package/lib/rules/newline-after-var.js +1 -1
  53. package/lib/rules/newline-before-return.js +2 -4
  54. package/lib/rules/no-alert.js +2 -4
  55. package/lib/rules/no-class-assign.js +1 -1
  56. package/lib/rules/no-const-assign.js +1 -1
  57. package/lib/rules/no-control-regex.js +2 -2
  58. package/lib/rules/no-duplicate-case.js +1 -1
  59. package/lib/rules/no-ex-assign.js +1 -1
  60. package/lib/rules/no-extend-native.js +3 -7
  61. package/lib/rules/no-extra-boolean-cast.js +14 -3
  62. package/lib/rules/no-extra-parens.js +4 -4
  63. package/lib/rules/no-func-assign.js +1 -1
  64. package/lib/rules/no-implicit-globals.js +4 -4
  65. package/lib/rules/no-irregular-whitespace.js +3 -3
  66. package/lib/rules/no-mixed-operators.js +1 -3
  67. package/lib/rules/no-mixed-requires.js +2 -2
  68. package/lib/rules/no-mixed-spaces-and-tabs.js +3 -3
  69. package/lib/rules/no-multi-spaces.js +1 -1
  70. package/lib/rules/no-new-symbol.js +1 -1
  71. package/lib/rules/no-redeclare.js +2 -4
  72. package/lib/rules/no-restricted-globals.js +2 -2
  73. package/lib/rules/no-restricted-imports.js +45 -11
  74. package/lib/rules/no-restricted-modules.js +53 -36
  75. package/lib/rules/no-restricted-syntax.js +2 -4
  76. package/lib/rules/no-return-await.js +77 -0
  77. package/lib/rules/no-tabs.js +1 -1
  78. package/lib/rules/no-this-before-super.js +2 -2
  79. package/lib/rules/no-undef.js +1 -1
  80. package/lib/rules/no-underscore-dangle.js +1 -3
  81. package/lib/rules/no-unused-vars.js +2 -6
  82. package/lib/rules/no-use-before-define.js +1 -1
  83. package/lib/rules/no-useless-escape.js +8 -54
  84. package/lib/rules/no-useless-return.js +6 -0
  85. package/lib/rules/no-warning-comments.js +2 -2
  86. package/lib/rules/object-shorthand.js +21 -10
  87. package/lib/rules/one-var-declaration-per-line.js +1 -1
  88. package/lib/rules/prefer-const.js +1 -3
  89. package/lib/rules/prefer-reflect.js +2 -1
  90. package/lib/rules/quote-props.js +1 -1
  91. package/lib/rules/quotes.js +1 -1
  92. package/lib/rules/radix.js +2 -2
  93. package/lib/rules/require-jsdoc.js +8 -0
  94. package/lib/rules/sort-vars.js +1 -1
  95. package/lib/rules/space-in-parens.js +1 -1
  96. package/lib/rules/space-infix-ops.js +1 -1
  97. package/lib/rules/spaced-comment.js +1 -1
  98. package/lib/rules/symbol-description.js +1 -1
  99. package/lib/rules/valid-jsdoc.js +3 -3
  100. package/lib/rules.js +2 -2
  101. package/lib/testers/event-generator-tester.js +5 -5
  102. package/lib/testers/rule-tester.js +11 -13
  103. package/lib/timing.js +11 -13
  104. package/lib/util/comment-event-generator.js +1 -1
  105. package/lib/util/glob-util.js +3 -5
  106. package/lib/util/npm-util.js +1 -1
  107. package/lib/util/source-code-fixer.js +3 -5
  108. package/lib/util/source-code-util.js +4 -4
  109. package/lib/util/source-code.js +3 -3
  110. package/lib/util/traverser.js +1 -3
  111. package/lib/util/xml-escape.js +1 -1
  112. package/package.json +3 -3
@@ -114,26 +114,18 @@ module.exports = {
114
114
 
115
115
  return {
116
116
  "Program:exit"() {
117
- let lines = sourceCode.lines.map(function(text, i) {
118
- return { lineNumber: i + 1, text };
119
- });
117
+ let lines = sourceCode.lines.map((text, i) => ({ lineNumber: i + 1, text }));
120
118
 
121
119
  if (skipBlankLines) {
122
- lines = lines.filter(function(l) {
123
- return l.text.trim() !== "";
124
- });
120
+ lines = lines.filter(l => l.text.trim() !== "");
125
121
  }
126
122
 
127
123
  if (skipComments) {
128
124
  const comments = sourceCode.getAllComments();
129
125
 
130
- const commentLines = lodash.flatten(comments.map(function(comment) {
131
- return getLinesWithoutCode(comment);
132
- }));
126
+ const commentLines = lodash.flatten(comments.map(comment => getLinesWithoutCode(comment)));
133
127
 
134
- lines = lines.filter(function(l) {
135
- return !lodash.includes(commentLines, l.lineNumber);
136
- });
128
+ lines = lines.filter(l => !lodash.includes(commentLines, l.lineNumber));
137
129
  }
138
130
 
139
131
  if (lines.length > max) {
@@ -84,9 +84,18 @@ module.exports = {
84
84
  */
85
85
  function reportIfTooManyStatements(node, count, max) {
86
86
  if (count > max) {
87
+ const messageEnd = " has too many statements ({{count}}). Maximum allowed is {{max}}.";
88
+ let name = "This function";
89
+
90
+ if (node.id) {
91
+ name = `Function '${node.id.name}'`;
92
+ } else if (node.parent.type === "MethodDefinition" || node.parent.type === "Property") {
93
+ name = `Function '${context.getSource(node.parent.key)}'`;
94
+ }
95
+
87
96
  context.report(
88
97
  node,
89
- "This function has too many statements ({{count}}). Maximum allowed is {{max}}.",
98
+ name + messageEnd,
90
99
  { count, max });
91
100
  }
92
101
  }
@@ -146,7 +155,7 @@ module.exports = {
146
155
  return;
147
156
  }
148
157
 
149
- topLevelFunctions.forEach(function(element) {
158
+ topLevelFunctions.forEach(element => {
150
159
  const count = element.count;
151
160
  const node = element.node;
152
161
 
@@ -37,7 +37,7 @@ module.exports = {
37
37
  const mode = context.options[0] === "never" ? "never" : "always";
38
38
 
39
39
  // Cache starting and ending line numbers of comments for faster lookup
40
- const commentEndLine = sourceCode.getAllComments().reduce(function(result, token) {
40
+ const commentEndLine = sourceCode.getAllComments().reduce((result, token) => {
41
41
  result[token.loc.start.line] = token.loc.end.line;
42
42
  return result;
43
43
  }, {});
@@ -36,9 +36,7 @@ module.exports = {
36
36
  function isPrecededByTokens(node, testTokens) {
37
37
  const tokenBefore = sourceCode.getTokenBefore(node);
38
38
 
39
- return testTokens.some(function(token) {
40
- return tokenBefore.value === token;
41
- });
39
+ return testTokens.some(token => tokenBefore.value === token);
42
40
  }
43
41
 
44
42
  /**
@@ -82,7 +80,7 @@ module.exports = {
82
80
  return numLinesComments;
83
81
  }
84
82
 
85
- comments.forEach(function(comment) {
83
+ comments.forEach(comment => {
86
84
  numLinesComments++;
87
85
 
88
86
  if (comment.type === "Block") {
@@ -41,10 +41,8 @@ function report(context, node, identifierName) {
41
41
  * @returns {Reference|null} Returns the found reference or null if none were found.
42
42
  */
43
43
  function findReference(scope, node) {
44
- const references = scope.references.filter(function(reference) {
45
- return reference.identifier.range[0] === node.range[0] &&
46
- reference.identifier.range[1] === node.range[1];
47
- });
44
+ const references = scope.references.filter(reference => reference.identifier.range[0] === node.range[0] &&
45
+ reference.identifier.range[1] === node.range[1]);
48
46
 
49
47
  if (references.length === 1) {
50
48
  return references[0];
@@ -30,7 +30,7 @@ module.exports = {
30
30
  * @returns {void}
31
31
  */
32
32
  function checkVariable(variable) {
33
- astUtils.getModifyingReferences(variable.references).forEach(function(reference) {
33
+ astUtils.getModifyingReferences(variable.references).forEach(reference => {
34
34
  context.report(
35
35
  reference.identifier,
36
36
  "'{{name}}' is a class.",
@@ -30,7 +30,7 @@ module.exports = {
30
30
  * @returns {void}
31
31
  */
32
32
  function checkVariable(variable) {
33
- astUtils.getModifyingReferences(variable.references).forEach(function(reference) {
33
+ astUtils.getModifyingReferences(variable.references).forEach(reference => {
34
34
  context.report(
35
35
  reference.identifier,
36
36
  "'{{name}}' is constant.",
@@ -85,7 +85,7 @@ module.exports = {
85
85
  stringControlChars = regexStr.slice(subStrIndex, -1)
86
86
  .split(consecutiveSlashes)
87
87
  .filter(Boolean)
88
- .map(function(x) {
88
+ .map(x => {
89
89
  const match = x.match(stringControlCharWithoutSlash) || [x];
90
90
 
91
91
  return `\\${match[0]}`;
@@ -93,7 +93,7 @@ module.exports = {
93
93
  }
94
94
  }
95
95
 
96
- return controlChars.map(function(x) {
96
+ return controlChars.map(x => {
97
97
  const hexCode = `0${x.charCodeAt(0).toString(16)}`.slice(-2);
98
98
 
99
99
  return `\\x${hexCode}`;
@@ -28,7 +28,7 @@ module.exports = {
28
28
  SwitchStatement(node) {
29
29
  const mapping = {};
30
30
 
31
- node.cases.forEach(function(switchCase) {
31
+ node.cases.forEach(switchCase => {
32
32
  const key = sourceCode.getText(switchCase.test);
33
33
 
34
34
  if (mapping[key]) {
@@ -30,7 +30,7 @@ module.exports = {
30
30
  * @returns {void}
31
31
  */
32
32
  function checkVariable(variable) {
33
- astUtils.getModifyingReferences(variable.references).forEach(function(reference) {
33
+ astUtils.getModifyingReferences(variable.references).forEach(reference => {
34
34
  context.report(
35
35
  reference.identifier,
36
36
  "Do not assign to the exception parameter.");
@@ -44,14 +44,10 @@ module.exports = {
44
44
 
45
45
  const config = context.options[0] || {};
46
46
  const exceptions = config.exceptions || [];
47
- let modifiedBuiltins = Object.keys(globals.builtin).filter(function(builtin) {
48
- return builtin[0].toUpperCase() === builtin[0];
49
- });
47
+ let modifiedBuiltins = Object.keys(globals.builtin).filter(builtin => builtin[0].toUpperCase() === builtin[0]);
50
48
 
51
49
  if (exceptions.length) {
52
- modifiedBuiltins = modifiedBuiltins.filter(function(builtIn) {
53
- return exceptions.indexOf(builtIn) === -1;
54
- });
50
+ modifiedBuiltins = modifiedBuiltins.filter(builtIn => exceptions.indexOf(builtIn) === -1);
55
51
  }
56
52
 
57
53
  return {
@@ -72,7 +68,7 @@ module.exports = {
72
68
  return;
73
69
  }
74
70
 
75
- modifiedBuiltins.forEach(function(builtin) {
71
+ modifiedBuiltins.forEach(builtin => {
76
72
  if (lhs.object.object.name === builtin) {
77
73
  context.report({
78
74
  node,
@@ -17,10 +17,13 @@ module.exports = {
17
17
  recommended: true
18
18
  },
19
19
 
20
- schema: []
20
+ schema: [],
21
+
22
+ fixable: "code"
21
23
  },
22
24
 
23
25
  create(context) {
26
+ const sourceCode = context.getSourceCode();
24
27
 
25
28
  // Node types which have a test which will coerce values to booleans.
26
29
  const BOOLEAN_NODE_TYPES = [
@@ -70,7 +73,11 @@ module.exports = {
70
73
  grandparent.callee.type === "Identifier" &&
71
74
  grandparent.callee.name === "Boolean")
72
75
  ) {
73
- context.report(node, "Redundant double negation.");
76
+ context.report({
77
+ node,
78
+ message: "Redundant double negation.",
79
+ fix: fixer => fixer.replaceText(parent, sourceCode.getText(node.argument))
80
+ });
74
81
  }
75
82
  },
76
83
  CallExpression(node) {
@@ -81,7 +88,11 @@ module.exports = {
81
88
  }
82
89
 
83
90
  if (isInBooleanContext(node, parent)) {
84
- context.report(node, "Redundant Boolean call.");
91
+ context.report({
92
+ node,
93
+ message: "Redundant Boolean call.",
94
+ fix: fixer => fixer.replaceText(node, sourceCode.getText(node.arguments[0]))
95
+ });
85
96
  }
86
97
  }
87
98
  };
@@ -352,7 +352,7 @@ module.exports = {
352
352
  report(node.arguments[0]);
353
353
  }
354
354
  } else {
355
- [].forEach.call(node.arguments, function(arg) {
355
+ [].forEach.call(node.arguments, arg => {
356
356
  if (hasExcessParens(arg) && precedence(arg) >= precedence({type: "AssignmentExpression"})) {
357
357
  report(arg);
358
358
  }
@@ -381,7 +381,7 @@ module.exports = {
381
381
 
382
382
  return {
383
383
  ArrayExpression(node) {
384
- [].forEach.call(node.elements, function(e) {
384
+ [].forEach.call(node.elements, e => {
385
385
  if (e && hasExcessParens(e) && precedence(e) >= precedence({type: "AssignmentExpression"})) {
386
386
  report(e);
387
387
  }
@@ -531,7 +531,7 @@ module.exports = {
531
531
  NewExpression: dryCallNew,
532
532
 
533
533
  ObjectExpression(node) {
534
- [].forEach.call(node.properties, function(e) {
534
+ [].forEach.call(node.properties, e => {
535
535
  const v = e.value;
536
536
 
537
537
  if (v && hasExcessParens(v) && precedence(v) >= precedence({type: "AssignmentExpression"})) {
@@ -557,7 +557,7 @@ module.exports = {
557
557
  },
558
558
 
559
559
  SequenceExpression(node) {
560
- [].forEach.call(node.expressions, function(e) {
560
+ [].forEach.call(node.expressions, e => {
561
561
  if (hasExcessParens(e) && precedence(e) >= precedence(node)) {
562
562
  report(e);
563
563
  }
@@ -30,7 +30,7 @@ module.exports = {
30
30
  * @returns {void}
31
31
  */
32
32
  function checkReference(references) {
33
- astUtils.getModifyingReferences(references).forEach(function(reference) {
33
+ astUtils.getModifyingReferences(references).forEach(reference => {
34
34
  context.report(
35
35
  reference.identifier,
36
36
  "'{{name}}' is a function.",
@@ -25,26 +25,26 @@ module.exports = {
25
25
  Program() {
26
26
  const scope = context.getScope();
27
27
 
28
- scope.variables.forEach(function(variable) {
28
+ scope.variables.forEach(variable => {
29
29
  if (variable.writeable) {
30
30
  return;
31
31
  }
32
32
 
33
- variable.defs.forEach(function(def) {
33
+ variable.defs.forEach(def => {
34
34
  if (def.type === "FunctionName" || (def.type === "Variable" && def.parent.kind === "var")) {
35
35
  context.report(def.node, "Implicit global variable, assign as global property instead.");
36
36
  }
37
37
  });
38
38
  });
39
39
 
40
- scope.implicit.variables.forEach(function(variable) {
40
+ scope.implicit.variables.forEach(variable => {
41
41
  const scopeVariable = scope.set.get(variable.name);
42
42
 
43
43
  if (scopeVariable && scopeVariable.writeable) {
44
44
  return;
45
45
  }
46
46
 
47
- variable.defs.forEach(function(def) {
47
+ variable.defs.forEach(def => {
48
48
  context.report(def.node, "Implicit global variable, assign as global property instead.");
49
49
  });
50
50
  });
@@ -76,7 +76,7 @@ module.exports = {
76
76
  const locStart = node.loc.start;
77
77
  const locEnd = node.loc.end;
78
78
 
79
- errors = errors.filter(function(error) {
79
+ errors = errors.filter(error => {
80
80
  const errorLoc = error[1];
81
81
 
82
82
  if (errorLoc.line >= locStart.line && errorLoc.line <= locEnd.line) {
@@ -142,7 +142,7 @@ module.exports = {
142
142
  function checkForIrregularWhitespace(node) {
143
143
  const sourceLines = sourceCode.lines;
144
144
 
145
- sourceLines.forEach(function(sourceLine, lineIndex) {
145
+ sourceLines.forEach((sourceLine, lineIndex) => {
146
146
  const lineNumber = lineIndex + 1;
147
147
  let match;
148
148
 
@@ -233,7 +233,7 @@ module.exports = {
233
233
  }
234
234
 
235
235
  // If we have any errors remaining report on them
236
- errors.forEach(function(error) {
236
+ errors.forEach(error => {
237
237
  context.report.apply(context, error);
238
238
  });
239
239
  };
@@ -62,9 +62,7 @@ function normalizeOptions(options) {
62
62
  * @returns {boolean} `true` if such group existed.
63
63
  */
64
64
  function includesBothInAGroup(groups, left, right) {
65
- return groups.some(function(group) {
66
- return group.indexOf(left) !== -1 && group.indexOf(right) !== -1;
67
- });
65
+ return groups.some(group => group.indexOf(left) !== -1 && group.indexOf(right) !== -1);
68
66
  }
69
67
 
70
68
  //------------------------------------------------------------------------------
@@ -169,7 +169,7 @@ module.exports = {
169
169
  function isMixed(declarations) {
170
170
  const contains = {};
171
171
 
172
- declarations.forEach(function(declaration) {
172
+ declarations.forEach(declaration => {
173
173
  const type = getDeclarationType(declaration.init);
174
174
 
175
175
  contains[type] = true;
@@ -190,7 +190,7 @@ module.exports = {
190
190
  function isGrouped(declarations) {
191
191
  const found = {};
192
192
 
193
- declarations.forEach(function(declaration) {
193
+ declarations.forEach(declaration => {
194
194
  if (getDeclarationType(declaration.init) === DECL_REQUIRE) {
195
195
  found[inferModuleType(declaration.init)] = true;
196
196
  }
@@ -89,11 +89,11 @@ module.exports = {
89
89
  const lines = sourceCode.lines,
90
90
  comments = sourceCode.getAllComments();
91
91
 
92
- comments.forEach(function(comment) {
92
+ comments.forEach(comment => {
93
93
  ignoredLocs.push(comment.loc);
94
94
  });
95
95
 
96
- ignoredLocs.sort(function(first, second) {
96
+ ignoredLocs.sort((first, second) => {
97
97
  if (beforeLoc(first, second.start.line, second.start.column)) {
98
98
  return 1;
99
99
  }
@@ -114,7 +114,7 @@ module.exports = {
114
114
  regex = /^(?=[\t ]* \t)/;
115
115
  }
116
116
 
117
- lines.forEach(function(line, i) {
117
+ lines.forEach((line, i) => {
118
118
  const match = regex.exec(line);
119
119
 
120
120
  if (match) {
@@ -47,7 +47,7 @@ module.exports = {
47
47
  lastCommentIndex = 0;
48
48
 
49
49
  if (options && options.exceptions) {
50
- Object.keys(options.exceptions).forEach(function(key) {
50
+ Object.keys(options.exceptions).forEach(key => {
51
51
  if (options.exceptions[key]) {
52
52
  exceptions[key] = true;
53
53
  } else {
@@ -28,7 +28,7 @@ module.exports = {
28
28
  const variable = globalScope.set.get("Symbol");
29
29
 
30
30
  if (variable && variable.defs.length === 0) {
31
- variable.references.forEach(function(ref) {
31
+ variable.references.forEach(ref => {
32
32
  const node = ref.identifier;
33
33
 
34
34
  if (node.parent && node.parent.type === "NewExpression") {
@@ -40,14 +40,12 @@ module.exports = {
40
40
  * @private
41
41
  */
42
42
  function findVariablesInScope(scope) {
43
- scope.variables.forEach(function(variable) {
43
+ scope.variables.forEach(variable => {
44
44
  const hasBuiltin = options.builtinGlobals && "writeable" in variable;
45
45
  const count = (hasBuiltin ? 1 : 0) + variable.identifiers.length;
46
46
 
47
47
  if (count >= 2) {
48
- variable.identifiers.sort(function(a, b) {
49
- return a.range[1] - b.range[1];
50
- });
48
+ variable.identifiers.sort((a, b) => a.range[1] - b.range[1]);
51
49
 
52
50
  for (let i = (hasBuiltin ? 0 : 1), l = variable.identifiers.length; i < l; i++) {
53
51
  context.report(
@@ -60,14 +60,14 @@ module.exports = {
60
60
  const scope = context.getScope();
61
61
 
62
62
  // Report variables declared elsewhere (ex: variables defined as "global" by eslint)
63
- scope.variables.forEach(function(variable) {
63
+ scope.variables.forEach(variable => {
64
64
  if (!variable.defs.length && isRestricted(variable.name)) {
65
65
  variable.references.forEach(reportReference);
66
66
  }
67
67
  });
68
68
 
69
69
  // Report variables not declared at all
70
- scope.through.forEach(function(reference) {
70
+ scope.through.forEach(reference => {
71
71
  if (isRestricted(reference.identifier.name)) {
72
72
  reportReference(reference);
73
73
  }
@@ -8,6 +8,16 @@
8
8
  // Rule Definition
9
9
  //------------------------------------------------------------------------------
10
10
 
11
+ const ignore = require("ignore");
12
+
13
+ const arrayOfStrings = {
14
+ type: "array",
15
+ items: {
16
+ type: "string"
17
+ },
18
+ uniqueItems: true
19
+ };
20
+
11
21
  module.exports = {
12
22
  meta: {
13
23
  docs: {
@@ -17,31 +27,55 @@ module.exports = {
17
27
  },
18
28
 
19
29
  schema: {
20
- type: "array",
21
- items: {
22
- type: "string"
23
- },
24
- uniqueItems: true
30
+ anyOf: [
31
+ arrayOfStrings,
32
+ {
33
+ type: "array",
34
+ items: [{
35
+ type: "object",
36
+ properties: {
37
+ paths: arrayOfStrings,
38
+ patterns: arrayOfStrings
39
+ },
40
+ additionalProperties: false
41
+ }],
42
+ additionalItems: false
43
+ }
44
+ ]
25
45
  }
26
46
  },
27
47
 
28
48
  create(context) {
29
- const restrictedImports = context.options;
49
+ const options = Array.isArray(context.options) ? context.options : [];
50
+ const isStringArray = typeof options[0] !== "object";
51
+ const restrictedPaths = new Set(isStringArray ? context.options : options[0].paths || []);
52
+ const restrictedPatterns = isStringArray ? [] : options[0].patterns || [];
30
53
 
31
54
  // if no imports are restricted we don"t need to check
32
- if (restrictedImports.length === 0) {
55
+ if (restrictedPaths.size === 0 && restrictedPatterns.length === 0) {
33
56
  return {};
34
57
  }
35
58
 
59
+ const ig = ignore().add(restrictedPatterns);
60
+
36
61
  return {
37
62
  ImportDeclaration(node) {
38
63
  if (node && node.source && node.source.value) {
39
64
 
40
- const value = node.source.value.trim();
65
+ const importName = node.source.value.trim();
41
66
 
42
- if (restrictedImports.indexOf(value) !== -1) {
43
- context.report(node, "'{{importName}}' import is restricted from being used.", {
44
- importName: value
67
+ if (restrictedPaths.has(importName)) {
68
+ context.report({
69
+ node,
70
+ message: "'{{importName}}' import is restricted from being used.",
71
+ data: { importName }
72
+ });
73
+ }
74
+ if (restrictedPatterns.length > 0 && ig.ignores(importName)) {
75
+ context.report({
76
+ node,
77
+ message: "'{{importName}}' import is restricted from being used by a pattern.",
78
+ data: { importName }
45
79
  });
46
80
  }
47
81
  }
@@ -8,6 +8,16 @@
8
8
  // Rule Definition
9
9
  //------------------------------------------------------------------------------
10
10
 
11
+ const ignore = require("ignore");
12
+
13
+ const arrayOfStrings = {
14
+ type: "array",
15
+ items: {
16
+ type: "string"
17
+ },
18
+ uniqueItems: true
19
+ };
20
+
11
21
  module.exports = {
12
22
  meta: {
13
23
  docs: {
@@ -17,24 +27,37 @@ module.exports = {
17
27
  },
18
28
 
19
29
  schema: {
20
- type: "array",
21
- items: {
22
- type: "string"
23
- },
24
- uniqueItems: true
30
+ anyOf: [
31
+ arrayOfStrings,
32
+ {
33
+ type: "array",
34
+ items: [{
35
+ type: "object",
36
+ properties: {
37
+ paths: arrayOfStrings,
38
+ patterns: arrayOfStrings
39
+ },
40
+ additionalProperties: false
41
+ }],
42
+ additionalItems: false
43
+ }
44
+ ]
25
45
  }
26
46
  },
27
47
 
28
48
  create(context) {
49
+ const options = Array.isArray(context.options) ? context.options : [];
50
+ const isStringArray = typeof options[0] !== "object";
51
+ const restrictedPaths = new Set(isStringArray ? context.options : options[0].paths || []);
52
+ const restrictedPatterns = isStringArray ? [] : options[0].patterns || [];
29
53
 
30
- // trim restricted module names
31
- const restrictedModules = context.options;
32
-
33
- // if no modules are restricted we don't need to check the CallExpressions
34
- if (restrictedModules.length === 0) {
54
+ // if no imports are restricted we don"t need to check
55
+ if (restrictedPaths.size === 0 && restrictedPatterns.length === 0) {
35
56
  return {};
36
57
  }
37
58
 
59
+ const ig = ignore().add(restrictedPatterns);
60
+
38
61
  /**
39
62
  * Function to check if a node is a string literal.
40
63
  * @param {ASTNode} node The node to check.
@@ -53,36 +76,30 @@ module.exports = {
53
76
  return node.callee.type === "Identifier" && node.callee.name === "require";
54
77
  }
55
78
 
56
- /**
57
- * Function to check if a node has an argument that is an restricted module and return its name.
58
- * @param {ASTNode} node The node to check
59
- * @returns {undefined|string} restricted module name or undefined if node argument isn't restricted.
60
- */
61
- function getRestrictedModuleName(node) {
62
- let moduleName;
63
-
64
- // node has arguments and first argument is string
65
- if (node.arguments.length && isString(node.arguments[0])) {
66
- const argumentValue = node.arguments[0].value.trim();
67
-
68
- // check if argument value is in restricted modules array
69
- if (restrictedModules.indexOf(argumentValue) !== -1) {
70
- moduleName = argumentValue;
71
- }
72
- }
73
-
74
- return moduleName;
75
- }
76
-
77
79
  return {
78
80
  CallExpression(node) {
79
81
  if (isRequireCall(node)) {
80
- const restrictedModuleName = getRestrictedModuleName(node);
81
82
 
82
- if (restrictedModuleName) {
83
- context.report(node, "'{{moduleName}}' module is restricted from being used.", {
84
- moduleName: restrictedModuleName
85
- });
83
+ // node has arguments and first argument is string
84
+ if (node.arguments.length && isString(node.arguments[0])) {
85
+ const moduleName = node.arguments[0].value.trim();
86
+
87
+ // check if argument value is in restricted modules array
88
+ if (restrictedPaths.has(moduleName)) {
89
+ context.report({
90
+ node,
91
+ message: "'{{moduleName}}' module is restricted from being used.",
92
+ data: { moduleName }
93
+ });
94
+ }
95
+
96
+ if (restrictedPatterns.length > 0 && ig.ignores(moduleName)) {
97
+ context.report({
98
+ node,
99
+ message: "'{{moduleName}}' module is restricted from being used by a pattern.",
100
+ data: { moduleName }
101
+ });
102
+ }
86
103
  }
87
104
  }
88
105
  }