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.

prefer-const.js 16KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468
  1. /**
  2. * @fileoverview A rule to suggest using of const declaration for variables that are never reassigned after declared.
  3. * @author Toru Nagashima
  4. */
  5. "use strict";
  6. const astUtils = require("../util/ast-utils");
  7. //------------------------------------------------------------------------------
  8. // Helpers
  9. //------------------------------------------------------------------------------
  10. const PATTERN_TYPE = /^(?:.+?Pattern|RestElement|SpreadProperty|ExperimentalRestProperty|Property)$/;
  11. const DECLARATION_HOST_TYPE = /^(?:Program|BlockStatement|SwitchCase)$/;
  12. const DESTRUCTURING_HOST_TYPE = /^(?:VariableDeclarator|AssignmentExpression)$/;
  13. /**
  14. * Checks whether a given node is located at `ForStatement.init` or not.
  15. *
  16. * @param {ASTNode} node - A node to check.
  17. * @returns {boolean} `true` if the node is located at `ForStatement.init`.
  18. */
  19. function isInitOfForStatement(node) {
  20. return node.parent.type === "ForStatement" && node.parent.init === node;
  21. }
  22. /**
  23. * Checks whether a given Identifier node becomes a VariableDeclaration or not.
  24. *
  25. * @param {ASTNode} identifier - An Identifier node to check.
  26. * @returns {boolean} `true` if the node can become a VariableDeclaration.
  27. */
  28. function canBecomeVariableDeclaration(identifier) {
  29. let node = identifier.parent;
  30. while (PATTERN_TYPE.test(node.type)) {
  31. node = node.parent;
  32. }
  33. return (
  34. node.type === "VariableDeclarator" ||
  35. (
  36. node.type === "AssignmentExpression" &&
  37. node.parent.type === "ExpressionStatement" &&
  38. DECLARATION_HOST_TYPE.test(node.parent.parent.type)
  39. )
  40. );
  41. }
  42. /**
  43. * Checks if an property or element is from outer scope or function parameters
  44. * in destructing pattern.
  45. *
  46. * @param {string} name - A variable name to be checked.
  47. * @param {eslint-scope.Scope} initScope - A scope to start find.
  48. * @returns {boolean} Indicates if the variable is from outer scope or function parameters.
  49. */
  50. function isOuterVariableInDestructing(name, initScope) {
  51. if (initScope.through.find(ref => ref.resolved && ref.resolved.name === name)) {
  52. return true;
  53. }
  54. const variable = astUtils.getVariableByName(initScope, name);
  55. if (variable !== null) {
  56. return variable.defs.some(def => def.type === "Parameter");
  57. }
  58. return false;
  59. }
  60. /**
  61. * Gets the VariableDeclarator/AssignmentExpression node that a given reference
  62. * belongs to.
  63. * This is used to detect a mix of reassigned and never reassigned in a
  64. * destructuring.
  65. *
  66. * @param {eslint-scope.Reference} reference - A reference to get.
  67. * @returns {ASTNode|null} A VariableDeclarator/AssignmentExpression node or
  68. * null.
  69. */
  70. function getDestructuringHost(reference) {
  71. if (!reference.isWrite()) {
  72. return null;
  73. }
  74. let node = reference.identifier.parent;
  75. while (PATTERN_TYPE.test(node.type)) {
  76. node = node.parent;
  77. }
  78. if (!DESTRUCTURING_HOST_TYPE.test(node.type)) {
  79. return null;
  80. }
  81. return node;
  82. }
  83. /**
  84. * Determines if a destructuring assignment node contains
  85. * any MemberExpression nodes. This is used to determine if a
  86. * variable that is only written once using destructuring can be
  87. * safely converted into a const declaration.
  88. * @param {ASTNode} node The ObjectPattern or ArrayPattern node to check.
  89. * @returns {boolean} True if the destructuring pattern contains
  90. * a MemberExpression, false if not.
  91. */
  92. function hasMemberExpressionAssignment(node) {
  93. switch (node.type) {
  94. case "ObjectPattern":
  95. return node.properties.some(prop => {
  96. if (prop) {
  97. /*
  98. * Spread elements have an argument property while
  99. * others have a value property. Because different
  100. * parsers use different node types for spread elements,
  101. * we just check if there is an argument property.
  102. */
  103. return hasMemberExpressionAssignment(prop.argument || prop.value);
  104. }
  105. return false;
  106. });
  107. case "ArrayPattern":
  108. return node.elements.some(element => {
  109. if (element) {
  110. return hasMemberExpressionAssignment(element);
  111. }
  112. return false;
  113. });
  114. case "AssignmentPattern":
  115. return hasMemberExpressionAssignment(node.left);
  116. case "MemberExpression":
  117. return true;
  118. // no default
  119. }
  120. return false;
  121. }
  122. /**
  123. * Gets an identifier node of a given variable.
  124. *
  125. * If the initialization exists or one or more reading references exist before
  126. * the first assignment, the identifier node is the node of the declaration.
  127. * Otherwise, the identifier node is the node of the first assignment.
  128. *
  129. * If the variable should not change to const, this function returns null.
  130. * - If the variable is reassigned.
  131. * - If the variable is never initialized nor assigned.
  132. * - If the variable is initialized in a different scope from the declaration.
  133. * - If the unique assignment of the variable cannot change to a declaration.
  134. * e.g. `if (a) b = 1` / `return (b = 1)`
  135. * - If the variable is declared in the global scope and `eslintUsed` is `true`.
  136. * `/*exported foo` directive comment makes such variables. This rule does not
  137. * warn such variables because this rule cannot distinguish whether the
  138. * exported variables are reassigned or not.
  139. *
  140. * @param {eslint-scope.Variable} variable - A variable to get.
  141. * @param {boolean} ignoreReadBeforeAssign -
  142. * The value of `ignoreReadBeforeAssign` option.
  143. * @returns {ASTNode|null}
  144. * An Identifier node if the variable should change to const.
  145. * Otherwise, null.
  146. */
  147. function getIdentifierIfShouldBeConst(variable, ignoreReadBeforeAssign) {
  148. if (variable.eslintUsed && variable.scope.type === "global") {
  149. return null;
  150. }
  151. // Finds the unique WriteReference.
  152. let writer = null;
  153. let isReadBeforeInit = false;
  154. const references = variable.references;
  155. for (let i = 0; i < references.length; ++i) {
  156. const reference = references[i];
  157. if (reference.isWrite()) {
  158. const isReassigned = (
  159. writer !== null &&
  160. writer.identifier !== reference.identifier
  161. );
  162. if (isReassigned) {
  163. return null;
  164. }
  165. const destructuringHost = getDestructuringHost(reference);
  166. if (destructuringHost !== null && destructuringHost.left !== void 0) {
  167. const leftNode = destructuringHost.left;
  168. let hasOuterVariables = false,
  169. hasNonIdentifiers = false;
  170. if (leftNode.type === "ObjectPattern") {
  171. const properties = leftNode.properties;
  172. hasOuterVariables = properties
  173. .filter(prop => prop.value)
  174. .map(prop => prop.value.name)
  175. .some(name => isOuterVariableInDestructing(name, variable.scope));
  176. hasNonIdentifiers = hasMemberExpressionAssignment(leftNode);
  177. } else if (leftNode.type === "ArrayPattern") {
  178. const elements = leftNode.elements;
  179. hasOuterVariables = elements
  180. .map(element => element && element.name)
  181. .some(name => isOuterVariableInDestructing(name, variable.scope));
  182. hasNonIdentifiers = hasMemberExpressionAssignment(leftNode);
  183. }
  184. if (hasOuterVariables || hasNonIdentifiers) {
  185. return null;
  186. }
  187. }
  188. writer = reference;
  189. } else if (reference.isRead() && writer === null) {
  190. if (ignoreReadBeforeAssign) {
  191. return null;
  192. }
  193. isReadBeforeInit = true;
  194. }
  195. }
  196. /*
  197. * If the assignment is from a different scope, ignore it.
  198. * If the assignment cannot change to a declaration, ignore it.
  199. */
  200. const shouldBeConst = (
  201. writer !== null &&
  202. writer.from === variable.scope &&
  203. canBecomeVariableDeclaration(writer.identifier)
  204. );
  205. if (!shouldBeConst) {
  206. return null;
  207. }
  208. if (isReadBeforeInit) {
  209. return variable.defs[0].name;
  210. }
  211. return writer.identifier;
  212. }
  213. /**
  214. * Groups by the VariableDeclarator/AssignmentExpression node that each
  215. * reference of given variables belongs to.
  216. * This is used to detect a mix of reassigned and never reassigned in a
  217. * destructuring.
  218. *
  219. * @param {eslint-scope.Variable[]} variables - Variables to group by destructuring.
  220. * @param {boolean} ignoreReadBeforeAssign -
  221. * The value of `ignoreReadBeforeAssign` option.
  222. * @returns {Map<ASTNode, ASTNode[]>} Grouped identifier nodes.
  223. */
  224. function groupByDestructuring(variables, ignoreReadBeforeAssign) {
  225. const identifierMap = new Map();
  226. for (let i = 0; i < variables.length; ++i) {
  227. const variable = variables[i];
  228. const references = variable.references;
  229. const identifier = getIdentifierIfShouldBeConst(variable, ignoreReadBeforeAssign);
  230. let prevId = null;
  231. for (let j = 0; j < references.length; ++j) {
  232. const reference = references[j];
  233. const id = reference.identifier;
  234. /*
  235. * Avoid counting a reference twice or more for default values of
  236. * destructuring.
  237. */
  238. if (id === prevId) {
  239. continue;
  240. }
  241. prevId = id;
  242. // Add the identifier node into the destructuring group.
  243. const group = getDestructuringHost(reference);
  244. if (group) {
  245. if (identifierMap.has(group)) {
  246. identifierMap.get(group).push(identifier);
  247. } else {
  248. identifierMap.set(group, [identifier]);
  249. }
  250. }
  251. }
  252. }
  253. return identifierMap;
  254. }
  255. /**
  256. * Finds the nearest parent of node with a given type.
  257. *
  258. * @param {ASTNode} node – The node to search from.
  259. * @param {string} type – The type field of the parent node.
  260. * @param {Function} shouldStop – a predicate that returns true if the traversal should stop, and false otherwise.
  261. * @returns {ASTNode} The closest ancestor with the specified type; null if no such ancestor exists.
  262. */
  263. function findUp(node, type, shouldStop) {
  264. if (!node || shouldStop(node)) {
  265. return null;
  266. }
  267. if (node.type === type) {
  268. return node;
  269. }
  270. return findUp(node.parent, type, shouldStop);
  271. }
  272. //------------------------------------------------------------------------------
  273. // Rule Definition
  274. //------------------------------------------------------------------------------
  275. module.exports = {
  276. meta: {
  277. type: "suggestion",
  278. docs: {
  279. description: "require `const` declarations for variables that are never reassigned after declared",
  280. category: "ECMAScript 6",
  281. recommended: false,
  282. url: "https://eslint.org/docs/rules/prefer-const"
  283. },
  284. fixable: "code",
  285. schema: [
  286. {
  287. type: "object",
  288. properties: {
  289. destructuring: { enum: ["any", "all"] },
  290. ignoreReadBeforeAssign: { type: "boolean" }
  291. },
  292. additionalProperties: false
  293. }
  294. ]
  295. },
  296. create(context) {
  297. const options = context.options[0] || {};
  298. const sourceCode = context.getSourceCode();
  299. const shouldMatchAnyDestructuredVariable = options.destructuring !== "all";
  300. const ignoreReadBeforeAssign = options.ignoreReadBeforeAssign === true;
  301. const variables = [];
  302. let reportCount = 0;
  303. let name = "";
  304. /**
  305. * Reports given identifier nodes if all of the nodes should be declared
  306. * as const.
  307. *
  308. * The argument 'nodes' is an array of Identifier nodes.
  309. * This node is the result of 'getIdentifierIfShouldBeConst()', so it's
  310. * nullable. In simple declaration or assignment cases, the length of
  311. * the array is 1. In destructuring cases, the length of the array can
  312. * be 2 or more.
  313. *
  314. * @param {(eslint-scope.Reference|null)[]} nodes -
  315. * References which are grouped by destructuring to report.
  316. * @returns {void}
  317. */
  318. function checkGroup(nodes) {
  319. const nodesToReport = nodes.filter(Boolean);
  320. if (nodes.length && (shouldMatchAnyDestructuredVariable || nodesToReport.length === nodes.length)) {
  321. const varDeclParent = findUp(nodes[0], "VariableDeclaration", parentNode => parentNode.type.endsWith("Statement"));
  322. const isVarDecParentNull = varDeclParent === null;
  323. if (!isVarDecParentNull && varDeclParent.declarations.length > 0) {
  324. const firstDeclaration = varDeclParent.declarations[0];
  325. if (firstDeclaration.init) {
  326. const firstDecParent = firstDeclaration.init.parent;
  327. /*
  328. * First we check the declaration type and then depending on
  329. * if the type is a "VariableDeclarator" or its an "ObjectPattern"
  330. * we compare the name from the first identifier, if the names are different
  331. * we assign the new name and reset the count of reportCount and nodeCount in
  332. * order to check each block for the number of reported errors and base our fix
  333. * based on comparing nodes.length and nodesToReport.length.
  334. */
  335. if (firstDecParent.type === "VariableDeclarator") {
  336. if (firstDecParent.id.name !== name) {
  337. name = firstDecParent.id.name;
  338. reportCount = 0;
  339. }
  340. if (firstDecParent.id.type === "ObjectPattern") {
  341. if (firstDecParent.init.name !== name) {
  342. name = firstDecParent.init.name;
  343. reportCount = 0;
  344. }
  345. }
  346. }
  347. }
  348. }
  349. let shouldFix = varDeclParent &&
  350. // Don't do a fix unless the variable is initialized (or it's in a for-in or for-of loop)
  351. (varDeclParent.parent.type === "ForInStatement" || varDeclParent.parent.type === "ForOfStatement" || varDeclParent.declarations[0].init) &&
  352. /*
  353. * If options.destructuring is "all", then this warning will not occur unless
  354. * every assignment in the destructuring should be const. In that case, it's safe
  355. * to apply the fix.
  356. */
  357. nodesToReport.length === nodes.length;
  358. if (!isVarDecParentNull && varDeclParent.declarations && varDeclParent.declarations.length !== 1) {
  359. if (varDeclParent && varDeclParent.declarations && varDeclParent.declarations.length >= 1) {
  360. /*
  361. * Add nodesToReport.length to a count, then comparing the count to the length
  362. * of the declarations in the current block.
  363. */
  364. reportCount += nodesToReport.length;
  365. shouldFix = shouldFix && (reportCount === varDeclParent.declarations.length);
  366. }
  367. }
  368. nodesToReport.forEach(node => {
  369. context.report({
  370. node,
  371. message: "'{{name}}' is never reassigned. Use 'const' instead.",
  372. data: node,
  373. fix: shouldFix ? fixer => fixer.replaceText(sourceCode.getFirstToken(varDeclParent), "const") : null
  374. });
  375. });
  376. }
  377. }
  378. return {
  379. "Program:exit"() {
  380. groupByDestructuring(variables, ignoreReadBeforeAssign).forEach(checkGroup);
  381. },
  382. VariableDeclaration(node) {
  383. if (node.kind === "let" && !isInitOfForStatement(node)) {
  384. variables.push(...context.getDeclaredVariables(node));
  385. }
  386. }
  387. };
  388. }
  389. };