eslint 8.49.0 → 8.50.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.
@@ -136,6 +136,76 @@ function getArrayMethodName(node) {
136
136
  return null;
137
137
  }
138
138
 
139
+ /**
140
+ * Checks if the given node is a void expression.
141
+ * @param {ASTNode} node The node to check.
142
+ * @returns {boolean} - `true` if the node is a void expression
143
+ */
144
+ function isExpressionVoid(node) {
145
+ return node.type === "UnaryExpression" && node.operator === "void";
146
+ }
147
+
148
+ /**
149
+ * Fixes the linting error by prepending "void " to the given node
150
+ * @param {Object} sourceCode context given by context.sourceCode
151
+ * @param {ASTNode} node The node to fix.
152
+ * @param {Object} fixer The fixer object provided by ESLint.
153
+ * @returns {Array<Object>} - An array of fix objects to apply to the node.
154
+ */
155
+ function voidPrependFixer(sourceCode, node, fixer) {
156
+
157
+ const requiresParens =
158
+
159
+ // prepending `void ` will fail if the node has a lower precedence than void
160
+ astUtils.getPrecedence(node) < astUtils.getPrecedence({ type: "UnaryExpression", operator: "void" }) &&
161
+
162
+ // check if there are parentheses around the node to avoid redundant parentheses
163
+ !astUtils.isParenthesised(sourceCode, node);
164
+
165
+ // avoid parentheses issues
166
+ const returnOrArrowToken = sourceCode.getTokenBefore(
167
+ node,
168
+ node.parent.type === "ArrowFunctionExpression"
169
+ ? astUtils.isArrowToken
170
+
171
+ // isReturnToken
172
+ : token => token.type === "Keyword" && token.value === "return"
173
+ );
174
+
175
+ const firstToken = sourceCode.getTokenAfter(returnOrArrowToken);
176
+
177
+ const prependSpace =
178
+
179
+ // is return token, as => allows void to be adjacent
180
+ returnOrArrowToken.value === "return" &&
181
+
182
+ // If two tokens (return and "(") are adjacent
183
+ returnOrArrowToken.range[1] === firstToken.range[0];
184
+
185
+ return [
186
+ fixer.insertTextBefore(firstToken, `${prependSpace ? " " : ""}void ${requiresParens ? "(" : ""}`),
187
+ fixer.insertTextAfter(node, requiresParens ? ")" : "")
188
+ ];
189
+ }
190
+
191
+ /**
192
+ * Fixes the linting error by `wrapping {}` around the given node's body.
193
+ * @param {Object} sourceCode context given by context.sourceCode
194
+ * @param {ASTNode} node The node to fix.
195
+ * @param {Object} fixer The fixer object provided by ESLint.
196
+ * @returns {Array<Object>} - An array of fix objects to apply to the node.
197
+ */
198
+ function curlyWrapFixer(sourceCode, node, fixer) {
199
+ const arrowToken = sourceCode.getTokenBefore(node.body, astUtils.isArrowToken);
200
+ const firstToken = sourceCode.getTokenAfter(arrowToken);
201
+ const lastToken = sourceCode.getLastToken(node);
202
+
203
+ return [
204
+ fixer.insertTextBefore(firstToken, "{"),
205
+ fixer.insertTextAfter(lastToken, "}")
206
+ ];
207
+ }
208
+
139
209
  //------------------------------------------------------------------------------
140
210
  // Rule Definition
141
211
  //------------------------------------------------------------------------------
@@ -151,6 +221,9 @@ module.exports = {
151
221
  url: "https://eslint.org/docs/latest/rules/array-callback-return"
152
222
  },
153
223
 
224
+ // eslint-disable-next-line eslint-plugin/require-meta-has-suggestions -- false positive
225
+ hasSuggestions: true,
226
+
154
227
  schema: [
155
228
  {
156
229
  type: "object",
@@ -162,6 +235,10 @@ module.exports = {
162
235
  checkForEach: {
163
236
  type: "boolean",
164
237
  default: false
238
+ },
239
+ allowVoid: {
240
+ type: "boolean",
241
+ default: false
165
242
  }
166
243
  },
167
244
  additionalProperties: false
@@ -172,13 +249,15 @@ module.exports = {
172
249
  expectedAtEnd: "{{arrayMethodName}}() expects a value to be returned at the end of {{name}}.",
173
250
  expectedInside: "{{arrayMethodName}}() expects a return value from {{name}}.",
174
251
  expectedReturnValue: "{{arrayMethodName}}() expects a return value from {{name}}.",
175
- expectedNoReturnValue: "{{arrayMethodName}}() expects no useless return value from {{name}}."
252
+ expectedNoReturnValue: "{{arrayMethodName}}() expects no useless return value from {{name}}.",
253
+ wrapBraces: "Wrap the expression in `{}`.",
254
+ prependVoid: "Prepend `void` to the expression."
176
255
  }
177
256
  },
178
257
 
179
258
  create(context) {
180
259
 
181
- const options = context.options[0] || { allowImplicit: false, checkForEach: false };
260
+ const options = context.options[0] || { allowImplicit: false, checkForEach: false, allowVoid: false };
182
261
  const sourceCode = context.sourceCode;
183
262
 
184
263
  let funcInfo = {
@@ -205,26 +284,56 @@ module.exports = {
205
284
  return;
206
285
  }
207
286
 
208
- let messageId = null;
287
+ const messageAndSuggestions = { messageId: "", suggest: [] };
209
288
 
210
289
  if (funcInfo.arrayMethodName === "forEach") {
211
290
  if (options.checkForEach && node.type === "ArrowFunctionExpression" && node.expression) {
212
- messageId = "expectedNoReturnValue";
291
+
292
+ if (options.allowVoid) {
293
+ if (isExpressionVoid(node.body)) {
294
+ return;
295
+ }
296
+
297
+ messageAndSuggestions.messageId = "expectedNoReturnValue";
298
+ messageAndSuggestions.suggest = [
299
+ {
300
+ messageId: "wrapBraces",
301
+ fix(fixer) {
302
+ return curlyWrapFixer(sourceCode, node, fixer);
303
+ }
304
+ },
305
+ {
306
+ messageId: "prependVoid",
307
+ fix(fixer) {
308
+ return voidPrependFixer(sourceCode, node.body, fixer);
309
+ }
310
+ }
311
+ ];
312
+ } else {
313
+ messageAndSuggestions.messageId = "expectedNoReturnValue";
314
+ messageAndSuggestions.suggest = [{
315
+ messageId: "wrapBraces",
316
+ fix(fixer) {
317
+ return curlyWrapFixer(sourceCode, node, fixer);
318
+ }
319
+ }];
320
+ }
213
321
  }
214
322
  } else {
215
323
  if (node.body.type === "BlockStatement" && isAnySegmentReachable(funcInfo.currentSegments)) {
216
- messageId = funcInfo.hasReturn ? "expectedAtEnd" : "expectedInside";
324
+ messageAndSuggestions.messageId = funcInfo.hasReturn ? "expectedAtEnd" : "expectedInside";
217
325
  }
218
326
  }
219
327
 
220
- if (messageId) {
328
+ if (messageAndSuggestions.messageId) {
221
329
  const name = astUtils.getFunctionNameWithKind(node);
222
330
 
223
331
  context.report({
224
332
  node,
225
333
  loc: astUtils.getFunctionHeadLoc(node, sourceCode),
226
- messageId,
227
- data: { name, arrayMethodName: fullMethodName(funcInfo.arrayMethodName) }
334
+ messageId: messageAndSuggestions.messageId,
335
+ data: { name, arrayMethodName: fullMethodName(funcInfo.arrayMethodName) },
336
+ suggest: messageAndSuggestions.suggest.length !== 0 ? messageAndSuggestions.suggest : null
228
337
  });
229
338
  }
230
339
  }
@@ -285,30 +394,46 @@ module.exports = {
285
394
 
286
395
  funcInfo.hasReturn = true;
287
396
 
288
- let messageId = null;
397
+ const messageAndSuggestions = { messageId: "", suggest: [] };
289
398
 
290
399
  if (funcInfo.arrayMethodName === "forEach") {
291
400
 
292
401
  // if checkForEach: true, returning a value at any path inside a forEach is not allowed
293
402
  if (options.checkForEach && node.argument) {
294
- messageId = "expectedNoReturnValue";
403
+
404
+ if (options.allowVoid) {
405
+ if (isExpressionVoid(node.argument)) {
406
+ return;
407
+ }
408
+
409
+ messageAndSuggestions.messageId = "expectedNoReturnValue";
410
+ messageAndSuggestions.suggest = [{
411
+ messageId: "prependVoid",
412
+ fix(fixer) {
413
+ return voidPrependFixer(sourceCode, node.argument, fixer);
414
+ }
415
+ }];
416
+ } else {
417
+ messageAndSuggestions.messageId = "expectedNoReturnValue";
418
+ }
295
419
  }
296
420
  } else {
297
421
 
298
422
  // if allowImplicit: false, should also check node.argument
299
423
  if (!options.allowImplicit && !node.argument) {
300
- messageId = "expectedReturnValue";
424
+ messageAndSuggestions.messageId = "expectedReturnValue";
301
425
  }
302
426
  }
303
427
 
304
- if (messageId) {
428
+ if (messageAndSuggestions.messageId) {
305
429
  context.report({
306
430
  node,
307
- messageId,
431
+ messageId: messageAndSuggestions.messageId,
308
432
  data: {
309
433
  name: astUtils.getFunctionNameWithKind(funcInfo.node),
310
434
  arrayMethodName: fullMethodName(funcInfo.arrayMethodName)
311
- }
435
+ },
436
+ suggest: messageAndSuggestions.suggest.length !== 0 ? messageAndSuggestions.suggest : null
312
437
  });
313
438
  }
314
439
  },
@@ -175,6 +175,7 @@ module.exports = new LazyLoadingRuleMap(Object.entries({
175
175
  "no-new-wrappers": () => require("./no-new-wrappers"),
176
176
  "no-nonoctal-decimal-escape": () => require("./no-nonoctal-decimal-escape"),
177
177
  "no-obj-calls": () => require("./no-obj-calls"),
178
+ "no-object-constructor": () => require("./no-object-constructor"),
178
179
  "no-octal": () => require("./no-octal"),
179
180
  "no-octal-escape": () => require("./no-octal-escape"),
180
181
  "no-param-reassign": () => require("./no-param-reassign"),
@@ -13,27 +13,34 @@ const { isValidWithUnicodeFlag } = require("./utils/regular-expressions");
13
13
  // Helpers
14
14
  //------------------------------------------------------------------------------
15
15
 
16
+ /**
17
+ * @typedef {import('@eslint-community/regexpp').AST.Character} Character
18
+ * @typedef {import('@eslint-community/regexpp').AST.CharacterClassElement} CharacterClassElement
19
+ */
20
+
16
21
  /**
17
22
  * Iterate character sequences of a given nodes.
18
23
  *
19
24
  * CharacterClassRange syntax can steal a part of character sequence,
20
25
  * so this function reverts CharacterClassRange syntax and restore the sequence.
21
- * @param {import('@eslint-community/regexpp').AST.CharacterClassElement[]} nodes The node list to iterate character sequences.
22
- * @returns {IterableIterator<number[]>} The list of character sequences.
26
+ * @param {CharacterClassElement[]} nodes The node list to iterate character sequences.
27
+ * @returns {IterableIterator<Character[]>} The list of character sequences.
23
28
  */
24
29
  function *iterateCharacterSequence(nodes) {
30
+
31
+ /** @type {Character[]} */
25
32
  let seq = [];
26
33
 
27
34
  for (const node of nodes) {
28
35
  switch (node.type) {
29
36
  case "Character":
30
- seq.push(node.value);
37
+ seq.push(node);
31
38
  break;
32
39
 
33
40
  case "CharacterClassRange":
34
- seq.push(node.min.value);
41
+ seq.push(node.min);
35
42
  yield seq;
36
- seq = [node.max.value];
43
+ seq = [node.max];
37
44
  break;
38
45
 
39
46
  case "CharacterSet":
@@ -55,32 +62,74 @@ function *iterateCharacterSequence(nodes) {
55
62
  }
56
63
  }
57
64
 
65
+
66
+ /**
67
+ * Checks whether the given character node is a Unicode code point escape or not.
68
+ * @param {Character} char the character node to check.
69
+ * @returns {boolean} `true` if the character node is a Unicode code point escape.
70
+ */
71
+ function isUnicodeCodePointEscape(char) {
72
+ return /^\\u\{[\da-f]+\}$/iu.test(char.raw);
73
+ }
74
+
75
+ /**
76
+ * Each function returns `true` if it detects that kind of problem.
77
+ * @type {Record<string, (chars: Character[]) => boolean>}
78
+ */
58
79
  const hasCharacterSequence = {
59
80
  surrogatePairWithoutUFlag(chars) {
60
- return chars.some((c, i) => i !== 0 && isSurrogatePair(chars[i - 1], c));
81
+ return chars.some((c, i) => {
82
+ if (i === 0) {
83
+ return false;
84
+ }
85
+ const c1 = chars[i - 1];
86
+
87
+ return (
88
+ isSurrogatePair(c1.value, c.value) &&
89
+ !isUnicodeCodePointEscape(c1) &&
90
+ !isUnicodeCodePointEscape(c)
91
+ );
92
+ });
93
+ },
94
+
95
+ surrogatePair(chars) {
96
+ return chars.some((c, i) => {
97
+ if (i === 0) {
98
+ return false;
99
+ }
100
+ const c1 = chars[i - 1];
101
+
102
+ return (
103
+ isSurrogatePair(c1.value, c.value) &&
104
+ (
105
+ isUnicodeCodePointEscape(c1) ||
106
+ isUnicodeCodePointEscape(c)
107
+ )
108
+ );
109
+ });
61
110
  },
62
111
 
63
112
  combiningClass(chars) {
64
113
  return chars.some((c, i) => (
65
114
  i !== 0 &&
66
- isCombiningCharacter(c) &&
67
- !isCombiningCharacter(chars[i - 1])
115
+ isCombiningCharacter(c.value) &&
116
+ !isCombiningCharacter(chars[i - 1].value)
68
117
  ));
69
118
  },
70
119
 
71
120
  emojiModifier(chars) {
72
121
  return chars.some((c, i) => (
73
122
  i !== 0 &&
74
- isEmojiModifier(c) &&
75
- !isEmojiModifier(chars[i - 1])
123
+ isEmojiModifier(c.value) &&
124
+ !isEmojiModifier(chars[i - 1].value)
76
125
  ));
77
126
  },
78
127
 
79
128
  regionalIndicatorSymbol(chars) {
80
129
  return chars.some((c, i) => (
81
130
  i !== 0 &&
82
- isRegionalIndicatorSymbol(c) &&
83
- isRegionalIndicatorSymbol(chars[i - 1])
131
+ isRegionalIndicatorSymbol(c.value) &&
132
+ isRegionalIndicatorSymbol(chars[i - 1].value)
84
133
  ));
85
134
  },
86
135
 
@@ -90,9 +139,9 @@ const hasCharacterSequence = {
90
139
  return chars.some((c, i) => (
91
140
  i !== 0 &&
92
141
  i !== lastIndex &&
93
- c === 0x200d &&
94
- chars[i - 1] !== 0x200d &&
95
- chars[i + 1] !== 0x200d
142
+ c.value === 0x200d &&
143
+ chars[i - 1].value !== 0x200d &&
144
+ chars[i + 1].value !== 0x200d
96
145
  ));
97
146
  }
98
147
  };
@@ -120,6 +169,7 @@ module.exports = {
120
169
 
121
170
  messages: {
122
171
  surrogatePairWithoutUFlag: "Unexpected surrogate pair in character class. Use 'u' flag.",
172
+ surrogatePair: "Unexpected surrogate pair in character class.",
123
173
  combiningClass: "Unexpected combined character in character class.",
124
174
  emojiModifier: "Unexpected modified Emoji in character class.",
125
175
  regionalIndicatorSymbol: "Unexpected national flag in character class.",
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * @fileoverview A rule to disallow calls to the Object constructor
3
3
  * @author Matt DuVall <http://www.mattduvall.com/>
4
+ * @deprecated in ESLint v8.50.0
4
5
  */
5
6
 
6
7
  "use strict";
@@ -26,6 +27,12 @@ module.exports = {
26
27
  url: "https://eslint.org/docs/latest/rules/no-new-object"
27
28
  },
28
29
 
30
+ deprecated: true,
31
+
32
+ replacedBy: [
33
+ "no-object-constructor"
34
+ ],
35
+
29
36
  schema: [],
30
37
 
31
38
  messages: {
@@ -0,0 +1,118 @@
1
+ /**
2
+ * @fileoverview Rule to disallow calls to the `Object` constructor without an argument
3
+ * @author Francesco Trotta
4
+ */
5
+
6
+ "use strict";
7
+
8
+ //------------------------------------------------------------------------------
9
+ // Requirements
10
+ //------------------------------------------------------------------------------
11
+
12
+ const { getVariableByName, isArrowToken } = require("./utils/ast-utils");
13
+
14
+ //------------------------------------------------------------------------------
15
+ // Helpers
16
+ //------------------------------------------------------------------------------
17
+
18
+ /**
19
+ * Tests if a node appears at the beginning of an ancestor ExpressionStatement node.
20
+ * @param {ASTNode} node The node to check.
21
+ * @returns {boolean} Whether the node appears at the beginning of an ancestor ExpressionStatement node.
22
+ */
23
+ function isStartOfExpressionStatement(node) {
24
+ const start = node.range[0];
25
+ let ancestor = node;
26
+
27
+ while ((ancestor = ancestor.parent) && ancestor.range[0] === start) {
28
+ if (ancestor.type === "ExpressionStatement") {
29
+ return true;
30
+ }
31
+ }
32
+ return false;
33
+ }
34
+
35
+ //------------------------------------------------------------------------------
36
+ // Rule Definition
37
+ //------------------------------------------------------------------------------
38
+
39
+ /** @type {import('../shared/types').Rule} */
40
+ module.exports = {
41
+ meta: {
42
+ type: "suggestion",
43
+
44
+ docs: {
45
+ description: "Disallow calls to the `Object` constructor without an argument",
46
+ recommended: false,
47
+ url: "https://eslint.org/docs/latest/rules/no-object-constructor"
48
+ },
49
+
50
+ hasSuggestions: true,
51
+
52
+ schema: [],
53
+
54
+ messages: {
55
+ preferLiteral: "The object literal notation {} is preferable.",
56
+ useLiteral: "Replace with '{{replacement}}'."
57
+ }
58
+ },
59
+
60
+ create(context) {
61
+
62
+ const sourceCode = context.sourceCode;
63
+
64
+ /**
65
+ * Determines whether or not an object literal that replaces a specified node needs to be enclosed in parentheses.
66
+ * @param {ASTNode} node The node to be replaced.
67
+ * @returns {boolean} Whether or not parentheses around the object literal are required.
68
+ */
69
+ function needsParentheses(node) {
70
+ if (isStartOfExpressionStatement(node)) {
71
+ return true;
72
+ }
73
+
74
+ const prevToken = sourceCode.getTokenBefore(node);
75
+
76
+ if (prevToken && isArrowToken(prevToken)) {
77
+ return true;
78
+ }
79
+
80
+ return false;
81
+ }
82
+
83
+ /**
84
+ * Reports on nodes where the `Object` constructor is called without arguments.
85
+ * @param {ASTNode} node The node to evaluate.
86
+ * @returns {void}
87
+ */
88
+ function check(node) {
89
+ if (node.callee.type !== "Identifier" || node.callee.name !== "Object" || node.arguments.length) {
90
+ return;
91
+ }
92
+
93
+ const variable = getVariableByName(sourceCode.getScope(node), "Object");
94
+
95
+ if (variable && variable.identifiers.length === 0) {
96
+ const replacement = needsParentheses(node) ? "({})" : "{}";
97
+
98
+ context.report({
99
+ node,
100
+ messageId: "preferLiteral",
101
+ suggest: [
102
+ {
103
+ messageId: "useLiteral",
104
+ data: { replacement },
105
+ fix: fixer => fixer.replaceText(node, replacement)
106
+ }
107
+ ]
108
+ });
109
+ }
110
+ }
111
+
112
+ return {
113
+ CallExpression: check,
114
+ NewExpression: check
115
+ };
116
+
117
+ }
118
+ };