[d565449] | 1 | /**
|
---|
| 2 | * @fileoverview Operator linebreak - enforces operator linebreak style of two types: after and before
|
---|
| 3 | * @author Benoît Zugmeyer
|
---|
| 4 | * @deprecated in ESLint v8.53.0
|
---|
| 5 | */
|
---|
| 6 |
|
---|
| 7 | "use strict";
|
---|
| 8 |
|
---|
| 9 | //------------------------------------------------------------------------------
|
---|
| 10 | // Requirements
|
---|
| 11 | //------------------------------------------------------------------------------
|
---|
| 12 |
|
---|
| 13 | const astUtils = require("./utils/ast-utils");
|
---|
| 14 |
|
---|
| 15 | //------------------------------------------------------------------------------
|
---|
| 16 | // Rule Definition
|
---|
| 17 | //------------------------------------------------------------------------------
|
---|
| 18 |
|
---|
| 19 | /** @type {import('../shared/types').Rule} */
|
---|
| 20 | module.exports = {
|
---|
| 21 | meta: {
|
---|
| 22 | deprecated: true,
|
---|
| 23 | replacedBy: [],
|
---|
| 24 | type: "layout",
|
---|
| 25 |
|
---|
| 26 | docs: {
|
---|
| 27 | description: "Enforce consistent linebreak style for operators",
|
---|
| 28 | recommended: false,
|
---|
| 29 | url: "https://eslint.org/docs/latest/rules/operator-linebreak"
|
---|
| 30 | },
|
---|
| 31 |
|
---|
| 32 | schema: [
|
---|
| 33 | {
|
---|
| 34 | enum: ["after", "before", "none", null]
|
---|
| 35 | },
|
---|
| 36 | {
|
---|
| 37 | type: "object",
|
---|
| 38 | properties: {
|
---|
| 39 | overrides: {
|
---|
| 40 | type: "object",
|
---|
| 41 | additionalProperties: {
|
---|
| 42 | enum: ["after", "before", "none", "ignore"]
|
---|
| 43 | }
|
---|
| 44 | }
|
---|
| 45 | },
|
---|
| 46 | additionalProperties: false
|
---|
| 47 | }
|
---|
| 48 | ],
|
---|
| 49 |
|
---|
| 50 | fixable: "code",
|
---|
| 51 |
|
---|
| 52 | messages: {
|
---|
| 53 | operatorAtBeginning: "'{{operator}}' should be placed at the beginning of the line.",
|
---|
| 54 | operatorAtEnd: "'{{operator}}' should be placed at the end of the line.",
|
---|
| 55 | badLinebreak: "Bad line breaking before and after '{{operator}}'.",
|
---|
| 56 | noLinebreak: "There should be no line break before or after '{{operator}}'."
|
---|
| 57 | }
|
---|
| 58 | },
|
---|
| 59 |
|
---|
| 60 | create(context) {
|
---|
| 61 |
|
---|
| 62 | const usedDefaultGlobal = !context.options[0];
|
---|
| 63 | const globalStyle = context.options[0] || "after";
|
---|
| 64 | const options = context.options[1] || {};
|
---|
| 65 | const styleOverrides = options.overrides ? Object.assign({}, options.overrides) : {};
|
---|
| 66 |
|
---|
| 67 | if (usedDefaultGlobal && !styleOverrides["?"]) {
|
---|
| 68 | styleOverrides["?"] = "before";
|
---|
| 69 | }
|
---|
| 70 |
|
---|
| 71 | if (usedDefaultGlobal && !styleOverrides[":"]) {
|
---|
| 72 | styleOverrides[":"] = "before";
|
---|
| 73 | }
|
---|
| 74 |
|
---|
| 75 | const sourceCode = context.sourceCode;
|
---|
| 76 |
|
---|
| 77 | //--------------------------------------------------------------------------
|
---|
| 78 | // Helpers
|
---|
| 79 | //--------------------------------------------------------------------------
|
---|
| 80 |
|
---|
| 81 | /**
|
---|
| 82 | * Gets a fixer function to fix rule issues
|
---|
| 83 | * @param {Token} operatorToken The operator token of an expression
|
---|
| 84 | * @param {string} desiredStyle The style for the rule. One of 'before', 'after', 'none'
|
---|
| 85 | * @returns {Function} A fixer function
|
---|
| 86 | */
|
---|
| 87 | function getFixer(operatorToken, desiredStyle) {
|
---|
| 88 | return fixer => {
|
---|
| 89 | const tokenBefore = sourceCode.getTokenBefore(operatorToken);
|
---|
| 90 | const tokenAfter = sourceCode.getTokenAfter(operatorToken);
|
---|
| 91 | const textBefore = sourceCode.text.slice(tokenBefore.range[1], operatorToken.range[0]);
|
---|
| 92 | const textAfter = sourceCode.text.slice(operatorToken.range[1], tokenAfter.range[0]);
|
---|
| 93 | const hasLinebreakBefore = !astUtils.isTokenOnSameLine(tokenBefore, operatorToken);
|
---|
| 94 | const hasLinebreakAfter = !astUtils.isTokenOnSameLine(operatorToken, tokenAfter);
|
---|
| 95 | let newTextBefore, newTextAfter;
|
---|
| 96 |
|
---|
| 97 | if (hasLinebreakBefore !== hasLinebreakAfter && desiredStyle !== "none") {
|
---|
| 98 |
|
---|
| 99 | // If there is a comment before and after the operator, don't do a fix.
|
---|
| 100 | if (sourceCode.getTokenBefore(operatorToken, { includeComments: true }) !== tokenBefore &&
|
---|
| 101 | sourceCode.getTokenAfter(operatorToken, { includeComments: true }) !== tokenAfter) {
|
---|
| 102 |
|
---|
| 103 | return null;
|
---|
| 104 | }
|
---|
| 105 |
|
---|
| 106 | /*
|
---|
| 107 | * If there is only one linebreak and it's on the wrong side of the operator, swap the text before and after the operator.
|
---|
| 108 | * foo &&
|
---|
| 109 | * bar
|
---|
| 110 | * would get fixed to
|
---|
| 111 | * foo
|
---|
| 112 | * && bar
|
---|
| 113 | */
|
---|
| 114 | newTextBefore = textAfter;
|
---|
| 115 | newTextAfter = textBefore;
|
---|
| 116 | } else {
|
---|
| 117 | const LINEBREAK_REGEX = astUtils.createGlobalLinebreakMatcher();
|
---|
| 118 |
|
---|
| 119 | // Otherwise, if no linebreak is desired and no comments interfere, replace the linebreaks with empty strings.
|
---|
| 120 | newTextBefore = desiredStyle === "before" || textBefore.trim() ? textBefore : textBefore.replace(LINEBREAK_REGEX, "");
|
---|
| 121 | newTextAfter = desiredStyle === "after" || textAfter.trim() ? textAfter : textAfter.replace(LINEBREAK_REGEX, "");
|
---|
| 122 |
|
---|
| 123 | // If there was no change (due to interfering comments), don't output a fix.
|
---|
| 124 | if (newTextBefore === textBefore && newTextAfter === textAfter) {
|
---|
| 125 | return null;
|
---|
| 126 | }
|
---|
| 127 | }
|
---|
| 128 |
|
---|
| 129 | if (newTextAfter === "" && tokenAfter.type === "Punctuator" && "+-".includes(operatorToken.value) && tokenAfter.value === operatorToken.value) {
|
---|
| 130 |
|
---|
| 131 | // To avoid accidentally creating a ++ or -- operator, insert a space if the operator is a +/- and the following token is a unary +/-.
|
---|
| 132 | newTextAfter += " ";
|
---|
| 133 | }
|
---|
| 134 |
|
---|
| 135 | return fixer.replaceTextRange([tokenBefore.range[1], tokenAfter.range[0]], newTextBefore + operatorToken.value + newTextAfter);
|
---|
| 136 | };
|
---|
| 137 | }
|
---|
| 138 |
|
---|
| 139 | /**
|
---|
| 140 | * Checks the operator placement
|
---|
| 141 | * @param {ASTNode} node The node to check
|
---|
| 142 | * @param {ASTNode} rightSide The node that comes after the operator in `node`
|
---|
| 143 | * @param {string} operator The operator
|
---|
| 144 | * @private
|
---|
| 145 | * @returns {void}
|
---|
| 146 | */
|
---|
| 147 | function validateNode(node, rightSide, operator) {
|
---|
| 148 |
|
---|
| 149 | /*
|
---|
| 150 | * Find the operator token by searching from the right side, because between the left side and the operator
|
---|
| 151 | * there could be additional tokens from type annotations. Search specifically for the token which
|
---|
| 152 | * value equals the operator, in order to skip possible opening parentheses before the right side node.
|
---|
| 153 | */
|
---|
| 154 | const operatorToken = sourceCode.getTokenBefore(rightSide, token => token.value === operator);
|
---|
| 155 | const leftToken = sourceCode.getTokenBefore(operatorToken);
|
---|
| 156 | const rightToken = sourceCode.getTokenAfter(operatorToken);
|
---|
| 157 | const operatorStyleOverride = styleOverrides[operator];
|
---|
| 158 | const style = operatorStyleOverride || globalStyle;
|
---|
| 159 | const fix = getFixer(operatorToken, style);
|
---|
| 160 |
|
---|
| 161 | // if single line
|
---|
| 162 | if (astUtils.isTokenOnSameLine(leftToken, operatorToken) &&
|
---|
| 163 | astUtils.isTokenOnSameLine(operatorToken, rightToken)) {
|
---|
| 164 |
|
---|
| 165 | // do nothing.
|
---|
| 166 |
|
---|
| 167 | } else if (operatorStyleOverride !== "ignore" && !astUtils.isTokenOnSameLine(leftToken, operatorToken) &&
|
---|
| 168 | !astUtils.isTokenOnSameLine(operatorToken, rightToken)) {
|
---|
| 169 |
|
---|
| 170 | // lone operator
|
---|
| 171 | context.report({
|
---|
| 172 | node,
|
---|
| 173 | loc: operatorToken.loc,
|
---|
| 174 | messageId: "badLinebreak",
|
---|
| 175 | data: {
|
---|
| 176 | operator
|
---|
| 177 | },
|
---|
| 178 | fix
|
---|
| 179 | });
|
---|
| 180 |
|
---|
| 181 | } else if (style === "before" && astUtils.isTokenOnSameLine(leftToken, operatorToken)) {
|
---|
| 182 |
|
---|
| 183 | context.report({
|
---|
| 184 | node,
|
---|
| 185 | loc: operatorToken.loc,
|
---|
| 186 | messageId: "operatorAtBeginning",
|
---|
| 187 | data: {
|
---|
| 188 | operator
|
---|
| 189 | },
|
---|
| 190 | fix
|
---|
| 191 | });
|
---|
| 192 |
|
---|
| 193 | } else if (style === "after" && astUtils.isTokenOnSameLine(operatorToken, rightToken)) {
|
---|
| 194 |
|
---|
| 195 | context.report({
|
---|
| 196 | node,
|
---|
| 197 | loc: operatorToken.loc,
|
---|
| 198 | messageId: "operatorAtEnd",
|
---|
| 199 | data: {
|
---|
| 200 | operator
|
---|
| 201 | },
|
---|
| 202 | fix
|
---|
| 203 | });
|
---|
| 204 |
|
---|
| 205 | } else if (style === "none") {
|
---|
| 206 |
|
---|
| 207 | context.report({
|
---|
| 208 | node,
|
---|
| 209 | loc: operatorToken.loc,
|
---|
| 210 | messageId: "noLinebreak",
|
---|
| 211 | data: {
|
---|
| 212 | operator
|
---|
| 213 | },
|
---|
| 214 | fix
|
---|
| 215 | });
|
---|
| 216 |
|
---|
| 217 | }
|
---|
| 218 | }
|
---|
| 219 |
|
---|
| 220 | /**
|
---|
| 221 | * Validates a binary expression using `validateNode`
|
---|
| 222 | * @param {BinaryExpression|LogicalExpression|AssignmentExpression} node node to be validated
|
---|
| 223 | * @returns {void}
|
---|
| 224 | */
|
---|
| 225 | function validateBinaryExpression(node) {
|
---|
| 226 | validateNode(node, node.right, node.operator);
|
---|
| 227 | }
|
---|
| 228 |
|
---|
| 229 | //--------------------------------------------------------------------------
|
---|
| 230 | // Public
|
---|
| 231 | //--------------------------------------------------------------------------
|
---|
| 232 |
|
---|
| 233 | return {
|
---|
| 234 | BinaryExpression: validateBinaryExpression,
|
---|
| 235 | LogicalExpression: validateBinaryExpression,
|
---|
| 236 | AssignmentExpression: validateBinaryExpression,
|
---|
| 237 | VariableDeclarator(node) {
|
---|
| 238 | if (node.init) {
|
---|
| 239 | validateNode(node, node.init, "=");
|
---|
| 240 | }
|
---|
| 241 | },
|
---|
| 242 | PropertyDefinition(node) {
|
---|
| 243 | if (node.value) {
|
---|
| 244 | validateNode(node, node.value, "=");
|
---|
| 245 | }
|
---|
| 246 | },
|
---|
| 247 | ConditionalExpression(node) {
|
---|
| 248 | validateNode(node, node.consequent, "?");
|
---|
| 249 | validateNode(node, node.alternate, ":");
|
---|
| 250 | }
|
---|
| 251 | };
|
---|
| 252 | }
|
---|
| 253 | };
|
---|