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.

no-unmodified-loop-condition.js 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369
  1. /**
  2. * @fileoverview Rule to disallow use of unmodified expressions in loop conditions
  3. * @author Toru Nagashima
  4. */
  5. "use strict";
  6. //------------------------------------------------------------------------------
  7. // Requirements
  8. //------------------------------------------------------------------------------
  9. const Traverser = require("../util/traverser"),
  10. astUtils = require("../util/ast-utils");
  11. //------------------------------------------------------------------------------
  12. // Helpers
  13. //------------------------------------------------------------------------------
  14. const SENTINEL_PATTERN = /(?:(?:Call|Class|Function|Member|New|Yield)Expression|Statement|Declaration)$/;
  15. const LOOP_PATTERN = /^(?:DoWhile|For|While)Statement$/; // for-in/of statements don't have `test` property.
  16. const GROUP_PATTERN = /^(?:BinaryExpression|ConditionalExpression)$/;
  17. const SKIP_PATTERN = /^(?:ArrowFunction|Class|Function)Expression$/;
  18. const DYNAMIC_PATTERN = /^(?:Call|Member|New|TaggedTemplate|Yield)Expression$/;
  19. /**
  20. * @typedef {Object} LoopConditionInfo
  21. * @property {eslint-scope.Reference} reference - The reference.
  22. * @property {ASTNode} group - BinaryExpression or ConditionalExpression nodes
  23. * that the reference is belonging to.
  24. * @property {Function} isInLoop - The predicate which checks a given reference
  25. * is in this loop.
  26. * @property {boolean} modified - The flag that the reference is modified in
  27. * this loop.
  28. */
  29. /**
  30. * Checks whether or not a given reference is a write reference.
  31. *
  32. * @param {eslint-scope.Reference} reference - A reference to check.
  33. * @returns {boolean} `true` if the reference is a write reference.
  34. */
  35. function isWriteReference(reference) {
  36. if (reference.init) {
  37. const def = reference.resolved && reference.resolved.defs[0];
  38. if (!def || def.type !== "Variable" || def.parent.kind !== "var") {
  39. return false;
  40. }
  41. }
  42. return reference.isWrite();
  43. }
  44. /**
  45. * Checks whether or not a given loop condition info does not have the modified
  46. * flag.
  47. *
  48. * @param {LoopConditionInfo} condition - A loop condition info to check.
  49. * @returns {boolean} `true` if the loop condition info is "unmodified".
  50. */
  51. function isUnmodified(condition) {
  52. return !condition.modified;
  53. }
  54. /**
  55. * Checks whether or not a given loop condition info does not have the modified
  56. * flag and does not have the group this condition belongs to.
  57. *
  58. * @param {LoopConditionInfo} condition - A loop condition info to check.
  59. * @returns {boolean} `true` if the loop condition info is "unmodified".
  60. */
  61. function isUnmodifiedAndNotBelongToGroup(condition) {
  62. return !(condition.modified || condition.group);
  63. }
  64. /**
  65. * Checks whether or not a given reference is inside of a given node.
  66. *
  67. * @param {ASTNode} node - A node to check.
  68. * @param {eslint-scope.Reference} reference - A reference to check.
  69. * @returns {boolean} `true` if the reference is inside of the node.
  70. */
  71. function isInRange(node, reference) {
  72. const or = node.range;
  73. const ir = reference.identifier.range;
  74. return or[0] <= ir[0] && ir[1] <= or[1];
  75. }
  76. /**
  77. * Checks whether or not a given reference is inside of a loop node's condition.
  78. *
  79. * @param {ASTNode} node - A node to check.
  80. * @param {eslint-scope.Reference} reference - A reference to check.
  81. * @returns {boolean} `true` if the reference is inside of the loop node's
  82. * condition.
  83. */
  84. const isInLoop = {
  85. WhileStatement: isInRange,
  86. DoWhileStatement: isInRange,
  87. ForStatement(node, reference) {
  88. return (
  89. isInRange(node, reference) &&
  90. !(node.init && isInRange(node.init, reference))
  91. );
  92. }
  93. };
  94. /**
  95. * Gets the function which encloses a given reference.
  96. * This supports only FunctionDeclaration.
  97. *
  98. * @param {eslint-scope.Reference} reference - A reference to get.
  99. * @returns {ASTNode|null} The function node or null.
  100. */
  101. function getEncloseFunctionDeclaration(reference) {
  102. let node = reference.identifier;
  103. while (node) {
  104. if (node.type === "FunctionDeclaration") {
  105. return node.id ? node : null;
  106. }
  107. node = node.parent;
  108. }
  109. return null;
  110. }
  111. /**
  112. * Updates the "modified" flags of given loop conditions with given modifiers.
  113. *
  114. * @param {LoopConditionInfo[]} conditions - The loop conditions to be updated.
  115. * @param {eslint-scope.Reference[]} modifiers - The references to update.
  116. * @returns {void}
  117. */
  118. function updateModifiedFlag(conditions, modifiers) {
  119. for (let i = 0; i < conditions.length; ++i) {
  120. const condition = conditions[i];
  121. for (let j = 0; !condition.modified && j < modifiers.length; ++j) {
  122. const modifier = modifiers[j];
  123. let funcNode, funcVar;
  124. /*
  125. * Besides checking for the condition being in the loop, we want to
  126. * check the function that this modifier is belonging to is called
  127. * in the loop.
  128. * FIXME: This should probably be extracted to a function.
  129. */
  130. const inLoop = condition.isInLoop(modifier) || Boolean(
  131. (funcNode = getEncloseFunctionDeclaration(modifier)) &&
  132. (funcVar = astUtils.getVariableByName(modifier.from.upper, funcNode.id.name)) &&
  133. funcVar.references.some(condition.isInLoop)
  134. );
  135. condition.modified = inLoop;
  136. }
  137. }
  138. }
  139. //------------------------------------------------------------------------------
  140. // Rule Definition
  141. //------------------------------------------------------------------------------
  142. module.exports = {
  143. meta: {
  144. type: "problem",
  145. docs: {
  146. description: "disallow unmodified loop conditions",
  147. category: "Best Practices",
  148. recommended: false,
  149. url: "https://eslint.org/docs/rules/no-unmodified-loop-condition"
  150. },
  151. schema: []
  152. },
  153. create(context) {
  154. const sourceCode = context.getSourceCode();
  155. let groupMap = null;
  156. /**
  157. * Reports a given condition info.
  158. *
  159. * @param {LoopConditionInfo} condition - A loop condition info to report.
  160. * @returns {void}
  161. */
  162. function report(condition) {
  163. const node = condition.reference.identifier;
  164. context.report({
  165. node,
  166. message: "'{{name}}' is not modified in this loop.",
  167. data: node
  168. });
  169. }
  170. /**
  171. * Registers given conditions to the group the condition belongs to.
  172. *
  173. * @param {LoopConditionInfo[]} conditions - A loop condition info to
  174. * register.
  175. * @returns {void}
  176. */
  177. function registerConditionsToGroup(conditions) {
  178. for (let i = 0; i < conditions.length; ++i) {
  179. const condition = conditions[i];
  180. if (condition.group) {
  181. let group = groupMap.get(condition.group);
  182. if (!group) {
  183. group = [];
  184. groupMap.set(condition.group, group);
  185. }
  186. group.push(condition);
  187. }
  188. }
  189. }
  190. /**
  191. * Reports references which are inside of unmodified groups.
  192. *
  193. * @param {LoopConditionInfo[]} conditions - A loop condition info to report.
  194. * @returns {void}
  195. */
  196. function checkConditionsInGroup(conditions) {
  197. if (conditions.every(isUnmodified)) {
  198. conditions.forEach(report);
  199. }
  200. }
  201. /**
  202. * Checks whether or not a given group node has any dynamic elements.
  203. *
  204. * @param {ASTNode} root - A node to check.
  205. * This node is one of BinaryExpression or ConditionalExpression.
  206. * @returns {boolean} `true` if the node is dynamic.
  207. */
  208. function hasDynamicExpressions(root) {
  209. let retv = false;
  210. Traverser.traverse(root, {
  211. visitorKeys: sourceCode.visitorKeys,
  212. enter(node) {
  213. if (DYNAMIC_PATTERN.test(node.type)) {
  214. retv = true;
  215. this.break();
  216. } else if (SKIP_PATTERN.test(node.type)) {
  217. this.skip();
  218. }
  219. }
  220. });
  221. return retv;
  222. }
  223. /**
  224. * Creates the loop condition information from a given reference.
  225. *
  226. * @param {eslint-scope.Reference} reference - A reference to create.
  227. * @returns {LoopConditionInfo|null} Created loop condition info, or null.
  228. */
  229. function toLoopCondition(reference) {
  230. if (reference.init) {
  231. return null;
  232. }
  233. let group = null;
  234. let child = reference.identifier;
  235. let node = child.parent;
  236. while (node) {
  237. if (SENTINEL_PATTERN.test(node.type)) {
  238. if (LOOP_PATTERN.test(node.type) && node.test === child) {
  239. // This reference is inside of a loop condition.
  240. return {
  241. reference,
  242. group,
  243. isInLoop: isInLoop[node.type].bind(null, node),
  244. modified: false
  245. };
  246. }
  247. // This reference is outside of a loop condition.
  248. break;
  249. }
  250. /*
  251. * If it's inside of a group, OK if either operand is modified.
  252. * So stores the group this reference belongs to.
  253. */
  254. if (GROUP_PATTERN.test(node.type)) {
  255. // If this expression is dynamic, no need to check.
  256. if (hasDynamicExpressions(node)) {
  257. break;
  258. } else {
  259. group = node;
  260. }
  261. }
  262. child = node;
  263. node = node.parent;
  264. }
  265. return null;
  266. }
  267. /**
  268. * Finds unmodified references which are inside of a loop condition.
  269. * Then reports the references which are outside of groups.
  270. *
  271. * @param {eslint-scope.Variable} variable - A variable to report.
  272. * @returns {void}
  273. */
  274. function checkReferences(variable) {
  275. // Gets references that exist in loop conditions.
  276. const conditions = variable
  277. .references
  278. .map(toLoopCondition)
  279. .filter(Boolean);
  280. if (conditions.length === 0) {
  281. return;
  282. }
  283. // Registers the conditions to belonging groups.
  284. registerConditionsToGroup(conditions);
  285. // Check the conditions are modified.
  286. const modifiers = variable.references.filter(isWriteReference);
  287. if (modifiers.length > 0) {
  288. updateModifiedFlag(conditions, modifiers);
  289. }
  290. /*
  291. * Reports the conditions which are not belonging to groups.
  292. * Others will be reported after all variables are done.
  293. */
  294. conditions
  295. .filter(isUnmodifiedAndNotBelongToGroup)
  296. .forEach(report);
  297. }
  298. return {
  299. "Program:exit"() {
  300. const queue = [context.getScope()];
  301. groupMap = new Map();
  302. let scope;
  303. while ((scope = queue.pop())) {
  304. queue.push(...scope.childScopes);
  305. scope.variables.forEach(checkReferences);
  306. }
  307. groupMap.forEach(checkConditionsInGroup);
  308. groupMap = null;
  309. }
  310. };
  311. }
  312. };