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, ":");
 | |
|             }
 | |
|         };
 | |
|     }
 | |
| };
 |