eslint 7.3.0 → 7.6.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 (71) hide show
  1. package/CHANGELOG.md +63 -0
  2. package/README.md +5 -3
  3. package/lib/cli-engine/config-array-factory.js +1 -28
  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/consistent-return.js +1 -12
  16. package/lib/rules/constructor-super.js +1 -0
  17. package/lib/rules/dot-location.js +20 -14
  18. package/lib/rules/dot-notation.js +36 -33
  19. package/lib/rules/func-call-spacing.js +42 -6
  20. package/lib/rules/func-name-matching.js +1 -4
  21. package/lib/rules/global-require.js +2 -1
  22. package/lib/rules/id-blacklist.js +14 -11
  23. package/lib/rules/id-denylist.js +230 -0
  24. package/lib/rules/indent.js +19 -0
  25. package/lib/rules/index.js +1 -0
  26. package/lib/rules/keyword-spacing.js +2 -2
  27. package/lib/rules/max-len.js +13 -2
  28. package/lib/rules/new-cap.js +10 -14
  29. package/lib/rules/newline-per-chained-call.js +15 -5
  30. package/lib/rules/no-alert.js +10 -3
  31. package/lib/rules/no-duplicate-case.js +23 -4
  32. package/lib/rules/no-eval.js +8 -38
  33. package/lib/rules/no-extend-native.js +37 -40
  34. package/lib/rules/no-extra-bind.js +57 -17
  35. package/lib/rules/no-extra-boolean-cast.js +7 -0
  36. package/lib/rules/no-extra-parens.js +48 -10
  37. package/lib/rules/no-implicit-coercion.js +11 -6
  38. package/lib/rules/no-implied-eval.js +7 -28
  39. package/lib/rules/no-import-assign.js +33 -32
  40. package/lib/rules/no-irregular-whitespace.js +22 -12
  41. package/lib/rules/no-magic-numbers.js +4 -8
  42. package/lib/rules/no-obj-calls.js +7 -4
  43. package/lib/rules/no-prototype-builtins.js +13 -3
  44. package/lib/rules/no-self-assign.js +3 -53
  45. package/lib/rules/no-setter-return.js +5 -8
  46. package/lib/rules/no-unexpected-multiline.js +2 -2
  47. package/lib/rules/no-unneeded-ternary.js +0 -2
  48. package/lib/rules/no-unused-expressions.js +55 -23
  49. package/lib/rules/no-useless-call.js +10 -7
  50. package/lib/rules/no-whitespace-before-property.js +16 -4
  51. package/lib/rules/object-curly-newline.js +4 -4
  52. package/lib/rules/operator-assignment.js +3 -42
  53. package/lib/rules/padding-line-between-statements.js +2 -2
  54. package/lib/rules/prefer-arrow-callback.js +90 -25
  55. package/lib/rules/prefer-exponentiation-operator.js +1 -1
  56. package/lib/rules/prefer-numeric-literals.js +4 -13
  57. package/lib/rules/prefer-promise-reject-errors.js +1 -3
  58. package/lib/rules/prefer-regex-literals.js +68 -13
  59. package/lib/rules/prefer-spread.js +2 -6
  60. package/lib/rules/radix.js +5 -2
  61. package/lib/rules/sort-imports.js +28 -0
  62. package/lib/rules/use-isnan.js +1 -1
  63. package/lib/rules/utils/ast-utils.js +317 -153
  64. package/lib/rules/wrap-iife.js +9 -2
  65. package/lib/rules/yoda.js +2 -55
  66. package/messages/extend-config-missing.txt +1 -1
  67. package/messages/no-config-found.txt +1 -1
  68. package/messages/plugin-conflict.txt +1 -1
  69. package/messages/plugin-missing.txt +1 -1
  70. package/messages/whitespace-found.txt +1 -1
  71. package/package.json +6 -6
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * @fileoverview Rule that warns when identifier names that are
3
- * blacklisted in the configuration are used.
3
+ * specified in the configuration are used.
4
4
  * @author Keith Cirkel (http://keithcirkel.co.uk)
5
5
  */
6
6
 
@@ -111,6 +111,9 @@ function isShorthandPropertyDefinition(node) {
111
111
 
112
112
  module.exports = {
113
113
  meta: {
114
+ deprecated: true,
115
+ replacedBy: ["id-denylist"],
116
+
114
117
  type: "suggestion",
115
118
 
116
119
  docs: {
@@ -128,25 +131,25 @@ module.exports = {
128
131
  uniqueItems: true
129
132
  },
130
133
  messages: {
131
- blacklisted: "Identifier '{{name}}' is blacklisted."
134
+ restricted: "Identifier '{{name}}' is restricted."
132
135
  }
133
136
  },
134
137
 
135
138
  create(context) {
136
139
 
137
- const blacklist = new Set(context.options);
140
+ const denyList = new Set(context.options);
138
141
  const reportedNodes = new Set();
139
142
 
140
143
  let globalScope;
141
144
 
142
145
  /**
143
- * Checks whether the given name is blacklisted.
146
+ * Checks whether the given name is restricted.
144
147
  * @param {string} name The name to check.
145
- * @returns {boolean} `true` if the name is blacklisted.
148
+ * @returns {boolean} `true` if the name is restricted.
146
149
  * @private
147
150
  */
148
- function isBlacklisted(name) {
149
- return blacklist.has(name);
151
+ function isRestricted(name) {
152
+ return denyList.has(name);
150
153
  }
151
154
 
152
155
  /**
@@ -172,8 +175,8 @@ module.exports = {
172
175
 
173
176
  /*
174
177
  * Member access has special rules for checking property names.
175
- * Read access to a property with a blacklisted name is allowed, because it can be on an object that user has no control over.
176
- * Write access isn't allowed, because it potentially creates a new property with a blacklisted name.
178
+ * Read access to a property with a restricted name is allowed, because it can be on an object that user has no control over.
179
+ * Write access isn't allowed, because it potentially creates a new property with a restricted name.
177
180
  */
178
181
  if (
179
182
  parent.type === "MemberExpression" &&
@@ -205,7 +208,7 @@ module.exports = {
205
208
  if (!reportedNodes.has(node)) {
206
209
  context.report({
207
210
  node,
208
- messageId: "blacklisted",
211
+ messageId: "restricted",
209
212
  data: {
210
213
  name: node.name
211
214
  }
@@ -221,7 +224,7 @@ module.exports = {
221
224
  },
222
225
 
223
226
  Identifier(node) {
224
- if (isBlacklisted(node.name) && shouldCheck(node)) {
227
+ if (isRestricted(node.name) && shouldCheck(node)) {
225
228
  report(node);
226
229
  }
227
230
  }
@@ -0,0 +1,230 @@
1
+ /**
2
+ * @fileoverview Rule that warns when identifier names that are
3
+ * specified in the configuration are used.
4
+ * @author Keith Cirkel (http://keithcirkel.co.uk)
5
+ */
6
+
7
+ "use strict";
8
+
9
+ //------------------------------------------------------------------------------
10
+ // Helpers
11
+ //------------------------------------------------------------------------------
12
+
13
+ /**
14
+ * Checks whether the given node represents assignment target in a normal assignment or destructuring.
15
+ * @param {ASTNode} node The node to check.
16
+ * @returns {boolean} `true` if the node is assignment target.
17
+ */
18
+ function isAssignmentTarget(node) {
19
+ const parent = node.parent;
20
+
21
+ return (
22
+
23
+ // normal assignment
24
+ (
25
+ parent.type === "AssignmentExpression" &&
26
+ parent.left === node
27
+ ) ||
28
+
29
+ // destructuring
30
+ parent.type === "ArrayPattern" ||
31
+ parent.type === "RestElement" ||
32
+ (
33
+ parent.type === "Property" &&
34
+ parent.value === node &&
35
+ parent.parent.type === "ObjectPattern"
36
+ ) ||
37
+ (
38
+ parent.type === "AssignmentPattern" &&
39
+ parent.left === node
40
+ )
41
+ );
42
+ }
43
+
44
+ /**
45
+ * Checks whether the given node represents an imported name that is renamed in the same import/export specifier.
46
+ *
47
+ * Examples:
48
+ * import { a as b } from 'mod'; // node `a` is renamed import
49
+ * export { a as b } from 'mod'; // node `a` is renamed import
50
+ * @param {ASTNode} node `Identifier` node to check.
51
+ * @returns {boolean} `true` if the node is a renamed import.
52
+ */
53
+ function isRenamedImport(node) {
54
+ const parent = node.parent;
55
+
56
+ return (
57
+ (
58
+ parent.type === "ImportSpecifier" &&
59
+ parent.imported !== parent.local &&
60
+ parent.imported === node
61
+ ) ||
62
+ (
63
+ parent.type === "ExportSpecifier" &&
64
+ parent.parent.source && // re-export
65
+ parent.local !== parent.exported &&
66
+ parent.local === node
67
+ )
68
+ );
69
+ }
70
+
71
+ /**
72
+ * Checks whether the given node is a renamed identifier node in an ObjectPattern destructuring.
73
+ *
74
+ * Examples:
75
+ * const { a : b } = foo; // node `a` is renamed node.
76
+ * @param {ASTNode} node `Identifier` node to check.
77
+ * @returns {boolean} `true` if the node is a renamed node in an ObjectPattern destructuring.
78
+ */
79
+ function isRenamedInDestructuring(node) {
80
+ const parent = node.parent;
81
+
82
+ return (
83
+ (
84
+ !parent.computed &&
85
+ parent.type === "Property" &&
86
+ parent.parent.type === "ObjectPattern" &&
87
+ parent.value !== node &&
88
+ parent.key === node
89
+ )
90
+ );
91
+ }
92
+
93
+ /**
94
+ * Checks whether the given node represents shorthand definition of a property in an object literal.
95
+ * @param {ASTNode} node `Identifier` node to check.
96
+ * @returns {boolean} `true` if the node is a shorthand property definition.
97
+ */
98
+ function isShorthandPropertyDefinition(node) {
99
+ const parent = node.parent;
100
+
101
+ return (
102
+ parent.type === "Property" &&
103
+ parent.parent.type === "ObjectExpression" &&
104
+ parent.shorthand
105
+ );
106
+ }
107
+
108
+ //------------------------------------------------------------------------------
109
+ // Rule Definition
110
+ //------------------------------------------------------------------------------
111
+
112
+ module.exports = {
113
+ meta: {
114
+ type: "suggestion",
115
+
116
+ docs: {
117
+ description: "disallow specified identifiers",
118
+ category: "Stylistic Issues",
119
+ recommended: false,
120
+ url: "https://eslint.org/docs/rules/id-denylist"
121
+ },
122
+
123
+ schema: {
124
+ type: "array",
125
+ items: {
126
+ type: "string"
127
+ },
128
+ uniqueItems: true
129
+ },
130
+ messages: {
131
+ restricted: "Identifier '{{name}}' is restricted."
132
+ }
133
+ },
134
+
135
+ create(context) {
136
+
137
+ const denyList = new Set(context.options);
138
+ const reportedNodes = new Set();
139
+
140
+ let globalScope;
141
+
142
+ /**
143
+ * Checks whether the given name is restricted.
144
+ * @param {string} name The name to check.
145
+ * @returns {boolean} `true` if the name is restricted.
146
+ * @private
147
+ */
148
+ function isRestricted(name) {
149
+ return denyList.has(name);
150
+ }
151
+
152
+ /**
153
+ * Checks whether the given node represents a reference to a global variable that is not declared in the source code.
154
+ * These identifiers will be allowed, as it is assumed that user has no control over the names of external global variables.
155
+ * @param {ASTNode} node `Identifier` node to check.
156
+ * @returns {boolean} `true` if the node is a reference to a global variable.
157
+ */
158
+ function isReferenceToGlobalVariable(node) {
159
+ const variable = globalScope.set.get(node.name);
160
+
161
+ return variable && variable.defs.length === 0 &&
162
+ variable.references.some(ref => ref.identifier === node);
163
+ }
164
+
165
+ /**
166
+ * Determines whether the given node should be checked.
167
+ * @param {ASTNode} node `Identifier` node.
168
+ * @returns {boolean} `true` if the node should be checked.
169
+ */
170
+ function shouldCheck(node) {
171
+ const parent = node.parent;
172
+
173
+ /*
174
+ * Member access has special rules for checking property names.
175
+ * Read access to a property with a restricted name is allowed, because it can be on an object that user has no control over.
176
+ * Write access isn't allowed, because it potentially creates a new property with a restricted name.
177
+ */
178
+ if (
179
+ parent.type === "MemberExpression" &&
180
+ parent.property === node &&
181
+ !parent.computed
182
+ ) {
183
+ return isAssignmentTarget(parent);
184
+ }
185
+
186
+ return (
187
+ parent.type !== "CallExpression" &&
188
+ parent.type !== "NewExpression" &&
189
+ !isRenamedImport(node) &&
190
+ !isRenamedInDestructuring(node) &&
191
+ !(
192
+ isReferenceToGlobalVariable(node) &&
193
+ !isShorthandPropertyDefinition(node)
194
+ )
195
+ );
196
+ }
197
+
198
+ /**
199
+ * Reports an AST node as a rule violation.
200
+ * @param {ASTNode} node The node to report.
201
+ * @returns {void}
202
+ * @private
203
+ */
204
+ function report(node) {
205
+ if (!reportedNodes.has(node)) {
206
+ context.report({
207
+ node,
208
+ messageId: "restricted",
209
+ data: {
210
+ name: node.name
211
+ }
212
+ });
213
+ reportedNodes.add(node);
214
+ }
215
+ }
216
+
217
+ return {
218
+
219
+ Program() {
220
+ globalScope = context.getScope();
221
+ },
222
+
223
+ Identifier(node) {
224
+ if (isRestricted(node.name) && shouldCheck(node)) {
225
+ report(node);
226
+ }
227
+ }
228
+ };
229
+ }
230
+ };
@@ -32,6 +32,7 @@ const KNOWN_NODES = new Set([
32
32
  "BreakStatement",
33
33
  "CallExpression",
34
34
  "CatchClause",
35
+ "ChainExpression",
35
36
  "ClassBody",
36
37
  "ClassDeclaration",
37
38
  "ClassExpression",
@@ -934,6 +935,24 @@ module.exports = {
934
935
  parameterParens.add(openingParen);
935
936
  parameterParens.add(closingParen);
936
937
 
938
+ /*
939
+ * If `?.` token exists, set desired offset for that.
940
+ * This logic is copied from `MemberExpression`'s.
941
+ */
942
+ if (node.optional) {
943
+ const dotToken = sourceCode.getTokenAfter(node.callee, astUtils.isQuestionDotToken);
944
+ const calleeParenCount = sourceCode.getTokensBetween(node.callee, dotToken, { filter: astUtils.isClosingParenToken }).length;
945
+ const firstTokenOfCallee = calleeParenCount
946
+ ? sourceCode.getTokenBefore(node.callee, { skip: calleeParenCount - 1 })
947
+ : sourceCode.getFirstToken(node.callee);
948
+ const lastTokenOfCallee = sourceCode.getTokenBefore(dotToken);
949
+ const offsetBase = lastTokenOfCallee.loc.end.line === openingParen.loc.start.line
950
+ ? lastTokenOfCallee
951
+ : firstTokenOfCallee;
952
+
953
+ offsets.setDesiredOffset(dotToken, offsetBase, 1);
954
+ }
955
+
937
956
  const offsetAfterToken = node.callee.type === "TaggedTemplateExpression" ? sourceCode.getFirstToken(node.callee.quasi) : openingParen;
938
957
  const offsetToken = sourceCode.getTokenBefore(offsetAfterToken);
939
958
 
@@ -57,6 +57,7 @@ module.exports = new LazyLoadingRuleMap(Object.entries({
57
57
  "guard-for-in": () => require("./guard-for-in"),
58
58
  "handle-callback-err": () => require("./handle-callback-err"),
59
59
  "id-blacklist": () => require("./id-blacklist"),
60
+ "id-denylist": () => require("./id-denylist"),
60
61
  "id-length": () => require("./id-length"),
61
62
  "id-match": () => require("./id-match"),
62
63
  "implicit-arrow-linebreak": () => require("./implicit-arrow-linebreak"),
@@ -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,
@@ -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.
@@ -6,6 +6,12 @@
6
6
 
7
7
  "use strict";
8
8
 
9
+ //------------------------------------------------------------------------------
10
+ // Requirements
11
+ //------------------------------------------------------------------------------
12
+
13
+ const astUtils = require("./utils/ast-utils");
14
+
9
15
  //------------------------------------------------------------------------------
10
16
  // Rule Definition
11
17
  //------------------------------------------------------------------------------
@@ -31,18 +37,31 @@ module.exports = {
31
37
  create(context) {
32
38
  const sourceCode = context.getSourceCode();
33
39
 
40
+ /**
41
+ * Determines whether the two given nodes are considered to be equal.
42
+ * @param {ASTNode} a First node.
43
+ * @param {ASTNode} b Second node.
44
+ * @returns {boolean} `true` if the nodes are considered to be equal.
45
+ */
46
+ function equal(a, b) {
47
+ if (a.type !== b.type) {
48
+ return false;
49
+ }
50
+
51
+ return astUtils.equalTokens(a, b, sourceCode);
52
+ }
34
53
  return {
35
54
  SwitchStatement(node) {
36
- const previousKeys = new Set();
55
+ const previousTests = [];
37
56
 
38
57
  for (const switchCase of node.cases) {
39
58
  if (switchCase.test) {
40
- const key = sourceCode.getText(switchCase.test);
59
+ const test = switchCase.test;
41
60
 
42
- if (previousKeys.has(key)) {
61
+ if (previousTests.some(previousTest => equal(previousTest, test))) {
43
62
  context.report({ node: switchCase, messageId: "unexpected" });
44
63
  } else {
45
- previousKeys.add(key);
64
+ previousTests.push(test);
46
65
  }
47
66
  }
48
67
  }
@@ -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
  },