eslint 8.48.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.
@@ -14,15 +14,23 @@ const astUtils = require("./utils/ast-utils");
14
14
  //------------------------------------------------------------------------------
15
15
  // Helpers
16
16
  //------------------------------------------------------------------------------
17
+
17
18
  const TARGET_NODE_TYPE = /^(?:Arrow)?FunctionExpression$/u;
18
19
 
19
20
  /**
20
- * Checks a given code path segment is reachable.
21
- * @param {CodePathSegment} segment A segment to check.
22
- * @returns {boolean} `true` if the segment is reachable.
21
+ * Checks all segments in a set and returns true if any are reachable.
22
+ * @param {Set<CodePathSegment>} segments The segments to check.
23
+ * @returns {boolean} True if any segment is reachable; false otherwise.
23
24
  */
24
- function isReachable(segment) {
25
- return segment.reachable;
25
+ function isAnySegmentReachable(segments) {
26
+
27
+ for (const segment of segments) {
28
+ if (segment.reachable) {
29
+ return true;
30
+ }
31
+ }
32
+
33
+ return false;
26
34
  }
27
35
 
28
36
  //------------------------------------------------------------------------------
@@ -71,7 +79,8 @@ module.exports = {
71
79
  codePath: null,
72
80
  hasReturn: false,
73
81
  shouldCheck: false,
74
- node: null
82
+ node: null,
83
+ currentSegments: []
75
84
  };
76
85
 
77
86
  /**
@@ -85,7 +94,7 @@ module.exports = {
85
94
  */
86
95
  function checkLastSegment(node) {
87
96
  if (funcInfo.shouldCheck &&
88
- funcInfo.codePath.currentSegments.some(isReachable)
97
+ isAnySegmentReachable(funcInfo.currentSegments)
89
98
  ) {
90
99
  context.report({
91
100
  node,
@@ -144,7 +153,8 @@ module.exports = {
144
153
  codePath,
145
154
  hasReturn: false,
146
155
  shouldCheck: isGetter(node),
147
- node
156
+ node,
157
+ currentSegments: new Set()
148
158
  };
149
159
  },
150
160
 
@@ -152,6 +162,21 @@ module.exports = {
152
162
  onCodePathEnd() {
153
163
  funcInfo = funcInfo.upper;
154
164
  },
165
+ onUnreachableCodePathSegmentStart(segment) {
166
+ funcInfo.currentSegments.add(segment);
167
+ },
168
+
169
+ onUnreachableCodePathSegmentEnd(segment) {
170
+ funcInfo.currentSegments.delete(segment);
171
+ },
172
+
173
+ onCodePathSegmentStart(segment) {
174
+ funcInfo.currentSegments.add(segment);
175
+ },
176
+
177
+ onCodePathSegmentEnd(segment) {
178
+ funcInfo.currentSegments.delete(segment);
179
+ },
155
180
 
156
181
  // Checks the return statement is valid.
157
182
  ReturnStatement(node) {
@@ -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"),
@@ -10,6 +10,21 @@
10
10
 
11
11
  const astUtils = require("./utils/ast-utils");
12
12
 
13
+ //------------------------------------------------------------------------------
14
+ // Helpers
15
+ //------------------------------------------------------------------------------
16
+
17
+ /**
18
+ * Types of class members.
19
+ * Those have `test` method to check it matches to the given class member.
20
+ * @private
21
+ */
22
+ const ClassMemberTypes = {
23
+ "*": { test: () => true },
24
+ field: { test: node => node.type === "PropertyDefinition" },
25
+ method: { test: node => node.type === "MethodDefinition" }
26
+ };
27
+
13
28
  //------------------------------------------------------------------------------
14
29
  // Rule Definition
15
30
  //------------------------------------------------------------------------------
@@ -29,7 +44,32 @@ module.exports = {
29
44
 
30
45
  schema: [
31
46
  {
32
- enum: ["always", "never"]
47
+ anyOf: [
48
+ {
49
+ type: "object",
50
+ properties: {
51
+ enforce: {
52
+ type: "array",
53
+ items: {
54
+ type: "object",
55
+ properties: {
56
+ blankLine: { enum: ["always", "never"] },
57
+ prev: { enum: ["method", "field", "*"] },
58
+ next: { enum: ["method", "field", "*"] }
59
+ },
60
+ additionalProperties: false,
61
+ required: ["blankLine", "prev", "next"]
62
+ },
63
+ minItems: 1
64
+ }
65
+ },
66
+ additionalProperties: false,
67
+ required: ["enforce"]
68
+ },
69
+ {
70
+ enum: ["always", "never"]
71
+ }
72
+ ]
33
73
  },
34
74
  {
35
75
  type: "object",
@@ -55,6 +95,7 @@ module.exports = {
55
95
  options[0] = context.options[0] || "always";
56
96
  options[1] = context.options[1] || { exceptAfterSingleLine: false };
57
97
 
98
+ const configureList = typeof options[0] === "object" ? options[0].enforce : [{ blankLine: options[0], prev: "*", next: "*" }];
58
99
  const sourceCode = context.sourceCode;
59
100
 
60
101
  /**
@@ -144,6 +185,38 @@ module.exports = {
144
185
  return sourceCode.getTokensBetween(before, after, { includeComments: true }).length !== 0;
145
186
  }
146
187
 
188
+ /**
189
+ * Checks whether the given node matches the given type.
190
+ * @param {ASTNode} node The class member node to check.
191
+ * @param {string} type The class member type to check.
192
+ * @returns {boolean} `true` if the class member node matched the type.
193
+ * @private
194
+ */
195
+ function match(node, type) {
196
+ return ClassMemberTypes[type].test(node);
197
+ }
198
+
199
+ /**
200
+ * Finds the last matched configuration from the configureList.
201
+ * @param {ASTNode} prevNode The previous node to match.
202
+ * @param {ASTNode} nextNode The current node to match.
203
+ * @returns {string|null} Padding type or `null` if no matches were found.
204
+ * @private
205
+ */
206
+ function getPaddingType(prevNode, nextNode) {
207
+ for (let i = configureList.length - 1; i >= 0; --i) {
208
+ const configure = configureList[i];
209
+ const matched =
210
+ match(prevNode, configure.prev) &&
211
+ match(nextNode, configure.next);
212
+
213
+ if (matched) {
214
+ return configure.blankLine;
215
+ }
216
+ }
217
+ return null;
218
+ }
219
+
147
220
  return {
148
221
  ClassBody(node) {
149
222
  const body = node.body;
@@ -158,22 +231,34 @@ module.exports = {
158
231
  const isPadded = afterPadding.loc.start.line - beforePadding.loc.end.line > 1;
159
232
  const hasTokenInPadding = hasTokenOrCommentBetween(beforePadding, afterPadding);
160
233
  const curLineLastToken = findLastConsecutiveTokenAfter(curLast, nextFirst, 0);
234
+ const paddingType = getPaddingType(body[i], body[i + 1]);
235
+
236
+ if (paddingType === "never" && isPadded) {
237
+ context.report({
238
+ node: body[i + 1],
239
+ messageId: "never",
161
240
 
162
- if ((options[0] === "always" && !skip && !isPadded) ||
163
- (options[0] === "never" && isPadded)) {
241
+ fix(fixer) {
242
+ if (hasTokenInPadding) {
243
+ return null;
244
+ }
245
+ return fixer.replaceTextRange([beforePadding.range[1], afterPadding.range[0]], "\n");
246
+ }
247
+ });
248
+ } else if (paddingType === "always" && !skip && !isPadded) {
164
249
  context.report({
165
250
  node: body[i + 1],
166
- messageId: isPadded ? "never" : "always",
251
+ messageId: "always",
252
+
167
253
  fix(fixer) {
168
254
  if (hasTokenInPadding) {
169
255
  return null;
170
256
  }
171
- return isPadded
172
- ? fixer.replaceTextRange([beforePadding.range[1], afterPadding.range[0]], "\n")
173
- : fixer.insertTextAfter(curLineLastToken, "\n");
257
+ return fixer.insertTextAfter(curLineLastToken, "\n");
174
258
  }
175
259
  });
176
260
  }
261
+
177
262
  }
178
263
  }
179
264
  };
@@ -16,6 +16,22 @@ const { directivesPattern } = require("../shared/directives");
16
16
 
17
17
  const DEFAULT_FALLTHROUGH_COMMENT = /falls?\s?through/iu;
18
18
 
19
+ /**
20
+ * Checks all segments in a set and returns true if any are reachable.
21
+ * @param {Set<CodePathSegment>} segments The segments to check.
22
+ * @returns {boolean} True if any segment is reachable; false otherwise.
23
+ */
24
+ function isAnySegmentReachable(segments) {
25
+
26
+ for (const segment of segments) {
27
+ if (segment.reachable) {
28
+ return true;
29
+ }
30
+ }
31
+
32
+ return false;
33
+ }
34
+
19
35
  /**
20
36
  * Checks whether or not a given comment string is really a fallthrough comment and not an ESLint directive.
21
37
  * @param {string} comment The comment string to check.
@@ -51,15 +67,6 @@ function hasFallthroughComment(caseWhichFallsThrough, subsequentCase, context, f
51
67
  return Boolean(comment && isFallThroughComment(comment.value, fallthroughCommentPattern));
52
68
  }
53
69
 
54
- /**
55
- * Checks whether or not a given code path segment is reachable.
56
- * @param {CodePathSegment} segment A CodePathSegment to check.
57
- * @returns {boolean} `true` if the segment is reachable.
58
- */
59
- function isReachable(segment) {
60
- return segment.reachable;
61
- }
62
-
63
70
  /**
64
71
  * Checks whether a node and a token are separated by blank lines
65
72
  * @param {ASTNode} node The node to check
@@ -109,7 +116,8 @@ module.exports = {
109
116
 
110
117
  create(context) {
111
118
  const options = context.options[0] || {};
112
- let currentCodePath = null;
119
+ const codePathSegments = [];
120
+ let currentCodePathSegments = new Set();
113
121
  const sourceCode = context.sourceCode;
114
122
  const allowEmptyCase = options.allowEmptyCase || false;
115
123
 
@@ -126,13 +134,33 @@ module.exports = {
126
134
  fallthroughCommentPattern = DEFAULT_FALLTHROUGH_COMMENT;
127
135
  }
128
136
  return {
129
- onCodePathStart(codePath) {
130
- currentCodePath = codePath;
137
+
138
+ onCodePathStart() {
139
+ codePathSegments.push(currentCodePathSegments);
140
+ currentCodePathSegments = new Set();
131
141
  },
142
+
132
143
  onCodePathEnd() {
133
- currentCodePath = currentCodePath.upper;
144
+ currentCodePathSegments = codePathSegments.pop();
145
+ },
146
+
147
+ onUnreachableCodePathSegmentStart(segment) {
148
+ currentCodePathSegments.add(segment);
149
+ },
150
+
151
+ onUnreachableCodePathSegmentEnd(segment) {
152
+ currentCodePathSegments.delete(segment);
153
+ },
154
+
155
+ onCodePathSegmentStart(segment) {
156
+ currentCodePathSegments.add(segment);
134
157
  },
135
158
 
159
+ onCodePathSegmentEnd(segment) {
160
+ currentCodePathSegments.delete(segment);
161
+ },
162
+
163
+
136
164
  SwitchCase(node) {
137
165
 
138
166
  /*
@@ -157,7 +185,7 @@ module.exports = {
157
185
  * `break`, `return`, or `throw` are unreachable.
158
186
  * And allows empty cases and the last case.
159
187
  */
160
- if (currentCodePath.currentSegments.some(isReachable) &&
188
+ if (isAnySegmentReachable(currentCodePathSegments) &&
161
189
  (node.consequent.length > 0 || (!allowEmptyCase && hasBlankLinesBetween(node, nextToken))) &&
162
190
  node.parent.cases[node.parent.cases.length - 1] !== node) {
163
191
  fallthroughCase = node;
@@ -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
+ };