Ohm-Management - Projektarbeit B-ME
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

constructor-super.js 13KB

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