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 +2 -2
- package/bin/eslint.js +1 -1
- package/lib/cli-engine/cli-engine.js +2 -2
- package/lib/cli-engine/file-enumerator.js +2 -2
- package/lib/cli-engine/lint-result-cache.js +2 -2
- package/lib/cli-engine/load-rules.js +2 -2
- package/lib/cli.js +5 -5
- package/lib/config/flat-config-array.js +1 -1
- package/lib/eslint/eslint-helpers.js +9 -9
- package/lib/eslint/eslint.js +6 -15
- package/lib/eslint/legacy-eslint.js +3 -3
- package/lib/linter/code-path-analysis/code-path-analyzer.js +1 -1
- package/lib/linter/code-path-analysis/fork-context.js +1 -1
- package/lib/linter/linter.js +1 -1
- package/lib/linter/report-translator.js +1 -1
- package/lib/rule-tester/rule-tester.js +4 -4
- package/lib/rules/func-style.js +45 -7
- package/lib/rules/line-comment-position.js +3 -0
- package/lib/rules/multiline-comment-style.js +3 -1
- package/lib/rules/no-constant-binary-expression.js +1 -1
- package/lib/rules/no-constructor-return.js +1 -1
- package/lib/rules/no-extra-boolean-cast.js +79 -27
- package/lib/rules/no-loop-func.js +161 -129
- package/lib/rules/no-misleading-character-class.js +111 -30
- package/lib/rules/no-restricted-exports.js +13 -2
- package/lib/rules/object-shorthand.js +21 -20
- package/lib/shared/runtime-info.js +2 -2
- package/lib/source-code/token-store/index.js +1 -1
- package/messages/config-file-missing.js +16 -0
- package/messages/eslintrc-incompat.js +22 -1
- package/package.json +6 -6
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/
|
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://
|
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("@
|
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
|
339
|
-
*
|
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:
|
342
|
+
* Performance note: `getConfig()` aggressively caches
|
343
343
|
* results so there is no performance penalty for calling
|
344
|
-
* it
|
344
|
+
* it multiple times with the same argument.
|
345
345
|
*/
|
346
|
-
if (pathMatches &&
|
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 &&
|
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.
|
548
|
+
ignored: !configs.getConfig(filePath)
|
549
549
|
});
|
550
550
|
}
|
551
551
|
|
package/lib/eslint/eslint.js
CHANGED
@@ -9,10 +9,9 @@
|
|
9
9
|
// Requirements
|
10
10
|
//------------------------------------------------------------------------------
|
11
11
|
|
12
|
-
|
13
|
-
const
|
14
|
-
const
|
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.
|
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
|
//------------------------------------------------------------------------------
|
package/lib/linter/linter.js
CHANGED
@@ -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("@
|
31
|
+
const { ConfigArraySymbol } = require("@eslint/config-array");
|
32
32
|
const { isSerializable } = require("../shared/serialization");
|
33
33
|
|
34
34
|
//------------------------------------------------------------------------------
|
package/lib/rules/func-style.js
CHANGED
@@ -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 (
|
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 (
|
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
|
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
|
-
|
90
|
-
|
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
|
-
*
|
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.
|
@@ -30,14 +30,28 @@ module.exports = {
|
|
30
30
|
},
|
31
31
|
|
32
32
|
schema: [{
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
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
|
-
|
119
|
-
|
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
|
-
|
122
|
-
|
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
|
-
|
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":
|