StackTraceAnalyzer.spec.ts 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459
  1. import { ServiceIdentifiers } from '../../../src/container/ServiceIdentifiers';
  2. import * as estraverse from 'estraverse';
  3. import * as ESTree from 'estree';
  4. import { assert } from 'chai';
  5. import { TNodeWithBlockStatement } from '../../../src/types/node/TNodeWithBlockStatement';
  6. import { IInversifyContainerFacade } from '../../../src/interfaces/container/IInversifyContainerFacade';
  7. import { IStackTraceAnalyzer } from '../../../src/interfaces/stack-trace-analyzer/IStackTraceAnalyzer';
  8. import { IStackTraceData } from '../../../src/interfaces/stack-trace-analyzer/IStackTraceData';
  9. import { readFileAsString } from '../../helpers/readFileAsString';
  10. import { InversifyContainerFacade } from '../../../src/container/InversifyContainerFacade';
  11. import { Node } from '../../../src/node/Node';
  12. import { Nodes } from '../../../src/node/Nodes';
  13. import { NodeUtils } from '../../../src/node/NodeUtils';
  14. /**
  15. * @param astTree
  16. * @param name
  17. * @returns {ESTree.FunctionDeclaration|null}
  18. */
  19. function getFunctionDeclarationByName (astTree: ESTree.Node, name: string): ESTree.FunctionDeclaration|null {
  20. let functionDeclarationNode: ESTree.FunctionDeclaration|null = null;
  21. estraverse.traverse(astTree, {
  22. enter: (node: ESTree.Node): any => {
  23. if (
  24. Node.isFunctionDeclarationNode(node) &&
  25. Node.isIdentifierNode(node.id) &&
  26. node.id.name === name
  27. ) {
  28. functionDeclarationNode = node;
  29. return estraverse.VisitorOption.Break;
  30. }
  31. }
  32. });
  33. return functionDeclarationNode;
  34. }
  35. /**
  36. * @param astTree
  37. * @param name
  38. * @returns {ESTree.FunctionExpression|null}
  39. */
  40. function getFunctionExpressionByName (astTree: ESTree.Node, name: string): ESTree.FunctionExpression|null {
  41. let functionExpressionNode: ESTree.FunctionExpression|null = null;
  42. estraverse.traverse(astTree, {
  43. enter: (node: ESTree.Node): any => {
  44. if (
  45. Node.isVariableDeclaratorNode(node) &&
  46. node.init &&
  47. Node.isFunctionExpressionNode(node.init) &&
  48. Node.isIdentifierNode(node.id) &&
  49. node.id.name === name
  50. ) {
  51. functionExpressionNode = node.init;
  52. return estraverse.VisitorOption.Break;
  53. }
  54. }
  55. });
  56. return functionExpressionNode;
  57. }
  58. /**
  59. * @param astTree
  60. * @param id
  61. * @returns {ESTree.FunctionExpression|null}
  62. */
  63. function getFunctionExpressionById (astTree: ESTree.Node, id: string): ESTree.FunctionExpression|null {
  64. let functionExpressionNode: ESTree.FunctionExpression|null = null;
  65. estraverse.traverse(astTree, {
  66. enter: (node: ESTree.Node): any => {
  67. if (
  68. Node.isFunctionExpressionNode(node) &&
  69. node.id &&
  70. Node.isIdentifierNode(node.id) &&
  71. node.id.name === id
  72. ) {
  73. functionExpressionNode = node;
  74. return estraverse.VisitorOption.Break;
  75. }
  76. }
  77. });
  78. return functionExpressionNode;
  79. }
  80. /**
  81. * @param astTree
  82. * @param objectName
  83. * @param name
  84. * @returns {ESTree.FunctionExpression|null}
  85. */
  86. function getObjectFunctionExpressionByName (astTree: ESTree.Node, objectName: string, name: string|number): ESTree.FunctionExpression|null {
  87. let functionExpressionNode: ESTree.FunctionExpression|null = null,
  88. targetObjectExpressionNode: ESTree.ObjectExpression|null = null;
  89. estraverse.traverse(astTree, {
  90. enter: (node: ESTree.Node): any => {
  91. if (
  92. Node.isVariableDeclaratorNode(node) &&
  93. Node.isIdentifierNode(node.id) &&
  94. node.init &&
  95. Node.isObjectExpressionNode(node.init) &&
  96. node.id.name === objectName
  97. ) {
  98. targetObjectExpressionNode = node.init;
  99. return estraverse.VisitorOption.Break;
  100. }
  101. }
  102. });
  103. if (!targetObjectExpressionNode) {
  104. return null;
  105. }
  106. estraverse.traverse(targetObjectExpressionNode, {
  107. enter: (node: ESTree.Node): any => {
  108. if (
  109. Node.isPropertyNode(node) &&
  110. Node.isFunctionExpressionNode(node.value) &&
  111. (
  112. (Node.isIdentifierNode(node.key) && node.key.name === name) ||
  113. (Node.isLiteralNode(node.key) && node.key.value === name)
  114. )
  115. ) {
  116. functionExpressionNode = node.value;
  117. return estraverse.VisitorOption.Break;
  118. }
  119. }
  120. });
  121. return functionExpressionNode;
  122. }
  123. describe('StackTraceAnalyzer', () => {
  124. describe('extract (): IStackTraceData[]', () => {
  125. const inversifyContainerFacade: IInversifyContainerFacade = new InversifyContainerFacade({});
  126. const stackTraceAnalyzer: IStackTraceAnalyzer = inversifyContainerFacade
  127. .get<IStackTraceAnalyzer>(ServiceIdentifiers.IStackTraceAnalyzer);
  128. let astTree: TNodeWithBlockStatement,
  129. stackTraceData: IStackTraceData[],
  130. expectedStackTraceData: IStackTraceData[];
  131. it('should returns correct IStackTraceData - variant #1: basic-1', () => {
  132. astTree = Nodes.getProgramNode(
  133. NodeUtils.convertCodeToStructure(
  134. readFileAsString('./test/fixtures/stack-trace-analyzer/basic-1.js')
  135. )
  136. );
  137. expectedStackTraceData = [
  138. {
  139. name: 'baz',
  140. callee: (<ESTree.FunctionDeclaration>getFunctionDeclarationByName(astTree, 'baz')).body,
  141. stackTrace: []
  142. },
  143. {
  144. name: 'foo',
  145. callee: (<ESTree.FunctionDeclaration>getFunctionDeclarationByName(astTree, 'foo')).body,
  146. stackTrace: []
  147. },
  148. {
  149. name: 'bar',
  150. callee: (<ESTree.FunctionDeclaration>getFunctionDeclarationByName(astTree, 'bar')).body,
  151. stackTrace: [
  152. {
  153. name: 'inner2',
  154. callee: (<ESTree.FunctionDeclaration>getFunctionDeclarationByName(astTree, 'inner2')).body,
  155. stackTrace: [
  156. {
  157. name: 'inner3',
  158. callee: (<ESTree.FunctionExpression>getFunctionExpressionByName(astTree, 'inner3')).body,
  159. stackTrace: []
  160. },
  161. ]
  162. },
  163. {
  164. name: 'inner1',
  165. callee: (<ESTree.FunctionDeclaration>getFunctionDeclarationByName(astTree, 'inner1')).body,
  166. stackTrace: []
  167. },
  168. ]
  169. }
  170. ];
  171. stackTraceData = stackTraceAnalyzer.analyze(astTree.body);
  172. assert.deepEqual(stackTraceData, expectedStackTraceData);
  173. });
  174. it('should returns correct IStackTraceData - variant #2: basic-2', () => {
  175. astTree = Nodes.getProgramNode(
  176. NodeUtils.convertCodeToStructure(
  177. readFileAsString('./test/fixtures/stack-trace-analyzer/basic-2.js')
  178. )
  179. );
  180. expectedStackTraceData = [
  181. {
  182. name: 'bar',
  183. callee: (<ESTree.FunctionDeclaration>getFunctionDeclarationByName(astTree, 'bar')).body,
  184. stackTrace: []
  185. },
  186. {
  187. name: 'baz',
  188. callee: (<ESTree.FunctionDeclaration>getFunctionDeclarationByName(astTree, 'baz')).body,
  189. stackTrace: [
  190. {
  191. name: 'inner1',
  192. callee: (<ESTree.FunctionDeclaration>getFunctionDeclarationByName(astTree, 'inner1')).body,
  193. stackTrace: []
  194. },
  195. ]
  196. },
  197. {
  198. name: 'foo',
  199. callee: (<ESTree.FunctionDeclaration>getFunctionDeclarationByName(astTree, 'foo')).body,
  200. stackTrace: []
  201. }
  202. ];
  203. stackTraceData = stackTraceAnalyzer.analyze(astTree.body);
  204. assert.deepEqual(stackTraceData, expectedStackTraceData);
  205. });
  206. it('should returns correct IStackTraceData - variant #3: deep conditions nesting', () => {
  207. astTree = Nodes.getProgramNode(
  208. NodeUtils.convertCodeToStructure(
  209. readFileAsString('./test/fixtures/stack-trace-analyzer/deep-conditions-nesting.js')
  210. )
  211. );
  212. expectedStackTraceData = [
  213. {
  214. name: 'bar',
  215. callee: (<ESTree.FunctionDeclaration>getFunctionDeclarationByName(astTree, 'bar')).body,
  216. stackTrace: []
  217. },
  218. {
  219. name: 'baz',
  220. callee: (<ESTree.FunctionDeclaration>getFunctionDeclarationByName(astTree, 'baz')).body,
  221. stackTrace: [
  222. {
  223. name: 'inner1',
  224. callee: (<ESTree.FunctionDeclaration>getFunctionDeclarationByName(astTree, 'inner1')).body,
  225. stackTrace: []
  226. },
  227. ]
  228. },
  229. {
  230. name: 'foo',
  231. callee: (<ESTree.FunctionDeclaration>getFunctionDeclarationByName(astTree, 'foo')).body,
  232. stackTrace: []
  233. }
  234. ];
  235. stackTraceData = stackTraceAnalyzer.analyze(astTree.body);
  236. assert.deepEqual(stackTraceData, expectedStackTraceData);
  237. });
  238. it('should returns correct IStackTraceData - variant #4: call before declaration', () => {
  239. astTree = Nodes.getProgramNode(
  240. NodeUtils.convertCodeToStructure(
  241. readFileAsString('./test/fixtures/stack-trace-analyzer/call-before-declaration.js')
  242. )
  243. );
  244. expectedStackTraceData = [
  245. {
  246. name: 'bar',
  247. callee: (<ESTree.FunctionDeclaration>getFunctionDeclarationByName(astTree, 'bar')).body,
  248. stackTrace: []
  249. }
  250. ];
  251. stackTraceData = stackTraceAnalyzer.analyze(astTree.body);
  252. assert.deepEqual(stackTraceData, expectedStackTraceData);
  253. });
  254. it('should returns correct IStackTraceData - variant #5: call expression of object member #1', () => {
  255. astTree = Nodes.getProgramNode(
  256. NodeUtils.convertCodeToStructure(
  257. readFileAsString('./test/fixtures/stack-trace-analyzer/call-expression-of-object-member-1.js')
  258. )
  259. );
  260. expectedStackTraceData = [
  261. {
  262. name: 'baz',
  263. callee: (<ESTree.FunctionExpression>getObjectFunctionExpressionByName(astTree, 'object1', 'baz')).body,
  264. stackTrace: []
  265. },
  266. {
  267. name: 'baz',
  268. callee: (<ESTree.FunctionExpression>getObjectFunctionExpressionByName(astTree, 'object1', 'baz')).body,
  269. stackTrace: []
  270. },
  271. {
  272. name: 'func',
  273. callee: (<ESTree.FunctionExpression>getObjectFunctionExpressionByName(astTree, 'object1', 'func')).body,
  274. stackTrace: []
  275. },
  276. {
  277. name: 'bar',
  278. callee: (<ESTree.FunctionExpression>getObjectFunctionExpressionByName(astTree, 'object1', 'bar')).body,
  279. stackTrace: [
  280. {
  281. name: 'inner1',
  282. callee: (<ESTree.FunctionDeclaration>getFunctionDeclarationByName(astTree, 'inner1')).body,
  283. stackTrace: [
  284. ]
  285. },
  286. ]
  287. },
  288. {
  289. name: 'bar',
  290. callee: (<ESTree.FunctionExpression>getObjectFunctionExpressionByName(astTree, 'object', 'bar')).body,
  291. stackTrace: [
  292. {
  293. name: 'inner',
  294. callee: (<ESTree.FunctionDeclaration>getFunctionDeclarationByName(astTree, 'inner')).body,
  295. stackTrace: [
  296. ]
  297. },
  298. ]
  299. }
  300. ];
  301. stackTraceData = stackTraceAnalyzer.analyze(astTree.body);
  302. assert.deepEqual(stackTraceData, expectedStackTraceData);
  303. });
  304. it('should returns correct IStackTraceData - variant #5: call expression of object member #2', () => {
  305. astTree = Nodes.getProgramNode(
  306. NodeUtils.convertCodeToStructure(
  307. readFileAsString('./test/fixtures/stack-trace-analyzer/call-expression-of-object-member-2.js')
  308. )
  309. );
  310. expectedStackTraceData = [
  311. {
  312. name: 'baz',
  313. callee: (<ESTree.FunctionExpression>getObjectFunctionExpressionByName(astTree, 'object', 'baz')).body,
  314. stackTrace: []
  315. },
  316. {
  317. name: 1,
  318. callee: (<ESTree.FunctionExpression>getObjectFunctionExpressionByName(astTree, 'object1', 1)).body,
  319. stackTrace: []
  320. },
  321. ];
  322. stackTraceData = stackTraceAnalyzer.analyze(astTree.body);
  323. assert.deepEqual(stackTraceData, expectedStackTraceData);
  324. });
  325. it('should returns correct IStackTraceData - variant #6: no call expressions', () => {
  326. astTree = Nodes.getProgramNode(
  327. NodeUtils.convertCodeToStructure(
  328. readFileAsString('./test/fixtures/stack-trace-analyzer/no-call-expressions.js')
  329. )
  330. );
  331. expectedStackTraceData = [];
  332. stackTraceData = stackTraceAnalyzer.analyze(astTree.body);
  333. assert.deepEqual(stackTraceData, expectedStackTraceData);
  334. });
  335. it('should returns correct IStackTraceData - variant #7: only call expression', () => {
  336. astTree = Nodes.getProgramNode(
  337. NodeUtils.convertCodeToStructure(
  338. readFileAsString('./test/fixtures/stack-trace-analyzer/only-call-expression.js')
  339. )
  340. );
  341. expectedStackTraceData = [];
  342. stackTraceData = stackTraceAnalyzer.analyze(astTree.body);
  343. assert.deepEqual(stackTraceData, expectedStackTraceData);
  344. });
  345. it('should returns correct IStackTraceData - variant #8: self-invoking functions', () => {
  346. astTree = Nodes.getProgramNode(
  347. NodeUtils.convertCodeToStructure(
  348. readFileAsString('./test/fixtures/stack-trace-analyzer/self-invoking-functions.js')
  349. )
  350. );
  351. expectedStackTraceData = [
  352. {
  353. name: null,
  354. callee: (<ESTree.FunctionExpression>getFunctionExpressionById(astTree, 'foo')).body,
  355. stackTrace: [{
  356. name: null,
  357. callee: (<ESTree.FunctionExpression>getFunctionExpressionById(astTree, 'bar')).body,
  358. stackTrace: [{
  359. name: null,
  360. callee: (<ESTree.FunctionExpression>getFunctionExpressionById(astTree, 'baz')).body,
  361. stackTrace: [{
  362. name: 'inner',
  363. callee: (<ESTree.FunctionDeclaration>getFunctionDeclarationByName(astTree, 'inner')).body,
  364. stackTrace: []
  365. }]
  366. }]
  367. }]
  368. }
  369. ];
  370. stackTraceData = stackTraceAnalyzer.analyze(astTree.body);
  371. assert.deepEqual(stackTraceData, expectedStackTraceData);
  372. });
  373. it('should returns correct IStackTraceData - variant #9: no recursion', () => {
  374. astTree = Nodes.getProgramNode(
  375. NodeUtils.convertCodeToStructure(
  376. readFileAsString('./test/fixtures/stack-trace-analyzer/no-recursion.js')
  377. )
  378. );
  379. expectedStackTraceData = [
  380. {
  381. name: 'bar',
  382. callee: (<ESTree.FunctionExpression>getFunctionExpressionByName(astTree, 'bar')).body,
  383. stackTrace: []
  384. }
  385. ];
  386. stackTraceData = stackTraceAnalyzer.analyze(astTree.body);
  387. assert.deepEqual(stackTraceData, expectedStackTraceData);
  388. });
  389. });
  390. });