no-identical-functions.js 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110
  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/S4144
  22. const equivalence_1 = require("../utils/equivalence");
  23. const locations_1 = require("../utils/locations");
  24. const docs_url_1 = require("../utils/docs-url");
  25. const DEFAULT_MIN_LINES = 3;
  26. const message = 'Update this function so that its implementation is not identical to the one on line {{line}}.';
  27. const rule = {
  28. meta: {
  29. messages: {
  30. identicalFunctions: message,
  31. sonarRuntime: '{{sonarRuntimeData}}',
  32. },
  33. type: 'problem',
  34. docs: {
  35. description: 'Functions should not have identical implementations',
  36. recommended: 'error',
  37. url: (0, docs_url_1.default)(__filename),
  38. },
  39. schema: [
  40. { type: 'integer', minimum: 3 },
  41. {
  42. enum: ['sonar-runtime'],
  43. },
  44. ],
  45. },
  46. create(context) {
  47. const functions = [];
  48. const minLines = typeof context.options[0] === 'number' ? context.options[0] : DEFAULT_MIN_LINES;
  49. return {
  50. FunctionDeclaration(node) {
  51. visitFunction(node);
  52. },
  53. 'VariableDeclarator > FunctionExpression, MethodDefinition > FunctionExpression': (node) => {
  54. visitFunction(node);
  55. },
  56. 'VariableDeclarator > ArrowFunctionExpression, MethodDefinition > ArrowFunctionExpression': (node) => {
  57. visitFunction(node);
  58. },
  59. 'Program:exit'() {
  60. processFunctions();
  61. },
  62. };
  63. function visitFunction(node) {
  64. if (isBigEnough(node.body)) {
  65. functions.push({ function: node, parent: node.parent });
  66. }
  67. }
  68. function processFunctions() {
  69. for (let i = 1; i < functions.length; i++) {
  70. const duplicatingFunction = functions[i].function;
  71. for (let j = 0; j < i; j++) {
  72. const originalFunction = functions[j].function;
  73. if ((0, equivalence_1.areEquivalent)(duplicatingFunction.body, originalFunction.body, context.getSourceCode()) &&
  74. originalFunction.loc) {
  75. const loc = (0, locations_1.getMainFunctionTokenLocation)(duplicatingFunction, functions[i].parent, context);
  76. const originalFunctionLoc = (0, locations_1.getMainFunctionTokenLocation)(originalFunction, functions[j].parent, context);
  77. const secondaryLocations = [
  78. (0, locations_1.issueLocation)(originalFunctionLoc, originalFunctionLoc, 'Original implementation'),
  79. ];
  80. (0, locations_1.report)(context, {
  81. messageId: 'identicalFunctions',
  82. data: {
  83. line: originalFunction.loc.start.line,
  84. },
  85. loc,
  86. }, secondaryLocations, message);
  87. break;
  88. }
  89. }
  90. }
  91. }
  92. function isBigEnough(node) {
  93. const tokens = context.getSourceCode().getTokens(node);
  94. if (tokens.length > 0 && tokens[0].value === '{') {
  95. tokens.shift();
  96. }
  97. if (tokens.length > 0 && tokens[tokens.length - 1].value === '}') {
  98. tokens.pop();
  99. }
  100. if (tokens.length > 0) {
  101. const firstLine = tokens[0].loc.start.line;
  102. const lastLine = tokens[tokens.length - 1].loc.end.line;
  103. return lastLine - firstLine + 1 >= minLines;
  104. }
  105. return false;
  106. }
  107. },
  108. };
  109. module.exports = rule;
  110. //# sourceMappingURL=no-identical-functions.js.map