eslint 7.2.0 → 7.5.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 (76) hide show
  1. package/CHANGELOG.md +78 -0
  2. package/README.md +14 -7
  3. package/lib/init/config-initializer.js +89 -68
  4. package/lib/linter/code-path-analysis/code-path-analyzer.js +38 -0
  5. package/lib/linter/code-path-analysis/code-path-segment.js +0 -1
  6. package/lib/linter/code-path-analysis/code-path-state.js +59 -0
  7. package/lib/linter/code-path-analysis/debug-helpers.js +26 -19
  8. package/lib/rule-tester/rule-tester.js +9 -0
  9. package/lib/rules/accessor-pairs.js +1 -14
  10. package/lib/rules/array-callback-return.js +26 -17
  11. package/lib/rules/arrow-body-style.js +43 -8
  12. package/lib/rules/arrow-parens.js +91 -108
  13. package/lib/rules/camelcase.js +47 -0
  14. package/lib/rules/comma-dangle.js +2 -1
  15. package/lib/rules/consistent-return.js +1 -12
  16. package/lib/rules/constructor-super.js +1 -0
  17. package/lib/rules/curly.js +8 -1
  18. package/lib/rules/dot-location.js +20 -14
  19. package/lib/rules/dot-notation.js +36 -33
  20. package/lib/rules/func-call-spacing.js +42 -6
  21. package/lib/rules/func-name-matching.js +1 -4
  22. package/lib/rules/global-require.js +2 -1
  23. package/lib/rules/id-blacklist.js +14 -11
  24. package/lib/rules/id-denylist.js +230 -0
  25. package/lib/rules/id-match.js +2 -1
  26. package/lib/rules/indent.js +19 -0
  27. package/lib/rules/index.js +3 -0
  28. package/lib/rules/key-spacing.js +6 -2
  29. package/lib/rules/keyword-spacing.js +2 -2
  30. package/lib/rules/max-len.js +13 -2
  31. package/lib/rules/max-lines.js +34 -8
  32. package/lib/rules/new-cap.js +10 -14
  33. package/lib/rules/newline-per-chained-call.js +15 -5
  34. package/lib/rules/no-alert.js +10 -3
  35. package/lib/rules/no-eval.js +8 -38
  36. package/lib/rules/no-extend-native.js +37 -40
  37. package/lib/rules/no-extra-bind.js +57 -17
  38. package/lib/rules/no-extra-boolean-cast.js +7 -0
  39. package/lib/rules/no-extra-parens.js +48 -10
  40. package/lib/rules/no-implicit-coercion.js +11 -6
  41. package/lib/rules/no-implied-eval.js +7 -28
  42. package/lib/rules/no-import-assign.js +33 -32
  43. package/lib/rules/no-irregular-whitespace.js +22 -12
  44. package/lib/rules/no-magic-numbers.js +4 -8
  45. package/lib/rules/no-obj-calls.js +7 -4
  46. package/lib/rules/no-promise-executor-return.js +121 -0
  47. package/lib/rules/no-prototype-builtins.js +13 -3
  48. package/lib/rules/no-self-assign.js +3 -53
  49. package/lib/rules/no-setter-return.js +5 -8
  50. package/lib/rules/no-unexpected-multiline.js +2 -2
  51. package/lib/rules/no-unneeded-ternary.js +0 -2
  52. package/lib/rules/no-unreachable-loop.js +150 -0
  53. package/lib/rules/no-unused-expressions.js +55 -23
  54. package/lib/rules/no-unused-vars.js +2 -1
  55. package/lib/rules/no-useless-call.js +10 -7
  56. package/lib/rules/no-whitespace-before-property.js +16 -4
  57. package/lib/rules/object-curly-newline.js +4 -4
  58. package/lib/rules/object-property-newline.js +1 -1
  59. package/lib/rules/operator-assignment.js +3 -42
  60. package/lib/rules/operator-linebreak.js +2 -5
  61. package/lib/rules/padded-blocks.js +2 -1
  62. package/lib/rules/padding-line-between-statements.js +2 -2
  63. package/lib/rules/prefer-arrow-callback.js +90 -25
  64. package/lib/rules/prefer-exponentiation-operator.js +1 -1
  65. package/lib/rules/prefer-numeric-literals.js +4 -13
  66. package/lib/rules/prefer-promise-reject-errors.js +1 -3
  67. package/lib/rules/prefer-regex-literals.js +68 -13
  68. package/lib/rules/prefer-spread.js +2 -6
  69. package/lib/rules/radix.js +5 -2
  70. package/lib/rules/semi-spacing.js +1 -0
  71. package/lib/rules/sort-imports.js +28 -0
  72. package/lib/rules/use-isnan.js +1 -1
  73. package/lib/rules/utils/ast-utils.js +317 -153
  74. package/lib/rules/wrap-iife.js +9 -2
  75. package/lib/rules/yoda.js +2 -55
  76. package/package.json +7 -7
@@ -5,6 +5,8 @@
5
5
 
6
6
  "use strict";
7
7
 
8
+ const astUtils = require("./utils/ast-utils");
9
+
8
10
  //------------------------------------------------------------------------------
9
11
  // Helpers
10
12
  //------------------------------------------------------------------------------
@@ -66,6 +68,7 @@ function getCallbackInfo(node) {
66
68
  const retv = { isCallback: false, isLexicalThis: false };
67
69
  let currentNode = node;
68
70
  let parent = node.parent;
71
+ let bound = false;
69
72
 
70
73
  while (currentNode) {
71
74
  switch (parent.type) {
@@ -73,23 +76,34 @@ function getCallbackInfo(node) {
73
76
  // Checks parents recursively.
74
77
 
75
78
  case "LogicalExpression":
79
+ case "ChainExpression":
76
80
  case "ConditionalExpression":
77
81
  break;
78
82
 
79
83
  // Checks whether the parent node is `.bind(this)` call.
80
84
  case "MemberExpression":
81
- if (parent.object === currentNode &&
85
+ if (
86
+ parent.object === currentNode &&
82
87
  !parent.property.computed &&
83
88
  parent.property.type === "Identifier" &&
84
- parent.property.name === "bind" &&
85
- parent.parent.type === "CallExpression" &&
86
- parent.parent.callee === parent
89
+ parent.property.name === "bind"
87
90
  ) {
88
- retv.isLexicalThis = (
89
- parent.parent.arguments.length === 1 &&
90
- parent.parent.arguments[0].type === "ThisExpression"
91
- );
92
- parent = parent.parent;
91
+ const maybeCallee = parent.parent.type === "ChainExpression"
92
+ ? parent.parent
93
+ : parent;
94
+
95
+ if (astUtils.isCallee(maybeCallee)) {
96
+ if (!bound) {
97
+ bound = true; // Use only the first `.bind()` to make `isLexicalThis` value.
98
+ retv.isLexicalThis = (
99
+ maybeCallee.parent.arguments.length === 1 &&
100
+ maybeCallee.parent.arguments[0].type === "ThisExpression"
101
+ );
102
+ }
103
+ parent = maybeCallee.parent;
104
+ } else {
105
+ return retv;
106
+ }
93
107
  } else {
94
108
  return retv;
95
109
  }
@@ -272,7 +286,7 @@ module.exports = {
272
286
  context.report({
273
287
  node,
274
288
  messageId: "preferArrowCallback",
275
- fix(fixer) {
289
+ *fix(fixer) {
276
290
  if ((!callbackInfo.isLexicalThis && scopeInfo.this) || hasDuplicateParams(node.params)) {
277
291
 
278
292
  /*
@@ -281,30 +295,81 @@ module.exports = {
281
295
  * If the callback function has duplicates in its list of parameters (possible in sloppy mode),
282
296
  * don't replace it with an arrow function, because this is a SyntaxError with arrow functions.
283
297
  */
284
- return null;
298
+ return; // eslint-disable-line eslint-plugin/fixer-return -- false positive
285
299
  }
286
300
 
287
- const paramsLeftParen = node.params.length ? sourceCode.getTokenBefore(node.params[0]) : sourceCode.getTokenBefore(node.body, 1);
288
- const paramsRightParen = sourceCode.getTokenBefore(node.body);
289
- const asyncKeyword = node.async ? "async " : "";
290
- const paramsFullText = sourceCode.text.slice(paramsLeftParen.range[0], paramsRightParen.range[1]);
291
- const arrowFunctionText = `${asyncKeyword}${paramsFullText} => ${sourceCode.getText(node.body)}`;
301
+ // Remove `.bind(this)` if exists.
302
+ if (callbackInfo.isLexicalThis) {
303
+ const memberNode = node.parent;
292
304
 
293
- /*
294
- * If the callback function has `.bind(this)`, replace it with an arrow function and remove the binding.
295
- * Otherwise, just replace the arrow function itself.
296
- */
297
- const replacedNode = callbackInfo.isLexicalThis ? node.parent.parent : node;
305
+ /*
306
+ * If `.bind(this)` exists but the parent is not `.bind(this)`, don't remove it automatically.
307
+ * E.g. `(foo || function(){}).bind(this)`
308
+ */
309
+ if (memberNode.type !== "MemberExpression") {
310
+ return; // eslint-disable-line eslint-plugin/fixer-return -- false positive
311
+ }
312
+
313
+ const callNode = memberNode.parent;
314
+ const firstTokenToRemove = sourceCode.getTokenAfter(memberNode.object, astUtils.isNotClosingParenToken);
315
+ const lastTokenToRemove = sourceCode.getLastToken(callNode);
316
+
317
+ /*
318
+ * If the member expression is parenthesized, don't remove the right paren.
319
+ * E.g. `(function(){}.bind)(this)`
320
+ * ^^^^^^^^^^^^
321
+ */
322
+ if (astUtils.isParenthesised(sourceCode, memberNode)) {
323
+ return; // eslint-disable-line eslint-plugin/fixer-return -- false positive
324
+ }
325
+
326
+ // If comments exist in the `.bind(this)`, don't remove those.
327
+ if (sourceCode.commentsExistBetween(firstTokenToRemove, lastTokenToRemove)) {
328
+ return; // eslint-disable-line eslint-plugin/fixer-return -- false positive
329
+ }
330
+
331
+ yield fixer.removeRange([firstTokenToRemove.range[0], lastTokenToRemove.range[1]]);
332
+ }
333
+
334
+ // Convert the function expression to an arrow function.
335
+ const functionToken = sourceCode.getFirstToken(node, node.async ? 1 : 0);
336
+ const leftParenToken = sourceCode.getTokenAfter(functionToken, astUtils.isOpeningParenToken);
337
+
338
+ if (sourceCode.commentsExistBetween(functionToken, leftParenToken)) {
339
+
340
+ // Remove only extra tokens to keep comments.
341
+ yield fixer.remove(functionToken);
342
+ if (node.id) {
343
+ yield fixer.remove(node.id);
344
+ }
345
+ } else {
346
+
347
+ // Remove extra tokens and spaces.
348
+ yield fixer.removeRange([functionToken.range[0], leftParenToken.range[0]]);
349
+ }
350
+ yield fixer.insertTextBefore(node.body, "=> ");
351
+
352
+ // Get the node that will become the new arrow function.
353
+ let replacedNode = callbackInfo.isLexicalThis ? node.parent.parent : node;
354
+
355
+ if (replacedNode.type === "ChainExpression") {
356
+ replacedNode = replacedNode.parent;
357
+ }
298
358
 
299
359
  /*
300
360
  * If the replaced node is part of a BinaryExpression, LogicalExpression, or MemberExpression, then
301
361
  * the arrow function needs to be parenthesized, because `foo || () => {}` is invalid syntax even
302
362
  * though `foo || function() {}` is valid.
303
363
  */
304
- const needsParens = replacedNode.parent.type !== "CallExpression" && replacedNode.parent.type !== "ConditionalExpression";
305
- const replacementText = needsParens ? `(${arrowFunctionText})` : arrowFunctionText;
306
-
307
- return fixer.replaceText(replacedNode, replacementText);
364
+ if (
365
+ replacedNode.parent.type !== "CallExpression" &&
366
+ replacedNode.parent.type !== "ConditionalExpression" &&
367
+ !astUtils.isParenthesised(sourceCode, replacedNode) &&
368
+ !astUtils.isParenthesised(sourceCode, node)
369
+ ) {
370
+ yield fixer.insertTextBefore(replacedNode, "(");
371
+ yield fixer.insertTextAfter(replacedNode, ")");
372
+ }
308
373
  }
309
374
  });
310
375
  }
@@ -52,7 +52,7 @@ function doesExponentNeedParens(exponent) {
52
52
  * @returns {boolean} `true` if the expression needs to be parenthesised.
53
53
  */
54
54
  function doesExponentiationExpressionNeedParens(node, sourceCode) {
55
- const parent = node.parent;
55
+ const parent = node.parent.type === "ChainExpression" ? node.parent.parent : node.parent;
56
56
 
57
57
  const needsParens = (
58
58
  parent.type === "ClassDeclaration" ||
@@ -29,19 +29,10 @@ const radixMap = new Map([
29
29
  * false otherwise.
30
30
  */
31
31
  function isParseInt(calleeNode) {
32
- switch (calleeNode.type) {
33
- case "Identifier":
34
- return calleeNode.name === "parseInt";
35
- case "MemberExpression":
36
- return calleeNode.object.type === "Identifier" &&
37
- calleeNode.object.name === "Number" &&
38
- calleeNode.property.type === "Identifier" &&
39
- calleeNode.property.name === "parseInt";
40
-
41
- // no default
42
- }
43
-
44
- return false;
32
+ return (
33
+ astUtils.isSpecificId(calleeNode, "parseInt") ||
34
+ astUtils.isSpecificMemberAccess(calleeNode, "Number", "parseInt")
35
+ );
45
36
  }
46
37
 
47
38
  //------------------------------------------------------------------------------
@@ -73,9 +73,7 @@ module.exports = {
73
73
  * @returns {boolean} `true` if the call is a Promise.reject() call
74
74
  */
75
75
  function isPromiseRejectCall(node) {
76
- return node.callee.type === "MemberExpression" &&
77
- node.callee.object.type === "Identifier" && node.callee.object.name === "Promise" &&
78
- node.callee.property.type === "Identifier" && node.callee.property.name === "reject";
76
+ return astUtils.isSpecificMemberAccess(node.callee, "Promise", "reject");
79
77
  }
80
78
 
81
79
  //----------------------------------------------------------------------
@@ -25,6 +25,15 @@ function isStringLiteral(node) {
25
25
  return node.type === "Literal" && typeof node.value === "string";
26
26
  }
27
27
 
28
+ /**
29
+ * Determines whether the given node is a regex literal.
30
+ * @param {ASTNode} node Node to check.
31
+ * @returns {boolean} True if the node is a regex literal.
32
+ */
33
+ function isRegexLiteral(node) {
34
+ return node.type === "Literal" && Object.prototype.hasOwnProperty.call(node, "regex");
35
+ }
36
+
28
37
  /**
29
38
  * Determines whether the given node is a template literal without expressions.
30
39
  * @param {ASTNode} node Node to check.
@@ -50,14 +59,28 @@ module.exports = {
50
59
  url: "https://eslint.org/docs/rules/prefer-regex-literals"
51
60
  },
52
61
 
53
- schema: [],
62
+ schema: [
63
+ {
64
+ type: "object",
65
+ properties: {
66
+ disallowRedundantWrapping: {
67
+ type: "boolean",
68
+ default: false
69
+ }
70
+ },
71
+ additionalProperties: false
72
+ }
73
+ ],
54
74
 
55
75
  messages: {
56
- unexpectedRegExp: "Use a regular expression literal instead of the 'RegExp' constructor."
76
+ unexpectedRegExp: "Use a regular expression literal instead of the 'RegExp' constructor.",
77
+ unexpectedRedundantRegExp: "Regular expression literal is unnecessarily wrapped within a 'RegExp' constructor.",
78
+ unexpectedRedundantRegExpWithFlags: "Use regular expression literal with flags instead of the 'RegExp' constructor."
57
79
  }
58
80
  },
59
81
 
60
82
  create(context) {
83
+ const [{ disallowRedundantWrapping = false } = {}] = context.options;
61
84
 
62
85
  /**
63
86
  * Determines whether the given identifier node is a reference to a global variable.
@@ -79,11 +102,8 @@ module.exports = {
79
102
  */
80
103
  function isStringRawTaggedStaticTemplateLiteral(node) {
81
104
  return node.type === "TaggedTemplateExpression" &&
82
- node.tag.type === "MemberExpression" &&
83
- node.tag.object.type === "Identifier" &&
84
- node.tag.object.name === "String" &&
85
- isGlobalReference(node.tag.object) &&
86
- astUtils.getStaticPropertyName(node.tag) === "raw" &&
105
+ astUtils.isSpecificMemberAccess(node.tag, "String", "raw") &&
106
+ isGlobalReference(astUtils.skipChainExpression(node.tag).object) &&
87
107
  isStaticTemplateLiteral(node.quasi);
88
108
  }
89
109
 
@@ -98,6 +118,40 @@ module.exports = {
98
118
  isStringRawTaggedStaticTemplateLiteral(node);
99
119
  }
100
120
 
121
+ /**
122
+ * Determines whether the relevant arguments of the given are all static string literals.
123
+ * @param {ASTNode} node Node to check.
124
+ * @returns {boolean} True if all arguments are static strings.
125
+ */
126
+ function hasOnlyStaticStringArguments(node) {
127
+ const args = node.arguments;
128
+
129
+ if ((args.length === 1 || args.length === 2) && args.every(isStaticString)) {
130
+ return true;
131
+ }
132
+
133
+ return false;
134
+ }
135
+
136
+ /**
137
+ * Determines whether the arguments of the given node indicate that a regex literal is unnecessarily wrapped.
138
+ * @param {ASTNode} node Node to check.
139
+ * @returns {boolean} True if the node already contains a regex literal argument.
140
+ */
141
+ function isUnnecessarilyWrappedRegexLiteral(node) {
142
+ const args = node.arguments;
143
+
144
+ if (args.length === 1 && isRegexLiteral(args[0])) {
145
+ return true;
146
+ }
147
+
148
+ if (args.length === 2 && isRegexLiteral(args[0]) && isStaticString(args[1])) {
149
+ return true;
150
+ }
151
+
152
+ return false;
153
+ }
154
+
101
155
  return {
102
156
  Program() {
103
157
  const scope = context.getScope();
@@ -110,12 +164,13 @@ module.exports = {
110
164
  };
111
165
 
112
166
  for (const { node } of tracker.iterateGlobalReferences(traceMap)) {
113
- const args = node.arguments;
114
-
115
- if (
116
- (args.length === 1 || args.length === 2) &&
117
- args.every(isStaticString)
118
- ) {
167
+ if (disallowRedundantWrapping && isUnnecessarilyWrappedRegexLiteral(node)) {
168
+ if (node.arguments.length === 2) {
169
+ context.report({ node, messageId: "unexpectedRedundantRegExpWithFlags" });
170
+ } else {
171
+ context.report({ node, messageId: "unexpectedRedundantRegExp" });
172
+ }
173
+ } else if (hasOnlyStaticStringArguments(node)) {
119
174
  context.report({ node, messageId: "unexpectedRegExp" });
120
175
  }
121
176
  }
@@ -18,17 +18,13 @@ const astUtils = require("./utils/ast-utils");
18
18
  */
19
19
  function isVariadicApplyCalling(node) {
20
20
  return (
21
- node.callee.type === "MemberExpression" &&
22
- node.callee.property.type === "Identifier" &&
23
- node.callee.property.name === "apply" &&
24
- node.callee.computed === false &&
21
+ astUtils.isSpecificMemberAccess(node.callee, null, "apply") &&
25
22
  node.arguments.length === 2 &&
26
23
  node.arguments[1].type !== "ArrayExpression" &&
27
24
  node.arguments[1].type !== "SpreadElement"
28
25
  );
29
26
  }
30
27
 
31
-
32
28
  /**
33
29
  * Checks whether or not `thisArg` is not changed by `.apply()`.
34
30
  * @param {ASTNode|null} expectedThis The node that is the owner of the applied function.
@@ -75,7 +71,7 @@ module.exports = {
75
71
  return;
76
72
  }
77
73
 
78
- const applied = node.callee.object;
74
+ const applied = astUtils.skipChainExpression(astUtils.skipChainExpression(node.callee).object);
79
75
  const expectedThis = (applied.type === "MemberExpression") ? applied.object : null;
80
76
  const thisArg = node.arguments[0];
81
77
 
@@ -166,9 +166,12 @@ module.exports = {
166
166
  if (variable && !isShadowed(variable)) {
167
167
  variable.references.forEach(reference => {
168
168
  const node = reference.identifier.parent;
169
+ const maybeCallee = node.parent.type === "ChainExpression"
170
+ ? node.parent
171
+ : node;
169
172
 
170
- if (isParseIntMethod(node) && astUtils.isCallee(node)) {
171
- checkArguments(node.parent);
173
+ if (isParseIntMethod(node) && astUtils.isCallee(maybeCallee)) {
174
+ checkArguments(maybeCallee.parent);
172
175
  }
173
176
  });
174
177
  }
@@ -223,6 +223,7 @@ module.exports = {
223
223
  BreakStatement: checkNode,
224
224
  ContinueStatement: checkNode,
225
225
  DebuggerStatement: checkNode,
226
+ DoWhileStatement: checkNode,
226
227
  ReturnStatement: checkNode,
227
228
  ThrowStatement: checkNode,
228
229
  ImportDeclaration: checkNode,
@@ -44,6 +44,10 @@ module.exports = {
44
44
  ignoreMemberSort: {
45
45
  type: "boolean",
46
46
  default: false
47
+ },
48
+ allowSeparatedGroups: {
49
+ type: "boolean",
50
+ default: false
47
51
  }
48
52
  },
49
53
  additionalProperties: false
@@ -66,6 +70,7 @@ module.exports = {
66
70
  ignoreDeclarationSort = configuration.ignoreDeclarationSort || false,
67
71
  ignoreMemberSort = configuration.ignoreMemberSort || false,
68
72
  memberSyntaxSortOrder = configuration.memberSyntaxSortOrder || ["none", "all", "multiple", "single"],
73
+ allowSeparatedGroups = configuration.allowSeparatedGroups || false,
69
74
  sourceCode = context.getSourceCode();
70
75
  let previousDeclaration = null;
71
76
 
@@ -115,9 +120,32 @@ module.exports = {
115
120
 
116
121
  }
117
122
 
123
+ /**
124
+ * Calculates number of lines between two nodes. It is assumed that the given `left` node appears before
125
+ * the given `right` node in the source code. Lines are counted from the end of the `left` node till the
126
+ * start of the `right` node. If the given nodes are on the same line, it returns `0`, same as if they were
127
+ * on two consecutive lines.
128
+ * @param {ASTNode} left node that appears before the given `right` node.
129
+ * @param {ASTNode} right node that appears after the given `left` node.
130
+ * @returns {number} number of lines between nodes.
131
+ */
132
+ function getNumberOfLinesBetween(left, right) {
133
+ return Math.max(right.loc.start.line - left.loc.end.line - 1, 0);
134
+ }
135
+
118
136
  return {
119
137
  ImportDeclaration(node) {
120
138
  if (!ignoreDeclarationSort) {
139
+ if (
140
+ previousDeclaration &&
141
+ allowSeparatedGroups &&
142
+ getNumberOfLinesBetween(previousDeclaration, node) > 0
143
+ ) {
144
+
145
+ // reset declaration sort
146
+ previousDeclaration = null;
147
+ }
148
+
121
149
  if (previousDeclaration) {
122
150
  const currentMemberSyntaxGroupIndex = getMemberParameterGroupIndex(node),
123
151
  previousMemberSyntaxGroupIndex = getMemberParameterGroupIndex(previousDeclaration);
@@ -106,7 +106,7 @@ module.exports = {
106
106
  * @returns {void}
107
107
  */
108
108
  function checkCallExpression(node) {
109
- const callee = node.callee;
109
+ const callee = astUtils.skipChainExpression(node.callee);
110
110
 
111
111
  if (callee.type === "MemberExpression") {
112
112
  const methodName = astUtils.getStaticPropertyName(callee);