eslint 8.4.1 → 8.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/README.md +3 -3
- package/bin/eslint.js +7 -1
- package/lib/cli-engine/cli-engine.js +10 -1
- package/lib/eslint/eslint.js +6 -0
- package/lib/linter/apply-disable-directives.js +55 -60
- package/lib/linter/linter.js +90 -27
- package/lib/rule-tester/rule-tester.js +19 -5
- package/lib/rules/camelcase.js +7 -1
- package/lib/rules/id-match.js +35 -1
- package/lib/rules/index.js +1 -0
- package/lib/rules/keyword-spacing.js +32 -0
- package/lib/rules/no-constant-condition.js +23 -4
- package/lib/rules/no-invalid-this.js +50 -53
- package/lib/rules/no-restricted-exports.js +9 -3
- package/lib/rules/no-restricted-imports.js +24 -7
- package/lib/rules/no-restricted-modules.js +2 -1
- package/lib/rules/no-self-assign.js +1 -1
- package/lib/rules/no-useless-rename.js +8 -4
- package/lib/rules/prefer-object-has-own.js +112 -0
- package/lib/rules/prefer-regex-literals.js +217 -1
- package/lib/rules/prefer-template.js +1 -1
- package/lib/rules/quotes.js +12 -1
- package/lib/rules/utils/ast-utils.js +21 -1
- package/lib/shared/types.js +15 -0
- package/messages/no-config-found.js +1 -1
- package/package.json +7 -7
- package/lib/init/autoconfig.js +0 -351
- package/lib/init/config-file.js +0 -144
- package/lib/init/config-initializer.js +0 -709
- package/lib/init/config-rule.js +0 -316
- package/lib/init/npm-utils.js +0 -179
- package/lib/init/source-code-utils.js +0 -110
@@ -216,6 +216,9 @@ function freezeDeeply(x) {
|
|
216
216
|
* @returns {string} The sanitized text.
|
217
217
|
*/
|
218
218
|
function sanitize(text) {
|
219
|
+
if (typeof text !== "string") {
|
220
|
+
return "";
|
221
|
+
}
|
219
222
|
return text.replace(
|
220
223
|
/[\u0000-\u0009\u000b-\u001a]/gu, // eslint-disable-line no-control-regex -- Escaping controls
|
221
224
|
c => `\\u${c.codePointAt(0).toString(16).padStart(4, "0")}`
|
@@ -691,6 +694,13 @@ class RuleTester {
|
|
691
694
|
* @private
|
692
695
|
*/
|
693
696
|
function testValidTemplate(item) {
|
697
|
+
const code = typeof item === "object" ? item.code : item;
|
698
|
+
|
699
|
+
assert.ok(typeof code === "string", "Test case must specify a string value for 'code'");
|
700
|
+
if (item.name) {
|
701
|
+
assert.ok(typeof item.name === "string", "Optional test case property 'name' must be a string");
|
702
|
+
}
|
703
|
+
|
694
704
|
const result = runRuleForItem(item);
|
695
705
|
const messages = result.messages;
|
696
706
|
|
@@ -731,6 +741,10 @@ class RuleTester {
|
|
731
741
|
* @private
|
732
742
|
*/
|
733
743
|
function testInvalidTemplate(item) {
|
744
|
+
assert.ok(typeof item.code === "string", "Test case must specify a string value for 'code'");
|
745
|
+
if (item.name) {
|
746
|
+
assert.ok(typeof item.name === "string", "Optional test case property 'name' must be a string");
|
747
|
+
}
|
734
748
|
assert.ok(item.errors || item.errors === 0,
|
735
749
|
`Did not specify errors for an invalid test of ${ruleName}`);
|
736
750
|
|
@@ -963,10 +977,10 @@ class RuleTester {
|
|
963
977
|
* This creates a mocha test suite and pipes all supplied info through
|
964
978
|
* one of the templates above.
|
965
979
|
*/
|
966
|
-
|
967
|
-
|
980
|
+
this.constructor.describe(ruleName, () => {
|
981
|
+
this.constructor.describe("valid", () => {
|
968
982
|
test.valid.forEach(valid => {
|
969
|
-
|
983
|
+
this.constructor[valid.only ? "itOnly" : "it"](
|
970
984
|
sanitize(typeof valid === "object" ? valid.name || valid.code : valid),
|
971
985
|
() => {
|
972
986
|
testValidTemplate(valid);
|
@@ -975,9 +989,9 @@ class RuleTester {
|
|
975
989
|
});
|
976
990
|
});
|
977
991
|
|
978
|
-
|
992
|
+
this.constructor.describe("invalid", () => {
|
979
993
|
test.invalid.forEach(invalid => {
|
980
|
-
|
994
|
+
this.constructor[invalid.only ? "itOnly" : "it"](
|
981
995
|
sanitize(invalid.name || invalid.code),
|
982
996
|
() => {
|
983
997
|
testInvalidTemplate(invalid);
|
package/lib/rules/camelcase.js
CHANGED
@@ -5,6 +5,12 @@
|
|
5
5
|
|
6
6
|
"use strict";
|
7
7
|
|
8
|
+
//------------------------------------------------------------------------------
|
9
|
+
// Requirements
|
10
|
+
//------------------------------------------------------------------------------
|
11
|
+
|
12
|
+
const astUtils = require("./utils/ast-utils");
|
13
|
+
|
8
14
|
//------------------------------------------------------------------------------
|
9
15
|
// Rule Definition
|
10
16
|
//------------------------------------------------------------------------------
|
@@ -165,7 +171,7 @@ module.exports = {
|
|
165
171
|
case "ImportSpecifier":
|
166
172
|
return (
|
167
173
|
parent.local === node &&
|
168
|
-
parent.imported
|
174
|
+
astUtils.getModuleExportName(parent.imported) === localName
|
169
175
|
);
|
170
176
|
|
171
177
|
default:
|
package/lib/rules/id-match.js
CHANGED
@@ -67,6 +67,8 @@ module.exports = {
|
|
67
67
|
onlyDeclarations = !!options.onlyDeclarations,
|
68
68
|
ignoreDestructuring = !!options.ignoreDestructuring;
|
69
69
|
|
70
|
+
let globalScope;
|
71
|
+
|
70
72
|
//--------------------------------------------------------------------------
|
71
73
|
// Helpers
|
72
74
|
//--------------------------------------------------------------------------
|
@@ -77,6 +79,19 @@ module.exports = {
|
|
77
79
|
const DECLARATION_TYPES = new Set(["FunctionDeclaration", "VariableDeclarator"]);
|
78
80
|
const IMPORT_TYPES = new Set(["ImportSpecifier", "ImportNamespaceSpecifier", "ImportDefaultSpecifier"]);
|
79
81
|
|
82
|
+
/**
|
83
|
+
* Checks whether the given node represents a reference to a global variable that is not declared in the source code.
|
84
|
+
* These identifiers will be allowed, as it is assumed that user has no control over the names of external global variables.
|
85
|
+
* @param {ASTNode} node `Identifier` node to check.
|
86
|
+
* @returns {boolean} `true` if the node is a reference to a global variable.
|
87
|
+
*/
|
88
|
+
function isReferenceToGlobalVariable(node) {
|
89
|
+
const variable = globalScope.set.get(node.name);
|
90
|
+
|
91
|
+
return variable && variable.defs.length === 0 &&
|
92
|
+
variable.references.some(ref => ref.identifier === node);
|
93
|
+
}
|
94
|
+
|
80
95
|
/**
|
81
96
|
* Checks if a string matches the provided pattern
|
82
97
|
* @param {string} name The string to check.
|
@@ -155,11 +170,19 @@ module.exports = {
|
|
155
170
|
|
156
171
|
return {
|
157
172
|
|
173
|
+
Program() {
|
174
|
+
globalScope = context.getScope();
|
175
|
+
},
|
176
|
+
|
158
177
|
Identifier(node) {
|
159
178
|
const name = node.name,
|
160
179
|
parent = node.parent,
|
161
180
|
effectiveParent = (parent.type === "MemberExpression") ? parent.parent : parent;
|
162
181
|
|
182
|
+
if (isReferenceToGlobalVariable(node)) {
|
183
|
+
return;
|
184
|
+
}
|
185
|
+
|
163
186
|
if (parent.type === "MemberExpression") {
|
164
187
|
|
165
188
|
if (!checkProperties) {
|
@@ -188,6 +211,17 @@ module.exports = {
|
|
188
211
|
}
|
189
212
|
}
|
190
213
|
|
214
|
+
// For https://github.com/eslint/eslint/issues/15123
|
215
|
+
} else if (
|
216
|
+
parent.type === "Property" &&
|
217
|
+
parent.parent.type === "ObjectExpression" &&
|
218
|
+
parent.key === node &&
|
219
|
+
!parent.computed
|
220
|
+
) {
|
221
|
+
if (checkProperties && isInvalid(name)) {
|
222
|
+
report(node);
|
223
|
+
}
|
224
|
+
|
191
225
|
/*
|
192
226
|
* Properties have their own rules, and
|
193
227
|
* AssignmentPattern nodes can be treated like Properties:
|
@@ -216,7 +250,7 @@ module.exports = {
|
|
216
250
|
}
|
217
251
|
|
218
252
|
// never check properties or always ignore destructuring
|
219
|
-
if (!checkProperties || (ignoreDestructuring && isInsideObjectPattern(node))) {
|
253
|
+
if ((!checkProperties && !parent.computed) || (ignoreDestructuring && isInsideObjectPattern(node))) {
|
220
254
|
return;
|
221
255
|
}
|
222
256
|
|
package/lib/rules/index.js
CHANGED
@@ -255,6 +255,7 @@ module.exports = new LazyLoadingRuleMap(Object.entries({
|
|
255
255
|
"prefer-exponentiation-operator": () => require("./prefer-exponentiation-operator"),
|
256
256
|
"prefer-named-capture-group": () => require("./prefer-named-capture-group"),
|
257
257
|
"prefer-numeric-literals": () => require("./prefer-numeric-literals"),
|
258
|
+
"prefer-object-has-own": () => require("./prefer-object-has-own"),
|
258
259
|
"prefer-object-spread": () => require("./prefer-object-spread"),
|
259
260
|
"prefer-promise-reject-errors": () => require("./prefer-promise-reject-errors"),
|
260
261
|
"prefer-reflect": () => require("./prefer-reflect"),
|
@@ -469,6 +469,7 @@ module.exports = {
|
|
469
469
|
const asToken = sourceCode.getTokenBefore(node.exported);
|
470
470
|
|
471
471
|
checkSpacingBefore(asToken, PREV_TOKEN_M);
|
472
|
+
checkSpacingAfter(asToken, NEXT_TOKEN_M);
|
472
473
|
}
|
473
474
|
|
474
475
|
if (node.source) {
|
@@ -479,6 +480,35 @@ module.exports = {
|
|
479
480
|
}
|
480
481
|
}
|
481
482
|
|
483
|
+
/**
|
484
|
+
* Reports `as` keyword of a given node if usage of spacing around this
|
485
|
+
* keyword is invalid.
|
486
|
+
* @param {ASTNode} node An `ImportSpecifier` node to check.
|
487
|
+
* @returns {void}
|
488
|
+
*/
|
489
|
+
function checkSpacingForImportSpecifier(node) {
|
490
|
+
if (node.imported.range[0] !== node.local.range[0]) {
|
491
|
+
const asToken = sourceCode.getTokenBefore(node.local);
|
492
|
+
|
493
|
+
checkSpacingBefore(asToken, PREV_TOKEN_M);
|
494
|
+
}
|
495
|
+
}
|
496
|
+
|
497
|
+
/**
|
498
|
+
* Reports `as` keyword of a given node if usage of spacing around this
|
499
|
+
* keyword is invalid.
|
500
|
+
* @param {ASTNode} node An `ExportSpecifier` node to check.
|
501
|
+
* @returns {void}
|
502
|
+
*/
|
503
|
+
function checkSpacingForExportSpecifier(node) {
|
504
|
+
if (node.local.range[0] !== node.exported.range[0]) {
|
505
|
+
const asToken = sourceCode.getTokenBefore(node.exported);
|
506
|
+
|
507
|
+
checkSpacingBefore(asToken, PREV_TOKEN_M);
|
508
|
+
checkSpacingAfter(asToken, NEXT_TOKEN_M);
|
509
|
+
}
|
510
|
+
}
|
511
|
+
|
482
512
|
/**
|
483
513
|
* Reports `as` keyword of a given node if usage of spacing around this
|
484
514
|
* keyword is invalid.
|
@@ -588,6 +618,8 @@ module.exports = {
|
|
588
618
|
YieldExpression: checkSpacingBeforeFirstToken,
|
589
619
|
|
590
620
|
// Others
|
621
|
+
ImportSpecifier: checkSpacingForImportSpecifier,
|
622
|
+
ExportSpecifier: checkSpacingForExportSpecifier,
|
591
623
|
ImportNamespaceSpecifier: checkSpacingForImportNamespaceSpecifier,
|
592
624
|
MethodDefinition: checkSpacingForProperty,
|
593
625
|
PropertyDefinition: checkSpacingForProperty,
|
@@ -124,7 +124,8 @@ module.exports = {
|
|
124
124
|
* Checks if a node has a constant truthiness value.
|
125
125
|
* @param {ASTNode} node The AST node to check.
|
126
126
|
* @param {boolean} inBooleanPosition `false` if checking branch of a condition.
|
127
|
-
* `true` in all other cases
|
127
|
+
* `true` in all other cases. When `false`, checks if -- for both string and
|
128
|
+
* number -- if coerced to that type, the value will be constant.
|
128
129
|
* @returns {Bool} true when node's truthiness is constant
|
129
130
|
* @private
|
130
131
|
*/
|
@@ -138,15 +139,31 @@ module.exports = {
|
|
138
139
|
case "Literal":
|
139
140
|
case "ArrowFunctionExpression":
|
140
141
|
case "FunctionExpression":
|
141
|
-
|
142
|
+
return true;
|
142
143
|
case "ClassExpression":
|
144
|
+
case "ObjectExpression":
|
145
|
+
|
146
|
+
/**
|
147
|
+
* In theory objects like:
|
148
|
+
*
|
149
|
+
* `{toString: () => a}`
|
150
|
+
* `{valueOf: () => a}`
|
151
|
+
*
|
152
|
+
* Or a classes like:
|
153
|
+
*
|
154
|
+
* `class { static toString() { return a } }`
|
155
|
+
* `class { static valueOf() { return a } }`
|
156
|
+
*
|
157
|
+
* Are not constant verifiably when `inBooleanPosition` is
|
158
|
+
* false, but it's an edge case we've opted not to handle.
|
159
|
+
*/
|
143
160
|
return true;
|
144
161
|
case "TemplateLiteral":
|
145
162
|
return (inBooleanPosition && node.quasis.some(quasi => quasi.value.cooked.length)) ||
|
146
|
-
node.expressions.every(exp => isConstant(exp,
|
163
|
+
node.expressions.every(exp => isConstant(exp, false));
|
147
164
|
|
148
165
|
case "ArrayExpression": {
|
149
|
-
if (
|
166
|
+
if (!inBooleanPosition) {
|
150
167
|
return node.elements.every(element => isConstant(element, false));
|
151
168
|
}
|
152
169
|
return true;
|
@@ -196,6 +213,8 @@ module.exports = {
|
|
196
213
|
|
197
214
|
case "SequenceExpression":
|
198
215
|
return isConstant(node.expressions[node.expressions.length - 1], inBooleanPosition);
|
216
|
+
case "SpreadElement":
|
217
|
+
return isConstant(node.argument, inBooleanPosition);
|
199
218
|
|
200
219
|
// no default
|
201
220
|
}
|
@@ -11,6 +11,21 @@
|
|
11
11
|
|
12
12
|
const astUtils = require("./utils/ast-utils");
|
13
13
|
|
14
|
+
//------------------------------------------------------------------------------
|
15
|
+
// Helpers
|
16
|
+
//------------------------------------------------------------------------------
|
17
|
+
|
18
|
+
/**
|
19
|
+
* Determines if the given code path is a code path with lexical `this` binding.
|
20
|
+
* That is, if `this` within the code path refers to `this` of surrounding code path.
|
21
|
+
* @param {CodePath} codePath Code path.
|
22
|
+
* @param {ASTNode} node Node that started the code path.
|
23
|
+
* @returns {boolean} `true` if it is a code path with lexical `this` binding.
|
24
|
+
*/
|
25
|
+
function isCodePathWithLexicalThis(codePath, node) {
|
26
|
+
return codePath.origin === "function" && node.type === "ArrowFunctionExpression";
|
27
|
+
}
|
28
|
+
|
14
29
|
//------------------------------------------------------------------------------
|
15
30
|
// Rule Definition
|
16
31
|
//------------------------------------------------------------------------------
|
@@ -72,71 +87,53 @@ module.exports = {
|
|
72
87
|
return current;
|
73
88
|
};
|
74
89
|
|
75
|
-
|
76
|
-
* Pushs new checking context into the stack.
|
77
|
-
*
|
78
|
-
* The checking context is not initialized yet.
|
79
|
-
* Because most functions don't have `this` keyword.
|
80
|
-
* When `this` keyword was found, the checking context is initialized.
|
81
|
-
* @param {ASTNode} node A function node that was entered.
|
82
|
-
* @returns {void}
|
83
|
-
*/
|
84
|
-
function enterFunction(node) {
|
85
|
-
|
86
|
-
// `this` can be invalid only under strict mode.
|
87
|
-
stack.push({
|
88
|
-
init: !context.getScope().isStrict,
|
89
|
-
node,
|
90
|
-
valid: true
|
91
|
-
});
|
92
|
-
}
|
90
|
+
return {
|
93
91
|
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
function exitFunction() {
|
99
|
-
stack.pop();
|
100
|
-
}
|
92
|
+
onCodePathStart(codePath, node) {
|
93
|
+
if (isCodePathWithLexicalThis(codePath, node)) {
|
94
|
+
return;
|
95
|
+
}
|
101
96
|
|
102
|
-
|
97
|
+
if (codePath.origin === "program") {
|
98
|
+
const scope = context.getScope();
|
99
|
+
const features = context.parserOptions.ecmaFeatures || {};
|
100
|
+
|
101
|
+
stack.push({
|
102
|
+
init: true,
|
103
|
+
node,
|
104
|
+
valid: !(
|
105
|
+
scope.isStrict ||
|
106
|
+
node.sourceType === "module" ||
|
107
|
+
(features.globalReturn && scope.childScopes[0].isStrict)
|
108
|
+
)
|
109
|
+
});
|
103
110
|
|
104
|
-
|
105
|
-
|
106
|
-
* Modules is always strict mode.
|
107
|
-
*/
|
108
|
-
Program(node) {
|
109
|
-
const scope = context.getScope(),
|
110
|
-
features = context.parserOptions.ecmaFeatures || {};
|
111
|
+
return;
|
112
|
+
}
|
111
113
|
|
114
|
+
/*
|
115
|
+
* `init: false` means that `valid` isn't determined yet.
|
116
|
+
* Most functions don't use `this`, and the calculation for `valid`
|
117
|
+
* is relatively costly, so we'll calculate it lazily when the first
|
118
|
+
* `this` within the function is traversed. A special case are non-strict
|
119
|
+
* functions, because `this` refers to the global object and therefore is
|
120
|
+
* always valid, so we can set `init: true` right away.
|
121
|
+
*/
|
112
122
|
stack.push({
|
113
|
-
init:
|
123
|
+
init: !context.getScope().isStrict,
|
114
124
|
node,
|
115
|
-
valid:
|
116
|
-
scope.isStrict ||
|
117
|
-
node.sourceType === "module" ||
|
118
|
-
(features.globalReturn && scope.childScopes[0].isStrict)
|
119
|
-
)
|
125
|
+
valid: true
|
120
126
|
});
|
121
127
|
},
|
122
128
|
|
123
|
-
|
129
|
+
onCodePathEnd(codePath, node) {
|
130
|
+
if (isCodePathWithLexicalThis(codePath, node)) {
|
131
|
+
return;
|
132
|
+
}
|
133
|
+
|
124
134
|
stack.pop();
|
125
135
|
},
|
126
136
|
|
127
|
-
FunctionDeclaration: enterFunction,
|
128
|
-
"FunctionDeclaration:exit": exitFunction,
|
129
|
-
FunctionExpression: enterFunction,
|
130
|
-
"FunctionExpression:exit": exitFunction,
|
131
|
-
|
132
|
-
// Field initializers are implicit functions.
|
133
|
-
"PropertyDefinition > *.value": enterFunction,
|
134
|
-
"PropertyDefinition > *.value:exit": exitFunction,
|
135
|
-
|
136
|
-
// Class static blocks are implicit functions.
|
137
|
-
StaticBlock: enterFunction,
|
138
|
-
"StaticBlock:exit": exitFunction,
|
139
|
-
|
140
137
|
// Reports if `this` of the current context is invalid.
|
141
138
|
ThisExpression(node) {
|
142
139
|
const current = stack.getCurrent();
|
@@ -5,6 +5,12 @@
|
|
5
5
|
|
6
6
|
"use strict";
|
7
7
|
|
8
|
+
//------------------------------------------------------------------------------
|
9
|
+
// Requirements
|
10
|
+
//------------------------------------------------------------------------------
|
11
|
+
|
12
|
+
const astUtils = require("./utils/ast-utils");
|
13
|
+
|
8
14
|
//------------------------------------------------------------------------------
|
9
15
|
// Rule Definition
|
10
16
|
//------------------------------------------------------------------------------
|
@@ -44,12 +50,12 @@ module.exports = {
|
|
44
50
|
const restrictedNames = new Set(context.options[0] && context.options[0].restrictedNamedExports);
|
45
51
|
|
46
52
|
/**
|
47
|
-
* Checks and reports given exported
|
48
|
-
* @param {ASTNode} node exported `Identifier` node to check.
|
53
|
+
* Checks and reports given exported name.
|
54
|
+
* @param {ASTNode} node exported `Identifier` or string `Literal` node to check.
|
49
55
|
* @returns {void}
|
50
56
|
*/
|
51
57
|
function checkExportedName(node) {
|
52
|
-
const name = node
|
58
|
+
const name = astUtils.getModuleExportName(node);
|
53
59
|
|
54
60
|
if (restrictedNames.has(name)) {
|
55
61
|
context.report({
|
@@ -4,6 +4,12 @@
|
|
4
4
|
*/
|
5
5
|
"use strict";
|
6
6
|
|
7
|
+
//------------------------------------------------------------------------------
|
8
|
+
// Requirements
|
9
|
+
//------------------------------------------------------------------------------
|
10
|
+
|
11
|
+
const astUtils = require("./utils/ast-utils");
|
12
|
+
|
7
13
|
//------------------------------------------------------------------------------
|
8
14
|
// Rule Definition
|
9
15
|
//------------------------------------------------------------------------------
|
@@ -63,6 +69,9 @@ const arrayOfStringsOrObjectPatterns = {
|
|
63
69
|
message: {
|
64
70
|
type: "string",
|
65
71
|
minLength: 1
|
72
|
+
},
|
73
|
+
caseSensitive: {
|
74
|
+
type: "boolean"
|
66
75
|
}
|
67
76
|
},
|
68
77
|
additionalProperties: false,
|
@@ -142,10 +151,18 @@ module.exports = {
|
|
142
151
|
}, {});
|
143
152
|
|
144
153
|
// Handle patterns too, either as strings or groups
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
154
|
+
let restrictedPatterns = (isPathAndPatternsObject ? options[0].patterns : []) || [];
|
155
|
+
|
156
|
+
// standardize to array of objects if we have an array of strings
|
157
|
+
if (restrictedPatterns.length > 0 && typeof restrictedPatterns[0] === "string") {
|
158
|
+
restrictedPatterns = [{ group: restrictedPatterns }];
|
159
|
+
}
|
160
|
+
|
161
|
+
// relative paths are supported for this rule
|
162
|
+
const restrictedPatternGroups = restrictedPatterns.map(({ group, message, caseSensitive }) => ({
|
163
|
+
matcher: ignore({ allowRelativePaths: true, ignorecase: !caseSensitive }).add(group),
|
164
|
+
customMessage: message
|
165
|
+
}));
|
149
166
|
|
150
167
|
// if no imports are restricted we don't need to check
|
151
168
|
if (Object.keys(restrictedPaths).length === 0 && restrictedPatternGroups.length === 0) {
|
@@ -269,12 +286,12 @@ module.exports = {
|
|
269
286
|
} else if (specifier.type === "ImportNamespaceSpecifier") {
|
270
287
|
name = "*";
|
271
288
|
} else if (specifier.imported) {
|
272
|
-
name = specifier.imported
|
289
|
+
name = astUtils.getModuleExportName(specifier.imported);
|
273
290
|
} else if (specifier.local) {
|
274
|
-
name = specifier.local
|
291
|
+
name = astUtils.getModuleExportName(specifier.local);
|
275
292
|
}
|
276
293
|
|
277
|
-
if (name) {
|
294
|
+
if (typeof name === "string") {
|
278
295
|
if (importNames.has(name)) {
|
279
296
|
importNames.get(name).push(specifierData);
|
280
297
|
} else {
|
@@ -132,8 +132,10 @@ module.exports = {
|
|
132
132
|
return;
|
133
133
|
}
|
134
134
|
|
135
|
-
if (
|
136
|
-
|
135
|
+
if (
|
136
|
+
node.imported.range[0] !== node.local.range[0] &&
|
137
|
+
astUtils.getModuleExportName(node.imported) === node.local.name
|
138
|
+
) {
|
137
139
|
reportError(node, node.imported, "Import");
|
138
140
|
}
|
139
141
|
}
|
@@ -148,8 +150,10 @@ module.exports = {
|
|
148
150
|
return;
|
149
151
|
}
|
150
152
|
|
151
|
-
if (
|
152
|
-
|
153
|
+
if (
|
154
|
+
node.local.range[0] !== node.exported.range[0] &&
|
155
|
+
astUtils.getModuleExportName(node.local) === astUtils.getModuleExportName(node.exported)
|
156
|
+
) {
|
153
157
|
reportError(node, node.local, "Export");
|
154
158
|
}
|
155
159
|
|
@@ -0,0 +1,112 @@
|
|
1
|
+
/**
|
2
|
+
* @fileoverview Prefers Object.hasOwn() instead of Object.prototype.hasOwnProperty.call()
|
3
|
+
* @author Nitin Kumar
|
4
|
+
* @author Gautam Arora
|
5
|
+
*/
|
6
|
+
|
7
|
+
"use strict";
|
8
|
+
|
9
|
+
//------------------------------------------------------------------------------
|
10
|
+
// Requirements
|
11
|
+
//------------------------------------------------------------------------------
|
12
|
+
|
13
|
+
const astUtils = require("./utils/ast-utils");
|
14
|
+
|
15
|
+
//------------------------------------------------------------------------------
|
16
|
+
// Helpers
|
17
|
+
//------------------------------------------------------------------------------
|
18
|
+
|
19
|
+
/**
|
20
|
+
* Checks if the given node is considered to be an access to a property of `Object.prototype`.
|
21
|
+
* @param {ASTNode} node `MemberExpression` node to evaluate.
|
22
|
+
* @returns {boolean} `true` if `node.object` is `Object`, `Object.prototype`, or `{}` (empty 'ObjectExpression' node).
|
23
|
+
*/
|
24
|
+
function hasLeftHandObject(node) {
|
25
|
+
|
26
|
+
/*
|
27
|
+
* ({}).hasOwnProperty.call(obj, prop) - `true`
|
28
|
+
* ({ foo }.hasOwnProperty.call(obj, prop)) - `false`, object literal should be empty
|
29
|
+
*/
|
30
|
+
if (node.object.type === "ObjectExpression" && node.object.properties.length === 0) {
|
31
|
+
return true;
|
32
|
+
}
|
33
|
+
|
34
|
+
const objectNodeToCheck = node.object.type === "MemberExpression" && astUtils.getStaticPropertyName(node.object) === "prototype" ? node.object.object : node.object;
|
35
|
+
|
36
|
+
if (objectNodeToCheck.type === "Identifier" && objectNodeToCheck.name === "Object") {
|
37
|
+
return true;
|
38
|
+
}
|
39
|
+
|
40
|
+
return false;
|
41
|
+
}
|
42
|
+
|
43
|
+
//------------------------------------------------------------------------------
|
44
|
+
// Rule Definition
|
45
|
+
//------------------------------------------------------------------------------
|
46
|
+
|
47
|
+
/** @type {import('../shared/types').Rule} */
|
48
|
+
module.exports = {
|
49
|
+
meta: {
|
50
|
+
type: "suggestion",
|
51
|
+
docs: {
|
52
|
+
description:
|
53
|
+
"disallow use of `Object.prototype.hasOwnProperty.call()` and prefer use of `Object.hasOwn()`",
|
54
|
+
recommended: false,
|
55
|
+
url: "https://eslint.org/docs/rules/prefer-object-has-own"
|
56
|
+
},
|
57
|
+
schema: [],
|
58
|
+
messages: {
|
59
|
+
useHasOwn: "Use 'Object.hasOwn()' instead of 'Object.prototype.hasOwnProperty.call()'."
|
60
|
+
},
|
61
|
+
fixable: "code"
|
62
|
+
},
|
63
|
+
create(context) {
|
64
|
+
return {
|
65
|
+
CallExpression(node) {
|
66
|
+
if (!(node.callee.type === "MemberExpression" && node.callee.object.type === "MemberExpression")) {
|
67
|
+
return;
|
68
|
+
}
|
69
|
+
|
70
|
+
const calleePropertyName = astUtils.getStaticPropertyName(node.callee);
|
71
|
+
const objectPropertyName = astUtils.getStaticPropertyName(node.callee.object);
|
72
|
+
const isObject = hasLeftHandObject(node.callee.object);
|
73
|
+
|
74
|
+
// check `Object` scope
|
75
|
+
const scope = context.getScope();
|
76
|
+
const variable = astUtils.getVariableByName(scope, "Object");
|
77
|
+
|
78
|
+
if (
|
79
|
+
calleePropertyName === "call" &&
|
80
|
+
objectPropertyName === "hasOwnProperty" &&
|
81
|
+
isObject &&
|
82
|
+
variable && variable.scope.type === "global"
|
83
|
+
) {
|
84
|
+
context.report({
|
85
|
+
node,
|
86
|
+
messageId: "useHasOwn",
|
87
|
+
fix(fixer) {
|
88
|
+
const sourceCode = context.getSourceCode();
|
89
|
+
|
90
|
+
if (sourceCode.getCommentsInside(node.callee).length > 0) {
|
91
|
+
return null;
|
92
|
+
}
|
93
|
+
|
94
|
+
const tokenJustBeforeNode = sourceCode.getTokenBefore(node.callee, { includeComments: true });
|
95
|
+
|
96
|
+
// for https://github.com/eslint/eslint/pull/15346#issuecomment-991417335
|
97
|
+
if (
|
98
|
+
tokenJustBeforeNode &&
|
99
|
+
tokenJustBeforeNode.range[1] === node.callee.range[0] &&
|
100
|
+
!astUtils.canTokensBeAdjacent(tokenJustBeforeNode, "Object.hasOwn")
|
101
|
+
) {
|
102
|
+
return fixer.replaceText(node.callee, " Object.hasOwn");
|
103
|
+
}
|
104
|
+
|
105
|
+
return fixer.replaceText(node.callee, "Object.hasOwn");
|
106
|
+
}
|
107
|
+
});
|
108
|
+
}
|
109
|
+
}
|
110
|
+
};
|
111
|
+
}
|
112
|
+
};
|