eslint 8.50.0 → 8.52.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.
@@ -139,7 +139,7 @@ module.exports = class ConfigCommentParser {
139
139
  const items = {};
140
140
 
141
141
  string.split(",").forEach(name => {
142
- const trimmedName = name.trim();
142
+ const trimmedName = name.trim().replace(/^(?<quote>['"]?)(?<ruleId>.*)\k<quote>$/us, "$<ruleId>");
143
143
 
144
144
  if (trimmedName) {
145
145
  items[trimmedName] = true;
package/lib/options.js CHANGED
@@ -47,7 +47,7 @@ const optionator = require("optionator");
47
47
  * @property {Object} [parserOptions] Specify parser options
48
48
  * @property {string[]} [plugin] Specify plugins
49
49
  * @property {string} [printConfig] Print the configuration for the given file
50
- * @property {boolean | undefined} reportUnusedDisableDirectives Adds reported errors for unused eslint-disable directives
50
+ * @property {boolean | undefined} reportUnusedDisableDirectives Adds reported errors for unused eslint-disable and eslint-enable directives
51
51
  * @property {string} [resolvePluginsRelativeTo] A folder where plugins should be resolved from, CWD by default
52
52
  * @property {Object} [rule] Specify rules
53
53
  * @property {string[]} [rulesdir] Load additional rules from this directory. Deprecated: Use rules from plugins
@@ -55,6 +55,7 @@ const optionator = require("optionator");
55
55
  * @property {string} [stdinFilename] Specify filename to process STDIN as
56
56
  * @property {boolean} quiet Report errors only
57
57
  * @property {boolean} [version] Output the version number
58
+ * @property {boolean} warnIgnored Show warnings when the file list includes ignored files
58
59
  * @property {string[]} _ Positional filenames or patterns
59
60
  */
60
61
 
@@ -139,6 +140,17 @@ module.exports = function(usingFlatConfig) {
139
140
  };
140
141
  }
141
142
 
143
+ let warnIgnoredFlag;
144
+
145
+ if (usingFlatConfig) {
146
+ warnIgnoredFlag = {
147
+ option: "warn-ignored",
148
+ type: "Boolean",
149
+ default: "true",
150
+ description: "Suppress warnings when the file list includes ignored files"
151
+ };
152
+ }
153
+
142
154
  return optionator({
143
155
  prepend: "eslint [options] file.js [file.js] [dir]",
144
156
  defaults: {
@@ -292,7 +304,7 @@ module.exports = function(usingFlatConfig) {
292
304
  option: "report-unused-disable-directives",
293
305
  type: "Boolean",
294
306
  default: void 0,
295
- description: "Adds reported errors for unused eslint-disable directives"
307
+ description: "Adds reported errors for unused eslint-disable and eslint-enable directives"
296
308
  },
297
309
  {
298
310
  heading: "Caching"
@@ -349,6 +361,7 @@ module.exports = function(usingFlatConfig) {
349
361
  default: "false",
350
362
  description: "Exit with exit code 2 in case of fatal error"
351
363
  },
364
+ warnIgnoredFlag,
352
365
  {
353
366
  option: "debug",
354
367
  type: "Boolean",
@@ -150,6 +150,31 @@ function isInsideWithBlock(node) {
150
150
  return node.parent.type === "WithStatement" && node.parent.body === node ? true : isInsideWithBlock(node.parent);
151
151
  }
152
152
 
153
+ /**
154
+ * Gets the leftmost operand of a consecutive logical expression.
155
+ * @param {SourceCode} sourceCode The ESLint source code object
156
+ * @param {LogicalExpression} node LogicalExpression
157
+ * @returns {Expression} Leftmost operand
158
+ */
159
+ function getLeftmostOperand(sourceCode, node) {
160
+ let left = node.left;
161
+
162
+ while (left.type === "LogicalExpression" && left.operator === node.operator) {
163
+
164
+ if (astUtils.isParenthesised(sourceCode, left)) {
165
+
166
+ /*
167
+ * It should have associativity,
168
+ * but ignore it if use parentheses to make the evaluation order clear.
169
+ */
170
+ return left;
171
+ }
172
+ left = left.left;
173
+ }
174
+ return left;
175
+
176
+ }
177
+
153
178
  //------------------------------------------------------------------------------
154
179
  // Rule Definition
155
180
  //------------------------------------------------------------------------------
@@ -318,7 +343,10 @@ module.exports = {
318
343
 
319
344
  // foo = foo || bar
320
345
  "AssignmentExpression[operator='='][right.type='LogicalExpression']"(assignment) {
321
- if (!astUtils.isSameReference(assignment.left, assignment.right.left)) {
346
+ const leftOperand = getLeftmostOperand(sourceCode, assignment.right);
347
+
348
+ if (!astUtils.isSameReference(assignment.left, leftOperand)
349
+ ) {
322
350
  return;
323
351
  }
324
352
 
@@ -342,10 +370,10 @@ module.exports = {
342
370
  yield ruleFixer.insertTextBefore(assignmentOperatorToken, assignment.right.operator);
343
371
 
344
372
  // -> foo ||= bar
345
- const logicalOperatorToken = getOperatorToken(assignment.right);
373
+ const logicalOperatorToken = getOperatorToken(leftOperand.parent);
346
374
  const firstRightOperandToken = sourceCode.getTokenAfter(logicalOperatorToken);
347
375
 
348
- yield ruleFixer.removeRange([assignment.right.range[0], firstRightOperandToken.range[0]]);
376
+ yield ruleFixer.removeRange([leftOperand.parent.range[0], firstRightOperandToken.range[0]]);
349
377
  }
350
378
  };
351
379
 
@@ -9,12 +9,51 @@
9
9
  // Requirements
10
10
  //------------------------------------------------------------------------------
11
11
 
12
- const { getVariableByName, isArrowToken } = require("./utils/ast-utils");
12
+ const { getVariableByName, isArrowToken, isClosingBraceToken, isClosingParenToken } = require("./utils/ast-utils");
13
13
 
14
14
  //------------------------------------------------------------------------------
15
15
  // Helpers
16
16
  //------------------------------------------------------------------------------
17
17
 
18
+ const BREAK_OR_CONTINUE = new Set(["BreakStatement", "ContinueStatement"]);
19
+
20
+ // Declaration types that must contain a string Literal node at the end.
21
+ const DECLARATIONS = new Set(["ExportAllDeclaration", "ExportNamedDeclaration", "ImportDeclaration"]);
22
+
23
+ const IDENTIFIER_OR_KEYWORD = new Set(["Identifier", "Keyword"]);
24
+
25
+ // Keywords that can immediately precede an ExpressionStatement node, mapped to the their node types.
26
+ const NODE_TYPES_BY_KEYWORD = {
27
+ __proto__: null,
28
+ break: "BreakStatement",
29
+ continue: "ContinueStatement",
30
+ debugger: "DebuggerStatement",
31
+ do: "DoWhileStatement",
32
+ else: "IfStatement",
33
+ return: "ReturnStatement",
34
+ yield: "YieldExpression"
35
+ };
36
+
37
+ /*
38
+ * Before an opening parenthesis, `>` (for JSX), and postfix `++` and `--` always trigger ASI;
39
+ * the tokens `:`, `;`, `{` and `=>` don't expect a semicolon, as that would count as an empty statement.
40
+ */
41
+ const PUNCTUATORS = new Set([":", ";", ">", "{", "=>", "++", "--"]);
42
+
43
+ /*
44
+ * Statements that can contain an `ExpressionStatement` after a closing parenthesis.
45
+ * DoWhileStatement is an exception in that it always triggers ASI after the closing parenthesis.
46
+ */
47
+ const STATEMENTS = new Set([
48
+ "DoWhileStatement",
49
+ "ForInStatement",
50
+ "ForOfStatement",
51
+ "ForStatement",
52
+ "IfStatement",
53
+ "WhileStatement",
54
+ "WithStatement"
55
+ ]);
56
+
18
57
  /**
19
58
  * Tests if a node appears at the beginning of an ancestor ExpressionStatement node.
20
59
  * @param {ASTNode} node The node to check.
@@ -53,7 +92,8 @@ module.exports = {
53
92
 
54
93
  messages: {
55
94
  preferLiteral: "The object literal notation {} is preferable.",
56
- useLiteral: "Replace with '{{replacement}}'."
95
+ useLiteral: "Replace with '{{replacement}}'.",
96
+ useLiteralAfterSemicolon: "Replace with '{{replacement}}', add preceding semicolon."
57
97
  }
58
98
  },
59
99
 
@@ -80,6 +120,50 @@ module.exports = {
80
120
  return false;
81
121
  }
82
122
 
123
+ /**
124
+ * Determines whether a parenthesized object literal that replaces a specified node needs to be preceded by a semicolon.
125
+ * @param {ASTNode} node The node to be replaced. This node should be at the start of an `ExpressionStatement` or at the start of the body of an `ArrowFunctionExpression`.
126
+ * @returns {boolean} Whether a semicolon is required before the parenthesized object literal.
127
+ */
128
+ function needsSemicolon(node) {
129
+ const prevToken = sourceCode.getTokenBefore(node);
130
+
131
+ if (!prevToken || prevToken.type === "Punctuator" && PUNCTUATORS.has(prevToken.value)) {
132
+ return false;
133
+ }
134
+
135
+ const prevNode = sourceCode.getNodeByRangeIndex(prevToken.range[0]);
136
+
137
+ if (isClosingParenToken(prevToken)) {
138
+ return !STATEMENTS.has(prevNode.type);
139
+ }
140
+
141
+ if (isClosingBraceToken(prevToken)) {
142
+ return (
143
+ prevNode.type === "BlockStatement" && prevNode.parent.type === "FunctionExpression" ||
144
+ prevNode.type === "ClassBody" && prevNode.parent.type === "ClassExpression" ||
145
+ prevNode.type === "ObjectExpression"
146
+ );
147
+ }
148
+
149
+ if (IDENTIFIER_OR_KEYWORD.has(prevToken.type)) {
150
+ if (BREAK_OR_CONTINUE.has(prevNode.parent.type)) {
151
+ return false;
152
+ }
153
+
154
+ const keyword = prevToken.value;
155
+ const nodeType = NODE_TYPES_BY_KEYWORD[keyword];
156
+
157
+ return prevNode.type !== nodeType;
158
+ }
159
+
160
+ if (prevToken.type === "String") {
161
+ return !DECLARATIONS.has(prevNode.parent.type);
162
+ }
163
+
164
+ return true;
165
+ }
166
+
83
167
  /**
84
168
  * Reports on nodes where the `Object` constructor is called without arguments.
85
169
  * @param {ASTNode} node The node to evaluate.
@@ -93,16 +177,30 @@ module.exports = {
93
177
  const variable = getVariableByName(sourceCode.getScope(node), "Object");
94
178
 
95
179
  if (variable && variable.identifiers.length === 0) {
96
- const replacement = needsParentheses(node) ? "({})" : "{}";
180
+ let replacement;
181
+ let fixText;
182
+ let messageId = "useLiteral";
183
+
184
+ if (needsParentheses(node)) {
185
+ replacement = "({})";
186
+ if (needsSemicolon(node)) {
187
+ fixText = ";({})";
188
+ messageId = "useLiteralAfterSemicolon";
189
+ } else {
190
+ fixText = "({})";
191
+ }
192
+ } else {
193
+ replacement = fixText = "{}";
194
+ }
97
195
 
98
196
  context.report({
99
197
  node,
100
198
  messageId: "preferLiteral",
101
199
  suggest: [
102
200
  {
103
- messageId: "useLiteral",
201
+ messageId,
104
202
  data: { replacement },
105
- fix: fixer => fixer.replaceText(node, replacement)
203
+ fix: fixer => fixer.replaceText(node, fixText)
106
204
  }
107
205
  ]
108
206
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint",
3
- "version": "8.50.0",
3
+ "version": "8.52.0",
4
4
  "author": "Nicholas C. Zakas <nicholas+npm@nczconsulting.com>",
5
5
  "description": "An AST-based pattern checker for JavaScript.",
6
6
  "bin": {
@@ -63,10 +63,11 @@
63
63
  "@eslint-community/eslint-utils": "^4.2.0",
64
64
  "@eslint-community/regexpp": "^4.6.1",
65
65
  "@eslint/eslintrc": "^2.1.2",
66
- "@eslint/js": "8.50.0",
67
- "@humanwhocodes/config-array": "^0.11.11",
66
+ "@eslint/js": "8.52.0",
67
+ "@humanwhocodes/config-array": "^0.11.13",
68
68
  "@humanwhocodes/module-importer": "^1.0.1",
69
69
  "@nodelib/fs.walk": "^1.2.8",
70
+ "@ungap/structured-clone": "^1.2.0",
70
71
  "ajv": "^6.12.4",
71
72
  "chalk": "^4.0.0",
72
73
  "cross-spawn": "^7.0.2",