diff options
Diffstat (limited to 'tools/eslint/lib/rules/no-extra-parens.js')
-rw-r--r-- | tools/eslint/lib/rules/no-extra-parens.js | 299 |
1 files changed, 299 insertions, 0 deletions
diff --git a/tools/eslint/lib/rules/no-extra-parens.js b/tools/eslint/lib/rules/no-extra-parens.js new file mode 100644 index 0000000000..2ec8647a68 --- /dev/null +++ b/tools/eslint/lib/rules/no-extra-parens.js @@ -0,0 +1,299 @@ +/** + * @fileoverview Disallow parenthesesisng higher precedence subexpressions. + * @author Michael Ficarra + * @copyright 2014 Michael Ficarra. All rights reserved. + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = function(context) { + + function isParenthesised(node) { + var previousToken = context.getTokenBefore(node), + nextToken = context.getTokenAfter(node); + + return previousToken && nextToken && + previousToken.value === "(" && previousToken.range[1] <= node.range[0] && + nextToken.value === ")" && nextToken.range[0] >= node.range[1]; + } + + function isParenthesisedTwice(node) { + var previousToken = context.getTokenBefore(node, 1), + nextToken = context.getTokenAfter(node, 1); + + return isParenthesised(node) && previousToken && nextToken && + previousToken.value === "(" && previousToken.range[1] <= node.range[0] && + nextToken.value === ")" && nextToken.range[0] >= node.range[1]; + } + + function precedence(node) { + + switch (node.type) { + case "SequenceExpression": + return 0; + + case "AssignmentExpression": + case "ArrowFunctionExpression": + case "YieldExpression": + return 1; + + case "ConditionalExpression": + return 3; + + case "LogicalExpression": + switch (node.operator) { + case "||": + return 4; + case "&&": + return 5; + // no default + } + + /* falls through */ + case "BinaryExpression": + switch (node.operator) { + case "|": + return 6; + case "^": + return 7; + case "&": + return 8; + case "==": + case "!=": + case "===": + case "!==": + return 9; + case "<": + case "<=": + case ">": + case ">=": + case "in": + case "instanceof": + return 10; + case "<<": + case ">>": + case ">>>": + return 11; + case "+": + case "-": + return 12; + case "*": + case "/": + case "%": + return 13; + // no default + } + /* falls through */ + case "UnaryExpression": + return 14; + case "UpdateExpression": + return 15; + case "CallExpression": + // IIFE is allowed to have parens in any position (#655) + if (node.callee.type === "FunctionExpression") { + return -1; + } + return 16; + case "NewExpression": + return 17; + // no default + } + return 18; + } + + function report(node) { + var previousToken = context.getTokenBefore(node); + context.report(node, previousToken.loc.start, "Gratuitous parentheses around expression."); + } + + function dryUnaryUpdate(node) { + if (isParenthesised(node.argument) && precedence(node.argument) >= precedence(node)) { + report(node.argument); + } + } + + function dryCallNew(node) { + if (isParenthesised(node.callee) && precedence(node.callee) >= precedence(node) && + !(node.type === "CallExpression" && node.callee.type === "FunctionExpression")) { + report(node.callee); + } + if (node.arguments.length === 1) { + if (isParenthesisedTwice(node.arguments[0]) && precedence(node.arguments[0]) >= precedence({type: "AssignmentExpression"})) { + report(node.arguments[0]); + } + } else { + [].forEach.call(node.arguments, function(arg) { + if (isParenthesised(arg) && precedence(arg) >= precedence({type: "AssignmentExpression"})) { + report(arg); + } + }); + } + } + + function dryBinaryLogical(node) { + var prec = precedence(node); + if (isParenthesised(node.left) && precedence(node.left) >= prec) { + report(node.left); + } + if (isParenthesised(node.right) && precedence(node.right) > prec) { + report(node.right); + } + } + + return { + "ArrayExpression": function(node) { + [].forEach.call(node.elements, function(e) { + if (e && isParenthesised(e) && precedence(e) >= precedence({type: "AssignmentExpression"})) { + report(e); + } + }); + }, + "ArrowFunctionExpression": function(node) { + if (node.body.type !== "BlockStatement" && isParenthesised(node.body) && precedence(node.body) >= precedence({type: "AssignmentExpression"})) { + report(node.body); + } + }, + "AssignmentExpression": function(node) { + if (isParenthesised(node.right) && precedence(node.right) >= precedence(node)) { + report(node.right); + } + }, + "BinaryExpression": dryBinaryLogical, + "CallExpression": dryCallNew, + "ConditionalExpression": function(node) { + if (isParenthesised(node.test) && precedence(node.test) >= precedence({type: "LogicalExpression", operator: "||"})) { + report(node.test); + } + if (isParenthesised(node.consequent) && precedence(node.consequent) >= precedence({type: "AssignmentExpression"})) { + report(node.consequent); + } + if (isParenthesised(node.alternate) && precedence(node.alternate) >= precedence({type: "AssignmentExpression"})) { + report(node.alternate); + } + }, + "DoWhileStatement": function(node) { + if (isParenthesisedTwice(node.test)) { + report(node.test); + } + }, + "ExpressionStatement": function(node) { + var firstToken; + if (isParenthesised(node.expression)) { + firstToken = context.getFirstToken(node.expression); + if (firstToken.value !== "function" && firstToken.value !== "{") { + report(node.expression); + } + } + }, + "ForInStatement": function(node) { + if (isParenthesised(node.right)) { + report(node.right); + } + }, + "ForOfStatement": function(node) { + if (isParenthesised(node.right)) { + report(node.right); + } + }, + "ForStatement": function(node) { + if (node.init && isParenthesised(node.init)) { + report(node.init); + } + + if (node.test && isParenthesised(node.test)) { + report(node.test); + } + + if (node.update && isParenthesised(node.update)) { + report(node.update); + } + }, + "IfStatement": function(node) { + if (isParenthesisedTwice(node.test)) { + report(node.test); + } + }, + "LogicalExpression": dryBinaryLogical, + "MemberExpression": function(node) { + if ( + isParenthesised(node.object) && + precedence(node.object) >= precedence(node) && + ( + node.computed || + !( + (node.object.type === "Literal" && + typeof node.object.value === "number" && + /^[0-9]+$/.test(context.getFirstToken(node.object).value)) + || + // RegExp literal is allowed to have parens (#1589) + (node.object.type === "Literal" && node.object.regex) + ) + ) + ) { + report(node.object); + } + }, + "NewExpression": dryCallNew, + "ObjectExpression": function(node) { + [].forEach.call(node.properties, function(e) { + var v = e.value; + if (v && isParenthesised(v) && precedence(v) >= precedence({type: "AssignmentExpression"})) { + report(v); + } + }); + }, + "ReturnStatement": function(node) { + if (node.argument && isParenthesised(node.argument) && + // RegExp literal is allowed to have parens (#1589) + !(node.argument.type === "Literal" && node.argument.regex)) { + report(node.argument); + } + }, + "SequenceExpression": function(node) { + [].forEach.call(node.expressions, function(e) { + if (isParenthesised(e) && precedence(e) >= precedence(node)) { + report(e); + } + }); + }, + "SwitchCase": function(node) { + if (node.test && isParenthesised(node.test)) { + report(node.test); + } + }, + "SwitchStatement": function(node) { + if (isParenthesisedTwice(node.discriminant)) { + report(node.discriminant); + } + }, + "ThrowStatement": function(node) { + if (isParenthesised(node.argument)) { + report(node.argument); + } + }, + "UnaryExpression": dryUnaryUpdate, + "UpdateExpression": dryUnaryUpdate, + "VariableDeclarator": function(node) { + if (node.init && isParenthesised(node.init) && + precedence(node.init) >= precedence({type: "AssignmentExpression"}) && + // RegExp literal is allowed to have parens (#1589) + !(node.init.type === "Literal" && node.init.regex)) { + report(node.init); + } + }, + "WhileStatement": function(node) { + if (isParenthesisedTwice(node.test)) { + report(node.test); + } + }, + "WithStatement": function(node) { + if (isParenthesisedTwice(node.object)) { + report(node.object); + } + } + }; + +}; |