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
@@ -5,7 +5,7 @@
5
5
 
6
6
  "use strict";
7
7
 
8
- const { isNumericLiteral } = require("./utils/ast-utils");
8
+ const astUtils = require("./utils/ast-utils");
9
9
 
10
10
  // Maximum array length by the ECMAScript Specification.
11
11
  const MAX_ARRAY_LENGTH = 2 ** 32 - 1;
@@ -61,6 +61,10 @@ module.exports = {
61
61
  ignoreArrayIndexes: {
62
62
  type: "boolean",
63
63
  default: false
64
+ },
65
+ ignoreDefaultValues: {
66
+ type: "boolean",
67
+ default: false
64
68
  }
65
69
  },
66
70
  additionalProperties: false
@@ -77,7 +81,8 @@ module.exports = {
77
81
  detectObjects = !!config.detectObjects,
78
82
  enforceConst = !!config.enforceConst,
79
83
  ignore = (config.ignore || []).map(normalizeIgnoreValue),
80
- ignoreArrayIndexes = !!config.ignoreArrayIndexes;
84
+ ignoreArrayIndexes = !!config.ignoreArrayIndexes,
85
+ ignoreDefaultValues = !!config.ignoreDefaultValues;
81
86
 
82
87
  const okTypes = detectObjects ? [] : ["ObjectExpression", "Property", "AssignmentExpression"];
83
88
 
@@ -90,6 +95,17 @@ module.exports = {
90
95
  return ignore.indexOf(value) !== -1;
91
96
  }
92
97
 
98
+ /**
99
+ * Returns whether the number is a default value assignment.
100
+ * @param {ASTNode} fullNumberNode `Literal` or `UnaryExpression` full number node
101
+ * @returns {boolean} true if the number is a default value
102
+ */
103
+ function isDefaultValue(fullNumberNode) {
104
+ const parent = fullNumberNode.parent;
105
+
106
+ return parent.type === "AssignmentPattern" && parent.right === fullNumberNode;
107
+ }
108
+
93
109
  /**
94
110
  * Returns whether the given node is used as a radix within parseInt() or Number.parseInt()
95
111
  * @param {ASTNode} fullNumberNode `Literal` or `UnaryExpression` full number node
@@ -100,12 +116,8 @@ module.exports = {
100
116
 
101
117
  return parent.type === "CallExpression" && fullNumberNode === parent.arguments[1] &&
102
118
  (
103
- parent.callee.name === "parseInt" ||
104
- (
105
- parent.callee.type === "MemberExpression" &&
106
- parent.callee.object.name === "Number" &&
107
- parent.callee.property.name === "parseInt"
108
- )
119
+ astUtils.isSpecificId(parent.callee, "parseInt") ||
120
+ astUtils.isSpecificMemberAccess(parent.callee, "Number", "parseInt")
109
121
  );
110
122
  }
111
123
 
@@ -157,7 +169,7 @@ module.exports = {
157
169
 
158
170
  return {
159
171
  Literal(node) {
160
- if (!isNumericLiteral(node)) {
172
+ if (!astUtils.isNumericLiteral(node)) {
161
173
  return;
162
174
  }
163
175
 
@@ -176,9 +188,12 @@ module.exports = {
176
188
  raw = node.raw;
177
189
  }
178
190
 
191
+ const parent = fullNumberNode.parent;
192
+
179
193
  // Always allow radix arguments and JSX props
180
194
  if (
181
195
  isIgnoredValue(value) ||
196
+ (ignoreDefaultValues && isDefaultValue(fullNumberNode)) ||
182
197
  isParseIntRadix(fullNumberNode) ||
183
198
  isJSXNumber(fullNumberNode) ||
184
199
  (ignoreArrayIndexes && isArrayIndex(fullNumberNode, value))
@@ -186,8 +201,6 @@ module.exports = {
186
201
  return;
187
202
  }
188
203
 
189
- const parent = fullNumberNode.parent;
190
-
191
204
  if (parent.type === "VariableDeclarator") {
192
205
  if (enforceConst && parent.parent.kind !== "const") {
193
206
  context.report({
@@ -24,10 +24,13 @@ const nonCallableGlobals = ["Atomics", "JSON", "Math", "Reflect"];
24
24
  * @returns {string} name to report
25
25
  */
26
26
  function getReportNodeName(node) {
27
- if (node.callee.type === "MemberExpression") {
28
- return getPropertyName(node.callee);
27
+ if (node.type === "ChainExpression") {
28
+ return getReportNodeName(node.expression);
29
29
  }
30
- return node.callee.name;
30
+ if (node.type === "MemberExpression") {
31
+ return getPropertyName(node);
32
+ }
33
+ return node.name;
31
34
  }
32
35
 
33
36
  //------------------------------------------------------------------------------
@@ -69,7 +72,7 @@ module.exports = {
69
72
  }
70
73
 
71
74
  for (const { node, path } of tracker.iterateGlobalReferences(traceMap)) {
72
- const name = getReportNodeName(node);
75
+ const name = getReportNodeName(node.callee);
73
76
  const ref = path[0];
74
77
  const messageId = name === ref ? "unexpectedCall" : "unexpectedRefCall";
75
78
 
@@ -4,6 +4,12 @@
4
4
  */
5
5
  "use strict";
6
6
 
7
+ //------------------------------------------------------------------------------
8
+ // Requirements
9
+ //------------------------------------------------------------------------------
10
+
11
+ const astUtils = require("./utils/ast-utils");
12
+
7
13
  //------------------------------------------------------------------------------
8
14
  // Rule Definition
9
15
  //------------------------------------------------------------------------------
@@ -39,15 +45,19 @@ module.exports = {
39
45
  * @returns {void}
40
46
  */
41
47
  function disallowBuiltIns(node) {
42
- if (node.callee.type !== "MemberExpression" || node.callee.computed) {
48
+
49
+ // TODO: just use `astUtils.getStaticPropertyName(node.callee)`
50
+ const callee = astUtils.skipChainExpression(node.callee);
51
+
52
+ if (callee.type !== "MemberExpression" || callee.computed) {
43
53
  return;
44
54
  }
45
- const propName = node.callee.property.name;
55
+ const propName = callee.property.name;
46
56
 
47
57
  if (DISALLOWED_PROPS.indexOf(propName) > -1) {
48
58
  context.report({
49
59
  messageId: "prototypeBuildIn",
50
- loc: node.callee.property.loc,
60
+ loc: callee.property.loc,
51
61
  data: { prop: propName },
52
62
  node
53
63
  });
@@ -17,56 +17,6 @@ const astUtils = require("./utils/ast-utils");
17
17
 
18
18
  const SPACES = /\s+/gu;
19
19
 
20
- /**
21
- * Checks whether the property of 2 given member expression nodes are the same
22
- * property or not.
23
- * @param {ASTNode} left A member expression node to check.
24
- * @param {ASTNode} right Another member expression node to check.
25
- * @returns {boolean} `true` if the member expressions have the same property.
26
- */
27
- function isSameProperty(left, right) {
28
- if (left.property.type === "Identifier" &&
29
- left.property.type === right.property.type &&
30
- left.property.name === right.property.name &&
31
- left.computed === right.computed
32
- ) {
33
- return true;
34
- }
35
-
36
- const lname = astUtils.getStaticPropertyName(left);
37
- const rname = astUtils.getStaticPropertyName(right);
38
-
39
- return lname !== null && lname === rname;
40
- }
41
-
42
- /**
43
- * Checks whether 2 given member expression nodes are the reference to the same
44
- * property or not.
45
- * @param {ASTNode} left A member expression node to check.
46
- * @param {ASTNode} right Another member expression node to check.
47
- * @returns {boolean} `true` if the member expressions are the reference to the
48
- * same property or not.
49
- */
50
- function isSameMember(left, right) {
51
- if (!isSameProperty(left, right)) {
52
- return false;
53
- }
54
-
55
- const lobj = left.object;
56
- const robj = right.object;
57
-
58
- if (lobj.type !== robj.type) {
59
- return false;
60
- }
61
- if (lobj.type === "MemberExpression") {
62
- return isSameMember(lobj, robj);
63
- }
64
- if (lobj.type === "ThisExpression") {
65
- return true;
66
- }
67
- return lobj.type === "Identifier" && lobj.name === robj.name;
68
- }
69
-
70
20
  /**
71
21
  * Traverses 2 Pattern nodes in parallel, then reports self-assignments.
72
22
  * @param {ASTNode|null} left A left node to traverse. This is a Pattern or
@@ -162,9 +112,9 @@ function eachSelfAssignment(left, right, props, report) {
162
112
  }
163
113
  } else if (
164
114
  props &&
165
- left.type === "MemberExpression" &&
166
- right.type === "MemberExpression" &&
167
- isSameMember(left, right)
115
+ astUtils.skipChainExpression(left).type === "MemberExpression" &&
116
+ astUtils.skipChainExpression(right).type === "MemberExpression" &&
117
+ astUtils.isSameReference(left, right)
168
118
  ) {
169
119
  report(right);
170
120
  }
@@ -39,15 +39,12 @@ function isGlobalReference(node, scope) {
39
39
  * @returns {boolean} `true` if the node is argument at the given position.
40
40
  */
41
41
  function isArgumentOfGlobalMethodCall(node, scope, objectName, methodName, index) {
42
- const parent = node.parent;
42
+ const callNode = node.parent;
43
43
 
44
- return parent.type === "CallExpression" &&
45
- parent.arguments[index] === node &&
46
- parent.callee.type === "MemberExpression" &&
47
- astUtils.getStaticPropertyName(parent.callee) === methodName &&
48
- parent.callee.object.type === "Identifier" &&
49
- parent.callee.object.name === objectName &&
50
- isGlobalReference(parent.callee.object, scope);
44
+ return callNode.type === "CallExpression" &&
45
+ callNode.arguments[index] === node &&
46
+ astUtils.isSpecificMemberAccess(callNode.callee, objectName, methodName) &&
47
+ isGlobalReference(astUtils.skipChainExpression(callNode.callee).object, scope);
51
48
  }
52
49
 
53
50
  /**
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @fileoverview Rule to flag trailing underscores in variable declarations.
2
+ * @fileoverview Rule to flag dangling underscores in variable declarations.
3
3
  * @author Matt DuVall <http://www.mattduvall.com>
4
4
  */
5
5
 
@@ -45,6 +45,10 @@ module.exports = {
45
45
  enforceInMethodNames: {
46
46
  type: "boolean",
47
47
  default: false
48
+ },
49
+ allowFunctionParams: {
50
+ type: "boolean",
51
+ default: true
48
52
  }
49
53
  },
50
54
  additionalProperties: false
@@ -64,6 +68,7 @@ module.exports = {
64
68
  const allowAfterSuper = typeof options.allowAfterSuper !== "undefined" ? options.allowAfterSuper : false;
65
69
  const allowAfterThisConstructor = typeof options.allowAfterThisConstructor !== "undefined" ? options.allowAfterThisConstructor : false;
66
70
  const enforceInMethodNames = typeof options.enforceInMethodNames !== "undefined" ? options.enforceInMethodNames : false;
71
+ const allowFunctionParams = typeof options.allowFunctionParams !== "undefined" ? options.allowFunctionParams : true;
67
72
 
68
73
  //-------------------------------------------------------------------------
69
74
  // Helpers
@@ -80,12 +85,12 @@ module.exports = {
80
85
  }
81
86
 
82
87
  /**
83
- * Check if identifier has a underscore at the end
88
+ * Check if identifier has a dangling underscore
84
89
  * @param {string} identifier name of the node
85
90
  * @returns {boolean} true if its is present
86
91
  * @private
87
92
  */
88
- function hasTrailingUnderscore(identifier) {
93
+ function hasDanglingUnderscore(identifier) {
89
94
  const len = identifier.length;
90
95
 
91
96
  return identifier !== "_" && (identifier[0] === "_" || identifier[len - 1] === "_");
@@ -126,16 +131,53 @@ module.exports = {
126
131
  }
127
132
 
128
133
  /**
129
- * Check if function has a underscore at the end
134
+ * Check if function parameter has a dangling underscore.
135
+ * @param {ASTNode} node function node to evaluate
136
+ * @returns {void}
137
+ * @private
138
+ */
139
+ function checkForDanglingUnderscoreInFunctionParameters(node) {
140
+ if (!allowFunctionParams) {
141
+ node.params.forEach(param => {
142
+ const { type } = param;
143
+ let nodeToCheck;
144
+
145
+ if (type === "RestElement") {
146
+ nodeToCheck = param.argument;
147
+ } else if (type === "AssignmentPattern") {
148
+ nodeToCheck = param.left;
149
+ } else {
150
+ nodeToCheck = param;
151
+ }
152
+
153
+ if (nodeToCheck.type === "Identifier") {
154
+ const identifier = nodeToCheck.name;
155
+
156
+ if (hasDanglingUnderscore(identifier) && !isAllowed(identifier)) {
157
+ context.report({
158
+ node: param,
159
+ messageId: "unexpectedUnderscore",
160
+ data: {
161
+ identifier
162
+ }
163
+ });
164
+ }
165
+ }
166
+ });
167
+ }
168
+ }
169
+
170
+ /**
171
+ * Check if function has a dangling underscore
130
172
  * @param {ASTNode} node node to evaluate
131
173
  * @returns {void}
132
174
  * @private
133
175
  */
134
- function checkForTrailingUnderscoreInFunctionDeclaration(node) {
135
- if (node.id) {
176
+ function checkForDanglingUnderscoreInFunction(node) {
177
+ if (node.type === "FunctionDeclaration" && node.id) {
136
178
  const identifier = node.id.name;
137
179
 
138
- if (typeof identifier !== "undefined" && hasTrailingUnderscore(identifier) && !isAllowed(identifier)) {
180
+ if (typeof identifier !== "undefined" && hasDanglingUnderscore(identifier) && !isAllowed(identifier)) {
139
181
  context.report({
140
182
  node,
141
183
  messageId: "unexpectedUnderscore",
@@ -145,18 +187,19 @@ module.exports = {
145
187
  });
146
188
  }
147
189
  }
190
+ checkForDanglingUnderscoreInFunctionParameters(node);
148
191
  }
149
192
 
150
193
  /**
151
- * Check if variable expression has a underscore at the end
194
+ * Check if variable expression has a dangling underscore
152
195
  * @param {ASTNode} node node to evaluate
153
196
  * @returns {void}
154
197
  * @private
155
198
  */
156
- function checkForTrailingUnderscoreInVariableExpression(node) {
199
+ function checkForDanglingUnderscoreInVariableExpression(node) {
157
200
  const identifier = node.id.name;
158
201
 
159
- if (typeof identifier !== "undefined" && hasTrailingUnderscore(identifier) &&
202
+ if (typeof identifier !== "undefined" && hasDanglingUnderscore(identifier) &&
160
203
  !isSpecialCaseIdentifierInVariableExpression(identifier) && !isAllowed(identifier)) {
161
204
  context.report({
162
205
  node,
@@ -169,18 +212,18 @@ module.exports = {
169
212
  }
170
213
 
171
214
  /**
172
- * Check if member expression has a underscore at the end
215
+ * Check if member expression has a dangling underscore
173
216
  * @param {ASTNode} node node to evaluate
174
217
  * @returns {void}
175
218
  * @private
176
219
  */
177
- function checkForTrailingUnderscoreInMemberExpression(node) {
220
+ function checkForDanglingUnderscoreInMemberExpression(node) {
178
221
  const identifier = node.property.name,
179
222
  isMemberOfThis = node.object.type === "ThisExpression",
180
223
  isMemberOfSuper = node.object.type === "Super",
181
224
  isMemberOfThisConstructor = isThisConstructorReference(node);
182
225
 
183
- if (typeof identifier !== "undefined" && hasTrailingUnderscore(identifier) &&
226
+ if (typeof identifier !== "undefined" && hasDanglingUnderscore(identifier) &&
184
227
  !(isMemberOfThis && allowAfterThis) &&
185
228
  !(isMemberOfSuper && allowAfterSuper) &&
186
229
  !(isMemberOfThisConstructor && allowAfterThisConstructor) &&
@@ -196,16 +239,16 @@ module.exports = {
196
239
  }
197
240
 
198
241
  /**
199
- * Check if method declaration or method property has a underscore at the end
242
+ * Check if method declaration or method property has a dangling underscore
200
243
  * @param {ASTNode} node node to evaluate
201
244
  * @returns {void}
202
245
  * @private
203
246
  */
204
- function checkForTrailingUnderscoreInMethod(node) {
247
+ function checkForDanglingUnderscoreInMethod(node) {
205
248
  const identifier = node.key.name;
206
249
  const isMethod = node.type === "MethodDefinition" || node.type === "Property" && node.method;
207
250
 
208
- if (typeof identifier !== "undefined" && enforceInMethodNames && isMethod && hasTrailingUnderscore(identifier) && !isAllowed(identifier)) {
251
+ if (typeof identifier !== "undefined" && enforceInMethodNames && isMethod && hasDanglingUnderscore(identifier) && !isAllowed(identifier)) {
209
252
  context.report({
210
253
  node,
211
254
  messageId: "unexpectedUnderscore",
@@ -221,11 +264,13 @@ module.exports = {
221
264
  //--------------------------------------------------------------------------
222
265
 
223
266
  return {
224
- FunctionDeclaration: checkForTrailingUnderscoreInFunctionDeclaration,
225
- VariableDeclarator: checkForTrailingUnderscoreInVariableExpression,
226
- MemberExpression: checkForTrailingUnderscoreInMemberExpression,
227
- MethodDefinition: checkForTrailingUnderscoreInMethod,
228
- Property: checkForTrailingUnderscoreInMethod
267
+ FunctionDeclaration: checkForDanglingUnderscoreInFunction,
268
+ VariableDeclarator: checkForDanglingUnderscoreInVariableExpression,
269
+ MemberExpression: checkForDanglingUnderscoreInMemberExpression,
270
+ MethodDefinition: checkForDanglingUnderscoreInMethod,
271
+ Property: checkForDanglingUnderscoreInMethod,
272
+ FunctionExpression: checkForDanglingUnderscoreInFunction,
273
+ ArrowFunctionExpression: checkForDanglingUnderscoreInFunction
229
274
  };
230
275
 
231
276
  }
@@ -68,7 +68,7 @@ module.exports = {
68
68
  return {
69
69
 
70
70
  MemberExpression(node) {
71
- if (!node.computed) {
71
+ if (!node.computed || node.optional) {
72
72
  return;
73
73
  }
74
74
  checkForBreakAfter(node.object, "property");
@@ -96,7 +96,7 @@ module.exports = {
96
96
  },
97
97
 
98
98
  CallExpression(node) {
99
- if (node.arguments.length === 0) {
99
+ if (node.arguments.length === 0 || node.optional) {
100
100
  return;
101
101
  }
102
102
  checkForBreakAfter(node.callee, "function");
@@ -122,7 +122,6 @@ module.exports = {
122
122
  if (isBooleanLiteral(node.alternate) && isBooleanLiteral(node.consequent)) {
123
123
  context.report({
124
124
  node,
125
- loc: node.consequent.loc.start,
126
125
  messageId: "unnecessaryConditionalExpression",
127
126
  fix(fixer) {
128
127
  if (node.consequent.value === node.alternate.value) {
@@ -144,7 +143,6 @@ module.exports = {
144
143
  } else if (!defaultAssignment && matchesDefaultAssignment(node)) {
145
144
  context.report({
146
145
  node,
147
- loc: node.consequent.loc.start,
148
146
  messageId: "unnecessaryConditionalAssignment",
149
147
  fix: fixer => {
150
148
  const shouldParenthesizeAlternate =
@@ -8,6 +8,22 @@
8
8
  // Rule Definition
9
9
  //------------------------------------------------------------------------------
10
10
 
11
+ /**
12
+ * Returns `true`.
13
+ * @returns {boolean} `true`.
14
+ */
15
+ function alwaysTrue() {
16
+ return true;
17
+ }
18
+
19
+ /**
20
+ * Returns `false`.
21
+ * @returns {boolean} `false`.
22
+ */
23
+ function alwaysFalse() {
24
+ return false;
25
+ }
26
+
11
27
  module.exports = {
12
28
  meta: {
13
29
  type: "suggestion",
@@ -101,40 +117,56 @@ module.exports = {
101
117
  }
102
118
 
103
119
  /**
104
- * Determines whether or not a given node is a valid expression. Recurses on short circuit eval and ternary nodes if enabled by flags.
105
- * @param {ASTNode} node any node
106
- * @returns {boolean} whether the given node is a valid expression
120
+ * The member functions return `true` if the type has no side-effects.
121
+ * Unknown nodes are handled as `false`, then this rule ignores those.
107
122
  */
108
- function isValidExpression(node) {
109
- if (allowTernary) {
110
-
111
- // Recursive check for ternary and logical expressions
112
- if (node.type === "ConditionalExpression") {
113
- return isValidExpression(node.consequent) && isValidExpression(node.alternate);
123
+ const Checker = Object.assign(Object.create(null), {
124
+ isDisallowed(node) {
125
+ return (Checker[node.type] || alwaysFalse)(node);
126
+ },
127
+
128
+ ArrayExpression: alwaysTrue,
129
+ ArrowFunctionExpression: alwaysTrue,
130
+ BinaryExpression: alwaysTrue,
131
+ ChainExpression(node) {
132
+ return Checker.isDisallowed(node.expression);
133
+ },
134
+ ClassExpression: alwaysTrue,
135
+ ConditionalExpression(node) {
136
+ if (allowTernary) {
137
+ return Checker.isDisallowed(node.consequent) || Checker.isDisallowed(node.alternate);
114
138
  }
115
- }
116
-
117
- if (allowShortCircuit) {
118
- if (node.type === "LogicalExpression") {
119
- return isValidExpression(node.right);
139
+ return true;
140
+ },
141
+ FunctionExpression: alwaysTrue,
142
+ Identifier: alwaysTrue,
143
+ Literal: alwaysTrue,
144
+ LogicalExpression(node) {
145
+ if (allowShortCircuit) {
146
+ return Checker.isDisallowed(node.right);
120
147
  }
121
- }
122
-
123
- if (allowTaggedTemplates && node.type === "TaggedTemplateExpression") {
124
148
  return true;
149
+ },
150
+ MemberExpression: alwaysTrue,
151
+ MetaProperty: alwaysTrue,
152
+ ObjectExpression: alwaysTrue,
153
+ SequenceExpression: alwaysTrue,
154
+ TaggedTemplateExpression() {
155
+ return !allowTaggedTemplates;
156
+ },
157
+ TemplateLiteral: alwaysTrue,
158
+ ThisExpression: alwaysTrue,
159
+ UnaryExpression(node) {
160
+ return node.operator !== "void" && node.operator !== "delete";
125
161
  }
126
-
127
- return /^(?:Assignment|Call|New|Update|Yield|Await|Import)Expression$/u.test(node.type) ||
128
- (node.type === "UnaryExpression" && ["delete", "void"].indexOf(node.operator) >= 0);
129
- }
162
+ });
130
163
 
131
164
  return {
132
165
  ExpressionStatement(node) {
133
- if (!isValidExpression(node.expression) && !isDirective(node, context.getAncestors())) {
166
+ if (Checker.isDisallowed(node.expression) && !isDirective(node, context.getAncestors())) {
134
167
  context.report({ node, messageId: "unusedExpression" });
135
168
  }
136
169
  }
137
170
  };
138
-
139
171
  }
140
172
  };
@@ -17,13 +17,15 @@ const astUtils = require("./utils/ast-utils");
17
17
  * @returns {boolean} Whether or not the node is a `.call()`/`.apply()`.
18
18
  */
19
19
  function isCallOrNonVariadicApply(node) {
20
+ const callee = astUtils.skipChainExpression(node.callee);
21
+
20
22
  return (
21
- node.callee.type === "MemberExpression" &&
22
- node.callee.property.type === "Identifier" &&
23
- node.callee.computed === false &&
23
+ callee.type === "MemberExpression" &&
24
+ callee.property.type === "Identifier" &&
25
+ callee.computed === false &&
24
26
  (
25
- (node.callee.property.name === "call" && node.arguments.length >= 1) ||
26
- (node.callee.property.name === "apply" && node.arguments.length === 2 && node.arguments[1].type === "ArrayExpression")
27
+ (callee.property.name === "call" && node.arguments.length >= 1) ||
28
+ (callee.property.name === "apply" && node.arguments.length === 2 && node.arguments[1].type === "ArrayExpression")
27
29
  )
28
30
  );
29
31
  }
@@ -74,12 +76,13 @@ module.exports = {
74
76
  return;
75
77
  }
76
78
 
77
- const applied = node.callee.object;
79
+ const callee = astUtils.skipChainExpression(node.callee);
80
+ const applied = astUtils.skipChainExpression(callee.object);
78
81
  const expectedThis = (applied.type === "MemberExpression") ? applied.object : null;
79
82
  const thisArg = node.arguments[0];
80
83
 
81
84
  if (isValidThisArg(expectedThis, thisArg, sourceCode)) {
82
- context.report({ node, messageId: "unnecessaryCall", data: { name: node.callee.property.name } });
85
+ context.report({ node, messageId: "unnecessaryCall", data: { name: callee.property.name } });
83
86
  }
84
87
  }
85
88
  };