no-element-overwrite.js 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. "use strict";
  2. /*
  3. * eslint-plugin-sonarjs
  4. * Copyright (C) 2018-2021 SonarSource SA
  5. * mailto:info AT sonarsource DOT com
  6. *
  7. * This program is free software; you can redistribute it and/or
  8. * modify it under the terms of the GNU Lesser General Public
  9. * License as published by the Free Software Foundation; either
  10. * version 3 of the License, or (at your option) any later version.
  11. *
  12. * This program is distributed in the hope that it will be useful,
  13. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  15. * Lesser General Public License for more details.
  16. *
  17. * You should have received a copy of the GNU Lesser General Public License
  18. * along with this program; if not, write to the Free Software Foundation,
  19. * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  20. */
  21. // https://sonarsource.github.io/rspec/#/rspec/S4143
  22. const equivalence_1 = require("../utils/equivalence");
  23. const nodes_1 = require("../utils/nodes");
  24. const locations_1 = require("../utils/locations");
  25. const docs_url_1 = require("../utils/docs-url");
  26. const message = 'Verify this is the index that was intended; "{{index}}" was already set on line {{line}}.';
  27. const rule = {
  28. meta: {
  29. messages: {
  30. verifyIntendedIndex: message,
  31. sonarRuntime: '{{sonarRuntimeData}}',
  32. },
  33. type: 'problem',
  34. docs: {
  35. description: 'Collection elements should not be replaced unconditionally',
  36. recommended: 'error',
  37. url: (0, docs_url_1.default)(__filename),
  38. },
  39. schema: [
  40. {
  41. // internal parameter
  42. enum: ['sonar-runtime'],
  43. },
  44. ],
  45. },
  46. create(context) {
  47. return {
  48. SwitchCase(node) {
  49. const switchCase = node;
  50. checkStatements(switchCase.consequent);
  51. },
  52. BlockStatement(node) {
  53. const block = node;
  54. checkStatements(block.body);
  55. },
  56. Program(node) {
  57. const program = node;
  58. checkStatements(program.body);
  59. },
  60. };
  61. function checkStatements(statements) {
  62. const usedKeys = new Map();
  63. let collection;
  64. statements.forEach(statement => {
  65. const keyWriteUsage = getKeyWriteUsage(statement);
  66. if (keyWriteUsage) {
  67. if (collection &&
  68. !(0, equivalence_1.areEquivalent)(keyWriteUsage.collectionNode, collection, context.getSourceCode())) {
  69. usedKeys.clear();
  70. }
  71. const sameKeyWriteUsage = usedKeys.get(keyWriteUsage.indexOrKey);
  72. if (sameKeyWriteUsage && sameKeyWriteUsage.node.loc) {
  73. const sameKeyWriteUsageLoc = sameKeyWriteUsage.node.loc;
  74. const secondaryLocations = [
  75. (0, locations_1.issueLocation)(sameKeyWriteUsageLoc, sameKeyWriteUsageLoc, 'Original value'),
  76. ];
  77. (0, locations_1.report)(context, {
  78. node: keyWriteUsage.node,
  79. messageId: 'verifyIntendedIndex',
  80. data: {
  81. index: keyWriteUsage.indexOrKey,
  82. line: sameKeyWriteUsage.node.loc.start.line,
  83. },
  84. }, secondaryLocations, message);
  85. }
  86. usedKeys.set(keyWriteUsage.indexOrKey, keyWriteUsage);
  87. collection = keyWriteUsage.collectionNode;
  88. }
  89. else {
  90. usedKeys.clear();
  91. }
  92. });
  93. }
  94. function getKeyWriteUsage(node) {
  95. if ((0, nodes_1.isExpressionStatement)(node)) {
  96. return arrayKeyWriteUsage(node.expression) || mapOrSetKeyWriteUsage(node.expression);
  97. }
  98. return undefined;
  99. }
  100. function arrayKeyWriteUsage(node) {
  101. // a[b] = ...
  102. if (isSimpleAssignment(node) && (0, nodes_1.isMemberExpression)(node.left) && node.left.computed) {
  103. const { left, right } = node;
  104. const index = extractIndex(left.property);
  105. if (index !== undefined && !isUsed(left.object, right)) {
  106. return {
  107. collectionNode: left.object,
  108. indexOrKey: index,
  109. node,
  110. };
  111. }
  112. }
  113. return undefined;
  114. }
  115. function mapOrSetKeyWriteUsage(node) {
  116. if ((0, nodes_1.isCallExpression)(node) && (0, nodes_1.isMemberExpression)(node.callee)) {
  117. const propertyAccess = node.callee;
  118. if ((0, nodes_1.isIdentifier)(propertyAccess.property)) {
  119. const methodName = propertyAccess.property.name;
  120. const addMethod = methodName === 'add' && node.arguments.length === 1;
  121. const setMethod = methodName === 'set' && node.arguments.length === 2;
  122. if (addMethod || setMethod) {
  123. const key = extractIndex(node.arguments[0]);
  124. if (key) {
  125. return {
  126. collectionNode: propertyAccess.object,
  127. indexOrKey: key,
  128. node,
  129. };
  130. }
  131. }
  132. }
  133. }
  134. return undefined;
  135. }
  136. function extractIndex(node) {
  137. if ((0, nodes_1.isLiteral)(node)) {
  138. const { value } = node;
  139. return typeof value === 'number' || typeof value === 'string' ? String(value) : undefined;
  140. }
  141. else if ((0, nodes_1.isIdentifier)(node)) {
  142. return node.name;
  143. }
  144. return undefined;
  145. }
  146. function isUsed(value, expression) {
  147. const valueTokens = context.getSourceCode().getTokens(value);
  148. const expressionTokens = context.getSourceCode().getTokens(expression);
  149. const foundUsage = expressionTokens.find((token, index) => {
  150. if (eq(token, valueTokens[0])) {
  151. for (let expressionIndex = index, valueIndex = 0; expressionIndex < expressionTokens.length && valueIndex < valueTokens.length; expressionIndex++, valueIndex++) {
  152. if (!eq(expressionTokens[expressionIndex], valueTokens[valueIndex])) {
  153. break;
  154. }
  155. else if (valueIndex === valueTokens.length - 1) {
  156. return true;
  157. }
  158. }
  159. }
  160. return false;
  161. });
  162. return foundUsage !== undefined;
  163. }
  164. },
  165. };
  166. function eq(token1, token2) {
  167. return token1.value === token2.value;
  168. }
  169. function isSimpleAssignment(node) {
  170. return (0, nodes_1.isAssignmentExpression)(node) && node.operator === '=';
  171. }
  172. module.exports = rule;
  173. //# sourceMappingURL=no-element-overwrite.js.map