no-this-before-super.js 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365
  1. /**
  2. * @fileoverview A rule to disallow using `this`/`super` before `super()`.
  3. * @author Toru Nagashima
  4. */
  5. "use strict";
  6. //------------------------------------------------------------------------------
  7. // Requirements
  8. //------------------------------------------------------------------------------
  9. const astUtils = require("./utils/ast-utils");
  10. //------------------------------------------------------------------------------
  11. // Helpers
  12. //------------------------------------------------------------------------------
  13. /**
  14. * Checks whether or not a given node is a constructor.
  15. * @param {ASTNode} node A node to check. This node type is one of
  16. * `Program`, `FunctionDeclaration`, `FunctionExpression`, and
  17. * `ArrowFunctionExpression`.
  18. * @returns {boolean} `true` if the node is a constructor.
  19. */
  20. function isConstructorFunction(node) {
  21. return (
  22. node.type === "FunctionExpression" &&
  23. node.parent.type === "MethodDefinition" &&
  24. node.parent.kind === "constructor"
  25. );
  26. }
  27. /*
  28. * Information for each code path segment.
  29. * - superCalled: The flag which shows `super()` called in all code paths.
  30. * - invalidNodes: The array of invalid ThisExpression and Super nodes.
  31. */
  32. /**
  33. *
  34. */
  35. class SegmentInfo {
  36. /**
  37. * Indicates whether `super()` is called in all code paths.
  38. * @type {boolean}
  39. */
  40. superCalled = false;
  41. /**
  42. * The array of invalid ThisExpression and Super nodes.
  43. * @type {ASTNode[]}
  44. */
  45. invalidNodes = [];
  46. }
  47. //------------------------------------------------------------------------------
  48. // Rule Definition
  49. //------------------------------------------------------------------------------
  50. /** @type {import('../types').Rule.RuleModule} */
  51. module.exports = {
  52. meta: {
  53. type: "problem",
  54. docs: {
  55. description:
  56. "Disallow `this`/`super` before calling `super()` in constructors",
  57. recommended: true,
  58. url: "https://eslint.org/docs/latest/rules/no-this-before-super",
  59. },
  60. schema: [],
  61. messages: {
  62. noBeforeSuper: "'{{kind}}' is not allowed before 'super()'.",
  63. },
  64. },
  65. create(context) {
  66. /*
  67. * Information for each constructor.
  68. * - upper: Information of the upper constructor.
  69. * - hasExtends: A flag which shows whether the owner class has a valid
  70. * `extends` part.
  71. * - scope: The scope of the owner class.
  72. * - codePath: The code path of this constructor.
  73. */
  74. let funcInfo = null;
  75. /** @type {Record<string, SegmentInfo>} */
  76. let segInfoMap = Object.create(null);
  77. /**
  78. * Gets whether or not `super()` is called in a given code path segment.
  79. * @param {CodePathSegment} segment A code path segment to get.
  80. * @returns {boolean} `true` if `super()` is called.
  81. */
  82. function isCalled(segment) {
  83. return !segment.reachable || segInfoMap[segment.id]?.superCalled;
  84. }
  85. /**
  86. * Checks whether or not this is in a constructor.
  87. * @returns {boolean} `true` if this is in a constructor.
  88. */
  89. function isInConstructorOfDerivedClass() {
  90. return Boolean(
  91. funcInfo && funcInfo.isConstructor && funcInfo.hasExtends,
  92. );
  93. }
  94. /**
  95. * Determines if every segment in a set has been called.
  96. * @param {Set<CodePathSegment>} segments The segments to search.
  97. * @returns {boolean} True if every segment has been called; false otherwise.
  98. */
  99. function isEverySegmentCalled(segments) {
  100. for (const segment of segments) {
  101. if (!isCalled(segment)) {
  102. return false;
  103. }
  104. }
  105. return true;
  106. }
  107. /**
  108. * Checks whether or not this is before `super()` is called.
  109. * @returns {boolean} `true` if this is before `super()` is called.
  110. */
  111. function isBeforeCallOfSuper() {
  112. return (
  113. isInConstructorOfDerivedClass() &&
  114. !isEverySegmentCalled(funcInfo.currentSegments)
  115. );
  116. }
  117. /**
  118. * Sets a given node as invalid.
  119. * @param {ASTNode} node A node to set as invalid. This is one of
  120. * a ThisExpression and a Super.
  121. * @returns {void}
  122. */
  123. function setInvalid(node) {
  124. const segments = funcInfo.currentSegments;
  125. for (const segment of segments) {
  126. if (segment.reachable) {
  127. segInfoMap[segment.id].invalidNodes.push(node);
  128. }
  129. }
  130. }
  131. /**
  132. * Sets the current segment as `super` was called.
  133. * @returns {void}
  134. */
  135. function setSuperCalled() {
  136. const segments = funcInfo.currentSegments;
  137. for (const segment of segments) {
  138. if (segment.reachable) {
  139. segInfoMap[segment.id].superCalled = true;
  140. }
  141. }
  142. }
  143. return {
  144. /**
  145. * Adds information of a constructor into the stack.
  146. * @param {CodePath} codePath A code path which was started.
  147. * @param {ASTNode} node The current node.
  148. * @returns {void}
  149. */
  150. onCodePathStart(codePath, node) {
  151. if (isConstructorFunction(node)) {
  152. // Class > ClassBody > MethodDefinition > FunctionExpression
  153. const classNode = node.parent.parent.parent;
  154. funcInfo = {
  155. upper: funcInfo,
  156. isConstructor: true,
  157. hasExtends: Boolean(
  158. classNode.superClass &&
  159. !astUtils.isNullOrUndefined(
  160. classNode.superClass,
  161. ),
  162. ),
  163. codePath,
  164. currentSegments: new Set(),
  165. };
  166. } else {
  167. funcInfo = {
  168. upper: funcInfo,
  169. isConstructor: false,
  170. hasExtends: false,
  171. codePath,
  172. currentSegments: new Set(),
  173. };
  174. }
  175. },
  176. /**
  177. * Removes the top of stack item.
  178. *
  179. * And this traverses all segments of this code path then reports every
  180. * invalid node.
  181. * @param {CodePath} codePath A code path which was ended.
  182. * @returns {void}
  183. */
  184. onCodePathEnd(codePath) {
  185. const isDerivedClass = funcInfo.hasExtends;
  186. funcInfo = funcInfo.upper;
  187. if (!isDerivedClass) {
  188. return;
  189. }
  190. /**
  191. * A collection of nodes to avoid duplicate reports.
  192. * @type {Set<ASTNode>}
  193. */
  194. const reported = new Set();
  195. codePath.traverseSegments((segment, controller) => {
  196. const info = segInfoMap[segment.id];
  197. const invalidNodes = info.invalidNodes.filter(
  198. /*
  199. * Avoid duplicate reports.
  200. * When there is a `finally`, invalidNodes may contain already reported node.
  201. */
  202. node => !reported.has(node),
  203. );
  204. for (const invalidNode of invalidNodes) {
  205. reported.add(invalidNode);
  206. context.report({
  207. messageId: "noBeforeSuper",
  208. node: invalidNode,
  209. data: {
  210. kind:
  211. invalidNode.type === "Super"
  212. ? "super"
  213. : "this",
  214. },
  215. });
  216. }
  217. if (info.superCalled) {
  218. controller.skip();
  219. }
  220. });
  221. },
  222. /**
  223. * Initialize information of a given code path segment.
  224. * @param {CodePathSegment} segment A code path segment to initialize.
  225. * @returns {void}
  226. */
  227. onCodePathSegmentStart(segment) {
  228. funcInfo.currentSegments.add(segment);
  229. if (!isInConstructorOfDerivedClass()) {
  230. return;
  231. }
  232. // Initialize info.
  233. segInfoMap[segment.id] = {
  234. superCalled:
  235. segment.prevSegments.length > 0 &&
  236. segment.prevSegments.every(isCalled),
  237. invalidNodes: [],
  238. };
  239. },
  240. onUnreachableCodePathSegmentStart(segment) {
  241. funcInfo.currentSegments.add(segment);
  242. },
  243. onUnreachableCodePathSegmentEnd(segment) {
  244. funcInfo.currentSegments.delete(segment);
  245. },
  246. onCodePathSegmentEnd(segment) {
  247. funcInfo.currentSegments.delete(segment);
  248. },
  249. /**
  250. * Update information of the code path segment when a code path was
  251. * looped.
  252. * @param {CodePathSegment} fromSegment The code path segment of the
  253. * end of a loop.
  254. * @param {CodePathSegment} toSegment A code path segment of the head
  255. * of a loop.
  256. * @returns {void}
  257. */
  258. onCodePathSegmentLoop(fromSegment, toSegment) {
  259. if (!isInConstructorOfDerivedClass()) {
  260. return;
  261. }
  262. // Update information inside of the loop.
  263. funcInfo.codePath.traverseSegments(
  264. { first: toSegment, last: fromSegment },
  265. (segment, controller) => {
  266. const info =
  267. segInfoMap[segment.id] ?? new SegmentInfo();
  268. if (info.superCalled) {
  269. controller.skip();
  270. } else if (
  271. segment.prevSegments.length > 0 &&
  272. segment.prevSegments.every(isCalled)
  273. ) {
  274. info.superCalled = true;
  275. }
  276. segInfoMap[segment.id] = info;
  277. },
  278. );
  279. },
  280. /**
  281. * Reports if this is before `super()`.
  282. * @param {ASTNode} node A target node.
  283. * @returns {void}
  284. */
  285. ThisExpression(node) {
  286. if (isBeforeCallOfSuper()) {
  287. setInvalid(node);
  288. }
  289. },
  290. /**
  291. * Reports if this is before `super()`.
  292. * @param {ASTNode} node A target node.
  293. * @returns {void}
  294. */
  295. Super(node) {
  296. if (!astUtils.isCallee(node) && isBeforeCallOfSuper()) {
  297. setInvalid(node);
  298. }
  299. },
  300. /**
  301. * Marks `super()` called.
  302. * @param {ASTNode} node A target node.
  303. * @returns {void}
  304. */
  305. "CallExpression:exit"(node) {
  306. if (node.callee.type === "Super" && isBeforeCallOfSuper()) {
  307. setSuperCalled();
  308. }
  309. },
  310. /**
  311. * Resets state.
  312. * @returns {void}
  313. */
  314. "Program:exit"() {
  315. segInfoMap = Object.create(null);
  316. },
  317. };
  318. },
  319. };