/** * @fileoverview Operator linebreak - enforces operator linebreak style of two types: after and before * @author Benoît Zugmeyer * @copyright 2015 Benoît Zugmeyer. All rights reserved. */ "use strict"; var assign = require("object-assign"), astUtils = require("../ast-utils"); //------------------------------------------------------------------------------ // Rule Definition //------------------------------------------------------------------------------ module.exports = function(context) { var usedDefaultGlobal = !context.options[0]; var globalStyle = context.options[0] || "after"; var options = context.options[1] || {}; var styleOverrides = options.overrides ? assign({}, options.overrides) : {}; if (usedDefaultGlobal && !styleOverrides["?"]) { styleOverrides["?"] = "before"; } if (usedDefaultGlobal && !styleOverrides[":"]) { styleOverrides[":"] = "before"; } //-------------------------------------------------------------------------- // Helpers //-------------------------------------------------------------------------- /** * Checks the operator placement * @param {ASTNode} node The node to check * @param {ASTNode} leftSide The node that comes before the operator in `node` * @private * @returns {void} */ function validateNode(node, leftSide) { var leftToken = context.getLastToken(leftSide); var operatorToken = context.getTokenAfter(leftToken); // When the left part of a binary expression is a single expression wrapped in // parentheses (ex: `(a) + b`), leftToken will be the last token of the expression // and operatorToken will be the closing parenthesis. // The leftToken should be the last closing parenthesis, and the operatorToken // should be the token right after that. while (operatorToken.value === ")") { leftToken = operatorToken; operatorToken = context.getTokenAfter(operatorToken); } var rightToken = context.getTokenAfter(operatorToken); var operator = operatorToken.value; var style = styleOverrides[operator] || globalStyle; // if single line if (astUtils.isTokenOnSameLine(leftToken, operatorToken) && astUtils.isTokenOnSameLine(operatorToken, rightToken)) { return; } else if (!astUtils.isTokenOnSameLine(leftToken, operatorToken) && !astUtils.isTokenOnSameLine(operatorToken, rightToken)) { // lone operator context.report(node, { line: operatorToken.loc.end.line, column: operatorToken.loc.end.column }, "Bad line breaking before and after '" + operator + "'."); } else if (style === "before" && astUtils.isTokenOnSameLine(leftToken, operatorToken)) { context.report(node, { line: operatorToken.loc.end.line, column: operatorToken.loc.end.column }, "'" + operator + "' should be placed at the beginning of the line."); } else if (style === "after" && astUtils.isTokenOnSameLine(operatorToken, rightToken)) { context.report(node, { line: operatorToken.loc.end.line, column: operatorToken.loc.end.column }, "'" + operator + "' should be placed at the end of the line."); } else if (style === "none") { context.report(node, { line: operatorToken.loc.end.line, column: operatorToken.loc.end.column }, "There should be no line break before or after '" + operator + "'"); } } /** * Validates a binary expression using `validateNode` * @param {BinaryExpression|LogicalExpression|AssignmentExpression} node node to be validated * @returns {void} */ function validateBinaryExpression(node) { validateNode(node, node.left); } //-------------------------------------------------------------------------- // Public //-------------------------------------------------------------------------- return { "BinaryExpression": validateBinaryExpression, "LogicalExpression": validateBinaryExpression, "AssignmentExpression": validateBinaryExpression, "VariableDeclarator": function(node) { if (node.init) { validateNode(node, node.id); } }, "ConditionalExpression": function(node) { validateNode(node, node.test); validateNode(node, node.consequent); } }; }; module.exports.schema = [ { "enum": ["after", "before", "none", null] }, { "type": "object", "properties": { "overrides": { "type": "object", "properties": { "anyOf": { "type": "string", "enum": ["after", "before", "none"] } } } }, "additionalProperties": false } ];