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.

yoda.js 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313
  1. /**
  2. * @fileoverview Rule to require or disallow yoda comparisons
  3. * @author Nicholas C. Zakas
  4. */
  5. "use strict";
  6. //--------------------------------------------------------------------------
  7. // Requirements
  8. //--------------------------------------------------------------------------
  9. const astUtils = require("../util/ast-utils");
  10. //--------------------------------------------------------------------------
  11. // Helpers
  12. //--------------------------------------------------------------------------
  13. /**
  14. * Determines whether an operator is a comparison operator.
  15. * @param {string} operator The operator to check.
  16. * @returns {boolean} Whether or not it is a comparison operator.
  17. */
  18. function isComparisonOperator(operator) {
  19. return (/^(==|===|!=|!==|<|>|<=|>=)$/).test(operator);
  20. }
  21. /**
  22. * Determines whether an operator is an equality operator.
  23. * @param {string} operator The operator to check.
  24. * @returns {boolean} Whether or not it is an equality operator.
  25. */
  26. function isEqualityOperator(operator) {
  27. return (/^(==|===)$/).test(operator);
  28. }
  29. /**
  30. * Determines whether an operator is one used in a range test.
  31. * Allowed operators are `<` and `<=`.
  32. * @param {string} operator The operator to check.
  33. * @returns {boolean} Whether the operator is used in range tests.
  34. */
  35. function isRangeTestOperator(operator) {
  36. return ["<", "<="].indexOf(operator) >= 0;
  37. }
  38. /**
  39. * Determines whether a non-Literal node is a negative number that should be
  40. * treated as if it were a single Literal node.
  41. * @param {ASTNode} node Node to test.
  42. * @returns {boolean} True if the node is a negative number that looks like a
  43. * real literal and should be treated as such.
  44. */
  45. function looksLikeLiteral(node) {
  46. return (node.type === "UnaryExpression" &&
  47. node.operator === "-" &&
  48. node.prefix &&
  49. node.argument.type === "Literal" &&
  50. typeof node.argument.value === "number");
  51. }
  52. /**
  53. * Attempts to derive a Literal node from nodes that are treated like literals.
  54. * @param {ASTNode} node Node to normalize.
  55. * @param {number} [defaultValue] The default value to be returned if the node
  56. * is not a Literal.
  57. * @returns {ASTNode} One of the following options.
  58. * 1. The original node if the node is already a Literal
  59. * 2. A normalized Literal node with the negative number as the value if the
  60. * node represents a negative number literal.
  61. * 3. The Literal node which has the `defaultValue` argument if it exists.
  62. * 4. Otherwise `null`.
  63. */
  64. function getNormalizedLiteral(node, defaultValue) {
  65. if (node.type === "Literal") {
  66. return node;
  67. }
  68. if (looksLikeLiteral(node)) {
  69. return {
  70. type: "Literal",
  71. value: -node.argument.value,
  72. raw: `-${node.argument.value}`
  73. };
  74. }
  75. if (defaultValue) {
  76. return {
  77. type: "Literal",
  78. value: defaultValue,
  79. raw: String(defaultValue)
  80. };
  81. }
  82. return null;
  83. }
  84. /**
  85. * Checks whether two expressions reference the same value. For example:
  86. * a = a
  87. * a.b = a.b
  88. * a[0] = a[0]
  89. * a['b'] = a['b']
  90. * @param {ASTNode} a Left side of the comparison.
  91. * @param {ASTNode} b Right side of the comparison.
  92. * @returns {boolean} True if both sides match and reference the same value.
  93. */
  94. function same(a, b) {
  95. if (a.type !== b.type) {
  96. return false;
  97. }
  98. switch (a.type) {
  99. case "Identifier":
  100. return a.name === b.name;
  101. case "Literal":
  102. return a.value === b.value;
  103. case "MemberExpression": {
  104. const nameA = astUtils.getStaticPropertyName(a);
  105. // x.y = x["y"]
  106. if (nameA) {
  107. return (
  108. same(a.object, b.object) &&
  109. nameA === astUtils.getStaticPropertyName(b)
  110. );
  111. }
  112. /*
  113. * x[0] = x[0]
  114. * x[y] = x[y]
  115. * x.y = x.y
  116. */
  117. return (
  118. a.computed === b.computed &&
  119. same(a.object, b.object) &&
  120. same(a.property, b.property)
  121. );
  122. }
  123. case "ThisExpression":
  124. return true;
  125. default:
  126. return false;
  127. }
  128. }
  129. //------------------------------------------------------------------------------
  130. // Rule Definition
  131. //------------------------------------------------------------------------------
  132. module.exports = {
  133. meta: {
  134. type: "suggestion",
  135. docs: {
  136. description: "require or disallow \"Yoda\" conditions",
  137. category: "Best Practices",
  138. recommended: false,
  139. url: "https://eslint.org/docs/rules/yoda"
  140. },
  141. schema: [
  142. {
  143. enum: ["always", "never"]
  144. },
  145. {
  146. type: "object",
  147. properties: {
  148. exceptRange: {
  149. type: "boolean"
  150. },
  151. onlyEquality: {
  152. type: "boolean"
  153. }
  154. },
  155. additionalProperties: false
  156. }
  157. ],
  158. fixable: "code"
  159. },
  160. create(context) {
  161. // Default to "never" (!always) if no option
  162. const always = (context.options[0] === "always");
  163. const exceptRange = (context.options[1] && context.options[1].exceptRange);
  164. const onlyEquality = (context.options[1] && context.options[1].onlyEquality);
  165. const sourceCode = context.getSourceCode();
  166. /**
  167. * Determines whether node represents a range test.
  168. * A range test is a "between" test like `(0 <= x && x < 1)` or an "outside"
  169. * test like `(x < 0 || 1 <= x)`. It must be wrapped in parentheses, and
  170. * both operators must be `<` or `<=`. Finally, the literal on the left side
  171. * must be less than or equal to the literal on the right side so that the
  172. * test makes any sense.
  173. * @param {ASTNode} node LogicalExpression node to test.
  174. * @returns {boolean} Whether node is a range test.
  175. */
  176. function isRangeTest(node) {
  177. const left = node.left,
  178. right = node.right;
  179. /**
  180. * Determines whether node is of the form `0 <= x && x < 1`.
  181. * @returns {boolean} Whether node is a "between" range test.
  182. */
  183. function isBetweenTest() {
  184. let leftLiteral, rightLiteral;
  185. return (node.operator === "&&" &&
  186. (leftLiteral = getNormalizedLiteral(left.left)) &&
  187. (rightLiteral = getNormalizedLiteral(right.right, Number.POSITIVE_INFINITY)) &&
  188. leftLiteral.value <= rightLiteral.value &&
  189. same(left.right, right.left));
  190. }
  191. /**
  192. * Determines whether node is of the form `x < 0 || 1 <= x`.
  193. * @returns {boolean} Whether node is an "outside" range test.
  194. */
  195. function isOutsideTest() {
  196. let leftLiteral, rightLiteral;
  197. return (node.operator === "||" &&
  198. (leftLiteral = getNormalizedLiteral(left.right, Number.NEGATIVE_INFINITY)) &&
  199. (rightLiteral = getNormalizedLiteral(right.left)) &&
  200. leftLiteral.value <= rightLiteral.value &&
  201. same(left.left, right.right));
  202. }
  203. /**
  204. * Determines whether node is wrapped in parentheses.
  205. * @returns {boolean} Whether node is preceded immediately by an open
  206. * paren token and followed immediately by a close
  207. * paren token.
  208. */
  209. function isParenWrapped() {
  210. return astUtils.isParenthesised(sourceCode, node);
  211. }
  212. return (node.type === "LogicalExpression" &&
  213. left.type === "BinaryExpression" &&
  214. right.type === "BinaryExpression" &&
  215. isRangeTestOperator(left.operator) &&
  216. isRangeTestOperator(right.operator) &&
  217. (isBetweenTest() || isOutsideTest()) &&
  218. isParenWrapped());
  219. }
  220. const OPERATOR_FLIP_MAP = {
  221. "===": "===",
  222. "!==": "!==",
  223. "==": "==",
  224. "!=": "!=",
  225. "<": ">",
  226. ">": "<",
  227. "<=": ">=",
  228. ">=": "<="
  229. };
  230. /**
  231. * Returns a string representation of a BinaryExpression node with its sides/operator flipped around.
  232. * @param {ASTNode} node The BinaryExpression node
  233. * @returns {string} A string representation of the node with the sides and operator flipped
  234. */
  235. function getFlippedString(node) {
  236. const operatorToken = sourceCode.getFirstTokenBetween(node.left, node.right, token => token.value === node.operator);
  237. const textBeforeOperator = sourceCode.getText().slice(sourceCode.getTokenBefore(operatorToken).range[1], operatorToken.range[0]);
  238. const textAfterOperator = sourceCode.getText().slice(operatorToken.range[1], sourceCode.getTokenAfter(operatorToken).range[0]);
  239. const leftText = sourceCode.getText().slice(node.range[0], sourceCode.getTokenBefore(operatorToken).range[1]);
  240. const rightText = sourceCode.getText().slice(sourceCode.getTokenAfter(operatorToken).range[0], node.range[1]);
  241. return rightText + textBeforeOperator + OPERATOR_FLIP_MAP[operatorToken.value] + textAfterOperator + leftText;
  242. }
  243. //--------------------------------------------------------------------------
  244. // Public
  245. //--------------------------------------------------------------------------
  246. return {
  247. BinaryExpression(node) {
  248. const expectedLiteral = always ? node.left : node.right;
  249. const expectedNonLiteral = always ? node.right : node.left;
  250. // If `expectedLiteral` is not a literal, and `expectedNonLiteral` is a literal, raise an error.
  251. if (
  252. (expectedNonLiteral.type === "Literal" || looksLikeLiteral(expectedNonLiteral)) &&
  253. !(expectedLiteral.type === "Literal" || looksLikeLiteral(expectedLiteral)) &&
  254. !(!isEqualityOperator(node.operator) && onlyEquality) &&
  255. isComparisonOperator(node.operator) &&
  256. !(exceptRange && isRangeTest(context.getAncestors().pop()))
  257. ) {
  258. context.report({
  259. node,
  260. message: "Expected literal to be on the {{expectedSide}} side of {{operator}}.",
  261. data: {
  262. operator: node.operator,
  263. expectedSide: always ? "left" : "right"
  264. },
  265. fix: fixer => fixer.replaceText(node, getFlippedString(node))
  266. });
  267. }
  268. }
  269. };
  270. }
  271. };