eslint 8.13.0 → 8.17.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 (63) hide show
  1. package/README.md +17 -12
  2. package/bin/eslint.js +1 -1
  3. package/lib/cli-engine/cli-engine.js +2 -4
  4. package/lib/cli-engine/lint-result-cache.js +1 -1
  5. package/lib/eslint/eslint.js +3 -3
  6. package/lib/linter/code-path-analysis/code-path-segment.js +1 -1
  7. package/lib/linter/code-path-analysis/code-path-state.js +1 -1
  8. package/lib/linter/code-path-analysis/code-path.js +1 -1
  9. package/lib/rules/accessor-pairs.js +4 -4
  10. package/lib/rules/callback-return.js +2 -2
  11. package/lib/rules/capitalized-comments.js +1 -1
  12. package/lib/rules/consistent-this.js +1 -1
  13. package/lib/rules/dot-notation.js +2 -2
  14. package/lib/rules/function-paren-newline.js +8 -5
  15. package/lib/rules/global-require.js +3 -3
  16. package/lib/rules/indent-legacy.js +2 -2
  17. package/lib/rules/indent.js +45 -13
  18. package/lib/rules/index.js +1 -0
  19. package/lib/rules/jsx-quotes.js +1 -1
  20. package/lib/rules/lines-around-comment.js +3 -3
  21. package/lib/rules/max-lines.js +2 -2
  22. package/lib/rules/max-statements.js +1 -1
  23. package/lib/rules/newline-before-return.js +1 -1
  24. package/lib/rules/no-bitwise.js +2 -2
  25. package/lib/rules/no-console.js +1 -1
  26. package/lib/rules/no-constant-binary-expression.js +500 -0
  27. package/lib/rules/no-constant-condition.js +4 -197
  28. package/lib/rules/no-control-regex.js +23 -10
  29. package/lib/rules/no-empty-function.js +1 -1
  30. package/lib/rules/no-extra-boolean-cast.js +3 -3
  31. package/lib/rules/no-extra-semi.js +1 -1
  32. package/lib/rules/no-global-assign.js +1 -1
  33. package/lib/rules/no-implicit-coercion.js +8 -8
  34. package/lib/rules/no-loop-func.js +1 -1
  35. package/lib/rules/no-magic-numbers.js +3 -3
  36. package/lib/rules/no-misleading-character-class.js +90 -17
  37. package/lib/rules/no-mixed-operators.js +1 -1
  38. package/lib/rules/no-mixed-requires.js +1 -1
  39. package/lib/rules/no-multi-spaces.js +1 -1
  40. package/lib/rules/no-native-reassign.js +1 -1
  41. package/lib/rules/no-new-object.js +1 -1
  42. package/lib/rules/no-new-wrappers.js +1 -1
  43. package/lib/rules/no-octal.js +2 -2
  44. package/lib/rules/no-prototype-builtins.js +3 -3
  45. package/lib/rules/no-shadow.js +5 -5
  46. package/lib/rules/no-sparse-arrays.js +1 -1
  47. package/lib/rules/no-underscore-dangle.js +31 -2
  48. package/lib/rules/no-unused-expressions.js +1 -1
  49. package/lib/rules/no-unused-vars.js +1 -1
  50. package/lib/rules/no-use-before-define.js +15 -2
  51. package/lib/rules/operator-assignment.js +2 -2
  52. package/lib/rules/prefer-const.js +1 -1
  53. package/lib/rules/prefer-reflect.js +2 -2
  54. package/lib/rules/prefer-regex-literals.js +3 -3
  55. package/lib/rules/quote-props.js +2 -2
  56. package/lib/rules/quotes.js +1 -1
  57. package/lib/rules/spaced-comment.js +1 -1
  58. package/lib/rules/utils/ast-utils.js +203 -7
  59. package/lib/rules/valid-jsdoc.js +1 -1
  60. package/lib/rules/valid-typeof.js +4 -4
  61. package/lib/rules/yoda.js +1 -1
  62. package/lib/shared/types.js +1 -1
  63. package/package.json +25 -8
@@ -5,6 +5,8 @@
5
5
 
6
6
  "use strict";
7
7
 
8
+ const { isConstant } = require("./utils/ast-utils");
9
+
8
10
  //------------------------------------------------------------------------------
9
11
  // Helpers
10
12
  //------------------------------------------------------------------------------
@@ -53,201 +55,6 @@ module.exports = {
53
55
  // Helpers
54
56
  //--------------------------------------------------------------------------
55
57
 
56
- /**
57
- * Returns literal's value converted to the Boolean type
58
- * @param {ASTNode} node any `Literal` node
59
- * @returns {boolean | null} `true` when node is truthy, `false` when node is falsy,
60
- * `null` when it cannot be determined.
61
- */
62
- function getBooleanValue(node) {
63
- if (node.value === null) {
64
-
65
- /*
66
- * it might be a null literal or bigint/regex literal in unsupported environments .
67
- * https://github.com/estree/estree/blob/14df8a024956ea289bd55b9c2226a1d5b8a473ee/es5.md#regexpliteral
68
- * https://github.com/estree/estree/blob/14df8a024956ea289bd55b9c2226a1d5b8a473ee/es2020.md#bigintliteral
69
- */
70
-
71
- if (node.raw === "null") {
72
- return false;
73
- }
74
-
75
- // regex is always truthy
76
- if (typeof node.regex === "object") {
77
- return true;
78
- }
79
-
80
- return null;
81
- }
82
-
83
- return !!node.value;
84
- }
85
-
86
- /**
87
- * Checks if a branch node of LogicalExpression short circuits the whole condition
88
- * @param {ASTNode} node The branch of main condition which needs to be checked
89
- * @param {string} operator The operator of the main LogicalExpression.
90
- * @returns {boolean} true when condition short circuits whole condition
91
- */
92
- function isLogicalIdentity(node, operator) {
93
- switch (node.type) {
94
- case "Literal":
95
- return (operator === "||" && getBooleanValue(node) === true) ||
96
- (operator === "&&" && getBooleanValue(node) === false);
97
-
98
- case "UnaryExpression":
99
- return (operator === "&&" && node.operator === "void");
100
-
101
- case "LogicalExpression":
102
-
103
- /*
104
- * handles `a && false || b`
105
- * `false` is an identity element of `&&` but not `||`
106
- */
107
- return operator === node.operator &&
108
- (
109
- isLogicalIdentity(node.left, operator) ||
110
- isLogicalIdentity(node.right, operator)
111
- );
112
-
113
- case "AssignmentExpression":
114
- return ["||=", "&&="].includes(node.operator) &&
115
- operator === node.operator.slice(0, -1) &&
116
- isLogicalIdentity(node.right, operator);
117
-
118
- // no default
119
- }
120
- return false;
121
- }
122
-
123
- /**
124
- * Checks if an identifier is a reference to a global variable.
125
- * @param {ASTNode} node An identifier node to check.
126
- * @returns {boolean} `true` if the identifier is a reference to a global variable.
127
- */
128
- function isReferenceToGlobalVariable(node) {
129
- const scope = context.getScope();
130
- const reference = scope.references.find(ref => ref.identifier === node);
131
-
132
- return Boolean(
133
- reference &&
134
- reference.resolved &&
135
- reference.resolved.scope.type === "global" &&
136
- reference.resolved.defs.length === 0
137
- );
138
- }
139
-
140
- /**
141
- * Checks if a node has a constant truthiness value.
142
- * @param {ASTNode} node The AST node to check.
143
- * @param {boolean} inBooleanPosition `true` if checking the test of a
144
- * condition. `false` in all other cases. When `false`, checks if -- for
145
- * both string and number -- if coerced to that type, the value will
146
- * be constant.
147
- * @returns {Bool} true when node's truthiness is constant
148
- * @private
149
- */
150
- function isConstant(node, inBooleanPosition) {
151
-
152
- // node.elements can return null values in the case of sparse arrays ex. [,]
153
- if (!node) {
154
- return true;
155
- }
156
- switch (node.type) {
157
- case "Literal":
158
- case "ArrowFunctionExpression":
159
- case "FunctionExpression":
160
- return true;
161
- case "ClassExpression":
162
- case "ObjectExpression":
163
-
164
- /**
165
- * In theory objects like:
166
- *
167
- * `{toString: () => a}`
168
- * `{valueOf: () => a}`
169
- *
170
- * Or a classes like:
171
- *
172
- * `class { static toString() { return a } }`
173
- * `class { static valueOf() { return a } }`
174
- *
175
- * Are not constant verifiably when `inBooleanPosition` is
176
- * false, but it's an edge case we've opted not to handle.
177
- */
178
- return true;
179
- case "TemplateLiteral":
180
- return (inBooleanPosition && node.quasis.some(quasi => quasi.value.cooked.length)) ||
181
- node.expressions.every(exp => isConstant(exp, false));
182
-
183
- case "ArrayExpression": {
184
- if (!inBooleanPosition) {
185
- return node.elements.every(element => isConstant(element, false));
186
- }
187
- return true;
188
- }
189
-
190
- case "UnaryExpression":
191
- if (
192
- node.operator === "void" ||
193
- node.operator === "typeof" && inBooleanPosition
194
- ) {
195
- return true;
196
- }
197
-
198
- if (node.operator === "!") {
199
- return isConstant(node.argument, true);
200
- }
201
-
202
- return isConstant(node.argument, false);
203
-
204
- case "BinaryExpression":
205
- return isConstant(node.left, false) &&
206
- isConstant(node.right, false) &&
207
- node.operator !== "in";
208
-
209
- case "LogicalExpression": {
210
- const isLeftConstant = isConstant(node.left, inBooleanPosition);
211
- const isRightConstant = isConstant(node.right, inBooleanPosition);
212
- const isLeftShortCircuit = (isLeftConstant && isLogicalIdentity(node.left, node.operator));
213
- const isRightShortCircuit = (inBooleanPosition && isRightConstant && isLogicalIdentity(node.right, node.operator));
214
-
215
- return (isLeftConstant && isRightConstant) ||
216
- isLeftShortCircuit ||
217
- isRightShortCircuit;
218
- }
219
- case "NewExpression":
220
- return inBooleanPosition;
221
- case "AssignmentExpression":
222
- if (node.operator === "=") {
223
- return isConstant(node.right, inBooleanPosition);
224
- }
225
-
226
- if (["||=", "&&="].includes(node.operator) && inBooleanPosition) {
227
- return isLogicalIdentity(node.right, node.operator.slice(0, -1));
228
- }
229
-
230
- return false;
231
-
232
- case "SequenceExpression":
233
- return isConstant(node.expressions[node.expressions.length - 1], inBooleanPosition);
234
- case "SpreadElement":
235
- return isConstant(node.argument, inBooleanPosition);
236
- case "CallExpression":
237
- if (node.callee.type === "Identifier" && node.callee.name === "Boolean") {
238
- if (node.arguments.length === 0 || isConstant(node.arguments[0], true)) {
239
- return isReferenceToGlobalVariable(node.callee);
240
- }
241
- }
242
- return false;
243
- case "Identifier":
244
- return node.name === "undefined" && isReferenceToGlobalVariable(node);
245
-
246
- // no default
247
- }
248
- return false;
249
- }
250
-
251
58
  /**
252
59
  * Tracks when the given node contains a constant condition.
253
60
  * @param {ASTNode} node The AST node to check.
@@ -255,7 +62,7 @@ module.exports = {
255
62
  * @private
256
63
  */
257
64
  function trackConstantConditionLoop(node) {
258
- if (node.test && isConstant(node.test, true)) {
65
+ if (node.test && isConstant(context.getScope(), node.test, true)) {
259
66
  loopsInCurrentScope.add(node);
260
67
  }
261
68
  }
@@ -280,7 +87,7 @@ module.exports = {
280
87
  * @private
281
88
  */
282
89
  function reportIfConstant(node) {
283
- if (node.test && isConstant(node.test, true)) {
90
+ if (node.test && isConstant(context.getScope(), node.test, true)) {
284
91
  context.report({ node: node.test, messageId: "unexpected" });
285
92
  }
286
93
  }
@@ -30,10 +30,12 @@ const collector = new (class {
30
30
  }
31
31
  }
32
32
 
33
- collectControlChars(regexpStr) {
33
+ collectControlChars(regexpStr, flags) {
34
+ const uFlag = typeof flags === "string" && flags.includes("u");
35
+
34
36
  try {
35
37
  this._source = regexpStr;
36
- this._validator.validatePattern(regexpStr); // Call onCharacter hook
38
+ this._validator.validatePattern(regexpStr, void 0, void 0, uFlag); // Call onCharacter hook
37
39
  } catch {
38
40
 
39
41
  // Ignore syntax errors in RegExp.
@@ -68,13 +70,15 @@ module.exports = {
68
70
 
69
71
  /**
70
72
  * Get the regex expression
71
- * @param {ASTNode} node node to evaluate
72
- * @returns {RegExp|null} Regex if found else null
73
+ * @param {ASTNode} node `Literal` node to evaluate
74
+ * @returns {{ pattern: string, flags: string | null } | null} Regex if found (the given node is either a regex literal
75
+ * or a string literal that is the pattern argument of a RegExp constructor call). Otherwise `null`. If flags cannot be determined,
76
+ * the `flags` property will be `null`.
73
77
  * @private
74
78
  */
75
- function getRegExpPattern(node) {
79
+ function getRegExp(node) {
76
80
  if (node.regex) {
77
- return node.regex.pattern;
81
+ return node.regex;
78
82
  }
79
83
  if (typeof node.value === "string" &&
80
84
  (node.parent.type === "NewExpression" || node.parent.type === "CallExpression") &&
@@ -82,7 +86,15 @@ module.exports = {
82
86
  node.parent.callee.name === "RegExp" &&
83
87
  node.parent.arguments[0] === node
84
88
  ) {
85
- return node.value;
89
+ const pattern = node.value;
90
+ const flags =
91
+ node.parent.arguments.length > 1 &&
92
+ node.parent.arguments[1].type === "Literal" &&
93
+ typeof node.parent.arguments[1].value === "string"
94
+ ? node.parent.arguments[1].value
95
+ : null;
96
+
97
+ return { pattern, flags };
86
98
  }
87
99
 
88
100
  return null;
@@ -90,10 +102,11 @@ module.exports = {
90
102
 
91
103
  return {
92
104
  Literal(node) {
93
- const pattern = getRegExpPattern(node);
105
+ const regExp = getRegExp(node);
94
106
 
95
- if (pattern) {
96
- const controlCharacters = collector.collectControlChars(pattern);
107
+ if (regExp) {
108
+ const { pattern, flags } = regExp;
109
+ const controlCharacters = collector.collectControlChars(pattern, flags);
97
110
 
98
111
  if (controlCharacters.length > 0) {
99
112
  context.report({
@@ -144,7 +144,7 @@ module.exports = {
144
144
  filter: astUtils.isCommentToken
145
145
  });
146
146
 
147
- if (allowed.indexOf(kind) === -1 &&
147
+ if (!allowed.includes(kind) &&
148
148
  node.body.type === "BlockStatement" &&
149
149
  node.body.body.length === 0 &&
150
150
  innerComments.length === 0
@@ -51,13 +51,13 @@ module.exports = {
51
51
  const sourceCode = context.getSourceCode();
52
52
 
53
53
  // Node types which have a test which will coerce values to booleans.
54
- const BOOLEAN_NODE_TYPES = [
54
+ const BOOLEAN_NODE_TYPES = new Set([
55
55
  "IfStatement",
56
56
  "DoWhileStatement",
57
57
  "WhileStatement",
58
58
  "ConditionalExpression",
59
59
  "ForStatement"
60
- ];
60
+ ]);
61
61
 
62
62
  /**
63
63
  * Check if a node is a Boolean function or constructor.
@@ -95,7 +95,7 @@ module.exports = {
95
95
  (isBooleanFunctionOrConstructorCall(node.parent) &&
96
96
  node === node.parent.arguments[0]) ||
97
97
 
98
- (BOOLEAN_NODE_TYPES.indexOf(node.parent.type) !== -1 &&
98
+ (BOOLEAN_NODE_TYPES.has(node.parent.type) &&
99
99
  node === node.parent.test) ||
100
100
 
101
101
  // !<bool>
@@ -98,7 +98,7 @@ module.exports = {
98
98
  "WithStatement"
99
99
  ];
100
100
 
101
- if (allowedParentTypes.indexOf(parent.type) === -1) {
101
+ if (!allowedParentTypes.includes(parent.type)) {
102
102
  report(node);
103
103
  }
104
104
  },
@@ -78,7 +78,7 @@ module.exports = {
78
78
  * @returns {void}
79
79
  */
80
80
  function checkVariable(variable) {
81
- if (variable.writeable === false && exceptions.indexOf(variable.name) === -1) {
81
+ if (variable.writeable === false && !exceptions.includes(variable.name)) {
82
82
  variable.references.forEach(checkReference);
83
83
  }
84
84
  }
@@ -30,9 +30,9 @@ function parseOptions(options) {
30
30
  }
31
31
 
32
32
  /**
33
- * Checks whether or not a node is a double logical nigating.
33
+ * Checks whether or not a node is a double logical negating.
34
34
  * @param {ASTNode} node An UnaryExpression node to check.
35
- * @returns {boolean} Whether or not the node is a double logical nigating.
35
+ * @returns {boolean} Whether or not the node is a double logical negating.
36
36
  */
37
37
  function isDoubleLogicalNegating(node) {
38
38
  return (
@@ -257,7 +257,7 @@ module.exports = {
257
257
  let operatorAllowed;
258
258
 
259
259
  // !!foo
260
- operatorAllowed = options.allow.indexOf("!!") >= 0;
260
+ operatorAllowed = options.allow.includes("!!");
261
261
  if (!operatorAllowed && options.boolean && isDoubleLogicalNegating(node)) {
262
262
  const recommendation = `Boolean(${sourceCode.getText(node.argument.argument)})`;
263
263
 
@@ -265,7 +265,7 @@ module.exports = {
265
265
  }
266
266
 
267
267
  // ~foo.indexOf(bar)
268
- operatorAllowed = options.allow.indexOf("~") >= 0;
268
+ operatorAllowed = options.allow.includes("~");
269
269
  if (!operatorAllowed && options.boolean && isBinaryNegatingOfIndexOf(node)) {
270
270
 
271
271
  // `foo?.indexOf(bar) !== -1` will be true (== found) if the `foo` is nullish. So use `>= 0` in that case.
@@ -276,7 +276,7 @@ module.exports = {
276
276
  }
277
277
 
278
278
  // +foo
279
- operatorAllowed = options.allow.indexOf("+") >= 0;
279
+ operatorAllowed = options.allow.includes("+");
280
280
  if (!operatorAllowed && options.number && node.operator === "+" && !isNumeric(node.argument)) {
281
281
  const recommendation = `Number(${sourceCode.getText(node.argument)})`;
282
282
 
@@ -289,7 +289,7 @@ module.exports = {
289
289
  let operatorAllowed;
290
290
 
291
291
  // 1 * foo
292
- operatorAllowed = options.allow.indexOf("*") >= 0;
292
+ operatorAllowed = options.allow.includes("*");
293
293
  const nonNumericOperand = !operatorAllowed && options.number && isMultiplyByOne(node) && getNonNumericOperand(node);
294
294
 
295
295
  if (nonNumericOperand) {
@@ -299,7 +299,7 @@ module.exports = {
299
299
  }
300
300
 
301
301
  // "" + foo
302
- operatorAllowed = options.allow.indexOf("+") >= 0;
302
+ operatorAllowed = options.allow.includes("+");
303
303
  if (!operatorAllowed && options.string && isConcatWithEmptyString(node)) {
304
304
  const recommendation = `String(${sourceCode.getText(getNonEmptyOperand(node))})`;
305
305
 
@@ -310,7 +310,7 @@ module.exports = {
310
310
  AssignmentExpression(node) {
311
311
 
312
312
  // foo += ""
313
- const operatorAllowed = options.allow.indexOf("+") >= 0;
313
+ const operatorAllowed = options.allow.includes("+");
314
314
 
315
315
  if (!operatorAllowed && options.string && isAppendEmptyString(node)) {
316
316
  const code = sourceCode.getText(getNonEmptyOperand(node));
@@ -125,7 +125,7 @@ function isSafe(loopNode, reference) {
125
125
  * The reference is every reference of the upper scope's variable we are
126
126
  * looking now.
127
127
  *
128
- * It's safeafe if the reference matches one of the following condition.
128
+ * It's safe if the reference matches one of the following condition.
129
129
  * - is readonly.
130
130
  * - doesn't exist inside a local function and after the border.
131
131
  * @param {eslint-scope.Reference} upperRef A reference to check.
@@ -80,7 +80,7 @@ module.exports = {
80
80
  const config = context.options[0] || {},
81
81
  detectObjects = !!config.detectObjects,
82
82
  enforceConst = !!config.enforceConst,
83
- ignore = (config.ignore || []).map(normalizeIgnoreValue),
83
+ ignore = new Set((config.ignore || []).map(normalizeIgnoreValue)),
84
84
  ignoreArrayIndexes = !!config.ignoreArrayIndexes,
85
85
  ignoreDefaultValues = !!config.ignoreDefaultValues;
86
86
 
@@ -92,7 +92,7 @@ module.exports = {
92
92
  * @returns {boolean} true if the value is ignored
93
93
  */
94
94
  function isIgnoredValue(value) {
95
- return ignore.indexOf(value) !== -1;
95
+ return ignore.has(value);
96
96
  }
97
97
 
98
98
  /**
@@ -209,7 +209,7 @@ module.exports = {
209
209
  });
210
210
  }
211
211
  } else if (
212
- okTypes.indexOf(parent.type) === -1 ||
212
+ !okTypes.includes(parent.type) ||
213
213
  (parent.type === "AssignmentExpression" && parent.left.type === "Identifier")
214
214
  ) {
215
215
  context.report({
@@ -4,13 +4,16 @@
4
4
  "use strict";
5
5
 
6
6
  const { CALL, CONSTRUCT, ReferenceTracker, getStringIfConstant } = require("eslint-utils");
7
- const { RegExpParser, visitRegExpAST } = require("regexpp");
7
+ const { RegExpValidator, RegExpParser, visitRegExpAST } = require("regexpp");
8
8
  const { isCombiningCharacter, isEmojiModifier, isRegionalIndicatorSymbol, isSurrogatePair } = require("./utils/unicode");
9
+ const astUtils = require("./utils/ast-utils.js");
9
10
 
10
11
  //------------------------------------------------------------------------------
11
12
  // Helpers
12
13
  //------------------------------------------------------------------------------
13
14
 
15
+ const REGEXPP_LATEST_ECMA_VERSION = 2022;
16
+
14
17
  /**
15
18
  * Iterate character sequences of a given nodes.
16
19
  *
@@ -109,6 +112,8 @@ module.exports = {
109
112
  url: "https://eslint.org/docs/rules/no-misleading-character-class"
110
113
  },
111
114
 
115
+ hasSuggestions: true,
116
+
112
117
  schema: [],
113
118
 
114
119
  messages: {
@@ -116,10 +121,12 @@ module.exports = {
116
121
  combiningClass: "Unexpected combined character in character class.",
117
122
  emojiModifier: "Unexpected modified Emoji in character class.",
118
123
  regionalIndicatorSymbol: "Unexpected national flag in character class.",
119
- zwj: "Unexpected joined character sequence in character class."
124
+ zwj: "Unexpected joined character sequence in character class.",
125
+ suggestUnicodeFlag: "Add unicode 'u' flag to regex."
120
126
  }
121
127
  },
122
128
  create(context) {
129
+ const sourceCode = context.getSourceCode();
123
130
  const parser = new RegExpParser();
124
131
 
125
132
  /**
@@ -127,17 +134,10 @@ module.exports = {
127
134
  * @param {Node} node The node to report.
128
135
  * @param {string} pattern The regular expression pattern to verify.
129
136
  * @param {string} flags The flags of the regular expression.
137
+ * @param {Function} unicodeFixer Fixer for missing "u" flag.
130
138
  * @returns {void}
131
139
  */
132
- function verify(node, pattern, flags) {
133
- const has = {
134
- surrogatePairWithoutUFlag: false,
135
- combiningClass: false,
136
- variationSelector: false,
137
- emojiModifier: false,
138
- regionalIndicatorSymbol: false,
139
- zwj: false
140
- };
140
+ function verify(node, pattern, flags, unicodeFixer) {
141
141
  let patternNode;
142
142
 
143
143
  try {
@@ -153,26 +153,75 @@ module.exports = {
153
153
  return;
154
154
  }
155
155
 
156
+ const foundKinds = new Set();
157
+
156
158
  visitRegExpAST(patternNode, {
157
159
  onCharacterClassEnter(ccNode) {
158
160
  for (const chars of iterateCharacterSequence(ccNode.elements)) {
159
161
  for (const kind of kinds) {
160
- has[kind] = has[kind] || hasCharacterSequence[kind](chars);
162
+ if (hasCharacterSequence[kind](chars)) {
163
+ foundKinds.add(kind);
164
+ }
161
165
  }
162
166
  }
163
167
  }
164
168
  });
165
169
 
166
- for (const kind of kinds) {
167
- if (has[kind]) {
168
- context.report({ node, messageId: kind });
170
+ for (const kind of foundKinds) {
171
+ let suggest;
172
+
173
+ if (kind === "surrogatePairWithoutUFlag") {
174
+ suggest = [{
175
+ messageId: "suggestUnicodeFlag",
176
+ fix: unicodeFixer
177
+ }];
169
178
  }
179
+
180
+ context.report({
181
+ node,
182
+ messageId: kind,
183
+ suggest
184
+ });
170
185
  }
171
186
  }
172
187
 
188
+ /**
189
+ * Checks if the given regular expression pattern would be valid with the `u` flag.
190
+ * @param {string} pattern The regular expression pattern to verify.
191
+ * @returns {boolean} `true` if the pattern would be valid with the `u` flag.
192
+ * `false` if the pattern would be invalid with the `u` flag or the configured
193
+ * ecmaVersion doesn't support the `u` flag.
194
+ */
195
+ function isValidWithUnicodeFlag(pattern) {
196
+ const { ecmaVersion } = context.parserOptions;
197
+
198
+ // ecmaVersion is unknown or it doesn't support the 'u' flag
199
+ if (typeof ecmaVersion !== "number" || ecmaVersion <= 5) {
200
+ return false;
201
+ }
202
+
203
+ const validator = new RegExpValidator({
204
+ ecmaVersion: Math.min(ecmaVersion + 2009, REGEXPP_LATEST_ECMA_VERSION)
205
+ });
206
+
207
+ try {
208
+ validator.validatePattern(pattern, void 0, void 0, /* uFlag = */ true);
209
+ } catch {
210
+ return false;
211
+ }
212
+
213
+ return true;
214
+ }
215
+
173
216
  return {
174
217
  "Literal[regex]"(node) {
175
- verify(node, node.regex.pattern, node.regex.flags);
218
+ verify(node, node.regex.pattern, node.regex.flags, fixer => {
219
+ if (!isValidWithUnicodeFlag(node.regex.pattern)) {
220
+ return null;
221
+ }
222
+
223
+ return fixer.insertTextAfter(node, "u");
224
+ });
176
225
  },
177
226
  "Program"() {
178
227
  const scope = context.getScope();
@@ -191,7 +240,31 @@ module.exports = {
191
240
  const flags = getStringIfConstant(flagsNode, scope);
192
241
 
193
242
  if (typeof pattern === "string") {
194
- verify(node, pattern, flags || "");
243
+ verify(node, pattern, flags || "", fixer => {
244
+
245
+ if (!isValidWithUnicodeFlag(pattern)) {
246
+ return null;
247
+ }
248
+
249
+ if (node.arguments.length === 1) {
250
+ const penultimateToken = sourceCode.getLastToken(node, { skip: 1 }); // skip closing parenthesis
251
+
252
+ return fixer.insertTextAfter(
253
+ penultimateToken,
254
+ astUtils.isCommaToken(penultimateToken)
255
+ ? ' "u",'
256
+ : ', "u"'
257
+ );
258
+ }
259
+
260
+ if ((flagsNode.type === "Literal" && typeof flagsNode.value === "string") || flagsNode.type === "TemplateLiteral") {
261
+ const range = [flagsNode.range[0], flagsNode.range[1] - 1];
262
+
263
+ return fixer.insertTextAfterRange(range, "u");
264
+ }
265
+
266
+ return null;
267
+ });
195
268
  }
196
269
  }
197
270
  }
@@ -64,7 +64,7 @@ function normalizeOptions(options = {}) {
64
64
  * @returns {boolean} `true` if such group existed.
65
65
  */
66
66
  function includesBothInAGroup(groups, left, right) {
67
- return groups.some(group => group.indexOf(left) !== -1 && group.indexOf(right) !== -1);
67
+ return groups.some(group => group.includes(left) && group.includes(right));
68
68
  }
69
69
 
70
70
  /**
@@ -160,7 +160,7 @@ module.exports = {
160
160
  return REQ_COMPUTED;
161
161
  }
162
162
 
163
- if (BUILTIN_MODULES.indexOf(arg.value) !== -1) {
163
+ if (BUILTIN_MODULES.includes(arg.value)) {
164
164
 
165
165
  // "var fs = require('fs');"
166
166
  return REQ_CORE;
@@ -56,7 +56,7 @@ module.exports = {
56
56
  const options = context.options[0] || {};
57
57
  const ignoreEOLComments = options.ignoreEOLComments;
58
58
  const exceptions = Object.assign({ Property: true }, options.exceptions);
59
- const hasExceptions = Object.keys(exceptions).filter(key => exceptions[key]).length > 0;
59
+ const hasExceptions = Object.keys(exceptions).some(key => exceptions[key]);
60
60
 
61
61
  /**
62
62
  * Formats value of given comment token for error message by truncating its length.
@@ -81,7 +81,7 @@ module.exports = {
81
81
  * @returns {void}
82
82
  */
83
83
  function checkVariable(variable) {
84
- if (variable.writeable === false && exceptions.indexOf(variable.name) === -1) {
84
+ if (variable.writeable === false && !exceptions.includes(variable.name)) {
85
85
  variable.references.forEach(checkReference);
86
86
  }
87
87
  }