eslint 6.0.0-rc.0 → 6.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.
Files changed (52) hide show
  1. package/CHANGELOG.md +90 -0
  2. package/README.md +4 -13
  3. package/bin/eslint.js +4 -1
  4. package/conf/config-schema.js +1 -0
  5. package/conf/environments.js +72 -15
  6. package/lib/cli-engine/cascading-config-array-factory.js +15 -3
  7. package/lib/cli-engine/cli-engine.js +13 -3
  8. package/lib/cli-engine/config-array/config-array.js +8 -2
  9. package/lib/cli-engine/config-array/extracted-config.js +16 -1
  10. package/lib/cli-engine/config-array-factory.js +9 -6
  11. package/lib/cli-engine/file-enumerator.js +5 -13
  12. package/lib/init/config-initializer.js +19 -9
  13. package/lib/init/npm-utils.js +2 -2
  14. package/lib/linter/code-path-analysis/code-path-analyzer.js +1 -0
  15. package/lib/linter/linter.js +49 -16
  16. package/lib/rule-tester/rule-tester.js +15 -3
  17. package/lib/rules/accessor-pairs.js +195 -35
  18. package/lib/rules/arrow-body-style.js +2 -2
  19. package/lib/rules/class-methods-use-this.js +10 -3
  20. package/lib/rules/dot-location.js +21 -17
  21. package/lib/rules/dot-notation.js +6 -2
  22. package/lib/rules/func-call-spacing.js +30 -20
  23. package/lib/rules/func-names.js +4 -0
  24. package/lib/rules/function-call-argument-newline.js +120 -0
  25. package/lib/rules/function-paren-newline.js +34 -22
  26. package/lib/rules/indent.js +13 -2
  27. package/lib/rules/index.js +1 -0
  28. package/lib/rules/max-len.js +7 -0
  29. package/lib/rules/multiline-comment-style.js +2 -1
  30. package/lib/rules/new-cap.js +2 -1
  31. package/lib/rules/no-dupe-keys.js +1 -1
  32. package/lib/rules/no-duplicate-case.js +10 -8
  33. package/lib/rules/no-else-return.js +127 -0
  34. package/lib/rules/no-extra-bind.js +1 -0
  35. package/lib/rules/no-extra-boolean-cast.js +44 -5
  36. package/lib/rules/no-extra-parens.js +295 -39
  37. package/lib/rules/no-mixed-operators.js +48 -13
  38. package/lib/rules/no-param-reassign.js +12 -1
  39. package/lib/rules/no-restricted-syntax.js +2 -2
  40. package/lib/rules/no-unused-vars.js +1 -1
  41. package/lib/rules/no-var.js +14 -1
  42. package/lib/rules/prefer-const.js +9 -3
  43. package/lib/rules/prefer-template.js +1 -10
  44. package/lib/rules/require-atomic-updates.js +63 -84
  45. package/lib/rules/sort-keys.js +11 -3
  46. package/lib/rules/utils/ast-utils.js +45 -3
  47. package/lib/rules/yoda.js +1 -1
  48. package/lib/{cli-engine → shared}/naming.js +0 -0
  49. package/lib/shared/types.js +2 -0
  50. package/messages/extend-config-missing.txt +2 -0
  51. package/messages/print-config-with-directory-path.txt +2 -0
  52. package/package.json +27 -30
@@ -78,21 +78,13 @@ module.exports = {
78
78
  /**
79
79
  * Check if open space is present in a function name
80
80
  * @param {ASTNode} node node to evaluate
81
+ * @param {Token} leftToken The last token of the callee. This may be the closing parenthesis that encloses the callee.
82
+ * @param {Token} rightToken Tha first token of the arguments. this is the opening parenthesis that encloses the arguments.
81
83
  * @returns {void}
82
84
  * @private
83
85
  */
84
- function checkSpacing(node) {
85
- const lastToken = sourceCode.getLastToken(node);
86
- const lastCalleeToken = sourceCode.getLastToken(node.callee);
87
- const parenToken = sourceCode.getFirstTokenBetween(lastCalleeToken, lastToken, astUtils.isOpeningParenToken);
88
- const prevToken = parenToken && sourceCode.getTokenBefore(parenToken);
89
-
90
- // Parens in NewExpression are optional
91
- if (!(parenToken && parenToken.range[1] < node.range[1])) {
92
- return;
93
- }
94
-
95
- const textBetweenTokens = text.slice(prevToken.range[1], parenToken.range[0]).replace(/\/\*.*?\*\//gu, "");
86
+ function checkSpacing(node, leftToken, rightToken) {
87
+ const textBetweenTokens = text.slice(leftToken.range[1], rightToken.range[0]).replace(/\/\*.*?\*\//gu, "");
96
88
  const hasWhitespace = /\s/u.test(textBetweenTokens);
97
89
  const hasNewline = hasWhitespace && astUtils.LINEBREAK_MATCHER.test(textBetweenTokens);
98
90
 
@@ -123,7 +115,7 @@ module.exports = {
123
115
  if (never && hasWhitespace) {
124
116
  context.report({
125
117
  node,
126
- loc: lastCalleeToken.loc.start,
118
+ loc: leftToken.loc.start,
127
119
  messageId: "unexpected",
128
120
  fix(fixer) {
129
121
 
@@ -132,7 +124,7 @@ module.exports = {
132
124
  * https://github.com/eslint/eslint/issues/7787
133
125
  */
134
126
  if (!hasNewline) {
135
- return fixer.removeRange([prevToken.range[1], parenToken.range[0]]);
127
+ return fixer.removeRange([leftToken.range[1], rightToken.range[0]]);
136
128
  }
137
129
 
138
130
  return null;
@@ -141,27 +133,45 @@ module.exports = {
141
133
  } else if (!never && !hasWhitespace) {
142
134
  context.report({
143
135
  node,
144
- loc: lastCalleeToken.loc.start,
136
+ loc: leftToken.loc.start,
145
137
  messageId: "missing",
146
138
  fix(fixer) {
147
- return fixer.insertTextBefore(parenToken, " ");
139
+ return fixer.insertTextBefore(rightToken, " ");
148
140
  }
149
141
  });
150
142
  } else if (!never && !allowNewlines && hasNewline) {
151
143
  context.report({
152
144
  node,
153
- loc: lastCalleeToken.loc.start,
145
+ loc: leftToken.loc.start,
154
146
  messageId: "unexpected",
155
147
  fix(fixer) {
156
- return fixer.replaceTextRange([prevToken.range[1], parenToken.range[0]], " ");
148
+ return fixer.replaceTextRange([leftToken.range[1], rightToken.range[0]], " ");
157
149
  }
158
150
  });
159
151
  }
160
152
  }
161
153
 
162
154
  return {
163
- CallExpression: checkSpacing,
164
- NewExpression: checkSpacing
155
+ "CallExpression, NewExpression"(node) {
156
+ const lastToken = sourceCode.getLastToken(node);
157
+ const lastCalleeToken = sourceCode.getLastToken(node.callee);
158
+ const parenToken = sourceCode.getFirstTokenBetween(lastCalleeToken, lastToken, astUtils.isOpeningParenToken);
159
+ const prevToken = parenToken && sourceCode.getTokenBefore(parenToken);
160
+
161
+ // Parens in NewExpression are optional
162
+ if (!(parenToken && parenToken.range[1] < node.range[1])) {
163
+ return;
164
+ }
165
+
166
+ checkSpacing(node, prevToken, parenToken);
167
+ },
168
+
169
+ ImportExpression(node) {
170
+ const leftToken = sourceCode.getFirstToken(node);
171
+ const rightToken = sourceCode.getTokenAfter(leftToken);
172
+
173
+ checkSpacing(node, leftToken, rightToken);
174
+ }
165
175
  };
166
176
 
167
177
  }
@@ -69,6 +69,8 @@ module.exports = {
69
69
 
70
70
  create(context) {
71
71
 
72
+ const sourceCode = context.getSourceCode();
73
+
72
74
  /**
73
75
  * Returns the config option for the given node.
74
76
  * @param {ASTNode} node - A node to get the config for.
@@ -130,6 +132,7 @@ module.exports = {
130
132
  context.report({
131
133
  node,
132
134
  messageId: "unnamed",
135
+ loc: astUtils.getFunctionHeadLoc(node, sourceCode),
133
136
  data: { name: astUtils.getFunctionNameWithKind(node) }
134
137
  });
135
138
  }
@@ -143,6 +146,7 @@ module.exports = {
143
146
  context.report({
144
147
  node,
145
148
  messageId: "named",
149
+ loc: astUtils.getFunctionHeadLoc(node, sourceCode),
146
150
  data: { name: astUtils.getFunctionNameWithKind(node) }
147
151
  });
148
152
  }
@@ -0,0 +1,120 @@
1
+ /**
2
+ * @fileoverview Rule to enforce line breaks between arguments of a function call
3
+ * @author Alexey Gonchar <https://github.com/finico>
4
+ */
5
+
6
+ "use strict";
7
+
8
+ //------------------------------------------------------------------------------
9
+ // Rule Definition
10
+ //------------------------------------------------------------------------------
11
+
12
+ module.exports = {
13
+ meta: {
14
+ type: "layout",
15
+
16
+ docs: {
17
+ description: "enforce line breaks between arguments of a function call",
18
+ category: "Stylistic Issues",
19
+ recommended: false,
20
+ url: "https://eslint.org/docs/rules/function-call-argument-newline"
21
+ },
22
+
23
+ fixable: "whitespace",
24
+
25
+ schema: [
26
+ {
27
+ enum: ["always", "never", "consistent"]
28
+ }
29
+ ],
30
+
31
+ messages: {
32
+ unexpectedLineBreak: "There should be no line break here.",
33
+ missingLineBreak: "There should be a line break after this argument."
34
+ }
35
+ },
36
+
37
+ create(context) {
38
+ const sourceCode = context.getSourceCode();
39
+
40
+ const checkers = {
41
+ unexpected: {
42
+ messageId: "unexpectedLineBreak",
43
+ check: (prevToken, currentToken) => prevToken.loc.start.line !== currentToken.loc.start.line,
44
+ createFix: (token, tokenBefore) => fixer =>
45
+ fixer.replaceTextRange([tokenBefore.range[1], token.range[0]], " ")
46
+ },
47
+ missing: {
48
+ messageId: "missingLineBreak",
49
+ check: (prevToken, currentToken) => prevToken.loc.start.line === currentToken.loc.start.line,
50
+ createFix: (token, tokenBefore) => fixer =>
51
+ fixer.replaceTextRange([tokenBefore.range[1], token.range[0]], "\n")
52
+ }
53
+ };
54
+
55
+ /**
56
+ * Check all arguments for line breaks in the CallExpression
57
+ * @param {CallExpression} node node to evaluate
58
+ * @param {{ messageId: string, check: Function }} checker selected checker
59
+ * @returns {void}
60
+ * @private
61
+ */
62
+ function checkArguments(node, checker) {
63
+ for (let i = 1; i < node.arguments.length; i++) {
64
+ const prevArgToken = sourceCode.getFirstToken(node.arguments[i - 1]);
65
+ const currentArgToken = sourceCode.getFirstToken(node.arguments[i]);
66
+
67
+ if (checker.check(prevArgToken, currentArgToken)) {
68
+ const tokenBefore = sourceCode.getTokenBefore(
69
+ currentArgToken,
70
+ { includeComments: true }
71
+ );
72
+
73
+ context.report({
74
+ node,
75
+ loc: {
76
+ start: tokenBefore.loc.end,
77
+ end: currentArgToken.loc.start
78
+ },
79
+ messageId: checker.messageId,
80
+ fix: checker.createFix(currentArgToken, tokenBefore)
81
+ });
82
+ }
83
+ }
84
+ }
85
+
86
+ /**
87
+ * Check if open space is present in a function name
88
+ * @param {CallExpression} node node to evaluate
89
+ * @returns {void}
90
+ * @private
91
+ */
92
+ function check(node) {
93
+ if (node.arguments.length < 2) {
94
+ return;
95
+ }
96
+
97
+ const option = context.options[0] || "always";
98
+
99
+ if (option === "never") {
100
+ checkArguments(node, checkers.unexpected);
101
+ } else if (option === "always") {
102
+ checkArguments(node, checkers.missing);
103
+ } else if (option === "consistent") {
104
+ const firstArgToken = sourceCode.getFirstToken(node.arguments[0]);
105
+ const secondArgToken = sourceCode.getFirstToken(node.arguments[1]);
106
+
107
+ if (firstArgToken.loc.start.line === secondArgToken.loc.start.line) {
108
+ checkArguments(node, checkers.unexpected);
109
+ } else {
110
+ checkArguments(node, checkers.missing);
111
+ }
112
+ }
113
+ }
114
+
115
+ return {
116
+ CallExpression: check,
117
+ NewExpression: check
118
+ };
119
+ }
120
+ };
@@ -232,25 +232,15 @@ module.exports = {
232
232
  };
233
233
  }
234
234
 
235
- default:
236
- throw new TypeError(`unexpected node with type ${node.type}`);
237
- }
238
- }
239
-
240
- /**
241
- * Validates the parentheses for a node
242
- * @param {ASTNode} node The node with parens
243
- * @returns {void}
244
- */
245
- function validateNode(node) {
246
- const parens = getParenTokens(node);
247
-
248
- if (parens) {
249
- validateParens(parens, astUtils.isFunction(node) ? node.params : node.arguments);
235
+ case "ImportExpression": {
236
+ const leftParen = sourceCode.getFirstToken(node, 1);
237
+ const rightParen = sourceCode.getLastToken(node);
250
238
 
251
- if (multilineArgumentsOption) {
252
- validateArguments(parens, astUtils.isFunction(node) ? node.params : node.arguments);
239
+ return { leftParen, rightParen };
253
240
  }
241
+
242
+ default:
243
+ throw new TypeError(`unexpected node with type ${node.type}`);
254
244
  }
255
245
  }
256
246
 
@@ -259,11 +249,33 @@ module.exports = {
259
249
  //----------------------------------------------------------------------
260
250
 
261
251
  return {
262
- ArrowFunctionExpression: validateNode,
263
- CallExpression: validateNode,
264
- FunctionDeclaration: validateNode,
265
- FunctionExpression: validateNode,
266
- NewExpression: validateNode
252
+ [[
253
+ "ArrowFunctionExpression",
254
+ "CallExpression",
255
+ "FunctionDeclaration",
256
+ "FunctionExpression",
257
+ "ImportExpression",
258
+ "NewExpression"
259
+ ]](node) {
260
+ const parens = getParenTokens(node);
261
+ let params;
262
+
263
+ if (node.type === "ImportExpression") {
264
+ params = [node.source];
265
+ } else if (astUtils.isFunction(node)) {
266
+ params = node.params;
267
+ } else {
268
+ params = node.arguments;
269
+ }
270
+
271
+ if (parens) {
272
+ validateParens(parens, params);
273
+
274
+ if (multilineArgumentsOption) {
275
+ validateArguments(parens, params);
276
+ }
277
+ }
278
+ }
267
279
  };
268
280
  }
269
281
  };
@@ -99,7 +99,8 @@ const KNOWN_NODES = new Set([
99
99
  "ImportDeclaration",
100
100
  "ImportSpecifier",
101
101
  "ImportDefaultSpecifier",
102
- "ImportNamespaceSpecifier"
102
+ "ImportNamespaceSpecifier",
103
+ "ImportExpression"
103
104
  ]);
104
105
 
105
106
  /*
@@ -1109,7 +1110,6 @@ module.exports = {
1109
1110
 
1110
1111
  CallExpression: addFunctionCallIndent,
1111
1112
 
1112
-
1113
1113
  "ClassDeclaration[superClass], ClassExpression[superClass]"(node) {
1114
1114
  const classToken = sourceCode.getFirstToken(node);
1115
1115
  const extendsToken = sourceCode.getTokenBefore(node.superClass, astUtils.isNotOpeningParenToken);
@@ -1236,6 +1236,17 @@ module.exports = {
1236
1236
  }
1237
1237
  },
1238
1238
 
1239
+ ImportExpression(node) {
1240
+ const openingParen = sourceCode.getFirstToken(node, 1);
1241
+ const closingParen = sourceCode.getLastToken(node);
1242
+
1243
+ parameterParens.add(openingParen);
1244
+ parameterParens.add(closingParen);
1245
+ offsets.setDesiredOffset(openingParen, sourceCode.getTokenBefore(openingParen), 0);
1246
+
1247
+ addElementListIndent([node.source], openingParen, closingParen, options.CallExpression.arguments);
1248
+ },
1249
+
1239
1250
  "MemberExpression, JSXMemberExpression, MetaProperty"(node) {
1240
1251
  const object = node.type === "MetaProperty" ? node.meta : node.object;
1241
1252
  const firstNonObjectToken = sourceCode.getFirstTokenBetween(object, node.property, astUtils.isNotClosingParenToken);
@@ -46,6 +46,7 @@ module.exports = new LazyLoadingRuleMap(Object.entries({
46
46
  "func-name-matching": () => require("./func-name-matching"),
47
47
  "func-names": () => require("./func-names"),
48
48
  "func-style": () => require("./func-style"),
49
+ "function-call-argument-newline": () => require("./function-call-argument-newline"),
49
50
  "function-paren-newline": () => require("./function-paren-newline"),
50
51
  "generator-star-spacing": () => require("./generator-star-spacing"),
51
52
  "getter-return": () => require("./getter-return"),
@@ -315,6 +315,13 @@ module.exports = {
315
315
  textToMeasure = line;
316
316
  } else if (ignoreTrailingComments && isTrailingComment(line, lineNumber, comment)) {
317
317
  textToMeasure = stripTrailingComment(line, comment);
318
+
319
+ // ignore multiple trailing comments in the same line
320
+ let lastIndex = commentsIndex;
321
+
322
+ while (isTrailingComment(textToMeasure, lineNumber, comments[--lastIndex])) {
323
+ textToMeasure = stripTrailingComment(textToMeasure, comments[lastIndex]);
324
+ }
318
325
  } else {
319
326
  textToMeasure = line;
320
327
  }
@@ -25,6 +25,7 @@ module.exports = {
25
25
  schema: [{ enum: ["starred-block", "separate-lines", "bare-block"] }],
26
26
  messages: {
27
27
  expectedBlock: "Expected a block comment instead of consecutive line comments.",
28
+ expectedBareBlock: "Expected a block comment without padding stars.",
28
29
  startNewline: "Expected a linebreak after '/*'.",
29
30
  endNewline: "Expected a linebreak before '*/'.",
30
31
  missingStar: "Expected a '*' at the start of this line.",
@@ -250,7 +251,7 @@ module.exports = {
250
251
  start: block.loc.start,
251
252
  end: { line: block.loc.start.line, column: block.loc.start.column + 2 }
252
253
  },
253
- messageId: "expectedBlock",
254
+ messageId: "expectedBareBlock",
254
255
  fix(fixer) {
255
256
  return fixer.replaceText(block, convertToBlock(block, commentLines.filter(line => line)));
256
257
  }
@@ -23,7 +23,8 @@ const CAPS_ALLOWED = [
23
23
  "Object",
24
24
  "RegExp",
25
25
  "String",
26
- "Symbol"
26
+ "Symbol",
27
+ "BigInt"
27
28
  ];
28
29
 
29
30
  /**
@@ -120,7 +120,7 @@ module.exports = {
120
120
  }
121
121
 
122
122
  // Skip if the name is not static.
123
- if (!name) {
123
+ if (name === null) {
124
124
  return;
125
125
  }
126
126
 
@@ -33,17 +33,19 @@ module.exports = {
33
33
 
34
34
  return {
35
35
  SwitchStatement(node) {
36
- const mapping = {};
36
+ const previousKeys = new Set();
37
37
 
38
- node.cases.forEach(switchCase => {
39
- const key = sourceCode.getText(switchCase.test);
38
+ for (const switchCase of node.cases) {
39
+ if (switchCase.test) {
40
+ const key = sourceCode.getText(switchCase.test);
40
41
 
41
- if (mapping[key]) {
42
- context.report({ node: switchCase, messageId: "unexpected" });
43
- } else {
44
- mapping[key] = switchCase;
42
+ if (previousKeys.has(key)) {
43
+ context.report({ node: switchCase, messageId: "unexpected" });
44
+ } else {
45
+ previousKeys.add(key);
46
+ }
45
47
  }
46
- });
48
+ }
47
49
  }
48
50
  };
49
51
  }
@@ -51,6 +51,124 @@ module.exports = {
51
51
  // Helpers
52
52
  //--------------------------------------------------------------------------
53
53
 
54
+ /**
55
+ * Checks whether the given names can be safely used to declare block-scoped variables
56
+ * in the given scope. Name collisions can produce redeclaration syntax errors,
57
+ * or silently change references and modify behavior of the original code.
58
+ *
59
+ * This is not a generic function. In particular, it is assumed that the scope is a function scope or
60
+ * a function's inner scope, and that the names can be valid identifiers in the given scope.
61
+ *
62
+ * @param {string[]} names Array of variable names.
63
+ * @param {eslint-scope.Scope} scope Function scope or a function's inner scope.
64
+ * @returns {boolean} True if all names can be safely declared, false otherwise.
65
+ */
66
+ function isSafeToDeclare(names, scope) {
67
+
68
+ if (names.length === 0) {
69
+ return true;
70
+ }
71
+
72
+ const functionScope = scope.variableScope;
73
+
74
+ /*
75
+ * If this is a function scope, scope.variables will contain parameters, implicit variables such as "arguments",
76
+ * all function-scoped variables ('var'), and block-scoped variables defined in the scope.
77
+ * If this is an inner scope, scope.variables will contain block-scoped variables defined in the scope.
78
+ *
79
+ * Redeclaring any of these would cause a syntax error, except for the implicit variables.
80
+ */
81
+ const declaredVariables = scope.variables.filter(({ defs }) => defs.length > 0);
82
+
83
+ if (declaredVariables.some(({ name }) => names.includes(name))) {
84
+ return false;
85
+ }
86
+
87
+ // Redeclaring a catch variable would also cause a syntax error.
88
+ if (scope !== functionScope && scope.upper.type === "catch") {
89
+ if (scope.upper.variables.some(({ name }) => names.includes(name))) {
90
+ return false;
91
+ }
92
+ }
93
+
94
+ /*
95
+ * Redeclaring an implicit variable, such as "arguments", would not cause a syntax error.
96
+ * However, if the variable was used, declaring a new one with the same name would change references
97
+ * and modify behavior.
98
+ */
99
+ const usedImplicitVariables = scope.variables.filter(({ defs, references }) =>
100
+ defs.length === 0 && references.length > 0);
101
+
102
+ if (usedImplicitVariables.some(({ name }) => names.includes(name))) {
103
+ return false;
104
+ }
105
+
106
+ /*
107
+ * Declaring a variable with a name that was already used to reference a variable from an upper scope
108
+ * would change references and modify behavior.
109
+ */
110
+ if (scope.through.some(t => names.includes(t.identifier.name))) {
111
+ return false;
112
+ }
113
+
114
+ /*
115
+ * If the scope is an inner scope (not the function scope), an uninitialized `var` variable declared inside
116
+ * the scope node (directly or in one of its descendants) is neither declared nor 'through' in the scope.
117
+ *
118
+ * For example, this would be a syntax error "Identifier 'a' has already been declared":
119
+ * function foo() { if (bar) { let a; if (baz) { var a; } } }
120
+ */
121
+ if (scope !== functionScope) {
122
+ const scopeNodeRange = scope.block.range;
123
+ const variablesToCheck = functionScope.variables.filter(({ name }) => names.includes(name));
124
+
125
+ if (variablesToCheck.some(v => v.defs.some(({ node: { range } }) =>
126
+ scopeNodeRange[0] <= range[0] && range[1] <= scopeNodeRange[1]))) {
127
+ return false;
128
+ }
129
+ }
130
+
131
+ return true;
132
+ }
133
+
134
+
135
+ /**
136
+ * Checks whether the removal of `else` and its braces is safe from variable name collisions.
137
+ *
138
+ * @param {Node} node The 'else' node.
139
+ * @param {eslint-scope.Scope} scope The scope in which the node and the whole 'if' statement is.
140
+ * @returns {boolean} True if it is safe, false otherwise.
141
+ */
142
+ function isSafeFromNameCollisions(node, scope) {
143
+
144
+ if (node.type === "FunctionDeclaration") {
145
+
146
+ // Conditional function declaration. Scope and hoisting are unpredictable, different engines work differently.
147
+ return false;
148
+ }
149
+
150
+ if (node.type !== "BlockStatement") {
151
+ return true;
152
+ }
153
+
154
+ const elseBlockScope = scope.childScopes.find(({ block }) => block === node);
155
+
156
+ if (!elseBlockScope) {
157
+
158
+ // ecmaVersion < 6, `else` block statement cannot have its own scope, no possible collisions.
159
+ return true;
160
+ }
161
+
162
+ /*
163
+ * elseBlockScope is supposed to merge into its upper scope. elseBlockScope.variables array contains
164
+ * only block-scoped variables (such as let and const variables or class and function declarations)
165
+ * defined directly in the elseBlockScope. These are exactly the only names that could cause collisions.
166
+ */
167
+ const namesToCheck = elseBlockScope.variables.map(({ name }) => name);
168
+
169
+ return isSafeToDeclare(namesToCheck, scope);
170
+ }
171
+
54
172
  /**
55
173
  * Display the context report if rule is violated
56
174
  *
@@ -58,10 +176,17 @@ module.exports = {
58
176
  * @returns {void}
59
177
  */
60
178
  function displayReport(node) {
179
+ const currentScope = context.getScope();
180
+
61
181
  context.report({
62
182
  node,
63
183
  messageId: "unexpected",
64
184
  fix: fixer => {
185
+
186
+ if (!isSafeFromNameCollisions(node, currentScope)) {
187
+ return null;
188
+ }
189
+
65
190
  const sourceCode = context.getSourceCode();
66
191
  const startToken = sourceCode.getFirstToken(node);
67
192
  const elseToken = sourceCode.getTokenBefore(startToken);
@@ -118,6 +243,8 @@ module.exports = {
118
243
  * Extend the replacement range to include the entire
119
244
  * function to avoid conflicting with no-useless-return.
120
245
  * https://github.com/eslint/eslint/issues/8026
246
+ *
247
+ * Also, to avoid name collisions between two else blocks.
121
248
  */
122
249
  return new FixTracker(fixer, sourceCode)
123
250
  .retainEnclosingFunction(node)
@@ -98,6 +98,7 @@ module.exports = {
98
98
  grandparent.type === "CallExpression" &&
99
99
  grandparent.callee === parent &&
100
100
  grandparent.arguments.length === 1 &&
101
+ grandparent.arguments[0].type !== "SpreadElement" &&
101
102
  parent.type === "MemberExpression" &&
102
103
  parent.object === node &&
103
104
  astUtils.getStaticPropertyName(parent) === "bind"