From 9cfc1a3b72adcc9e88bad059adfb4e887e2b6b95 Mon Sep 17 00:00:00 2001 From: ark120202 Date: Sun, 18 Aug 2019 05:48:00 +0500 Subject: [PATCH] Update: Add checkMethods option to no-useless-computed-key --- lib/rules/no-useless-computed-key.js | 91 +++++++----- tests/lib/rules/no-useless-computed-key.js | 156 ++++++++++++++++++++- 2 files changed, 211 insertions(+), 36 deletions(-) diff --git a/lib/rules/no-useless-computed-key.js b/lib/rules/no-useless-computed-key.js index 95527832b2d7..b44a925ecd81 100644 --- a/lib/rules/no-useless-computed-key.js +++ b/lib/rules/no-useless-computed-key.js @@ -8,6 +8,7 @@ // Requirements //------------------------------------------------------------------------------ +const lodash = require("lodash"); const astUtils = require("./utils/ast-utils"); //------------------------------------------------------------------------------ @@ -27,51 +28,71 @@ module.exports = { url: "https://eslint.org/docs/rules/no-useless-computed-key" }, - schema: [], + schema: [{ + type: "object", + properties: { + checkMethods: { + type: "boolean", + default: false + } + }, + additionalProperties: false + }], fixable: "code" }, create(context) { const sourceCode = context.getSourceCode(); + const checkMethods = context.options[0] && context.options[0].checkMethods; + + /** + * Reports a given node if it violated this rule. + * + * @param {ASTNode} node - The node to check. + * @returns {void} + */ + function check(node) { + if (!node.computed) { + return; + } - return { - Property(node) { - if (!node.computed) { - return; - } - - const key = node.key, - nodeType = typeof key.value; - - if (key.type === "Literal" && (nodeType === "string" || nodeType === "number") && key.value !== "__proto__") { - context.report({ - node, - message: MESSAGE_UNNECESSARY_COMPUTED, - data: { property: sourceCode.getText(key) }, - fix(fixer) { - const leftSquareBracket = sourceCode.getFirstToken(node, astUtils.isOpeningBracketToken); - const rightSquareBracket = sourceCode.getFirstTokenBetween(node.key, node.value, astUtils.isClosingBracketToken); - const tokensBetween = sourceCode.getTokensBetween(leftSquareBracket, rightSquareBracket, 1); - - if (tokensBetween.slice(0, -1).some((token, index) => - sourceCode.getText().slice(token.range[1], tokensBetween[index + 1].range[0]).trim())) { - - // If there are comments between the brackets and the property name, don't do a fix. - return null; - } + const allowedKeys = node.type === "MethodDefinition" ? ["constructor"] : ["__proto__"]; + const key = node.key, + nodeType = typeof key.value; + + if (key.type === "Literal" && (nodeType === "string" || nodeType === "number") && !allowedKeys.includes(key.value)) { + context.report({ + node, + message: MESSAGE_UNNECESSARY_COMPUTED, + data: { property: sourceCode.getText(key) }, + fix(fixer) { + const leftSquareBracket = sourceCode.getFirstToken(node, astUtils.isOpeningBracketToken); + const rightSquareBracket = sourceCode.getFirstTokenBetween(node.key, node.value, astUtils.isClosingBracketToken); + const tokensBetween = sourceCode.getTokensBetween(leftSquareBracket, rightSquareBracket, 1); + + if (tokensBetween.slice(0, -1).some((token, index) => + sourceCode.getText().slice(token.range[1], tokensBetween[index + 1].range[0]).trim())) { + + // If there are comments between the brackets and the property name, don't do a fix. + return null; + } - const tokenBeforeLeftBracket = sourceCode.getTokenBefore(leftSquareBracket); + 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)); + // 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; + const replacementKey = (needsSpaceBeforeKey ? " " : "") + key.raw; - return fixer.replaceTextRange([leftSquareBracket.range[0], rightSquareBracket.range[1]], replacementKey); - } - }); - } + return fixer.replaceTextRange([leftSquareBracket.range[0], rightSquareBracket.range[1]], replacementKey); + } + }); } + } + + return { + Property: check, + MethodDefinition: checkMethods ? check : lodash.noop }; } }; diff --git a/tests/lib/rules/no-useless-computed-key.js b/tests/lib/rules/no-useless-computed-key.js index 1c9969206dd0..7c3a772a7bff 100644 --- a/tests/lib/rules/no-useless-computed-key.js +++ b/tests/lib/rules/no-useless-computed-key.js @@ -23,7 +23,10 @@ ruleTester.run("no-useless-computed-key", rule, { "({ 'a': 0, b(){} })", "({ [x]: 0 });", "({ a: 0, [b](){} })", - "({ ['__proto__']: [] })" + "({ ['__proto__']: [] })", + "class Foo { 'a'() {} }", + "class Foo { [x]() {} }", + "class Foo { ['constructor']() {} }" ], invalid: [ { @@ -168,6 +171,157 @@ ruleTester.run("no-useless-computed-key", rule, { errors: [{ message: "Unnecessarily computed property [2] found.", type: "Property" }] + }, { + code: "class Foo { ['0']() {} }", + output: "class Foo { '0'() {} }", + options: [{ checkMethods: true }], + errors: [{ + message: "Unnecessarily computed property ['0'] found.", type: "MethodDefinition" + }] + }, { + code: "class Foo { ['0+1,234']() {} }", + output: "class Foo { '0+1,234'() {} }", + options: [{ checkMethods: true }], + errors: [{ + message: "Unnecessarily computed property ['0+1,234'] found.", type: "MethodDefinition" + }] + }, { + code: "class Foo { ['x']() {} }", + output: "class Foo { 'x'() {} }", + options: [{ checkMethods: true }], + errors: [{ + message: "Unnecessarily computed property ['x'] found.", type: "MethodDefinition" + }] + }, { + code: "class Foo { [/* this comment prevents a fix */ 'x']() {} }", + output: null, + options: [{ checkMethods: true }], + errors: [{ + message: "Unnecessarily computed property ['x'] found.", type: "MethodDefinition" + }] + }, { + code: "class Foo { ['x' /* this comment also prevents a fix */]() {} }", + output: null, + options: [{ checkMethods: true }], + errors: [{ + message: "Unnecessarily computed property ['x'] found.", type: "MethodDefinition" + }] + }, { + code: "class Foo { [('x')]() {} }", + output: "class Foo { 'x'() {} }", + options: [{ checkMethods: true }], + errors: [{ + message: "Unnecessarily computed property ['x'] found.", type: "MethodDefinition" + }] + }, { + code: "class Foo { *['x']() {} }", + output: "class Foo { *'x'() {} }", + options: [{ checkMethods: true }], + errors: [{ + message: "Unnecessarily computed property ['x'] found.", type: "MethodDefinition" + }] + }, { + code: "class Foo { async ['x']() {} }", + output: "class Foo { async 'x'() {} }", + options: [{ checkMethods: true }], + parserOptions: { ecmaVersion: 8 }, + errors: [{ + message: "Unnecessarily computed property ['x'] found.", type: "MethodDefinition" + }] + }, { + code: "class Foo { get[.2]() {} }", + output: "class Foo { get.2() {} }", + options: [{ checkMethods: true }], + errors: [{ + message: "Unnecessarily computed property [.2] found.", type: "MethodDefinition" + }] + }, { + code: "class Foo { set[.2](value) {} }", + output: "class Foo { set.2(value) {} }", + options: [{ checkMethods: true }], + errors: [{ + message: "Unnecessarily computed property [.2] found.", type: "MethodDefinition" + }] + }, { + code: "class Foo { async[.2]() {} }", + output: "class Foo { async.2() {} }", + options: [{ checkMethods: true }], + parserOptions: { ecmaVersion: 8 }, + errors: [{ + message: "Unnecessarily computed property [.2] found.", type: "MethodDefinition" + }] + }, { + code: "class Foo { [2]() {} }", + output: "class Foo { 2() {} }", + options: [{ checkMethods: true }], + errors: [{ + message: "Unnecessarily computed property [2] found.", type: "MethodDefinition" + }] + }, { + code: "class Foo { get [2]() {} }", + output: "class Foo { get 2() {} }", + options: [{ checkMethods: true }], + errors: [{ + message: "Unnecessarily computed property [2] found.", type: "MethodDefinition" + }] + }, { + code: "class Foo { set [2](value) {} }", + output: "class Foo { set 2(value) {} }", + options: [{ checkMethods: true }], + errors: [{ + message: "Unnecessarily computed property [2] found.", type: "MethodDefinition" + }] + }, { + code: "class Foo { async [2]() {} }", + output: "class Foo { async 2() {} }", + options: [{ checkMethods: true }], + parserOptions: { ecmaVersion: 8 }, + errors: [{ + message: "Unnecessarily computed property [2] found.", type: "MethodDefinition" + }] + }, { + code: "class Foo { get[2]() {} }", + output: "class Foo { get 2() {} }", + options: [{ checkMethods: true }], + errors: [{ + message: "Unnecessarily computed property [2] found.", type: "MethodDefinition" + }] + }, { + code: "class Foo { set[2](value) {} }", + output: "class Foo { set 2(value) {} }", + options: [{ checkMethods: true }], + errors: [{ + message: "Unnecessarily computed property [2] found.", type: "MethodDefinition" + }] + }, { + code: "class Foo { async[2]() {} }", + output: "class Foo { async 2() {} }", + options: [{ checkMethods: true }], + parserOptions: { ecmaVersion: 8 }, + errors: [{ + message: "Unnecessarily computed property [2] found.", type: "MethodDefinition" + }] + }, { + code: "class Foo { get['foo']() {} }", + output: "class Foo { get'foo'() {} }", + options: [{ checkMethods: true }], + errors: [{ + message: "Unnecessarily computed property ['foo'] found.", type: "MethodDefinition" + }] + }, { + code: "class Foo { *[2]() {} }", + output: "class Foo { *2() {} }", + options: [{ checkMethods: true }], + errors: [{ + message: "Unnecessarily computed property [2] found.", type: "MethodDefinition" + }] + }, { + code: "class Foo { async*[2]() {} }", + output: "class Foo { async*2() {} }", + options: [{ checkMethods: true }], + errors: [{ + message: "Unnecessarily computed property [2] found.", type: "MethodDefinition" + }] } ] });