eslint 7.3.1 → 7.7.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 (73) hide show
  1. package/CHANGELOG.md +80 -0
  2. package/README.md +28 -16
  3. package/lib/cli-engine/config-array-factory.js +1 -33
  4. package/lib/cli-engine/formatters/checkstyle.js +2 -2
  5. package/lib/linter/code-path-analysis/code-path-analyzer.js +38 -0
  6. package/lib/linter/code-path-analysis/code-path-segment.js +0 -1
  7. package/lib/linter/code-path-analysis/code-path-state.js +59 -0
  8. package/lib/linter/code-path-analysis/debug-helpers.js +26 -19
  9. package/lib/rule-tester/rule-tester.js +10 -0
  10. package/lib/rules/accessor-pairs.js +1 -14
  11. package/lib/rules/array-callback-return.js +26 -17
  12. package/lib/rules/arrow-body-style.js +43 -8
  13. package/lib/rules/arrow-parens.js +91 -108
  14. package/lib/rules/camelcase.js +47 -0
  15. package/lib/rules/comma-dangle.js +1 -2
  16. package/lib/rules/consistent-return.js +1 -12
  17. package/lib/rules/constructor-super.js +1 -0
  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/indent.js +23 -3
  26. package/lib/rules/index.js +1 -0
  27. package/lib/rules/keyword-spacing.js +2 -2
  28. package/lib/rules/max-len.js +13 -2
  29. package/lib/rules/new-cap.js +10 -14
  30. package/lib/rules/newline-per-chained-call.js +15 -5
  31. package/lib/rules/no-alert.js +10 -3
  32. package/lib/rules/no-duplicate-case.js +23 -4
  33. package/lib/rules/no-eval.js +8 -38
  34. package/lib/rules/no-extend-native.js +37 -40
  35. package/lib/rules/no-extra-bind.js +57 -17
  36. package/lib/rules/no-extra-boolean-cast.js +7 -0
  37. package/lib/rules/no-extra-parens.js +48 -10
  38. package/lib/rules/no-implicit-coercion.js +11 -6
  39. package/lib/rules/no-implied-eval.js +7 -28
  40. package/lib/rules/no-import-assign.js +33 -32
  41. package/lib/rules/no-irregular-whitespace.js +22 -12
  42. package/lib/rules/no-magic-numbers.js +4 -8
  43. package/lib/rules/no-obj-calls.js +7 -4
  44. package/lib/rules/no-prototype-builtins.js +13 -3
  45. package/lib/rules/no-self-assign.js +3 -53
  46. package/lib/rules/no-setter-return.js +5 -8
  47. package/lib/rules/no-underscore-dangle.js +66 -21
  48. package/lib/rules/no-unexpected-multiline.js +2 -2
  49. package/lib/rules/no-unneeded-ternary.js +0 -2
  50. package/lib/rules/no-unused-expressions.js +55 -23
  51. package/lib/rules/no-useless-call.js +10 -7
  52. package/lib/rules/no-whitespace-before-property.js +16 -4
  53. package/lib/rules/object-curly-newline.js +4 -4
  54. package/lib/rules/operator-assignment.js +3 -42
  55. package/lib/rules/padding-line-between-statements.js +2 -2
  56. package/lib/rules/prefer-arrow-callback.js +90 -25
  57. package/lib/rules/prefer-exponentiation-operator.js +1 -1
  58. package/lib/rules/prefer-numeric-literals.js +4 -13
  59. package/lib/rules/prefer-promise-reject-errors.js +1 -3
  60. package/lib/rules/prefer-regex-literals.js +68 -13
  61. package/lib/rules/prefer-spread.js +2 -6
  62. package/lib/rules/radix.js +5 -2
  63. package/lib/rules/sort-imports.js +28 -0
  64. package/lib/rules/use-isnan.js +1 -1
  65. package/lib/rules/utils/ast-utils.js +317 -153
  66. package/lib/rules/wrap-iife.js +9 -2
  67. package/lib/rules/yoda.js +2 -55
  68. package/messages/extend-config-missing.txt +1 -1
  69. package/messages/no-config-found.txt +1 -1
  70. package/messages/plugin-conflict.txt +1 -1
  71. package/messages/plugin-missing.txt +1 -1
  72. package/messages/whitespace-found.txt +1 -1
  73. package/package.json +6 -7
@@ -9,16 +9,12 @@
9
9
  // Helpers
10
10
  //------------------------------------------------------------------------------
11
11
 
12
- const { findVariable, getPropertyName } = require("eslint-utils");
13
-
14
- const MutationMethods = {
15
- Object: new Set([
16
- "assign", "defineProperties", "defineProperty", "freeze",
17
- "setPrototypeOf"
18
- ]),
19
- Reflect: new Set([
20
- "defineProperty", "deleteProperty", "set", "setPrototypeOf"
21
- ])
12
+ const { findVariable } = require("eslint-utils");
13
+ const astUtils = require("./utils/ast-utils");
14
+
15
+ const WellKnownMutationFunctions = {
16
+ Object: /^(?:assign|definePropert(?:y|ies)|freeze|setPrototypeOf)$/u,
17
+ Reflect: /^(?:(?:define|delete)Property|set(?:PrototypeOf)?)$/u
22
18
  };
23
19
 
24
20
  /**
@@ -56,17 +52,20 @@ function isAssignmentLeft(node) {
56
52
  * @returns {boolean} `true` if the node is the operand of mutation unary operator.
57
53
  */
58
54
  function isOperandOfMutationUnaryOperator(node) {
59
- const { parent } = node;
55
+ const argumentNode = node.parent.type === "ChainExpression"
56
+ ? node.parent
57
+ : node;
58
+ const { parent } = argumentNode;
60
59
 
61
60
  return (
62
61
  (
63
62
  parent.type === "UpdateExpression" &&
64
- parent.argument === node
63
+ parent.argument === argumentNode
65
64
  ) ||
66
65
  (
67
66
  parent.type === "UnaryExpression" &&
68
67
  parent.operator === "delete" &&
69
- parent.argument === node
68
+ parent.argument === argumentNode
70
69
  )
71
70
  );
72
71
  }
@@ -92,35 +91,37 @@ function isIterationVariable(node) {
92
91
  }
93
92
 
94
93
  /**
95
- * Check if a given node is the iteration variable of `for-in`/`for-of` syntax.
94
+ * Check if a given node is at the first argument of a well-known mutation function.
95
+ * - `Object.assign`
96
+ * - `Object.defineProperty`
97
+ * - `Object.defineProperties`
98
+ * - `Object.freeze`
99
+ * - `Object.setPrototypeOf`
100
+ * - `Refrect.defineProperty`
101
+ * - `Refrect.deleteProperty`
102
+ * - `Refrect.set`
103
+ * - `Refrect.setPrototypeOf`
96
104
  * @param {ASTNode} node The node to check.
97
105
  * @param {Scope} scope A `escope.Scope` object to find variable (whichever).
98
- * @returns {boolean} `true` if the node is the iteration variable.
106
+ * @returns {boolean} `true` if the node is at the first argument of a well-known mutation function.
99
107
  */
100
108
  function isArgumentOfWellKnownMutationFunction(node, scope) {
101
109
  const { parent } = node;
102
110
 
111
+ if (parent.type !== "CallExpression" || parent.arguments[0] !== node) {
112
+ return false;
113
+ }
114
+ const callee = astUtils.skipChainExpression(parent.callee);
115
+
103
116
  if (
104
- parent.type === "CallExpression" &&
105
- parent.arguments[0] === node &&
106
- parent.callee.type === "MemberExpression" &&
107
- parent.callee.object.type === "Identifier"
117
+ !astUtils.isSpecificMemberAccess(callee, "Object", WellKnownMutationFunctions.Object) &&
118
+ !astUtils.isSpecificMemberAccess(callee, "Reflect", WellKnownMutationFunctions.Reflect)
108
119
  ) {
109
- const { callee } = parent;
110
- const { object } = callee;
111
-
112
- if (Object.keys(MutationMethods).includes(object.name)) {
113
- const variable = findVariable(scope, object);
114
-
115
- return (
116
- variable !== null &&
117
- variable.scope.type === "global" &&
118
- MutationMethods[object.name].has(getPropertyName(callee, scope))
119
- );
120
- }
120
+ return false;
121
121
  }
122
+ const variable = findVariable(scope, callee.object);
122
123
 
123
- return false;
124
+ return variable !== null && variable.scope.type === "global";
124
125
  }
125
126
 
126
127
  /**
@@ -91,7 +91,7 @@ module.exports = {
91
91
  const locStart = node.loc.start;
92
92
  const locEnd = node.loc.end;
93
93
 
94
- errors = errors.filter(({ loc: errorLoc }) => {
94
+ errors = errors.filter(({ loc: { start: errorLoc } }) => {
95
95
  if (errorLoc.line >= locStart.line && errorLoc.line <= locEnd.line) {
96
96
  if (errorLoc.column >= locStart.column && (errorLoc.column <= locEnd.column || errorLoc.line < locEnd.line)) {
97
97
  return false;
@@ -160,15 +160,19 @@ module.exports = {
160
160
  let match;
161
161
 
162
162
  while ((match = IRREGULAR_WHITESPACE.exec(sourceLine)) !== null) {
163
- const location = {
164
- line: lineNumber,
165
- column: match.index
166
- };
167
-
168
163
  errors.push({
169
164
  node,
170
165
  messageId: "noIrregularWhitespace",
171
- loc: location
166
+ loc: {
167
+ start: {
168
+ line: lineNumber,
169
+ column: match.index
170
+ },
171
+ end: {
172
+ line: lineNumber,
173
+ column: match.index + match[0].length
174
+ }
175
+ }
172
176
  });
173
177
  }
174
178
  });
@@ -189,16 +193,22 @@ module.exports = {
189
193
 
190
194
  while ((match = IRREGULAR_LINE_TERMINATORS.exec(source)) !== null) {
191
195
  const lineIndex = linebreaks.indexOf(match[0], lastLineIndex + 1) || 0;
192
- const location = {
193
- line: lineIndex + 1,
194
- column: sourceLines[lineIndex].length
195
- };
196
196
 
197
197
  errors.push({
198
198
  node,
199
199
  messageId: "noIrregularWhitespace",
200
- loc: location
200
+ loc: {
201
+ start: {
202
+ line: lineIndex + 1,
203
+ column: sourceLines[lineIndex].length
204
+ },
205
+ end: {
206
+ line: lineIndex + 2,
207
+ column: 0
208
+ }
209
+ }
201
210
  });
211
+
202
212
  lastLineIndex = lineIndex;
203
213
  }
204
214
  }
@@ -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;
@@ -100,12 +100,8 @@ module.exports = {
100
100
 
101
101
  return parent.type === "CallExpression" && fullNumberNode === parent.arguments[1] &&
102
102
  (
103
- parent.callee.name === "parseInt" ||
104
- (
105
- parent.callee.type === "MemberExpression" &&
106
- parent.callee.object.name === "Number" &&
107
- parent.callee.property.name === "parseInt"
108
- )
103
+ astUtils.isSpecificId(parent.callee, "parseInt") ||
104
+ astUtils.isSpecificMemberAccess(parent.callee, "Number", "parseInt")
109
105
  );
110
106
  }
111
107
 
@@ -157,7 +153,7 @@ module.exports = {
157
153
 
158
154
  return {
159
155
  Literal(node) {
160
- if (!isNumericLiteral(node)) {
156
+ if (!astUtils.isNumericLiteral(node)) {
161
157
  return;
162
158
  }
163
159
 
@@ -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
  };