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.
- package/CHANGELOG.md +94 -0
- package/README.md +7 -8
- package/conf/config-schema.js +2 -0
- package/conf/default-cli-options.js +1 -1
- package/conf/eslint-recommended.js +0 -1
- package/lib/cli-engine/cascading-config-array-factory.js +38 -13
- package/lib/cli-engine/cli-engine.js +41 -14
- package/lib/cli-engine/config-array/config-array.js +13 -0
- package/lib/cli-engine/config-array/extracted-config.js +27 -0
- package/lib/cli-engine/config-array/ignore-pattern.js +231 -0
- package/lib/cli-engine/config-array/index.js +2 -0
- package/lib/cli-engine/config-array-factory.js +115 -1
- package/lib/cli-engine/file-enumerator.js +73 -40
- package/lib/cli-engine/lint-result-cache.js +2 -1
- package/lib/cli.js +2 -1
- package/lib/init/config-initializer.js +4 -3
- package/lib/linter/config-comment-parser.js +1 -1
- package/lib/linter/report-translator.js +73 -7
- package/lib/options.js +6 -0
- package/lib/rule-tester/rule-tester.js +42 -6
- package/lib/rules/array-bracket-spacing.js +8 -8
- package/lib/rules/camelcase.js +19 -6
- package/lib/rules/comma-dangle.js +5 -2
- package/lib/rules/computed-property-spacing.js +4 -4
- package/lib/rules/curly.js +9 -4
- package/lib/rules/function-call-argument-newline.js +3 -1
- package/lib/rules/grouped-accessor-pairs.js +224 -0
- package/lib/rules/indent.js +11 -0
- package/lib/rules/index.js +5 -0
- package/lib/rules/key-spacing.js +34 -15
- package/lib/rules/lines-between-class-members.js +42 -53
- package/lib/rules/multiline-comment-style.js +237 -106
- package/lib/rules/no-cond-assign.js +14 -4
- package/lib/rules/no-constructor-return.js +62 -0
- package/lib/rules/no-dupe-else-if.js +122 -0
- package/lib/rules/no-implicit-globals.js +90 -8
- package/lib/rules/no-inline-comments.js +25 -11
- package/lib/rules/no-invalid-this.js +16 -2
- package/lib/rules/no-multiple-empty-lines.js +1 -1
- package/lib/rules/no-octal-escape.js +1 -1
- package/lib/rules/no-restricted-imports.js +2 -2
- package/lib/rules/no-setter-return.js +227 -0
- package/lib/rules/no-underscore-dangle.js +23 -4
- package/lib/rules/no-unexpected-multiline.js +8 -0
- package/lib/rules/no-unsafe-negation.js +30 -5
- package/lib/rules/no-useless-computed-key.js +60 -33
- package/lib/rules/no-useless-escape.js +26 -3
- package/lib/rules/object-curly-spacing.js +8 -8
- package/lib/rules/operator-assignment.js +11 -2
- package/lib/rules/prefer-const.js +14 -7
- package/lib/rules/prefer-exponentiation-operator.js +189 -0
- package/lib/rules/prefer-numeric-literals.js +29 -28
- package/lib/rules/require-atomic-updates.js +1 -1
- package/lib/rules/require-await.js +8 -0
- package/lib/rules/semi.js +6 -3
- package/lib/rules/space-infix-ops.js +1 -1
- package/lib/rules/spaced-comment.js +5 -4
- package/lib/rules/utils/ast-utils.js +31 -4
- package/lib/shared/types.js +9 -0
- package/lib/source-code/source-code.js +87 -10
- package/package.json +4 -3
- 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
|
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
|
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
|
-
|
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
|
-
|
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
|
47
|
-
|
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
|
-
|
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
|
-
|
39
|
-
|
40
|
-
|
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
|
-
|
45
|
+
// Nothing on both sides
|
46
|
+
if (isPreambleEmpty && isPostambleEmpty) {
|
47
|
+
return;
|
48
|
+
}
|
43
49
|
|
44
|
-
//
|
45
|
-
|
50
|
+
// JSX Exception
|
51
|
+
if (
|
52
|
+
(isPreambleEmpty || preamble === "{") &&
|
53
|
+
(isPostambleEmpty || postamble === "}")
|
54
|
+
) {
|
55
|
+
const enclosingNode = sourceCode.getNodeByRangeIndex(node.range[0]);
|
46
56
|
|
47
|
-
|
48
|
-
|
57
|
+
if (enclosingNode && enclosingNode.type === "JSXEmptyExpression") {
|
58
|
+
return;
|
59
|
+
}
|
60
|
+
}
|
49
61
|
|
50
|
-
//
|
51
|
-
if (
|
52
|
-
|
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])/
|
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
|
+
};
|