eslint 8.48.0 → 8.50.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/README.md +2 -2
- package/lib/config/flat-config-schema.js +11 -1
- package/lib/config/rule-validator.js +2 -1
- package/lib/linter/code-path-analysis/code-path-analyzer.js +32 -24
- package/lib/linter/code-path-analysis/code-path.js +1 -0
- package/lib/linter/linter.js +173 -57
- package/lib/rule-tester/flat-rule-tester.js +77 -5
- package/lib/rule-tester/rule-tester.js +146 -3
- package/lib/rules/array-callback-return.js +175 -25
- package/lib/rules/consistent-return.js +32 -7
- package/lib/rules/constructor-super.js +37 -14
- package/lib/rules/getter-return.js +33 -8
- package/lib/rules/index.js +1 -0
- package/lib/rules/lines-between-class-members.js +92 -7
- package/lib/rules/no-fallthrough.js +42 -14
- package/lib/rules/no-misleading-character-class.js +65 -15
- package/lib/rules/no-new-object.js +7 -0
- package/lib/rules/no-object-constructor.js +118 -0
- package/lib/rules/no-this-before-super.js +38 -11
- package/lib/rules/no-unreachable-loop.js +47 -12
- package/lib/rules/no-unreachable.js +39 -10
- package/lib/rules/no-useless-return.js +35 -4
- package/lib/rules/require-atomic-updates.js +21 -7
- package/lib/source-code/source-code.js +350 -3
- package/package.json +11 -9
@@ -14,15 +14,23 @@ const astUtils = require("./utils/ast-utils");
|
|
14
14
|
//------------------------------------------------------------------------------
|
15
15
|
// Helpers
|
16
16
|
//------------------------------------------------------------------------------
|
17
|
+
|
17
18
|
const TARGET_NODE_TYPE = /^(?:Arrow)?FunctionExpression$/u;
|
18
19
|
|
19
20
|
/**
|
20
|
-
* Checks a
|
21
|
-
* @param {CodePathSegment}
|
22
|
-
* @returns {boolean}
|
21
|
+
* Checks all segments in a set and returns true if any are reachable.
|
22
|
+
* @param {Set<CodePathSegment>} segments The segments to check.
|
23
|
+
* @returns {boolean} True if any segment is reachable; false otherwise.
|
23
24
|
*/
|
24
|
-
function
|
25
|
-
|
25
|
+
function isAnySegmentReachable(segments) {
|
26
|
+
|
27
|
+
for (const segment of segments) {
|
28
|
+
if (segment.reachable) {
|
29
|
+
return true;
|
30
|
+
}
|
31
|
+
}
|
32
|
+
|
33
|
+
return false;
|
26
34
|
}
|
27
35
|
|
28
36
|
//------------------------------------------------------------------------------
|
@@ -71,7 +79,8 @@ module.exports = {
|
|
71
79
|
codePath: null,
|
72
80
|
hasReturn: false,
|
73
81
|
shouldCheck: false,
|
74
|
-
node: null
|
82
|
+
node: null,
|
83
|
+
currentSegments: []
|
75
84
|
};
|
76
85
|
|
77
86
|
/**
|
@@ -85,7 +94,7 @@ module.exports = {
|
|
85
94
|
*/
|
86
95
|
function checkLastSegment(node) {
|
87
96
|
if (funcInfo.shouldCheck &&
|
88
|
-
funcInfo.
|
97
|
+
isAnySegmentReachable(funcInfo.currentSegments)
|
89
98
|
) {
|
90
99
|
context.report({
|
91
100
|
node,
|
@@ -144,7 +153,8 @@ module.exports = {
|
|
144
153
|
codePath,
|
145
154
|
hasReturn: false,
|
146
155
|
shouldCheck: isGetter(node),
|
147
|
-
node
|
156
|
+
node,
|
157
|
+
currentSegments: new Set()
|
148
158
|
};
|
149
159
|
},
|
150
160
|
|
@@ -152,6 +162,21 @@ module.exports = {
|
|
152
162
|
onCodePathEnd() {
|
153
163
|
funcInfo = funcInfo.upper;
|
154
164
|
},
|
165
|
+
onUnreachableCodePathSegmentStart(segment) {
|
166
|
+
funcInfo.currentSegments.add(segment);
|
167
|
+
},
|
168
|
+
|
169
|
+
onUnreachableCodePathSegmentEnd(segment) {
|
170
|
+
funcInfo.currentSegments.delete(segment);
|
171
|
+
},
|
172
|
+
|
173
|
+
onCodePathSegmentStart(segment) {
|
174
|
+
funcInfo.currentSegments.add(segment);
|
175
|
+
},
|
176
|
+
|
177
|
+
onCodePathSegmentEnd(segment) {
|
178
|
+
funcInfo.currentSegments.delete(segment);
|
179
|
+
},
|
155
180
|
|
156
181
|
// Checks the return statement is valid.
|
157
182
|
ReturnStatement(node) {
|
package/lib/rules/index.js
CHANGED
@@ -175,6 +175,7 @@ module.exports = new LazyLoadingRuleMap(Object.entries({
|
|
175
175
|
"no-new-wrappers": () => require("./no-new-wrappers"),
|
176
176
|
"no-nonoctal-decimal-escape": () => require("./no-nonoctal-decimal-escape"),
|
177
177
|
"no-obj-calls": () => require("./no-obj-calls"),
|
178
|
+
"no-object-constructor": () => require("./no-object-constructor"),
|
178
179
|
"no-octal": () => require("./no-octal"),
|
179
180
|
"no-octal-escape": () => require("./no-octal-escape"),
|
180
181
|
"no-param-reassign": () => require("./no-param-reassign"),
|
@@ -10,6 +10,21 @@
|
|
10
10
|
|
11
11
|
const astUtils = require("./utils/ast-utils");
|
12
12
|
|
13
|
+
//------------------------------------------------------------------------------
|
14
|
+
// Helpers
|
15
|
+
//------------------------------------------------------------------------------
|
16
|
+
|
17
|
+
/**
|
18
|
+
* Types of class members.
|
19
|
+
* Those have `test` method to check it matches to the given class member.
|
20
|
+
* @private
|
21
|
+
*/
|
22
|
+
const ClassMemberTypes = {
|
23
|
+
"*": { test: () => true },
|
24
|
+
field: { test: node => node.type === "PropertyDefinition" },
|
25
|
+
method: { test: node => node.type === "MethodDefinition" }
|
26
|
+
};
|
27
|
+
|
13
28
|
//------------------------------------------------------------------------------
|
14
29
|
// Rule Definition
|
15
30
|
//------------------------------------------------------------------------------
|
@@ -29,7 +44,32 @@ module.exports = {
|
|
29
44
|
|
30
45
|
schema: [
|
31
46
|
{
|
32
|
-
|
47
|
+
anyOf: [
|
48
|
+
{
|
49
|
+
type: "object",
|
50
|
+
properties: {
|
51
|
+
enforce: {
|
52
|
+
type: "array",
|
53
|
+
items: {
|
54
|
+
type: "object",
|
55
|
+
properties: {
|
56
|
+
blankLine: { enum: ["always", "never"] },
|
57
|
+
prev: { enum: ["method", "field", "*"] },
|
58
|
+
next: { enum: ["method", "field", "*"] }
|
59
|
+
},
|
60
|
+
additionalProperties: false,
|
61
|
+
required: ["blankLine", "prev", "next"]
|
62
|
+
},
|
63
|
+
minItems: 1
|
64
|
+
}
|
65
|
+
},
|
66
|
+
additionalProperties: false,
|
67
|
+
required: ["enforce"]
|
68
|
+
},
|
69
|
+
{
|
70
|
+
enum: ["always", "never"]
|
71
|
+
}
|
72
|
+
]
|
33
73
|
},
|
34
74
|
{
|
35
75
|
type: "object",
|
@@ -55,6 +95,7 @@ module.exports = {
|
|
55
95
|
options[0] = context.options[0] || "always";
|
56
96
|
options[1] = context.options[1] || { exceptAfterSingleLine: false };
|
57
97
|
|
98
|
+
const configureList = typeof options[0] === "object" ? options[0].enforce : [{ blankLine: options[0], prev: "*", next: "*" }];
|
58
99
|
const sourceCode = context.sourceCode;
|
59
100
|
|
60
101
|
/**
|
@@ -144,6 +185,38 @@ module.exports = {
|
|
144
185
|
return sourceCode.getTokensBetween(before, after, { includeComments: true }).length !== 0;
|
145
186
|
}
|
146
187
|
|
188
|
+
/**
|
189
|
+
* Checks whether the given node matches the given type.
|
190
|
+
* @param {ASTNode} node The class member node to check.
|
191
|
+
* @param {string} type The class member type to check.
|
192
|
+
* @returns {boolean} `true` if the class member node matched the type.
|
193
|
+
* @private
|
194
|
+
*/
|
195
|
+
function match(node, type) {
|
196
|
+
return ClassMemberTypes[type].test(node);
|
197
|
+
}
|
198
|
+
|
199
|
+
/**
|
200
|
+
* Finds the last matched configuration from the configureList.
|
201
|
+
* @param {ASTNode} prevNode The previous node to match.
|
202
|
+
* @param {ASTNode} nextNode The current node to match.
|
203
|
+
* @returns {string|null} Padding type or `null` if no matches were found.
|
204
|
+
* @private
|
205
|
+
*/
|
206
|
+
function getPaddingType(prevNode, nextNode) {
|
207
|
+
for (let i = configureList.length - 1; i >= 0; --i) {
|
208
|
+
const configure = configureList[i];
|
209
|
+
const matched =
|
210
|
+
match(prevNode, configure.prev) &&
|
211
|
+
match(nextNode, configure.next);
|
212
|
+
|
213
|
+
if (matched) {
|
214
|
+
return configure.blankLine;
|
215
|
+
}
|
216
|
+
}
|
217
|
+
return null;
|
218
|
+
}
|
219
|
+
|
147
220
|
return {
|
148
221
|
ClassBody(node) {
|
149
222
|
const body = node.body;
|
@@ -158,22 +231,34 @@ module.exports = {
|
|
158
231
|
const isPadded = afterPadding.loc.start.line - beforePadding.loc.end.line > 1;
|
159
232
|
const hasTokenInPadding = hasTokenOrCommentBetween(beforePadding, afterPadding);
|
160
233
|
const curLineLastToken = findLastConsecutiveTokenAfter(curLast, nextFirst, 0);
|
234
|
+
const paddingType = getPaddingType(body[i], body[i + 1]);
|
235
|
+
|
236
|
+
if (paddingType === "never" && isPadded) {
|
237
|
+
context.report({
|
238
|
+
node: body[i + 1],
|
239
|
+
messageId: "never",
|
161
240
|
|
162
|
-
|
163
|
-
|
241
|
+
fix(fixer) {
|
242
|
+
if (hasTokenInPadding) {
|
243
|
+
return null;
|
244
|
+
}
|
245
|
+
return fixer.replaceTextRange([beforePadding.range[1], afterPadding.range[0]], "\n");
|
246
|
+
}
|
247
|
+
});
|
248
|
+
} else if (paddingType === "always" && !skip && !isPadded) {
|
164
249
|
context.report({
|
165
250
|
node: body[i + 1],
|
166
|
-
messageId:
|
251
|
+
messageId: "always",
|
252
|
+
|
167
253
|
fix(fixer) {
|
168
254
|
if (hasTokenInPadding) {
|
169
255
|
return null;
|
170
256
|
}
|
171
|
-
return
|
172
|
-
? fixer.replaceTextRange([beforePadding.range[1], afterPadding.range[0]], "\n")
|
173
|
-
: fixer.insertTextAfter(curLineLastToken, "\n");
|
257
|
+
return fixer.insertTextAfter(curLineLastToken, "\n");
|
174
258
|
}
|
175
259
|
});
|
176
260
|
}
|
261
|
+
|
177
262
|
}
|
178
263
|
}
|
179
264
|
};
|
@@ -16,6 +16,22 @@ const { directivesPattern } = require("../shared/directives");
|
|
16
16
|
|
17
17
|
const DEFAULT_FALLTHROUGH_COMMENT = /falls?\s?through/iu;
|
18
18
|
|
19
|
+
/**
|
20
|
+
* Checks all segments in a set and returns true if any are reachable.
|
21
|
+
* @param {Set<CodePathSegment>} segments The segments to check.
|
22
|
+
* @returns {boolean} True if any segment is reachable; false otherwise.
|
23
|
+
*/
|
24
|
+
function isAnySegmentReachable(segments) {
|
25
|
+
|
26
|
+
for (const segment of segments) {
|
27
|
+
if (segment.reachable) {
|
28
|
+
return true;
|
29
|
+
}
|
30
|
+
}
|
31
|
+
|
32
|
+
return false;
|
33
|
+
}
|
34
|
+
|
19
35
|
/**
|
20
36
|
* Checks whether or not a given comment string is really a fallthrough comment and not an ESLint directive.
|
21
37
|
* @param {string} comment The comment string to check.
|
@@ -51,15 +67,6 @@ function hasFallthroughComment(caseWhichFallsThrough, subsequentCase, context, f
|
|
51
67
|
return Boolean(comment && isFallThroughComment(comment.value, fallthroughCommentPattern));
|
52
68
|
}
|
53
69
|
|
54
|
-
/**
|
55
|
-
* Checks whether or not a given code path segment is reachable.
|
56
|
-
* @param {CodePathSegment} segment A CodePathSegment to check.
|
57
|
-
* @returns {boolean} `true` if the segment is reachable.
|
58
|
-
*/
|
59
|
-
function isReachable(segment) {
|
60
|
-
return segment.reachable;
|
61
|
-
}
|
62
|
-
|
63
70
|
/**
|
64
71
|
* Checks whether a node and a token are separated by blank lines
|
65
72
|
* @param {ASTNode} node The node to check
|
@@ -109,7 +116,8 @@ module.exports = {
|
|
109
116
|
|
110
117
|
create(context) {
|
111
118
|
const options = context.options[0] || {};
|
112
|
-
|
119
|
+
const codePathSegments = [];
|
120
|
+
let currentCodePathSegments = new Set();
|
113
121
|
const sourceCode = context.sourceCode;
|
114
122
|
const allowEmptyCase = options.allowEmptyCase || false;
|
115
123
|
|
@@ -126,13 +134,33 @@ module.exports = {
|
|
126
134
|
fallthroughCommentPattern = DEFAULT_FALLTHROUGH_COMMENT;
|
127
135
|
}
|
128
136
|
return {
|
129
|
-
|
130
|
-
|
137
|
+
|
138
|
+
onCodePathStart() {
|
139
|
+
codePathSegments.push(currentCodePathSegments);
|
140
|
+
currentCodePathSegments = new Set();
|
131
141
|
},
|
142
|
+
|
132
143
|
onCodePathEnd() {
|
133
|
-
|
144
|
+
currentCodePathSegments = codePathSegments.pop();
|
145
|
+
},
|
146
|
+
|
147
|
+
onUnreachableCodePathSegmentStart(segment) {
|
148
|
+
currentCodePathSegments.add(segment);
|
149
|
+
},
|
150
|
+
|
151
|
+
onUnreachableCodePathSegmentEnd(segment) {
|
152
|
+
currentCodePathSegments.delete(segment);
|
153
|
+
},
|
154
|
+
|
155
|
+
onCodePathSegmentStart(segment) {
|
156
|
+
currentCodePathSegments.add(segment);
|
134
157
|
},
|
135
158
|
|
159
|
+
onCodePathSegmentEnd(segment) {
|
160
|
+
currentCodePathSegments.delete(segment);
|
161
|
+
},
|
162
|
+
|
163
|
+
|
136
164
|
SwitchCase(node) {
|
137
165
|
|
138
166
|
/*
|
@@ -157,7 +185,7 @@ module.exports = {
|
|
157
185
|
* `break`, `return`, or `throw` are unreachable.
|
158
186
|
* And allows empty cases and the last case.
|
159
187
|
*/
|
160
|
-
if (
|
188
|
+
if (isAnySegmentReachable(currentCodePathSegments) &&
|
161
189
|
(node.consequent.length > 0 || (!allowEmptyCase && hasBlankLinesBetween(node, nextToken))) &&
|
162
190
|
node.parent.cases[node.parent.cases.length - 1] !== node) {
|
163
191
|
fallthroughCase = node;
|
@@ -13,27 +13,34 @@ const { isValidWithUnicodeFlag } = require("./utils/regular-expressions");
|
|
13
13
|
// Helpers
|
14
14
|
//------------------------------------------------------------------------------
|
15
15
|
|
16
|
+
/**
|
17
|
+
* @typedef {import('@eslint-community/regexpp').AST.Character} Character
|
18
|
+
* @typedef {import('@eslint-community/regexpp').AST.CharacterClassElement} CharacterClassElement
|
19
|
+
*/
|
20
|
+
|
16
21
|
/**
|
17
22
|
* Iterate character sequences of a given nodes.
|
18
23
|
*
|
19
24
|
* CharacterClassRange syntax can steal a part of character sequence,
|
20
25
|
* so this function reverts CharacterClassRange syntax and restore the sequence.
|
21
|
-
* @param {
|
22
|
-
* @returns {IterableIterator<
|
26
|
+
* @param {CharacterClassElement[]} nodes The node list to iterate character sequences.
|
27
|
+
* @returns {IterableIterator<Character[]>} The list of character sequences.
|
23
28
|
*/
|
24
29
|
function *iterateCharacterSequence(nodes) {
|
30
|
+
|
31
|
+
/** @type {Character[]} */
|
25
32
|
let seq = [];
|
26
33
|
|
27
34
|
for (const node of nodes) {
|
28
35
|
switch (node.type) {
|
29
36
|
case "Character":
|
30
|
-
seq.push(node
|
37
|
+
seq.push(node);
|
31
38
|
break;
|
32
39
|
|
33
40
|
case "CharacterClassRange":
|
34
|
-
seq.push(node.min
|
41
|
+
seq.push(node.min);
|
35
42
|
yield seq;
|
36
|
-
seq = [node.max
|
43
|
+
seq = [node.max];
|
37
44
|
break;
|
38
45
|
|
39
46
|
case "CharacterSet":
|
@@ -55,32 +62,74 @@ function *iterateCharacterSequence(nodes) {
|
|
55
62
|
}
|
56
63
|
}
|
57
64
|
|
65
|
+
|
66
|
+
/**
|
67
|
+
* Checks whether the given character node is a Unicode code point escape or not.
|
68
|
+
* @param {Character} char the character node to check.
|
69
|
+
* @returns {boolean} `true` if the character node is a Unicode code point escape.
|
70
|
+
*/
|
71
|
+
function isUnicodeCodePointEscape(char) {
|
72
|
+
return /^\\u\{[\da-f]+\}$/iu.test(char.raw);
|
73
|
+
}
|
74
|
+
|
75
|
+
/**
|
76
|
+
* Each function returns `true` if it detects that kind of problem.
|
77
|
+
* @type {Record<string, (chars: Character[]) => boolean>}
|
78
|
+
*/
|
58
79
|
const hasCharacterSequence = {
|
59
80
|
surrogatePairWithoutUFlag(chars) {
|
60
|
-
return chars.some((c, i) =>
|
81
|
+
return chars.some((c, i) => {
|
82
|
+
if (i === 0) {
|
83
|
+
return false;
|
84
|
+
}
|
85
|
+
const c1 = chars[i - 1];
|
86
|
+
|
87
|
+
return (
|
88
|
+
isSurrogatePair(c1.value, c.value) &&
|
89
|
+
!isUnicodeCodePointEscape(c1) &&
|
90
|
+
!isUnicodeCodePointEscape(c)
|
91
|
+
);
|
92
|
+
});
|
93
|
+
},
|
94
|
+
|
95
|
+
surrogatePair(chars) {
|
96
|
+
return chars.some((c, i) => {
|
97
|
+
if (i === 0) {
|
98
|
+
return false;
|
99
|
+
}
|
100
|
+
const c1 = chars[i - 1];
|
101
|
+
|
102
|
+
return (
|
103
|
+
isSurrogatePair(c1.value, c.value) &&
|
104
|
+
(
|
105
|
+
isUnicodeCodePointEscape(c1) ||
|
106
|
+
isUnicodeCodePointEscape(c)
|
107
|
+
)
|
108
|
+
);
|
109
|
+
});
|
61
110
|
},
|
62
111
|
|
63
112
|
combiningClass(chars) {
|
64
113
|
return chars.some((c, i) => (
|
65
114
|
i !== 0 &&
|
66
|
-
isCombiningCharacter(c) &&
|
67
|
-
!isCombiningCharacter(chars[i - 1])
|
115
|
+
isCombiningCharacter(c.value) &&
|
116
|
+
!isCombiningCharacter(chars[i - 1].value)
|
68
117
|
));
|
69
118
|
},
|
70
119
|
|
71
120
|
emojiModifier(chars) {
|
72
121
|
return chars.some((c, i) => (
|
73
122
|
i !== 0 &&
|
74
|
-
isEmojiModifier(c) &&
|
75
|
-
!isEmojiModifier(chars[i - 1])
|
123
|
+
isEmojiModifier(c.value) &&
|
124
|
+
!isEmojiModifier(chars[i - 1].value)
|
76
125
|
));
|
77
126
|
},
|
78
127
|
|
79
128
|
regionalIndicatorSymbol(chars) {
|
80
129
|
return chars.some((c, i) => (
|
81
130
|
i !== 0 &&
|
82
|
-
isRegionalIndicatorSymbol(c) &&
|
83
|
-
isRegionalIndicatorSymbol(chars[i - 1])
|
131
|
+
isRegionalIndicatorSymbol(c.value) &&
|
132
|
+
isRegionalIndicatorSymbol(chars[i - 1].value)
|
84
133
|
));
|
85
134
|
},
|
86
135
|
|
@@ -90,9 +139,9 @@ const hasCharacterSequence = {
|
|
90
139
|
return chars.some((c, i) => (
|
91
140
|
i !== 0 &&
|
92
141
|
i !== lastIndex &&
|
93
|
-
c === 0x200d &&
|
94
|
-
chars[i - 1] !== 0x200d &&
|
95
|
-
chars[i + 1] !== 0x200d
|
142
|
+
c.value === 0x200d &&
|
143
|
+
chars[i - 1].value !== 0x200d &&
|
144
|
+
chars[i + 1].value !== 0x200d
|
96
145
|
));
|
97
146
|
}
|
98
147
|
};
|
@@ -120,6 +169,7 @@ module.exports = {
|
|
120
169
|
|
121
170
|
messages: {
|
122
171
|
surrogatePairWithoutUFlag: "Unexpected surrogate pair in character class. Use 'u' flag.",
|
172
|
+
surrogatePair: "Unexpected surrogate pair in character class.",
|
123
173
|
combiningClass: "Unexpected combined character in character class.",
|
124
174
|
emojiModifier: "Unexpected modified Emoji in character class.",
|
125
175
|
regionalIndicatorSymbol: "Unexpected national flag in character class.",
|
@@ -1,6 +1,7 @@
|
|
1
1
|
/**
|
2
2
|
* @fileoverview A rule to disallow calls to the Object constructor
|
3
3
|
* @author Matt DuVall <http://www.mattduvall.com/>
|
4
|
+
* @deprecated in ESLint v8.50.0
|
4
5
|
*/
|
5
6
|
|
6
7
|
"use strict";
|
@@ -26,6 +27,12 @@ module.exports = {
|
|
26
27
|
url: "https://eslint.org/docs/latest/rules/no-new-object"
|
27
28
|
},
|
28
29
|
|
30
|
+
deprecated: true,
|
31
|
+
|
32
|
+
replacedBy: [
|
33
|
+
"no-object-constructor"
|
34
|
+
],
|
35
|
+
|
29
36
|
schema: [],
|
30
37
|
|
31
38
|
messages: {
|
@@ -0,0 +1,118 @@
|
|
1
|
+
/**
|
2
|
+
* @fileoverview Rule to disallow calls to the `Object` constructor without an argument
|
3
|
+
* @author Francesco Trotta
|
4
|
+
*/
|
5
|
+
|
6
|
+
"use strict";
|
7
|
+
|
8
|
+
//------------------------------------------------------------------------------
|
9
|
+
// Requirements
|
10
|
+
//------------------------------------------------------------------------------
|
11
|
+
|
12
|
+
const { getVariableByName, isArrowToken } = require("./utils/ast-utils");
|
13
|
+
|
14
|
+
//------------------------------------------------------------------------------
|
15
|
+
// Helpers
|
16
|
+
//------------------------------------------------------------------------------
|
17
|
+
|
18
|
+
/**
|
19
|
+
* Tests if a node appears at the beginning of an ancestor ExpressionStatement node.
|
20
|
+
* @param {ASTNode} node The node to check.
|
21
|
+
* @returns {boolean} Whether the node appears at the beginning of an ancestor ExpressionStatement node.
|
22
|
+
*/
|
23
|
+
function isStartOfExpressionStatement(node) {
|
24
|
+
const start = node.range[0];
|
25
|
+
let ancestor = node;
|
26
|
+
|
27
|
+
while ((ancestor = ancestor.parent) && ancestor.range[0] === start) {
|
28
|
+
if (ancestor.type === "ExpressionStatement") {
|
29
|
+
return true;
|
30
|
+
}
|
31
|
+
}
|
32
|
+
return false;
|
33
|
+
}
|
34
|
+
|
35
|
+
//------------------------------------------------------------------------------
|
36
|
+
// Rule Definition
|
37
|
+
//------------------------------------------------------------------------------
|
38
|
+
|
39
|
+
/** @type {import('../shared/types').Rule} */
|
40
|
+
module.exports = {
|
41
|
+
meta: {
|
42
|
+
type: "suggestion",
|
43
|
+
|
44
|
+
docs: {
|
45
|
+
description: "Disallow calls to the `Object` constructor without an argument",
|
46
|
+
recommended: false,
|
47
|
+
url: "https://eslint.org/docs/latest/rules/no-object-constructor"
|
48
|
+
},
|
49
|
+
|
50
|
+
hasSuggestions: true,
|
51
|
+
|
52
|
+
schema: [],
|
53
|
+
|
54
|
+
messages: {
|
55
|
+
preferLiteral: "The object literal notation {} is preferable.",
|
56
|
+
useLiteral: "Replace with '{{replacement}}'."
|
57
|
+
}
|
58
|
+
},
|
59
|
+
|
60
|
+
create(context) {
|
61
|
+
|
62
|
+
const sourceCode = context.sourceCode;
|
63
|
+
|
64
|
+
/**
|
65
|
+
* Determines whether or not an object literal that replaces a specified node needs to be enclosed in parentheses.
|
66
|
+
* @param {ASTNode} node The node to be replaced.
|
67
|
+
* @returns {boolean} Whether or not parentheses around the object literal are required.
|
68
|
+
*/
|
69
|
+
function needsParentheses(node) {
|
70
|
+
if (isStartOfExpressionStatement(node)) {
|
71
|
+
return true;
|
72
|
+
}
|
73
|
+
|
74
|
+
const prevToken = sourceCode.getTokenBefore(node);
|
75
|
+
|
76
|
+
if (prevToken && isArrowToken(prevToken)) {
|
77
|
+
return true;
|
78
|
+
}
|
79
|
+
|
80
|
+
return false;
|
81
|
+
}
|
82
|
+
|
83
|
+
/**
|
84
|
+
* Reports on nodes where the `Object` constructor is called without arguments.
|
85
|
+
* @param {ASTNode} node The node to evaluate.
|
86
|
+
* @returns {void}
|
87
|
+
*/
|
88
|
+
function check(node) {
|
89
|
+
if (node.callee.type !== "Identifier" || node.callee.name !== "Object" || node.arguments.length) {
|
90
|
+
return;
|
91
|
+
}
|
92
|
+
|
93
|
+
const variable = getVariableByName(sourceCode.getScope(node), "Object");
|
94
|
+
|
95
|
+
if (variable && variable.identifiers.length === 0) {
|
96
|
+
const replacement = needsParentheses(node) ? "({})" : "{}";
|
97
|
+
|
98
|
+
context.report({
|
99
|
+
node,
|
100
|
+
messageId: "preferLiteral",
|
101
|
+
suggest: [
|
102
|
+
{
|
103
|
+
messageId: "useLiteral",
|
104
|
+
data: { replacement },
|
105
|
+
fix: fixer => fixer.replaceText(node, replacement)
|
106
|
+
}
|
107
|
+
]
|
108
|
+
});
|
109
|
+
}
|
110
|
+
}
|
111
|
+
|
112
|
+
return {
|
113
|
+
CallExpression: check,
|
114
|
+
NewExpression: check
|
115
|
+
};
|
116
|
+
|
117
|
+
}
|
118
|
+
};
|