254 lines
		
	
	
		
			9.9 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			254 lines
		
	
	
		
			9.9 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
/**
 | 
						|
 * @fileoverview Operator linebreak - enforces operator linebreak style of two types: after and before
 | 
						|
 * @author Benoît Zugmeyer
 | 
						|
 * @deprecated in ESLint v8.53.0
 | 
						|
 */
 | 
						|
 | 
						|
"use strict";
 | 
						|
 | 
						|
//------------------------------------------------------------------------------
 | 
						|
// Requirements
 | 
						|
//------------------------------------------------------------------------------
 | 
						|
 | 
						|
const astUtils = require("./utils/ast-utils");
 | 
						|
 | 
						|
//------------------------------------------------------------------------------
 | 
						|
// Rule Definition
 | 
						|
//------------------------------------------------------------------------------
 | 
						|
 | 
						|
/** @type {import('../shared/types').Rule} */
 | 
						|
module.exports = {
 | 
						|
    meta: {
 | 
						|
        deprecated: true,
 | 
						|
        replacedBy: [],
 | 
						|
        type: "layout",
 | 
						|
 | 
						|
        docs: {
 | 
						|
            description: "Enforce consistent linebreak style for operators",
 | 
						|
            recommended: false,
 | 
						|
            url: "https://eslint.org/docs/latest/rules/operator-linebreak"
 | 
						|
        },
 | 
						|
 | 
						|
        schema: [
 | 
						|
            {
 | 
						|
                enum: ["after", "before", "none", null]
 | 
						|
            },
 | 
						|
            {
 | 
						|
                type: "object",
 | 
						|
                properties: {
 | 
						|
                    overrides: {
 | 
						|
                        type: "object",
 | 
						|
                        additionalProperties: {
 | 
						|
                            enum: ["after", "before", "none", "ignore"]
 | 
						|
                        }
 | 
						|
                    }
 | 
						|
                },
 | 
						|
                additionalProperties: false
 | 
						|
            }
 | 
						|
        ],
 | 
						|
 | 
						|
        fixable: "code",
 | 
						|
 | 
						|
        messages: {
 | 
						|
            operatorAtBeginning: "'{{operator}}' should be placed at the beginning of the line.",
 | 
						|
            operatorAtEnd: "'{{operator}}' should be placed at the end of the line.",
 | 
						|
            badLinebreak: "Bad line breaking before and after '{{operator}}'.",
 | 
						|
            noLinebreak: "There should be no line break before or after '{{operator}}'."
 | 
						|
        }
 | 
						|
    },
 | 
						|
 | 
						|
    create(context) {
 | 
						|
 | 
						|
        const usedDefaultGlobal = !context.options[0];
 | 
						|
        const globalStyle = context.options[0] || "after";
 | 
						|
        const options = context.options[1] || {};
 | 
						|
        const styleOverrides = options.overrides ? Object.assign({}, options.overrides) : {};
 | 
						|
 | 
						|
        if (usedDefaultGlobal && !styleOverrides["?"]) {
 | 
						|
            styleOverrides["?"] = "before";
 | 
						|
        }
 | 
						|
 | 
						|
        if (usedDefaultGlobal && !styleOverrides[":"]) {
 | 
						|
            styleOverrides[":"] = "before";
 | 
						|
        }
 | 
						|
 | 
						|
        const sourceCode = context.sourceCode;
 | 
						|
 | 
						|
        //--------------------------------------------------------------------------
 | 
						|
        // Helpers
 | 
						|
        //--------------------------------------------------------------------------
 | 
						|
 | 
						|
        /**
 | 
						|
         * Gets a fixer function to fix rule issues
 | 
						|
         * @param {Token} operatorToken The operator token of an expression
 | 
						|
         * @param {string} desiredStyle The style for the rule. One of 'before', 'after', 'none'
 | 
						|
         * @returns {Function} A fixer function
 | 
						|
         */
 | 
						|
        function getFixer(operatorToken, desiredStyle) {
 | 
						|
            return fixer => {
 | 
						|
                const tokenBefore = sourceCode.getTokenBefore(operatorToken);
 | 
						|
                const tokenAfter = sourceCode.getTokenAfter(operatorToken);
 | 
						|
                const textBefore = sourceCode.text.slice(tokenBefore.range[1], operatorToken.range[0]);
 | 
						|
                const textAfter = sourceCode.text.slice(operatorToken.range[1], tokenAfter.range[0]);
 | 
						|
                const hasLinebreakBefore = !astUtils.isTokenOnSameLine(tokenBefore, operatorToken);
 | 
						|
                const hasLinebreakAfter = !astUtils.isTokenOnSameLine(operatorToken, tokenAfter);
 | 
						|
                let newTextBefore, newTextAfter;
 | 
						|
 | 
						|
                if (hasLinebreakBefore !== hasLinebreakAfter && desiredStyle !== "none") {
 | 
						|
 | 
						|
                    // If there is a comment before and after the operator, don't do a fix.
 | 
						|
                    if (sourceCode.getTokenBefore(operatorToken, { includeComments: true }) !== tokenBefore &&
 | 
						|
                        sourceCode.getTokenAfter(operatorToken, { includeComments: true }) !== tokenAfter) {
 | 
						|
 | 
						|
                        return null;
 | 
						|
                    }
 | 
						|
 | 
						|
                    /*
 | 
						|
                     * If there is only one linebreak and it's on the wrong side of the operator, swap the text before and after the operator.
 | 
						|
                     * foo &&
 | 
						|
                     *           bar
 | 
						|
                     * would get fixed to
 | 
						|
                     * foo
 | 
						|
                     *        && bar
 | 
						|
                     */
 | 
						|
                    newTextBefore = textAfter;
 | 
						|
                    newTextAfter = textBefore;
 | 
						|
                } else {
 | 
						|
                    const LINEBREAK_REGEX = astUtils.createGlobalLinebreakMatcher();
 | 
						|
 | 
						|
                    // Otherwise, if no linebreak is desired and no comments interfere, replace the linebreaks with empty strings.
 | 
						|
                    newTextBefore = desiredStyle === "before" || textBefore.trim() ? textBefore : textBefore.replace(LINEBREAK_REGEX, "");
 | 
						|
                    newTextAfter = desiredStyle === "after" || textAfter.trim() ? textAfter : textAfter.replace(LINEBREAK_REGEX, "");
 | 
						|
 | 
						|
                    // If there was no change (due to interfering comments), don't output a fix.
 | 
						|
                    if (newTextBefore === textBefore && newTextAfter === textAfter) {
 | 
						|
                        return null;
 | 
						|
                    }
 | 
						|
                }
 | 
						|
 | 
						|
                if (newTextAfter === "" && tokenAfter.type === "Punctuator" && "+-".includes(operatorToken.value) && tokenAfter.value === operatorToken.value) {
 | 
						|
 | 
						|
                    // To avoid accidentally creating a ++ or -- operator, insert a space if the operator is a +/- and the following token is a unary +/-.
 | 
						|
                    newTextAfter += " ";
 | 
						|
                }
 | 
						|
 | 
						|
                return fixer.replaceTextRange([tokenBefore.range[1], tokenAfter.range[0]], newTextBefore + operatorToken.value + newTextAfter);
 | 
						|
            };
 | 
						|
        }
 | 
						|
 | 
						|
        /**
 | 
						|
         * Checks the operator placement
 | 
						|
         * @param {ASTNode} node The node to check
 | 
						|
         * @param {ASTNode} rightSide The node that comes after the operator in `node`
 | 
						|
         * @param {string} operator The operator
 | 
						|
         * @private
 | 
						|
         * @returns {void}
 | 
						|
         */
 | 
						|
        function validateNode(node, rightSide, operator) {
 | 
						|
 | 
						|
            /*
 | 
						|
             * Find the operator token by searching from the right side, because between the left side and the operator
 | 
						|
             * there could be additional tokens from type annotations. Search specifically for the token which
 | 
						|
             * value equals the operator, in order to skip possible opening parentheses before the right side node.
 | 
						|
             */
 | 
						|
            const operatorToken = sourceCode.getTokenBefore(rightSide, token => token.value === operator);
 | 
						|
            const leftToken = sourceCode.getTokenBefore(operatorToken);
 | 
						|
            const rightToken = sourceCode.getTokenAfter(operatorToken);
 | 
						|
            const operatorStyleOverride = styleOverrides[operator];
 | 
						|
            const style = operatorStyleOverride || globalStyle;
 | 
						|
            const fix = getFixer(operatorToken, style);
 | 
						|
 | 
						|
            // if single line
 | 
						|
            if (astUtils.isTokenOnSameLine(leftToken, operatorToken) &&
 | 
						|
                    astUtils.isTokenOnSameLine(operatorToken, rightToken)) {
 | 
						|
 | 
						|
                // do nothing.
 | 
						|
 | 
						|
            } else if (operatorStyleOverride !== "ignore" && !astUtils.isTokenOnSameLine(leftToken, operatorToken) &&
 | 
						|
                    !astUtils.isTokenOnSameLine(operatorToken, rightToken)) {
 | 
						|
 | 
						|
                // lone operator
 | 
						|
                context.report({
 | 
						|
                    node,
 | 
						|
                    loc: operatorToken.loc,
 | 
						|
                    messageId: "badLinebreak",
 | 
						|
                    data: {
 | 
						|
                        operator
 | 
						|
                    },
 | 
						|
                    fix
 | 
						|
                });
 | 
						|
 | 
						|
            } else if (style === "before" && astUtils.isTokenOnSameLine(leftToken, operatorToken)) {
 | 
						|
 | 
						|
                context.report({
 | 
						|
                    node,
 | 
						|
                    loc: operatorToken.loc,
 | 
						|
                    messageId: "operatorAtBeginning",
 | 
						|
                    data: {
 | 
						|
                        operator
 | 
						|
                    },
 | 
						|
                    fix
 | 
						|
                });
 | 
						|
 | 
						|
            } else if (style === "after" && astUtils.isTokenOnSameLine(operatorToken, rightToken)) {
 | 
						|
 | 
						|
                context.report({
 | 
						|
                    node,
 | 
						|
                    loc: operatorToken.loc,
 | 
						|
                    messageId: "operatorAtEnd",
 | 
						|
                    data: {
 | 
						|
                        operator
 | 
						|
                    },
 | 
						|
                    fix
 | 
						|
                });
 | 
						|
 | 
						|
            } else if (style === "none") {
 | 
						|
 | 
						|
                context.report({
 | 
						|
                    node,
 | 
						|
                    loc: operatorToken.loc,
 | 
						|
                    messageId: "noLinebreak",
 | 
						|
                    data: {
 | 
						|
                        operator
 | 
						|
                    },
 | 
						|
                    fix
 | 
						|
                });
 | 
						|
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        /**
 | 
						|
         * Validates a binary expression using `validateNode`
 | 
						|
         * @param {BinaryExpression|LogicalExpression|AssignmentExpression} node node to be validated
 | 
						|
         * @returns {void}
 | 
						|
         */
 | 
						|
        function validateBinaryExpression(node) {
 | 
						|
            validateNode(node, node.right, node.operator);
 | 
						|
        }
 | 
						|
 | 
						|
        //--------------------------------------------------------------------------
 | 
						|
        // Public
 | 
						|
        //--------------------------------------------------------------------------
 | 
						|
 | 
						|
        return {
 | 
						|
            BinaryExpression: validateBinaryExpression,
 | 
						|
            LogicalExpression: validateBinaryExpression,
 | 
						|
            AssignmentExpression: validateBinaryExpression,
 | 
						|
            VariableDeclarator(node) {
 | 
						|
                if (node.init) {
 | 
						|
                    validateNode(node, node.init, "=");
 | 
						|
                }
 | 
						|
            },
 | 
						|
            PropertyDefinition(node) {
 | 
						|
                if (node.value) {
 | 
						|
                    validateNode(node, node.value, "=");
 | 
						|
                }
 | 
						|
            },
 | 
						|
            ConditionalExpression(node) {
 | 
						|
                validateNode(node, node.consequent, "?");
 | 
						|
                validateNode(node, node.alternate, ":");
 | 
						|
            }
 | 
						|
        };
 | 
						|
    }
 | 
						|
};
 |