eslint 6.6.0 → 6.8.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 (62) hide show
  1. package/CHANGELOG.md +94 -0
  2. package/README.md +7 -8
  3. package/conf/config-schema.js +2 -0
  4. package/conf/default-cli-options.js +1 -1
  5. package/conf/eslint-recommended.js +0 -1
  6. package/lib/cli-engine/cascading-config-array-factory.js +38 -13
  7. package/lib/cli-engine/cli-engine.js +41 -14
  8. package/lib/cli-engine/config-array/config-array.js +13 -0
  9. package/lib/cli-engine/config-array/extracted-config.js +27 -0
  10. package/lib/cli-engine/config-array/ignore-pattern.js +231 -0
  11. package/lib/cli-engine/config-array/index.js +2 -0
  12. package/lib/cli-engine/config-array-factory.js +115 -1
  13. package/lib/cli-engine/file-enumerator.js +73 -40
  14. package/lib/cli-engine/lint-result-cache.js +2 -1
  15. package/lib/cli.js +2 -1
  16. package/lib/init/config-initializer.js +4 -3
  17. package/lib/linter/config-comment-parser.js +1 -1
  18. package/lib/linter/report-translator.js +73 -7
  19. package/lib/options.js +6 -0
  20. package/lib/rule-tester/rule-tester.js +42 -6
  21. package/lib/rules/array-bracket-spacing.js +8 -8
  22. package/lib/rules/camelcase.js +19 -6
  23. package/lib/rules/comma-dangle.js +5 -2
  24. package/lib/rules/computed-property-spacing.js +4 -4
  25. package/lib/rules/curly.js +9 -4
  26. package/lib/rules/function-call-argument-newline.js +3 -1
  27. package/lib/rules/grouped-accessor-pairs.js +224 -0
  28. package/lib/rules/indent.js +11 -0
  29. package/lib/rules/index.js +5 -0
  30. package/lib/rules/key-spacing.js +34 -15
  31. package/lib/rules/lines-between-class-members.js +42 -53
  32. package/lib/rules/multiline-comment-style.js +237 -106
  33. package/lib/rules/no-cond-assign.js +14 -4
  34. package/lib/rules/no-constructor-return.js +62 -0
  35. package/lib/rules/no-dupe-else-if.js +122 -0
  36. package/lib/rules/no-implicit-globals.js +90 -8
  37. package/lib/rules/no-inline-comments.js +25 -11
  38. package/lib/rules/no-invalid-this.js +16 -2
  39. package/lib/rules/no-multiple-empty-lines.js +1 -1
  40. package/lib/rules/no-octal-escape.js +1 -1
  41. package/lib/rules/no-restricted-imports.js +2 -2
  42. package/lib/rules/no-setter-return.js +227 -0
  43. package/lib/rules/no-underscore-dangle.js +23 -4
  44. package/lib/rules/no-unexpected-multiline.js +8 -0
  45. package/lib/rules/no-unsafe-negation.js +30 -5
  46. package/lib/rules/no-useless-computed-key.js +60 -33
  47. package/lib/rules/no-useless-escape.js +26 -3
  48. package/lib/rules/object-curly-spacing.js +8 -8
  49. package/lib/rules/operator-assignment.js +11 -2
  50. package/lib/rules/prefer-const.js +14 -7
  51. package/lib/rules/prefer-exponentiation-operator.js +189 -0
  52. package/lib/rules/prefer-numeric-literals.js +29 -28
  53. package/lib/rules/require-atomic-updates.js +1 -1
  54. package/lib/rules/require-await.js +8 -0
  55. package/lib/rules/semi.js +6 -3
  56. package/lib/rules/space-infix-ops.js +1 -1
  57. package/lib/rules/spaced-comment.js +5 -4
  58. package/lib/rules/utils/ast-utils.js +31 -4
  59. package/lib/shared/types.js +9 -0
  60. package/lib/source-code/source-code.js +87 -10
  61. package/package.json +4 -3
  62. package/lib/cli-engine/ignored-paths.js +0 -363
@@ -0,0 +1,122 @@
1
+ /**
2
+ * @fileoverview Rule to disallow duplicate conditions in if-else-if chains
3
+ * @author Milos Djermanovic
4
+ */
5
+
6
+ "use strict";
7
+
8
+ //------------------------------------------------------------------------------
9
+ // Requirements
10
+ //------------------------------------------------------------------------------
11
+
12
+ const astUtils = require("./utils/ast-utils");
13
+
14
+ //------------------------------------------------------------------------------
15
+ // Helpers
16
+ //------------------------------------------------------------------------------
17
+
18
+ /**
19
+ * Determines whether the first given array is a subset of the second given array.
20
+ * @param {Function} comparator A function to compare two elements, should return `true` if they are equal.
21
+ * @param {Array} arrA The array to compare from.
22
+ * @param {Array} arrB The array to compare against.
23
+ * @returns {boolean} `true` if the array `arrA` is a subset of the array `arrB`.
24
+ */
25
+ function isSubsetByComparator(comparator, arrA, arrB) {
26
+ return arrA.every(a => arrB.some(b => comparator(a, b)));
27
+ }
28
+
29
+ /**
30
+ * Splits the given node by the given logical operator.
31
+ * @param {string} operator Logical operator `||` or `&&`.
32
+ * @param {ASTNode} node The node to split.
33
+ * @returns {ASTNode[]} Array of conditions that makes the node when joined by the operator.
34
+ */
35
+ function splitByLogicalOperator(operator, node) {
36
+ if (node.type === "LogicalExpression" && node.operator === operator) {
37
+ return [...splitByLogicalOperator(operator, node.left), ...splitByLogicalOperator(operator, node.right)];
38
+ }
39
+ return [node];
40
+ }
41
+
42
+ const splitByOr = splitByLogicalOperator.bind(null, "||");
43
+ const splitByAnd = splitByLogicalOperator.bind(null, "&&");
44
+
45
+ //------------------------------------------------------------------------------
46
+ // Rule Definition
47
+ //------------------------------------------------------------------------------
48
+
49
+ module.exports = {
50
+ meta: {
51
+ type: "problem",
52
+
53
+ docs: {
54
+ description: "disallow duplicate conditions in if-else-if chains",
55
+ category: "Possible Errors",
56
+ recommended: false,
57
+ url: "https://eslint.org/docs/rules/no-dupe-else-if"
58
+ },
59
+
60
+ schema: [],
61
+
62
+ messages: {
63
+ unexpected: "This branch can never execute. Its condition is a duplicate or covered by previous conditions in the if-else-if chain."
64
+ }
65
+ },
66
+
67
+ create(context) {
68
+ const sourceCode = context.getSourceCode();
69
+
70
+ /**
71
+ * Determines whether the two given nodes are considered to be equal. In particular, given that the nodes
72
+ * represent expressions in a boolean context, `||` and `&&` can be considered as commutative operators.
73
+ * @param {ASTNode} a First node.
74
+ * @param {ASTNode} b Second node.
75
+ * @returns {boolean} `true` if the nodes are considered to be equal.
76
+ */
77
+ function equal(a, b) {
78
+ if (a.type !== b.type) {
79
+ return false;
80
+ }
81
+
82
+ if (
83
+ a.type === "LogicalExpression" &&
84
+ (a.operator === "||" || a.operator === "&&") &&
85
+ a.operator === b.operator
86
+ ) {
87
+ return equal(a.left, b.left) && equal(a.right, b.right) ||
88
+ equal(a.left, b.right) && equal(a.right, b.left);
89
+ }
90
+
91
+ return astUtils.equalTokens(a, b, sourceCode);
92
+ }
93
+
94
+ const isSubset = isSubsetByComparator.bind(null, equal);
95
+
96
+ return {
97
+ IfStatement(node) {
98
+ const test = node.test,
99
+ conditionsToCheck = test.type === "LogicalExpression" && test.operator === "&&"
100
+ ? [test, ...splitByAnd(test)]
101
+ : [test];
102
+ let current = node,
103
+ listToCheck = conditionsToCheck.map(c => splitByOr(c).map(splitByAnd));
104
+
105
+ while (current.parent && current.parent.type === "IfStatement" && current.parent.alternate === current) {
106
+ current = current.parent;
107
+
108
+ const currentOrOperands = splitByOr(current.test).map(splitByAnd);
109
+
110
+ listToCheck = listToCheck.map(orOperands => orOperands.filter(
111
+ orOperand => !currentOrOperands.some(currentOrOperand => isSubset(currentOrOperand, orOperand))
112
+ ));
113
+
114
+ if (listToCheck.some(orOperands => orOperands.length === 0)) {
115
+ context.report({ node: test, messageId: "unexpected" });
116
+ break;
117
+ }
118
+ }
119
+ }
120
+ };
121
+ }
122
+ };
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @fileoverview Rule to check for implicit global variables and functions.
2
+ * @fileoverview Rule to check for implicit global variables, functions and classes.
3
3
  * @author Joshua Peek
4
4
  */
5
5
 
@@ -14,41 +14,123 @@ module.exports = {
14
14
  type: "suggestion",
15
15
 
16
16
  docs: {
17
- description: "disallow variable and `function` declarations in the global scope",
17
+ description: "disallow declarations in the global scope",
18
18
  category: "Best Practices",
19
19
  recommended: false,
20
20
  url: "https://eslint.org/docs/rules/no-implicit-globals"
21
21
  },
22
22
 
23
- schema: []
23
+ schema: [{
24
+ type: "object",
25
+ properties: {
26
+ lexicalBindings: {
27
+ type: "boolean",
28
+ default: false
29
+ }
30
+ },
31
+ additionalProperties: false
32
+ }],
33
+
34
+ messages: {
35
+ globalNonLexicalBinding: "Unexpected {{kind}} declaration in the global scope, wrap in an IIFE for a local variable, assign as global property for a global variable.",
36
+ globalLexicalBinding: "Unexpected {{kind}} declaration in the global scope, wrap in a block or in an IIFE.",
37
+ globalVariableLeak: "Global variable leak, declare the variable if it is intended to be local.",
38
+ assignmentToReadonlyGlobal: "Unexpected assignment to read-only global variable.",
39
+ redeclarationOfReadonlyGlobal: "Unexpected redeclaration of read-only global variable."
40
+ }
24
41
  },
25
42
 
26
43
  create(context) {
44
+
45
+ const checkLexicalBindings = context.options[0] && context.options[0].lexicalBindings === true;
46
+
47
+ /**
48
+ * Reports the node.
49
+ * @param {ASTNode} node Node to report.
50
+ * @param {string} messageId Id of the message to report.
51
+ * @param {string|undefined} kind Declaration kind, can be 'var', 'const', 'let', function or class.
52
+ * @returns {void}
53
+ */
54
+ function report(node, messageId, kind) {
55
+ context.report({
56
+ node,
57
+ messageId,
58
+ data: {
59
+ kind
60
+ }
61
+ });
62
+ }
63
+
27
64
  return {
28
65
  Program() {
29
66
  const scope = context.getScope();
30
67
 
31
68
  scope.variables.forEach(variable => {
32
- if (variable.writeable) {
69
+
70
+ // Only ESLint global variables have the `writable` key.
71
+ const isReadonlyEslintGlobalVariable = variable.writeable === false;
72
+ const isWritableEslintGlobalVariable = variable.writeable === true;
73
+
74
+ if (isWritableEslintGlobalVariable) {
75
+
76
+ // Everything is allowed with writable ESLint global variables.
33
77
  return;
34
78
  }
35
79
 
36
80
  variable.defs.forEach(def => {
81
+ const defNode = def.node;
82
+
37
83
  if (def.type === "FunctionName" || (def.type === "Variable" && def.parent.kind === "var")) {
38
- context.report({ node: def.node, message: "Implicit global variable, assign as global property instead." });
84
+ if (isReadonlyEslintGlobalVariable) {
85
+ report(defNode, "redeclarationOfReadonlyGlobal");
86
+ } else {
87
+ report(
88
+ defNode,
89
+ "globalNonLexicalBinding",
90
+ def.type === "FunctionName" ? "function" : `'${def.parent.kind}'`
91
+ );
92
+ }
93
+ }
94
+
95
+ if (checkLexicalBindings) {
96
+ if (def.type === "ClassName" ||
97
+ (def.type === "Variable" && (def.parent.kind === "let" || def.parent.kind === "const"))) {
98
+ if (isReadonlyEslintGlobalVariable) {
99
+ report(defNode, "redeclarationOfReadonlyGlobal");
100
+ } else {
101
+ report(
102
+ defNode,
103
+ "globalLexicalBinding",
104
+ def.type === "ClassName" ? "class" : `'${def.parent.kind}'`
105
+ );
106
+ }
107
+ }
39
108
  }
40
109
  });
41
110
  });
42
111
 
112
+ // Undeclared assigned variables.
43
113
  scope.implicit.variables.forEach(variable => {
44
114
  const scopeVariable = scope.set.get(variable.name);
115
+ let messageId;
45
116
 
46
- if (scopeVariable && scopeVariable.writeable) {
47
- return;
117
+ if (scopeVariable) {
118
+
119
+ // ESLint global variable
120
+ if (scopeVariable.writeable) {
121
+ return;
122
+ }
123
+ messageId = "assignmentToReadonlyGlobal";
124
+
125
+ } else {
126
+
127
+ // Reference to an unknown variable, possible global leak.
128
+ messageId = "globalVariableLeak";
48
129
  }
49
130
 
131
+ // def.node is an AssignmentExpression, ForInStatement or ForOfStatement.
50
132
  variable.defs.forEach(def => {
51
- context.report({ node: def.node, message: "Implicit global variable, assign as global property instead." });
133
+ report(def.node, messageId);
52
134
  });
53
135
  });
54
136
  }
@@ -35,22 +35,36 @@ module.exports = {
35
35
  */
36
36
  function testCodeAroundComment(node) {
37
37
 
38
- // Get the whole line and cut it off at the start of the comment
39
- const startLine = String(sourceCode.lines[node.loc.start.line - 1]);
40
- const endLine = String(sourceCode.lines[node.loc.end.line - 1]);
38
+ const startLine = String(sourceCode.lines[node.loc.start.line - 1]),
39
+ endLine = String(sourceCode.lines[node.loc.end.line - 1]),
40
+ preamble = startLine.slice(0, node.loc.start.column).trim(),
41
+ postamble = endLine.slice(node.loc.end.column).trim(),
42
+ isPreambleEmpty = !preamble,
43
+ isPostambleEmpty = !postamble;
41
44
 
42
- const preamble = startLine.slice(0, node.loc.start.column).trim();
45
+ // Nothing on both sides
46
+ if (isPreambleEmpty && isPostambleEmpty) {
47
+ return;
48
+ }
43
49
 
44
- // Also check after the comment
45
- const postamble = endLine.slice(node.loc.end.column).trim();
50
+ // JSX Exception
51
+ if (
52
+ (isPreambleEmpty || preamble === "{") &&
53
+ (isPostambleEmpty || postamble === "}")
54
+ ) {
55
+ const enclosingNode = sourceCode.getNodeByRangeIndex(node.range[0]);
46
56
 
47
- // Check that this comment isn't an ESLint directive
48
- const isDirective = astUtils.isDirectiveComment(node);
57
+ if (enclosingNode && enclosingNode.type === "JSXEmptyExpression") {
58
+ return;
59
+ }
60
+ }
49
61
 
50
- // Should be empty if there was only whitespace around the comment
51
- if (!isDirective && (preamble || postamble)) {
52
- context.report({ node, message: "Unexpected comment inline with code." });
62
+ // Don't report ESLint directive comments
63
+ if (astUtils.isDirectiveComment(node)) {
64
+ return;
53
65
  }
66
+
67
+ context.report({ node, message: "Unexpected comment inline with code." });
54
68
  }
55
69
 
56
70
  //--------------------------------------------------------------------------
@@ -26,10 +26,23 @@ module.exports = {
26
26
  url: "https://eslint.org/docs/rules/no-invalid-this"
27
27
  },
28
28
 
29
- schema: []
29
+ schema: [
30
+ {
31
+ type: "object",
32
+ properties: {
33
+ capIsConstructor: {
34
+ type: "boolean",
35
+ default: true
36
+ }
37
+ },
38
+ additionalProperties: false
39
+ }
40
+ ]
30
41
  },
31
42
 
32
43
  create(context) {
44
+ const options = context.options[0] || {};
45
+ const capIsConstructor = options.capIsConstructor !== false;
33
46
  const stack = [],
34
47
  sourceCode = context.getSourceCode();
35
48
 
@@ -48,7 +61,8 @@ module.exports = {
48
61
  current.init = true;
49
62
  current.valid = !astUtils.isDefaultThisBinding(
50
63
  current.node,
51
- sourceCode
64
+ sourceCode,
65
+ { capIsConstructor }
52
66
  );
53
67
  }
54
68
  return current;
@@ -110,7 +110,7 @@ module.exports = {
110
110
  if (lineNumber - lastLineNumber - 1 > maxAllowed) {
111
111
  context.report({
112
112
  node,
113
- loc: { start: { line: lastLineNumber + 1, column: 0 }, end: { line: lineNumber, column: 0 } },
113
+ loc: { start: { line: lastLineNumber + maxAllowed + 1, column: 0 }, end: { line: lineNumber, column: 0 } },
114
114
  message,
115
115
  data: { max: maxAllowed, pluralizedLines: maxAllowed === 1 ? "line" : "lines" },
116
116
  fix(fixer) {
@@ -38,7 +38,7 @@ module.exports = {
38
38
 
39
39
  // \0 represents a valid NULL character if it isn't followed by a digit.
40
40
  const match = node.raw.match(
41
- /^(?:[^\\]|\\.)*?\\([0-3][0-7]{1,2}|[4-7][0-7]|[1-7])/u
41
+ /^(?:[^\\]|\\.)*?\\([0-3][0-7]{1,2}|[4-7][0-7]|0(?=[89])|[1-7])/su
42
42
  );
43
43
 
44
44
  if (match) {
@@ -72,14 +72,14 @@ module.exports = {
72
72
  arrayOfStringsOrObjects,
73
73
  {
74
74
  type: "array",
75
- items: {
75
+ items: [{
76
76
  type: "object",
77
77
  properties: {
78
78
  paths: arrayOfStringsOrObjects,
79
79
  patterns: arrayOfStrings
80
80
  },
81
81
  additionalProperties: false
82
- },
82
+ }],
83
83
  additionalItems: false
84
84
  }
85
85
  ]
@@ -0,0 +1,227 @@
1
+ /**
2
+ * @fileoverview Rule to disallow returning values from setters
3
+ * @author Milos Djermanovic
4
+ */
5
+
6
+ "use strict";
7
+
8
+ //------------------------------------------------------------------------------
9
+ // Requirements
10
+ //------------------------------------------------------------------------------
11
+
12
+ const astUtils = require("./utils/ast-utils");
13
+ const { findVariable } = require("eslint-utils");
14
+
15
+ //------------------------------------------------------------------------------
16
+ // Helpers
17
+ //------------------------------------------------------------------------------
18
+
19
+ /**
20
+ * Determines whether the given identifier node is a reference to a global variable.
21
+ * @param {ASTNode} node `Identifier` node to check.
22
+ * @param {Scope} scope Scope to which the node belongs.
23
+ * @returns {boolean} True if the identifier is a reference to a global variable.
24
+ */
25
+ function isGlobalReference(node, scope) {
26
+ const variable = findVariable(scope, node);
27
+
28
+ return variable !== null && variable.scope.type === "global" && variable.defs.length === 0;
29
+ }
30
+
31
+ /**
32
+ * Determines whether the given node is an argument of the specified global method call, at the given `index` position.
33
+ * E.g., for given `index === 1`, this function checks for `objectName.methodName(foo, node)`, where objectName is a global variable.
34
+ * @param {ASTNode} node The node to check.
35
+ * @param {Scope} scope Scope to which the node belongs.
36
+ * @param {string} objectName Name of the global object.
37
+ * @param {string} methodName Name of the method.
38
+ * @param {number} index The given position.
39
+ * @returns {boolean} `true` if the node is argument at the given position.
40
+ */
41
+ function isArgumentOfGlobalMethodCall(node, scope, objectName, methodName, index) {
42
+ const parent = node.parent;
43
+
44
+ return parent.type === "CallExpression" &&
45
+ parent.arguments[index] === node &&
46
+ parent.callee.type === "MemberExpression" &&
47
+ astUtils.getStaticPropertyName(parent.callee) === methodName &&
48
+ parent.callee.object.type === "Identifier" &&
49
+ parent.callee.object.name === objectName &&
50
+ isGlobalReference(parent.callee.object, scope);
51
+ }
52
+
53
+ /**
54
+ * Determines whether the given node is used as a property descriptor.
55
+ * @param {ASTNode} node The node to check.
56
+ * @param {Scope} scope Scope to which the node belongs.
57
+ * @returns {boolean} `true` if the node is a property descriptor.
58
+ */
59
+ function isPropertyDescriptor(node, scope) {
60
+ if (
61
+ isArgumentOfGlobalMethodCall(node, scope, "Object", "defineProperty", 2) ||
62
+ isArgumentOfGlobalMethodCall(node, scope, "Reflect", "defineProperty", 2)
63
+ ) {
64
+ return true;
65
+ }
66
+
67
+ const parent = node.parent;
68
+
69
+ if (
70
+ parent.type === "Property" &&
71
+ parent.value === node
72
+ ) {
73
+ const grandparent = parent.parent;
74
+
75
+ if (
76
+ grandparent.type === "ObjectExpression" &&
77
+ (
78
+ isArgumentOfGlobalMethodCall(grandparent, scope, "Object", "create", 1) ||
79
+ isArgumentOfGlobalMethodCall(grandparent, scope, "Object", "defineProperties", 1)
80
+ )
81
+ ) {
82
+ return true;
83
+ }
84
+ }
85
+
86
+ return false;
87
+ }
88
+
89
+ /**
90
+ * Determines whether the given function node is used as a setter function.
91
+ * @param {ASTNode} node The node to check.
92
+ * @param {Scope} scope Scope to which the node belongs.
93
+ * @returns {boolean} `true` if the node is a setter.
94
+ */
95
+ function isSetter(node, scope) {
96
+ const parent = node.parent;
97
+
98
+ if (
99
+ parent.kind === "set" &&
100
+ parent.value === node
101
+ ) {
102
+
103
+ // Setter in an object literal or in a class
104
+ return true;
105
+ }
106
+
107
+ if (
108
+ parent.type === "Property" &&
109
+ parent.value === node &&
110
+ astUtils.getStaticPropertyName(parent) === "set" &&
111
+ parent.parent.type === "ObjectExpression" &&
112
+ isPropertyDescriptor(parent.parent, scope)
113
+ ) {
114
+
115
+ // Setter in a property descriptor
116
+ return true;
117
+ }
118
+
119
+ return false;
120
+ }
121
+
122
+ /**
123
+ * Finds function's outer scope.
124
+ * @param {Scope} scope Function's own scope.
125
+ * @returns {Scope} Function's outer scope.
126
+ */
127
+ function getOuterScope(scope) {
128
+ const upper = scope.upper;
129
+
130
+ if (upper.type === "function-expression-name") {
131
+ return upper.upper;
132
+ }
133
+
134
+ return upper;
135
+ }
136
+
137
+ //------------------------------------------------------------------------------
138
+ // Rule Definition
139
+ //------------------------------------------------------------------------------
140
+
141
+ module.exports = {
142
+ meta: {
143
+ type: "problem",
144
+
145
+ docs: {
146
+ description: "disallow returning values from setters",
147
+ category: "Possible Errors",
148
+ recommended: false,
149
+ url: "https://eslint.org/docs/rules/no-setter-return"
150
+ },
151
+
152
+ schema: [],
153
+
154
+ messages: {
155
+ returnsValue: "Setter cannot return a value."
156
+ }
157
+ },
158
+
159
+ create(context) {
160
+ let funcInfo = null;
161
+
162
+ /**
163
+ * Creates and pushes to the stack a function info object for the given function node.
164
+ * @param {ASTNode} node The function node.
165
+ * @returns {void}
166
+ */
167
+ function enterFunction(node) {
168
+ const outerScope = getOuterScope(context.getScope());
169
+
170
+ funcInfo = {
171
+ upper: funcInfo,
172
+ isSetter: isSetter(node, outerScope)
173
+ };
174
+ }
175
+
176
+ /**
177
+ * Pops the current function info object from the stack.
178
+ * @returns {void}
179
+ */
180
+ function exitFunction() {
181
+ funcInfo = funcInfo.upper;
182
+ }
183
+
184
+ /**
185
+ * Reports the given node.
186
+ * @param {ASTNode} node Node to report.
187
+ * @returns {void}
188
+ */
189
+ function report(node) {
190
+ context.report({ node, messageId: "returnsValue" });
191
+ }
192
+
193
+ return {
194
+
195
+ /*
196
+ * Function declarations cannot be setters, but we still have to track them in the `funcInfo` stack to avoid
197
+ * false positives, because a ReturnStatement node can belong to a function declaration inside a setter.
198
+ *
199
+ * Note: A previously declared function can be referenced and actually used as a setter in a property descriptor,
200
+ * but that's out of scope for this rule.
201
+ */
202
+ FunctionDeclaration: enterFunction,
203
+ FunctionExpression: enterFunction,
204
+ ArrowFunctionExpression(node) {
205
+ enterFunction(node);
206
+
207
+ if (funcInfo.isSetter && node.expression) {
208
+
209
+ // { set: foo => bar } property descriptor. Report implicit return 'bar' as the equivalent for a return statement.
210
+ report(node.body);
211
+ }
212
+ },
213
+
214
+ "FunctionDeclaration:exit": exitFunction,
215
+ "FunctionExpression:exit": exitFunction,
216
+ "ArrowFunctionExpression:exit": exitFunction,
217
+
218
+ ReturnStatement(node) {
219
+
220
+ // Global returns (e.g., at the top level of a Node module) don't have `funcInfo`.
221
+ if (funcInfo && funcInfo.isSetter && node.argument) {
222
+ report(node);
223
+ }
224
+ }
225
+ };
226
+ }
227
+ };