SplitStringTransformer.ts 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  1. import { inject, injectable, } from 'inversify';
  2. import { ServiceIdentifiers } from '../../container/ServiceIdentifiers';
  3. import * as estraverse from 'estraverse';
  4. import * as ESTree from 'estree';
  5. import * as stringz from 'stringz';
  6. import { IOptions } from '../../interfaces/options/IOptions';
  7. import { IRandomGenerator } from '../../interfaces/utils/IRandomGenerator';
  8. import { IVisitor } from '../../interfaces/node-transformers/IVisitor';
  9. import { NodeTransformer } from '../../enums/node-transformers/NodeTransformer';
  10. import { NodeTransformationStage } from '../../enums/node-transformers/NodeTransformationStage';
  11. import { AbstractNodeTransformer } from '../AbstractNodeTransformer';
  12. import { NodeFactory } from '../../node/NodeFactory';
  13. import { NodeGuards } from '../../node/NodeGuards';
  14. import { NodeLiteralUtils } from '../../node/NodeLiteralUtils';
  15. import { NodeUtils } from '../../node/NodeUtils';
  16. /**
  17. * Splits strings into parts
  18. */
  19. @injectable()
  20. export class SplitStringTransformer extends AbstractNodeTransformer {
  21. /**
  22. * @type {number}
  23. */
  24. private static readonly firstPassChunkLength: number = 1000;
  25. /**
  26. * @type {NodeTransformer[]}
  27. */
  28. public runAfter: NodeTransformer[] = [
  29. NodeTransformer.ObjectExpressionKeysTransformer,
  30. NodeTransformer.TemplateLiteralTransformer
  31. ];
  32. /**
  33. * @param {IRandomGenerator} randomGenerator
  34. * @param {IOptions} options
  35. */
  36. public constructor (
  37. @inject(ServiceIdentifiers.IRandomGenerator) randomGenerator: IRandomGenerator,
  38. @inject(ServiceIdentifiers.IOptions) options: IOptions
  39. ) {
  40. super(randomGenerator, options);
  41. }
  42. /**
  43. * @param {string} string
  44. * @param {number} stringLength
  45. * @param {number} chunkSize
  46. * @returns {string[]}
  47. */
  48. private static chunkString (
  49. string: string,
  50. stringLength: number,
  51. chunkSize: number
  52. ): string[] {
  53. const chunksCount: number = Math.ceil(stringLength / chunkSize);
  54. const chunks: string[] = [];
  55. let nextChunkStartIndex: number = 0;
  56. for (
  57. let chunkIndex: number = 0;
  58. chunkIndex < chunksCount;
  59. ++chunkIndex, nextChunkStartIndex += chunkSize
  60. ) {
  61. chunks[chunkIndex] = stringz.substr(string, nextChunkStartIndex, chunkSize);
  62. }
  63. return chunks;
  64. }
  65. /**
  66. * @param {NodeTransformationStage} nodeTransformationStage
  67. * @returns {IVisitor | null}
  68. */
  69. public getVisitor (nodeTransformationStage: NodeTransformationStage): IVisitor | null {
  70. switch (nodeTransformationStage) {
  71. case NodeTransformationStage.Converting:
  72. return {
  73. enter: (node: ESTree.Node, parentNode: ESTree.Node | null): ESTree.Node | undefined => {
  74. if (!this.options.splitStrings) {
  75. return;
  76. }
  77. if (parentNode && NodeGuards.isLiteralNode(node)) {
  78. return this.transformNode(node, parentNode);
  79. }
  80. }
  81. };
  82. default:
  83. return null;
  84. }
  85. }
  86. /**
  87. * Needs to split string on chunks of length `splitStringsChunkLength` in two pass, because of
  88. * `Maximum call stack size exceeded` error in `esrecurse` package
  89. *
  90. * @param {Literal} literalNode
  91. * @param {Node} parentNode
  92. * @returns {Node}
  93. */
  94. public transformNode (literalNode: ESTree.Literal, parentNode: ESTree.Node): ESTree.Node {
  95. if (NodeLiteralUtils.isProhibitedLiteralNode(literalNode, parentNode)) {
  96. return literalNode;
  97. }
  98. // pass #1: split string on a large chunks with length of `firstPassChunkLength`
  99. const firstPassChunksNode: ESTree.Node = this.transformLiteralNodeByChunkLength(
  100. literalNode,
  101. parentNode,
  102. SplitStringTransformer.firstPassChunkLength
  103. );
  104. // pass #2: split large chunks on a chunks with length of `splitStringsChunkLength`
  105. const secondPassChunksNode: ESTree.Node = estraverse.replace(firstPassChunksNode, {
  106. // eslint-disable-next-line @typescript-eslint/no-shadow
  107. enter: (node: ESTree.Node, parentNode: ESTree.Node | null) => {
  108. if (parentNode && NodeGuards.isLiteralNode(node)) {
  109. return this.transformLiteralNodeByChunkLength(
  110. node,
  111. parentNode,
  112. this.options.splitStringsChunkLength
  113. );
  114. }
  115. }
  116. });
  117. return secondPassChunksNode;
  118. }
  119. /**
  120. * @param {Literal} literalNode
  121. * @param {Node} parentNode
  122. * @param {number} chunkLength
  123. * @returns {Node}
  124. */
  125. private transformLiteralNodeByChunkLength (
  126. literalNode: ESTree.Literal,
  127. parentNode: ESTree.Node,
  128. chunkLength: number
  129. ): ESTree.Node {
  130. if (!NodeLiteralUtils.isStringLiteralNode(literalNode)) {
  131. return literalNode;
  132. }
  133. const valueLength: number = stringz.length(literalNode.value);
  134. if (chunkLength >= valueLength) {
  135. return literalNode;
  136. }
  137. const stringChunks: string[] = SplitStringTransformer.chunkString(
  138. literalNode.value,
  139. valueLength,
  140. chunkLength
  141. );
  142. const binaryExpressionNode: ESTree.BinaryExpression =
  143. this.transformStringChunksToBinaryExpressionNode(stringChunks);
  144. NodeUtils.parentizeAst(binaryExpressionNode);
  145. NodeUtils.parentizeNode(binaryExpressionNode, parentNode);
  146. return binaryExpressionNode;
  147. }
  148. /**
  149. * @param {string[]} chunks
  150. * @returns {BinaryExpression}
  151. */
  152. private transformStringChunksToBinaryExpressionNode (chunks: string[]): ESTree.BinaryExpression {
  153. const firstChunk: string | undefined = chunks.shift();
  154. const secondChunk: string | undefined = chunks.shift();
  155. if (!firstChunk || !secondChunk) {
  156. throw new Error('First and second chunks values should not be empty');
  157. }
  158. const initialBinaryExpressionNode: ESTree.BinaryExpression = NodeFactory.binaryExpressionNode(
  159. '+',
  160. NodeFactory.literalNode(firstChunk),
  161. NodeFactory.literalNode(secondChunk)
  162. );
  163. return chunks.reduce<ESTree.BinaryExpression>(
  164. (binaryExpressionNode: ESTree.BinaryExpression, chunk: string) => {
  165. const chunkLiteralNode: ESTree.Literal = NodeFactory.literalNode(chunk);
  166. return NodeFactory.binaryExpressionNode(
  167. '+',
  168. binaryExpressionNode,
  169. chunkLiteralNode
  170. );
  171. },
  172. initialBinaryExpressionNode
  173. );
  174. }
  175. }