no-extra-arguments.js 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  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/S930
  22. const nodes_1 = require("../utils/nodes");
  23. const locations_1 = require("../utils/locations");
  24. const docs_url_1 = require("../utils/docs-url");
  25. const message = 'This function expects {{expectedArguments}}, but {{providedArguments}} provided.';
  26. const rule = {
  27. meta: {
  28. messages: {
  29. tooManyArguments: message,
  30. sonarRuntime: '{{sonarRuntimeData}}',
  31. },
  32. type: 'problem',
  33. docs: {
  34. description: 'Function calls should not pass extra arguments',
  35. recommended: 'error',
  36. url: (0, docs_url_1.default)(__filename),
  37. },
  38. schema: [
  39. {
  40. // internal parameter
  41. enum: ['sonar-runtime'],
  42. },
  43. ],
  44. },
  45. create(context) {
  46. const callExpressionsToCheck = [];
  47. const usingArguments = new Set();
  48. const emptyFunctions = new Set();
  49. return {
  50. // eslint-disable-next-line sonarjs/cognitive-complexity
  51. CallExpression(node) {
  52. const callExpr = node;
  53. if ((0, nodes_1.isIdentifier)(callExpr.callee)) {
  54. const reference = context
  55. .getScope()
  56. .references.find(ref => ref.identifier === callExpr.callee);
  57. const definition = reference && getSingleDefinition(reference);
  58. if (definition) {
  59. if (definition.type === 'FunctionName') {
  60. checkFunction(callExpr, definition.node);
  61. }
  62. else if (definition.type === 'Variable') {
  63. const { init } = definition.node;
  64. if (init && ((0, nodes_1.isFunctionExpression)(init) || (0, nodes_1.isArrowFunctionExpression)(init))) {
  65. checkFunction(callExpr, init);
  66. }
  67. }
  68. }
  69. }
  70. else if ((0, nodes_1.isArrowFunctionExpression)(callExpr.callee) ||
  71. (0, nodes_1.isFunctionExpression)(callExpr.callee)) {
  72. // IIFE
  73. checkFunction(callExpr, callExpr.callee);
  74. }
  75. },
  76. ':function'(node) {
  77. const fn = node;
  78. if ((0, nodes_1.isBlockStatement)(fn.body) && fn.body.body.length === 0 && fn.params.length === 0) {
  79. emptyFunctions.add(node);
  80. }
  81. },
  82. 'FunctionDeclaration > BlockStatement Identifier'(node) {
  83. checkArguments(node);
  84. },
  85. 'FunctionExpression > BlockStatement Identifier'(node) {
  86. checkArguments(node);
  87. },
  88. 'Program:exit'() {
  89. callExpressionsToCheck.forEach(({ callExpr, functionNode }) => {
  90. if (!usingArguments.has(functionNode) && !emptyFunctions.has(functionNode)) {
  91. reportIssue(callExpr, functionNode);
  92. }
  93. });
  94. },
  95. };
  96. function getSingleDefinition(reference) {
  97. if (reference && reference.resolved) {
  98. const variable = reference.resolved;
  99. if (variable.defs.length === 1) {
  100. return variable.defs[0];
  101. }
  102. }
  103. return undefined;
  104. }
  105. function checkArguments(identifier) {
  106. if (identifier.name === 'arguments') {
  107. const reference = context.getScope().references.find(ref => ref.identifier === identifier);
  108. const definition = reference && getSingleDefinition(reference);
  109. // special `arguments` variable has no definition
  110. if (!definition) {
  111. const ancestors = context.getAncestors().reverse();
  112. const fn = ancestors.find(node => (0, nodes_1.isFunctionDeclaration)(node) || (0, nodes_1.isFunctionExpression)(node));
  113. if (fn) {
  114. usingArguments.add(fn);
  115. }
  116. }
  117. }
  118. }
  119. function checkFunction(callExpr, functionNode) {
  120. const hasRest = functionNode.params.some(param => param.type === 'RestElement');
  121. if (!hasRest && callExpr.arguments.length > functionNode.params.length) {
  122. callExpressionsToCheck.push({ callExpr, functionNode });
  123. }
  124. }
  125. function reportIssue(callExpr, functionNode) {
  126. const paramLength = functionNode.params.length;
  127. const argsLength = callExpr.arguments.length;
  128. // prettier-ignore
  129. const expectedArguments =
  130. // eslint-disable-next-line no-nested-ternary
  131. paramLength === 0 ? "no arguments" :
  132. paramLength === 1 ? "1 argument" :
  133. `${paramLength} arguments`;
  134. // prettier-ignore
  135. const providedArguments =
  136. // eslint-disable-next-line no-nested-ternary
  137. argsLength === 0 ? "none was" :
  138. argsLength === 1 ? "1 was" :
  139. `${argsLength} were`;
  140. (0, locations_1.report)(context, {
  141. messageId: 'tooManyArguments',
  142. data: {
  143. expectedArguments,
  144. providedArguments,
  145. },
  146. node: callExpr.callee,
  147. }, getSecondaryLocations(callExpr, functionNode), message);
  148. }
  149. function getSecondaryLocations(callExpr, functionNode) {
  150. const paramLength = functionNode.params.length;
  151. const secondaryLocations = [];
  152. if (paramLength > 0) {
  153. const startLoc = functionNode.params[0].loc;
  154. const endLoc = functionNode.params[paramLength - 1].loc;
  155. secondaryLocations.push((0, locations_1.issueLocation)(startLoc, endLoc, 'Formal parameters'));
  156. }
  157. else {
  158. // as we're not providing parent node, `getMainFunctionTokenLocation` may return `undefined`
  159. const fnToken = (0, locations_1.getMainFunctionTokenLocation)(functionNode, undefined, context);
  160. if (fnToken) {
  161. secondaryLocations.push((0, locations_1.issueLocation)(fnToken, fnToken, 'Formal parameters'));
  162. }
  163. }
  164. // find actual extra arguments to highlight
  165. callExpr.arguments.forEach((argument, index) => {
  166. if (index >= paramLength) {
  167. secondaryLocations.push((0, locations_1.toSecondaryLocation)(argument, 'Extra argument'));
  168. }
  169. });
  170. return secondaryLocations;
  171. }
  172. },
  173. };
  174. module.exports = rule;
  175. //# sourceMappingURL=no-extra-arguments.js.map