eslint 9.2.0 → 9.4.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.
package/README.md CHANGED
@@ -296,8 +296,8 @@ The following companies, organizations, and individuals support ESLint's ongoing
296
296
  <h3>Platinum Sponsors</h3>
297
297
  <p><a href="https://automattic.com"><img src="https://images.opencollective.com/automattic/d0ef3e1/logo.png" alt="Automattic" height="undefined"></a></p><h3>Gold Sponsors</h3>
298
298
  <p><a href="https://engineering.salesforce.com"><img src="https://images.opencollective.com/salesforce/ca8f997/logo.png" alt="Salesforce" height="96"></a> <a href="https://www.airbnb.com/"><img src="https://images.opencollective.com/airbnb/d327d66/logo.png" alt="Airbnb" height="96"></a></p><h3>Silver Sponsors</h3>
299
- <p><a href="https://www.jetbrains.com/"><img src="https://images.opencollective.com/jetbrains/eb04ddc/logo.png" alt="JetBrains" height="64"></a> <a href="https://liftoff.io/"><img src="https://images.opencollective.com/liftoff/5c4fa84/logo.png" alt="Liftoff" height="64"></a> <a href="https://americanexpress.io"><img src="https://avatars.githubusercontent.com/u/3853301?v=4" alt="American Express" height="64"></a> <a href="https://www.workleap.com"><img src="https://avatars.githubusercontent.com/u/53535748?u=d1e55d7661d724bf2281c1bfd33cb8f99fe2465f&v=4" alt="Workleap" height="64"></a></p><h3>Bronze Sponsors</h3>
300
- <p><a href="https://www.notion.so"><img src="https://images.opencollective.com/notion/bf3b117/logo.png" alt="notion" height="32"></a> <a href="https://www.crosswordsolver.org/anagram-solver/"><img src="https://images.opencollective.com/anagram-solver/2666271/logo.png" alt="Anagram Solver" height="32"></a> <a href="https://icons8.com/"><img src="https://images.opencollective.com/icons8/7fa1641/logo.png" alt="Icons8" height="32"></a> <a href="https://discord.com"><img src="https://images.opencollective.com/discordapp/f9645d9/logo.png" alt="Discord" height="32"></a> <a href="https://transloadit.com/"><img src="https://avatars.githubusercontent.com/u/125754?v=4" alt="Transloadit" height="32"></a> <a href="https://www.ignitionapp.com"><img src="https://avatars.githubusercontent.com/u/5753491?v=4" alt="Ignition" height="32"></a> <a href="https://nx.dev"><img src="https://avatars.githubusercontent.com/u/23692104?v=4" alt="Nx" height="32"></a> <a href="https://herocoders.com"><img src="https://avatars.githubusercontent.com/u/37549774?v=4" alt="HeroCoders" height="32"></a> <a href="https://usenextbase.com"><img src="https://avatars.githubusercontent.com/u/145838380?v=4" alt="Nextbase Starter Kit" height="32"></a></p>
299
+ <p><a href="https://www.jetbrains.com/"><img src="https://images.opencollective.com/jetbrains/fe76f99/logo.png" alt="JetBrains" height="64"></a> <a href="https://liftoff.io/"><img src="https://images.opencollective.com/liftoff/5c4fa84/logo.png" alt="Liftoff" height="64"></a> <a href="https://americanexpress.io"><img src="https://avatars.githubusercontent.com/u/3853301?v=4" alt="American Express" height="64"></a> <a href="https://www.workleap.com"><img src="https://avatars.githubusercontent.com/u/53535748?u=d1e55d7661d724bf2281c1bfd33cb8f99fe2465f&v=4" alt="Workleap" height="64"></a></p><h3>Bronze Sponsors</h3>
300
+ <p><a href="https://www.notion.so"><img src="https://images.opencollective.com/notion/bf3b117/logo.png" alt="notion" height="32"></a> <a href="https://www.crosswordsolver.org/anagram-solver/"><img src="https://images.opencollective.com/anagram-solver/2666271/logo.png" alt="Anagram Solver" height="32"></a> <a href="https://icons8.com/"><img src="https://images.opencollective.com/icons8/7fa1641/logo.png" alt="Icons8" height="32"></a> <a href="https://discord.com"><img src="https://images.opencollective.com/discordapp/f9645d9/logo.png" alt="Discord" height="32"></a> <a href="https://www.ignitionapp.com"><img src="https://avatars.githubusercontent.com/u/5753491?v=4" alt="Ignition" height="32"></a> <a href="https://nx.dev"><img src="https://avatars.githubusercontent.com/u/23692104?v=4" alt="Nx" height="32"></a> <a href="https://herocoders.com"><img src="https://avatars.githubusercontent.com/u/37549774?v=4" alt="HeroCoders" height="32"></a> <a href="https://usenextbase.com"><img src="https://avatars.githubusercontent.com/u/145838380?v=4" alt="Nextbase Starter Kit" height="32"></a></p>
301
301
  <!--sponsorsend-->
302
302
 
303
303
  ## Technology Sponsors
package/bin/eslint.js CHANGED
@@ -64,7 +64,7 @@ function readStdin() {
64
64
  function getErrorMessage(error) {
65
65
 
66
66
  // Lazy loading because this is used only if an error happened.
67
- const util = require("util");
67
+ const util = require("node:util");
68
68
 
69
69
  // Foolproof -- third-party module might throw non-object.
70
70
  if (typeof error !== "object" || error === null) {
@@ -15,8 +15,8 @@
15
15
  // Requirements
16
16
  //------------------------------------------------------------------------------
17
17
 
18
- const fs = require("fs");
19
- const path = require("path");
18
+ const fs = require("node:fs");
19
+ const path = require("node:path");
20
20
  const defaultOptions = require("../../conf/default-cli-options");
21
21
  const pkg = require("../../package.json");
22
22
 
@@ -34,8 +34,8 @@
34
34
  // Requirements
35
35
  //------------------------------------------------------------------------------
36
36
 
37
- const fs = require("fs");
38
- const path = require("path");
37
+ const fs = require("node:fs");
38
+ const path = require("node:path");
39
39
  const getGlobParent = require("glob-parent");
40
40
  const isGlob = require("is-glob");
41
41
  const escapeRegExp = require("escape-string-regexp");
@@ -8,8 +8,8 @@
8
8
  // Requirements
9
9
  //-----------------------------------------------------------------------------
10
10
 
11
- const assert = require("assert");
12
- const fs = require("fs");
11
+ const assert = require("node:assert");
12
+ const fs = require("node:fs");
13
13
  const fileEntryCache = require("file-entry-cache");
14
14
  const stringify = require("json-stable-stringify-without-jsonify");
15
15
  const pkg = require("../../package.json");
@@ -9,8 +9,8 @@
9
9
  // Requirements
10
10
  //------------------------------------------------------------------------------
11
11
 
12
- const fs = require("fs"),
13
- path = require("path");
12
+ const fs = require("node:fs"),
13
+ path = require("node:path");
14
14
 
15
15
  const rulesDirCache = {};
16
16
 
package/lib/cli.js CHANGED
@@ -15,9 +15,9 @@
15
15
  // Requirements
16
16
  //------------------------------------------------------------------------------
17
17
 
18
- const fs = require("fs"),
19
- path = require("path"),
20
- { promisify } = require("util"),
18
+ const fs = require("node:fs"),
19
+ path = require("node:path"),
20
+ { promisify } = require("node:util"),
21
21
  { LegacyESLint } = require("./eslint"),
22
22
  { ESLint, shouldUseFlatConfig, locateConfigFileToUse } = require("./eslint/eslint"),
23
23
  createCLIOptions = require("./options"),
@@ -448,14 +448,14 @@ const cli = {
448
448
 
449
449
  if (options.inspectConfig) {
450
450
 
451
- log.info("You can also run this command directly using 'npx @eslint/config-inspector' in the same directory as your configuration file.");
451
+ log.info("You can also run this command directly using 'npx @eslint/config-inspector@latest' in the same directory as your configuration file.");
452
452
 
453
453
  try {
454
454
  const flatOptions = await translateOptions(options, "flat");
455
455
  const spawn = require("cross-spawn");
456
456
  const flags = await cli.calculateInspectConfigFlags(flatOptions.overrideConfigFile);
457
457
 
458
- spawn.sync("npx", ["@eslint/config-inspector", ...flags], { encoding: "utf8", stdio: "inherit" });
458
+ spawn.sync("npx", ["@eslint/config-inspector@latest", ...flags], { encoding: "utf8", stdio: "inherit" });
459
459
  } catch (error) {
460
460
  log.error(error);
461
461
  return 2;
@@ -9,7 +9,7 @@
9
9
  // Requirements
10
10
  //-----------------------------------------------------------------------------
11
11
 
12
- const { ConfigArray, ConfigArraySymbol } = require("@humanwhocodes/config-array");
12
+ const { ConfigArray, ConfigArraySymbol } = require("@eslint/config-array");
13
13
  const { flatConfigSchema } = require("./flat-config-schema");
14
14
  const { RuleValidator } = require("./rule-validator");
15
15
  const { defaultConfig } = require("./default-config");
@@ -9,8 +9,8 @@
9
9
  // Requirements
10
10
  //-----------------------------------------------------------------------------
11
11
 
12
- const path = require("path");
13
- const fs = require("fs");
12
+ const path = require("node:path");
13
+ const fs = require("node:fs");
14
14
  const fsp = fs.promises;
15
15
  const isGlob = require("is-glob");
16
16
  const hash = require("../cli-engine/hash");
@@ -335,15 +335,15 @@ async function globSearch({
335
335
 
336
336
  /*
337
337
  * We updated the unmatched patterns set only if the path
338
- * matches and the file isn't ignored. If the file is
339
- * ignored, that means there wasn't a match for the
338
+ * matches and the file has a config. If the file has no
339
+ * config, that means there wasn't a match for the
340
340
  * pattern so it should not be removed.
341
341
  *
342
- * Performance note: isFileIgnored() aggressively caches
342
+ * Performance note: `getConfig()` aggressively caches
343
343
  * results so there is no performance penalty for calling
344
- * it twice with the same argument.
344
+ * it multiple times with the same argument.
345
345
  */
346
- if (pathMatches && !configs.isFileIgnored(entry.path)) {
346
+ if (pathMatches && configs.getConfig(entry.path)) {
347
347
  unmatchedPatterns.delete(matcher.pattern);
348
348
  }
349
349
 
@@ -351,7 +351,7 @@ async function globSearch({
351
351
  }, false)
352
352
  : matchers.some(matcher => matcher.match(relativePath));
353
353
 
354
- return matchesPattern && !configs.isFileIgnored(entry.path);
354
+ return matchesPattern && configs.getConfig(entry.path) !== void 0;
355
355
  })
356
356
  },
357
357
  (error, entries) => {
@@ -545,7 +545,7 @@ async function findFiles({
545
545
  if (stat.isFile()) {
546
546
  results.push({
547
547
  filePath,
548
- ignored: configs.isFileIgnored(filePath)
548
+ ignored: !configs.getConfig(filePath)
549
549
  });
550
550
  }
551
551
 
@@ -9,10 +9,9 @@
9
9
  // Requirements
10
10
  //------------------------------------------------------------------------------
11
11
 
12
- // Note: Node.js 12 does not support fs/promises.
13
- const fs = require("fs").promises;
14
- const { existsSync } = require("fs");
15
- const path = require("path");
12
+ const fs = require("node:fs/promises");
13
+ const { existsSync } = require("node:fs");
14
+ const path = require("node:path");
16
15
  const findUp = require("find-up");
17
16
  const { version } = require("../../package.json");
18
17
  const { Linter } = require("../linter");
@@ -39,7 +38,7 @@ const {
39
38
 
40
39
  processOptions
41
40
  } = require("./eslint-helpers");
42
- const { pathToFileURL } = require("url");
41
+ const { pathToFileURL } = require("node:url");
43
42
  const { FlatConfigArray } = require("../config/flat-config-array");
44
43
  const LintResultCache = require("../cli-engine/lint-result-cache");
45
44
  const { Retrier } = require("@humanwhocodes/retry");
@@ -348,6 +347,7 @@ async function locateConfigFileToUse({ configFile, cwd }) {
348
347
  basePath = path.resolve(path.dirname(configFilePath));
349
348
  } else {
350
349
  error = new Error("Could not find config file.");
350
+ error.messageTemplate = "config-file-missing";
351
351
  }
352
352
 
353
353
  }
@@ -509,7 +509,7 @@ function verifyText({
509
509
  * @returns {boolean} `true` if the linter should adopt the code block.
510
510
  */
511
511
  filterCodeBlock(blockFilename) {
512
- return configs.isExplicitMatch(blockFilename);
512
+ return configs.getConfig(blockFilename) !== void 0;
513
513
  }
514
514
  }
515
515
  );
@@ -881,15 +881,6 @@ class ESLint {
881
881
 
882
882
  const config = configs.getConfig(filePath);
883
883
 
884
- /*
885
- * Sometimes a file found through a glob pattern will
886
- * be ignored. In this case, `config` will be undefined
887
- * and we just silently ignore the file.
888
- */
889
- if (!config) {
890
- return void 0;
891
- }
892
-
893
884
  // Skip if there is cached result.
894
885
  if (lintResultCache) {
895
886
  const cachedResult =
@@ -10,9 +10,9 @@
10
10
  // Requirements
11
11
  //------------------------------------------------------------------------------
12
12
 
13
- const path = require("path");
14
- const fs = require("fs");
15
- const { promisify } = require("util");
13
+ const path = require("node:path");
14
+ const fs = require("node:fs");
15
+ const { promisify } = require("node:util");
16
16
  const { CLIEngine, getCLIEngineInternalSlots } = require("../cli-engine/cli-engine");
17
17
  const BuiltinRules = require("../rules");
18
18
  const {
@@ -9,7 +9,7 @@
9
9
  // Requirements
10
10
  //------------------------------------------------------------------------------
11
11
 
12
- const assert = require("assert"),
12
+ const assert = require("node:assert"),
13
13
  { breakableTypePattern } = require("../../shared/ast-utils"),
14
14
  CodePath = require("./code-path"),
15
15
  CodePathSegment = require("./code-path-segment"),
@@ -13,7 +13,7 @@
13
13
  // Requirements
14
14
  //------------------------------------------------------------------------------
15
15
 
16
- const assert = require("assert"),
16
+ const assert = require("node:assert"),
17
17
  CodePathSegment = require("./code-path-segment");
18
18
 
19
19
  //------------------------------------------------------------------------------
@@ -11,7 +11,7 @@
11
11
  //------------------------------------------------------------------------------
12
12
 
13
13
  const
14
- path = require("path"),
14
+ path = require("node:path"),
15
15
  eslintScope = require("eslint-scope"),
16
16
  evk = require("eslint-visitor-keys"),
17
17
  espree = require("espree"),
@@ -9,7 +9,7 @@
9
9
  // Requirements
10
10
  //------------------------------------------------------------------------------
11
11
 
12
- const assert = require("assert");
12
+ const assert = require("node:assert");
13
13
  const ruleFixer = require("./rule-fixer");
14
14
  const { interpolate } = require("./interpolate");
15
15
 
@@ -11,9 +11,9 @@
11
11
  //------------------------------------------------------------------------------
12
12
 
13
13
  const
14
- assert = require("assert"),
15
- util = require("util"),
16
- path = require("path"),
14
+ assert = require("node:assert"),
15
+ util = require("node:util"),
16
+ path = require("node:path"),
17
17
  equal = require("fast-deep-equal"),
18
18
  Traverser = require("../shared/traverser"),
19
19
  { getRuleOptionsSchema } = require("../config/flat-config-helpers"),
@@ -28,7 +28,7 @@ const ajv = require("../shared/ajv")({ strictDefaults: true });
28
28
 
29
29
  const parserSymbol = Symbol.for("eslint.RuleTester.parser");
30
30
  const { SourceCode } = require("../source-code");
31
- const { ConfigArraySymbol } = require("@humanwhocodes/config-array");
31
+ const { ConfigArraySymbol } = require("@eslint/config-array");
32
32
  const { isSerializable } = require("../shared/serialization");
33
33
 
34
34
  //------------------------------------------------------------------------------
@@ -14,7 +14,7 @@ module.exports = {
14
14
  type: "suggestion",
15
15
 
16
16
  docs: {
17
- description: "Enforce the consistent use of either `function` declarations or expressions",
17
+ description: "Enforce the consistent use of either `function` declarations or expressions assigned to variables",
18
18
  recommended: false,
19
19
  url: "https://eslint.org/docs/latest/rules/func-style"
20
20
  },
@@ -29,6 +29,15 @@ module.exports = {
29
29
  allowArrowFunctions: {
30
30
  type: "boolean",
31
31
  default: false
32
+ },
33
+ overrides: {
34
+ type: "object",
35
+ properties: {
36
+ namedExports: {
37
+ enum: ["declaration", "expression", "ignore"]
38
+ }
39
+ },
40
+ additionalProperties: false
32
41
  }
33
42
  },
34
43
  additionalProperties: false
@@ -46,13 +55,22 @@ module.exports = {
46
55
  const style = context.options[0],
47
56
  allowArrowFunctions = context.options[1] && context.options[1].allowArrowFunctions,
48
57
  enforceDeclarations = (style === "declaration"),
58
+ exportFunctionStyle = context.options[1] && context.options[1].overrides && context.options[1].overrides.namedExports,
49
59
  stack = [];
50
60
 
51
61
  const nodesToCheck = {
52
62
  FunctionDeclaration(node) {
53
63
  stack.push(false);
54
64
 
55
- if (!enforceDeclarations && node.parent.type !== "ExportDefaultDeclaration") {
65
+ if (
66
+ !enforceDeclarations &&
67
+ node.parent.type !== "ExportDefaultDeclaration" &&
68
+ (typeof exportFunctionStyle === "undefined" || node.parent.type !== "ExportNamedDeclaration")
69
+ ) {
70
+ context.report({ node, messageId: "expression" });
71
+ }
72
+
73
+ if (node.parent.type === "ExportNamedDeclaration" && exportFunctionStyle === "expression") {
56
74
  context.report({ node, messageId: "expression" });
57
75
  }
58
76
  },
@@ -63,7 +81,18 @@ module.exports = {
63
81
  FunctionExpression(node) {
64
82
  stack.push(false);
65
83
 
66
- if (enforceDeclarations && node.parent.type === "VariableDeclarator") {
84
+ if (
85
+ enforceDeclarations &&
86
+ node.parent.type === "VariableDeclarator" &&
87
+ (typeof exportFunctionStyle === "undefined" || node.parent.parent.parent.type !== "ExportNamedDeclaration")
88
+ ) {
89
+ context.report({ node: node.parent, messageId: "declaration" });
90
+ }
91
+
92
+ if (
93
+ node.parent.type === "VariableDeclarator" && node.parent.parent.parent.type === "ExportNamedDeclaration" &&
94
+ exportFunctionStyle === "declaration"
95
+ ) {
67
96
  context.report({ node: node.parent, messageId: "declaration" });
68
97
  }
69
98
  },
@@ -71,7 +100,7 @@ module.exports = {
71
100
  stack.pop();
72
101
  },
73
102
 
74
- ThisExpression() {
103
+ "ThisExpression, Super"() {
75
104
  if (stack.length > 0) {
76
105
  stack[stack.length - 1] = true;
77
106
  }
@@ -84,10 +113,19 @@ module.exports = {
84
113
  };
85
114
 
86
115
  nodesToCheck["ArrowFunctionExpression:exit"] = function(node) {
87
- const hasThisExpr = stack.pop();
116
+ const hasThisOrSuperExpr = stack.pop();
117
+
118
+ if (!hasThisOrSuperExpr && node.parent.type === "VariableDeclarator") {
119
+ if (
120
+ enforceDeclarations &&
121
+ (typeof exportFunctionStyle === "undefined" || node.parent.parent.parent.type !== "ExportNamedDeclaration")
122
+ ) {
123
+ context.report({ node: node.parent, messageId: "declaration" });
124
+ }
88
125
 
89
- if (enforceDeclarations && !hasThisExpr && node.parent.type === "VariableDeclarator") {
90
- context.report({ node: node.parent, messageId: "declaration" });
126
+ if (node.parent.parent.parent.type === "ExportNamedDeclaration" && exportFunctionStyle === "declaration") {
127
+ context.report({ node: node.parent, messageId: "declaration" });
128
+ }
91
129
  }
92
130
  };
93
131
  }
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * @fileoverview Rule to enforce the position of line comments
3
3
  * @author Alberto Rodríguez
4
+ * @deprecated in ESLint v9.3.0
4
5
  */
5
6
  "use strict";
6
7
 
@@ -13,6 +14,8 @@ const astUtils = require("./utils/ast-utils");
13
14
  /** @type {import('../shared/types').Rule} */
14
15
  module.exports = {
15
16
  meta: {
17
+ deprecated: true,
18
+ replacedBy: [],
16
19
  type: "layout",
17
20
 
18
21
  docs: {
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * @fileoverview enforce a particular style for multiline comments
3
3
  * @author Teddy Katz
4
+ * @deprecated in ESLint v9.3.0
4
5
  */
5
6
  "use strict";
6
7
 
@@ -13,8 +14,9 @@ const astUtils = require("./utils/ast-utils");
13
14
  /** @type {import('../shared/types').Rule} */
14
15
  module.exports = {
15
16
  meta: {
17
+ deprecated: true,
18
+ replacedBy: [],
16
19
  type: "suggestion",
17
-
18
20
  docs: {
19
21
  description: "Enforce a particular style for multiline comments",
20
22
  recommended: false,
@@ -148,7 +148,7 @@ function isStaticBoolean(scope, node) {
148
148
  * truthiness.
149
149
  * https://262.ecma-international.org/5.1/#sec-11.9.3
150
150
  *
151
- * Javascript `==` operator works by converting the boolean to `1` (true) or
151
+ * JavaScript `==` operator works by converting the boolean to `1` (true) or
152
152
  * `+0` (false) and then checks the values `==` equality to that number.
153
153
  * @param {Scope} scope The scope in which node was found.
154
154
  * @param {ASTNode} node The node to test.
@@ -49,7 +49,7 @@ module.exports = {
49
49
  if (
50
50
  last.parent.type === "MethodDefinition" &&
51
51
  last.parent.kind === "constructor" &&
52
- (node.parent.parent === last || node.argument)
52
+ node.argument
53
53
  ) {
54
54
  context.report({
55
55
  node,
@@ -30,14 +30,28 @@ module.exports = {
30
30
  },
31
31
 
32
32
  schema: [{
33
- type: "object",
34
- properties: {
35
- enforceForLogicalOperands: {
36
- type: "boolean",
37
- default: false
33
+ anyOf: [
34
+ {
35
+ type: "object",
36
+ properties: {
37
+ enforceForInnerExpressions: {
38
+ type: "boolean"
39
+ }
40
+ },
41
+ additionalProperties: false
42
+ },
43
+
44
+ // deprecated
45
+ {
46
+ type: "object",
47
+ properties: {
48
+ enforceForLogicalOperands: {
49
+ type: "boolean"
50
+ }
51
+ },
52
+ additionalProperties: false
38
53
  }
39
- },
40
- additionalProperties: false
54
+ ]
41
55
  }],
42
56
  fixable: "code",
43
57
 
@@ -49,6 +63,9 @@ module.exports = {
49
63
 
50
64
  create(context) {
51
65
  const sourceCode = context.sourceCode;
66
+ const enforceForLogicalOperands = context.options[0]?.enforceForLogicalOperands === true;
67
+ const enforceForInnerExpressions = context.options[0]?.enforceForInnerExpressions === true;
68
+
52
69
 
53
70
  // Node types which have a test which will coerce values to booleans.
54
71
  const BOOLEAN_NODE_TYPES = new Set([
@@ -72,19 +89,6 @@ module.exports = {
72
89
  node.callee.name === "Boolean";
73
90
  }
74
91
 
75
- /**
76
- * Checks whether the node is a logical expression and that the option is enabled
77
- * @param {ASTNode} node the node
78
- * @returns {boolean} if the node is a logical expression and option is enabled
79
- */
80
- function isLogicalContext(node) {
81
- return node.type === "LogicalExpression" &&
82
- (node.operator === "||" || node.operator === "&&") &&
83
- (context.options.length && context.options[0].enforceForLogicalOperands === true);
84
-
85
- }
86
-
87
-
88
92
  /**
89
93
  * Check if a node is in a context where its value would be coerced to a boolean at runtime.
90
94
  * @param {ASTNode} node The node
@@ -115,12 +119,51 @@ module.exports = {
115
119
  return isInFlaggedContext(node.parent);
116
120
  }
117
121
 
118
- return isInBooleanContext(node) ||
119
- (isLogicalContext(node.parent) &&
122
+ /*
123
+ * legacy behavior - enforceForLogicalOperands will only recurse on
124
+ * logical expressions, not on other contexts.
125
+ * enforceForInnerExpressions will recurse on logical expressions
126
+ * as well as the other recursive syntaxes.
127
+ */
128
+
129
+ if (enforceForLogicalOperands || enforceForInnerExpressions) {
130
+ if (node.parent.type === "LogicalExpression") {
131
+ if (node.parent.operator === "||" || node.parent.operator === "&&") {
132
+ return isInFlaggedContext(node.parent);
133
+ }
120
134
 
121
- // For nested logical statements
122
- isInFlaggedContext(node.parent)
123
- );
135
+ // Check the right hand side of a `??` operator.
136
+ if (enforceForInnerExpressions &&
137
+ node.parent.operator === "??" &&
138
+ node.parent.right === node
139
+ ) {
140
+ return isInFlaggedContext(node.parent);
141
+ }
142
+ }
143
+ }
144
+
145
+ if (enforceForInnerExpressions) {
146
+ if (
147
+ node.parent.type === "ConditionalExpression" &&
148
+ (node.parent.consequent === node || node.parent.alternate === node)
149
+ ) {
150
+ return isInFlaggedContext(node.parent);
151
+ }
152
+
153
+ /*
154
+ * Check last expression only in a sequence, i.e. if ((1, 2, Boolean(3))) {}, since
155
+ * the others don't affect the result of the expression.
156
+ */
157
+ if (
158
+ node.parent.type === "SequenceExpression" &&
159
+ node.parent.expressions.at(-1) === node
160
+ ) {
161
+ return isInFlaggedContext(node.parent);
162
+ }
163
+
164
+ }
165
+
166
+ return isInBooleanContext(node);
124
167
  }
125
168
 
126
169
 
@@ -147,7 +190,6 @@ module.exports = {
147
190
  * Determines whether the given node needs to be parenthesized when replacing the previous node.
148
191
  * It assumes that `previousNode` is the node to be reported by this rule, so it has a limited list
149
192
  * of possible parent node types. By the same assumption, the node's role in a particular parent is already known.
150
- * For example, if the parent is `ConditionalExpression`, `previousNode` must be its `test` child.
151
193
  * @param {ASTNode} previousNode Previous node.
152
194
  * @param {ASTNode} node The node to check.
153
195
  * @throws {Error} (Unreachable.)
@@ -157,6 +199,7 @@ module.exports = {
157
199
  if (previousNode.parent.type === "ChainExpression") {
158
200
  return needsParens(previousNode.parent, node);
159
201
  }
202
+
160
203
  if (isParenthesized(previousNode)) {
161
204
 
162
205
  // parentheses around the previous node will stay, so there is no need for an additional pair
@@ -174,9 +217,18 @@ module.exports = {
174
217
  case "DoWhileStatement":
175
218
  case "WhileStatement":
176
219
  case "ForStatement":
220
+ case "SequenceExpression":
177
221
  return false;
178
222
  case "ConditionalExpression":
179
- return precedence(node) <= precedence(parent);
223
+ if (previousNode === parent.test) {
224
+ return precedence(node) <= precedence(parent);
225
+ }
226
+ if (previousNode === parent.consequent || previousNode === parent.alternate) {
227
+ return precedence(node) < precedence({ type: "AssignmentExpression" });
228
+ }
229
+
230
+ /* c8 ignore next */
231
+ throw new Error("Ternary child must be test, consequent, or alternate.");
180
232
  case "UnaryExpression":
181
233
  return precedence(node) < precedence(parent);
182
234
  case "LogicalExpression":