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