constructor-super.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423
  1. /**
  2. * @fileoverview A rule to verify `super()` callings in constructor.
  3. * @author Toru Nagashima
  4. */
  5. "use strict";
  6. //------------------------------------------------------------------------------
  7. // Helpers
  8. //------------------------------------------------------------------------------
  9. /**
  10. * Checks whether a given code path segment is reachable or not.
  11. * @param {CodePathSegment} segment A code path segment to check.
  12. * @returns {boolean} `true` if the segment is reachable.
  13. */
  14. function isReachable(segment) {
  15. return segment.reachable;
  16. }
  17. /**
  18. * Checks whether or not a given node is a constructor.
  19. * @param {ASTNode} node A node to check. This node type is one of
  20. * `Program`, `FunctionDeclaration`, `FunctionExpression`, and
  21. * `ArrowFunctionExpression`.
  22. * @returns {boolean} `true` if the node is a constructor.
  23. */
  24. function isConstructorFunction(node) {
  25. return (
  26. node.type === "FunctionExpression" &&
  27. node.parent.type === "MethodDefinition" &&
  28. node.parent.kind === "constructor"
  29. );
  30. }
  31. /**
  32. * Checks whether a given node can be a constructor or not.
  33. * @param {ASTNode} node A node to check.
  34. * @returns {boolean} `true` if the node can be a constructor.
  35. */
  36. function isPossibleConstructor(node) {
  37. if (!node) {
  38. return false;
  39. }
  40. switch (node.type) {
  41. case "ClassExpression":
  42. case "FunctionExpression":
  43. case "ThisExpression":
  44. case "MemberExpression":
  45. case "CallExpression":
  46. case "NewExpression":
  47. case "ChainExpression":
  48. case "YieldExpression":
  49. case "TaggedTemplateExpression":
  50. case "MetaProperty":
  51. return true;
  52. case "Identifier":
  53. return node.name !== "undefined";
  54. case "AssignmentExpression":
  55. if (["=", "&&="].includes(node.operator)) {
  56. return isPossibleConstructor(node.right);
  57. }
  58. if (["||=", "??="].includes(node.operator)) {
  59. return (
  60. isPossibleConstructor(node.left) ||
  61. isPossibleConstructor(node.right)
  62. );
  63. }
  64. /**
  65. * All other assignment operators are mathematical assignment operators (arithmetic or bitwise).
  66. * An assignment expression with a mathematical operator can either evaluate to a primitive value,
  67. * or throw, depending on the operands. Thus, it cannot evaluate to a constructor function.
  68. */
  69. return false;
  70. case "LogicalExpression":
  71. /*
  72. * If the && operator short-circuits, the left side was falsy and therefore not a constructor, and if
  73. * it doesn't short-circuit, it takes the value from the right side, so the right side must always be a
  74. * possible constructor. A future improvement could verify that the left side could be truthy by
  75. * excluding falsy literals.
  76. */
  77. if (node.operator === "&&") {
  78. return isPossibleConstructor(node.right);
  79. }
  80. return (
  81. isPossibleConstructor(node.left) ||
  82. isPossibleConstructor(node.right)
  83. );
  84. case "ConditionalExpression":
  85. return (
  86. isPossibleConstructor(node.alternate) ||
  87. isPossibleConstructor(node.consequent)
  88. );
  89. case "SequenceExpression": {
  90. const lastExpression = node.expressions[node.expressions.length - 1];
  91. return isPossibleConstructor(lastExpression);
  92. }
  93. default:
  94. return false;
  95. }
  96. }
  97. //------------------------------------------------------------------------------
  98. // Rule Definition
  99. //------------------------------------------------------------------------------
  100. /** @type {import('../shared/types').Rule} */
  101. module.exports = {
  102. meta: {
  103. type: "problem",
  104. docs: {
  105. description: "Require `super()` calls in constructors",
  106. recommended: true,
  107. url: "https://eslint.org/docs/rules/constructor-super"
  108. },
  109. schema: [],
  110. messages: {
  111. missingSome: "Lacked a call of 'super()' in some code paths.",
  112. missingAll: "Expected to call 'super()'.",
  113. duplicate: "Unexpected duplicate 'super()'.",
  114. badSuper: "Unexpected 'super()' because 'super' is not a constructor.",
  115. unexpected: "Unexpected 'super()'."
  116. }
  117. },
  118. create(context) {
  119. /*
  120. * {{hasExtends: boolean, scope: Scope, codePath: CodePath}[]}
  121. * Information for each constructor.
  122. * - upper: Information of the upper constructor.
  123. * - hasExtends: A flag which shows whether own class has a valid `extends`
  124. * part.
  125. * - scope: The scope of own class.
  126. * - codePath: The code path object of the constructor.
  127. */
  128. let funcInfo = null;
  129. /*
  130. * {Map<string, {calledInSomePaths: boolean, calledInEveryPaths: boolean}>}
  131. * Information for each code path segment.
  132. * - calledInSomePaths: A flag of be called `super()` in some code paths.
  133. * - calledInEveryPaths: A flag of be called `super()` in all code paths.
  134. * - validNodes:
  135. */
  136. let segInfoMap = Object.create(null);
  137. /**
  138. * Gets the flag which shows `super()` is called in some paths.
  139. * @param {CodePathSegment} segment A code path segment to get.
  140. * @returns {boolean} The flag which shows `super()` is called in some paths
  141. */
  142. function isCalledInSomePath(segment) {
  143. return segment.reachable && segInfoMap[segment.id].calledInSomePaths;
  144. }
  145. /**
  146. * Gets the flag which shows `super()` is called in all paths.
  147. * @param {CodePathSegment} segment A code path segment to get.
  148. * @returns {boolean} The flag which shows `super()` is called in all paths.
  149. */
  150. function isCalledInEveryPath(segment) {
  151. /*
  152. * If specific segment is the looped segment of the current segment,
  153. * skip the segment.
  154. * If not skipped, this never becomes true after a loop.
  155. */
  156. if (segment.nextSegments.length === 1 &&
  157. segment.nextSegments[0].isLoopedPrevSegment(segment)
  158. ) {
  159. return true;
  160. }
  161. return segment.reachable && segInfoMap[segment.id].calledInEveryPaths;
  162. }
  163. return {
  164. /**
  165. * Stacks a constructor information.
  166. * @param {CodePath} codePath A code path which was started.
  167. * @param {ASTNode} node The current node.
  168. * @returns {void}
  169. */
  170. onCodePathStart(codePath, node) {
  171. if (isConstructorFunction(node)) {
  172. // Class > ClassBody > MethodDefinition > FunctionExpression
  173. const classNode = node.parent.parent.parent;
  174. const superClass = classNode.superClass;
  175. funcInfo = {
  176. upper: funcInfo,
  177. isConstructor: true,
  178. hasExtends: Boolean(superClass),
  179. superIsConstructor: isPossibleConstructor(superClass),
  180. codePath
  181. };
  182. } else {
  183. funcInfo = {
  184. upper: funcInfo,
  185. isConstructor: false,
  186. hasExtends: false,
  187. superIsConstructor: false,
  188. codePath
  189. };
  190. }
  191. },
  192. /**
  193. * Pops a constructor information.
  194. * And reports if `super()` lacked.
  195. * @param {CodePath} codePath A code path which was ended.
  196. * @param {ASTNode} node The current node.
  197. * @returns {void}
  198. */
  199. onCodePathEnd(codePath, node) {
  200. const hasExtends = funcInfo.hasExtends;
  201. // Pop.
  202. funcInfo = funcInfo.upper;
  203. if (!hasExtends) {
  204. return;
  205. }
  206. // Reports if `super()` lacked.
  207. const segments = codePath.returnedSegments;
  208. const calledInEveryPaths = segments.every(isCalledInEveryPath);
  209. const calledInSomePaths = segments.some(isCalledInSomePath);
  210. if (!calledInEveryPaths) {
  211. context.report({
  212. messageId: calledInSomePaths
  213. ? "missingSome"
  214. : "missingAll",
  215. node: node.parent
  216. });
  217. }
  218. },
  219. /**
  220. * Initialize information of a given code path segment.
  221. * @param {CodePathSegment} segment A code path segment to initialize.
  222. * @returns {void}
  223. */
  224. onCodePathSegmentStart(segment) {
  225. if (!(funcInfo && funcInfo.isConstructor && funcInfo.hasExtends)) {
  226. return;
  227. }
  228. // Initialize info.
  229. const info = segInfoMap[segment.id] = {
  230. calledInSomePaths: false,
  231. calledInEveryPaths: false,
  232. validNodes: []
  233. };
  234. // When there are previous segments, aggregates these.
  235. const prevSegments = segment.prevSegments;
  236. if (prevSegments.length > 0) {
  237. info.calledInSomePaths = prevSegments.some(isCalledInSomePath);
  238. info.calledInEveryPaths = prevSegments.every(isCalledInEveryPath);
  239. }
  240. },
  241. /**
  242. * Update information of the code path segment when a code path was
  243. * looped.
  244. * @param {CodePathSegment} fromSegment The code path segment of the
  245. * end of a loop.
  246. * @param {CodePathSegment} toSegment A code path segment of the head
  247. * of a loop.
  248. * @returns {void}
  249. */
  250. onCodePathSegmentLoop(fromSegment, toSegment) {
  251. if (!(funcInfo && funcInfo.isConstructor && funcInfo.hasExtends)) {
  252. return;
  253. }
  254. // Update information inside of the loop.
  255. const isRealLoop = toSegment.prevSegments.length >= 2;
  256. funcInfo.codePath.traverseSegments(
  257. { first: toSegment, last: fromSegment },
  258. segment => {
  259. const info = segInfoMap[segment.id];
  260. const prevSegments = segment.prevSegments;
  261. // Updates flags.
  262. info.calledInSomePaths = prevSegments.some(isCalledInSomePath);
  263. info.calledInEveryPaths = prevSegments.every(isCalledInEveryPath);
  264. // If flags become true anew, reports the valid nodes.
  265. if (info.calledInSomePaths || isRealLoop) {
  266. const nodes = info.validNodes;
  267. info.validNodes = [];
  268. for (let i = 0; i < nodes.length; ++i) {
  269. const node = nodes[i];
  270. context.report({
  271. messageId: "duplicate",
  272. node
  273. });
  274. }
  275. }
  276. }
  277. );
  278. },
  279. /**
  280. * Checks for a call of `super()`.
  281. * @param {ASTNode} node A CallExpression node to check.
  282. * @returns {void}
  283. */
  284. "CallExpression:exit"(node) {
  285. if (!(funcInfo && funcInfo.isConstructor)) {
  286. return;
  287. }
  288. // Skips except `super()`.
  289. if (node.callee.type !== "Super") {
  290. return;
  291. }
  292. // Reports if needed.
  293. if (funcInfo.hasExtends) {
  294. const segments = funcInfo.codePath.currentSegments;
  295. let duplicate = false;
  296. let info = null;
  297. for (let i = 0; i < segments.length; ++i) {
  298. const segment = segments[i];
  299. if (segment.reachable) {
  300. info = segInfoMap[segment.id];
  301. duplicate = duplicate || info.calledInSomePaths;
  302. info.calledInSomePaths = info.calledInEveryPaths = true;
  303. }
  304. }
  305. if (info) {
  306. if (duplicate) {
  307. context.report({
  308. messageId: "duplicate",
  309. node
  310. });
  311. } else if (!funcInfo.superIsConstructor) {
  312. context.report({
  313. messageId: "badSuper",
  314. node
  315. });
  316. } else {
  317. info.validNodes.push(node);
  318. }
  319. }
  320. } else if (funcInfo.codePath.currentSegments.some(isReachable)) {
  321. context.report({
  322. messageId: "unexpected",
  323. node
  324. });
  325. }
  326. },
  327. /**
  328. * Set the mark to the returned path as `super()` was called.
  329. * @param {ASTNode} node A ReturnStatement node to check.
  330. * @returns {void}
  331. */
  332. ReturnStatement(node) {
  333. if (!(funcInfo && funcInfo.isConstructor && funcInfo.hasExtends)) {
  334. return;
  335. }
  336. // Skips if no argument.
  337. if (!node.argument) {
  338. return;
  339. }
  340. // Returning argument is a substitute of 'super()'.
  341. const segments = funcInfo.codePath.currentSegments;
  342. for (let i = 0; i < segments.length; ++i) {
  343. const segment = segments[i];
  344. if (segment.reachable) {
  345. const info = segInfoMap[segment.id];
  346. info.calledInSomePaths = info.calledInEveryPaths = true;
  347. }
  348. }
  349. },
  350. /**
  351. * Resets state.
  352. * @returns {void}
  353. */
  354. "Program:exit"() {
  355. segInfoMap = Object.create(null);
  356. }
  357. };
  358. }
  359. };