eslint 8.23.1 → 8.25.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/lib/cli.js +22 -14
- package/lib/config/flat-config-array.js +1 -1
- package/lib/eslint/eslint-helpers.js +5 -7
- package/lib/eslint/eslint.js +5 -2
- package/lib/eslint/flat-eslint.js +9 -50
- package/lib/linter/linter.js +12 -6
- package/lib/options.js +12 -6
- package/lib/rules/array-callback-return.js +1 -1
- package/lib/rules/id-length.js +43 -2
- package/lib/rules/index.js +1 -0
- package/lib/rules/lines-around-comment.js +2 -2
- package/lib/rules/logical-assignment-operators.js +474 -0
- package/lib/rules/no-loss-of-precision.js +2 -2
- package/lib/rules/no-use-before-define.js +1 -1
- package/lib/rules/strict.js +1 -1
- package/lib/shared/traverser.js +1 -1
- package/lib/shared/types.js +14 -1
- package/package.json +3 -5
package/lib/cli.js
CHANGED
@@ -25,7 +25,6 @@ const fs = require("fs"),
|
|
25
25
|
RuntimeInfo = require("./shared/runtime-info");
|
26
26
|
const { Legacy: { naming } } = require("@eslint/eslintrc");
|
27
27
|
const { findFlatConfigFile } = require("./eslint/flat-eslint");
|
28
|
-
const { gitignoreToMinimatch } = require("@humanwhocodes/gitignore-to-minimatch");
|
29
28
|
const { ModuleImporter } = require("@humanwhocodes/module-importer");
|
30
29
|
|
31
30
|
const debug = require("debug")("eslint:cli");
|
@@ -38,6 +37,7 @@ const debug = require("debug")("eslint:cli");
|
|
38
37
|
/** @typedef {import("./eslint/eslint").LintMessage} LintMessage */
|
39
38
|
/** @typedef {import("./eslint/eslint").LintResult} LintResult */
|
40
39
|
/** @typedef {import("./options").ParsedCLIOptions} ParsedCLIOptions */
|
40
|
+
/** @typedef {import("./shared/types").ResultsMeta} ResultsMeta */
|
41
41
|
|
42
42
|
//------------------------------------------------------------------------------
|
43
43
|
// Helpers
|
@@ -145,7 +145,7 @@ async function translateOptions({
|
|
145
145
|
|
146
146
|
if (ignorePattern) {
|
147
147
|
overrideConfig.push({
|
148
|
-
ignores: ignorePattern
|
148
|
+
ignores: ignorePattern
|
149
149
|
});
|
150
150
|
}
|
151
151
|
|
@@ -182,7 +182,6 @@ async function translateOptions({
|
|
182
182
|
fix: (fix || fixDryRun) && (quiet ? quietFixPredicate : true),
|
183
183
|
fixTypes: fixType,
|
184
184
|
ignore,
|
185
|
-
ignorePath,
|
186
185
|
overrideConfig,
|
187
186
|
overrideConfigFile,
|
188
187
|
reportUnusedDisableDirectives: reportUnusedDisableDirectives ? "error" : void 0
|
@@ -193,6 +192,7 @@ async function translateOptions({
|
|
193
192
|
options.rulePaths = rulesdir;
|
194
193
|
options.useEslintrc = eslintrc;
|
195
194
|
options.extensions = ext;
|
195
|
+
options.ignorePath = ignorePath;
|
196
196
|
}
|
197
197
|
|
198
198
|
return options;
|
@@ -201,7 +201,7 @@ async function translateOptions({
|
|
201
201
|
/**
|
202
202
|
* Count error messages.
|
203
203
|
* @param {LintResult[]} results The lint results.
|
204
|
-
* @returns {{errorCount:number;warningCount:number}} The number of error messages.
|
204
|
+
* @returns {{errorCount:number;fatalErrorCount:number,warningCount:number}} The number of error messages.
|
205
205
|
*/
|
206
206
|
function countErrors(results) {
|
207
207
|
let errorCount = 0;
|
@@ -239,10 +239,11 @@ async function isDirectory(filePath) {
|
|
239
239
|
* @param {LintResult[]} results The results to print.
|
240
240
|
* @param {string} format The name of the formatter to use or the path to the formatter.
|
241
241
|
* @param {string} outputFile The path for the output file.
|
242
|
+
* @param {ResultsMeta} resultsMeta Warning count and max threshold.
|
242
243
|
* @returns {Promise<boolean>} True if the printing succeeds, false if not.
|
243
244
|
* @private
|
244
245
|
*/
|
245
|
-
async function printResults(engine, results, format, outputFile) {
|
246
|
+
async function printResults(engine, results, format, outputFile, resultsMeta) {
|
246
247
|
let formatter;
|
247
248
|
|
248
249
|
try {
|
@@ -252,7 +253,7 @@ async function printResults(engine, results, format, outputFile) {
|
|
252
253
|
return false;
|
253
254
|
}
|
254
255
|
|
255
|
-
const output = await formatter.format(results);
|
256
|
+
const output = await formatter.format(results, resultsMeta);
|
256
257
|
|
257
258
|
if (output) {
|
258
259
|
if (outputFile) {
|
@@ -407,17 +408,24 @@ const cli = {
|
|
407
408
|
resultsToPrint = ActiveESLint.getErrorResults(resultsToPrint);
|
408
409
|
}
|
409
410
|
|
410
|
-
|
411
|
+
const resultCounts = countErrors(results);
|
412
|
+
const tooManyWarnings = options.maxWarnings >= 0 && resultCounts.warningCount > options.maxWarnings;
|
413
|
+
const resultsMeta = tooManyWarnings
|
414
|
+
? {
|
415
|
+
maxWarningsExceeded: {
|
416
|
+
maxWarnings: options.maxWarnings,
|
417
|
+
foundWarnings: resultCounts.warningCount
|
418
|
+
}
|
419
|
+
}
|
420
|
+
: {};
|
411
421
|
|
412
|
-
|
413
|
-
const { errorCount, fatalErrorCount, warningCount } = countErrors(results);
|
422
|
+
if (await printResults(engine, resultsToPrint, options.format, options.outputFile, resultsMeta)) {
|
414
423
|
|
415
|
-
|
416
|
-
options.maxWarnings >= 0 && warningCount > options.maxWarnings;
|
424
|
+
// Errors and warnings from the original unfiltered results should determine the exit code
|
417
425
|
const shouldExitForFatalErrors =
|
418
|
-
options.exitOnFatalError && fatalErrorCount > 0;
|
426
|
+
options.exitOnFatalError && resultCounts.fatalErrorCount > 0;
|
419
427
|
|
420
|
-
if (!errorCount && tooManyWarnings) {
|
428
|
+
if (!resultCounts.errorCount && tooManyWarnings) {
|
421
429
|
log.error(
|
422
430
|
"ESLint found too many warnings (maximum: %s).",
|
423
431
|
options.maxWarnings
|
@@ -428,7 +436,7 @@ const cli = {
|
|
428
436
|
return 2;
|
429
437
|
}
|
430
438
|
|
431
|
-
return (errorCount || tooManyWarnings) ? 1 : 0;
|
439
|
+
return (resultCounts.errorCount || tooManyWarnings) ? 1 : 0;
|
432
440
|
}
|
433
441
|
|
434
442
|
return 2;
|
@@ -67,9 +67,9 @@ function isNonEmptyString(x) {
|
|
67
67
|
}
|
68
68
|
|
69
69
|
/**
|
70
|
-
* Check if a given value is an array of non-empty
|
70
|
+
* Check if a given value is an array of non-empty strings or not.
|
71
71
|
* @param {any} x The value to check.
|
72
|
-
* @returns {boolean} `true` if `x` is an array of non-empty
|
72
|
+
* @returns {boolean} `true` if `x` is an array of non-empty strings.
|
73
73
|
*/
|
74
74
|
function isArrayOfNonEmptyString(x) {
|
75
75
|
return Array.isArray(x) && x.every(isNonEmptyString);
|
@@ -410,7 +410,6 @@ function processOptions({
|
|
410
410
|
fixTypes = null, // ← should be null by default because if it's an array then it suppresses rules that don't have the `meta.type` property.
|
411
411
|
globInputPaths = true,
|
412
412
|
ignore = true,
|
413
|
-
ignorePath = null, // ← should be null by default because if it's a string then it may throw ENOENT.
|
414
413
|
ignorePatterns = null,
|
415
414
|
overrideConfig = null,
|
416
415
|
overrideConfigFile = null,
|
@@ -441,6 +440,9 @@ function processOptions({
|
|
441
440
|
if (unknownOptionKeys.includes("globals")) {
|
442
441
|
errors.push("'globals' has been removed. Please use the 'overrideConfig.languageOptions.globals' option instead.");
|
443
442
|
}
|
443
|
+
if (unknownOptionKeys.includes("ignorePath")) {
|
444
|
+
errors.push("'ignorePath' has been removed.");
|
445
|
+
}
|
444
446
|
if (unknownOptionKeys.includes("ignorePattern")) {
|
445
447
|
errors.push("'ignorePattern' has been removed. Please use the 'overrideConfig.ignorePatterns' option instead.");
|
446
448
|
}
|
@@ -493,9 +495,6 @@ function processOptions({
|
|
493
495
|
if (typeof ignore !== "boolean") {
|
494
496
|
errors.push("'ignore' must be a boolean.");
|
495
497
|
}
|
496
|
-
if (!isNonEmptyString(ignorePath) && ignorePath !== null) {
|
497
|
-
errors.push("'ignorePath' must be a non-empty string or null.");
|
498
|
-
}
|
499
498
|
if (typeof overrideConfig !== "object") {
|
500
499
|
errors.push("'overrideConfig' must be an object or null.");
|
501
500
|
}
|
@@ -538,7 +537,6 @@ function processOptions({
|
|
538
537
|
fixTypes,
|
539
538
|
globInputPaths,
|
540
539
|
ignore,
|
541
|
-
ignorePath,
|
542
540
|
ignorePatterns,
|
543
541
|
reportUnusedDisableDirectives
|
544
542
|
};
|
package/lib/eslint/eslint.js
CHANGED
@@ -36,11 +36,12 @@ const { version } = require("../../package.json");
|
|
36
36
|
/** @typedef {import("../shared/types").Plugin} Plugin */
|
37
37
|
/** @typedef {import("../shared/types").Rule} Rule */
|
38
38
|
/** @typedef {import("../shared/types").LintResult} LintResult */
|
39
|
+
/** @typedef {import("../shared/types").ResultsMeta} ResultsMeta */
|
39
40
|
|
40
41
|
/**
|
41
42
|
* The main formatter object.
|
42
43
|
* @typedef LoadedFormatter
|
43
|
-
* @property {
|
44
|
+
* @property {(results: LintResult[], resultsMeta: ResultsMeta) => string | Promise<string>} format format function.
|
44
45
|
*/
|
45
46
|
|
46
47
|
/**
|
@@ -625,14 +626,16 @@ class ESLint {
|
|
625
626
|
/**
|
626
627
|
* The main formatter method.
|
627
628
|
* @param {LintResult[]} results The lint results to format.
|
629
|
+
* @param {ResultsMeta} resultsMeta Warning count and max threshold.
|
628
630
|
* @returns {string | Promise<string>} The formatted lint results.
|
629
631
|
*/
|
630
|
-
format(results) {
|
632
|
+
format(results, resultsMeta) {
|
631
633
|
let rulesMeta = null;
|
632
634
|
|
633
635
|
results.sort(compareResultsByFilePath);
|
634
636
|
|
635
637
|
return formatter(results, {
|
638
|
+
...resultsMeta,
|
636
639
|
get cwd() {
|
637
640
|
return options.cwd;
|
638
641
|
},
|
@@ -16,7 +16,6 @@ const findUp = require("find-up");
|
|
16
16
|
const { version } = require("../../package.json");
|
17
17
|
const { Linter } = require("../linter");
|
18
18
|
const { getRuleFromConfig } = require("../config/flat-config-helpers");
|
19
|
-
const { gitignoreToMinimatch } = require("@humanwhocodes/gitignore-to-minimatch");
|
20
19
|
const {
|
21
20
|
Legacy: {
|
22
21
|
ConfigOps: {
|
@@ -28,7 +27,6 @@ const {
|
|
28
27
|
} = require("@eslint/eslintrc");
|
29
28
|
|
30
29
|
const {
|
31
|
-
fileExists,
|
32
30
|
findFiles,
|
33
31
|
getCacheFile,
|
34
32
|
|
@@ -59,6 +57,7 @@ const LintResultCache = require("../cli-engine/lint-result-cache");
|
|
59
57
|
/** @typedef {import("../shared/types").LintMessage} LintMessage */
|
60
58
|
/** @typedef {import("../shared/types").ParserOptions} ParserOptions */
|
61
59
|
/** @typedef {import("../shared/types").Plugin} Plugin */
|
60
|
+
/** @typedef {import("../shared/types").ResultsMeta} ResultsMeta */
|
62
61
|
/** @typedef {import("../shared/types").RuleConf} RuleConf */
|
63
62
|
/** @typedef {import("../shared/types").Rule} Rule */
|
64
63
|
/** @typedef {ReturnType<ConfigArray.extractConfig>} ExtractedConfig */
|
@@ -76,9 +75,8 @@ const LintResultCache = require("../cli-engine/lint-result-cache");
|
|
76
75
|
* @property {boolean|Function} [fix] Execute in autofix mode. If a function, should return a boolean.
|
77
76
|
* @property {string[]} [fixTypes] Array of rule types to apply fixes for.
|
78
77
|
* @property {boolean} [globInputPaths] Set to false to skip glob resolution of input file paths to lint (default: true). If false, each input file paths is assumed to be a non-glob path to an existing file.
|
79
|
-
* @property {boolean} [ignore] False disables
|
80
|
-
* @property {string} [
|
81
|
-
* @property {string[]} [ignorePatterns] Ignore file patterns to use in addition to .eslintignore.
|
78
|
+
* @property {boolean} [ignore] False disables all ignore patterns except for the default ones.
|
79
|
+
* @property {string[]} [ignorePatterns] Ignore file patterns to use in addition to config ignores.
|
82
80
|
* @property {ConfigData} [overrideConfig] Override config object, overrides all configs used with this instance
|
83
81
|
* @property {boolean|string} [overrideConfigFile] Searches for default config file when falsy;
|
84
82
|
* doesn't do any config file lookup when `true`; considered to be a config filename
|
@@ -151,30 +149,6 @@ function calculateStatsPerRun(results) {
|
|
151
149
|
});
|
152
150
|
}
|
153
151
|
|
154
|
-
/**
|
155
|
-
* Loads global ignore patterns from an ignore file (usually .eslintignore).
|
156
|
-
* @param {string} filePath The filename to load.
|
157
|
-
* @returns {ignore} A function encapsulating the ignore patterns.
|
158
|
-
* @throws {Error} If the file cannot be read.
|
159
|
-
* @private
|
160
|
-
*/
|
161
|
-
async function loadIgnoreFilePatterns(filePath) {
|
162
|
-
debug(`Loading ignore file: ${filePath}`);
|
163
|
-
|
164
|
-
try {
|
165
|
-
const ignoreFileText = await fs.readFile(filePath, { encoding: "utf8" });
|
166
|
-
|
167
|
-
return ignoreFileText
|
168
|
-
.split(/\r?\n/gu)
|
169
|
-
.filter(line => line.trim() !== "" && !line.startsWith("#"));
|
170
|
-
|
171
|
-
} catch (e) {
|
172
|
-
debug(`Error reading ignore file: ${filePath}`);
|
173
|
-
e.message = `Cannot read ignore file: ${filePath}\nError: ${e.message}`;
|
174
|
-
throw e;
|
175
|
-
}
|
176
|
-
}
|
177
|
-
|
178
152
|
/**
|
179
153
|
* Create rulesMeta object.
|
180
154
|
* @param {Map<string,Rule>} rules a map of rules from which to generate the object.
|
@@ -319,7 +293,6 @@ async function calculateConfigArray(eslint, {
|
|
319
293
|
overrideConfig,
|
320
294
|
configFile,
|
321
295
|
ignore: shouldIgnore,
|
322
|
-
ignorePath,
|
323
296
|
ignorePatterns
|
324
297
|
}) {
|
325
298
|
|
@@ -364,22 +337,6 @@ async function calculateConfigArray(eslint, {
|
|
364
337
|
configs.push(...slots.defaultConfigs);
|
365
338
|
|
366
339
|
let allIgnorePatterns = [];
|
367
|
-
let ignoreFilePath;
|
368
|
-
|
369
|
-
// load ignore file if necessary
|
370
|
-
if (shouldIgnore) {
|
371
|
-
if (ignorePath) {
|
372
|
-
ignoreFilePath = path.resolve(cwd, ignorePath);
|
373
|
-
allIgnorePatterns = await loadIgnoreFilePatterns(ignoreFilePath);
|
374
|
-
} else {
|
375
|
-
ignoreFilePath = path.resolve(cwd, ".eslintignore");
|
376
|
-
|
377
|
-
// no error if .eslintignore doesn't exist`
|
378
|
-
if (fileExists(ignoreFilePath)) {
|
379
|
-
allIgnorePatterns = await loadIgnoreFilePatterns(ignoreFilePath);
|
380
|
-
}
|
381
|
-
}
|
382
|
-
}
|
383
340
|
|
384
341
|
// append command line ignore patterns
|
385
342
|
if (ignorePatterns) {
|
@@ -428,7 +385,7 @@ async function calculateConfigArray(eslint, {
|
|
428
385
|
* so they can override default ignores.
|
429
386
|
*/
|
430
387
|
configs.push({
|
431
|
-
ignores: allIgnorePatterns
|
388
|
+
ignores: allIgnorePatterns
|
432
389
|
});
|
433
390
|
}
|
434
391
|
|
@@ -872,7 +829,7 @@ class FlatESLint {
|
|
872
829
|
}
|
873
830
|
|
874
831
|
|
875
|
-
// set up fixer for
|
832
|
+
// set up fixer for fixTypes if necessary
|
876
833
|
let fixer = fix;
|
877
834
|
|
878
835
|
if (fix && fixTypesSet) {
|
@@ -1048,7 +1005,7 @@ class FlatESLint {
|
|
1048
1005
|
* The following values are allowed:
|
1049
1006
|
* - `undefined` ... Load `stylish` builtin formatter.
|
1050
1007
|
* - A builtin formatter name ... Load the builtin formatter.
|
1051
|
-
* - A
|
1008
|
+
* - A third-party formatter name:
|
1052
1009
|
* - `foo` → `eslint-formatter-foo`
|
1053
1010
|
* - `@foo` → `@foo/eslint-formatter`
|
1054
1011
|
* - `@foo/bar` → `@foo/eslint-formatter-bar`
|
@@ -1114,14 +1071,16 @@ class FlatESLint {
|
|
1114
1071
|
/**
|
1115
1072
|
* The main formatter method.
|
1116
1073
|
* @param {LintResults[]} results The lint results to format.
|
1074
|
+
* @param {ResultsMeta} resultsMeta Warning count and max threshold.
|
1117
1075
|
* @returns {string} The formatted lint results.
|
1118
1076
|
*/
|
1119
|
-
format(results) {
|
1077
|
+
format(results, resultsMeta) {
|
1120
1078
|
let rulesMeta = null;
|
1121
1079
|
|
1122
1080
|
results.sort(compareResultsByFilePath);
|
1123
1081
|
|
1124
1082
|
return formatter(results, {
|
1083
|
+
...resultsMeta,
|
1125
1084
|
cwd,
|
1126
1085
|
get rulesMeta() {
|
1127
1086
|
if (!rulesMeta) {
|
package/lib/linter/linter.js
CHANGED
@@ -1601,12 +1601,18 @@ class Linter {
|
|
1601
1601
|
languageOptions.ecmaVersion
|
1602
1602
|
);
|
1603
1603
|
|
1604
|
-
|
1605
|
-
|
1606
|
-
|
1607
|
-
|
1608
|
-
|
1609
|
-
|
1604
|
+
/*
|
1605
|
+
* add configured globals and language globals
|
1606
|
+
*
|
1607
|
+
* using Object.assign instead of object spread for performance reasons
|
1608
|
+
* https://github.com/eslint/eslint/issues/16302
|
1609
|
+
*/
|
1610
|
+
const configuredGlobals = Object.assign(
|
1611
|
+
{},
|
1612
|
+
getGlobalsForEcmaVersion(languageOptions.ecmaVersion),
|
1613
|
+
languageOptions.sourceType === "commonjs" ? globals.commonjs : void 0,
|
1614
|
+
languageOptions.globals
|
1615
|
+
);
|
1610
1616
|
|
1611
1617
|
// double check that there is a parser to avoid mysterious error messages
|
1612
1618
|
if (!languageOptions.parser) {
|
package/lib/options.js
CHANGED
@@ -67,7 +67,7 @@ const optionator = require("optionator");
|
|
67
67
|
/**
|
68
68
|
* Creates the CLI options for ESLint.
|
69
69
|
* @param {boolean} usingFlatConfig Indicates if flat config is being used.
|
70
|
-
* @returns {Object} The
|
70
|
+
* @returns {Object} The optionator instance.
|
71
71
|
*/
|
72
72
|
module.exports = function(usingFlatConfig) {
|
73
73
|
|
@@ -129,6 +129,16 @@ module.exports = function(usingFlatConfig) {
|
|
129
129
|
};
|
130
130
|
}
|
131
131
|
|
132
|
+
let ignorePathFlag;
|
133
|
+
|
134
|
+
if (!usingFlatConfig) {
|
135
|
+
ignorePathFlag = {
|
136
|
+
option: "ignore-path",
|
137
|
+
type: "path::String",
|
138
|
+
description: "Specify path of ignore file"
|
139
|
+
};
|
140
|
+
}
|
141
|
+
|
132
142
|
return optionator({
|
133
143
|
prepend: "eslint [options] file.js [file.js] [dir]",
|
134
144
|
defaults: {
|
@@ -203,11 +213,7 @@ module.exports = function(usingFlatConfig) {
|
|
203
213
|
{
|
204
214
|
heading: "Ignoring files"
|
205
215
|
},
|
206
|
-
|
207
|
-
option: "ignore-path",
|
208
|
-
type: "path::String",
|
209
|
-
description: "Specify path of ignore file"
|
210
|
-
},
|
216
|
+
ignorePathFlag,
|
211
217
|
{
|
212
218
|
option: "ignore",
|
213
219
|
type: "Boolean",
|
@@ -16,7 +16,7 @@ const astUtils = require("./utils/ast-utils");
|
|
16
16
|
//------------------------------------------------------------------------------
|
17
17
|
|
18
18
|
const TARGET_NODE_TYPE = /^(?:Arrow)?FunctionExpression$/u;
|
19
|
-
const TARGET_METHODS = /^(?:every|filter|find(?:Index)?|flatMap|forEach|map|reduce(?:Right)?|some|sort)$/u;
|
19
|
+
const TARGET_METHODS = /^(?:every|filter|find(?:Last)?(?:Index)?|flatMap|forEach|map|reduce(?:Right)?|some|sort)$/u;
|
20
20
|
|
21
21
|
/**
|
22
22
|
* Checks a given code path segment is reachable.
|
package/lib/rules/id-length.js
CHANGED
@@ -6,6 +6,45 @@
|
|
6
6
|
|
7
7
|
"use strict";
|
8
8
|
|
9
|
+
//------------------------------------------------------------------------------
|
10
|
+
// Requirements
|
11
|
+
//------------------------------------------------------------------------------
|
12
|
+
const GraphemeSplitter = require("grapheme-splitter");
|
13
|
+
|
14
|
+
//------------------------------------------------------------------------------
|
15
|
+
// Helpers
|
16
|
+
//------------------------------------------------------------------------------
|
17
|
+
|
18
|
+
/**
|
19
|
+
* Checks if the string given as argument is ASCII or not.
|
20
|
+
* @param {string} value A string that you want to know if it is ASCII or not.
|
21
|
+
* @returns {boolean} `true` if `value` is ASCII string.
|
22
|
+
*/
|
23
|
+
function isASCII(value) {
|
24
|
+
if (typeof value !== "string") {
|
25
|
+
return false;
|
26
|
+
}
|
27
|
+
return /^[\u0020-\u007f]*$/u.test(value);
|
28
|
+
}
|
29
|
+
|
30
|
+
/** @type {GraphemeSplitter | undefined} */
|
31
|
+
let splitter;
|
32
|
+
|
33
|
+
/**
|
34
|
+
* Gets the length of the string. If the string is not in ASCII, counts graphemes.
|
35
|
+
* @param {string} value A string that you want to get the length.
|
36
|
+
* @returns {number} The length of `value`.
|
37
|
+
*/
|
38
|
+
function getStringLength(value) {
|
39
|
+
if (isASCII(value)) {
|
40
|
+
return value.length;
|
41
|
+
}
|
42
|
+
if (!splitter) {
|
43
|
+
splitter = new GraphemeSplitter();
|
44
|
+
}
|
45
|
+
return splitter.countGraphemes(value);
|
46
|
+
}
|
47
|
+
|
9
48
|
//------------------------------------------------------------------------------
|
10
49
|
// Rule Definition
|
11
50
|
//------------------------------------------------------------------------------
|
@@ -130,8 +169,10 @@ module.exports = {
|
|
130
169
|
const name = node.name;
|
131
170
|
const parent = node.parent;
|
132
171
|
|
133
|
-
const
|
134
|
-
|
172
|
+
const nameLength = getStringLength(name);
|
173
|
+
|
174
|
+
const isShort = nameLength < minLength;
|
175
|
+
const isLong = nameLength > maxLength;
|
135
176
|
|
136
177
|
if (!(isShort || isLong) || exceptions.has(name) || matchesExceptionPattern(name)) {
|
137
178
|
return; // Nothing to report
|
package/lib/rules/index.js
CHANGED
@@ -72,6 +72,7 @@ module.exports = new LazyLoadingRuleMap(Object.entries({
|
|
72
72
|
"lines-around-comment": () => require("./lines-around-comment"),
|
73
73
|
"lines-around-directive": () => require("./lines-around-directive"),
|
74
74
|
"lines-between-class-members": () => require("./lines-between-class-members"),
|
75
|
+
"logical-assignment-operators": () => require("./logical-assignment-operators"),
|
75
76
|
"max-classes-per-file": () => require("./max-classes-per-file"),
|
76
77
|
"max-depth": () => require("./max-depth"),
|
77
78
|
"max-len": () => require("./max-len"),
|
@@ -15,7 +15,7 @@ const astUtils = require("./utils/ast-utils");
|
|
15
15
|
//------------------------------------------------------------------------------
|
16
16
|
|
17
17
|
/**
|
18
|
-
* Return an array with
|
18
|
+
* Return an array with any line numbers that are empty.
|
19
19
|
* @param {Array} lines An array of each line of the file.
|
20
20
|
* @returns {Array} An array of line numbers.
|
21
21
|
*/
|
@@ -29,7 +29,7 @@ function getEmptyLineNums(lines) {
|
|
29
29
|
}
|
30
30
|
|
31
31
|
/**
|
32
|
-
* Return an array with
|
32
|
+
* Return an array with any line numbers that contain comments.
|
33
33
|
* @param {Array} comments An array of comment tokens.
|
34
34
|
* @returns {Array} An array of line numbers.
|
35
35
|
*/
|
@@ -0,0 +1,474 @@
|
|
1
|
+
/**
|
2
|
+
* @fileoverview Rule to replace assignment expressions with logical operator assignment
|
3
|
+
* @author Daniel Martens
|
4
|
+
*/
|
5
|
+
"use strict";
|
6
|
+
|
7
|
+
//------------------------------------------------------------------------------
|
8
|
+
// Requirements
|
9
|
+
//------------------------------------------------------------------------------
|
10
|
+
const astUtils = require("./utils/ast-utils.js");
|
11
|
+
|
12
|
+
//------------------------------------------------------------------------------
|
13
|
+
// Helpers
|
14
|
+
//------------------------------------------------------------------------------
|
15
|
+
|
16
|
+
const baseTypes = new Set(["Identifier", "Super", "ThisExpression"]);
|
17
|
+
|
18
|
+
/**
|
19
|
+
* Returns true iff either "undefined" or a void expression (eg. "void 0")
|
20
|
+
* @param {ASTNode} expression Expression to check
|
21
|
+
* @param {import('eslint-scope').Scope} scope Scope of the expression
|
22
|
+
* @returns {boolean} True iff "undefined" or "void ..."
|
23
|
+
*/
|
24
|
+
function isUndefined(expression, scope) {
|
25
|
+
if (expression.type === "Identifier" && expression.name === "undefined") {
|
26
|
+
return astUtils.isReferenceToGlobalVariable(scope, expression);
|
27
|
+
}
|
28
|
+
|
29
|
+
return expression.type === "UnaryExpression" &&
|
30
|
+
expression.operator === "void" &&
|
31
|
+
expression.argument.type === "Literal" &&
|
32
|
+
expression.argument.value === 0;
|
33
|
+
}
|
34
|
+
|
35
|
+
/**
|
36
|
+
* Returns true iff the reference is either an identifier or member expression
|
37
|
+
* @param {ASTNode} expression Expression to check
|
38
|
+
* @returns {boolean} True for identifiers and member expressions
|
39
|
+
*/
|
40
|
+
function isReference(expression) {
|
41
|
+
return (expression.type === "Identifier" && expression.name !== "undefined") ||
|
42
|
+
expression.type === "MemberExpression";
|
43
|
+
}
|
44
|
+
|
45
|
+
/**
|
46
|
+
* Returns true iff the expression checks for nullish with loose equals.
|
47
|
+
* Examples: value == null, value == void 0
|
48
|
+
* @param {ASTNode} expression Test condition
|
49
|
+
* @param {import('eslint-scope').Scope} scope Scope of the expression
|
50
|
+
* @returns {boolean} True iff implicit nullish comparison
|
51
|
+
*/
|
52
|
+
function isImplicitNullishComparison(expression, scope) {
|
53
|
+
if (expression.type !== "BinaryExpression" || expression.operator !== "==") {
|
54
|
+
return false;
|
55
|
+
}
|
56
|
+
|
57
|
+
const reference = isReference(expression.left) ? "left" : "right";
|
58
|
+
const nullish = reference === "left" ? "right" : "left";
|
59
|
+
|
60
|
+
return isReference(expression[reference]) &&
|
61
|
+
(astUtils.isNullLiteral(expression[nullish]) || isUndefined(expression[nullish], scope));
|
62
|
+
}
|
63
|
+
|
64
|
+
/**
|
65
|
+
* Condition with two equal comparisons.
|
66
|
+
* @param {ASTNode} expression Condition
|
67
|
+
* @returns {boolean} True iff matches ? === ? || ? === ?
|
68
|
+
*/
|
69
|
+
function isDoubleComparison(expression) {
|
70
|
+
return expression.type === "LogicalExpression" &&
|
71
|
+
expression.operator === "||" &&
|
72
|
+
expression.left.type === "BinaryExpression" &&
|
73
|
+
expression.left.operator === "===" &&
|
74
|
+
expression.right.type === "BinaryExpression" &&
|
75
|
+
expression.right.operator === "===";
|
76
|
+
}
|
77
|
+
|
78
|
+
/**
|
79
|
+
* Returns true iff the expression checks for undefined and null.
|
80
|
+
* Example: value === null || value === undefined
|
81
|
+
* @param {ASTNode} expression Test condition
|
82
|
+
* @param {import('eslint-scope').Scope} scope Scope of the expression
|
83
|
+
* @returns {boolean} True iff explicit nullish comparison
|
84
|
+
*/
|
85
|
+
function isExplicitNullishComparison(expression, scope) {
|
86
|
+
if (!isDoubleComparison(expression)) {
|
87
|
+
return false;
|
88
|
+
}
|
89
|
+
const leftReference = isReference(expression.left.left) ? "left" : "right";
|
90
|
+
const leftNullish = leftReference === "left" ? "right" : "left";
|
91
|
+
const rightReference = isReference(expression.right.left) ? "left" : "right";
|
92
|
+
const rightNullish = rightReference === "left" ? "right" : "left";
|
93
|
+
|
94
|
+
return astUtils.isSameReference(expression.left[leftReference], expression.right[rightReference]) &&
|
95
|
+
((astUtils.isNullLiteral(expression.left[leftNullish]) && isUndefined(expression.right[rightNullish], scope)) ||
|
96
|
+
(isUndefined(expression.left[leftNullish], scope) && astUtils.isNullLiteral(expression.right[rightNullish])));
|
97
|
+
}
|
98
|
+
|
99
|
+
/**
|
100
|
+
* Returns true for Boolean(arg) calls
|
101
|
+
* @param {ASTNode} expression Test condition
|
102
|
+
* @param {import('eslint-scope').Scope} scope Scope of the expression
|
103
|
+
* @returns {boolean} Whether the expression is a boolean cast
|
104
|
+
*/
|
105
|
+
function isBooleanCast(expression, scope) {
|
106
|
+
return expression.type === "CallExpression" &&
|
107
|
+
expression.callee.name === "Boolean" &&
|
108
|
+
expression.arguments.length === 1 &&
|
109
|
+
astUtils.isReferenceToGlobalVariable(scope, expression.callee);
|
110
|
+
}
|
111
|
+
|
112
|
+
/**
|
113
|
+
* Returns true for:
|
114
|
+
* truthiness checks: value, Boolean(value), !!value
|
115
|
+
* falsyness checks: !value, !Boolean(value)
|
116
|
+
* nullish checks: value == null, value === undefined || value === null
|
117
|
+
* @param {ASTNode} expression Test condition
|
118
|
+
* @param {import('eslint-scope').Scope} scope Scope of the expression
|
119
|
+
* @returns {?{ reference: ASTNode, operator: '??'|'||'|'&&'}} Null if not a known existence
|
120
|
+
*/
|
121
|
+
function getExistence(expression, scope) {
|
122
|
+
const isNegated = expression.type === "UnaryExpression" && expression.operator === "!";
|
123
|
+
const base = isNegated ? expression.argument : expression;
|
124
|
+
|
125
|
+
switch (true) {
|
126
|
+
case isReference(base):
|
127
|
+
return { reference: base, operator: isNegated ? "||" : "&&" };
|
128
|
+
case base.type === "UnaryExpression" && base.operator === "!" && isReference(base.argument):
|
129
|
+
return { reference: base.argument, operator: "&&" };
|
130
|
+
case isBooleanCast(base, scope) && isReference(base.arguments[0]):
|
131
|
+
return { reference: base.arguments[0], operator: isNegated ? "||" : "&&" };
|
132
|
+
case isImplicitNullishComparison(expression, scope):
|
133
|
+
return { reference: isReference(expression.left) ? expression.left : expression.right, operator: "??" };
|
134
|
+
case isExplicitNullishComparison(expression, scope):
|
135
|
+
return { reference: isReference(expression.left.left) ? expression.left.left : expression.left.right, operator: "??" };
|
136
|
+
default: return null;
|
137
|
+
}
|
138
|
+
}
|
139
|
+
|
140
|
+
/**
|
141
|
+
* Returns true iff the node is inside a with block
|
142
|
+
* @param {ASTNode} node Node to check
|
143
|
+
* @returns {boolean} True iff passed node is inside a with block
|
144
|
+
*/
|
145
|
+
function isInsideWithBlock(node) {
|
146
|
+
if (node.type === "Program") {
|
147
|
+
return false;
|
148
|
+
}
|
149
|
+
|
150
|
+
return node.parent.type === "WithStatement" && node.parent.body === node ? true : isInsideWithBlock(node.parent);
|
151
|
+
}
|
152
|
+
|
153
|
+
//------------------------------------------------------------------------------
|
154
|
+
// Rule Definition
|
155
|
+
//------------------------------------------------------------------------------
|
156
|
+
/** @type {import('../shared/types').Rule} */
|
157
|
+
module.exports = {
|
158
|
+
meta: {
|
159
|
+
type: "suggestion",
|
160
|
+
|
161
|
+
docs: {
|
162
|
+
description: "Require or disallow logical assignment logical operator shorthand",
|
163
|
+
recommended: false,
|
164
|
+
url: "https://eslint.org/docs/rules/logical-assignment-operators"
|
165
|
+
},
|
166
|
+
|
167
|
+
schema: {
|
168
|
+
type: "array",
|
169
|
+
oneOf: [{
|
170
|
+
items: [
|
171
|
+
{ const: "always" },
|
172
|
+
{
|
173
|
+
type: "object",
|
174
|
+
properties: {
|
175
|
+
enforceForIfStatements: {
|
176
|
+
type: "boolean"
|
177
|
+
}
|
178
|
+
},
|
179
|
+
additionalProperties: false
|
180
|
+
}
|
181
|
+
],
|
182
|
+
minItems: 0, // 0 for allowing passing no options
|
183
|
+
maxItems: 2
|
184
|
+
}, {
|
185
|
+
items: [{ const: "never" }],
|
186
|
+
minItems: 1,
|
187
|
+
maxItems: 1
|
188
|
+
}]
|
189
|
+
},
|
190
|
+
fixable: "code",
|
191
|
+
// eslint-disable-next-line eslint-plugin/require-meta-has-suggestions -- Does not detect conditional suggestions
|
192
|
+
hasSuggestions: true,
|
193
|
+
messages: {
|
194
|
+
assignment: "Assignment (=) can be replaced with operator assignment ({{operator}}).",
|
195
|
+
useLogicalOperator: "Convert this assignment to use the operator {{ operator }}.",
|
196
|
+
logical: "Logical expression can be replaced with an assignment ({{ operator }}).",
|
197
|
+
convertLogical: "Replace this logical expression with an assignment with the operator {{ operator }}.",
|
198
|
+
if: "'if' statement can be replaced with a logical operator assignment with operator {{ operator }}.",
|
199
|
+
convertIf: "Replace this 'if' statement with a logical assignment with operator {{ operator }}.",
|
200
|
+
unexpected: "Unexpected logical operator assignment ({{operator}}) shorthand.",
|
201
|
+
separate: "Separate the logical assignment into an assignment with a logical operator."
|
202
|
+
}
|
203
|
+
},
|
204
|
+
|
205
|
+
create(context) {
|
206
|
+
const mode = context.options[0] === "never" ? "never" : "always";
|
207
|
+
const checkIf = mode === "always" && context.options.length > 1 && context.options[1].enforceForIfStatements;
|
208
|
+
const sourceCode = context.getSourceCode();
|
209
|
+
const isStrict = context.getScope().isStrict;
|
210
|
+
|
211
|
+
/**
|
212
|
+
* Returns false if the access could be a getter
|
213
|
+
* @param {ASTNode} node Assignment expression
|
214
|
+
* @returns {boolean} True iff the fix is safe
|
215
|
+
*/
|
216
|
+
function cannotBeGetter(node) {
|
217
|
+
return node.type === "Identifier" &&
|
218
|
+
(isStrict || !isInsideWithBlock(node));
|
219
|
+
}
|
220
|
+
|
221
|
+
/**
|
222
|
+
* Check whether only a single property is accessed
|
223
|
+
* @param {ASTNode} node reference
|
224
|
+
* @returns {boolean} True iff a single property is accessed
|
225
|
+
*/
|
226
|
+
function accessesSingleProperty(node) {
|
227
|
+
if (!isStrict && isInsideWithBlock(node)) {
|
228
|
+
return node.type === "Identifier";
|
229
|
+
}
|
230
|
+
|
231
|
+
return node.type === "MemberExpression" &&
|
232
|
+
baseTypes.has(node.object.type) &&
|
233
|
+
(!node.computed || (node.property.type !== "MemberExpression" && node.property.type !== "ChainExpression"));
|
234
|
+
}
|
235
|
+
|
236
|
+
/**
|
237
|
+
* Adds a fixer or suggestion whether on the fix is safe.
|
238
|
+
* @param {{ messageId: string, node: ASTNode }} descriptor Report descriptor without fix or suggest
|
239
|
+
* @param {{ messageId: string, fix: Function }} suggestion Adds the fix or the whole suggestion as only element in "suggest" to suggestion
|
240
|
+
* @param {boolean} shouldBeFixed Fix iff the condition is true
|
241
|
+
* @returns {Object} Descriptor with either an added fix or suggestion
|
242
|
+
*/
|
243
|
+
function createConditionalFixer(descriptor, suggestion, shouldBeFixed) {
|
244
|
+
if (shouldBeFixed) {
|
245
|
+
return {
|
246
|
+
...descriptor,
|
247
|
+
fix: suggestion.fix
|
248
|
+
};
|
249
|
+
}
|
250
|
+
|
251
|
+
return {
|
252
|
+
...descriptor,
|
253
|
+
suggest: [suggestion]
|
254
|
+
};
|
255
|
+
}
|
256
|
+
|
257
|
+
|
258
|
+
/**
|
259
|
+
* Returns the operator token for assignments and binary expressions
|
260
|
+
* @param {ASTNode} node AssignmentExpression or BinaryExpression
|
261
|
+
* @returns {import('eslint').AST.Token} Operator token between the left and right expression
|
262
|
+
*/
|
263
|
+
function getOperatorToken(node) {
|
264
|
+
return sourceCode.getFirstTokenBetween(node.left, node.right, token => token.value === node.operator);
|
265
|
+
}
|
266
|
+
|
267
|
+
if (mode === "never") {
|
268
|
+
return {
|
269
|
+
|
270
|
+
// foo ||= bar
|
271
|
+
"AssignmentExpression"(assignment) {
|
272
|
+
if (!astUtils.isLogicalAssignmentOperator(assignment.operator)) {
|
273
|
+
return;
|
274
|
+
}
|
275
|
+
|
276
|
+
const descriptor = {
|
277
|
+
messageId: "unexpected",
|
278
|
+
node: assignment,
|
279
|
+
data: { operator: assignment.operator }
|
280
|
+
};
|
281
|
+
const suggestion = {
|
282
|
+
messageId: "separate",
|
283
|
+
*fix(ruleFixer) {
|
284
|
+
if (sourceCode.getCommentsInside(assignment).length > 0) {
|
285
|
+
return;
|
286
|
+
}
|
287
|
+
|
288
|
+
const operatorToken = getOperatorToken(assignment);
|
289
|
+
|
290
|
+
// -> foo = bar
|
291
|
+
yield ruleFixer.replaceText(operatorToken, "=");
|
292
|
+
|
293
|
+
const assignmentText = sourceCode.getText(assignment.left);
|
294
|
+
const operator = assignment.operator.slice(0, -1);
|
295
|
+
|
296
|
+
// -> foo = foo || bar
|
297
|
+
yield ruleFixer.insertTextAfter(operatorToken, ` ${assignmentText} ${operator}`);
|
298
|
+
|
299
|
+
const precedence = astUtils.getPrecedence(assignment.right) <= astUtils.getPrecedence({ type: "LogicalExpression", operator });
|
300
|
+
|
301
|
+
// ?? and || / && cannot be mixed but have same precedence
|
302
|
+
const mixed = assignment.operator === "??=" && astUtils.isLogicalExpression(assignment.right);
|
303
|
+
|
304
|
+
if (!astUtils.isParenthesised(sourceCode, assignment.right) && (precedence || mixed)) {
|
305
|
+
|
306
|
+
// -> foo = foo || (bar)
|
307
|
+
yield ruleFixer.insertTextBefore(assignment.right, "(");
|
308
|
+
yield ruleFixer.insertTextAfter(assignment.right, ")");
|
309
|
+
}
|
310
|
+
}
|
311
|
+
};
|
312
|
+
|
313
|
+
context.report(createConditionalFixer(descriptor, suggestion, cannotBeGetter(assignment.left)));
|
314
|
+
}
|
315
|
+
};
|
316
|
+
}
|
317
|
+
|
318
|
+
return {
|
319
|
+
|
320
|
+
// foo = foo || bar
|
321
|
+
"AssignmentExpression[operator='='][right.type='LogicalExpression']"(assignment) {
|
322
|
+
if (!astUtils.isSameReference(assignment.left, assignment.right.left)) {
|
323
|
+
return;
|
324
|
+
}
|
325
|
+
|
326
|
+
const descriptor = {
|
327
|
+
messageId: "assignment",
|
328
|
+
node: assignment,
|
329
|
+
data: { operator: `${assignment.right.operator}=` }
|
330
|
+
};
|
331
|
+
const suggestion = {
|
332
|
+
messageId: "useLogicalOperator",
|
333
|
+
data: { operator: `${assignment.right.operator}=` },
|
334
|
+
*fix(ruleFixer) {
|
335
|
+
if (sourceCode.getCommentsInside(assignment).length > 0) {
|
336
|
+
return;
|
337
|
+
}
|
338
|
+
|
339
|
+
// No need for parenthesis around the assignment based on precedence as the precedence stays the same even with changed operator
|
340
|
+
const assignmentOperatorToken = getOperatorToken(assignment);
|
341
|
+
|
342
|
+
// -> foo ||= foo || bar
|
343
|
+
yield ruleFixer.insertTextBefore(assignmentOperatorToken, assignment.right.operator);
|
344
|
+
|
345
|
+
// -> foo ||= bar
|
346
|
+
const logicalOperatorToken = getOperatorToken(assignment.right);
|
347
|
+
const firstRightOperandToken = sourceCode.getTokenAfter(logicalOperatorToken);
|
348
|
+
|
349
|
+
yield ruleFixer.removeRange([assignment.right.range[0], firstRightOperandToken.range[0]]);
|
350
|
+
}
|
351
|
+
};
|
352
|
+
|
353
|
+
context.report(createConditionalFixer(descriptor, suggestion, cannotBeGetter(assignment.left)));
|
354
|
+
},
|
355
|
+
|
356
|
+
// foo || (foo = bar)
|
357
|
+
'LogicalExpression[right.type="AssignmentExpression"][right.operator="="]'(logical) {
|
358
|
+
|
359
|
+
// Right side has to be parenthesized, otherwise would be parsed as (foo || foo) = bar which is illegal
|
360
|
+
if (isReference(logical.left) && astUtils.isSameReference(logical.left, logical.right.left)) {
|
361
|
+
const descriptor = {
|
362
|
+
messageId: "logical",
|
363
|
+
node: logical,
|
364
|
+
data: { operator: `${logical.operator}=` }
|
365
|
+
};
|
366
|
+
const suggestion = {
|
367
|
+
messageId: "convertLogical",
|
368
|
+
data: { operator: `${logical.operator}=` },
|
369
|
+
*fix(ruleFixer) {
|
370
|
+
if (sourceCode.getCommentsInside(logical).length > 0) {
|
371
|
+
return;
|
372
|
+
}
|
373
|
+
|
374
|
+
const requiresOuterParenthesis = logical.parent.type !== "ExpressionStatement" &&
|
375
|
+
(astUtils.getPrecedence({ type: "AssignmentExpression" }) < astUtils.getPrecedence(logical.parent));
|
376
|
+
|
377
|
+
if (!astUtils.isParenthesised(sourceCode, logical) && requiresOuterParenthesis) {
|
378
|
+
yield ruleFixer.insertTextBefore(logical, "(");
|
379
|
+
yield ruleFixer.insertTextAfter(logical, ")");
|
380
|
+
}
|
381
|
+
|
382
|
+
// Also removes all opening parenthesis
|
383
|
+
yield ruleFixer.removeRange([logical.range[0], logical.right.range[0]]); // -> foo = bar)
|
384
|
+
|
385
|
+
// Also removes all ending parenthesis
|
386
|
+
yield ruleFixer.removeRange([logical.right.range[1], logical.range[1]]); // -> foo = bar
|
387
|
+
|
388
|
+
const operatorToken = getOperatorToken(logical.right);
|
389
|
+
|
390
|
+
yield ruleFixer.insertTextBefore(operatorToken, logical.operator); // -> foo ||= bar
|
391
|
+
}
|
392
|
+
};
|
393
|
+
const fix = cannotBeGetter(logical.left) || accessesSingleProperty(logical.left);
|
394
|
+
|
395
|
+
context.report(createConditionalFixer(descriptor, suggestion, fix));
|
396
|
+
}
|
397
|
+
},
|
398
|
+
|
399
|
+
// if (foo) foo = bar
|
400
|
+
"IfStatement[alternate=null]"(ifNode) {
|
401
|
+
if (!checkIf) {
|
402
|
+
return;
|
403
|
+
}
|
404
|
+
|
405
|
+
const hasBody = ifNode.consequent.type === "BlockStatement";
|
406
|
+
|
407
|
+
if (hasBody && ifNode.consequent.body.length !== 1) {
|
408
|
+
return;
|
409
|
+
}
|
410
|
+
|
411
|
+
const body = hasBody ? ifNode.consequent.body[0] : ifNode.consequent;
|
412
|
+
const scope = context.getScope();
|
413
|
+
const existence = getExistence(ifNode.test, scope);
|
414
|
+
|
415
|
+
if (
|
416
|
+
body.type === "ExpressionStatement" &&
|
417
|
+
body.expression.type === "AssignmentExpression" &&
|
418
|
+
body.expression.operator === "=" &&
|
419
|
+
existence !== null &&
|
420
|
+
astUtils.isSameReference(existence.reference, body.expression.left)
|
421
|
+
) {
|
422
|
+
const descriptor = {
|
423
|
+
messageId: "if",
|
424
|
+
node: ifNode,
|
425
|
+
data: { operator: `${existence.operator}=` }
|
426
|
+
};
|
427
|
+
const suggestion = {
|
428
|
+
messageId: "convertIf",
|
429
|
+
data: { operator: `${existence.operator}=` },
|
430
|
+
*fix(ruleFixer) {
|
431
|
+
if (sourceCode.getCommentsInside(ifNode).length > 0) {
|
432
|
+
return;
|
433
|
+
}
|
434
|
+
|
435
|
+
const firstBodyToken = sourceCode.getFirstToken(body);
|
436
|
+
const prevToken = sourceCode.getTokenBefore(ifNode);
|
437
|
+
|
438
|
+
if (
|
439
|
+
prevToken !== null &&
|
440
|
+
prevToken.value !== ";" &&
|
441
|
+
prevToken.value !== "{" &&
|
442
|
+
firstBodyToken.type !== "Identifier" &&
|
443
|
+
firstBodyToken.type !== "Keyword"
|
444
|
+
) {
|
445
|
+
|
446
|
+
// Do not fix if the fixed statement could be part of the previous statement (eg. fn() if (a == null) (a) = b --> fn()(a) ??= b)
|
447
|
+
return;
|
448
|
+
}
|
449
|
+
|
450
|
+
|
451
|
+
const operatorToken = getOperatorToken(body.expression);
|
452
|
+
|
453
|
+
yield ruleFixer.insertTextBefore(operatorToken, existence.operator); // -> if (foo) foo ||= bar
|
454
|
+
|
455
|
+
yield ruleFixer.removeRange([ifNode.range[0], body.range[0]]); // -> foo ||= bar
|
456
|
+
|
457
|
+
yield ruleFixer.removeRange([body.range[1], ifNode.range[1]]); // -> foo ||= bar, only present if "if" had a body
|
458
|
+
|
459
|
+
const nextToken = sourceCode.getTokenAfter(body.expression);
|
460
|
+
|
461
|
+
if (hasBody && (nextToken !== null && nextToken.value !== ";")) {
|
462
|
+
yield ruleFixer.insertTextAfter(ifNode, ";");
|
463
|
+
}
|
464
|
+
}
|
465
|
+
};
|
466
|
+
const shouldBeFixed = cannotBeGetter(existence.reference) ||
|
467
|
+
(ifNode.test.type !== "LogicalExpression" && accessesSingleProperty(existence.reference));
|
468
|
+
|
469
|
+
context.report(createConditionalFixer(descriptor, suggestion, shouldBeFixed));
|
470
|
+
}
|
471
|
+
}
|
472
|
+
};
|
473
|
+
}
|
474
|
+
};
|
@@ -105,7 +105,7 @@ module.exports = {
|
|
105
105
|
}
|
106
106
|
|
107
107
|
/**
|
108
|
-
* Converts an integer to
|
108
|
+
* Converts an integer to an object containing the integer's coefficient and order of magnitude
|
109
109
|
* @param {string} stringInteger the string representation of the integer being converted
|
110
110
|
* @returns {Object} the object containing the integer's coefficient and order of magnitude
|
111
111
|
*/
|
@@ -120,7 +120,7 @@ module.exports = {
|
|
120
120
|
|
121
121
|
/**
|
122
122
|
*
|
123
|
-
* Converts a float to
|
123
|
+
* Converts a float to an object containing the floats's coefficient and order of magnitude
|
124
124
|
* @param {string} stringFloat the string representation of the float being converted
|
125
125
|
* @returns {Object} the object containing the integer's coefficient and order of magnitude
|
126
126
|
*/
|
@@ -68,7 +68,7 @@ function isInClassStaticInitializerRange(node, location) {
|
|
68
68
|
}
|
69
69
|
|
70
70
|
/**
|
71
|
-
* Checks whether a given scope is the scope of a
|
71
|
+
* Checks whether a given scope is the scope of a class static initializer.
|
72
72
|
* Static initializers are static blocks and initializers of static fields.
|
73
73
|
* @param {eslint-scope.Scope} scope A scope to check.
|
74
74
|
* @returns {boolean} `true` if the scope is a class static initializer scope.
|
package/lib/rules/strict.js
CHANGED
@@ -105,7 +105,7 @@ module.exports = {
|
|
105
105
|
if (ecmaFeatures.impliedStrict) {
|
106
106
|
mode = "implied";
|
107
107
|
} else if (mode === "safe") {
|
108
|
-
mode = ecmaFeatures.globalReturn ? "global" : "function";
|
108
|
+
mode = ecmaFeatures.globalReturn || context.languageOptions.sourceType === "commonjs" ? "global" : "function";
|
109
109
|
}
|
110
110
|
|
111
111
|
/**
|
package/lib/shared/traverser.js
CHANGED
package/lib/shared/types.js
CHANGED
@@ -190,10 +190,23 @@ module.exports = {};
|
|
190
190
|
* @property {DeprecatedRuleInfo[]} usedDeprecatedRules The list of used deprecated rules.
|
191
191
|
*/
|
192
192
|
|
193
|
+
/**
|
194
|
+
* Information provided when the maximum warning threshold is exceeded.
|
195
|
+
* @typedef {Object} MaxWarningsExceeded
|
196
|
+
* @property {number} maxWarnings Number of warnings to trigger nonzero exit code.
|
197
|
+
* @property {number} foundWarnings Number of warnings found while linting.
|
198
|
+
*/
|
199
|
+
|
200
|
+
/**
|
201
|
+
* Metadata about results for formatters.
|
202
|
+
* @typedef {Object} ResultsMeta
|
203
|
+
* @property {MaxWarningsExceeded} [maxWarningsExceeded] Present if the maxWarnings threshold was exceeded.
|
204
|
+
*/
|
205
|
+
|
193
206
|
/**
|
194
207
|
* A formatter function.
|
195
208
|
* @callback FormatterFunction
|
196
209
|
* @param {LintResult[]} results The list of linting results.
|
197
|
-
* @param {{cwd: string, rulesMeta: Record<string, RuleMeta>}} [context] A context object.
|
210
|
+
* @param {{cwd: string, maxWarningsExceeded?: MaxWarningsExceeded, rulesMeta: Record<string, RuleMeta>}} [context] A context object.
|
198
211
|
* @returns {string | Promise<string>} Formatted text.
|
199
212
|
*/
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "eslint",
|
3
|
-
"version": "8.
|
3
|
+
"version": "8.25.0",
|
4
4
|
"author": "Nicholas C. Zakas <nicholas+npm@nczconsulting.com>",
|
5
5
|
"description": "An AST-based pattern checker for JavaScript.",
|
6
6
|
"bin": {
|
@@ -55,9 +55,8 @@
|
|
55
55
|
"homepage": "https://eslint.org",
|
56
56
|
"bugs": "https://github.com/eslint/eslint/issues/",
|
57
57
|
"dependencies": {
|
58
|
-
"@eslint/eslintrc": "^1.3.
|
59
|
-
"@humanwhocodes/config-array": "^0.10.
|
60
|
-
"@humanwhocodes/gitignore-to-minimatch": "^1.0.2",
|
58
|
+
"@eslint/eslintrc": "^1.3.3",
|
59
|
+
"@humanwhocodes/config-array": "^0.10.5",
|
61
60
|
"@humanwhocodes/module-importer": "^1.0.1",
|
62
61
|
"ajv": "^6.10.0",
|
63
62
|
"chalk": "^4.0.0",
|
@@ -121,7 +120,6 @@
|
|
121
120
|
"glob": "^7.1.6",
|
122
121
|
"got": "^11.8.3",
|
123
122
|
"gray-matter": "^4.0.3",
|
124
|
-
"jsdoc": "^3.5.5",
|
125
123
|
"karma": "^6.1.1",
|
126
124
|
"karma-chrome-launcher": "^3.1.0",
|
127
125
|
"karma-mocha": "^2.0.1",
|