eslint 7.1.0 → 7.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/CHANGELOG.md +69 -0
  2. package/README.md +12 -7
  3. package/lib/cli-engine/cli-engine.js +2 -2
  4. package/lib/cli-engine/config-array-factory.js +1 -1
  5. package/lib/init/config-initializer.js +91 -74
  6. package/lib/linter/code-path-analysis/code-path-analyzer.js +2 -2
  7. package/lib/linter/code-path-analysis/code-path-state.js +34 -12
  8. package/lib/linter/config-comment-parser.js +1 -1
  9. package/lib/rule-tester/rule-tester.js +9 -0
  10. package/lib/rules/array-callback-return.js +21 -10
  11. package/lib/rules/arrow-body-style.js +2 -2
  12. package/lib/rules/arrow-parens.js +91 -108
  13. package/lib/rules/camelcase.js +47 -0
  14. package/lib/rules/comma-dangle.js +2 -1
  15. package/lib/rules/curly.js +8 -1
  16. package/lib/rules/func-call-spacing.js +18 -3
  17. package/lib/rules/{id-blacklist.js → id-denylist.js} +12 -12
  18. package/lib/rules/id-match.js +2 -1
  19. package/lib/rules/index.js +6 -1
  20. package/lib/rules/key-spacing.js +6 -2
  21. package/lib/rules/keyword-spacing.js +9 -2
  22. package/lib/rules/max-lines.js +34 -8
  23. package/lib/rules/multiline-ternary.js +44 -25
  24. package/lib/rules/no-control-regex.js +1 -1
  25. package/lib/rules/no-extra-boolean-cast.js +3 -0
  26. package/lib/rules/no-extra-parens.js +50 -4
  27. package/lib/rules/no-invalid-regexp.js +1 -1
  28. package/lib/rules/no-misleading-character-class.js +1 -1
  29. package/lib/rules/no-mixed-operators.js +3 -2
  30. package/lib/rules/no-mixed-spaces-and-tabs.js +14 -6
  31. package/lib/rules/no-promise-executor-return.js +121 -0
  32. package/lib/rules/no-regex-spaces.js +1 -1
  33. package/lib/rules/no-restricted-exports.js +6 -0
  34. package/lib/rules/no-unneeded-ternary.js +6 -4
  35. package/lib/rules/no-unreachable-loop.js +150 -0
  36. package/lib/rules/no-unused-expressions.js +1 -1
  37. package/lib/rules/no-unused-vars.js +5 -2
  38. package/lib/rules/no-useless-backreference.js +1 -1
  39. package/lib/rules/object-property-newline.js +1 -1
  40. package/lib/rules/operator-linebreak.js +2 -5
  41. package/lib/rules/padded-blocks.js +2 -1
  42. package/lib/rules/prefer-named-capture-group.js +1 -1
  43. package/lib/rules/prefer-regex-literals.js +66 -8
  44. package/lib/rules/quote-props.js +2 -2
  45. package/lib/rules/semi-spacing.js +1 -0
  46. package/lib/rules/template-tag-spacing.js +8 -2
  47. package/lib/rules/utils/ast-utils.js +55 -3
  48. package/package.json +6 -6
@@ -0,0 +1,150 @@
1
+ /**
2
+ * @fileoverview Rule to disallow loops with a body that allows only one iteration
3
+ * @author Milos Djermanovic
4
+ */
5
+
6
+ "use strict";
7
+
8
+ //------------------------------------------------------------------------------
9
+ // Helpers
10
+ //------------------------------------------------------------------------------
11
+
12
+ const allLoopTypes = ["WhileStatement", "DoWhileStatement", "ForStatement", "ForInStatement", "ForOfStatement"];
13
+
14
+ /**
15
+ * Determines whether the given node is the first node in the code path to which a loop statement
16
+ * 'loops' for the next iteration.
17
+ * @param {ASTNode} node The node to check.
18
+ * @returns {boolean} `true` if the node is a looping target.
19
+ */
20
+ function isLoopingTarget(node) {
21
+ const parent = node.parent;
22
+
23
+ if (parent) {
24
+ switch (parent.type) {
25
+ case "WhileStatement":
26
+ return node === parent.test;
27
+ case "DoWhileStatement":
28
+ return node === parent.body;
29
+ case "ForStatement":
30
+ return node === (parent.update || parent.test || parent.body);
31
+ case "ForInStatement":
32
+ case "ForOfStatement":
33
+ return node === parent.left;
34
+
35
+ // no default
36
+ }
37
+ }
38
+
39
+ return false;
40
+ }
41
+
42
+ /**
43
+ * Creates an array with elements from the first given array that are not included in the second given array.
44
+ * @param {Array} arrA The array to compare from.
45
+ * @param {Array} arrB The array to compare against.
46
+ * @returns {Array} a new array that represents `arrA \ arrB`.
47
+ */
48
+ function getDifference(arrA, arrB) {
49
+ return arrA.filter(a => !arrB.includes(a));
50
+ }
51
+
52
+ //------------------------------------------------------------------------------
53
+ // Rule Definition
54
+ //------------------------------------------------------------------------------
55
+
56
+ module.exports = {
57
+ meta: {
58
+ type: "problem",
59
+
60
+ docs: {
61
+ description: "disallow loops with a body that allows only one iteration",
62
+ category: "Possible Errors",
63
+ recommended: false,
64
+ url: "https://eslint.org/docs/rules/no-unreachable-loop"
65
+ },
66
+
67
+ schema: [{
68
+ type: "object",
69
+ properties: {
70
+ ignore: {
71
+ type: "array",
72
+ items: {
73
+ enum: allLoopTypes
74
+ },
75
+ uniqueItems: true
76
+ }
77
+ },
78
+ additionalProperties: false
79
+ }],
80
+
81
+ messages: {
82
+ invalid: "Invalid loop. Its body allows only one iteration."
83
+ }
84
+ },
85
+
86
+ create(context) {
87
+ const ignoredLoopTypes = context.options[0] && context.options[0].ignore || [],
88
+ loopTypesToCheck = getDifference(allLoopTypes, ignoredLoopTypes),
89
+ loopSelector = loopTypesToCheck.join(","),
90
+ loopsByTargetSegments = new Map(),
91
+ loopsToReport = new Set();
92
+
93
+ let currentCodePath = null;
94
+
95
+ return {
96
+ onCodePathStart(codePath) {
97
+ currentCodePath = codePath;
98
+ },
99
+
100
+ onCodePathEnd() {
101
+ currentCodePath = currentCodePath.upper;
102
+ },
103
+
104
+ [loopSelector](node) {
105
+
106
+ /**
107
+ * Ignore unreachable loop statements to avoid unnecessary complexity in the implementation, or false positives otherwise.
108
+ * For unreachable segments, the code path analysis does not raise events required for this implementation.
109
+ */
110
+ if (currentCodePath.currentSegments.some(segment => segment.reachable)) {
111
+ loopsToReport.add(node);
112
+ }
113
+ },
114
+
115
+ onCodePathSegmentStart(segment, node) {
116
+ if (isLoopingTarget(node)) {
117
+ const loop = node.parent;
118
+
119
+ loopsByTargetSegments.set(segment, loop);
120
+ }
121
+ },
122
+
123
+ onCodePathSegmentLoop(_, toSegment, node) {
124
+ const loop = loopsByTargetSegments.get(toSegment);
125
+
126
+ /**
127
+ * The second iteration is reachable, meaning that the loop is valid by the logic of this rule,
128
+ * only if there is at least one loop event with the appropriate target (which has been already
129
+ * determined in the `loopsByTargetSegments` map), raised from either:
130
+ *
131
+ * - the end of the loop's body (in which case `node === loop`)
132
+ * - a `continue` statement
133
+ *
134
+ * This condition skips loop events raised from `ForInStatement > .right` and `ForOfStatement > .right` nodes.
135
+ */
136
+ if (node === loop || node.type === "ContinueStatement") {
137
+
138
+ // Removes loop if it exists in the set. Otherwise, `Set#delete` has no effect and doesn't throw.
139
+ loopsToReport.delete(loop);
140
+ }
141
+ },
142
+
143
+ "Program:exit"() {
144
+ loopsToReport.forEach(
145
+ node => context.report({ node, messageId: "invalid" })
146
+ );
147
+ }
148
+ };
149
+ }
150
+ };
@@ -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
 
@@ -68,7 +68,8 @@ module.exports = {
68
68
  caughtErrorsIgnorePattern: {
69
69
  type: "string"
70
70
  }
71
- }
71
+ },
72
+ additionalProperties: false
72
73
  }
73
74
  ]
74
75
  }
@@ -619,7 +620,9 @@ module.exports = {
619
620
  // Report the first declaration.
620
621
  if (unusedVar.defs.length > 0) {
621
622
  context.report({
622
- node: unusedVar.identifiers[0],
623
+ node: unusedVar.references.length ? unusedVar.references[
624
+ unusedVar.references.length - 1
625
+ ].identifier : unusedVar.identifiers[0],
623
626
  messageId: "unusedVar",
624
627
  data: unusedVar.references.some(ref => ref.isWrite())
625
628
  ? getAssignedMessageData(unusedVar)
@@ -95,7 +95,7 @@ module.exports = {
95
95
 
96
96
  try {
97
97
  regExpAST = parser.parsePattern(pattern, 0, pattern.length, flags.includes("u"));
98
- } catch (e) {
98
+ } catch {
99
99
 
100
100
  // Ignore regular expressions with syntax errors
101
101
  return;
@@ -77,7 +77,7 @@ module.exports = {
77
77
  if (lastTokenOfPreviousProperty.loc.end.line === firstTokenOfCurrentProperty.loc.start.line) {
78
78
  context.report({
79
79
  node,
80
- loc: firstTokenOfCurrentProperty.loc.start,
80
+ loc: firstTokenOfCurrentProperty.loc,
81
81
  messageId,
82
82
  fix(fixer) {
83
83
  const comma = sourceCode.getTokenBefore(firstTokenOfCurrentProperty);
@@ -35,11 +35,8 @@ module.exports = {
35
35
  properties: {
36
36
  overrides: {
37
37
  type: "object",
38
- properties: {
39
- anyOf: {
40
- type: "string",
41
- enum: ["after", "before", "none", "ignore"]
42
- }
38
+ additionalProperties: {
39
+ enum: ["after", "before", "none", "ignore"]
43
40
  }
44
41
  }
45
42
  },
@@ -58,7 +58,8 @@ module.exports = {
58
58
  allowSingleLineBlocks: {
59
59
  type: "boolean"
60
60
  }
61
- }
61
+ },
62
+ additionalProperties: false
62
63
  }
63
64
  ],
64
65
 
@@ -59,7 +59,7 @@ module.exports = {
59
59
 
60
60
  try {
61
61
  ast = parser.parsePattern(pattern, 0, pattern.length, uFlag);
62
- } catch (_) {
62
+ } catch {
63
63
 
64
64
  // ignore regex syntax errors
65
65
  return;
@@ -25,6 +25,15 @@ function isStringLiteral(node) {
25
25
  return node.type === "Literal" && typeof node.value === "string";
26
26
  }
27
27
 
28
+ /**
29
+ * Determines whether the given node is a regex literal.
30
+ * @param {ASTNode} node Node to check.
31
+ * @returns {boolean} True if the node is a regex literal.
32
+ */
33
+ function isRegexLiteral(node) {
34
+ return node.type === "Literal" && Object.prototype.hasOwnProperty.call(node, "regex");
35
+ }
36
+
28
37
  /**
29
38
  * Determines whether the given node is a template literal without expressions.
30
39
  * @param {ASTNode} node Node to check.
@@ -50,14 +59,28 @@ module.exports = {
50
59
  url: "https://eslint.org/docs/rules/prefer-regex-literals"
51
60
  },
52
61
 
53
- schema: [],
62
+ schema: [
63
+ {
64
+ type: "object",
65
+ properties: {
66
+ disallowRedundantWrapping: {
67
+ type: "boolean",
68
+ default: false
69
+ }
70
+ },
71
+ additionalProperties: false
72
+ }
73
+ ],
54
74
 
55
75
  messages: {
56
- unexpectedRegExp: "Use a regular expression literal instead of the 'RegExp' constructor."
76
+ unexpectedRegExp: "Use a regular expression literal instead of the 'RegExp' constructor.",
77
+ unexpectedRedundantRegExp: "Regular expression literal is unnecessarily wrapped within a 'RegExp' constructor.",
78
+ unexpectedRedundantRegExpWithFlags: "Use regular expression literal with flags instead of the 'RegExp' constructor."
57
79
  }
58
80
  },
59
81
 
60
82
  create(context) {
83
+ const [{ disallowRedundantWrapping = false } = {}] = context.options;
61
84
 
62
85
  /**
63
86
  * Determines whether the given identifier node is a reference to a global variable.
@@ -98,6 +121,40 @@ module.exports = {
98
121
  isStringRawTaggedStaticTemplateLiteral(node);
99
122
  }
100
123
 
124
+ /**
125
+ * Determines whether the relevant arguments of the given are all static string literals.
126
+ * @param {ASTNode} node Node to check.
127
+ * @returns {boolean} True if all arguments are static strings.
128
+ */
129
+ function hasOnlyStaticStringArguments(node) {
130
+ const args = node.arguments;
131
+
132
+ if ((args.length === 1 || args.length === 2) && args.every(isStaticString)) {
133
+ return true;
134
+ }
135
+
136
+ return false;
137
+ }
138
+
139
+ /**
140
+ * Determines whether the arguments of the given node indicate that a regex literal is unnecessarily wrapped.
141
+ * @param {ASTNode} node Node to check.
142
+ * @returns {boolean} True if the node already contains a regex literal argument.
143
+ */
144
+ function isUnnecessarilyWrappedRegexLiteral(node) {
145
+ const args = node.arguments;
146
+
147
+ if (args.length === 1 && isRegexLiteral(args[0])) {
148
+ return true;
149
+ }
150
+
151
+ if (args.length === 2 && isRegexLiteral(args[0]) && isStaticString(args[1])) {
152
+ return true;
153
+ }
154
+
155
+ return false;
156
+ }
157
+
101
158
  return {
102
159
  Program() {
103
160
  const scope = context.getScope();
@@ -110,12 +167,13 @@ module.exports = {
110
167
  };
111
168
 
112
169
  for (const { node } of tracker.iterateGlobalReferences(traceMap)) {
113
- const args = node.arguments;
114
-
115
- if (
116
- (args.length === 1 || args.length === 2) &&
117
- args.every(isStaticString)
118
- ) {
170
+ if (disallowRedundantWrapping && isUnnecessarilyWrappedRegexLiteral(node)) {
171
+ if (node.arguments.length === 2) {
172
+ context.report({ node, messageId: "unexpectedRedundantRegExpWithFlags" });
173
+ } else {
174
+ context.report({ node, messageId: "unexpectedRedundantRegExp" });
175
+ }
176
+ } else if (hasOnlyStaticStringArguments(node)) {
119
177
  context.report({ node, messageId: "unexpectedRegExp" });
120
178
  }
121
179
  }
@@ -154,7 +154,7 @@ module.exports = {
154
154
 
155
155
  try {
156
156
  tokens = espree.tokenize(key.value);
157
- } catch (e) {
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 (e) {
242
+ } catch {
243
243
  necessaryQuotes = true;
244
244
  return;
245
245
  }
@@ -223,6 +223,7 @@ module.exports = {
223
223
  BreakStatement: checkNode,
224
224
  ContinueStatement: checkNode,
225
225
  DebuggerStatement: checkNode,
226
+ DoWhileStatement: checkNode,
226
227
  ReturnStatement: checkNode,
227
228
  ThrowStatement: checkNode,
228
229
  ImportDeclaration: checkNode,
@@ -49,7 +49,10 @@ module.exports = {
49
49
  if (never && hasWhitespace) {
50
50
  context.report({
51
51
  node,
52
- loc: tagToken.loc.start,
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: tagToken.loc.start,
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 (e) {
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 (e) {
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.1.0",
3
+ "version": "7.4.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,11 @@
52
52
  "cross-spawn": "^7.0.2",
53
53
  "debug": "^4.0.1",
54
54
  "doctrine": "^3.0.0",
55
- "eslint-scope": "^5.0.0",
55
+ "enquirer": "^2.3.5",
56
+ "eslint-scope": "^5.1.0",
56
57
  "eslint-utils": "^2.0.0",
57
- "eslint-visitor-keys": "^1.1.0",
58
- "espree": "^7.0.0",
58
+ "eslint-visitor-keys": "^1.2.0",
59
+ "espree": "^7.1.0",
59
60
  "esquery": "^1.2.0",
60
61
  "esutils": "^2.0.2",
61
62
  "file-entry-cache": "^5.0.1",
@@ -65,7 +66,6 @@
65
66
  "ignore": "^4.0.6",
66
67
  "import-fresh": "^3.0.0",
67
68
  "imurmurhash": "^0.1.4",
68
- "inquirer": "^7.0.0",
69
69
  "is-glob": "^4.0.0",
70
70
  "js-yaml": "^3.13.1",
71
71
  "json-stable-stringify-without-jsonify": "^1.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.1.1",
89
+ "acorn": "^7.2.0",
90
90
  "babel-loader": "^8.0.5",
91
91
  "chai": "^4.0.1",
92
92
  "cheerio": "^0.22.0",