no-this-before-super.js 12 KB

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