Ohm-Management - Projektarbeit B-ME
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

operator-linebreak.js 10KB

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