eslint 10.4.1 → 10.6.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 +5 -5
- package/lib/cli-engine/formatters/html.js +4 -4
- package/lib/rules/array-callback-return.js +1 -15
- package/lib/rules/consistent-return.js +2 -16
- package/lib/rules/getter-return.js +1 -15
- package/lib/rules/max-classes-per-file.js +4 -0
- package/lib/rules/max-depth.js +21 -2
- package/lib/rules/max-lines-per-function.js +1 -0
- package/lib/rules/max-nested-callbacks.js +23 -5
- package/lib/rules/max-statements.js +1 -0
- package/lib/rules/no-constant-binary-expression.js +69 -3
- package/lib/rules/no-extra-boolean-cast.js +4 -2
- package/lib/rules/no-fallthrough.js +1 -15
- package/lib/rules/no-promise-executor-return.js +3 -2
- package/lib/rules/no-throw-literal.js +6 -1
- package/lib/rules/no-unreachable-loop.js +2 -15
- package/lib/rules/no-unreachable.js +7 -16
- package/lib/rules/no-useless-return.js +1 -15
- package/lib/rules/no-with.js +7 -1
- package/lib/rules/prefer-exponentiation-operator.js +44 -5
- package/lib/rules/prefer-promise-reject-errors.js +17 -7
- package/lib/rules/radix.js +6 -3
- package/lib/rules/utils/code-path-utils.js +22 -0
- package/lib/types/rules.d.ts +10 -1
- package/package.json +14 -12
package/README.md
CHANGED
|
@@ -45,7 +45,7 @@ ESLint is a tool for identifying and reporting on patterns found in ECMAScript/J
|
|
|
45
45
|
|
|
46
46
|
### Prerequisites
|
|
47
47
|
|
|
48
|
-
To use ESLint, you must have [Node.js](https://nodejs.org/) (`^20.19.0`, `^22.13.0`, or `>=24`) installed and built with SSL support. (If you are using an official Node.js distribution, SSL
|
|
48
|
+
To use ESLint, you must have [Node.js](https://nodejs.org/) (`^20.19.0`, `^22.13.0`, or `>=24`) installed and built with SSL and ICU support. (If you are using an official Node.js distribution, both SSL and ICU are always built in.)
|
|
49
49
|
|
|
50
50
|
If you use ESLint's TypeScript type definitions, TypeScript 5.3 or later is required.
|
|
51
51
|
|
|
@@ -320,8 +320,8 @@ lumir
|
|
|
320
320
|
</a>
|
|
321
321
|
</td><td align="center" valign="top" width="11%">
|
|
322
322
|
<a href="https://github.com/Pixel998">
|
|
323
|
-
<img src="https://github.com/Pixel998.png?s=75" width="75" height="75" alt="
|
|
324
|
-
|
|
323
|
+
<img src="https://github.com/Pixel998.png?s=75" width="75" height="75" alt="Pixel's Avatar"><br />
|
|
324
|
+
Pixel
|
|
325
325
|
</a>
|
|
326
326
|
</td></tr></tbody></table>
|
|
327
327
|
|
|
@@ -358,9 +358,9 @@ to get your logo on our READMEs and [website](https://eslint.org/sponsors).
|
|
|
358
358
|
|
|
359
359
|
<h3>Platinum Sponsors</h3>
|
|
360
360
|
<p><a href="https://automattic.com"><img src="https://images.opencollective.com/automattic/d0ef3e1/logo.png" alt="Automattic" height="128"></a></p><h3>Gold Sponsors</h3>
|
|
361
|
-
<p><a href="https://qlty.sh/"><img src="https://images.opencollective.com/qltysh/33d157d/logo.png" alt="Qlty Software" height="96"></a></p><h3>Silver Sponsors</h3>
|
|
361
|
+
<p><a href="https://qlty.sh/"><img src="https://images.opencollective.com/qltysh/33d157d/logo.png" alt="Qlty Software" height="96"></a> <a href="https://shopify.engineering/"><img src="https://avatars.githubusercontent.com/u/8085" alt="Shopify" height="96"></a> <a href="https://www.coderabbit.ai/?utm_source=cr_org&utm_medium=github"><img src="https://avatars.githubusercontent.com/u/132028505" alt="CodeRabbit" height="96"></a></p><h3>Silver Sponsors</h3>
|
|
362
362
|
<p><a href="https://vite.dev/"><img src="https://images.opencollective.com/vite/d472863/logo.png" alt="Vite" height="64"></a> <a href="https://liftoff.io/"><img src="https://images.opencollective.com/liftoff/2d6c3b6/logo.png" alt="Liftoff" height="64"></a> <a href="https://stackblitz.com"><img src="https://avatars.githubusercontent.com/u/28635252" alt="StackBlitz" height="64"></a></p><h3>Bronze Sponsors</h3>
|
|
363
|
-
<p><a href="https://cybozu.co.jp/"><img src="https://images.opencollective.com/cybozu/933e46d/logo.png" alt="Cybozu" height="32"></a> <a href="https://opensource.sap.com"><img src="https://avatars.githubusercontent.com/u/2531208" alt="SAP" height="32"></a> <a href="https://www.crawljobs.com/"><img src="https://images.opencollective.com/crawljobs-poland/fa43a17/logo.png" alt="CrawlJobs" height="32"></a> <a href="https://syntax.fm"><img src="https://github.com/syntaxfm.png" alt="Syntax" height="32"></a> <a href="https://depot.dev"><img src="https://images.opencollective.com/depot/39125a1/logo.png" alt="Depot" 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.gitbook.com"><img src="https://avatars.githubusercontent.com/u/7111340" alt="GitBook" height="32"></a> <a href="https://
|
|
363
|
+
<p><a href="https://cybozu.co.jp/"><img src="https://images.opencollective.com/cybozu/933e46d/logo.png" alt="Cybozu" height="32"></a> <a href="https://opensource.sap.com"><img src="https://avatars.githubusercontent.com/u/2531208" alt="SAP" height="32"></a> <a href="https://www.crawljobs.com/"><img src="https://images.opencollective.com/crawljobs-poland/fa43a17/logo.png" alt="CrawlJobs" height="32"></a> <a href="https://syntax.fm"><img src="https://github.com/syntaxfm.png" alt="Syntax" height="32"></a> <a href="https://depot.dev"><img src="https://images.opencollective.com/depot/39125a1/logo.png" alt="Depot" 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.gitbook.com"><img src="https://avatars.githubusercontent.com/u/7111340" alt="GitBook" height="32"></a> <a href="https://citadel.co.jp"><img src="https://avatars.githubusercontent.com/u/75781367" alt="Citadel AI" height="32"></a></p>
|
|
364
364
|
<h3>Technology Sponsors</h3>
|
|
365
365
|
Technology sponsors allow us to use their products and services for free as part of a contribution to the open source ecosystem and our work.
|
|
366
366
|
<p><a href="https://netlify.com"><img src="https://raw.githubusercontent.com/eslint/eslint.org/main/src/assets/images/techsponsors/netlify-icon.svg" alt="Netlify" height="32"></a> <a href="https://algolia.com"><img src="https://raw.githubusercontent.com/eslint/eslint.org/main/src/assets/images/techsponsors/algolia-icon.svg" alt="Algolia" height="32"></a> <a href="https://1password.com"><img src="https://raw.githubusercontent.com/eslint/eslint.org/main/src/assets/images/techsponsors/1password-icon.svg" alt="1Password" height="32"></a></p>
|
|
@@ -191,8 +191,8 @@ function pluralize(word, count) {
|
|
|
191
191
|
|
|
192
192
|
/**
|
|
193
193
|
* Renders text along the template of x problems (x errors, x warnings)
|
|
194
|
-
* @param {
|
|
195
|
-
* @param {
|
|
194
|
+
* @param {number} totalErrors Total errors
|
|
195
|
+
* @param {number} totalWarnings Total warnings
|
|
196
196
|
* @returns {string} The formatted string, pluralized where necessary
|
|
197
197
|
*/
|
|
198
198
|
function renderSummary(totalErrors, totalWarnings) {
|
|
@@ -207,8 +207,8 @@ function renderSummary(totalErrors, totalWarnings) {
|
|
|
207
207
|
|
|
208
208
|
/**
|
|
209
209
|
* Get the color based on whether there are errors/warnings...
|
|
210
|
-
* @param {
|
|
211
|
-
* @param {
|
|
210
|
+
* @param {number} totalErrors Total errors
|
|
211
|
+
* @param {number} totalWarnings Total warnings
|
|
212
212
|
* @returns {number} The color code (0 = green, 1 = yellow, 2 = red)
|
|
213
213
|
*/
|
|
214
214
|
function renderColor(totalErrors, totalWarnings) {
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
//------------------------------------------------------------------------------
|
|
11
11
|
|
|
12
12
|
const astUtils = require("./utils/ast-utils");
|
|
13
|
+
const { isAnySegmentReachable } = require("./utils/code-path-utils");
|
|
13
14
|
|
|
14
15
|
//------------------------------------------------------------------------------
|
|
15
16
|
// Helpers
|
|
@@ -30,21 +31,6 @@ function isTargetMethod(node) {
|
|
|
30
31
|
return astUtils.isSpecificMemberAccess(node, null, TARGET_METHODS);
|
|
31
32
|
}
|
|
32
33
|
|
|
33
|
-
/**
|
|
34
|
-
* Checks all segments in a set and returns true if any are reachable.
|
|
35
|
-
* @param {Set<CodePathSegment>} segments The segments to check.
|
|
36
|
-
* @returns {boolean} True if any segment is reachable; false otherwise.
|
|
37
|
-
*/
|
|
38
|
-
function isAnySegmentReachable(segments) {
|
|
39
|
-
for (const segment of segments) {
|
|
40
|
-
if (segment.reachable) {
|
|
41
|
-
return true;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
return false;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
34
|
/**
|
|
49
35
|
* Returns a human-legible description of an array method
|
|
50
36
|
* @param {string} arrayMethodName A method name to fully qualify
|
|
@@ -10,26 +10,12 @@
|
|
|
10
10
|
|
|
11
11
|
const astUtils = require("./utils/ast-utils");
|
|
12
12
|
const { upperCaseFirst } = require("../shared/string-utils");
|
|
13
|
+
const { isAnySegmentReachable } = require("./utils/code-path-utils");
|
|
13
14
|
|
|
14
15
|
//------------------------------------------------------------------------------
|
|
15
16
|
// Helpers
|
|
16
17
|
//------------------------------------------------------------------------------
|
|
17
18
|
|
|
18
|
-
/**
|
|
19
|
-
* Checks all segments in a set and returns true if all are unreachable.
|
|
20
|
-
* @param {Set<CodePathSegment>} segments The segments to check.
|
|
21
|
-
* @returns {boolean} True if all segments are unreachable; false otherwise.
|
|
22
|
-
*/
|
|
23
|
-
function areAllSegmentsUnreachable(segments) {
|
|
24
|
-
for (const segment of segments) {
|
|
25
|
-
if (segment.reachable) {
|
|
26
|
-
return false;
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
return true;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
19
|
/**
|
|
34
20
|
* Checks whether a given node is a `constructor` method in an ES6 class
|
|
35
21
|
* @param {ASTNode} node A node to check
|
|
@@ -100,7 +86,7 @@ module.exports = {
|
|
|
100
86
|
*/
|
|
101
87
|
if (
|
|
102
88
|
!funcInfo.hasReturnValue ||
|
|
103
|
-
|
|
89
|
+
!isAnySegmentReachable(funcInfo.currentSegments) ||
|
|
104
90
|
astUtils.isES5Constructor(node) ||
|
|
105
91
|
isClassConstructor(node)
|
|
106
92
|
) {
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
//------------------------------------------------------------------------------
|
|
11
11
|
|
|
12
12
|
const astUtils = require("./utils/ast-utils");
|
|
13
|
+
const { isAnySegmentReachable } = require("./utils/code-path-utils");
|
|
13
14
|
|
|
14
15
|
//------------------------------------------------------------------------------
|
|
15
16
|
// Helpers
|
|
@@ -17,21 +18,6 @@ const astUtils = require("./utils/ast-utils");
|
|
|
17
18
|
|
|
18
19
|
const TARGET_NODE_TYPE = /^(?:Arrow)?FunctionExpression$/u;
|
|
19
20
|
|
|
20
|
-
/**
|
|
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.
|
|
24
|
-
*/
|
|
25
|
-
function isAnySegmentReachable(segments) {
|
|
26
|
-
for (const segment of segments) {
|
|
27
|
-
if (segment.reachable) {
|
|
28
|
-
return true;
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
return false;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
21
|
//------------------------------------------------------------------------------
|
|
36
22
|
// Rule Definition
|
|
37
23
|
//------------------------------------------------------------------------------
|
package/lib/rules/max-depth.js
CHANGED
|
@@ -54,6 +54,8 @@ module.exports = {
|
|
|
54
54
|
},
|
|
55
55
|
|
|
56
56
|
create(context) {
|
|
57
|
+
const sourceCode = context.sourceCode;
|
|
58
|
+
|
|
57
59
|
//--------------------------------------------------------------------------
|
|
58
60
|
// Helpers
|
|
59
61
|
//--------------------------------------------------------------------------
|
|
@@ -102,6 +104,7 @@ module.exports = {
|
|
|
102
104
|
if (len > maxDepth) {
|
|
103
105
|
context.report({
|
|
104
106
|
node,
|
|
107
|
+
loc: sourceCode.getFirstToken(node).loc,
|
|
105
108
|
messageId: "tooDeeply",
|
|
106
109
|
data: { depth: len, maxDepth },
|
|
107
110
|
});
|
|
@@ -117,6 +120,18 @@ module.exports = {
|
|
|
117
120
|
functionStack[functionStack.length - 1]--;
|
|
118
121
|
}
|
|
119
122
|
|
|
123
|
+
/**
|
|
124
|
+
* Checks whether a node is an else-if statement.
|
|
125
|
+
* @param {ASTNode} node node to evaluate
|
|
126
|
+
* @returns {boolean} Whether the node is an else-if statement
|
|
127
|
+
*/
|
|
128
|
+
function isElseIf(node) {
|
|
129
|
+
return (
|
|
130
|
+
node.parent.type === "IfStatement" &&
|
|
131
|
+
node.parent.alternate === node
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
|
|
120
135
|
//--------------------------------------------------------------------------
|
|
121
136
|
// Public API
|
|
122
137
|
//--------------------------------------------------------------------------
|
|
@@ -129,7 +144,7 @@ module.exports = {
|
|
|
129
144
|
StaticBlock: startFunction,
|
|
130
145
|
|
|
131
146
|
IfStatement(node) {
|
|
132
|
-
if (node
|
|
147
|
+
if (!isElseIf(node)) {
|
|
133
148
|
pushBlock(node);
|
|
134
149
|
}
|
|
135
150
|
},
|
|
@@ -142,7 +157,11 @@ module.exports = {
|
|
|
142
157
|
ForInStatement: pushBlock,
|
|
143
158
|
ForOfStatement: pushBlock,
|
|
144
159
|
|
|
145
|
-
"IfStatement:exit"
|
|
160
|
+
"IfStatement:exit"(node) {
|
|
161
|
+
if (!isElseIf(node)) {
|
|
162
|
+
popBlock();
|
|
163
|
+
}
|
|
164
|
+
},
|
|
146
165
|
"SwitchStatement:exit": popBlock,
|
|
147
166
|
"TryStatement:exit": popBlock,
|
|
148
167
|
"DoWhileStatement:exit": popBlock,
|
|
@@ -5,6 +5,12 @@
|
|
|
5
5
|
|
|
6
6
|
"use strict";
|
|
7
7
|
|
|
8
|
+
//------------------------------------------------------------------------------
|
|
9
|
+
// Requirements
|
|
10
|
+
//------------------------------------------------------------------------------
|
|
11
|
+
|
|
12
|
+
const astUtils = require("./utils/ast-utils");
|
|
13
|
+
|
|
8
14
|
//------------------------------------------------------------------------------
|
|
9
15
|
// Rule Definition
|
|
10
16
|
//------------------------------------------------------------------------------
|
|
@@ -53,6 +59,8 @@ module.exports = {
|
|
|
53
59
|
},
|
|
54
60
|
|
|
55
61
|
create(context) {
|
|
62
|
+
const sourceCode = context.sourceCode;
|
|
63
|
+
|
|
56
64
|
//--------------------------------------------------------------------------
|
|
57
65
|
// Constants
|
|
58
66
|
//--------------------------------------------------------------------------
|
|
@@ -83,24 +91,34 @@ module.exports = {
|
|
|
83
91
|
function checkFunction(node) {
|
|
84
92
|
const parent = node.parent;
|
|
85
93
|
|
|
86
|
-
if (parent.type
|
|
87
|
-
|
|
94
|
+
if (parent.type !== "CallExpression" || parent.callee === node) {
|
|
95
|
+
return;
|
|
88
96
|
}
|
|
89
97
|
|
|
98
|
+
callbackStack.push(node);
|
|
99
|
+
|
|
90
100
|
if (callbackStack.length > THRESHOLD) {
|
|
91
101
|
const opts = { num: callbackStack.length, max: THRESHOLD };
|
|
92
102
|
|
|
93
|
-
context.report({
|
|
103
|
+
context.report({
|
|
104
|
+
node,
|
|
105
|
+
loc: astUtils.getFunctionHeadLoc(node, sourceCode),
|
|
106
|
+
messageId: "exceed",
|
|
107
|
+
data: opts,
|
|
108
|
+
});
|
|
94
109
|
}
|
|
95
110
|
}
|
|
96
111
|
|
|
97
112
|
/**
|
|
98
113
|
* Pops the call stack.
|
|
114
|
+
* @param {ASTNode} node The node to check.
|
|
99
115
|
* @returns {void}
|
|
100
116
|
* @private
|
|
101
117
|
*/
|
|
102
|
-
function popStack() {
|
|
103
|
-
callbackStack.
|
|
118
|
+
function popStack(node) {
|
|
119
|
+
if (callbackStack.at(-1) === node) {
|
|
120
|
+
callbackStack.pop();
|
|
121
|
+
}
|
|
104
122
|
}
|
|
105
123
|
|
|
106
124
|
//--------------------------------------------------------------------------
|
|
@@ -28,6 +28,8 @@ const NUMERIC_OR_STRING_BINARY_OPERATORS = new Set([
|
|
|
28
28
|
">>>",
|
|
29
29
|
]);
|
|
30
30
|
|
|
31
|
+
const RELATIONAL_OPERATORS = new Set(["<", "<=", ">", ">="]);
|
|
32
|
+
|
|
31
33
|
//------------------------------------------------------------------------------
|
|
32
34
|
// Helpers
|
|
33
35
|
//------------------------------------------------------------------------------
|
|
@@ -87,7 +89,9 @@ function hasConstantNullishness(scope, node, nonNullish) {
|
|
|
87
89
|
return (
|
|
88
90
|
(functionName === "Boolean" ||
|
|
89
91
|
functionName === "String" ||
|
|
90
|
-
functionName === "Number"
|
|
92
|
+
functionName === "Number" ||
|
|
93
|
+
functionName === "Symbol" ||
|
|
94
|
+
functionName === "BigInt") &&
|
|
91
95
|
isReferenceToGlobalVariable(scope, node.callee)
|
|
92
96
|
);
|
|
93
97
|
}
|
|
@@ -372,7 +376,10 @@ function hasConstantStrictBooleanComparison(scope, node) {
|
|
|
372
376
|
const functionName = node.callee.name;
|
|
373
377
|
|
|
374
378
|
if (
|
|
375
|
-
(functionName === "String" ||
|
|
379
|
+
(functionName === "String" ||
|
|
380
|
+
functionName === "Number" ||
|
|
381
|
+
functionName === "BigInt" ||
|
|
382
|
+
functionName === "Symbol") &&
|
|
376
383
|
isReferenceToGlobalVariable(scope, node.callee)
|
|
377
384
|
) {
|
|
378
385
|
return true;
|
|
@@ -485,6 +492,33 @@ function findBinaryExpressionConstantOperand(scope, a, b, operator) {
|
|
|
485
492
|
return null;
|
|
486
493
|
}
|
|
487
494
|
|
|
495
|
+
/**
|
|
496
|
+
* Checks if a node is a statically knowable literal.
|
|
497
|
+
* @param {Scope} scope The scope in which the node was found.
|
|
498
|
+
* @param {ASTNode} node The node to test.
|
|
499
|
+
* @returns {boolean} `true` if the node is a literal.
|
|
500
|
+
*/
|
|
501
|
+
function isStaticLiteral(scope, node) {
|
|
502
|
+
switch (node.type) {
|
|
503
|
+
case "Literal":
|
|
504
|
+
return true;
|
|
505
|
+
case "UnaryExpression":
|
|
506
|
+
return (
|
|
507
|
+
["-", "+", "~"].includes(node.operator) &&
|
|
508
|
+
node.argument.type === "Literal"
|
|
509
|
+
);
|
|
510
|
+
case "Identifier":
|
|
511
|
+
return (
|
|
512
|
+
node.name === "undefined" &&
|
|
513
|
+
isReferenceToGlobalVariable(scope, node)
|
|
514
|
+
);
|
|
515
|
+
case "TemplateLiteral":
|
|
516
|
+
return node.expressions.length === 0;
|
|
517
|
+
default:
|
|
518
|
+
return false;
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
|
|
488
522
|
//------------------------------------------------------------------------------
|
|
489
523
|
// Rule Definition
|
|
490
524
|
//------------------------------------------------------------------------------
|
|
@@ -499,7 +533,22 @@ module.exports = {
|
|
|
499
533
|
recommended: true,
|
|
500
534
|
url: "https://eslint.org/docs/latest/rules/no-constant-binary-expression",
|
|
501
535
|
},
|
|
502
|
-
|
|
536
|
+
defaultOptions: [
|
|
537
|
+
{
|
|
538
|
+
checkRelationalComparisons: false,
|
|
539
|
+
},
|
|
540
|
+
],
|
|
541
|
+
schema: [
|
|
542
|
+
{
|
|
543
|
+
type: "object",
|
|
544
|
+
properties: {
|
|
545
|
+
checkRelationalComparisons: {
|
|
546
|
+
type: "boolean",
|
|
547
|
+
},
|
|
548
|
+
},
|
|
549
|
+
additionalProperties: false,
|
|
550
|
+
},
|
|
551
|
+
],
|
|
503
552
|
messages: {
|
|
504
553
|
constantBinaryOperand:
|
|
505
554
|
"Unexpected constant binary expression. Compares constantly with the {{otherSide}}-hand side of the `{{operator}}`.",
|
|
@@ -509,11 +558,14 @@ module.exports = {
|
|
|
509
558
|
"Unexpected comparison to newly constructed object. These two values can never be equal.",
|
|
510
559
|
bothAlwaysNew:
|
|
511
560
|
"Unexpected comparison of two newly constructed objects. These two values can never be equal.",
|
|
561
|
+
constantRelationalComparison:
|
|
562
|
+
"Unexpected constant relational comparison. Both sides of the `{{operator}}` are literal values.",
|
|
512
563
|
},
|
|
513
564
|
},
|
|
514
565
|
|
|
515
566
|
create(context) {
|
|
516
567
|
const sourceCode = context.sourceCode;
|
|
568
|
+
const { checkRelationalComparisons } = context.options[0];
|
|
517
569
|
|
|
518
570
|
return {
|
|
519
571
|
LogicalExpression(node) {
|
|
@@ -587,6 +639,20 @@ module.exports = {
|
|
|
587
639
|
messageId: "bothAlwaysNew",
|
|
588
640
|
});
|
|
589
641
|
}
|
|
642
|
+
} else if (
|
|
643
|
+
checkRelationalComparisons &&
|
|
644
|
+
RELATIONAL_OPERATORS.has(operator)
|
|
645
|
+
) {
|
|
646
|
+
if (
|
|
647
|
+
isStaticLiteral(scope, left) &&
|
|
648
|
+
isStaticLiteral(scope, right)
|
|
649
|
+
) {
|
|
650
|
+
context.report({
|
|
651
|
+
node,
|
|
652
|
+
messageId: "constantRelationalComparison",
|
|
653
|
+
data: { operator },
|
|
654
|
+
});
|
|
655
|
+
}
|
|
590
656
|
}
|
|
591
657
|
},
|
|
592
658
|
|
|
@@ -91,7 +91,8 @@ module.exports = {
|
|
|
91
91
|
(node.type === "CallExpression" ||
|
|
92
92
|
node.type === "NewExpression") &&
|
|
93
93
|
node.callee.type === "Identifier" &&
|
|
94
|
-
node.callee.name === "Boolean"
|
|
94
|
+
node.callee.name === "Boolean" &&
|
|
95
|
+
sourceCode.isGlobalReference(node.callee)
|
|
95
96
|
);
|
|
96
97
|
}
|
|
97
98
|
|
|
@@ -323,7 +324,8 @@ module.exports = {
|
|
|
323
324
|
CallExpression(node) {
|
|
324
325
|
if (
|
|
325
326
|
node.callee.type !== "Identifier" ||
|
|
326
|
-
node.callee.name !== "Boolean"
|
|
327
|
+
node.callee.name !== "Boolean" ||
|
|
328
|
+
!sourceCode.isGlobalReference(node.callee)
|
|
327
329
|
) {
|
|
328
330
|
return;
|
|
329
331
|
}
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
//------------------------------------------------------------------------------
|
|
10
10
|
|
|
11
11
|
const { directivesPattern } = require("../shared/directives");
|
|
12
|
+
const { isAnySegmentReachable } = require("./utils/code-path-utils");
|
|
12
13
|
|
|
13
14
|
//------------------------------------------------------------------------------
|
|
14
15
|
// Helpers
|
|
@@ -16,21 +17,6 @@ const { directivesPattern } = require("../shared/directives");
|
|
|
16
17
|
|
|
17
18
|
const DEFAULT_FALLTHROUGH_COMMENT = /falls?\s?through/iu;
|
|
18
19
|
|
|
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
|
-
for (const segment of segments) {
|
|
26
|
-
if (segment.reachable) {
|
|
27
|
-
return true;
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
return false;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
20
|
/**
|
|
35
21
|
* Checks whether or not a given comment string is really a fallthrough comment and not an ESLint directive.
|
|
36
22
|
* @param {string} comment The comment string to check.
|
|
@@ -199,10 +199,11 @@ module.exports = {
|
|
|
199
199
|
});
|
|
200
200
|
}
|
|
201
201
|
|
|
202
|
-
// Do not suggest wrapping an unnamed
|
|
202
|
+
// Do not suggest wrapping an unnamed function or class expression in braces as that would be invalid syntax.
|
|
203
203
|
if (
|
|
204
204
|
!(
|
|
205
|
-
node.body.type === "FunctionExpression"
|
|
205
|
+
(node.body.type === "FunctionExpression" ||
|
|
206
|
+
node.body.type === "ClassExpression") &&
|
|
206
207
|
!node.body.id
|
|
207
208
|
)
|
|
208
209
|
) {
|
|
@@ -31,12 +31,17 @@ module.exports = {
|
|
|
31
31
|
},
|
|
32
32
|
|
|
33
33
|
create(context) {
|
|
34
|
+
const sourceCode = context.sourceCode;
|
|
35
|
+
|
|
34
36
|
return {
|
|
35
37
|
ThrowStatement(node) {
|
|
36
38
|
if (!astUtils.couldBeError(node.argument)) {
|
|
37
39
|
context.report({ node, messageId: "object" });
|
|
38
40
|
} else if (node.argument.type === "Identifier") {
|
|
39
|
-
if (
|
|
41
|
+
if (
|
|
42
|
+
node.argument.name === "undefined" &&
|
|
43
|
+
sourceCode.isGlobalReference(node.argument)
|
|
44
|
+
) {
|
|
40
45
|
context.report({ node, messageId: "undef" });
|
|
41
46
|
}
|
|
42
47
|
}
|
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
|
|
6
6
|
"use strict";
|
|
7
7
|
|
|
8
|
+
const { isAnySegmentReachable } = require("./utils/code-path-utils");
|
|
9
|
+
|
|
8
10
|
//------------------------------------------------------------------------------
|
|
9
11
|
// Helpers
|
|
10
12
|
//------------------------------------------------------------------------------
|
|
@@ -17,21 +19,6 @@ const allLoopTypes = [
|
|
|
17
19
|
"ForOfStatement",
|
|
18
20
|
];
|
|
19
21
|
|
|
20
|
-
/**
|
|
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.
|
|
24
|
-
*/
|
|
25
|
-
function isAnySegmentReachable(segments) {
|
|
26
|
-
for (const segment of segments) {
|
|
27
|
-
if (segment.reachable) {
|
|
28
|
-
return true;
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
return false;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
22
|
/**
|
|
36
23
|
* Determines whether the given node is the first node in the code path to which a loop statement
|
|
37
24
|
* 'loops' for the next iteration.
|
|
@@ -4,6 +4,12 @@
|
|
|
4
4
|
*/
|
|
5
5
|
"use strict";
|
|
6
6
|
|
|
7
|
+
//------------------------------------------------------------------------------
|
|
8
|
+
// Requirements
|
|
9
|
+
//------------------------------------------------------------------------------
|
|
10
|
+
|
|
11
|
+
const { isAnySegmentReachable } = require("./utils/code-path-utils");
|
|
12
|
+
|
|
7
13
|
//------------------------------------------------------------------------------
|
|
8
14
|
// Helpers
|
|
9
15
|
//------------------------------------------------------------------------------
|
|
@@ -23,21 +29,6 @@ function isInitialized(node) {
|
|
|
23
29
|
return Boolean(node.init);
|
|
24
30
|
}
|
|
25
31
|
|
|
26
|
-
/**
|
|
27
|
-
* Checks all segments in a set and returns true if all are unreachable.
|
|
28
|
-
* @param {Set<CodePathSegment>} segments The segments to check.
|
|
29
|
-
* @returns {boolean} True if all segments are unreachable; false otherwise.
|
|
30
|
-
*/
|
|
31
|
-
function areAllSegmentsUnreachable(segments) {
|
|
32
|
-
for (const segment of segments) {
|
|
33
|
-
if (segment.reachable) {
|
|
34
|
-
return false;
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
return true;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
32
|
/**
|
|
42
33
|
* The class to distinguish consecutive unreachable statements.
|
|
43
34
|
*/
|
|
@@ -154,7 +145,7 @@ module.exports = {
|
|
|
154
145
|
if (
|
|
155
146
|
node &&
|
|
156
147
|
(node.type === "PropertyDefinition" ||
|
|
157
|
-
|
|
148
|
+
!isAnySegmentReachable(currentCodePathSegments))
|
|
158
149
|
) {
|
|
159
150
|
// Store this statement to distinguish consecutive statements.
|
|
160
151
|
if (range.isEmpty) {
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
const astUtils = require("./utils/ast-utils"),
|
|
12
12
|
FixTracker = require("./utils/fix-tracker");
|
|
13
|
+
const { isAnySegmentReachable } = require("./utils/code-path-utils");
|
|
13
14
|
|
|
14
15
|
//------------------------------------------------------------------------------
|
|
15
16
|
// Helpers
|
|
@@ -60,21 +61,6 @@ function isInFinally(node) {
|
|
|
60
61
|
return false;
|
|
61
62
|
}
|
|
62
63
|
|
|
63
|
-
/**
|
|
64
|
-
* Checks all segments in a set and returns true if any are reachable.
|
|
65
|
-
* @param {Set<CodePathSegment>} segments The segments to check.
|
|
66
|
-
* @returns {boolean} True if any segment is reachable; false otherwise.
|
|
67
|
-
*/
|
|
68
|
-
function isAnySegmentReachable(segments) {
|
|
69
|
-
for (const segment of segments) {
|
|
70
|
-
if (segment.reachable) {
|
|
71
|
-
return true;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
return false;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
64
|
//------------------------------------------------------------------------------
|
|
79
65
|
// Rule Definition
|
|
80
66
|
//------------------------------------------------------------------------------
|
package/lib/rules/no-with.js
CHANGED
|
@@ -28,9 +28,15 @@ module.exports = {
|
|
|
28
28
|
},
|
|
29
29
|
|
|
30
30
|
create(context) {
|
|
31
|
+
const sourceCode = context.sourceCode;
|
|
32
|
+
|
|
31
33
|
return {
|
|
32
34
|
WithStatement(node) {
|
|
33
|
-
context.report({
|
|
35
|
+
context.report({
|
|
36
|
+
node,
|
|
37
|
+
loc: sourceCode.getFirstToken(node).loc,
|
|
38
|
+
messageId: "unexpectedWith",
|
|
39
|
+
});
|
|
34
40
|
},
|
|
35
41
|
};
|
|
36
42
|
},
|
|
@@ -21,6 +21,11 @@ const PRECEDENCE_OF_EXPONENTIATION_EXPR = astUtils.getPrecedence({
|
|
|
21
21
|
operator: "**",
|
|
22
22
|
});
|
|
23
23
|
|
|
24
|
+
/*
|
|
25
|
+
* Characters that can cause continuation without preceding semi
|
|
26
|
+
*/
|
|
27
|
+
const continuationChars = new Set(["(", "[", "/", "`"]);
|
|
28
|
+
|
|
24
29
|
/**
|
|
25
30
|
* Determines whether the given node needs parens if used as the base in an exponentiation binary expression.
|
|
26
31
|
* @param {ASTNode} base The node to check.
|
|
@@ -149,11 +154,36 @@ module.exports = {
|
|
|
149
154
|
shouldParenthesizeBase = doesBaseNeedParens(base),
|
|
150
155
|
shouldParenthesizeExponent =
|
|
151
156
|
doesExponentNeedParens(exponent),
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
+
isStartOfExpressionStatement =
|
|
158
|
+
astUtils.isStartOfExpressionStatement(node);
|
|
159
|
+
|
|
160
|
+
let shouldParenthesizeAll =
|
|
161
|
+
doesExponentiationExpressionNeedParens(
|
|
162
|
+
node,
|
|
163
|
+
sourceCode,
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
/*
|
|
167
|
+
* `function`, `class`, or `{` token at the start of an expression statement
|
|
168
|
+
* would cause incorrect parsing.
|
|
169
|
+
* https://github.com/eslint/eslint/issues/20987
|
|
170
|
+
*/
|
|
171
|
+
if (
|
|
172
|
+
!shouldParenthesizeAll &&
|
|
173
|
+
!shouldParenthesizeBase &&
|
|
174
|
+
isStartOfExpressionStatement
|
|
175
|
+
) {
|
|
176
|
+
const firstTokenOfBase = sourceCode.getFirstToken(base);
|
|
177
|
+
|
|
178
|
+
if (
|
|
179
|
+
astUtils.isOpeningBraceToken(firstTokenOfBase) ||
|
|
180
|
+
(firstTokenOfBase.type === "Keyword" &&
|
|
181
|
+
(firstTokenOfBase.value === "function" ||
|
|
182
|
+
firstTokenOfBase.value === "class"))
|
|
183
|
+
) {
|
|
184
|
+
shouldParenthesizeAll = true;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
157
187
|
|
|
158
188
|
let prefix = "",
|
|
159
189
|
suffix = "";
|
|
@@ -206,6 +236,15 @@ module.exports = {
|
|
|
206
236
|
shouldParenthesizeAll,
|
|
207
237
|
);
|
|
208
238
|
|
|
239
|
+
if (
|
|
240
|
+
!prefix &&
|
|
241
|
+
isStartOfExpressionStatement &&
|
|
242
|
+
continuationChars.has(replacement[0]) &&
|
|
243
|
+
astUtils.needsPrecedingSemicolon(sourceCode, node)
|
|
244
|
+
) {
|
|
245
|
+
prefix = ";";
|
|
246
|
+
}
|
|
247
|
+
|
|
209
248
|
return fixer.replaceText(
|
|
210
249
|
node,
|
|
211
250
|
`${prefix}${replacement}${suffix}`,
|
|
@@ -63,11 +63,15 @@ module.exports = {
|
|
|
63
63
|
if (!callExpression.arguments.length && allowEmptyReject) {
|
|
64
64
|
return;
|
|
65
65
|
}
|
|
66
|
+
|
|
67
|
+
const rejectionReason = callExpression.arguments[0];
|
|
68
|
+
|
|
66
69
|
if (
|
|
67
70
|
!callExpression.arguments.length ||
|
|
68
|
-
!astUtils.couldBeError(
|
|
69
|
-
(
|
|
70
|
-
|
|
71
|
+
!astUtils.couldBeError(rejectionReason) ||
|
|
72
|
+
(rejectionReason.type === "Identifier" &&
|
|
73
|
+
rejectionReason.name === "undefined" &&
|
|
74
|
+
sourceCode.isGlobalReference(rejectionReason))
|
|
71
75
|
) {
|
|
72
76
|
context.report({
|
|
73
77
|
node: callExpression,
|
|
@@ -82,10 +86,15 @@ module.exports = {
|
|
|
82
86
|
* @returns {boolean} `true` if the call is a Promise.reject() call
|
|
83
87
|
*/
|
|
84
88
|
function isPromiseRejectCall(node) {
|
|
85
|
-
return
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
+
return (
|
|
90
|
+
astUtils.isSpecificMemberAccess(
|
|
91
|
+
node.callee,
|
|
92
|
+
"Promise",
|
|
93
|
+
"reject",
|
|
94
|
+
) &&
|
|
95
|
+
sourceCode.isGlobalReference(
|
|
96
|
+
astUtils.skipChainExpression(node.callee).object,
|
|
97
|
+
)
|
|
89
98
|
);
|
|
90
99
|
}
|
|
91
100
|
|
|
@@ -110,6 +119,7 @@ module.exports = {
|
|
|
110
119
|
if (
|
|
111
120
|
node.callee.type === "Identifier" &&
|
|
112
121
|
node.callee.name === "Promise" &&
|
|
122
|
+
sourceCode.isGlobalReference(node.callee) &&
|
|
113
123
|
node.arguments.length &&
|
|
114
124
|
astUtils.isFunction(node.arguments[0]) &&
|
|
115
125
|
node.arguments[0].params.length > 1 &&
|
package/lib/rules/radix.js
CHANGED
|
@@ -57,12 +57,15 @@ function isParseIntMethod(node) {
|
|
|
57
57
|
* - A literal except integers between 2 and 36.
|
|
58
58
|
* - undefined.
|
|
59
59
|
* @param {ASTNode} radix A node of radix to check.
|
|
60
|
+
* @param {SourceCode} sourceCode The source code object.
|
|
60
61
|
* @returns {boolean} `true` if the node is valid.
|
|
61
62
|
*/
|
|
62
|
-
function isValidRadix(radix) {
|
|
63
|
+
function isValidRadix(radix, sourceCode) {
|
|
63
64
|
return !(
|
|
64
65
|
(radix.type === "Literal" && !validRadixValues.has(radix.value)) ||
|
|
65
|
-
(radix.type === "Identifier" &&
|
|
66
|
+
(radix.type === "Identifier" &&
|
|
67
|
+
radix.name === "undefined" &&
|
|
68
|
+
sourceCode.isGlobalReference(radix))
|
|
66
69
|
);
|
|
67
70
|
}
|
|
68
71
|
|
|
@@ -148,7 +151,7 @@ module.exports = {
|
|
|
148
151
|
break;
|
|
149
152
|
|
|
150
153
|
default:
|
|
151
|
-
if (!isValidRadix(args[1])) {
|
|
154
|
+
if (!isValidRadix(args[1], sourceCode)) {
|
|
152
155
|
context.report({
|
|
153
156
|
node,
|
|
154
157
|
messageId: "invalidRadix",
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Code path related utilities.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
"use strict";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Checks all segments in a set and returns true if any are reachable.
|
|
9
|
+
* @param {Set<CodePathSegment>} segments The segments to check.
|
|
10
|
+
* @returns {boolean} `true` if any segment is reachable; `false` otherwise.
|
|
11
|
+
*/
|
|
12
|
+
function isAnySegmentReachable(segments) {
|
|
13
|
+
for (const segment of segments) {
|
|
14
|
+
if (segment.reachable) {
|
|
15
|
+
return true;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
module.exports = { isAnySegmentReachable };
|
package/lib/types/rules.d.ts
CHANGED
|
@@ -2228,7 +2228,16 @@ export interface ESLintRules extends Linter.RulesRecord {
|
|
|
2228
2228
|
* @since 8.14.0
|
|
2229
2229
|
* @see https://eslint.org/docs/latest/rules/no-constant-binary-expression
|
|
2230
2230
|
*/
|
|
2231
|
-
"no-constant-binary-expression": Linter.RuleEntry<
|
|
2231
|
+
"no-constant-binary-expression": Linter.RuleEntry<
|
|
2232
|
+
[
|
|
2233
|
+
Partial<{
|
|
2234
|
+
/**
|
|
2235
|
+
* @default false
|
|
2236
|
+
*/
|
|
2237
|
+
checkRelationalComparisons: boolean;
|
|
2238
|
+
}>,
|
|
2239
|
+
]
|
|
2240
|
+
>;
|
|
2232
2241
|
|
|
2233
2242
|
/**
|
|
2234
2243
|
* Rule to disallow constant expressions in conditions.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint",
|
|
3
|
-
"version": "10.
|
|
3
|
+
"version": "10.6.0",
|
|
4
4
|
"author": "Nicholas C. Zakas <nicholas+npm@nczconsulting.com>",
|
|
5
5
|
"description": "An AST-based pattern checker for JavaScript.",
|
|
6
6
|
"type": "commonjs",
|
|
@@ -78,8 +78,15 @@
|
|
|
78
78
|
"test:fuzz": "node Makefile.js fuzz",
|
|
79
79
|
"test:performance": "node Makefile.js perf",
|
|
80
80
|
"test:pnpm": "cd tests/pnpm && node check.js && pnpm install && pnpm exec tsc",
|
|
81
|
-
"test:types": "tsc -p tests/lib/types/tsconfig.json"
|
|
81
|
+
"test:types": "tsc -p tests/lib/types/tsconfig.json && npm run test:types --workspaces --if-present",
|
|
82
|
+
"test:types:5.3": "npx -p typescript@5.3 -y -- tsc -p tsconfig.types-legacy.json",
|
|
83
|
+
"test:types:5.x": "npx -p typescript@5.x -y -- tsc -p tsconfig.types.json",
|
|
84
|
+
"test:types:7.x": "npx -p @typescript/native-preview@latest -y -- tsgo -p tsconfig.types.json",
|
|
85
|
+
"test:types:all": "npm run test:types && npm run test:types:5.3 && npm run test:types:5.x && npm run test:types:7.x"
|
|
82
86
|
},
|
|
87
|
+
"workspaces": [
|
|
88
|
+
"packages/*"
|
|
89
|
+
],
|
|
83
90
|
"gitHooks": {
|
|
84
91
|
"pre-commit": "lint-staged"
|
|
85
92
|
},
|
|
@@ -151,12 +158,12 @@
|
|
|
151
158
|
"optionator": "^0.9.3"
|
|
152
159
|
},
|
|
153
160
|
"devDependencies": {
|
|
154
|
-
"@arethetypeswrong/cli": "^0.18.
|
|
161
|
+
"@arethetypeswrong/cli": "^0.18.3",
|
|
155
162
|
"@babel/core": "^7.4.3",
|
|
156
163
|
"@babel/preset-env": "^7.4.3",
|
|
157
164
|
"@cypress/webpack-preprocessor": "^6.0.2",
|
|
158
165
|
"@eslint/eslintrc": "^3.3.5",
|
|
159
|
-
"@eslint/json": "^
|
|
166
|
+
"@eslint/json": "^2.0.0",
|
|
160
167
|
"@types/esquery": "^1.5.4",
|
|
161
168
|
"@types/node": "^22.13.14",
|
|
162
169
|
"@typescript-eslint/parser": "^8.58.2",
|
|
@@ -185,8 +192,8 @@
|
|
|
185
192
|
"jiti": "^2.6.1",
|
|
186
193
|
"knip": "^6.13.1",
|
|
187
194
|
"lint-staged": "^11.0.0",
|
|
188
|
-
"markdown-it": "^
|
|
189
|
-
"markdown-it-container": "^
|
|
195
|
+
"markdown-it": "^14.2.0",
|
|
196
|
+
"markdown-it-container": "^4.0.0",
|
|
190
197
|
"markdownlint-cli2": "^0.22.0",
|
|
191
198
|
"marked": "^4.0.8",
|
|
192
199
|
"metascraper": "^5.25.7",
|
|
@@ -198,7 +205,7 @@
|
|
|
198
205
|
"mocha": "^11.7.1",
|
|
199
206
|
"node-polyfill-webpack-plugin": "^1.0.3",
|
|
200
207
|
"npm-license": "^0.3.3",
|
|
201
|
-
"prettier": "3.8.
|
|
208
|
+
"prettier": "3.8.4",
|
|
202
209
|
"progress": "^2.0.3",
|
|
203
210
|
"proxyquire": "^2.0.1",
|
|
204
211
|
"regenerator-runtime": "^0.14.0",
|
|
@@ -228,10 +235,5 @@
|
|
|
228
235
|
"license": "MIT",
|
|
229
236
|
"engines": {
|
|
230
237
|
"node": "^20.19.0 || ^22.13.0 || >=24"
|
|
231
|
-
},
|
|
232
|
-
"overrides": {
|
|
233
|
-
"@arethetypeswrong/core": {
|
|
234
|
-
"fflate": "0.8.2"
|
|
235
|
-
}
|
|
236
238
|
}
|
|
237
239
|
}
|