| /** | |
|  * @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`. | |
|  * @throws {Error} (Unreachable.) | |
|  * @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"; | |
| 
 | |
|         /* c8 ignore next */ | |
|         default: | |
|             throw new Error(`Unexpected node type: ${node.type}`); | |
|     } | |
| 
 | |
| } | |
| 
 | |
| //------------------------------------------------------------------------------ | |
| // Rule Definition | |
| //------------------------------------------------------------------------------ | |
|  | |
| /** @type {import('../shared/types').Rule} */ | |
| module.exports = { | |
|     meta: { | |
|         type: "suggestion", | |
| 
 | |
|         docs: { | |
|             description: "Disallow unnecessary computed property keys in objects and classes", | |
|             recommended: false, | |
|             url: "https://eslint.org/docs/latest/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.sourceCode; | |
|         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 | |
|         }; | |
|     } | |
| };
 |