eslint 7.1.0 → 7.2.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/CHANGELOG.md +19 -0
- package/README.md +2 -2
- package/lib/cli-engine/cli-engine.js +2 -2
- package/lib/cli-engine/config-array-factory.js +1 -1
- package/lib/init/config-initializer.js +2 -6
- package/lib/linter/code-path-analysis/code-path-analyzer.js +2 -2
- package/lib/linter/code-path-analysis/code-path-state.js +34 -12
- package/lib/linter/config-comment-parser.js +1 -1
- package/lib/rules/func-call-spacing.js +18 -3
- package/lib/rules/keyword-spacing.js +9 -2
- package/lib/rules/multiline-ternary.js +44 -25
- package/lib/rules/no-control-regex.js +1 -1
- package/lib/rules/no-extra-boolean-cast.js +3 -0
- package/lib/rules/no-extra-parens.js +30 -2
- package/lib/rules/no-invalid-regexp.js +1 -1
- package/lib/rules/no-misleading-character-class.js +1 -1
- package/lib/rules/no-mixed-operators.js +3 -2
- package/lib/rules/no-mixed-spaces-and-tabs.js +14 -6
- package/lib/rules/no-regex-spaces.js +1 -1
- package/lib/rules/no-restricted-exports.js +6 -0
- package/lib/rules/no-unneeded-ternary.js +6 -4
- package/lib/rules/no-unused-expressions.js +1 -1
- package/lib/rules/no-unused-vars.js +3 -1
- package/lib/rules/no-useless-backreference.js +1 -1
- package/lib/rules/prefer-named-capture-group.js +1 -1
- package/lib/rules/quote-props.js +2 -2
- package/lib/rules/template-tag-spacing.js +8 -2
- package/lib/rules/utils/ast-utils.js +55 -3
- package/package.json +5 -5
package/CHANGELOG.md
CHANGED
@@ -1,3 +1,22 @@
|
|
1
|
+
v7.2.0 - June 5, 2020
|
2
|
+
|
3
|
+
* [`b735a48`](https://github.com/eslint/eslint/commit/b735a485e77bcc791e4c4c6b8716801d94e98b2c) Update: add enforceForFunctionPrototypeMethods option to no-extra-parens (#12895) (Milos Djermanovic)
|
4
|
+
* [`27ef73f`](https://github.com/eslint/eslint/commit/27ef73ffb7428d5eff792d443186a2313e417bda) Update: reporter locr of func-call-spacing (refs #12334) (#13311) (Anix)
|
5
|
+
* [`353bfe9`](https://github.com/eslint/eslint/commit/353bfe9760ec640f470859855d4018df084a4e88) Update: handle parentheses in multiline-ternary (fixes #13195) (#13367) (Milos Djermanovic)
|
6
|
+
* [`a7fd343`](https://github.com/eslint/eslint/commit/a7fd343991cde99d8a219e3b25616db5792fe9a9) Update: keyword-spacing unexpected space loc improve (refs #12334) (#13377) (Anix)
|
7
|
+
* [`e49732e`](https://github.com/eslint/eslint/commit/e49732eb41bff6347ca7718c3c5ca1d13f1cd2d3) Fix: Ignore import expressions in no-unused-expressions rule (#13387) (Veniamin Krol)
|
8
|
+
* [`220349f`](https://github.com/eslint/eslint/commit/220349f5404060effe02fb5ec176a92e1383c3b5) Chore: Remove duplicate health files (#13380) (Nicholas C. Zakas)
|
9
|
+
* [`dd949ae`](https://github.com/eslint/eslint/commit/dd949aedb81fa772e10568920156daf075d25ea2) Update: support `??` operator, import.meta, and `export * as ns` (#13196) (Toru Nagashima)
|
10
|
+
* [`d5fce9f`](https://github.com/eslint/eslint/commit/d5fce9fa07e37ce61010a1fbb65964f1f7aefd82) Update: enable es2020 environment in --init (#13357) (Milos Djermanovic)
|
11
|
+
* [`21b1583`](https://github.com/eslint/eslint/commit/21b15832e326f96d349c063cd7e85e72c3abb670) Docs: fixed broken hash link for working-with-rules.md (#13386) (Yosuke Ota)
|
12
|
+
* [`b76aef7`](https://github.com/eslint/eslint/commit/b76aef778befb32afe7ad249934b132dc49713d2) Update: Improve report location for template-tag-spacing (refs #12334) (#13203) (Milos Djermanovic)
|
13
|
+
* [`578efad`](https://github.com/eslint/eslint/commit/578efad331b797e28c0f5f1547ce4769d2ea23ee) Chore: update no-unused-vars caughtErrors in eslint-config-eslint (#13351) (Milos Djermanovic)
|
14
|
+
* [`426088c`](https://github.com/eslint/eslint/commit/426088c966dc79dc338b33100f3adf827b147d69) Fix: no-unused-vars updated location to last reference (fixes #13181) (#13354) (Anix)
|
15
|
+
* [`cb50b69`](https://github.com/eslint/eslint/commit/cb50b69c08d4393e32d5c42c537d769c51dd34d8) Update: Improve location for no-mixed-spaces-and-tabs (refs #12334) (#13365) (Milos Djermanovic)
|
16
|
+
* [`f858f2a`](https://github.com/eslint/eslint/commit/f858f2a8f83232484491bd90b0bc5001b5056ad0) Chore: Add Tidelift to funding.yml (#13371) (Nicholas C. Zakas)
|
17
|
+
* [`ee30e5d`](https://github.com/eslint/eslint/commit/ee30e5d8bb1a4c82a2a3fbe1b9ee9f979b55c5c4) Sponsors: Sync README with website (ESLint Jenkins)
|
18
|
+
* [`c29bd9f`](https://github.com/eslint/eslint/commit/c29bd9f75582e5b1a403a8ffd0aafd1ffc8c58e1) Chore: Add breaking/core change link to issue templates (#13344) (Kai Cataldo)
|
19
|
+
* [`d55490f`](https://github.com/eslint/eslint/commit/d55490fa73ff69416de375e4c1cd67b6edba531c) Sponsors: Sync README with website (ESLint Jenkins)
|
1
20
|
v7.1.0 - May 22, 2020
|
2
21
|
|
3
22
|
* [`a93083a`](https://github.com/eslint/eslint/commit/a93083af89c6f9714dcdd4a7f27c8655a0b0dba6) Fix: astUtils.getNextLocation returns invalid location after CRLF (#13275) (Milos Djermanovic)
|
package/README.md
CHANGED
@@ -250,8 +250,8 @@ The following companies, organizations, and individuals support ESLint's ongoing
|
|
250
250
|
<!--sponsorsstart-->
|
251
251
|
<h3>Gold Sponsors</h3>
|
252
252
|
<p><a href="https://www.shopify.com"><img src="https://images.opencollective.com/shopify/e780cd4/logo.png" alt="Shopify" height="96"></a> <a href="https://www.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>
|
253
|
-
<p><a href="https://www.ampproject.org/"><img src="https://images.opencollective.com/amp/c8a3b25/logo.png" alt="AMP Project" height="64"></a></p><h3>Bronze Sponsors</h3>
|
254
|
-
<p><a href="https://bruce.agency"><img src="https://images.opencollective.com/brucemade/0c70c59/logo.png" alt="Bruce" height="32"></a> <a href="https://edubirdie.com/"><img src="https://images.opencollective.com/edubirdie2/b1d51ab/logo.png" alt="EduBirdie" height="32"></a> <a href="https://www.casinotop.com/"><img src="https://images.opencollective.com/casinotop-com/10fd95b/logo.png" alt="CasinoTop.com" height="32"></a> <a href="https://www.casinotopp.net/"><img src="https://images.opencollective.com/casino-topp/1dd399a/logo.png" alt="Casino Topp" height="32"></a> <a href="https://writersperhour.com/
|
253
|
+
<p><a href="https://liftoff.io/"><img src="https://images.opencollective.com/liftoff/5c4fa84/logo.png" alt="Liftoff" height="64"></a> <a href="https://www.ampproject.org/"><img src="https://images.opencollective.com/amp/c8a3b25/logo.png" alt="AMP Project" height="64"></a></p><h3>Bronze Sponsors</h3>
|
254
|
+
<p><a href="https://bruce.agency"><img src="https://images.opencollective.com/brucemade/0c70c59/logo.png" alt="Bruce" height="32"></a> <a href="https://edubirdie.com/"><img src="https://images.opencollective.com/edubirdie2/b1d51ab/logo.png" alt="EduBirdie" height="32"></a> <a href="https://www.casinotop.com/"><img src="https://images.opencollective.com/casinotop-com/10fd95b/logo.png" alt="CasinoTop.com" height="32"></a> <a href="https://www.casinotopp.net/"><img src="https://images.opencollective.com/casino-topp/1dd399a/logo.png" alt="Casino Topp" height="32"></a> <a href="https://writersperhour.com/write-my-essay"><img src="https://images.opencollective.com/writersperhour/5787d4b/logo.png" alt="Writers Per Hour" 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://cooltechzone.com/netflix-vpn"><img src="https://images.opencollective.com/vpn-netflix/4850160/logo.png" alt="vpn netflix" height="32"></a> <a href="https://www.kasinot.fi"><img src="https://images.opencollective.com/kasinot-fi/e09aa2e/logo.png" alt="Kasinot.fi" height="32"></a> <a href="https://www.pelisivut.com"><img src="https://images.opencollective.com/pelisivut/04f08f2/logo.png" alt="Pelisivut" height="32"></a> <a href="https://www.nettikasinot.org"><img src="https://images.opencollective.com/nettikasinot-org/bbd887f/logo.png" alt="Nettikasinot.org" height="32"></a> <a href="https://www.bonus.com.de/freispiele"><img src="https://images.opencollective.com/bonusfinder-deutschland/646169e/logo.png" alt="BonusFinder Deutschland" height="32"></a> <a href="https://www.bugsnag.com/platforms?utm_source=Open Collective&utm_medium=Website&utm_content=open-source&utm_campaign=2019-community&utm_term="><img src="https://images.opencollective.com/bugsnag-stability-monitoring/c2cef36/logo.png" alt="Bugsnag Stability Monitoring" height="32"></a> <a href="https://mixpanel.com"><img src="https://images.opencollective.com/mixpanel/cd682f7/logo.png" alt="Mixpanel" height="32"></a> <a href="https://www.vpsserver.com"><img src="https://images.opencollective.com/vpsservercom/logo.png" alt="VPS Server" height="32"></a> <a href="https://icons8.com"><img src="https://images.opencollective.com/icons8/0b37d14/logo.png" alt="Free Icons by Icons8" height="32"></a> <a href="https://discordapp.com"><img src="https://images.opencollective.com/discordapp/7e3d9a9/logo.png" alt="Discord" height="32"></a> <a href="https://themeisle.com"><img src="https://images.opencollective.com/themeisle/d5592fe/logo.png" alt="ThemeIsle" height="32"></a> <a href="https://tekhattan.com"><img src="https://images.opencollective.com/tekhattan/bc73c28/logo.png" alt="TekHattan" height="32"></a> <a href="https://www.marfeel.com/"><img src="https://images.opencollective.com/marfeel/4b88e30/logo.png" alt="Marfeel" height="32"></a> <a href="http://www.firesticktricks.com"><img src="https://images.opencollective.com/fire-stick-tricks/b8fbe2c/logo.png" alt="Fire Stick Tricks" height="32"></a></p>
|
255
255
|
<!--sponsorsend-->
|
256
256
|
|
257
257
|
## <a name="technology-sponsors"></a>Technology Sponsors
|
@@ -403,7 +403,7 @@ function getCacheFile(cacheFile, cwd) {
|
|
403
403
|
|
404
404
|
try {
|
405
405
|
fileStats = fs.lstatSync(resolvedCacheFile);
|
406
|
-
} catch
|
406
|
+
} catch {
|
407
407
|
fileStats = null;
|
408
408
|
}
|
409
409
|
|
@@ -991,7 +991,7 @@ class CLIEngine {
|
|
991
991
|
const npmFormat = naming.normalizePackageName(normalizedFormatName, "eslint-formatter");
|
992
992
|
|
993
993
|
formatterPath = ModuleResolver.resolve(npmFormat, path.join(cwd, "__placeholder__.js"));
|
994
|
-
} catch
|
994
|
+
} catch {
|
995
995
|
formatterPath = path.resolve(__dirname, "formatters", normalizedFormatName);
|
996
996
|
}
|
997
997
|
}
|
@@ -265,11 +265,7 @@ function processAnswers(answers) {
|
|
265
265
|
};
|
266
266
|
|
267
267
|
config.parserOptions.ecmaVersion = espree.latestEcmaVersion;
|
268
|
-
config.env.
|
269
|
-
config.globals = {
|
270
|
-
Atomics: "readonly",
|
271
|
-
SharedArrayBuffer: "readonly"
|
272
|
-
};
|
268
|
+
config.env.es2020 = true;
|
273
269
|
|
274
270
|
// set the module type
|
275
271
|
if (answers.moduleType === "esm") {
|
@@ -350,7 +346,7 @@ function getLocalESLintVersion() {
|
|
350
346
|
const eslint = require(eslintPath);
|
351
347
|
|
352
348
|
return eslint.linter.version || null;
|
353
|
-
} catch
|
349
|
+
} catch {
|
354
350
|
return null;
|
355
351
|
}
|
356
352
|
}
|
@@ -33,10 +33,10 @@ function isCaseNode(node) {
|
|
33
33
|
* Checks whether the given logical operator is taken into account for the code
|
34
34
|
* path analysis.
|
35
35
|
* @param {string} operator The operator found in the LogicalExpression node
|
36
|
-
* @returns {boolean} `true` if the operator is "&&" or "||"
|
36
|
+
* @returns {boolean} `true` if the operator is "&&" or "||" or "??"
|
37
37
|
*/
|
38
38
|
function isHandledLogicalOperator(operator) {
|
39
|
-
return operator === "&&" || operator === "||";
|
39
|
+
return operator === "&&" || operator === "||" || operator === "??";
|
40
40
|
}
|
41
41
|
|
42
42
|
/**
|
@@ -201,6 +201,7 @@ function finalizeTestSegmentsOfFor(context, choiceContext, head) {
|
|
201
201
|
if (!choiceContext.processed) {
|
202
202
|
choiceContext.trueForkContext.add(head);
|
203
203
|
choiceContext.falseForkContext.add(head);
|
204
|
+
choiceContext.qqForkContext.add(head);
|
204
205
|
}
|
205
206
|
|
206
207
|
if (context.test !== true) {
|
@@ -351,6 +352,7 @@ class CodePathState {
|
|
351
352
|
isForkingAsResult,
|
352
353
|
trueForkContext: ForkContext.newEmpty(this.forkContext),
|
353
354
|
falseForkContext: ForkContext.newEmpty(this.forkContext),
|
355
|
+
qqForkContext: ForkContext.newEmpty(this.forkContext),
|
354
356
|
processed: false
|
355
357
|
};
|
356
358
|
}
|
@@ -370,6 +372,7 @@ class CodePathState {
|
|
370
372
|
switch (context.kind) {
|
371
373
|
case "&&":
|
372
374
|
case "||":
|
375
|
+
case "??":
|
373
376
|
|
374
377
|
/*
|
375
378
|
* If any result were not transferred from child contexts,
|
@@ -379,6 +382,7 @@ class CodePathState {
|
|
379
382
|
if (!context.processed) {
|
380
383
|
context.trueForkContext.add(headSegments);
|
381
384
|
context.falseForkContext.add(headSegments);
|
385
|
+
context.qqForkContext.add(headSegments);
|
382
386
|
}
|
383
387
|
|
384
388
|
/*
|
@@ -390,6 +394,7 @@ class CodePathState {
|
|
390
394
|
|
391
395
|
parentContext.trueForkContext.addAll(context.trueForkContext);
|
392
396
|
parentContext.falseForkContext.addAll(context.falseForkContext);
|
397
|
+
parentContext.qqForkContext.addAll(context.qqForkContext);
|
393
398
|
parentContext.processed = true;
|
394
399
|
|
395
400
|
return context;
|
@@ -456,13 +461,24 @@ class CodePathState {
|
|
456
461
|
* This got segments already from the child choice context.
|
457
462
|
* Creates the next path from own true/false fork context.
|
458
463
|
*/
|
459
|
-
|
460
|
-
|
461
|
-
|
464
|
+
let prevForkContext;
|
465
|
+
|
466
|
+
switch (context.kind) {
|
467
|
+
case "&&": // if true then go to the right-hand side.
|
468
|
+
prevForkContext = context.trueForkContext;
|
469
|
+
break;
|
470
|
+
case "||": // if false then go to the right-hand side.
|
471
|
+
prevForkContext = context.falseForkContext;
|
472
|
+
break;
|
473
|
+
case "??": // Both true/false can short-circuit, so needs the third path to go to the right-hand side. That's qqForkContext.
|
474
|
+
prevForkContext = context.qqForkContext;
|
475
|
+
break;
|
476
|
+
default:
|
477
|
+
throw new Error("unreachable");
|
478
|
+
}
|
462
479
|
|
463
480
|
forkContext.replaceHead(prevForkContext.makeNext(0, -1));
|
464
481
|
prevForkContext.clear();
|
465
|
-
|
466
482
|
context.processed = false;
|
467
483
|
} else {
|
468
484
|
|
@@ -471,14 +487,19 @@ class CodePathState {
|
|
471
487
|
* So addresses the head segments.
|
472
488
|
* The head segments are the path of the left-hand operand.
|
473
489
|
*/
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
490
|
+
switch (context.kind) {
|
491
|
+
case "&&": // the false path can short-circuit.
|
492
|
+
context.falseForkContext.add(forkContext.head);
|
493
|
+
break;
|
494
|
+
case "||": // the true path can short-circuit.
|
495
|
+
context.trueForkContext.add(forkContext.head);
|
496
|
+
break;
|
497
|
+
case "??": // both can short-circuit.
|
498
|
+
context.trueForkContext.add(forkContext.head);
|
499
|
+
context.falseForkContext.add(forkContext.head);
|
500
|
+
break;
|
501
|
+
default:
|
502
|
+
throw new Error("unreachable");
|
482
503
|
}
|
483
504
|
|
484
505
|
forkContext.replaceHead(forkContext.makeNext(-1, -1));
|
@@ -501,6 +522,7 @@ class CodePathState {
|
|
501
522
|
if (!context.processed) {
|
502
523
|
context.trueForkContext.add(forkContext.head);
|
503
524
|
context.falseForkContext.add(forkContext.head);
|
525
|
+
context.qqForkContext.add(forkContext.head);
|
504
526
|
}
|
505
527
|
|
506
528
|
context.processed = false;
|
@@ -116,7 +116,13 @@ module.exports = {
|
|
116
116
|
if (never && hasWhitespace) {
|
117
117
|
context.report({
|
118
118
|
node,
|
119
|
-
loc:
|
119
|
+
loc: {
|
120
|
+
start: leftToken.loc.end,
|
121
|
+
end: {
|
122
|
+
line: rightToken.loc.start.line,
|
123
|
+
column: rightToken.loc.start.column - 1
|
124
|
+
}
|
125
|
+
},
|
120
126
|
messageId: "unexpectedWhitespace",
|
121
127
|
fix(fixer) {
|
122
128
|
|
@@ -134,7 +140,13 @@ module.exports = {
|
|
134
140
|
} else if (!never && !hasWhitespace) {
|
135
141
|
context.report({
|
136
142
|
node,
|
137
|
-
loc:
|
143
|
+
loc: {
|
144
|
+
start: {
|
145
|
+
line: leftToken.loc.end.line,
|
146
|
+
column: leftToken.loc.end.column - 1
|
147
|
+
},
|
148
|
+
end: rightToken.loc.start
|
149
|
+
},
|
138
150
|
messageId: "missing",
|
139
151
|
fix(fixer) {
|
140
152
|
return fixer.insertTextBefore(rightToken, " ");
|
@@ -143,7 +155,10 @@ module.exports = {
|
|
143
155
|
} else if (!never && !allowNewlines && hasNewline) {
|
144
156
|
context.report({
|
145
157
|
node,
|
146
|
-
loc:
|
158
|
+
loc: {
|
159
|
+
start: leftToken.loc.end,
|
160
|
+
end: rightToken.loc.start
|
161
|
+
},
|
147
162
|
messageId: "unexpectedNewline",
|
148
163
|
fix(fixer) {
|
149
164
|
return fixer.replaceTextRange([leftToken.range[1], rightToken.range[0]], " ");
|
@@ -152,7 +152,7 @@ module.exports = {
|
|
152
152
|
sourceCode.isSpaceBetweenTokens(prevToken, token)
|
153
153
|
) {
|
154
154
|
context.report({
|
155
|
-
loc: token.loc.start,
|
155
|
+
loc: { start: prevToken.loc.end, end: token.loc.start },
|
156
156
|
messageId: "unexpectedBefore",
|
157
157
|
data: token,
|
158
158
|
fix(fixer) {
|
@@ -203,8 +203,9 @@ module.exports = {
|
|
203
203
|
astUtils.isTokenOnSameLine(token, nextToken) &&
|
204
204
|
sourceCode.isSpaceBetweenTokens(token, nextToken)
|
205
205
|
) {
|
206
|
+
|
206
207
|
context.report({
|
207
|
-
loc: token.loc.start,
|
208
|
+
loc: { start: token.loc.end, end: nextToken.loc.start },
|
208
209
|
messageId: "unexpectedAfter",
|
209
210
|
data: token,
|
210
211
|
fix(fixer) {
|
@@ -442,6 +443,12 @@ module.exports = {
|
|
442
443
|
checkSpacingAround(sourceCode.getTokenAfter(firstToken));
|
443
444
|
}
|
444
445
|
|
446
|
+
if (node.type === "ExportAllDeclaration" && node.exported) {
|
447
|
+
const asToken = sourceCode.getTokenBefore(node.exported);
|
448
|
+
|
449
|
+
checkSpacingBefore(asToken, PREV_TOKEN_M);
|
450
|
+
}
|
451
|
+
|
445
452
|
if (node.source) {
|
446
453
|
const fromToken = sourceCode.getTokenBefore(node.source);
|
447
454
|
|
@@ -39,25 +39,7 @@ module.exports = {
|
|
39
39
|
const option = context.options[0];
|
40
40
|
const multiline = option !== "never";
|
41
41
|
const allowSingleLine = option === "always-multiline";
|
42
|
-
|
43
|
-
//--------------------------------------------------------------------------
|
44
|
-
// Helpers
|
45
|
-
//--------------------------------------------------------------------------
|
46
|
-
|
47
|
-
/**
|
48
|
-
* Tests whether node is preceded by supplied tokens
|
49
|
-
* @param {ASTNode} node node to check
|
50
|
-
* @param {ASTNode} parentNode parent of node to report
|
51
|
-
* @param {boolean} expected whether newline was expected or not
|
52
|
-
* @returns {void}
|
53
|
-
* @private
|
54
|
-
*/
|
55
|
-
function reportError(node, parentNode, expected) {
|
56
|
-
context.report({
|
57
|
-
node,
|
58
|
-
messageId: `${expected ? "expected" : "unexpected"}${node === parentNode.test ? "TestCons" : "ConsAlt"}`
|
59
|
-
});
|
60
|
-
}
|
42
|
+
const sourceCode = context.getSourceCode();
|
61
43
|
|
62
44
|
//--------------------------------------------------------------------------
|
63
45
|
// Public
|
@@ -65,16 +47,39 @@ module.exports = {
|
|
65
47
|
|
66
48
|
return {
|
67
49
|
ConditionalExpression(node) {
|
68
|
-
const
|
69
|
-
const
|
50
|
+
const questionToken = sourceCode.getTokenAfter(node.test, astUtils.isNotClosingParenToken);
|
51
|
+
const colonToken = sourceCode.getTokenAfter(node.consequent, astUtils.isNotClosingParenToken);
|
52
|
+
|
53
|
+
const firstTokenOfTest = sourceCode.getFirstToken(node);
|
54
|
+
const lastTokenOfTest = sourceCode.getTokenBefore(questionToken);
|
55
|
+
const firstTokenOfConsequent = sourceCode.getTokenAfter(questionToken);
|
56
|
+
const lastTokenOfConsequent = sourceCode.getTokenBefore(colonToken);
|
57
|
+
const firstTokenOfAlternate = sourceCode.getTokenAfter(colonToken);
|
58
|
+
|
59
|
+
const areTestAndConsequentOnSameLine = astUtils.isTokenOnSameLine(lastTokenOfTest, firstTokenOfConsequent);
|
60
|
+
const areConsequentAndAlternateOnSameLine = astUtils.isTokenOnSameLine(lastTokenOfConsequent, firstTokenOfAlternate);
|
70
61
|
|
71
62
|
if (!multiline) {
|
72
63
|
if (!areTestAndConsequentOnSameLine) {
|
73
|
-
|
64
|
+
context.report({
|
65
|
+
node: node.test,
|
66
|
+
loc: {
|
67
|
+
start: firstTokenOfTest.loc.start,
|
68
|
+
end: lastTokenOfTest.loc.end
|
69
|
+
},
|
70
|
+
messageId: "unexpectedTestCons"
|
71
|
+
});
|
74
72
|
}
|
75
73
|
|
76
74
|
if (!areConsequentAndAlternateOnSameLine) {
|
77
|
-
|
75
|
+
context.report({
|
76
|
+
node: node.consequent,
|
77
|
+
loc: {
|
78
|
+
start: firstTokenOfConsequent.loc.start,
|
79
|
+
end: lastTokenOfConsequent.loc.end
|
80
|
+
},
|
81
|
+
messageId: "unexpectedConsAlt"
|
82
|
+
});
|
78
83
|
}
|
79
84
|
} else {
|
80
85
|
if (allowSingleLine && node.loc.start.line === node.loc.end.line) {
|
@@ -82,11 +87,25 @@ module.exports = {
|
|
82
87
|
}
|
83
88
|
|
84
89
|
if (areTestAndConsequentOnSameLine) {
|
85
|
-
|
90
|
+
context.report({
|
91
|
+
node: node.test,
|
92
|
+
loc: {
|
93
|
+
start: firstTokenOfTest.loc.start,
|
94
|
+
end: lastTokenOfTest.loc.end
|
95
|
+
},
|
96
|
+
messageId: "expectedTestCons"
|
97
|
+
});
|
86
98
|
}
|
87
99
|
|
88
100
|
if (areConsequentAndAlternateOnSameLine) {
|
89
|
-
|
101
|
+
context.report({
|
102
|
+
node: node.consequent,
|
103
|
+
loc: {
|
104
|
+
start: firstTokenOfConsequent.loc.start,
|
105
|
+
end: lastTokenOfConsequent.loc.end
|
106
|
+
},
|
107
|
+
messageId: "expectedConsAlt"
|
108
|
+
});
|
90
109
|
}
|
91
110
|
}
|
92
111
|
}
|
@@ -172,6 +172,9 @@ module.exports = {
|
|
172
172
|
case "UnaryExpression":
|
173
173
|
return precedence(node) < precedence(parent);
|
174
174
|
case "LogicalExpression":
|
175
|
+
if (astUtils.isMixedLogicalAndCoalesceExpressions(node, parent)) {
|
176
|
+
return true;
|
177
|
+
}
|
175
178
|
if (previousNode === parent.left) {
|
176
179
|
return precedence(node) < precedence(parent);
|
177
180
|
}
|
@@ -51,7 +51,8 @@ module.exports = {
|
|
51
51
|
ignoreJSX: { enum: ["none", "all", "single-line", "multi-line"] },
|
52
52
|
enforceForArrowConditionals: { type: "boolean" },
|
53
53
|
enforceForSequenceExpressions: { type: "boolean" },
|
54
|
-
enforceForNewInMemberExpressions: { type: "boolean" }
|
54
|
+
enforceForNewInMemberExpressions: { type: "boolean" },
|
55
|
+
enforceForFunctionPrototypeMethods: { type: "boolean" }
|
55
56
|
},
|
56
57
|
additionalProperties: false
|
57
58
|
}
|
@@ -83,12 +84,28 @@ module.exports = {
|
|
83
84
|
context.options[1].enforceForSequenceExpressions === false;
|
84
85
|
const IGNORE_NEW_IN_MEMBER_EXPR = ALL_NODES && context.options[1] &&
|
85
86
|
context.options[1].enforceForNewInMemberExpressions === false;
|
87
|
+
const IGNORE_FUNCTION_PROTOTYPE_METHODS = ALL_NODES && context.options[1] &&
|
88
|
+
context.options[1].enforceForFunctionPrototypeMethods === false;
|
86
89
|
|
87
90
|
const PRECEDENCE_OF_ASSIGNMENT_EXPR = precedence({ type: "AssignmentExpression" });
|
88
91
|
const PRECEDENCE_OF_UPDATE_EXPR = precedence({ type: "UpdateExpression" });
|
89
92
|
|
90
93
|
let reportsBuffer;
|
91
94
|
|
95
|
+
/**
|
96
|
+
* Determines whether the given node is a `call` or `apply` method call, invoked directly on a `FunctionExpression` node.
|
97
|
+
* Example: function(){}.call()
|
98
|
+
* @param {ASTNode} node The node to be checked.
|
99
|
+
* @returns {boolean} True if the node is an immediate `call` or `apply` method call.
|
100
|
+
* @private
|
101
|
+
*/
|
102
|
+
function isImmediateFunctionPrototypeMethodCall(node) {
|
103
|
+
return node.type === "CallExpression" &&
|
104
|
+
node.callee.type === "MemberExpression" &&
|
105
|
+
node.callee.object.type === "FunctionExpression" &&
|
106
|
+
["call", "apply"].includes(astUtils.getStaticPropertyName(node.callee));
|
107
|
+
}
|
108
|
+
|
92
109
|
/**
|
93
110
|
* Determines if this rule should be enforced for a node given the current configuration.
|
94
111
|
* @param {ASTNode} node The node to be checked.
|
@@ -125,6 +142,10 @@ module.exports = {
|
|
125
142
|
return false;
|
126
143
|
}
|
127
144
|
|
145
|
+
if (isImmediateFunctionPrototypeMethodCall(node) && IGNORE_FUNCTION_PROTOTYPE_METHODS) {
|
146
|
+
return false;
|
147
|
+
}
|
148
|
+
|
128
149
|
return ALL_NODES || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression";
|
129
150
|
}
|
130
151
|
|
@@ -478,6 +499,7 @@ module.exports = {
|
|
478
499
|
if (!shouldSkipLeft && hasExcessParens(node.left)) {
|
479
500
|
if (
|
480
501
|
!(node.left.type === "UnaryExpression" && isExponentiation) &&
|
502
|
+
!astUtils.isMixedLogicalAndCoalesceExpressions(node.left, node) &&
|
481
503
|
(leftPrecedence > prec || (leftPrecedence === prec && !isExponentiation)) ||
|
482
504
|
isParenthesisedTwice(node.left)
|
483
505
|
) {
|
@@ -487,6 +509,7 @@ module.exports = {
|
|
487
509
|
|
488
510
|
if (!shouldSkipRight && hasExcessParens(node.right)) {
|
489
511
|
if (
|
512
|
+
!astUtils.isMixedLogicalAndCoalesceExpressions(node.right, node) &&
|
490
513
|
(rightPrecedence > prec || (rightPrecedence === prec && isExponentiation)) ||
|
491
514
|
isParenthesisedTwice(node.right)
|
492
515
|
) {
|
@@ -927,7 +950,12 @@ module.exports = {
|
|
927
950
|
LogicalExpression: checkBinaryLogical,
|
928
951
|
|
929
952
|
MemberExpression(node) {
|
930
|
-
const nodeObjHasExcessParens = hasExcessParens(node.object)
|
953
|
+
const nodeObjHasExcessParens = hasExcessParens(node.object) &&
|
954
|
+
!(
|
955
|
+
isImmediateFunctionPrototypeMethodCall(node.parent) &&
|
956
|
+
node.parent.callee === node &&
|
957
|
+
IGNORE_FUNCTION_PROTOTYPE_METHODS
|
958
|
+
);
|
931
959
|
|
932
960
|
if (
|
933
961
|
nodeObjHasExcessParens &&
|
@@ -21,13 +21,15 @@ const COMPARISON_OPERATORS = ["==", "!=", "===", "!==", ">", ">=", "<", "<="];
|
|
21
21
|
const LOGICAL_OPERATORS = ["&&", "||"];
|
22
22
|
const RELATIONAL_OPERATORS = ["in", "instanceof"];
|
23
23
|
const TERNARY_OPERATOR = ["?:"];
|
24
|
+
const COALESCE_OPERATOR = ["??"];
|
24
25
|
const ALL_OPERATORS = [].concat(
|
25
26
|
ARITHMETIC_OPERATORS,
|
26
27
|
BITWISE_OPERATORS,
|
27
28
|
COMPARISON_OPERATORS,
|
28
29
|
LOGICAL_OPERATORS,
|
29
30
|
RELATIONAL_OPERATORS,
|
30
|
-
TERNARY_OPERATOR
|
31
|
+
TERNARY_OPERATOR,
|
32
|
+
COALESCE_OPERATOR
|
31
33
|
);
|
32
34
|
const DEFAULT_GROUPS = [
|
33
35
|
ARITHMETIC_OPERATORS,
|
@@ -236,7 +238,6 @@ module.exports = {
|
|
236
238
|
return {
|
237
239
|
BinaryExpression: check,
|
238
240
|
LogicalExpression: check
|
239
|
-
|
240
241
|
};
|
241
242
|
}
|
242
243
|
};
|
@@ -67,7 +67,7 @@ module.exports = {
|
|
67
67
|
* or the reverse before non-tab/-space
|
68
68
|
* characters begin.
|
69
69
|
*/
|
70
|
-
let regex = /^(?=
|
70
|
+
let regex = /^(?=( +|\t+))\1(?:\t| )/u;
|
71
71
|
|
72
72
|
if (smartTabs) {
|
73
73
|
|
@@ -75,19 +75,27 @@ module.exports = {
|
|
75
75
|
* At least one space followed by a tab
|
76
76
|
* before non-tab/-space characters begin.
|
77
77
|
*/
|
78
|
-
regex = /^(?=
|
78
|
+
regex = /^(?=(\t*))\1(?=( +))\2\t/u;
|
79
79
|
}
|
80
80
|
|
81
81
|
lines.forEach((line, i) => {
|
82
82
|
const match = regex.exec(line);
|
83
83
|
|
84
84
|
if (match) {
|
85
|
-
const lineNumber = i + 1
|
86
|
-
|
87
|
-
|
85
|
+
const lineNumber = i + 1;
|
86
|
+
const loc = {
|
87
|
+
start: {
|
88
|
+
line: lineNumber,
|
89
|
+
column: match[0].length - 2
|
90
|
+
},
|
91
|
+
end: {
|
92
|
+
line: lineNumber,
|
93
|
+
column: match[0].length
|
94
|
+
}
|
95
|
+
};
|
88
96
|
|
89
97
|
if (!ignoredCommentLines.has(lineNumber)) {
|
90
|
-
const containingNode = sourceCode.getNodeByRangeIndex(sourceCode.getIndexFromLoc(loc));
|
98
|
+
const containingNode = sourceCode.getNodeByRangeIndex(sourceCode.getIndexFromLoc(loc.start));
|
91
99
|
|
92
100
|
if (!(containingNode && ["Literal", "TemplateElement"].includes(containingNode.type))) {
|
93
101
|
context.report({
|
@@ -147,10 +147,12 @@ module.exports = {
|
|
147
147
|
loc: node.consequent.loc.start,
|
148
148
|
messageId: "unnecessaryConditionalAssignment",
|
149
149
|
fix: fixer => {
|
150
|
-
const shouldParenthesizeAlternate =
|
151
|
-
|
152
|
-
|
153
|
-
|
150
|
+
const shouldParenthesizeAlternate =
|
151
|
+
(
|
152
|
+
astUtils.getPrecedence(node.alternate) < OR_PRECEDENCE ||
|
153
|
+
astUtils.isCoalesceExpression(node.alternate)
|
154
|
+
) &&
|
155
|
+
!astUtils.isParenthesised(sourceCode, node.alternate);
|
154
156
|
const alternateText = shouldParenthesizeAlternate
|
155
157
|
? `(${sourceCode.getText(node.alternate)})`
|
156
158
|
: astUtils.getParenthesisedText(sourceCode, node.alternate);
|
@@ -124,7 +124,7 @@ module.exports = {
|
|
124
124
|
return true;
|
125
125
|
}
|
126
126
|
|
127
|
-
return /^(?:Assignment|Call|New|Update|Yield|Await)Expression$/u.test(node.type) ||
|
127
|
+
return /^(?:Assignment|Call|New|Update|Yield|Await|Import)Expression$/u.test(node.type) ||
|
128
128
|
(node.type === "UnaryExpression" && ["delete", "void"].indexOf(node.operator) >= 0);
|
129
129
|
}
|
130
130
|
|
@@ -619,7 +619,9 @@ module.exports = {
|
|
619
619
|
// Report the first declaration.
|
620
620
|
if (unusedVar.defs.length > 0) {
|
621
621
|
context.report({
|
622
|
-
node: unusedVar.
|
622
|
+
node: unusedVar.references.length ? unusedVar.references[
|
623
|
+
unusedVar.references.length - 1
|
624
|
+
].identifier : unusedVar.identifiers[0],
|
623
625
|
messageId: "unusedVar",
|
624
626
|
data: unusedVar.references.some(ref => ref.isWrite())
|
625
627
|
? getAssignedMessageData(unusedVar)
|
package/lib/rules/quote-props.js
CHANGED
@@ -154,7 +154,7 @@ module.exports = {
|
|
154
154
|
|
155
155
|
try {
|
156
156
|
tokens = espree.tokenize(key.value);
|
157
|
-
} catch
|
157
|
+
} catch {
|
158
158
|
return;
|
159
159
|
}
|
160
160
|
|
@@ -239,7 +239,7 @@ module.exports = {
|
|
239
239
|
|
240
240
|
try {
|
241
241
|
tokens = espree.tokenize(key.value);
|
242
|
-
} catch
|
242
|
+
} catch {
|
243
243
|
necessaryQuotes = true;
|
244
244
|
return;
|
245
245
|
}
|
@@ -49,7 +49,10 @@ module.exports = {
|
|
49
49
|
if (never && hasWhitespace) {
|
50
50
|
context.report({
|
51
51
|
node,
|
52
|
-
loc:
|
52
|
+
loc: {
|
53
|
+
start: tagToken.loc.end,
|
54
|
+
end: literalToken.loc.start
|
55
|
+
},
|
53
56
|
messageId: "unexpected",
|
54
57
|
fix(fixer) {
|
55
58
|
const comments = sourceCode.getCommentsBefore(node.quasi);
|
@@ -68,7 +71,10 @@ module.exports = {
|
|
68
71
|
} else if (!never && !hasWhitespace) {
|
69
72
|
context.report({
|
70
73
|
node,
|
71
|
-
loc:
|
74
|
+
loc: {
|
75
|
+
start: node.loc.start,
|
76
|
+
end: literalToken.loc.start
|
77
|
+
},
|
72
78
|
messageId: "missing",
|
73
79
|
fix(fixer) {
|
74
80
|
return fixer.insertTextAfter(tagToken, " ");
|
@@ -416,6 +416,53 @@ function equalTokens(left, right, sourceCode) {
|
|
416
416
|
return true;
|
417
417
|
}
|
418
418
|
|
419
|
+
/**
|
420
|
+
* Check if the given node is a true logical expression or not.
|
421
|
+
*
|
422
|
+
* The three binary expressions logical-or (`||`), logical-and (`&&`), and
|
423
|
+
* coalesce (`??`) are known as `ShortCircuitExpression`.
|
424
|
+
* But ESTree represents those by `LogicalExpression` node.
|
425
|
+
*
|
426
|
+
* This function rejects coalesce expressions of `LogicalExpression` node.
|
427
|
+
* @param {ASTNode} node The node to check.
|
428
|
+
* @returns {boolean} `true` if the node is `&&` or `||`.
|
429
|
+
* @see https://tc39.es/ecma262/#prod-ShortCircuitExpression
|
430
|
+
*/
|
431
|
+
function isLogicalExpression(node) {
|
432
|
+
return (
|
433
|
+
node.type === "LogicalExpression" &&
|
434
|
+
(node.operator === "&&" || node.operator === "||")
|
435
|
+
);
|
436
|
+
}
|
437
|
+
|
438
|
+
/**
|
439
|
+
* Check if the given node is a nullish coalescing expression or not.
|
440
|
+
*
|
441
|
+
* The three binary expressions logical-or (`||`), logical-and (`&&`), and
|
442
|
+
* coalesce (`??`) are known as `ShortCircuitExpression`.
|
443
|
+
* But ESTree represents those by `LogicalExpression` node.
|
444
|
+
*
|
445
|
+
* This function finds only coalesce expressions of `LogicalExpression` node.
|
446
|
+
* @param {ASTNode} node The node to check.
|
447
|
+
* @returns {boolean} `true` if the node is `??`.
|
448
|
+
*/
|
449
|
+
function isCoalesceExpression(node) {
|
450
|
+
return node.type === "LogicalExpression" && node.operator === "??";
|
451
|
+
}
|
452
|
+
|
453
|
+
/**
|
454
|
+
* Check if given two nodes are the pair of a logical expression and a coalesce expression.
|
455
|
+
* @param {ASTNode} left A node to check.
|
456
|
+
* @param {ASTNode} right Another node to check.
|
457
|
+
* @returns {boolean} `true` if the two nodes are the pair of a logical expression and a coalesce expression.
|
458
|
+
*/
|
459
|
+
function isMixedLogicalAndCoalesceExpressions(left, right) {
|
460
|
+
return (
|
461
|
+
(isLogicalExpression(left) && isCoalesceExpression(right)) ||
|
462
|
+
(isCoalesceExpression(left) && isLogicalExpression(right))
|
463
|
+
);
|
464
|
+
}
|
465
|
+
|
419
466
|
//------------------------------------------------------------------------------
|
420
467
|
// Public Interface
|
421
468
|
//------------------------------------------------------------------------------
|
@@ -779,6 +826,7 @@ module.exports = {
|
|
779
826
|
case "LogicalExpression":
|
780
827
|
switch (node.operator) {
|
781
828
|
case "||":
|
829
|
+
case "??":
|
782
830
|
return 4;
|
783
831
|
case "&&":
|
784
832
|
return 5;
|
@@ -1415,7 +1463,7 @@ module.exports = {
|
|
1415
1463
|
|
1416
1464
|
try {
|
1417
1465
|
tokens = espree.tokenize(leftValue, espreeOptions);
|
1418
|
-
} catch
|
1466
|
+
} catch {
|
1419
1467
|
return false;
|
1420
1468
|
}
|
1421
1469
|
|
@@ -1444,7 +1492,7 @@ module.exports = {
|
|
1444
1492
|
|
1445
1493
|
try {
|
1446
1494
|
tokens = espree.tokenize(rightValue, espreeOptions);
|
1447
|
-
} catch
|
1495
|
+
} catch {
|
1448
1496
|
return false;
|
1449
1497
|
}
|
1450
1498
|
|
@@ -1538,5 +1586,9 @@ module.exports = {
|
|
1538
1586
|
*/
|
1539
1587
|
hasOctalEscapeSequence(rawString) {
|
1540
1588
|
return OCTAL_ESCAPE_PATTERN.test(rawString);
|
1541
|
-
}
|
1589
|
+
},
|
1590
|
+
|
1591
|
+
isLogicalExpression,
|
1592
|
+
isCoalesceExpression,
|
1593
|
+
isMixedLogicalAndCoalesceExpressions
|
1542
1594
|
};
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "eslint",
|
3
|
-
"version": "7.
|
3
|
+
"version": "7.2.0",
|
4
4
|
"author": "Nicholas C. Zakas <nicholas+npm@nczconsulting.com>",
|
5
5
|
"description": "An AST-based pattern checker for JavaScript.",
|
6
6
|
"bin": {
|
@@ -52,10 +52,10 @@
|
|
52
52
|
"cross-spawn": "^7.0.2",
|
53
53
|
"debug": "^4.0.1",
|
54
54
|
"doctrine": "^3.0.0",
|
55
|
-
"eslint-scope": "^5.
|
55
|
+
"eslint-scope": "^5.1.0",
|
56
56
|
"eslint-utils": "^2.0.0",
|
57
|
-
"eslint-visitor-keys": "^1.
|
58
|
-
"espree": "^7.
|
57
|
+
"eslint-visitor-keys": "^1.2.0",
|
58
|
+
"espree": "^7.1.0",
|
59
59
|
"esquery": "^1.2.0",
|
60
60
|
"esutils": "^2.0.2",
|
61
61
|
"file-entry-cache": "^5.0.1",
|
@@ -86,7 +86,7 @@
|
|
86
86
|
"devDependencies": {
|
87
87
|
"@babel/core": "^7.4.3",
|
88
88
|
"@babel/preset-env": "^7.4.3",
|
89
|
-
"acorn": "^7.
|
89
|
+
"acorn": "^7.2.0",
|
90
90
|
"babel-loader": "^8.0.5",
|
91
91
|
"chai": "^4.0.1",
|
92
92
|
"cheerio": "^0.22.0",
|