eslint 7.4.0 → 7.8.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 (93) hide show
  1. package/CHANGELOG.md +86 -0
  2. package/README.md +29 -17
  3. package/conf/config-schema.js +12 -0
  4. package/lib/cli-engine/cascading-config-array-factory.js +12 -0
  5. package/lib/cli-engine/cli-engine.js +2 -2
  6. package/lib/cli-engine/config-array/config-array.js +12 -0
  7. package/lib/cli-engine/config-array/config-dependency.js +12 -0
  8. package/lib/cli-engine/config-array/extracted-config.js +12 -0
  9. package/lib/cli-engine/config-array/ignore-pattern.js +12 -0
  10. package/lib/cli-engine/config-array/index.js +12 -0
  11. package/lib/cli-engine/config-array/override-tester.js +12 -0
  12. package/lib/cli-engine/config-array-factory.js +13 -1
  13. package/lib/cli-engine/formatters/checkstyle.js +2 -2
  14. package/lib/eslint/eslint.js +7 -1
  15. package/lib/init/autoconfig.js +1 -1
  16. package/lib/init/config-initializer.js +3 -3
  17. package/lib/linter/code-path-analysis/code-path-analyzer.js +76 -0
  18. package/lib/linter/code-path-analysis/code-path-segment.js +0 -1
  19. package/lib/linter/code-path-analysis/code-path-state.js +61 -2
  20. package/lib/linter/code-path-analysis/debug-helpers.js +26 -19
  21. package/lib/linter/config-comment-parser.js +1 -1
  22. package/lib/linter/linter.js +6 -3
  23. package/lib/rule-tester/rule-tester.js +10 -0
  24. package/lib/rules/accessor-pairs.js +1 -14
  25. package/lib/rules/array-callback-return.js +5 -7
  26. package/lib/rules/arrow-body-style.js +41 -6
  27. package/lib/rules/comma-dangle.js +1 -2
  28. package/lib/rules/consistent-return.js +1 -12
  29. package/lib/rules/constructor-super.js +18 -1
  30. package/lib/rules/dot-location.js +20 -14
  31. package/lib/rules/dot-notation.js +36 -33
  32. package/lib/rules/func-call-spacing.js +42 -6
  33. package/lib/rules/func-name-matching.js +1 -4
  34. package/lib/rules/global-require.js +2 -1
  35. package/lib/rules/id-blacklist.js +233 -0
  36. package/lib/rules/id-length.js +19 -1
  37. package/lib/rules/indent.js +23 -3
  38. package/lib/rules/index.js +1 -3
  39. package/lib/rules/keyword-spacing.js +2 -2
  40. package/lib/rules/max-len.js +13 -2
  41. package/lib/rules/new-cap.js +10 -14
  42. package/lib/rules/newline-per-chained-call.js +15 -5
  43. package/lib/rules/no-alert.js +10 -3
  44. package/lib/rules/no-duplicate-case.js +23 -4
  45. package/lib/rules/no-eval.js +8 -38
  46. package/lib/rules/no-extend-native.js +37 -40
  47. package/lib/rules/no-extra-bind.js +57 -17
  48. package/lib/rules/no-extra-boolean-cast.js +7 -0
  49. package/lib/rules/no-extra-parens.js +27 -7
  50. package/lib/rules/no-implicit-coercion.js +11 -6
  51. package/lib/rules/no-implied-eval.js +7 -28
  52. package/lib/rules/no-import-assign.js +33 -32
  53. package/lib/rules/no-irregular-whitespace.js +22 -12
  54. package/lib/rules/no-loss-of-precision.js +10 -2
  55. package/lib/rules/no-magic-numbers.js +24 -11
  56. package/lib/rules/no-obj-calls.js +7 -4
  57. package/lib/rules/no-prototype-builtins.js +13 -3
  58. package/lib/rules/no-self-assign.js +3 -53
  59. package/lib/rules/no-setter-return.js +5 -8
  60. package/lib/rules/no-underscore-dangle.js +66 -21
  61. package/lib/rules/no-unexpected-multiline.js +2 -2
  62. package/lib/rules/no-unneeded-ternary.js +0 -2
  63. package/lib/rules/no-unused-expressions.js +55 -23
  64. package/lib/rules/no-useless-call.js +10 -7
  65. package/lib/rules/no-warning-comments.js +40 -7
  66. package/lib/rules/no-whitespace-before-property.js +16 -4
  67. package/lib/rules/object-curly-newline.js +4 -4
  68. package/lib/rules/operator-assignment.js +4 -43
  69. package/lib/rules/padding-line-between-statements.js +2 -2
  70. package/lib/rules/prefer-arrow-callback.js +90 -25
  71. package/lib/rules/prefer-exponentiation-operator.js +1 -1
  72. package/lib/rules/prefer-numeric-literals.js +14 -13
  73. package/lib/rules/prefer-promise-reject-errors.js +1 -3
  74. package/lib/rules/prefer-regex-literals.js +2 -5
  75. package/lib/rules/prefer-spread.js +2 -6
  76. package/lib/rules/radix.js +5 -2
  77. package/lib/rules/sort-imports.js +28 -0
  78. package/lib/rules/use-isnan.js +1 -1
  79. package/lib/rules/utils/ast-utils.js +363 -165
  80. package/lib/rules/wrap-iife.js +9 -2
  81. package/lib/rules/yoda.js +2 -55
  82. package/lib/shared/config-validator.js +14 -2
  83. package/lib/shared/relative-module-resolver.js +12 -0
  84. package/lib/shared/types.js +1 -1
  85. package/messages/extend-config-missing.txt +1 -1
  86. package/messages/no-config-found.txt +1 -1
  87. package/messages/plugin-conflict.txt +1 -1
  88. package/messages/plugin-missing.txt +1 -1
  89. package/messages/whitespace-found.txt +1 -1
  90. package/package.json +7 -7
  91. package/conf/environments.js +0 -168
  92. package/lib/shared/config-ops.js +0 -130
  93. package/lib/shared/naming.js +0 -97
@@ -8,6 +8,8 @@
8
8
  const { escapeRegExp } = require("lodash");
9
9
  const astUtils = require("./utils/ast-utils");
10
10
 
11
+ const CHAR_LIMIT = 40;
12
+
11
13
  //------------------------------------------------------------------------------
12
14
  // Rule Definition
13
15
  //------------------------------------------------------------------------------
@@ -42,12 +44,11 @@ module.exports = {
42
44
  ],
43
45
 
44
46
  messages: {
45
- unexpectedComment: "Unexpected '{{matchedTerm}}' comment."
47
+ unexpectedComment: "Unexpected '{{matchedTerm}}' comment: '{{comment}}'."
46
48
  }
47
49
  },
48
50
 
49
51
  create(context) {
50
-
51
52
  const sourceCode = context.getSourceCode(),
52
53
  configuration = context.options[0] || {},
53
54
  warningTerms = configuration.terms || ["todo", "fixme", "xxx"],
@@ -107,7 +108,15 @@ module.exports = {
107
108
  * \bTERM\b|\bTERM\b, this checks the entire comment
108
109
  * for the term.
109
110
  */
110
- return new RegExp(prefix + escaped + suffix + eitherOrWordBoundary + term + wordBoundary, "iu");
111
+ return new RegExp(
112
+ prefix +
113
+ escaped +
114
+ suffix +
115
+ eitherOrWordBoundary +
116
+ term +
117
+ wordBoundary,
118
+ "iu"
119
+ );
111
120
  }
112
121
 
113
122
  const warningRegExps = warningTerms.map(convertToRegExp);
@@ -135,18 +144,40 @@ module.exports = {
135
144
  * @returns {void} undefined.
136
145
  */
137
146
  function checkComment(node) {
138
- if (astUtils.isDirectiveComment(node) && selfConfigRegEx.test(node.value)) {
147
+ const comment = node.value;
148
+
149
+ if (
150
+ astUtils.isDirectiveComment(node) &&
151
+ selfConfigRegEx.test(comment)
152
+ ) {
139
153
  return;
140
154
  }
141
155
 
142
- const matches = commentContainsWarningTerm(node.value);
156
+ const matches = commentContainsWarningTerm(comment);
143
157
 
144
158
  matches.forEach(matchedTerm => {
159
+ let commentToDisplay = "";
160
+ let truncated = false;
161
+
162
+ for (const c of comment.trim().split(/\s+/u)) {
163
+ const tmp = commentToDisplay ? `${commentToDisplay} ${c}` : c;
164
+
165
+ if (tmp.length <= CHAR_LIMIT) {
166
+ commentToDisplay = tmp;
167
+ } else {
168
+ truncated = true;
169
+ break;
170
+ }
171
+ }
172
+
145
173
  context.report({
146
174
  node,
147
175
  messageId: "unexpectedComment",
148
176
  data: {
149
- matchedTerm
177
+ matchedTerm,
178
+ comment: `${commentToDisplay}${
179
+ truncated ? "..." : ""
180
+ }`
150
181
  }
151
182
  });
152
183
  });
@@ -156,7 +187,9 @@ module.exports = {
156
187
  Program() {
157
188
  const comments = sourceCode.getAllComments();
158
189
 
159
- comments.filter(token => token.type !== "Shebang").forEach(checkComment);
190
+ comments
191
+ .filter(token => token.type !== "Shebang")
192
+ .forEach(checkComment);
160
193
  }
161
194
  };
162
195
  }
@@ -49,8 +49,6 @@ module.exports = {
49
49
  * @private
50
50
  */
51
51
  function reportError(node, leftToken, rightToken) {
52
- const replacementText = node.computed ? "" : ".";
53
-
54
52
  context.report({
55
53
  node,
56
54
  messageId: "unexpectedWhitespace",
@@ -58,7 +56,9 @@ module.exports = {
58
56
  propName: sourceCode.getText(node.property)
59
57
  },
60
58
  fix(fixer) {
61
- if (!node.computed && astUtils.isDecimalInteger(node.object)) {
59
+ let replacementText = "";
60
+
61
+ if (!node.computed && !node.optional && astUtils.isDecimalInteger(node.object)) {
62
62
 
63
63
  /*
64
64
  * If the object is a number literal, fixing it to something like 5.toString() would cause a SyntaxError.
@@ -66,6 +66,18 @@ module.exports = {
66
66
  */
67
67
  return null;
68
68
  }
69
+
70
+ // Don't fix if comments exist.
71
+ if (sourceCode.commentsExistBetween(leftToken, rightToken)) {
72
+ return null;
73
+ }
74
+
75
+ if (node.optional) {
76
+ replacementText = "?.";
77
+ } else if (!node.computed) {
78
+ replacementText = ".";
79
+ }
80
+
69
81
  return fixer.replaceTextRange([leftToken.range[1], rightToken.range[0]], replacementText);
70
82
  }
71
83
  });
@@ -86,7 +98,7 @@ module.exports = {
86
98
 
87
99
  if (node.computed) {
88
100
  rightToken = sourceCode.getTokenBefore(node.property, astUtils.isOpeningBracketToken);
89
- leftToken = sourceCode.getTokenBefore(rightToken);
101
+ leftToken = sourceCode.getTokenBefore(rightToken, node.optional ? 1 : 0);
90
102
  } else {
91
103
  rightToken = sourceCode.getFirstToken(node.property);
92
104
  leftToken = sourceCode.getTokenBefore(rightToken, 1);
@@ -224,7 +224,7 @@ module.exports = {
224
224
  context.report({
225
225
  messageId: "expectedLinebreakAfterOpeningBrace",
226
226
  node,
227
- loc: openBrace.loc.start,
227
+ loc: openBrace.loc,
228
228
  fix(fixer) {
229
229
  if (hasCommentsFirstToken) {
230
230
  return null;
@@ -238,7 +238,7 @@ module.exports = {
238
238
  context.report({
239
239
  messageId: "expectedLinebreakBeforeClosingBrace",
240
240
  node,
241
- loc: closeBrace.loc.start,
241
+ loc: closeBrace.loc,
242
242
  fix(fixer) {
243
243
  if (hasCommentsLastToken) {
244
244
  return null;
@@ -260,7 +260,7 @@ module.exports = {
260
260
  context.report({
261
261
  messageId: "unexpectedLinebreakAfterOpeningBrace",
262
262
  node,
263
- loc: openBrace.loc.start,
263
+ loc: openBrace.loc,
264
264
  fix(fixer) {
265
265
  if (hasCommentsFirstToken) {
266
266
  return null;
@@ -280,7 +280,7 @@ module.exports = {
280
280
  context.report({
281
281
  messageId: "unexpectedLinebreakBeforeClosingBrace",
282
282
  node,
283
- loc: closeBrace.loc.start,
283
+ loc: closeBrace.loc,
284
284
  fix(fixer) {
285
285
  if (hasCommentsLastToken) {
286
286
  return null;
@@ -40,45 +40,6 @@ function isNonCommutativeOperatorWithShorthand(operator) {
40
40
  // Rule Definition
41
41
  //------------------------------------------------------------------------------
42
42
 
43
- /**
44
- * Checks whether two expressions reference the same value. For example:
45
- * a = a
46
- * a.b = a.b
47
- * a[0] = a[0]
48
- * a['b'] = a['b']
49
- * @param {ASTNode} a Left side of the comparison.
50
- * @param {ASTNode} b Right side of the comparison.
51
- * @returns {boolean} True if both sides match and reference the same value.
52
- */
53
- function same(a, b) {
54
- if (a.type !== b.type) {
55
- return false;
56
- }
57
-
58
- switch (a.type) {
59
- case "Identifier":
60
- return a.name === b.name;
61
-
62
- case "Literal":
63
- return a.value === b.value;
64
-
65
- case "MemberExpression":
66
-
67
- /*
68
- * x[0] = x[0]
69
- * x[y] = x[y]
70
- * x.y = x.y
71
- */
72
- return same(a.object, b.object) && same(a.property, b.property);
73
-
74
- case "ThisExpression":
75
- return true;
76
-
77
- default:
78
- return false;
79
- }
80
- }
81
-
82
43
  /**
83
44
  * Determines if the left side of a node can be safely fixed (i.e. if it activates the same getters/setters and)
84
45
  * toString calls regardless of whether assignment shorthand is used)
@@ -148,12 +109,12 @@ module.exports = {
148
109
  const operator = expr.operator;
149
110
 
150
111
  if (isCommutativeOperatorWithShorthand(operator) || isNonCommutativeOperatorWithShorthand(operator)) {
151
- if (same(left, expr.left)) {
112
+ if (astUtils.isSameReference(left, expr.left, true)) {
152
113
  context.report({
153
114
  node,
154
115
  messageId: "replaced",
155
116
  fix(fixer) {
156
- if (canBeFixed(left)) {
117
+ if (canBeFixed(left) && canBeFixed(expr.left)) {
157
118
  const equalsToken = getOperatorToken(node);
158
119
  const operatorToken = getOperatorToken(expr);
159
120
  const leftText = sourceCode.getText().slice(node.range[0], equalsToken.range[0]);
@@ -169,7 +130,7 @@ module.exports = {
169
130
  return null;
170
131
  }
171
132
  });
172
- } else if (same(left, expr.right) && isCommutativeOperatorWithShorthand(operator)) {
133
+ } else if (astUtils.isSameReference(left, expr.right, true) && isCommutativeOperatorWithShorthand(operator)) {
173
134
 
174
135
  /*
175
136
  * This case can't be fixed safely.
@@ -190,7 +151,7 @@ module.exports = {
190
151
  * @returns {void}
191
152
  */
192
153
  function prohibit(node) {
193
- if (node.operator !== "=") {
154
+ if (node.operator !== "=" && !astUtils.isLogicalAssignmentOperator(node.operator)) {
194
155
  context.report({
195
156
  node,
196
157
  messageId: "unexpected",
@@ -85,10 +85,10 @@ function newNodeTypeTester(type) {
85
85
  */
86
86
  function isIIFEStatement(node) {
87
87
  if (node.type === "ExpressionStatement") {
88
- let call = node.expression;
88
+ let call = astUtils.skipChainExpression(node.expression);
89
89
 
90
90
  if (call.type === "UnaryExpression") {
91
- call = call.argument;
91
+ call = astUtils.skipChainExpression(call.argument);
92
92
  }
93
93
  return call.type === "CallExpression" && astUtils.isFunction(call.callee);
94
94
  }
@@ -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
  //------------------------------------------------------------------------------
@@ -112,6 +103,16 @@ module.exports = {
112
103
  /*
113
104
  * If the newly-produced literal would be invalid, (e.g. 0b1234),
114
105
  * or it would yield an incorrect parseInt result for some other reason, don't make a fix.
106
+ *
107
+ * If `str` had numeric separators, `+replacement` will evaluate to `NaN` because unary `+`
108
+ * per the specification doesn't support numeric separators. Thus, the above condition will be `true`
109
+ * (`NaN !== anything` is always `true`) regardless of the `parseInt(str, radix)` value.
110
+ * Consequently, no autofixes will be made. This is correct behavior because `parseInt` also
111
+ * doesn't support numeric separators, but it does parse part of the string before the first `_`,
112
+ * so the autofix would be invalid:
113
+ *
114
+ * parseInt("1_1", 2) // === 1
115
+ * 0b1_1 // === 3
115
116
  */
116
117
  return null;
117
118
  }
@@ -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
  //----------------------------------------------------------------------
@@ -102,11 +102,8 @@ module.exports = {
102
102
  */
103
103
  function isStringRawTaggedStaticTemplateLiteral(node) {
104
104
  return node.type === "TaggedTemplateExpression" &&
105
- node.tag.type === "MemberExpression" &&
106
- node.tag.object.type === "Identifier" &&
107
- node.tag.object.name === "String" &&
108
- isGlobalReference(node.tag.object) &&
109
- astUtils.getStaticPropertyName(node.tag) === "raw" &&
105
+ astUtils.isSpecificMemberAccess(node.tag, "String", "raw") &&
106
+ isGlobalReference(astUtils.skipChainExpression(node.tag).object) &&
110
107
  isStaticTemplateLiteral(node.quasi);
111
108
  }
112
109
 
@@ -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
  }
@@ -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);