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