Skip to content

Commit

Permalink
Update: Add checkMethods option to no-useless-computed-key
Browse files Browse the repository at this point in the history
  • Loading branch information
ark120202 committed Aug 18, 2019
1 parent 92ec2cb commit 9cfc1a3
Show file tree
Hide file tree
Showing 2 changed files with 211 additions and 36 deletions.
91 changes: 56 additions & 35 deletions lib/rules/no-useless-computed-key.js
Expand Up @@ -8,6 +8,7 @@
// Requirements
//------------------------------------------------------------------------------

const lodash = require("lodash");
const astUtils = require("./utils/ast-utils");

//------------------------------------------------------------------------------
Expand All @@ -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
};
}
};
156 changes: 155 additions & 1 deletion tests/lib/rules/no-useless-computed-key.js
Expand Up @@ -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: [
{
Expand Down Expand Up @@ -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"
}]
}
]
});

0 comments on commit 9cfc1a3

Please sign in to comment.