diff --git a/src/node-transformers/control-flow-transformers/BlockStatementControlFlowTransformer.ts b/src/node-transformers/control-flow-transformers/BlockStatementControlFlowTransformer.ts index 54a9356c..9a09ca26 100644 --- a/src/node-transformers/control-flow-transformers/BlockStatementControlFlowTransformer.ts +++ b/src/node-transformers/control-flow-transformers/BlockStatementControlFlowTransformer.ts @@ -77,6 +77,10 @@ export class BlockStatementControlFlowTransformer extends AbstractNodeTransforme * @returns {boolean} */ private static canTransformBlockStatementNode(blockStatementNode: ESTree.BlockStatement): boolean { + if (blockStatementNode.body.length <= 4) { + return false; + } + let canTransform: boolean = true; estraverse.traverse(blockStatementNode, { @@ -91,10 +95,6 @@ export class BlockStatementControlFlowTransformer extends AbstractNodeTransforme } }); - if (blockStatementNode.body.length <= 4) { - canTransform = false; - } - return canTransform; } @@ -138,8 +138,11 @@ export class BlockStatementControlFlowTransformer extends AbstractNodeTransforme const blockStatementBody: ESTree.Statement[] = blockStatementNode.body; const originalKeys: number[] = this.arrayUtils.createWithRange(blockStatementBody.length); const shuffledKeys: number[] = this.arrayUtils.shuffle(originalKeys); - const originalKeysIndexesInShuffledArray: number[] = originalKeys.map((key: number) => - shuffledKeys.indexOf(key) + const shuffledKeyToIndex: Map = new Map( + shuffledKeys.map((key: number, index: number) => [key, index]) + ); + const originalKeysIndexesInShuffledArray: number[] = originalKeys.map( + (key: number) => shuffledKeyToIndex.get(key)! ); const blockStatementControlFlowFlatteningCustomNode: ICustomNode< TInitialData diff --git a/src/node-transformers/control-flow-transformers/control-flow-replacers/AbstractControlFlowReplacer.ts b/src/node-transformers/control-flow-transformers/control-flow-replacers/AbstractControlFlowReplacer.ts index e26e2fee..f19f20be 100644 --- a/src/node-transformers/control-flow-transformers/control-flow-replacers/AbstractControlFlowReplacer.ts +++ b/src/node-transformers/control-flow-transformers/control-flow-replacers/AbstractControlFlowReplacer.ts @@ -68,10 +68,10 @@ export abstract class AbstractControlFlowReplacer implements IControlFlowReplace * @returns {string} */ public generateStorageKey(controlFlowStorage: IControlFlowStorage): string { - const key: string = this.randomGenerator.getRandomString(5); + let key: string = this.randomGenerator.getRandomString(5); - if (controlFlowStorage.has(key)) { - return this.generateStorageKey(controlFlowStorage); + while (controlFlowStorage.has(key)) { + key = this.randomGenerator.getRandomString(5); } return key; @@ -91,9 +91,21 @@ export abstract class AbstractControlFlowReplacer implements IControlFlowReplace usingExistingIdentifierChance: number ): string { const controlFlowStorageId: string = controlFlowStorage.getStorageId(); - const storageKeysById: Map = - this.replacerDataByControlFlowStorageId.get(controlFlowStorageId) ?? new Map(); - const storageKeysForCurrentId: string[] = storageKeysById.get(replacerId) ?? []; + + let storageKeysById: Map | undefined = + this.replacerDataByControlFlowStorageId.get(controlFlowStorageId); + + if (!storageKeysById) { + storageKeysById = new Map(); + this.replacerDataByControlFlowStorageId.set(controlFlowStorageId, storageKeysById); + } + + let storageKeysForCurrentId: string[] | undefined = storageKeysById.get(replacerId); + + if (!storageKeysForCurrentId) { + storageKeysForCurrentId = []; + storageKeysById.set(replacerId, storageKeysForCurrentId); + } const shouldPickFromStorageKeysById = this.randomGenerator.getMathRandom() < usingExistingIdentifierChance && storageKeysForCurrentId.length; @@ -105,8 +117,6 @@ export abstract class AbstractControlFlowReplacer implements IControlFlowReplace const storageKey: string = this.generateStorageKey(controlFlowStorage); storageKeysForCurrentId.push(storageKey); - storageKeysById.set(replacerId, storageKeysForCurrentId); - this.replacerDataByControlFlowStorageId.set(controlFlowStorageId, storageKeysById); controlFlowStorage.set(storageKey, customNode); return storageKey; diff --git a/src/node-transformers/converting-transformers/ClassFieldTransformer.ts b/src/node-transformers/converting-transformers/ClassFieldTransformer.ts index a8ef3ac5..f19ca4e9 100644 --- a/src/node-transformers/converting-transformers/ClassFieldTransformer.ts +++ b/src/node-transformers/converting-transformers/ClassFieldTransformer.ts @@ -31,9 +31,9 @@ import { NodeGuards } from '../../node/NodeGuards'; @injectable() export class ClassFieldTransformer extends AbstractNodeTransformer { /** - * @type {string[]} + * @type {string} */ - private static readonly ignoredNames: string[] = ['constructor']; + private static readonly ignoredName: string = 'constructor'; /** * @param {IRandomGenerator} randomGenerator @@ -98,7 +98,7 @@ export class ClassFieldTransformer extends AbstractNodeTransformer { classFieldNode: ESTree.MethodDefinition | ESTree.PropertyDefinition, keyNode: ESTree.Identifier ): ESTree.MethodDefinition | ESTree.PropertyDefinition { - if (!ClassFieldTransformer.ignoredNames.includes(keyNode.name) && !classFieldNode.computed) { + if (keyNode.name !== ClassFieldTransformer.ignoredName && !classFieldNode.computed) { classFieldNode.computed = true; classFieldNode.key = NodeFactory.literalNode(keyNode.name); } @@ -117,7 +117,7 @@ export class ClassFieldTransformer extends AbstractNodeTransformer { ): ESTree.MethodDefinition | ESTree.PropertyDefinition { if ( typeof keyNode.value === 'string' && - !ClassFieldTransformer.ignoredNames.includes(keyNode.value) && + keyNode.value !== ClassFieldTransformer.ignoredName && !classFieldNode.computed ) { classFieldNode.computed = true; diff --git a/src/node-transformers/converting-transformers/ObjectExpressionKeysTransformer.ts b/src/node-transformers/converting-transformers/ObjectExpressionKeysTransformer.ts index 15ac0ae2..e0addf21 100644 --- a/src/node-transformers/converting-transformers/ObjectExpressionKeysTransformer.ts +++ b/src/node-transformers/converting-transformers/ObjectExpressionKeysTransformer.ts @@ -53,6 +53,61 @@ export class ObjectExpressionKeysTransformer extends AbstractNodeTransformer { this.objectExpressionExtractorFactory = objectExpressionExtractorFactory; } + /** + * Combined prohibition check result + */ + private static checkProhibitedPatterns( + objectExpressionNode: ESTree.ObjectExpression, + objectExpressionHostNode: ESTree.Node + ): { hasReferencedIdentifier: boolean; hasCallExpression: boolean } { + const identifierNamesSet: Set = new Set(); + + let hasReferencedIdentifier: boolean = false; + let hasCallExpression: boolean = false; + let isInsideObjectExpression: boolean = false; + + estraverse.traverse(objectExpressionHostNode, { + // eslint-disable-next-line complexity + enter: (node: ESTree.Node): void | estraverse.VisitorOption => { + if (node === objectExpressionNode) { + isInsideObjectExpression = true; + } + + if (isInsideObjectExpression && !hasCallExpression) { + if (NodeGuards.isCallExpressionNode(node) || NodeGuards.isNewExpressionNode(node)) { + hasCallExpression = true; + } + } + + if (NodeGuards.isIdentifierNode(node) || NodeGuards.isThisExpressionNode(node)) { + const identifierName: string = NodeGuards.isIdentifierNode(node) + ? node.name + : ObjectExpressionKeysTransformer.thisIdentifierName; + + if (!isInsideObjectExpression) { + identifierNamesSet.add(identifierName); + } else if (identifierNamesSet.has(identifierName)) { + hasReferencedIdentifier = true; + } + } + + if (hasReferencedIdentifier && hasCallExpression) { + return estraverse.VisitorOption.Break; + } + }, + leave: (node: ESTree.Node): void | estraverse.VisitorOption => { + if (node === objectExpressionNode) { + isInsideObjectExpression = false; + if (hasReferencedIdentifier || hasCallExpression) { + return estraverse.VisitorOption.Break; + } + } + } + }); + + return { hasReferencedIdentifier, hasCallExpression }; + } + /** * @param {ObjectExpression} objectExpressionNode * @param {Node} objectExpressionParentNode @@ -64,86 +119,25 @@ export class ObjectExpressionKeysTransformer extends AbstractNodeTransformer { objectExpressionParentNode: ESTree.Node, objectExpressionHostStatement: ESTree.Statement ): boolean { - return ( - ObjectExpressionKeysTransformer.isReferencedIdentifierName( - objectExpressionNode, - objectExpressionHostStatement - ) || + if ( ObjectExpressionKeysTransformer.isProhibitedArrowFunctionExpression( objectExpressionNode, objectExpressionParentNode ) || - ObjectExpressionKeysTransformer.isObjectExpressionWithCallExpression(objectExpressionNode) || ObjectExpressionKeysTransformer.isProhibitedSequenceExpression( objectExpressionNode, objectExpressionHostStatement ) - ); - } - - /** - * @param {Identifier | ThisExpression} node - * @returns {string} - */ - private static getReferencedIdentifierName(node: ESTree.Identifier | ESTree.ThisExpression): string { - if (NodeGuards.isIdentifierNode(node)) { - return node.name; - } else { - return ObjectExpressionKeysTransformer.thisIdentifierName; + ) { + return true; } - } - /** - * @param {ObjectExpression} objectExpressionNode - * @param {Node} objectExpressionHostNode - * @returns {boolean} - */ - private static isReferencedIdentifierName( - objectExpressionNode: ESTree.ObjectExpression, - objectExpressionHostNode: ESTree.Node - ): boolean { - const identifierNamesSet: Set = new Set(); + const { hasReferencedIdentifier, hasCallExpression } = ObjectExpressionKeysTransformer.checkProhibitedPatterns( + objectExpressionNode, + objectExpressionHostStatement + ); - let isReferencedIdentifierName: boolean = false; - let isCurrentNode: boolean = false; - - // should mark node as prohibited if identifier of node is referenced somewhere inside other nodes - estraverse.traverse(objectExpressionHostNode, { - enter: (node: ESTree.Node): void | estraverse.VisitorOption => { - if (node === objectExpressionNode) { - isCurrentNode = true; - } - - if (!NodeGuards.isIdentifierNode(node) && !NodeGuards.isThisExpressionNode(node)) { - return; - } - - if (!isCurrentNode) { - identifierNamesSet.add(ObjectExpressionKeysTransformer.getReferencedIdentifierName(node)); - - return; - } - - const hasReferencedIdentifierName: boolean = identifierNamesSet.has( - ObjectExpressionKeysTransformer.getReferencedIdentifierName(node) - ); - - if (hasReferencedIdentifierName) { - isReferencedIdentifierName = true; - - return estraverse.VisitorOption.Break; - } - }, - leave: (node: ESTree.Node): void | estraverse.VisitorOption => { - if (node === objectExpressionNode) { - isCurrentNode = false; - - return estraverse.VisitorOption.Break; - } - } - }); - - return isReferencedIdentifierName; + return hasReferencedIdentifier || hasCallExpression; } /** @@ -161,31 +155,6 @@ export class ObjectExpressionKeysTransformer extends AbstractNodeTransformer { ); } - /** - * @param {ObjectExpression} objectExpressionNode - * @returns {boolean} - */ - private static isObjectExpressionWithCallExpression(objectExpressionNode: ESTree.ObjectExpression): boolean { - let isCallExpressionLikeNodeFound: boolean = false; - - estraverse.traverse(objectExpressionNode, { - enter: (node: ESTree.Node): void | estraverse.VisitorOption => { - const isCallExpressionLikeNode = - NodeGuards.isCallExpressionNode(node) || NodeGuards.isNewExpressionNode(node); - - if (!isCallExpressionLikeNode) { - return; - } - - isCallExpressionLikeNodeFound = true; - - return estraverse.VisitorOption.Break; - } - }); - - return isCallExpressionLikeNodeFound; - } - /** * @param {ObjectExpression} objectExpressionNode * @param {Node} objectExpressionHostNode @@ -263,32 +232,28 @@ export class ObjectExpressionKeysTransformer extends AbstractNodeTransformer { return objectExpressionNode; } - return this.applyObjectExpressionKeysExtractorsRecursive( - ObjectExpressionKeysTransformer.objectExpressionExtractorNames, - objectExpressionNode, - hostStatement - ); + return this.applyObjectExpressionKeysExtractorsRecursive(objectExpressionNode, hostStatement, 0); } /** - * @param {ObjectExpressionExtractor[]} objectExpressionExtractorNames * @param {ObjectExpression} objectExpressionNode * @param {Statement} hostStatement + * @param {number} extractorIndex * @returns {Node} */ private applyObjectExpressionKeysExtractorsRecursive( - objectExpressionExtractorNames: ObjectExpressionExtractor[], objectExpressionNode: ESTree.ObjectExpression, - hostStatement: ESTree.Statement + hostStatement: ESTree.Statement, + extractorIndex: number ): ESTree.Node { - const newObjectExpressionExtractorNames: ObjectExpressionExtractor[] = [...objectExpressionExtractorNames]; - const objectExpressionExtractor: ObjectExpressionExtractor | undefined = - newObjectExpressionExtractorNames.shift(); + const objectExpressionExtractorNames = ObjectExpressionKeysTransformer.objectExpressionExtractorNames; - if (!objectExpressionExtractor) { + if (extractorIndex >= objectExpressionExtractorNames.length) { return objectExpressionNode; } + const objectExpressionExtractor: ObjectExpressionExtractor = objectExpressionExtractorNames[extractorIndex]; + const { nodeToReplace, objectExpressionHostStatement: newObjectExpressionHostStatement, @@ -299,9 +264,9 @@ export class ObjectExpressionKeysTransformer extends AbstractNodeTransformer { ); this.applyObjectExpressionKeysExtractorsRecursive( - newObjectExpressionExtractorNames, newObjectExpressionNode, - newObjectExpressionHostStatement + newObjectExpressionHostStatement, + extractorIndex + 1 ); return nodeToReplace; diff --git a/src/node-transformers/converting-transformers/SplitStringTransformer.ts b/src/node-transformers/converting-transformers/SplitStringTransformer.ts index 2481cd35..3ab4de01 100644 --- a/src/node-transformers/converting-transformers/SplitStringTransformer.ts +++ b/src/node-transformers/converting-transformers/SplitStringTransformer.ts @@ -152,26 +152,25 @@ export class SplitStringTransformer extends AbstractNodeTransformer { * @returns {BinaryExpression} */ private transformStringChunksToBinaryExpressionNode(chunks: string[]): ESTree.BinaryExpression { - const firstChunk: string | undefined = chunks.shift(); - const secondChunk: string | undefined = chunks.shift(); + const chunksLength: number = chunks.length; - if (!firstChunk || !secondChunk) { + if (chunksLength < 2) { throw new Error('First and second chunks values should not be empty'); } const initialBinaryExpressionNode: ESTree.BinaryExpression = NodeFactory.binaryExpressionNode( '+', - NodeFactory.literalNode(firstChunk), - NodeFactory.literalNode(secondChunk) + NodeFactory.literalNode(chunks[0]), + NodeFactory.literalNode(chunks[1]) ); - return chunks.reduce( - (binaryExpressionNode: ESTree.BinaryExpression, chunk: string) => { - const chunkLiteralNode: ESTree.Literal = NodeFactory.literalNode(chunk); + let result: ESTree.BinaryExpression = initialBinaryExpressionNode; - return NodeFactory.binaryExpressionNode('+', binaryExpressionNode, chunkLiteralNode); - }, - initialBinaryExpressionNode - ); + // Start from index 2 since we already used 0 and 1 + for (let i: number = 2; i < chunksLength; i++) { + result = NodeFactory.binaryExpressionNode('+', result, NodeFactory.literalNode(chunks[i])); + } + + return result; } } diff --git a/src/node-transformers/converting-transformers/TemplateLiteralTransformer.ts b/src/node-transformers/converting-transformers/TemplateLiteralTransformer.ts index ae684a59..9de8392a 100644 --- a/src/node-transformers/converting-transformers/TemplateLiteralTransformer.ts +++ b/src/node-transformers/converting-transformers/TemplateLiteralTransformer.ts @@ -83,53 +83,60 @@ export class TemplateLiteralTransformer extends AbstractNodeTransformer { ): ESTree.Expression { const templateLiteralExpressions: ESTree.Expression[] = templateLiteralNode.expressions; - let nodes: ESTree.Expression[] = []; + const nodes: ESTree.Expression[] = []; + + const quasis: ESTree.TemplateElement[] = templateLiteralNode.quasis; + const quasisLength: number = quasis.length; + + for (let i: number = 0; i < quasisLength; i++) { + const templateElement: ESTree.TemplateElement = quasis[i]; - templateLiteralNode.quasis.forEach((templateElement: ESTree.TemplateElement) => { if (templateElement.value.cooked === undefined || templateElement.value.cooked === null) { - return; + continue; } nodes.push(NodeFactory.literalNode(templateElement.value.cooked)); - const expression: ESTree.Expression | undefined = templateLiteralExpressions.shift(); + const expression: ESTree.Expression | undefined = templateLiteralExpressions[i]; if (!expression) { - return; + continue; } nodes.push(expression); - }); + } - nodes = nodes.filter((node: ESTree.Literal | ESTree.Expression) => { + const filteredNodes: ESTree.Expression[] = nodes.filter((node: ESTree.Literal | ESTree.Expression) => { return !(NodeGuards.isLiteralNode(node) && node.value === ''); }); // since `+` is left-to-right associative // ensure the first node is a string if first/second isn't if ( - !TemplateLiteralTransformer.isLiteralNodeWithStringValue(nodes[0]) && - !TemplateLiteralTransformer.isLiteralNodeWithStringValue(nodes[1]) + !TemplateLiteralTransformer.isLiteralNodeWithStringValue(filteredNodes[0]) && + !TemplateLiteralTransformer.isLiteralNodeWithStringValue(filteredNodes[1]) ) { - nodes.unshift(NodeFactory.literalNode('')); + filteredNodes.unshift(NodeFactory.literalNode('')); } let transformedNode: ESTree.Node; - if (nodes.length > 1) { + if (filteredNodes.length > 1) { let root: ESTree.BinaryExpression = NodeFactory.binaryExpressionNode( '+', - nodes.shift(), - nodes.shift() + filteredNodes[0], + filteredNodes[1] ); - nodes.forEach((node: ESTree.Literal | ESTree.Expression) => { - root = NodeFactory.binaryExpressionNode('+', root, node); - }); + // Start from index 2 since we already used 0 and 1 + const filteredNodesLength: number = filteredNodes.length; + for (let i: number = 2; i < filteredNodesLength; i++) { + root = NodeFactory.binaryExpressionNode('+', root, filteredNodes[i]); + } transformedNode = root; } else { - transformedNode = nodes[0]; + transformedNode = filteredNodes[0]; } NodeUtils.parentizeAst(transformedNode); diff --git a/src/node-transformers/converting-transformers/object-expression-extractors/BasePropertiesExtractor.ts b/src/node-transformers/converting-transformers/object-expression-extractors/BasePropertiesExtractor.ts index 4eea6ae2..b4c90440 100644 --- a/src/node-transformers/converting-transformers/object-expression-extractors/BasePropertiesExtractor.ts +++ b/src/node-transformers/converting-transformers/object-expression-extractors/BasePropertiesExtractor.ts @@ -216,8 +216,10 @@ export class BasePropertiesExtractor implements IObjectExpressionExtractor { objectExpressionNode: ESTree.ObjectExpression, removablePropertyIds: number[] ): void { + const removablePropertyIdsSet: Set = new Set(removablePropertyIds); + objectExpressionNode.properties = objectExpressionNode.properties.filter( - (property: ESTree.Property | ESTree.SpreadElement, index: number) => !removablePropertyIds.includes(index) + (property: ESTree.Property | ESTree.SpreadElement, index: number) => !removablePropertyIdsSet.has(index) ); } } diff --git a/src/node-transformers/finalizing-transformers/DirectivePlacementTransformer.ts b/src/node-transformers/finalizing-transformers/DirectivePlacementTransformer.ts index 63f41477..8d25c0f9 100644 --- a/src/node-transformers/finalizing-transformers/DirectivePlacementTransformer.ts +++ b/src/node-transformers/finalizing-transformers/DirectivePlacementTransformer.ts @@ -127,21 +127,9 @@ export class DirectivePlacementTransformer extends AbstractNodeTransformer { // append new directive node at the top of lexical scope statements NodeAppender.prepend(nodeWithLexicalScopeStatements, [newDirectiveNode]); - // remove found directive node - let isDirectiveNodeRemoved: boolean = false; - estraverse.replace(nodeWithLexicalScopeStatements, { - enter: (node: ESTree.Node): estraverse.VisitorOption | undefined => { - if (isDirectiveNodeRemoved) { - return estraverse.VisitorOption.Break; - } - - if (node === directiveNode) { - isDirectiveNodeRemoved = true; - - return estraverse.VisitorOption.Remove; - } - } - }); + nodeWithLexicalScopeStatements.body = nodeWithLexicalScopeStatements.body.filter( + (node) => node !== directiveNode + ); } return nodeWithLexicalScopeStatements; diff --git a/src/node-transformers/string-array-transformers/StringArrayScopeCallsWrapperTransformer.ts b/src/node-transformers/string-array-transformers/StringArrayScopeCallsWrapperTransformer.ts index 68a7eb3e..5aa19210 100644 --- a/src/node-transformers/string-array-transformers/StringArrayScopeCallsWrapperTransformer.ts +++ b/src/node-transformers/string-array-transformers/StringArrayScopeCallsWrapperTransformer.ts @@ -140,15 +140,16 @@ export class StringArrayScopeCallsWrapperTransformer extends AbstractNodeTransfo const { scopeCallsWrappersData } = stringArrayScopeCallsWrappersData; const scopeCallsWrappersDataLength: number = scopeCallsWrappersData.length; + const upperStringArrayCallsWrapperData = this.getUpperStringArrayCallsWrapperData( + stringArrayScopeCallsWrappersData + ); + /** * Iterates over each name of scope wrapper name * Reverse iteration appends wrappers at index `0` at the correct order */ for (let i = scopeCallsWrappersDataLength - 1; i >= 0; i--) { const stringArrayScopeCallsWrapperData = scopeCallsWrappersData[i]; - const upperStringArrayCallsWrapperData = this.getUpperStringArrayCallsWrapperData( - stringArrayScopeCallsWrappersData - ); this.getAndAppendStringArrayScopeCallsWrapperNode( lexicalScopeBodyNode, diff --git a/src/node-transformers/string-array-transformers/StringArrayTransformer.ts b/src/node-transformers/string-array-transformers/StringArrayTransformer.ts index 6c80f573..6993f1f9 100644 --- a/src/node-transformers/string-array-transformers/StringArrayTransformer.ts +++ b/src/node-transformers/string-array-transformers/StringArrayTransformer.ts @@ -328,18 +328,24 @@ export class StringArrayTransformer extends AbstractNodeTransformer { const nextScopeCallsWrapperParameterIndexesData: IStringArrayScopeCallsWrapperParameterIndexesData | null = this.getStringArrayCallsWrapperParameterIndexesData(); - stringArrayScopeCallsWrappersDataByEncoding[encoding] = { - encoding, - scopeCallsWrappersData: [ - ...stringArrayScopeCallsWrappersData, - { - name: nextScopeCallsWrapperName, - index: nextScopeCallsWrapperShiftedIndex, - parameterIndexesData: nextScopeCallsWrapperParameterIndexesData - } - ] + const newWrapperData: IStringArrayScopeCallsWrapperData = { + name: nextScopeCallsWrapperName, + index: nextScopeCallsWrapperShiftedIndex, + parameterIndexesData: nextScopeCallsWrapperParameterIndexesData }; + let encodingData = stringArrayScopeCallsWrappersDataByEncoding[encoding]; + + if (!encodingData) { + encodingData = { + encoding, + scopeCallsWrappersData: [newWrapperData] + }; + stringArrayScopeCallsWrappersDataByEncoding[encoding] = encodingData; + } else { + encodingData.scopeCallsWrappersData.push(newWrapperData); + } + this.stringArrayScopeCallsWrappersDataStorage.set( currentLexicalScopeBodyNode, stringArrayScopeCallsWrappersDataByEncoding diff --git a/src/storages/string-array-transformers/StringArrayStorage.ts b/src/storages/string-array-transformers/StringArrayStorage.ts index ebdd0407..d58c8586 100644 --- a/src/storages/string-array-transformers/StringArrayStorage.ts +++ b/src/storages/string-array-transformers/StringArrayStorage.ts @@ -78,9 +78,9 @@ export class StringArrayStorage private readonly rc4Keys: string[]; /** - * @type {Map} + * @type {Map>} */ - private readonly rc4EncodedValuesSourcesCache: Map = new Map(); + private readonly rc4EncodedValuesSourcesCache: Map> = new Map(); /** * @type {number} @@ -224,19 +224,13 @@ export class StringArrayStorage this.storage = new Map( this.arrayUtils .shuffle(Array.from(this.storage.entries())) - .map<[`${string}-${TStringArrayEncoding}`, IStringArrayStorageItemData]>( - ([value, stringArrayStorageItemData], index: number) => { - stringArrayStorageItemData.index = index; + .map< + [`${string}-${TStringArrayEncoding}`, IStringArrayStorageItemData] + >(([value, stringArrayStorageItemData], index: number) => { + stringArrayStorageItemData.index = index; - return [value, stringArrayStorageItemData]; - } - ) - .sort( - ( - [, stringArrayStorageItemDataA]: [string, IStringArrayStorageItemData], - [, stringArrayStorageItemDataB]: [string, IStringArrayStorageItemData] - ) => stringArrayStorageItemDataA.index - stringArrayStorageItemDataB.index - ) + return [value, stringArrayStorageItemData]; + }) ); } @@ -296,29 +290,36 @@ export class StringArrayStorage * if collision will happen, just try to encode value again */ case StringArrayEncoding.Rc4: { - const decodeKey: string = this.randomGenerator.getRandomGenerator().pickone(this.rc4Keys); - const encodedValue: string = this.cryptUtilsStringArray.btoa( - this.cryptUtilsStringArray.rc4(value, decodeKey) - ); + const maxRetryAttempts: number = 50; - const encodedValueSources: string[] = this.rc4EncodedValuesSourcesCache.get(encodedValue) ?? []; - let encodedValueSourcesLength: number = encodedValueSources.length; + for (let attempt: number = 0; attempt < maxRetryAttempts; attempt++) { + const decodeKey: string = this.randomGenerator.getRandomGenerator().pickone(this.rc4Keys); + const encodedValue: string = this.cryptUtilsStringArray.btoa( + this.cryptUtilsStringArray.rc4(value, decodeKey) + ); - const shouldAddValueToSourcesCache: boolean = - !encodedValueSourcesLength || !encodedValueSources.includes(value); + const encodedValueSources: Set = + this.rc4EncodedValuesSourcesCache.get(encodedValue) ?? new Set(); - if (shouldAddValueToSourcesCache) { - encodedValueSources.push(value); - encodedValueSourcesLength++; + const shouldAddValueToSourcesCache: boolean = + encodedValueSources.size === 0 || !encodedValueSources.has(value); + + if (shouldAddValueToSourcesCache) { + encodedValueSources.add(value); + } + + this.rc4EncodedValuesSourcesCache.set(encodedValue, encodedValueSources); + + if (encodedValueSources.size <= 1) { + return { encodedValue, encoding, decodeKey }; + } } - this.rc4EncodedValuesSourcesCache.set(encodedValue, encodedValueSources); - - if (encodedValueSourcesLength > 1) { - return this.getEncodedValue(value); - } - - return { encodedValue, encoding, decodeKey }; + return { + encodedValue: this.cryptUtilsStringArray.btoa(value), + encoding: StringArrayEncoding.Base64, + decodeKey: null + }; } case StringArrayEncoding.Base64: { diff --git a/src/utils/ArrayUtils.ts b/src/utils/ArrayUtils.ts index 718ee229..4c1c2557 100644 --- a/src/utils/ArrayUtils.ts +++ b/src/utils/ArrayUtils.ts @@ -103,7 +103,9 @@ export class ArrayUtils implements IArrayUtils { * @returns {T[]} */ public rotate(array: T[], times: number): T[] { - if (!array.length) { + const arrayLength: number = array.length; + + if (!arrayLength) { throw new ReferenceError('Cannot rotate empty array.'); } @@ -111,19 +113,16 @@ export class ArrayUtils implements IArrayUtils { return array; } - const newArray: T[] = array; + // Normalize rotation amount to avoid unnecessary full rotations + // O(N) algorithm using slice instead of O(N*R) with pop/unshift + const normalizedTimes: number = times % arrayLength; - let temp: T | undefined; - - while (times--) { - temp = newArray.pop(); - - if (temp) { - newArray.unshift(temp); - } + if (normalizedTimes === 0) { + return [...array]; } - return newArray; + // Right rotation: take last `normalizedTimes` elements and put them at the front + return [...array.slice(-normalizedTimes), ...array.slice(0, -normalizedTimes)]; } /** diff --git a/src/utils/EscapeSequenceEncoder.ts b/src/utils/EscapeSequenceEncoder.ts index 6db012ab..75511d7c 100644 --- a/src/utils/EscapeSequenceEncoder.ts +++ b/src/utils/EscapeSequenceEncoder.ts @@ -19,6 +19,11 @@ export class EscapeSequenceEncoder implements IEscapeSequenceEncoder { */ private static readonly forceEscapeCharactersRegExp: RegExp = /[\x00-\x1F\x7F-\x9F'"\\\s]/; + /** + * @type {RegExp} + */ + private static readonly replaceRegExp: RegExp = /[\s\S]/g; + /** * @type {Map} */ @@ -37,12 +42,11 @@ export class EscapeSequenceEncoder implements IEscapeSequenceEncoder { } const radix: number = 16; - const replaceRegExp: RegExp = new RegExp('[\\s\\S]', 'g'); let prefix: string; let template: string; - const result: string = string.replace(replaceRegExp, (character: string): string => { + const result: string = string.replace(EscapeSequenceEncoder.replaceRegExp, (character: string): string => { const shouldEncodeCharacter: boolean = encodeAllSymbols || EscapeSequenceEncoder.forceEscapeCharactersRegExp.test(character);