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
@@ -433,11 +433,15 @@ module.exports = {
433
433
  tokenBeforeColon = sourceCode.getTokenBefore(nextColon, { includeComments: true }),
434
434
  tokenAfterColon = sourceCode.getTokenAfter(nextColon, { includeComments: true }),
435
435
  isKeySide = side === "key",
436
- locStart = isKeySide ? tokenBeforeColon.loc.start : tokenAfterColon.loc.start,
437
436
  isExtra = diff > 0,
438
437
  diffAbs = Math.abs(diff),
439
438
  spaces = Array(diffAbs + 1).join(" ");
440
439
 
440
+ const locStart = isKeySide ? tokenBeforeColon.loc.end : nextColon.loc.start;
441
+ const locEnd = isKeySide ? nextColon.loc.start : tokenAfterColon.loc.start;
442
+ const missingLoc = isKeySide ? tokenBeforeColon.loc : tokenAfterColon.loc;
443
+ const loc = isExtra ? { start: locStart, end: locEnd } : missingLoc;
444
+
441
445
  if ((
442
446
  diff && mode === "strict" ||
443
447
  diff < 0 && mode === "minimum" ||
@@ -482,7 +486,7 @@ module.exports = {
482
486
 
483
487
  context.report({
484
488
  node: property[side],
485
- loc: locStart,
489
+ loc,
486
490
  messageId,
487
491
  data: {
488
492
  computed: property.computed ? "computed " : "",
@@ -126,7 +126,7 @@ module.exports = {
126
126
  !sourceCode.isSpaceBetweenTokens(prevToken, token)
127
127
  ) {
128
128
  context.report({
129
- loc: token.loc.start,
129
+ loc: token.loc,
130
130
  messageId: "expectedBefore",
131
131
  data: token,
132
132
  fix(fixer) {
@@ -178,7 +178,7 @@ module.exports = {
178
178
  !sourceCode.isSpaceBetweenTokens(token, nextToken)
179
179
  ) {
180
180
  context.report({
181
- loc: token.loc.start,
181
+ loc: token.loc,
182
182
  messageId: "expectedAfter",
183
183
  data: token,
184
184
  fix(fixer) {
@@ -383,11 +383,22 @@ module.exports = {
383
383
  return;
384
384
  }
385
385
 
386
+ const loc = {
387
+ start: {
388
+ line: lineNumber,
389
+ column: 0
390
+ },
391
+ end: {
392
+ line: lineNumber,
393
+ column: textToMeasure.length
394
+ }
395
+ };
396
+
386
397
  if (commentLengthApplies) {
387
398
  if (lineLength > maxCommentLength) {
388
399
  context.report({
389
400
  node,
390
- loc: { line: lineNumber, column: 0 },
401
+ loc,
391
402
  messageId: "maxComment",
392
403
  data: {
393
404
  lineLength,
@@ -398,7 +409,7 @@ module.exports = {
398
409
  } else if (lineLength > maxLength) {
399
410
  context.report({
400
411
  node,
401
- loc: { line: lineNumber, column: 0 },
412
+ loc,
402
413
  messageId: "max",
403
414
  data: {
404
415
  lineLength,
@@ -53,7 +53,8 @@ module.exports = {
53
53
  }
54
54
  ],
55
55
  messages: {
56
- exceed: "File has too many lines ({{actual}}). Maximum allowed is {{max}}."
56
+ exceed:
57
+ "File has too many lines ({{actual}}). Maximum allowed is {{max}}."
57
58
  }
58
59
  },
59
60
 
@@ -61,7 +62,10 @@ module.exports = {
61
62
  const option = context.options[0];
62
63
  let max = 300;
63
64
 
64
- if (typeof option === "object" && Object.prototype.hasOwnProperty.call(option, "max")) {
65
+ if (
66
+ typeof option === "object" &&
67
+ Object.prototype.hasOwnProperty.call(option, "max")
68
+ ) {
65
69
  max = option.max;
66
70
  } else if (typeof option === "number") {
67
71
  max = option;
@@ -94,7 +98,9 @@ module.exports = {
94
98
 
95
99
  token = comment;
96
100
  do {
97
- token = sourceCode.getTokenBefore(token, { includeComments: true });
101
+ token = sourceCode.getTokenBefore(token, {
102
+ includeComments: true
103
+ });
98
104
  } while (isCommentNodeType(token));
99
105
 
100
106
  if (token && astUtils.isTokenOnSameLine(token, comment)) {
@@ -103,7 +109,9 @@ module.exports = {
103
109
 
104
110
  token = comment;
105
111
  do {
106
- token = sourceCode.getTokenAfter(token, { includeComments: true });
112
+ token = sourceCode.getTokenAfter(token, {
113
+ includeComments: true
114
+ });
107
115
  } while (isCommentNodeType(token));
108
116
 
109
117
  if (token && astUtils.isTokenOnSameLine(comment, token)) {
@@ -118,7 +126,10 @@ module.exports = {
118
126
 
119
127
  return {
120
128
  "Program:exit"() {
121
- let lines = sourceCode.lines.map((text, i) => ({ lineNumber: i + 1, text }));
129
+ let lines = sourceCode.lines.map((text, i) => ({
130
+ lineNumber: i + 1,
131
+ text
132
+ }));
122
133
 
123
134
  if (skipBlankLines) {
124
135
  lines = lines.filter(l => l.text.trim() !== "");
@@ -127,14 +138,29 @@ module.exports = {
127
138
  if (skipComments) {
128
139
  const comments = sourceCode.getAllComments();
129
140
 
130
- const commentLines = lodash.flatten(comments.map(comment => getLinesWithoutCode(comment)));
141
+ const commentLines = lodash.flatten(
142
+ comments.map(comment => getLinesWithoutCode(comment))
143
+ );
131
144
 
132
- lines = lines.filter(l => !lodash.includes(commentLines, l.lineNumber));
145
+ lines = lines.filter(
146
+ l => !lodash.includes(commentLines, l.lineNumber)
147
+ );
133
148
  }
134
149
 
135
150
  if (lines.length > max) {
151
+ const loc = {
152
+ start: {
153
+ line: lines[max].lineNumber,
154
+ column: 0
155
+ },
156
+ end: {
157
+ line: sourceCode.lines.length,
158
+ column: lodash.last(sourceCode.lines).length
159
+ }
160
+ };
161
+
136
162
  context.report({
137
- loc: { line: 1, column: 0 },
163
+ loc,
138
164
  messageId: "exceed",
139
165
  data: {
140
166
  max,
@@ -158,15 +158,9 @@ module.exports = {
158
158
  * @returns {string} name
159
159
  */
160
160
  function extractNameFromExpression(node) {
161
-
162
- let name = "";
163
-
164
- if (node.callee.type === "MemberExpression") {
165
- name = astUtils.getStaticPropertyName(node.callee) || "";
166
- } else {
167
- name = node.callee.name;
168
- }
169
- return name;
161
+ return node.callee.type === "Identifier"
162
+ ? node.callee.name
163
+ : astUtils.getStaticPropertyName(node.callee) || "";
170
164
  }
171
165
 
172
166
  /**
@@ -212,14 +206,16 @@ module.exports = {
212
206
  return true;
213
207
  }
214
208
 
215
- if (calleeName === "UTC" && node.callee.type === "MemberExpression") {
209
+ const callee = astUtils.skipChainExpression(node.callee);
210
+
211
+ if (calleeName === "UTC" && callee.type === "MemberExpression") {
216
212
 
217
213
  // allow if callee is Date.UTC
218
- return node.callee.object.type === "Identifier" &&
219
- node.callee.object.name === "Date";
214
+ return callee.object.type === "Identifier" &&
215
+ callee.object.name === "Date";
220
216
  }
221
217
 
222
- return skipProperties && node.callee.type === "MemberExpression";
218
+ return skipProperties && callee.type === "MemberExpression";
223
219
  }
224
220
 
225
221
  /**
@@ -229,7 +225,7 @@ module.exports = {
229
225
  * @returns {void}
230
226
  */
231
227
  function report(node, messageId) {
232
- let callee = node.callee;
228
+ let callee = astUtils.skipChainExpression(node.callee);
233
229
 
234
230
  if (callee.type === "MemberExpression") {
235
231
  callee = callee.property;
@@ -57,7 +57,16 @@ module.exports = {
57
57
  * @returns {string} The prefix of the node.
58
58
  */
59
59
  function getPrefix(node) {
60
- return node.computed ? "[" : ".";
60
+ if (node.computed) {
61
+ if (node.optional) {
62
+ return "?.[";
63
+ }
64
+ return "[";
65
+ }
66
+ if (node.optional) {
67
+ return "?.";
68
+ }
69
+ return ".";
61
70
  }
62
71
 
63
72
  /**
@@ -76,17 +85,18 @@ module.exports = {
76
85
 
77
86
  return {
78
87
  "CallExpression:exit"(node) {
79
- if (!node.callee || node.callee.type !== "MemberExpression") {
88
+ const callee = astUtils.skipChainExpression(node.callee);
89
+
90
+ if (callee.type !== "MemberExpression") {
80
91
  return;
81
92
  }
82
93
 
83
- const callee = node.callee;
84
- let parent = callee.object;
94
+ let parent = astUtils.skipChainExpression(callee.object);
85
95
  let depth = 1;
86
96
 
87
97
  while (parent && parent.callee) {
88
98
  depth += 1;
89
- parent = parent.callee.object;
99
+ parent = astUtils.skipChainExpression(astUtils.skipChainExpression(parent.callee).object);
90
100
  }
91
101
 
92
102
  if (depth > ignoreChainWithDepth && astUtils.isTokenOnSameLine(callee.object, callee.property)) {
@@ -10,7 +10,8 @@
10
10
 
11
11
  const {
12
12
  getStaticPropertyName: getPropertyName,
13
- getVariableByName
13
+ getVariableByName,
14
+ skipChainExpression
14
15
  } = require("./utils/ast-utils");
15
16
 
16
17
  //------------------------------------------------------------------------------
@@ -64,7 +65,13 @@ function isGlobalThisReferenceOrGlobalWindow(scope, node) {
64
65
  if (scope.type === "global" && node.type === "ThisExpression") {
65
66
  return true;
66
67
  }
67
- if (node.name === "window" || (node.name === "globalThis" && getVariableByName(scope, "globalThis"))) {
68
+ if (
69
+ node.type === "Identifier" &&
70
+ (
71
+ node.name === "window" ||
72
+ (node.name === "globalThis" && getVariableByName(scope, "globalThis"))
73
+ )
74
+ ) {
68
75
  return !isShadowed(scope, node);
69
76
  }
70
77
 
@@ -96,7 +103,7 @@ module.exports = {
96
103
  create(context) {
97
104
  return {
98
105
  CallExpression(node) {
99
- const callee = node.callee,
106
+ const callee = skipChainExpression(node.callee),
100
107
  currentScope = context.getScope();
101
108
 
102
109
  // without window.
@@ -21,38 +21,6 @@ const candidatesOfGlobalObject = Object.freeze([
21
21
  "globalThis"
22
22
  ]);
23
23
 
24
- /**
25
- * Checks a given node is a Identifier node of the specified name.
26
- * @param {ASTNode} node A node to check.
27
- * @param {string} name A name to check.
28
- * @returns {boolean} `true` if the node is a Identifier node of the name.
29
- */
30
- function isIdentifier(node, name) {
31
- return node.type === "Identifier" && node.name === name;
32
- }
33
-
34
- /**
35
- * Checks a given node is a Literal node of the specified string value.
36
- * @param {ASTNode} node A node to check.
37
- * @param {string} name A name to check.
38
- * @returns {boolean} `true` if the node is a Literal node of the name.
39
- */
40
- function isConstant(node, name) {
41
- switch (node.type) {
42
- case "Literal":
43
- return node.value === name;
44
-
45
- case "TemplateLiteral":
46
- return (
47
- node.expressions.length === 0 &&
48
- node.quasis[0].value.cooked === name
49
- );
50
-
51
- default:
52
- return false;
53
- }
54
- }
55
-
56
24
  /**
57
25
  * Checks a given node is a MemberExpression node which has the specified name's
58
26
  * property.
@@ -62,10 +30,7 @@ function isConstant(node, name) {
62
30
  * the specified name's property
63
31
  */
64
32
  function isMember(node, name) {
65
- return (
66
- node.type === "MemberExpression" &&
67
- (node.computed ? isConstant : isIdentifier)(node.property, name)
68
- );
33
+ return astUtils.isSpecificMemberAccess(node, null, name);
69
34
  }
70
35
 
71
36
  //------------------------------------------------------------------------------
@@ -230,7 +195,12 @@ module.exports = {
230
195
  "CallExpression:exit"(node) {
231
196
  const callee = node.callee;
232
197
 
233
- if (isIdentifier(callee, "eval")) {
198
+ /*
199
+ * Optional call (`eval?.("code")`) is not direct eval.
200
+ * The direct eval is only step 6.a.vi of https://tc39.es/ecma262/#sec-function-calls-runtime-semantics-evaluation
201
+ * But the optional call is https://tc39.es/ecma262/#sec-optional-chaining-chain-evaluation
202
+ */
203
+ if (!node.optional && astUtils.isSpecificId(callee, "eval")) {
234
204
  report(callee);
235
205
  }
236
206
  }
@@ -241,7 +211,7 @@ module.exports = {
241
211
  "CallExpression:exit"(node) {
242
212
  const callee = node.callee;
243
213
 
244
- if (isIdentifier(callee, "eval")) {
214
+ if (astUtils.isSpecificId(callee, "eval")) {
245
215
  report(callee);
246
216
  }
247
217
  },
@@ -12,12 +12,6 @@
12
12
  const astUtils = require("./utils/ast-utils");
13
13
  const globals = require("globals");
14
14
 
15
- //------------------------------------------------------------------------------
16
- // Helpers
17
- //------------------------------------------------------------------------------
18
-
19
- const propertyDefinitionMethods = new Set(["defineProperty", "defineProperties"]);
20
-
21
15
  //------------------------------------------------------------------------------
22
16
  // Rule Definition
23
17
  //------------------------------------------------------------------------------
@@ -100,40 +94,30 @@ module.exports = {
100
94
  }
101
95
 
102
96
  /**
103
- * Checks that an identifier is an object of a prototype whose member
104
- * is being assigned in an AssignmentExpression.
105
- * Example: Object.prototype.foo = "bar"
106
- * @param {ASTNode} identifierNode The identifier to check.
107
- * @returns {boolean} True if the identifier's prototype is modified.
97
+ * Check if it's an assignment to the property of the given node.
98
+ * Example: `*.prop = 0` // the `*` is the given node.
99
+ * @param {ASTNode} node The node to check.
100
+ * @returns {boolean} True if an assignment to the property of the node.
108
101
  */
109
- function isInPrototypePropertyAssignment(identifierNode) {
110
- return Boolean(
111
- isPrototypePropertyAccessed(identifierNode) &&
112
- identifierNode.parent.parent.type === "MemberExpression" &&
113
- identifierNode.parent.parent.parent.type === "AssignmentExpression" &&
114
- identifierNode.parent.parent.parent.left === identifierNode.parent.parent
102
+ function isAssigningToPropertyOf(node) {
103
+ return (
104
+ node.parent.type === "MemberExpression" &&
105
+ node.parent.object === node &&
106
+ node.parent.parent.type === "AssignmentExpression" &&
107
+ node.parent.parent.left === node.parent
115
108
  );
116
109
  }
117
110
 
118
111
  /**
119
- * Checks that an identifier is an object of a prototype whose member
120
- * is being extended via the Object.defineProperty() or
121
- * Object.defineProperties() methods.
122
- * Example: Object.defineProperty(Array.prototype, "foo", ...)
123
- * Example: Object.defineProperties(Array.prototype, ...)
124
- * @param {ASTNode} identifierNode The identifier to check.
125
- * @returns {boolean} True if the identifier's prototype is modified.
112
+ * Checks if the given node is at the first argument of the method call of `Object.defineProperty()` or `Object.defineProperties()`.
113
+ * @param {ASTNode} node The node to check.
114
+ * @returns {boolean} True if the node is at the first argument of the method call of `Object.defineProperty()` or `Object.defineProperties()`.
126
115
  */
127
- function isInDefinePropertyCall(identifierNode) {
128
- return Boolean(
129
- isPrototypePropertyAccessed(identifierNode) &&
130
- identifierNode.parent.parent.type === "CallExpression" &&
131
- identifierNode.parent.parent.arguments[0] === identifierNode.parent &&
132
- identifierNode.parent.parent.callee.type === "MemberExpression" &&
133
- identifierNode.parent.parent.callee.object.type === "Identifier" &&
134
- identifierNode.parent.parent.callee.object.name === "Object" &&
135
- identifierNode.parent.parent.callee.property.type === "Identifier" &&
136
- propertyDefinitionMethods.has(identifierNode.parent.parent.callee.property.name)
116
+ function isInDefinePropertyCall(node) {
117
+ return (
118
+ node.parent.type === "CallExpression" &&
119
+ node.parent.arguments[0] === node &&
120
+ astUtils.isSpecificMemberAccess(node.parent.callee, "Object", /^definePropert(?:y|ies)$/u)
137
121
  );
138
122
  }
139
123
 
@@ -149,14 +133,27 @@ module.exports = {
149
133
  * @returns {void}
150
134
  */
151
135
  function checkAndReportPrototypeExtension(identifierNode) {
152
- if (isInPrototypePropertyAssignment(identifierNode)) {
136
+ if (!isPrototypePropertyAccessed(identifierNode)) {
137
+ return; // This is not `*.prototype` access.
138
+ }
139
+
140
+ /*
141
+ * `identifierNode.parent` is a MamberExpression `*.prototype`.
142
+ * If it's an optional member access, it may be wrapped by a `ChainExpression` node.
143
+ */
144
+ const prototypeNode =
145
+ identifierNode.parent.parent.type === "ChainExpression"
146
+ ? identifierNode.parent.parent
147
+ : identifierNode.parent;
148
+
149
+ if (isAssigningToPropertyOf(prototypeNode)) {
153
150
 
154
- // Identifier --> MemberExpression --> MemberExpression --> AssignmentExpression
155
- reportNode(identifierNode.parent.parent.parent, identifierNode.name);
156
- } else if (isInDefinePropertyCall(identifierNode)) {
151
+ // `*.prototype` -> MemberExpression -> AssignmentExpression
152
+ reportNode(prototypeNode.parent.parent, identifierNode.name);
153
+ } else if (isInDefinePropertyCall(prototypeNode)) {
157
154
 
158
- // Identifier --> MemberExpression --> CallExpression
159
- reportNode(identifierNode.parent.parent, identifierNode.name);
155
+ // `*.prototype` -> CallExpression
156
+ reportNode(prototypeNode.parent, identifierNode.name);
160
157
  }
161
158
  }
162
159
 
@@ -61,24 +61,62 @@ module.exports = {
61
61
  * @returns {void}
62
62
  */
63
63
  function report(node) {
64
+ const memberNode = node.parent;
65
+ const callNode = memberNode.parent.type === "ChainExpression"
66
+ ? memberNode.parent.parent
67
+ : memberNode.parent;
68
+
64
69
  context.report({
65
- node: node.parent.parent,
70
+ node: callNode,
66
71
  messageId: "unexpected",
67
- loc: node.parent.property.loc,
72
+ loc: memberNode.property.loc,
73
+
68
74
  fix(fixer) {
69
- if (node.parent.parent.arguments.length && !isSideEffectFree(node.parent.parent.arguments[0])) {
75
+ if (!isSideEffectFree(callNode.arguments[0])) {
70
76
  return null;
71
77
  }
72
78
 
73
- const firstTokenToRemove = sourceCode
74
- .getFirstTokenBetween(node.parent.object, node.parent.property, astUtils.isNotClosingParenToken);
75
- const lastTokenToRemove = sourceCode.getLastToken(node.parent.parent);
79
+ /*
80
+ * The list of the first/last token pair of a removal range.
81
+ * This is two parts because closing parentheses may exist between the method name and arguments.
82
+ * E.g. `(function(){}.bind ) (obj)`
83
+ * ^^^^^ ^^^^^ < removal ranges
84
+ * E.g. `(function(){}?.['bind'] ) ?.(obj)`
85
+ * ^^^^^^^^^^ ^^^^^^^ < removal ranges
86
+ */
87
+ const tokenPairs = [
88
+ [
89
+
90
+ // `.`, `?.`, or `[` token.
91
+ sourceCode.getTokenAfter(
92
+ memberNode.object,
93
+ astUtils.isNotClosingParenToken
94
+ ),
95
+
96
+ // property name or `]` token.
97
+ sourceCode.getLastToken(memberNode)
98
+ ],
99
+ [
100
+
101
+ // `?.` or `(` token of arguments.
102
+ sourceCode.getTokenAfter(
103
+ memberNode,
104
+ astUtils.isNotClosingParenToken
105
+ ),
106
+
107
+ // `)` token of arguments.
108
+ sourceCode.getLastToken(callNode)
109
+ ]
110
+ ];
111
+ const firstTokenToRemove = tokenPairs[0][0];
112
+ const lastTokenToRemove = tokenPairs[1][1];
76
113
 
77
114
  if (sourceCode.commentsExistBetween(firstTokenToRemove, lastTokenToRemove)) {
78
115
  return null;
79
116
  }
80
117
 
81
- return fixer.removeRange([firstTokenToRemove.range[0], node.parent.parent.range[1]]);
118
+ return tokenPairs.map(([start, end]) =>
119
+ fixer.removeRange([start.range[0], end.range[1]]));
82
120
  }
83
121
  });
84
122
  }
@@ -93,18 +131,20 @@ module.exports = {
93
131
  * @returns {boolean} `true` if the node is the callee of `.bind()` method.
94
132
  */
95
133
  function isCalleeOfBindMethod(node) {
96
- const parent = node.parent;
97
- const grandparent = parent.parent;
134
+ if (!astUtils.isSpecificMemberAccess(node.parent, null, "bind")) {
135
+ return false;
136
+ }
137
+
138
+ // The node of `*.bind` member access.
139
+ const bindNode = node.parent.parent.type === "ChainExpression"
140
+ ? node.parent.parent
141
+ : node.parent;
98
142
 
99
143
  return (
100
- grandparent &&
101
- grandparent.type === "CallExpression" &&
102
- grandparent.callee === parent &&
103
- grandparent.arguments.length === 1 &&
104
- grandparent.arguments[0].type !== "SpreadElement" &&
105
- parent.type === "MemberExpression" &&
106
- parent.object === node &&
107
- astUtils.getStaticPropertyName(parent) === "bind"
144
+ bindNode.parent.type === "CallExpression" &&
145
+ bindNode.parent.callee === bindNode &&
146
+ bindNode.parent.arguments.length === 1 &&
147
+ bindNode.parent.arguments[0].type !== "SpreadElement"
108
148
  );
109
149
  }
110
150
 
@@ -111,6 +111,10 @@ module.exports = {
111
111
  * @returns {boolean} If the node is in one of the flagged contexts
112
112
  */
113
113
  function isInFlaggedContext(node) {
114
+ if (node.parent.type === "ChainExpression") {
115
+ return isInFlaggedContext(node.parent);
116
+ }
117
+
114
118
  return isInBooleanContext(node) ||
115
119
  (isLogicalContext(node.parent) &&
116
120
 
@@ -149,6 +153,9 @@ module.exports = {
149
153
  * @returns {boolean} `true` if the node needs to be parenthesized.
150
154
  */
151
155
  function needsParens(previousNode, node) {
156
+ if (previousNode.parent.type === "ChainExpression") {
157
+ return needsParens(previousNode.parent, node);
158
+ }
152
159
  if (isParenthesized(previousNode)) {
153
160
 
154
161
  // parentheses around the previous node will stay, so there is no need for an additional pair