/
no-useless-computed-key.js
167 lines (138 loc) · 6.26 KB
/
no-useless-computed-key.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
/**
* @fileoverview Rule to disallow unnecessary computed property keys in object literals
* @author Burak Yigit Kaya
*/
"use strict";
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const astUtils = require("./utils/ast-utils");
//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------
/**
* Determines whether the computed key syntax is unnecessarily used for the given node.
* In particular, it determines whether removing the square brackets and using the content between them
* directly as the key (e.g. ['foo'] -> 'foo') would produce valid syntax and preserve the same behavior.
* Valid non-computed keys are only: identifiers, number literals and string literals.
* Only literals can preserve the same behavior, with a few exceptions for specific node types:
* Property
* - { ["__proto__"]: foo } defines a property named "__proto__"
* { "__proto__": foo } defines object's prototype
* PropertyDefinition
* - class C { ["constructor"]; } defines an instance field named "constructor"
* class C { "constructor"; } produces a parsing error
* - class C { static ["constructor"]; } defines a static field named "constructor"
* class C { static "constructor"; } produces a parsing error
* - class C { static ["prototype"]; } produces a runtime error (doesn't break the whole script)
* class C { static "prototype"; } produces a parsing error (breaks the whole script)
* MethodDefinition
* - class C { ["constructor"]() {} } defines a prototype method named "constructor"
* class C { "constructor"() {} } defines the constructor
* - class C { static ["prototype"]() {} } produces a runtime error (doesn't break the whole script)
* class C { static "prototype"() {} } produces a parsing error (breaks the whole script)
* @param {ASTNode} node The node to check. It can be `Property`, `PropertyDefinition` or `MethodDefinition`.
* @returns {void} `true` if the node has useless computed key.
*/
function hasUselessComputedKey(node) {
if (!node.computed) {
return false;
}
const { key } = node;
if (key.type !== "Literal") {
return false;
}
const { value } = key;
if (typeof value !== "number" && typeof value !== "string") {
return false;
}
switch (node.type) {
case "Property":
return value !== "__proto__";
case "PropertyDefinition":
if (node.static) {
return value !== "constructor" && value !== "prototype";
}
return value !== "constructor";
case "MethodDefinition":
if (node.static) {
return value !== "prototype";
}
return value !== "constructor";
/* istanbul ignore next */
default:
throw new Error(`Unexpected node type: ${node.type}`);
}
}
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
module.exports = {
meta: {
type: "suggestion",
docs: {
description: "disallow unnecessary computed property keys in objects and classes",
category: "ECMAScript 6",
recommended: false,
url: "https://eslint.org/docs/rules/no-useless-computed-key"
},
schema: [{
type: "object",
properties: {
enforceForClassMembers: {
type: "boolean",
default: false
}
},
additionalProperties: false
}],
fixable: "code",
messages: {
unnecessarilyComputedProperty: "Unnecessarily computed property [{{property}}] found."
}
},
create(context) {
const sourceCode = context.getSourceCode();
const enforceForClassMembers = context.options[0] && context.options[0].enforceForClassMembers;
/**
* Reports a given node if it violated this rule.
* @param {ASTNode} node The node to check.
* @returns {void}
*/
function check(node) {
if (hasUselessComputedKey(node)) {
const { key } = node;
context.report({
node,
messageId: "unnecessarilyComputedProperty",
data: { property: sourceCode.getText(key) },
fix(fixer) {
const leftSquareBracket = sourceCode.getTokenBefore(key, astUtils.isOpeningBracketToken);
const rightSquareBracket = sourceCode.getTokenAfter(key, astUtils.isClosingBracketToken);
// If there are comments between the brackets and the property name, don't do a fix.
if (sourceCode.commentsExistBetween(leftSquareBracket, rightSquareBracket)) {
return null;
}
const tokenBeforeLeftBracket = sourceCode.getTokenBefore(leftSquareBracket);
// Insert a space before the key to avoid changing identifiers, e.g. ({ get[2]() {} }) to ({ get2() {} })
const needsSpaceBeforeKey = tokenBeforeLeftBracket.range[1] === leftSquareBracket.range[0] &&
!astUtils.canTokensBeAdjacent(tokenBeforeLeftBracket, sourceCode.getFirstToken(key));
const replacementKey = (needsSpaceBeforeKey ? " " : "") + key.raw;
return fixer.replaceTextRange([leftSquareBracket.range[0], rightSquareBracket.range[1]], replacementKey);
}
});
}
}
/**
* A no-op function to act as placeholder for checking a node when the `enforceForClassMembers` option is `false`.
* @returns {void}
* @private
*/
function noop() {}
return {
Property: check,
MethodDefinition: enforceForClassMembers ? check : noop,
PropertyDefinition: enforceForClassMembers ? check : noop
};
}
};