no-this-before-super.js 10.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304
  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. // Rule Definition
  29. //------------------------------------------------------------------------------
  30. /** @type {import('../shared/types').Rule} */
  31. module.exports = {
  32. meta: {
  33. type: "problem",
  34. docs: {
  35. description: "Disallow `this`/`super` before calling `super()` in constructors",
  36. recommended: true,
  37. url: "https://eslint.org/docs/rules/no-this-before-super"
  38. },
  39. schema: [],
  40. messages: {
  41. noBeforeSuper: "'{{kind}}' is not allowed before 'super()'."
  42. }
  43. },
  44. create(context) {
  45. /*
  46. * Information for each constructor.
  47. * - upper: Information of the upper constructor.
  48. * - hasExtends: A flag which shows whether the owner class has a valid
  49. * `extends` part.
  50. * - scope: The scope of the owner class.
  51. * - codePath: The code path of this constructor.
  52. */
  53. let funcInfo = null;
  54. /*
  55. * Information for each code path segment.
  56. * Each key is the id of a code path segment.
  57. * Each value is an object:
  58. * - superCalled: The flag which shows `super()` called in all code paths.
  59. * - invalidNodes: The array of invalid ThisExpression and Super nodes.
  60. */
  61. let segInfoMap = Object.create(null);
  62. /**
  63. * Gets whether or not `super()` is called in a given code path segment.
  64. * @param {CodePathSegment} segment A code path segment to get.
  65. * @returns {boolean} `true` if `super()` is called.
  66. */
  67. function isCalled(segment) {
  68. return !segment.reachable || segInfoMap[segment.id].superCalled;
  69. }
  70. /**
  71. * Checks whether or not this is in a constructor.
  72. * @returns {boolean} `true` if this is in a constructor.
  73. */
  74. function isInConstructorOfDerivedClass() {
  75. return Boolean(funcInfo && funcInfo.isConstructor && funcInfo.hasExtends);
  76. }
  77. /**
  78. * Checks whether or not this is before `super()` is called.
  79. * @returns {boolean} `true` if this is before `super()` is called.
  80. */
  81. function isBeforeCallOfSuper() {
  82. return (
  83. isInConstructorOfDerivedClass() &&
  84. !funcInfo.codePath.currentSegments.every(isCalled)
  85. );
  86. }
  87. /**
  88. * Sets a given node as invalid.
  89. * @param {ASTNode} node A node to set as invalid. This is one of
  90. * a ThisExpression and a Super.
  91. * @returns {void}
  92. */
  93. function setInvalid(node) {
  94. const segments = funcInfo.codePath.currentSegments;
  95. for (let i = 0; i < segments.length; ++i) {
  96. const segment = segments[i];
  97. if (segment.reachable) {
  98. segInfoMap[segment.id].invalidNodes.push(node);
  99. }
  100. }
  101. }
  102. /**
  103. * Sets the current segment as `super` was called.
  104. * @returns {void}
  105. */
  106. function setSuperCalled() {
  107. const segments = funcInfo.codePath.currentSegments;
  108. for (let i = 0; i < segments.length; ++i) {
  109. const segment = segments[i];
  110. if (segment.reachable) {
  111. segInfoMap[segment.id].superCalled = true;
  112. }
  113. }
  114. }
  115. return {
  116. /**
  117. * Adds information of a constructor into the stack.
  118. * @param {CodePath} codePath A code path which was started.
  119. * @param {ASTNode} node The current node.
  120. * @returns {void}
  121. */
  122. onCodePathStart(codePath, node) {
  123. if (isConstructorFunction(node)) {
  124. // Class > ClassBody > MethodDefinition > FunctionExpression
  125. const classNode = node.parent.parent.parent;
  126. funcInfo = {
  127. upper: funcInfo,
  128. isConstructor: true,
  129. hasExtends: Boolean(
  130. classNode.superClass &&
  131. !astUtils.isNullOrUndefined(classNode.superClass)
  132. ),
  133. codePath
  134. };
  135. } else {
  136. funcInfo = {
  137. upper: funcInfo,
  138. isConstructor: false,
  139. hasExtends: false,
  140. codePath
  141. };
  142. }
  143. },
  144. /**
  145. * Removes the top of stack item.
  146. *
  147. * And this traverses all segments of this code path then reports every
  148. * invalid node.
  149. * @param {CodePath} codePath A code path which was ended.
  150. * @returns {void}
  151. */
  152. onCodePathEnd(codePath) {
  153. const isDerivedClass = funcInfo.hasExtends;
  154. funcInfo = funcInfo.upper;
  155. if (!isDerivedClass) {
  156. return;
  157. }
  158. codePath.traverseSegments((segment, controller) => {
  159. const info = segInfoMap[segment.id];
  160. for (let i = 0; i < info.invalidNodes.length; ++i) {
  161. const invalidNode = info.invalidNodes[i];
  162. context.report({
  163. messageId: "noBeforeSuper",
  164. node: invalidNode,
  165. data: {
  166. kind: invalidNode.type === "Super" ? "super" : "this"
  167. }
  168. });
  169. }
  170. if (info.superCalled) {
  171. controller.skip();
  172. }
  173. });
  174. },
  175. /**
  176. * Initialize information of a given code path segment.
  177. * @param {CodePathSegment} segment A code path segment to initialize.
  178. * @returns {void}
  179. */
  180. onCodePathSegmentStart(segment) {
  181. if (!isInConstructorOfDerivedClass()) {
  182. return;
  183. }
  184. // Initialize info.
  185. segInfoMap[segment.id] = {
  186. superCalled: (
  187. segment.prevSegments.length > 0 &&
  188. segment.prevSegments.every(isCalled)
  189. ),
  190. invalidNodes: []
  191. };
  192. },
  193. /**
  194. * Update information of the code path segment when a code path was
  195. * looped.
  196. * @param {CodePathSegment} fromSegment The code path segment of the
  197. * end of a loop.
  198. * @param {CodePathSegment} toSegment A code path segment of the head
  199. * of a loop.
  200. * @returns {void}
  201. */
  202. onCodePathSegmentLoop(fromSegment, toSegment) {
  203. if (!isInConstructorOfDerivedClass()) {
  204. return;
  205. }
  206. // Update information inside of the loop.
  207. funcInfo.codePath.traverseSegments(
  208. { first: toSegment, last: fromSegment },
  209. (segment, controller) => {
  210. const info = segInfoMap[segment.id];
  211. if (info.superCalled) {
  212. info.invalidNodes = [];
  213. controller.skip();
  214. } else if (
  215. segment.prevSegments.length > 0 &&
  216. segment.prevSegments.every(isCalled)
  217. ) {
  218. info.superCalled = true;
  219. info.invalidNodes = [];
  220. }
  221. }
  222. );
  223. },
  224. /**
  225. * Reports if this is before `super()`.
  226. * @param {ASTNode} node A target node.
  227. * @returns {void}
  228. */
  229. ThisExpression(node) {
  230. if (isBeforeCallOfSuper()) {
  231. setInvalid(node);
  232. }
  233. },
  234. /**
  235. * Reports if this is before `super()`.
  236. * @param {ASTNode} node A target node.
  237. * @returns {void}
  238. */
  239. Super(node) {
  240. if (!astUtils.isCallee(node) && isBeforeCallOfSuper()) {
  241. setInvalid(node);
  242. }
  243. },
  244. /**
  245. * Marks `super()` called.
  246. * @param {ASTNode} node A target node.
  247. * @returns {void}
  248. */
  249. "CallExpression:exit"(node) {
  250. if (node.callee.type === "Super" && isBeforeCallOfSuper()) {
  251. setSuperCalled();
  252. }
  253. },
  254. /**
  255. * Resets state.
  256. * @returns {void}
  257. */
  258. "Program:exit"() {
  259. segInfoMap = Object.create(null);
  260. }
  261. };
  262. }
  263. };