Ver Fonte

Merge branch 'dev' into yarn

Conflicts:
	dist/index.js
	package.json
	scripts/test-coveralls
	scripts/test-full
	scripts/test-mocha
sanex3339 há 8 anos atrás
pai
commit
abd3bfbbf2
51 ficheiros alterados com 1122 adições e 365 exclusões
  1. 13 0
      CHANGELOG.md
  2. 10 8
      README.md
  3. 230 167
      dist/index.js
  4. 7 7
      package.json
  5. 1 0
      scripts/test-coveralls
  6. 1 0
      scripts/test-full
  7. 1 0
      scripts/test-mocha
  8. 80 50
      src/Obfuscator.ts
  9. 13 20
      src/container/modules/node-transformers/NodeTransformersModule.ts
  10. 1 2
      src/declarations/ESTree.d.ts
  11. 2 0
      src/enums/NodeType.ts
  12. 6 0
      src/interfaces/node-transformers/INodeTransformer.d.ts
  13. 1 1
      src/interfaces/storages/IStorage.d.ts
  14. 6 0
      src/node-transformers/AbstractNodeTransformer.ts
  15. 16 1
      src/node-transformers/control-flow-transformers/BlockStatementControlFlowTransformer.ts
  16. 15 1
      src/node-transformers/control-flow-transformers/FunctionControlFlowTransformer.ts
  17. 16 1
      src/node-transformers/converting-transformers/MemberExpressionTransformer.ts
  18. 14 0
      src/node-transformers/converting-transformers/MethodDefinitionTransformer.ts
  19. 14 0
      src/node-transformers/converting-transformers/TemplateLiteralTransformer.ts
  20. 16 2
      src/node-transformers/obfuscating-transformers/CatchClauseTransformer.ts
  21. 13 0
      src/node-transformers/obfuscating-transformers/FunctionDeclarationTransformer.ts
  22. 36 4
      src/node-transformers/obfuscating-transformers/FunctionTransformer.ts
  23. 15 1
      src/node-transformers/obfuscating-transformers/LabeledStatementTransformer.ts
  24. 14 0
      src/node-transformers/obfuscating-transformers/LiteralTransformer.ts
  25. 16 1
      src/node-transformers/obfuscating-transformers/ObjectExpressionTransformer.ts
  26. 20 3
      src/node-transformers/obfuscating-transformers/VariableDeclarationTransformer.ts
  27. 3 3
      src/node-transformers/obfuscating-transformers/replacers/StringLiteralReplacer.ts
  28. 41 0
      src/node/Node.ts
  29. 1 1
      src/node/NodeUtils.ts
  30. 27 27
      src/node/Nodes.ts
  31. 22 9
      src/storages/ArrayStorage.ts
  32. 7 2
      src/storages/MapStorage.ts
  33. 4 0
      src/types/TVisitorFunction.d.ts
  34. 1 1
      src/types/container/TNodeTransformersFactory.d.ts
  35. 0 16
      src/utils/Utils.ts
  36. 2 2
      test/functional-tests/node-transformers/control-flow-transformers/control-flow-replacers/binary-expression-control-flow-replacer/BinaryExpressionControlFlowReplacer.spec.ts
  37. 2 2
      test/functional-tests/node-transformers/control-flow-transformers/control-flow-replacers/call-expression-control-flow-replacer/CallExpressionControlFlowReplacer.spec.ts
  38. 2 2
      test/functional-tests/node-transformers/control-flow-transformers/control-flow-replacers/logical-expression-control-flow-replacer/LogicalExpressionControlFlowReplacer.spec.ts
  39. 120 0
      test/functional-tests/node-transformers/obfuscating-transformers/function-transformer/FunctionTransformer.spec.ts
  40. 5 0
      test/functional-tests/node-transformers/obfuscating-transformers/function-transformer/fixtures/array-pattern-as-parameter.js
  41. 5 0
      test/functional-tests/node-transformers/obfuscating-transformers/function-transformer/fixtures/assignment-pattern-as-parameter-1.js
  42. 6 0
      test/functional-tests/node-transformers/obfuscating-transformers/function-transformer/fixtures/assignment-pattern-as-parameter-2.js
  43. 6 0
      test/functional-tests/node-transformers/obfuscating-transformers/function-transformer/fixtures/assignment-pattern-as-parameter-3.js
  44. 30 2
      test/functional-tests/node-transformers/obfuscating-transformers/variable-declaration-transformer/VariableDeclarationTransformer.spec.ts
  45. 5 0
      test/functional-tests/node-transformers/obfuscating-transformers/variable-declaration-transformer/fixtures/array-pattern.js
  46. 1 0
      test/functional-tests/node-transformers/obfuscating-transformers/variable-declaration-transformer/fixtures/object-pattern.js
  47. 2 0
      test/index.spec.ts
  48. 126 0
      test/unit-tests/storages/ArrayStorage.spec.ts
  49. 126 0
      test/unit-tests/storages/MapStorage.spec.ts
  50. 0 28
      test/unit-tests/utils/utils/Utils.spec.ts
  51. 1 1
      webpack.config.js

+ 13 - 0
CHANGELOG.md

@@ -1,5 +1,18 @@
 Change Log
 ===
+v0.9.0-beta.2
+---
+* Transformers refactoring
+
+v0.9.0-beta.1
+---
+* **New option:** `controlFlowFlattening` allows to enable/disable **Control Flow flattening**. Control flow flattening is a structure transformation of the source code that hinders program comprehension.
+* **New option:** `controlFlowFlatteningThreshold` allows to set percentage of nodes that will affected by `controlFlowFlattening`.
+* Better `es2015` support: correct obfuscation of `TemplateLiteral`, `ArrayPattern`, `AssignmentPattern` nodes.
+* Obfuscation performance boost.
+* Huge internal refactoring.
+* Various bug fixes.
+
 v0.8.6
 ---
 * additional fixes for https://github.com/javascript-obfuscator/javascript-obfuscator/issues/29

+ 10 - 8
README.md

@@ -9,7 +9,7 @@
 JavaScript obfuscator for Node.js is a free obfuscator with wide number of features which provides protection for your source code.
 
 * without any limits and sending data to a server;
-* compatible with ES6;
+* compatible with `es2015`;
 * tested on Angular2 bundle.
 
 Online version: [javascriptobfuscator.herokuapp.com](https://javascriptobfuscator.herokuapp.com)
@@ -25,6 +25,8 @@ Example of obfuscated code: [gist.github.com](https://gist.github.com/sanex3339/
 [![Build Status](https://travis-ci.org/javascript-obfuscator/javascript-obfuscator.svg?branch=master)](https://travis-ci.org/javascript-obfuscator/javascript-obfuscator)
 [![Coverage Status](https://coveralls.io/repos/github/javascript-obfuscator/javascript-obfuscator/badge.svg?branch=master)](https://coveralls.io/github/javascript-obfuscator/javascript-obfuscator?branch=master)
 
+*NOTE! the README on the master branch might not match that of the latest stable release!*
+
 ## :warning: Important
 #####Obfuscate only the code that belongs to you. 
 
@@ -122,7 +124,7 @@ For available options see [options](#options).
 Usage:
 ```sh
 javascript-obfuscator in.js [options]
-javascript-obfuscator in.js -output out.js [options]
+javascript-obfuscator in.js --output out.js [options]
 ```
 
 If the destination path is not specified through `--output` option, obfuscated code will saved into input file directory with name like `INPUT_FILE_NAME-obfuscated.js`
@@ -409,7 +411,7 @@ Available values:
 * `true` (`boolean`): encode `stringArray` values using `base64`
 * `false` (`boolean`): don't encode `stringArray` values
 * `'base64'` (`string`): encode `stringArray` values using `base64`
-* `'rc4'` (`string`): encode `stringArray` values using `rc4`. **About 30-50% slower then `base64`, but more harder to get initial values.** It is recommended to disable [`unicodeEscapeSequence`](#unicodeescapesequence) option with `rc4` encoding to prevent very large size of obfuscated code.
+* `'rc4'` (`string`): encode `stringArray` values using `rc4`. **About 30-50% slower than `base64`, but more harder to get initial values.** It is recommended to disable [`unicodeEscapeSequence`](#unicodeescapesequence) option with `rc4` encoding to prevent very large size of obfuscated code.
     
 ### `stringArrayThreshold`
 Type: `number` Default: `0.8` Min: `0` Max: `1`
@@ -432,7 +434,7 @@ Unicode escape sequence increases code size greatly. It is recommended to disabl
 ## Preset Options
 ### High obfuscation, low performance
 
-Performance will 50-100% slower then without obfuscation
+Performance will 50-100% slower than without obfuscation
 
 ```javascript
 {
@@ -453,7 +455,7 @@ Performance will 50-100% slower then without obfuscation
 
 ### Medium obfuscation, optimal performance
 
-Performance will 30-35% slower then without obfuscation
+Performance will 30-35% slower than without obfuscation
 
 ```javascript
 {
@@ -474,7 +476,7 @@ Performance will 30-35% slower then without obfuscation
 
 ### Low obfuscation, High performance
 
-Performance will slightly slower then without obfuscation
+Performance will slightly slower than without obfuscation
 
 ```javascript
 {
@@ -493,7 +495,7 @@ Performance will slightly slower then without obfuscation
 ```
 
 ## License
-Copyright (C) 2016 [Timofey Kachalov](http://github.com/sanex3339).
+Copyright (C) 2017 [Timofey Kachalov](http://github.com/sanex3339).
 
 Redistribution and use in source and binary forms, with or without
 modification, are permitted provided that the following conditions are met:
@@ -513,4 +515,4 @@ DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
-THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

Diff do ficheiro suprimidas por serem muito extensas
+ 230 - 167
dist/index.js


+ 7 - 7
package.json

@@ -1,6 +1,6 @@
 {
   "name": "javascript-obfuscator",
-  "version": "0.9.0-dev.10",
+  "version": "0.9.0-beta.2",
   "description": "JavaScript obfuscator",
   "keywords": [
     "obfuscator",
@@ -29,7 +29,7 @@
     "lodash": "4.17.4",
     "mkdirp": "0.5.1",
     "reflect-metadata": "0.1.9",
-    "source-map-support": "0.4.8",
+    "source-map-support": "0.4.10",
     "string-template": "1.0.0",
     "tslib": "1.5.0"
   },
@@ -43,15 +43,15 @@
     "@types/estree": "0.0.34",
     "@types/lodash": "4.14.50",
     "@types/mkdirp": "0.3.29",
-    "@types/mocha": "2.2.37",
+    "@types/mocha": "2.2.38",
     "@types/node": "7.0.0",
     "@types/sinon": "1.16.34",
     "@types/string-template": "1.0.2",
     "awesome-typescript-loader": "3.0.0-beta.18",
-    "babel-cli": "6.18.0",
+    "babel-cli": "6.22.1",
     "babel-loader": "6.2.10",
-    "babel-plugin-transform-runtime": "6.15.0",
-    "babel-preset-es2015": "6.18.0",
+    "babel-plugin-transform-runtime": "6.22.0",
+    "babel-preset-es2015": "6.22.0",
     "chai": "4.0.0-canary.1",
     "coveralls": "2.11.15",
     "istanbul": "1.1.0-alpha.1",
@@ -61,7 +61,7 @@
     "tslint": "4.3.1",
     "tslint-loader": "3.3.0",
     "typescript": "2.1.5",
-    "webpack": "2.2.0-rc.4",
+    "webpack": "2.2.0",
     "webpack-node-externals": "1.5.4"
   },
   "repository": {

+ 1 - 0
scripts/test-coveralls

@@ -1,5 +1,6 @@
 #!/bin/bash
 
+yarn run test:removeTmpDir &&
 yarn run test:compile &&
 $(yarn bin)/istanbul cover $(yarn bin)/_mocha -- test-tmp/test/index.spec.js --report lcovonly &&
 cat ./coverage/lcov.info | $(yarn bin)/coveralls &&

+ 1 - 0
scripts/test-full

@@ -1,5 +1,6 @@
 #!/bin/bash
 
+yarn run test:removeTmpDir &&
 yarn run test:compile &&
 node test-tmp/test/dev/dev.js &&
 $(yarn bin)/istanbul cover $(yarn bin)/_mocha -- test-tmp/test/index.spec.js &&

+ 1 - 0
scripts/test-mocha

@@ -1,5 +1,6 @@
 #!/bin/bash
 
+yarn run test:removeTmpDir &&
 yarn run test:compile &&
 $(yarn bin)/mocha test-tmp/test/index.spec.js &&
 yarn run test:removeTmpDir

+ 80 - 50
src/Obfuscator.ts

@@ -6,18 +6,17 @@ import * as ESTree from 'estree';
 
 import { TNodeTransformersFactory } from './types/container/TNodeTransformersFactory';
 import { TVisitorDirection } from './types/TVisitorDirection';
+import { TVisitorFunction } from './types/TVisitorFunction';
 
 import { ICustomNodeGroup } from './interfaces/custom-nodes/ICustomNodeGroup';
 import { IObfuscationEventEmitter } from './interfaces/event-emitters/IObfuscationEventEmitter';
 import { IObfuscator } from './interfaces/IObfuscator';
 import { IOptions } from './interfaces/options/IOptions';
-import { INodeTransformer } from './interfaces/node-transformers/INodeTransformer';
 import { IStackTraceAnalyzer } from './interfaces/stack-trace-analyzer/IStackTraceAnalyzer';
 import { IStackTraceData } from './interfaces/stack-trace-analyzer/IStackTraceData';
 import { IStorage } from './interfaces/storages/IStorage';
 
 import { NodeTransformers } from './enums/container/NodeTransformers';
-import { NodeType } from './enums/NodeType';
 import { ObfuscationEvents } from './enums/ObfuscationEvents';
 import { VisitorDirection } from './enums/VisitorDirection';
 
@@ -27,40 +26,34 @@ import { NodeUtils } from './node/NodeUtils';
 @injectable()
 export class Obfuscator implements IObfuscator {
     /**
-     * @type {Map<string, NodeTransformers[]>}
+     * @type {NodeTransformers[]}
      */
-    private static readonly controlFlowTransformersMap: Map <string, NodeTransformers[]> = new Map([
-        [NodeType.BlockStatement, [NodeTransformers.BlockStatementControlFlowTransformer]],
-        [NodeType.FunctionDeclaration, [NodeTransformers.FunctionControlFlowTransformer]],
-        [NodeType.FunctionExpression, [NodeTransformers.FunctionControlFlowTransformer]]
-    ]);
+    private static readonly controlFlowTransformersList: NodeTransformers[] = [
+        NodeTransformers.BlockStatementControlFlowTransformer,
+        NodeTransformers.FunctionControlFlowTransformer
+    ];
 
     /**
-     * @type {Map<string, NodeTransformers[]>}
+     * @type {NodeTransformers[]}
      */
-    private static readonly convertingTransformersMap: Map <string, NodeTransformers[]> = new Map([
-        [NodeType.MemberExpression, [NodeTransformers.MemberExpressionTransformer]],
-        [NodeType.MethodDefinition, [NodeTransformers.MethodDefinitionTransformer]],
-        [NodeType.TemplateLiteral, [NodeTransformers.TemplateLiteralTransformer]],
-    ]);
+    private static readonly convertingTransformersList: NodeTransformers[] = [
+        NodeTransformers.MemberExpressionTransformer,
+        NodeTransformers.MethodDefinitionTransformer,
+        NodeTransformers.TemplateLiteralTransformer
+    ];
 
     /**
-     * @type {Map<string, NodeTransformers[]>}
+     * @type {NodeTransformers[]}
      */
-    private static readonly obfuscatingTransformersMap: Map <string, NodeTransformers[]> = new Map([
-        [NodeType.ArrowFunctionExpression, [NodeTransformers.FunctionTransformer]],
-        [NodeType.ClassDeclaration, [NodeTransformers.FunctionDeclarationTransformer]],
-        [NodeType.CatchClause, [NodeTransformers.CatchClauseTransformer]],
-        [NodeType.FunctionDeclaration, [
-            NodeTransformers.FunctionDeclarationTransformer,
-            NodeTransformers.FunctionTransformer
-        ]],
-        [NodeType.FunctionExpression, [NodeTransformers.FunctionTransformer]],
-        [NodeType.ObjectExpression, [NodeTransformers.ObjectExpressionTransformer]],
-        [NodeType.VariableDeclaration, [NodeTransformers.VariableDeclarationTransformer]],
-        [NodeType.LabeledStatement, [NodeTransformers.LabeledStatementTransformer]],
-        [NodeType.Literal, [NodeTransformers.LiteralTransformer]]
-    ]);
+    private static readonly obfuscatingTransformersList: NodeTransformers[] = [
+        NodeTransformers.CatchClauseTransformer,
+        NodeTransformers.FunctionDeclarationTransformer,
+        NodeTransformers.FunctionTransformer,
+        NodeTransformers.LabeledStatementTransformer,
+        NodeTransformers.LiteralTransformer,
+        NodeTransformers.ObjectExpressionTransformer,
+        NodeTransformers.VariableDeclarationTransformer
+    ];
 
     /**
      * @type {IStorage<ICustomNodeGroup>}
@@ -139,21 +132,17 @@ export class Obfuscator implements IObfuscator {
         if (this.options.controlFlowFlattening) {
             astTree = this.transformAstTree(
                 astTree,
-                VisitorDirection.leave,
-                this.nodeTransformersFactory(Obfuscator.controlFlowTransformersMap)
+                Obfuscator.controlFlowTransformersList
             );
         }
 
         // second pass: nodes obfuscation
         astTree = this.transformAstTree(
             astTree,
-            VisitorDirection.enter,
-            this.nodeTransformersFactory(
-                new Map([
-                    ...Obfuscator.convertingTransformersMap,
-                    ...Obfuscator.obfuscatingTransformersMap
-                ])
-            )
+            [
+                ...Obfuscator.convertingTransformersList,
+                ...Obfuscator.obfuscatingTransformersList
+            ]
         );
 
         this.obfuscationEventEmitter.emit(ObfuscationEvents.AfterObfuscation, astTree, stackTraceData);
@@ -163,26 +152,67 @@ export class Obfuscator implements IObfuscator {
 
     /**
      * @param astTree
-     * @param direction
-     * @param nodeTransformersConcreteFactory
+     * @param nodeTransformers
      */
     private transformAstTree (
         astTree: ESTree.Program,
-        direction: TVisitorDirection,
-        nodeTransformersConcreteFactory: (nodeType: string) => INodeTransformer[]
+        nodeTransformers: NodeTransformers[]
     ): ESTree.Program {
+        const visitors: estraverse.Visitor[] = nodeTransformers
+            .map((nodeTransformer: NodeTransformers): estraverse.Visitor => {
+                return this.nodeTransformersFactory(nodeTransformer).getVisitor();
+            });
+
         estraverse.replace(astTree, {
-            [direction]: (node: ESTree.Node, parentNode: ESTree.Node): ESTree.Node => {
-                const nodeTransformers: INodeTransformer[] = nodeTransformersConcreteFactory(node.type);
+            enter: this.mergeVisitorsForDirection(
+                visitors.filter((visitor: estraverse.Visitor) => visitor.enter !== undefined),
+                VisitorDirection.enter
+            ),
+            leave: this.mergeVisitorsForDirection(
+                visitors.filter((visitor: estraverse.Visitor) => visitor.leave !== undefined),
+                VisitorDirection.leave
+            )
+        });
+
+        return astTree;
+    }
+
+    /**
+     * @param visitors
+     * @param direction
+     * @return {TVisitorDirection}
+     */
+    private mergeVisitorsForDirection (visitors: estraverse.Visitor[], direction: TVisitorDirection): TVisitorFunction {
+        if (!visitors.length) {
+            return (node: ESTree.Node, parentNode: ESTree.Node) => node;
+        }
+
+        return (node: ESTree.Node, parentNode: ESTree.Node) => {
+            for (const visitor of visitors) {
+                const visitorFunction: TVisitorFunction | undefined = visitor[direction];
 
-                nodeTransformers.forEach((nodeTransformer: INodeTransformer) => {
-                    node = nodeTransformer.transformNode(node, parentNode);
-                });
+                if (!visitorFunction) {
+                    continue;
+                }
 
-                return node;
+                const visitorResult: estraverse.VisitorOption | ESTree.Node | void = visitorFunction(node, parentNode);
+
+                if (!visitorResult) {
+                    continue;
+                }
+
+                if (
+                    visitorResult === estraverse.VisitorOption.Break ||
+                    visitorResult === estraverse.VisitorOption.Remove ||
+                    visitorResult === estraverse.VisitorOption.Skip
+                ) {
+                    return visitorResult;
+                }
+
+                node = <ESTree.Node>visitorResult;
             }
-        });
 
-        return astTree;
+            return node;
+        };
     }
 }

+ 13 - 20
src/container/modules/node-transformers/NodeTransformersModule.ts

@@ -72,31 +72,24 @@ export const nodeTransformersModule: interfaces.ContainerModule = new ContainerM
         .whenTargetNamed(NodeTransformers.VariableDeclarationTransformer);
 
     // node transformers factory
-    bind<INodeTransformer[]>(ServiceIdentifiers.Factory__INodeTransformer)
-        .toFactory<INodeTransformer[]>((context: interfaces.Context) => {
+    bind<INodeTransformer>(ServiceIdentifiers.Factory__INodeTransformer)
+        .toFactory<INodeTransformer>((context: interfaces.Context) => {
             const cache: Map <NodeTransformers, INodeTransformer> = new Map();
 
-            return (nodeTransformersMap: Map<string, NodeTransformers[]>) => (nodeType: string) => {
-                const nodeTransformers: NodeTransformers[] = nodeTransformersMap.get(nodeType) || [];
-                const instancesArray: INodeTransformer[] = [];
+            return (nodeTransformerName: NodeTransformers) => {
+                if (cache.has(nodeTransformerName)) {
+                    return <INodeTransformer>cache.get(nodeTransformerName);
+                }
 
-                nodeTransformers.forEach((transformer: NodeTransformers) => {
-                    let nodeTransformer: INodeTransformer;
+                const nodeTransformer: INodeTransformer = context.container
+                    .getNamed<INodeTransformer>(
+                        ServiceIdentifiers.INodeTransformer,
+                        nodeTransformerName
+                    );
 
-                    if (cache.has(transformer)) {
-                        nodeTransformer = <INodeTransformer>cache.get(transformer);
-                    } else {
-                        nodeTransformer = context.container.getNamed<INodeTransformer>(
-                            ServiceIdentifiers.INodeTransformer,
-                            transformer
-                        );
-                        cache.set(transformer, nodeTransformer);
-                    }
+                cache.set(nodeTransformerName, nodeTransformer);
 
-                    instancesArray.push(nodeTransformer);
-                });
-
-                return instancesArray;
+                return nodeTransformer;
             };
         });
 });

+ 1 - 2
src/declarations/ESTree.d.ts

@@ -4,9 +4,8 @@ import * as ESTree from 'estree';
 
 declare module 'estree' {
     interface BaseNode {
-        obfuscated?: boolean;
+        obfuscatedNode?: boolean;
         parentNode?: ESTree.Node;
-        skippedByControlFlow?: boolean;
     }
 
     interface SimpleLiteral extends ESTree.BaseNode, ESTree.BaseExpression {

+ 2 - 0
src/enums/NodeType.ts

@@ -4,6 +4,7 @@ export const NodeType: any = Utils.strEnumify({
     ArrayExpression: 'ArrayExpression',
     ArrowFunctionExpression: 'ArrowFunctionExpression',
     AssignmentExpression: 'AssignmentExpression',
+    AssignmentPattern: 'AssignmentPattern',
     BinaryExpression: 'BinaryExpression',
     BlockStatement: 'BlockStatement',
     BreakStatement: 'BreakStatement',
@@ -22,6 +23,7 @@ export const NodeType: any = Utils.strEnumify({
     MemberExpression: 'MemberExpression',
     MethodDefinition: 'MethodDefinition',
     ObjectExpression: 'ObjectExpression',
+    ObjectPattern: 'ObjectPattern',
     Program: 'Program',
     Property: 'Property',
     ReturnStatement: 'ReturnStatement',

+ 6 - 0
src/interfaces/node-transformers/INodeTransformer.d.ts

@@ -1,6 +1,12 @@
+import * as estraverse from 'estraverse';
 import * as ESTree from 'estree';
 
 export interface INodeTransformer {
+    /**
+     * @returns {estraverse.Visitor}
+     */
+    getVisitor (): estraverse.Visitor;
+
     /**
      * @param node
      * @param parentNode

+ 1 - 1
src/interfaces/storages/IStorage.d.ts

@@ -43,7 +43,7 @@ export interface IStorage <T> extends IInitializable {
      * @param key
      * @param value
      */
-    set (key: string | number | null, value: T): void;
+    set (key: string | number, value: T): void;
 
     /**
      * @returns string

+ 6 - 0
src/node-transformers/AbstractNodeTransformer.ts

@@ -1,6 +1,7 @@
 import { injectable, inject } from 'inversify';
 import { ServiceIdentifiers } from '../container/ServiceIdentifiers';
 
+import * as estraverse from 'estraverse';
 import * as ESTree from 'estree';
 
 import { INodeTransformer } from '../interfaces/node-transformers/INodeTransformer';
@@ -29,6 +30,11 @@ export abstract class AbstractNodeTransformer implements INodeTransformer {
         this.options = options;
     }
 
+    /**
+     * @returns {estraverse.Visitor}
+     */
+    public abstract getVisitor (): estraverse.Visitor;
+
     /**
      * @param node
      * @param parentNode

+ 16 - 1
src/node-transformers/control-flow-transformers/BlockStatementControlFlowTransformer.ts

@@ -1,6 +1,7 @@
 import { injectable, inject } from 'inversify';
 import { ServiceIdentifiers } from '../../container/ServiceIdentifiers';
 
+import * as estraverse from 'estraverse';
 import * as ESTree from 'estree';
 
 import { TCustomNodeFactory } from '../../types/container/TCustomNodeFactory';
@@ -49,11 +50,25 @@ export class BlockStatementControlFlowTransformer extends AbstractNodeTransforme
         });
     }
 
+    /**
+     * @return {estraverse.Visitor}
+     */
+    public getVisitor (): estraverse.Visitor {
+        return {
+            leave: (node: ESTree.Node, parentNode: ESTree.Node) => {
+                if (Node.isBlockStatementNode(node)) {
+                    return this.transformNode(node, parentNode);
+                }
+            }
+        };
+    }
+
     /**
      * @param blockStatementNode
+     * @param parentNode
      * @returns {ESTree.Node}
      */
-    public transformNode (blockStatementNode: ESTree.BlockStatement): ESTree.Node {
+    public transformNode (blockStatementNode: ESTree.BlockStatement, parentNode: ESTree.Node): ESTree.Node {
         if (
             RandomGeneratorUtils.getRandomFloat(0, 1) > this.options.controlFlowFlatteningThreshold ||
             BlockStatementControlFlowTransformer.blockStatementHasProhibitedStatements(blockStatementNode)

+ 15 - 1
src/node-transformers/control-flow-transformers/FunctionControlFlowTransformer.ts

@@ -112,11 +112,25 @@ export class FunctionControlFlowTransformer extends AbstractNodeTransformer {
         return RandomGeneratorUtils.getRandomGenerator().pickone(blockScopesOfNode);
     }
 
+    /**
+     * @return {estraverse.Visitor}
+     */
+    public getVisitor (): estraverse.Visitor {
+        return {
+            leave: (node: ESTree.Node, parentNode: ESTree.Node) => {
+                if (Node.isFunctionDeclarationNode(node) || Node.isFunctionExpressionNode(node)) {
+                    return this.transformNode(node, parentNode);
+                }
+            }
+        };
+    }
+
     /**
      * @param functionNode
+     * @param parentNode
      * @returns {ESTree.Node}
      */
-    public transformNode (functionNode: ESTree.Function): ESTree.Node {
+    public transformNode (functionNode: ESTree.Function, parentNode: ESTree.Node): ESTree.Node {
         if (Node.isArrowFunctionExpressionNode(functionNode)) {
             return functionNode;
         }

+ 16 - 1
src/node-transformers/converting-transformers/MemberExpressionTransformer.ts

@@ -1,6 +1,7 @@
 import { injectable, inject } from 'inversify';
 import { ServiceIdentifiers } from '../../container/ServiceIdentifiers';
 
+import * as estraverse from 'estraverse';
 import * as ESTree from 'estree';
 
 import { IOptions } from '../../interfaces/options/IOptions';
@@ -21,6 +22,19 @@ export class MemberExpressionTransformer extends AbstractNodeTransformer {
         super(options);
     }
 
+    /**
+     * @return {estraverse.Visitor}
+     */
+    public getVisitor (): estraverse.Visitor {
+        return {
+            enter: (node: ESTree.Node, parentNode: ESTree.Node) => {
+                if (Node.isMemberExpressionNode(node)) {
+                    return this.transformNode(node, parentNode);
+                }
+            }
+        };
+    }
+
     /**
      * replaces:
      *     object.identifier = 1;
@@ -34,9 +48,10 @@ export class MemberExpressionTransformer extends AbstractNodeTransformer {
      * Literal node will be obfuscated by LiteralTransformer
      *
      * @param memberExpressionNode
+     * @param parentNode
      * @returns {ESTree.Node}
      */
-    public transformNode (memberExpressionNode: ESTree.MemberExpression): ESTree.Node {
+    public transformNode (memberExpressionNode: ESTree.MemberExpression, parentNode: ESTree.Node): ESTree.Node {
         if (Node.isIdentifierNode(memberExpressionNode.property)) {
             if (memberExpressionNode.computed) {
                 return memberExpressionNode;

+ 14 - 0
src/node-transformers/converting-transformers/MethodDefinitionTransformer.ts

@@ -1,6 +1,7 @@
 import { injectable, inject } from 'inversify';
 import { ServiceIdentifiers } from '../../container/ServiceIdentifiers';
 
+import * as estraverse from 'estraverse';
 import * as ESTree from 'estree';
 
 import { IOptions } from '../../interfaces/options/IOptions';
@@ -35,6 +36,19 @@ export class MethodDefinitionTransformer extends AbstractNodeTransformer {
         super(options);
     }
 
+    /**
+     * @return {estraverse.Visitor}
+     */
+    public getVisitor (): estraverse.Visitor {
+        return {
+            enter: (node: ESTree.Node, parentNode: ESTree.Node) => {
+                if (Node.isMethodDefinitionNode(node)) {
+                    return this.transformNode(node, parentNode);
+                }
+            }
+        };
+    }
+
     /**
      * replaces:
      *     object.identifier = 1;

+ 14 - 0
src/node-transformers/converting-transformers/TemplateLiteralTransformer.ts

@@ -1,6 +1,7 @@
 import { injectable, inject } from 'inversify';
 import { ServiceIdentifiers } from '../../container/ServiceIdentifiers';
 
+import * as estraverse from 'estraverse';
 import * as ESTree from 'estree';
 
 import { IOptions } from '../../interfaces/options/IOptions';
@@ -32,6 +33,19 @@ export class TemplateLiteralTransformer extends AbstractNodeTransformer {
         return node && Node.isLiteralNode(node) && typeof node.value === 'string';
     }
 
+    /**
+     * @return {estraverse.Visitor}
+     */
+    public getVisitor (): estraverse.Visitor {
+        return {
+            enter: (node: ESTree.Node, parentNode: ESTree.Node) => {
+                if (Node.isTemplateLiteralNode(node)) {
+                    return this.transformNode(node, parentNode);
+                }
+            }
+        };
+    }
+
     /**
      * @param templateLiteralNode
      * @param parentNode

+ 16 - 2
src/node-transformers/obfuscating-transformers/CatchClauseTransformer.ts

@@ -41,11 +41,25 @@ export class CatchClauseTransformer extends AbstractNodeTransformer {
         this.identifierReplacer = <IObfuscationReplacerWithStorage>replacersFactory(NodeObfuscatorsReplacers.IdentifierReplacer);
     }
 
+    /**
+     * @return {estraverse.Visitor}
+     */
+    public getVisitor (): estraverse.Visitor {
+        return {
+            enter: (node: ESTree.Node, parentNode: ESTree.Node) => {
+                if (Node.isCatchClauseNode(node)) {
+                    return this.transformNode(node, parentNode);
+                }
+            }
+        };
+    }
+
     /**
      * @param catchClauseNode
+     * @param parentNode
      * @returns {ESTree.Node}
      */
-    public transformNode (catchClauseNode: ESTree.CatchClause): ESTree.Node {
+    public transformNode (catchClauseNode: ESTree.CatchClause, parentNode: ESTree.Node): ESTree.Node {
         const nodeIdentifier: number = this.nodeIdentifier++;
 
         this.storeCatchClauseParam(catchClauseNode, nodeIdentifier);
@@ -76,7 +90,7 @@ export class CatchClauseTransformer extends AbstractNodeTransformer {
 
                     if (node.name !== newNodeName) {
                         node.name = newNodeName;
-                        node.obfuscated = true;
+                        node.obfuscatedNode = true;
                     }
                 }
             }

+ 13 - 0
src/node-transformers/obfuscating-transformers/FunctionDeclarationTransformer.ts

@@ -51,6 +51,19 @@ export class FunctionDeclarationTransformer extends AbstractNodeTransformer {
         this.identifierReplacer = <IObfuscationReplacerWithStorage>nodeObfuscatorsReplacersFactory(NodeObfuscatorsReplacers.IdentifierReplacer);
     }
 
+    /**
+     * @return {estraverse.Visitor}
+     */
+    public getVisitor (): estraverse.Visitor {
+        return {
+            enter: (node: ESTree.Node, parentNode: ESTree.Node) => {
+                if (Node.isFunctionDeclarationNode(node)) {
+                    return this.transformNode(node, parentNode);
+                }
+            }
+        };
+    }
+
     /**
      * @param functionDeclarationNode
      * @param parentNode

+ 36 - 4
src/node-transformers/obfuscating-transformers/FunctionTransformer.ts

@@ -41,11 +41,29 @@ export class FunctionTransformer extends AbstractNodeTransformer {
         this.identifierReplacer = <IObfuscationReplacerWithStorage>nodeObfuscatorsReplacersFactory(NodeObfuscatorsReplacers.IdentifierReplacer);
     }
 
+    /**
+     * @return {estraverse.Visitor}
+     */
+    public getVisitor (): estraverse.Visitor {
+        return {
+            enter: (node: ESTree.Node, parentNode: ESTree.Node) => {
+                if (
+                    Node.isFunctionDeclarationNode(node) ||
+                    Node.isFunctionExpressionNode(node) ||
+                    Node.isArrowFunctionExpressionNode(node)
+                ) {
+                    return this.transformNode(node, parentNode);
+                }
+            }
+        };
+    }
+
     /**
      * @param functionNode
+     * @param parentNode
      * @returns {ESTree.Node}
      */
-    public transformNode (functionNode: ESTree.Function): ESTree.Node {
+    public transformNode (functionNode: ESTree.Function, parentNode: ESTree.Node): ESTree.Node {
         const nodeIdentifier: number = this.nodeIdentifier++;
 
         this.storeFunctionParams(functionNode, nodeIdentifier);
@@ -61,9 +79,23 @@ export class FunctionTransformer extends AbstractNodeTransformer {
     private storeFunctionParams (functionNode: ESTree.Function, nodeIdentifier: number): void {
         functionNode.params
             .forEach((paramsNode: ESTree.Node) => {
-                if (Node.isIdentifierNode(paramsNode)) {
-                    this.identifierReplacer.storeNames(paramsNode.name, nodeIdentifier);
+                if (Node.isObjectPatternNode(paramsNode)) {
+                    return estraverse.VisitorOption.Skip;
                 }
+
+                estraverse.traverse(paramsNode, {
+                    enter: (node: ESTree.Node): any => {
+                        if (Node.isAssignmentPatternNode(node) && Node.isIdentifierNode(node.left)) {
+                            this.identifierReplacer.storeNames(node.left.name, nodeIdentifier);
+
+                            return estraverse.VisitorOption.Skip;
+                        }
+
+                        if (Node.isIdentifierNode(node)) {
+                            this.identifierReplacer.storeNames(node.name, nodeIdentifier);
+                        }
+                    }
+                });
             });
     }
 
@@ -79,7 +111,7 @@ export class FunctionTransformer extends AbstractNodeTransformer {
 
                     if (node.name !== newNodeName) {
                         node.name = newNodeName;
-                        node.obfuscated = true;
+                        node.obfuscatedNode = true;
                     }
                 }
             }

+ 15 - 1
src/node-transformers/obfuscating-transformers/LabeledStatementTransformer.ts

@@ -49,11 +49,25 @@ export class LabeledStatementTransformer extends AbstractNodeTransformer {
         this.identifierReplacer = <IObfuscationReplacerWithStorage>nodeObfuscatorsReplacersFactory(NodeObfuscatorsReplacers.IdentifierReplacer);
     }
 
+    /**
+     * @return {estraverse.Visitor}
+     */
+    public getVisitor (): estraverse.Visitor {
+        return {
+            enter: (node: ESTree.Node, parentNode: ESTree.Node) => {
+                if (Node.isLabeledStatementNode(node)) {
+                    return this.transformNode(node, parentNode);
+                }
+            }
+        };
+    }
+
     /**
      * @param labeledStatementNode
+     * @param parentNode
      * @returns {ESTree.Node}
      */
-    public transformNode (labeledStatementNode: ESTree.LabeledStatement): ESTree.Node {
+    public transformNode (labeledStatementNode: ESTree.LabeledStatement, parentNode: ESTree.Node): ESTree.Node {
         const nodeIdentifier: number = this.nodeIdentifier++;
 
         this.storeLabeledStatementName(labeledStatementNode, nodeIdentifier);

+ 14 - 0
src/node-transformers/obfuscating-transformers/LiteralTransformer.ts

@@ -2,6 +2,7 @@ import { injectable, inject } from 'inversify';
 import { ServiceIdentifiers } from '../../container/ServiceIdentifiers';
 
 import * as escodegen from 'escodegen';
+import * as estraverse from 'estraverse';
 import * as ESTree from 'estree';
 
 import { IOptions } from '../../interfaces/options/IOptions';
@@ -32,6 +33,19 @@ export class LiteralTransformer extends AbstractNodeTransformer {
         this.replacersFactory = replacersFactory;
     }
 
+    /**
+     * @return {estraverse.Visitor}
+     */
+    public getVisitor (): estraverse.Visitor {
+        return {
+            enter: (node: ESTree.Node, parentNode: ESTree.Node) => {
+                if (Node.isLiteralNode(node)) {
+                    return this.transformNode(node, parentNode);
+                }
+            }
+        };
+    }
+
     /**
      * @param literalNode
      * @param parentNode

+ 16 - 1
src/node-transformers/obfuscating-transformers/ObjectExpressionTransformer.ts

@@ -2,6 +2,7 @@ import { injectable, inject } from 'inversify';
 import { ServiceIdentifiers } from '../../container/ServiceIdentifiers';
 
 import * as escodegen from 'escodegen';
+import * as estraverse from 'estraverse';
 import * as ESTree from 'estree';
 
 import { IOptions } from '../../interfaces/options/IOptions';
@@ -64,11 +65,25 @@ export class ObjectExpressionTransformer extends AbstractNodeTransformer {
         };
     }
 
+    /**
+     * @return {estraverse.Visitor}
+     */
+    public getVisitor (): estraverse.Visitor {
+        return {
+            enter: (node: ESTree.Node, parentNode: ESTree.Node) => {
+                if (Node.isObjectExpressionNode(node)) {
+                    return this.transformNode(node, parentNode);
+                }
+            }
+        };
+    }
+
     /**
      * @param objectExpressionNode
+     * @param parentNode
      * @returns {ESTree.Node}
      */
-    public transformNode (objectExpressionNode: ESTree.ObjectExpression): ESTree.Node {
+    public transformNode (objectExpressionNode: ESTree.ObjectExpression, parentNode: ESTree.Node): ESTree.Node {
         objectExpressionNode.properties
             .forEach((property: ESTree.Property) => {
                 if (property.shorthand) {

+ 20 - 3
src/node-transformers/obfuscating-transformers/VariableDeclarationTransformer.ts

@@ -52,6 +52,19 @@ export class VariableDeclarationTransformer extends AbstractNodeTransformer {
         this.identifierReplacer = <IObfuscationReplacerWithStorage>replacersFactory(NodeObfuscatorsReplacers.IdentifierReplacer);
     }
 
+    /**
+     * @return {estraverse.Visitor}
+     */
+    public getVisitor (): estraverse.Visitor {
+        return {
+            enter: (node: ESTree.Node, parentNode: ESTree.Node) => {
+                if (Node.isVariableDeclarationNode(node)) {
+                    return this.transformNode(node, parentNode);
+                }
+            }
+        };
+    }
+
     /**
      * @param variableDeclarationNode
      * @param parentNode
@@ -83,9 +96,13 @@ export class VariableDeclarationTransformer extends AbstractNodeTransformer {
     private storeVariableNames (variableDeclarationNode: ESTree.VariableDeclaration, nodeIdentifier: number): void {
         variableDeclarationNode.declarations
             .forEach((declarationNode: ESTree.VariableDeclarator) => {
-                if (Node.isIdentifierNode(declarationNode.id)) {
-                    this.identifierReplacer.storeNames(declarationNode.id.name, nodeIdentifier);
+                if (Node.isObjectPatternNode(declarationNode.id)) {
+                    return estraverse.VisitorOption.Skip;
                 }
+
+                NodeUtils.typedTraverse(declarationNode.id, NodeType.Identifier, {
+                    enter: (node: ESTree.Identifier) => this.identifierReplacer.storeNames(node.name, nodeIdentifier)
+                });
             });
     }
 
@@ -111,7 +128,7 @@ export class VariableDeclarationTransformer extends AbstractNodeTransformer {
 
         estraverse.replace(scopeNode, {
             enter: (node: ESTree.Node, parentNode: ESTree.Node): any => {
-                if (!node.obfuscated && Node.isReplaceableIdentifierNode(node, parentNode)) {
+                if (!node.obfuscatedNode && Node.isReplaceableIdentifierNode(node, parentNode)) {
                     const newNodeName: string = this.identifierReplacer.replace(node.name, nodeIdentifier);
 
                     if (node.name !== newNodeName) {

+ 3 - 3
src/node-transformers/obfuscating-transformers/replacers/StringLiteralReplacer.ts

@@ -102,10 +102,10 @@ export class StringLiteralReplacer extends AbstractReplacer {
             return <string>this.stringLiteralHexadecimalIndexCache.get(value);
         }
 
-        const indexOfValue: number = this.stringArrayStorage.getLength();
-        const hexadecimalIndex: string = `${Utils.hexadecimalPrefix}${Utils.decToHex(indexOfValue)}`;
+        const stringArrayStorageLength: number = this.stringArrayStorage.getLength();
+        const hexadecimalIndex: string = `${Utils.hexadecimalPrefix}${Utils.decToHex(stringArrayStorageLength)}`;
 
-        this.stringArrayStorage.set(null, value);
+        this.stringArrayStorage.set(stringArrayStorageLength, value);
         this.stringLiteralHexadecimalIndexCache.set(value, hexadecimalIndex);
 
         return hexadecimalIndex;

+ 41 - 0
src/node/Node.ts

@@ -13,6 +13,14 @@ export class Node {
         return node.type === NodeType.ArrowFunctionExpression;
     }
 
+    /**
+     * @param node
+     * @returns {boolean}
+     */
+    public static isAssignmentPatternNode (node: ESTree.Node): node is ESTree.AssignmentPattern {
+        return node.type === NodeType.AssignmentPattern;
+    }
+
     /**
      * @param node
      * @returns {boolean}
@@ -37,6 +45,14 @@ export class Node {
         return node.type === NodeType.CallExpression;
     }
 
+    /**
+     * @param node
+     * @returns {boolean}
+     */
+    public static isCatchClauseNode (node: ESTree.Node): node is ESTree.CatchClause {
+        return node.type === NodeType.CatchClause;
+    }
+
     /**
      * @param node
      * @returns {boolean}
@@ -122,6 +138,22 @@ export class Node {
         return node.type === NodeType.MemberExpression;
     }
 
+    /**
+     * @param node
+     * @returns {boolean}
+     */
+    public static isMethodDefinitionNode (node: ESTree.Node): node is ESTree.MethodDefinition {
+        return node.type === NodeType.MethodDefinition;
+    }
+
+    /**
+     * @param node
+     * @returns {boolean}
+     */
+    public static isObjectPatternNode (node: ESTree.Node): node is ESTree.ObjectPattern {
+        return node.type === NodeType.ObjectPattern;
+    }
+
     /**
      * @param node
      * @returns {boolean}
@@ -168,6 +200,15 @@ export class Node {
         return !parentNodeIsPropertyNode && !parentNodeIsMemberExpressionNode && !Node.isLabelIdentifierNode(node, parentNode);
     }
 
+    /**
+     *
+     * @param node
+     * @returns {boolean}
+     */
+    public static isTemplateLiteralNode (node: ESTree.Node): node is ESTree.TemplateLiteral {
+        return node.type === NodeType.TemplateLiteral;
+    }
+
     /**
      *
      * @param node

+ 1 - 1
src/node/NodeUtils.ts

@@ -177,7 +177,7 @@ export class NodeUtils {
                 }
 
                 node.parentNode = value;
-                node.obfuscated = false;
+                node.obfuscatedNode = false;
             }
         });
 

+ 27 - 27
src/node/Nodes.ts

@@ -15,7 +15,7 @@ export class Nodes {
             type: NodeType.Program,
             body,
             sourceType: 'script',
-            obfuscated: false
+            obfuscatedNode: false
         };
     }
 
@@ -35,7 +35,7 @@ export class Nodes {
             operator,
             left,
             right,
-            obfuscated: false
+            obfuscatedNode: false
         };
     }
 
@@ -55,7 +55,7 @@ export class Nodes {
             operator,
             left,
             right,
-            obfuscated: false
+            obfuscatedNode: false
         };
     }
 
@@ -67,7 +67,7 @@ export class Nodes {
         return {
             type: NodeType.BlockStatement,
             body,
-            obfuscated: false
+            obfuscatedNode: false
         };
     }
 
@@ -78,7 +78,7 @@ export class Nodes {
     public static getBreakStatement (label?: ESTree.Identifier): ESTree.BreakStatement {
         const breakStatementNode: ESTree.BreakStatement = {
             type: NodeType.BreakStatement,
-            obfuscated: false
+            obfuscatedNode: false
         };
 
         if (label) {
@@ -97,7 +97,7 @@ export class Nodes {
             type: NodeType.CatchClause,
             param: Nodes.getIdentifierNode('err'),
             body: Nodes.getBlockStatementNode(body),
-            obfuscated: false
+            obfuscatedNode: false
         };
     }
 
@@ -114,7 +114,7 @@ export class Nodes {
             type: NodeType.CallExpression,
             callee,
             arguments: args,
-            obfuscated: false
+            obfuscatedNode: false
         };
     }
 
@@ -125,7 +125,7 @@ export class Nodes {
     public static getContinueStatement (label?: ESTree.Identifier): ESTree.ContinueStatement {
         const continueStatementNode: ESTree.ContinueStatement = {
             type: NodeType.ContinueStatement,
-            obfuscated: false
+            obfuscatedNode: false
         };
 
         if (label) {
@@ -143,7 +143,7 @@ export class Nodes {
         return {
             type: NodeType.ExpressionStatement,
             expression,
-            obfuscated: false
+            obfuscatedNode: false
         };
     }
 
@@ -164,7 +164,7 @@ export class Nodes {
             params,
             body,
             generator: false,
-            obfuscated: false
+            obfuscatedNode: false
         };
     }
 
@@ -182,7 +182,7 @@ export class Nodes {
             params,
             body,
             generator: false,
-            obfuscated: false
+            obfuscatedNode: false
         };
     }
 
@@ -196,7 +196,7 @@ export class Nodes {
             type: NodeType.IfStatement,
             test,
             consequent,
-            obfuscated: false
+            obfuscatedNode: false
         };
     }
 
@@ -208,7 +208,7 @@ export class Nodes {
         return {
             type: NodeType.Identifier,
             name,
-            obfuscated: false
+            obfuscatedNode: false
         };
     }
 
@@ -222,7 +222,7 @@ export class Nodes {
             type: NodeType.LabeledStatement,
             label,
             body,
-            obfuscated: false
+            obfuscatedNode: false
         };
     }
 
@@ -239,7 +239,7 @@ export class Nodes {
                 content: `'${value}'`,
                 precedence: escodegen.Precedence.Primary
             },
-            obfuscated: false
+            obfuscatedNode: false
         };
     }
 
@@ -259,7 +259,7 @@ export class Nodes {
             operator,
             left,
             right,
-            obfuscated: false
+            obfuscatedNode: false
         };
     }
 
@@ -279,7 +279,7 @@ export class Nodes {
             computed,
             object,
             property,
-            obfuscated: false
+            obfuscatedNode: false
         };
     }
 
@@ -291,7 +291,7 @@ export class Nodes {
         return {
             type: NodeType.ObjectExpression,
             properties,
-            obfuscated: false
+            obfuscatedNode: false
         };
     }
 
@@ -311,7 +311,7 @@ export class Nodes {
             method: false,
             shorthand: false,
             computed,
-            obfuscated: false
+            obfuscatedNode: false
         };
     }
 
@@ -331,7 +331,7 @@ export class Nodes {
             operator,
             argument,
             prefix,
-            obfuscated: false
+            obfuscatedNode: false
         };
     }
 
@@ -343,7 +343,7 @@ export class Nodes {
         return {
             type: NodeType.ReturnStatement,
             argument,
-            obfuscated: false
+            obfuscatedNode: false
         };
     }
 
@@ -360,7 +360,7 @@ export class Nodes {
             type: NodeType.SwitchStatement,
             discriminant,
             cases,
-            obfuscated: false
+            obfuscatedNode: false
         };
     }
 
@@ -374,7 +374,7 @@ export class Nodes {
             type: NodeType.SwitchCase,
             test,
             consequent,
-            obfuscated: false
+            obfuscatedNode: false
         };
     }
 
@@ -389,7 +389,7 @@ export class Nodes {
             operator,
             argument: argumentExpr,
             prefix: false,
-            obfuscated: false
+            obfuscatedNode: false
         };
     }
 
@@ -406,7 +406,7 @@ export class Nodes {
             type: NodeType.VariableDeclaration,
             declarations,
             kind,
-            obfuscated: false
+            obfuscatedNode: false
         };
     }
 
@@ -420,7 +420,7 @@ export class Nodes {
             type: NodeType.VariableDeclarator,
             id,
             init,
-            obfuscated: false
+            obfuscatedNode: false
         };
     }
 
@@ -434,7 +434,7 @@ export class Nodes {
             type: NodeType.WhileStatement,
             test,
             body,
-            obfuscated: false
+            obfuscatedNode: false
         };
     }
 }

+ 22 - 9
src/storages/ArrayStorage.ts

@@ -8,6 +8,12 @@ import { RandomGeneratorUtils } from '../utils/RandomGeneratorUtils';
 
 @injectable()
 export abstract class ArrayStorage <T> implements IStorage <T> {
+    /**
+     * @type {T[]}
+     */
+    @initializable()
+    protected storage: T[];
+
     /**
      * @type {string}
      */
@@ -15,10 +21,9 @@ export abstract class ArrayStorage <T> implements IStorage <T> {
     protected storageId: string;
 
     /**
-     * @type {T[]}
+     * @type {number}
      */
-    @initializable()
-    protected storage: T[];
+    private storageLength: number = 0;
 
     /**
      * @param key
@@ -36,17 +41,19 @@ export abstract class ArrayStorage <T> implements IStorage <T> {
 
     /**
      * @param value
-     * @returns {string | number}
+     * @returns {number | null}
      */
-    public getKeyOf (value: T): string | number {
-        return this.storage.indexOf(value);
+    public getKeyOf (value: T): number | null {
+        const key: number = this.storage.indexOf(value);
+
+        return key >= 0 ? key : null;
     }
 
     /**
      * @returns {number}
      */
     public getLength (): number {
-        return this.storage.length;
+        return this.storageLength;
     }
 
     /**
@@ -87,7 +94,13 @@ export abstract class ArrayStorage <T> implements IStorage <T> {
      * @param key
      * @param value
      */
-    public set (key: string | null, value: T): void {
-        this.storage.push(value);
+    public set (key: number, value: T): void {
+        if (key === this.storageLength) {
+            this.storage.push(value);
+        } else {
+            this.storage.splice(key, 0, value);
+        }
+
+        this.storageLength++;
     }
 }

+ 7 - 2
src/storages/MapStorage.ts

@@ -5,7 +5,6 @@ import { IStorage } from '../interfaces/storages/IStorage';
 import { initializable } from '../decorators/Initializable';
 
 import { RandomGeneratorUtils } from '../utils/RandomGeneratorUtils';
-import { Utils } from '../utils/Utils';
 
 @injectable()
 export abstract class MapStorage <T> implements IStorage <T> {
@@ -40,7 +39,13 @@ export abstract class MapStorage <T> implements IStorage <T> {
      * @returns {string | number | null}
      */
     public getKeyOf (value: T): string | number | null {
-        return Utils.mapGetFirstKeyOf <string | number, T> (this.storage, value);
+        for (const [key, storageValue] of this.storage) {
+            if (value === storageValue) {
+                return key;
+            }
+        }
+
+        return null;
     }
 
     /**

+ 4 - 0
src/types/TVisitorFunction.d.ts

@@ -0,0 +1,4 @@
+import * as estraverse from 'estraverse';
+import * as ESTree from 'estree';
+
+export type TVisitorFunction = (node: ESTree.Node, parentNode: ESTree.Node | null) => estraverse.VisitorOption | ESTree.Node | void;

+ 1 - 1
src/types/container/TNodeTransformersFactory.d.ts

@@ -2,4 +2,4 @@ import { INodeTransformer } from '../../interfaces/node-transformers/INodeTransf
 
 import { NodeTransformers } from '../../enums/container/NodeTransformers';
 
-export type TNodeTransformersFactory = (nodeTransformersMap: Map<string, NodeTransformers[]>) => (nodeType: string) => INodeTransformer[];
+export type TNodeTransformersFactory = (nodeTransformerName: NodeTransformers) => INodeTransformer;

+ 0 - 16
src/utils/Utils.ts

@@ -1,4 +1,3 @@
-import * as _ from 'lodash';
 import { JSFuck } from '../enums/JSFuck';
 
 import { RandomGeneratorUtils } from './RandomGeneratorUtils';
@@ -92,21 +91,6 @@ export class Utils {
         return number % 1 === 0;
     }
 
-    /**
-     * @param map
-     * @param value
-     * @returns {T | null}
-     */
-    public static mapGetFirstKeyOf <T, U> (map: Map <T, U>, value: U): T | null {
-        for (const [key, storageValue] of map) {
-            if (_.isEqual(value, storageValue)) {
-                return key;
-            }
-        }
-
-        return null;
-    }
-
     /**
      * @param obj
      * @returns {T}

+ 2 - 2
test/functional-tests/node-transformers/control-flow-transformers/control-flow-replacers/binary-expression-control-flow-replacer/BinaryExpressionControlFlowReplacer.spec.ts

@@ -58,8 +58,6 @@ describe('BinaryExpressionControlFlowReplacer', () => {
 
                     assert.match(obfuscatedCode, controlFlowStorageCallRegExp1);
                     assert.match(obfuscatedCode, controlFlowStorageCallRegExp2);
-                    assert.isOk(firstMatch);
-                    assert.isOk(secondMatch);
 
                     if (firstMatch === secondMatch) {
                         equalsValue++;
@@ -67,6 +65,8 @@ describe('BinaryExpressionControlFlowReplacer', () => {
                 }
 
                 assert.closeTo(equalsValue / samplesCount, expectedValue, delta);
+
+                console.log(equalsValue / samplesCount);
             });
         });
     });

+ 2 - 2
test/functional-tests/node-transformers/control-flow-transformers/control-flow-replacers/call-expression-control-flow-replacer/CallExpressionControlFlowReplacer.spec.ts

@@ -58,8 +58,6 @@ describe('CallExpressionControlFlowReplacer', () => {
 
                     assert.match(obfuscatedCode, controlFlowStorageCallRegExp1);
                     assert.match(obfuscatedCode, controlFlowStorageCallRegExp2);
-                    assert.isOk(firstMatch);
-                    assert.isOk(secondMatch);
 
                     if (firstMatch === secondMatch) {
                         equalsValue++;
@@ -67,6 +65,8 @@ describe('CallExpressionControlFlowReplacer', () => {
                 }
 
                 assert.closeTo(equalsValue / samplesCount, expectedValue, delta);
+
+                console.log(equalsValue / samplesCount);
             });
         });
 

+ 2 - 2
test/functional-tests/node-transformers/control-flow-transformers/control-flow-replacers/logical-expression-control-flow-replacer/LogicalExpressionControlFlowReplacer.spec.ts

@@ -58,8 +58,6 @@ describe('LogicalExpressionControlFlowReplacer', () => {
 
                     assert.match(obfuscatedCode, controlFlowStorageCallRegExp1);
                     assert.match(obfuscatedCode, controlFlowStorageCallRegExp2);
-                    assert.isOk(firstMatch);
-                    assert.isOk(secondMatch);
 
                     if (firstMatch === secondMatch) {
                         equalsValue++;
@@ -67,6 +65,8 @@ describe('LogicalExpressionControlFlowReplacer', () => {
                 }
 
                 assert.closeTo(equalsValue / samplesCount, expectedValue, delta);
+
+                console.log(equalsValue / samplesCount);
             });
         });
 

+ 120 - 0
test/functional-tests/node-transformers/obfuscating-transformers/function-transformer/FunctionTransformer.spec.ts

@@ -52,4 +52,124 @@ describe('FunctionTransformer', () => {
             assert.match(obfuscatedCode, functionBodyMatch);
         });
     });
+
+    describe('assignment pattern as parameter', () => {
+        describe('literal as right value', () => {
+            const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                readFileAsString(__dirname + '/fixtures/assignment-pattern-as-parameter-1.js'),
+                {
+                    ...NO_CUSTOM_NODES_PRESET
+                }
+            );
+            const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
+
+            it('should transform function parameter assignment pattern identifier', () => {
+                const functionParameterMatch: RegExp = /function *\(_0x[a-f0-9]{4,6} *= *0x1\) *\{/;
+                const functionBodyMatch: RegExp = /return *_0x[a-f0-9]{4,6};/;
+
+                assert.match(obfuscatedCode, functionParameterMatch);
+                assert.match(obfuscatedCode, functionBodyMatch);
+            });
+        });
+
+        describe('identifier as right value', () => {
+            const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                readFileAsString(__dirname + '/fixtures/assignment-pattern-as-parameter-2.js'),
+                {
+                    ...NO_CUSTOM_NODES_PRESET
+                }
+            );
+            const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
+
+            const variableDeclarationMatch: RegExp = /var *(_0x[a-f0-9]{4,6}) *= *0x1;/;
+            const functionParameterMatch: RegExp = /function *\((_0x[a-f0-9]{4,6}) *= *(_0x[a-f0-9]{4,6})\) *\{/;
+            const functionBodyMatch: RegExp = /return *(_0x[a-f0-9]{4,6});/;
+
+            const variableDeclarationIdentifierName: string = obfuscatedCode.match(variableDeclarationMatch)![1];
+            const functionParameterIdentifierName: string = obfuscatedCode.match(functionParameterMatch)![1];
+            const functionDefaultParameterIdentifierName: string = obfuscatedCode.match(functionParameterMatch)![2];
+
+            const functionBodyIdentifierName: string = obfuscatedCode.match(functionBodyMatch)![1];
+
+            it('should transform function parameter assignment pattern identifier', () => {
+                assert.match(obfuscatedCode, variableDeclarationMatch);
+                assert.match(obfuscatedCode, functionParameterMatch);
+                assert.match(obfuscatedCode, functionBodyMatch);
+            });
+
+            it('should keep same names for identifier in variable declaration and default value identifier of function parameter', () => {
+                assert.equal(variableDeclarationIdentifierName, functionDefaultParameterIdentifierName);
+            });
+
+            it('should keep same names for identifiers in function params and function body', () => {
+                assert.equal(functionParameterIdentifierName, functionBodyIdentifierName);
+            });
+        });
+
+        describe('identifier as right value', () => {
+            const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                readFileAsString(__dirname + '/fixtures/assignment-pattern-as-parameter-3.js'),
+                {
+                    ...NO_CUSTOM_NODES_PRESET
+                }
+            );
+            const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
+
+            const variableDeclarationMatch: RegExp = /var *(_0x[a-f0-9]{4,6}) *= *0x1;/;
+            const functionParameterMatch: RegExp = /function *\((_0x[a-f0-9]{4,6}), *(_0x[a-f0-9]{4,6}) *= *(_0x[a-f0-9]{4,6})\) *\{/;
+            const functionBodyMatch: RegExp = /return *(_0x[a-f0-9]{4,6}) *\+ *(_0x[a-f0-9]{4,6});/;
+
+            const variableDeclarationIdentifierName: string = obfuscatedCode.match(variableDeclarationMatch)![1];
+            const functionParameterIdentifierName: string = obfuscatedCode.match(functionParameterMatch)![1];
+            const functionDefaultParameterIdentifierName1: string = obfuscatedCode.match(functionParameterMatch)![2];
+            const functionDefaultParameterIdentifierName2: string = obfuscatedCode.match(functionParameterMatch)![3];
+
+            const functionBodyIdentifierName1: string = obfuscatedCode.match(functionBodyMatch)![1];
+            const functionBodyIdentifierName2: string = obfuscatedCode.match(functionBodyMatch)![2];
+
+            it('should transform function parameter assignment pattern identifier', () => {
+                assert.match(obfuscatedCode, variableDeclarationMatch);
+                assert.match(obfuscatedCode, functionParameterMatch);
+                assert.match(obfuscatedCode, functionBodyMatch);
+            });
+
+            it('shouldn\'t keep same names variable declaration identifier and function parameters identifiers', () => {
+                assert.notEqual(variableDeclarationIdentifierName, functionParameterIdentifierName);
+                assert.notEqual(variableDeclarationIdentifierName, functionDefaultParameterIdentifierName1);
+                assert.notEqual(variableDeclarationIdentifierName, functionDefaultParameterIdentifierName2);
+            });
+
+            it('should keep same names for identifier in first function parameter and default value identifier of second function parameter', () => {
+                assert.equal(functionParameterIdentifierName, functionDefaultParameterIdentifierName2);
+            });
+
+            it('should keep same names for identifiers in function params and function body', () => {
+                assert.equal(functionParameterIdentifierName, functionBodyIdentifierName1);
+                assert.equal(functionDefaultParameterIdentifierName1, functionBodyIdentifierName2);
+            });
+        });
+    });
+
+    describe('array pattern as parameter', () => {
+        const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+            readFileAsString(__dirname + '/fixtures/array-pattern-as-parameter.js'),
+            {
+                ...NO_CUSTOM_NODES_PRESET
+            }
+        );
+        const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
+
+        const functionParameterMatch: RegExp = /function *\(\[(_0x[a-f0-9]{4,6}), *(_0x[a-f0-9]{4,6})\]\) *\{/;
+        const functionBodyMatch: RegExp = /return *(_0x[a-f0-9]{4,6}) *\+ *(_0x[a-f0-9]{4,6});/;
+
+        const arrayPatternIdentifierName1: string = obfuscatedCode.match(functionParameterMatch)![1];
+        const arrayPatternIdentifierName2: string = obfuscatedCode.match(functionParameterMatch)![2];
+        const functionBodyIdentifierName1: string = obfuscatedCode.match(functionBodyMatch)![1];
+        const functionBodyIdentifierName2: string = obfuscatedCode.match(functionBodyMatch)![2];
+
+        it('should keep same names for identifiers in function parameter array pattern and function body', () => {
+            assert.equal(arrayPatternIdentifierName1, functionBodyIdentifierName1);
+            assert.equal(arrayPatternIdentifierName2, functionBodyIdentifierName2);
+        });
+    });
 });

+ 5 - 0
test/functional-tests/node-transformers/obfuscating-transformers/function-transformer/fixtures/array-pattern-as-parameter.js

@@ -0,0 +1,5 @@
+(function () {
+    var test = function ([foo, bar]) {
+        return foo + bar;
+    }
+})();

+ 5 - 0
test/functional-tests/node-transformers/obfuscating-transformers/function-transformer/fixtures/assignment-pattern-as-parameter-1.js

@@ -0,0 +1,5 @@
+(function () {
+    var test = function (bar = 1) {
+        return bar;
+    }
+})();

+ 6 - 0
test/functional-tests/node-transformers/obfuscating-transformers/function-transformer/fixtures/assignment-pattern-as-parameter-2.js

@@ -0,0 +1,6 @@
+(function () {
+    var foo = 1;
+    var test = function (bar = foo) {
+        return bar;
+    }
+})();

+ 6 - 0
test/functional-tests/node-transformers/obfuscating-transformers/function-transformer/fixtures/assignment-pattern-as-parameter-3.js

@@ -0,0 +1,6 @@
+(function () {
+    var foo = 1;
+    var test = function (foo, bar = foo) {
+        return foo + bar;
+    }
+})();

+ 30 - 2
test/functional-tests/node-transformers/obfuscating-transformers/variable-declaration-transformer/VariableDeclarationTransformer.spec.ts

@@ -200,11 +200,39 @@ describe('VariableDeclarationTransformer', () => {
         const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
 
         it('shouldn\'t transform object pattern variable declarator', () => {
-            const objectPatternVariableDeclarator: RegExp = /var *\{ *bar *\} *= *\{ *'\\x62\\x61\\x72' *: *'\\x66\\x6f\\x6f' *\};/;
+            const objectPatternVariableDeclaratorMatch: RegExp = /var *\{ *bar *\} *= *\{ *'\\x62\\x61\\x72' *: *'\\x66\\x6f\\x6f' *\};/;
             const variableUsageMatch: RegExp = /console\['\\x6c\\x6f\\x67'\]\(bar\);/;
 
-            assert.match(obfuscatedCode, objectPatternVariableDeclarator);
+            assert.match(obfuscatedCode, objectPatternVariableDeclaratorMatch);
             assert.match(obfuscatedCode, variableUsageMatch);
         });
     });
+
+    describe('array pattern as variable declarator', () => {
+        const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+            readFileAsString(__dirname + '/fixtures/array-pattern.js'),
+            {
+                ...NO_CUSTOM_NODES_PRESET
+            }
+        );
+        const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
+
+        const objectPatternVariableDeclaratorMatch: RegExp = /var *\[ *(_0x([a-f0-9]){4,6}), *(_0x([a-f0-9]){4,6}) *\] *= *\[0x1, *0x2\];/;
+        const variableUsageMatch: RegExp = /console\['\\x6c\\x6f\\x67'\]\((_0x([a-f0-9]){4,6}), *(_0x([a-f0-9]){4,6})\);/;
+
+        const objectPatternIdentifierName1: string = obfuscatedCode.match(objectPatternVariableDeclaratorMatch)![1];
+        const objectPatternIdentifierName2: string = obfuscatedCode.match(objectPatternVariableDeclaratorMatch)![2];
+        const identifierName1: string = obfuscatedCode.match(variableUsageMatch)![1];
+        const identifierName2: string = obfuscatedCode.match(variableUsageMatch)![2];
+
+        it('should transform array pattern variable declarator', () => {
+            assert.match(obfuscatedCode, objectPatternVariableDeclaratorMatch);
+            assert.match(obfuscatedCode, variableUsageMatch);
+        });
+
+        it('should keep same identifier names same for identifiers in variable declaration and after variable declaration', () => {
+            assert.equal(objectPatternIdentifierName1, identifierName1);
+            assert.equal(objectPatternIdentifierName2, identifierName2);
+        });
+    });
 });

+ 5 - 0
test/functional-tests/node-transformers/obfuscating-transformers/variable-declaration-transformer/fixtures/array-pattern.js

@@ -0,0 +1,5 @@
+function foo () {
+    var [bar, baz] = [1, 2];
+
+    console.log(bar, baz);
+}

+ 1 - 0
test/functional-tests/node-transformers/obfuscating-transformers/variable-declaration-transformer/fixtures/object-pattern.js

@@ -1,4 +1,5 @@
 (function () {
     var { bar } = { bar: 'foo' };
+
     console.log(bar);
 })();

+ 2 - 0
test/index.spec.ts

@@ -13,6 +13,8 @@ import './unit-tests/obfuscation-result/ObfuscationResult.spec';
 import './unit-tests/options/options-normalizer/OptionsNormalizer.spec';
 import './unit-tests/source-map-corrector/SourceMapCorrector.spec';
 import './unit-tests/stack-trace-analyzer/stack-trace-analyzer/StackTraceAnalyzer.spec';
+import './unit-tests/storages/ArrayStorage.spec';
+import './unit-tests/storages/MapStorage.spec';
 import './unit-tests/utils/crypt-utils/CryptUtils.spec';
 import './unit-tests/utils/random-generator-utils/RandomGeneratorUtils.spec';
 import './unit-tests/utils/utils/Utils.spec';

+ 126 - 0
test/unit-tests/storages/ArrayStorage.spec.ts

@@ -0,0 +1,126 @@
+import { assert } from 'chai';
+
+import { IStorage } from '../../../src/interfaces/storages/IStorage';
+
+import { ArrayStorage } from '../../../src/storages/ArrayStorage';
+
+class ConcreteStorage extends ArrayStorage <string> {
+    constructor () {
+        super();
+    }
+}
+
+describe('ArrayStorage', () => {
+    describe('initialize (...args: any[]): void', () => {
+        it('should throws an error when storage isn\'t initialized', () => {
+            const storage: IStorage <string> = new ConcreteStorage();
+
+            assert.throws(() => storage.set(0, 'foo'), Error);
+        });
+    });
+
+    describe('getStorage (): T[]', () => {
+        it('should returns storage', () => {
+            const storage: IStorage <string> = new ConcreteStorage();
+
+            storage.initialize();
+
+            assert.instanceOf(storage.getStorage(), Array);
+        });
+    });
+
+    describe('get (key: number): T', () => {
+        it('should returns value from storage by key', () => {
+            const storage: IStorage <string> = new ConcreteStorage();
+
+            storage.initialize();
+            storage.set(0, 'foo');
+
+            assert.equal(storage.get(0), 'foo');
+        });
+
+        it('should throw an error if value isn\'t exist', () => {
+            const storage: IStorage <string> = new ConcreteStorage();
+
+            storage.initialize();
+
+            assert.throws(() => storage.get(0), Error);
+        });
+    });
+
+    describe('getLength (): number', () => {
+        it('should returns length of storage', () => {
+            const storage: IStorage <string> = new ConcreteStorage();
+
+            storage.initialize();
+            storage.set(0, 'foo');
+
+            assert.equal(storage.getLength(), 1);
+        });
+    });
+
+    describe('getKeyOf (value: T): number | null', () => {
+        it('should returns key of string value', () => {
+            const storage: IStorage <string> = new ConcreteStorage();
+
+            storage.initialize();
+            storage.set(0, 'foo');
+
+            assert.equal(storage.getKeyOf('foo'), 0);
+        });
+
+        it('should returns key of object if objects in `set` and `get` are two linked objects', () => {
+            const storage: IStorage <Object> = new ConcreteStorage();
+            const object: Object = {
+                foo: 'bar'
+            };
+
+            storage.initialize();
+            storage.set(0, object);
+
+            assert.equal(storage.getKeyOf(object), 0);
+        });
+
+        it('should return `null` if objects in `set` and `get` are two equal objects', () => {
+            const storage: IStorage <Object> = new ConcreteStorage();
+
+            storage.initialize();
+            storage.set(0, {
+                foo: 'bar'
+            });
+
+            assert.equal(storage.getKeyOf({
+                foo: 'bar'
+            }), null);
+        });
+    });
+
+    describe('set (key: number, value: T): void', () => {
+        it('should set value to the storage', () => {
+            const storage: IStorage <string> = new ConcreteStorage();
+
+            storage.initialize();
+            storage.set(0, 'foo');
+
+            assert.equal(storage.get(0), 'foo');
+            assert.equal(storage.getLength(), 1);
+        });
+    });
+
+    describe('mergeWith (storage: this, mergeId: boolean = false): void', () => {
+        it('should merge two storages', () => {
+            const storage1: IStorage <string> = new ConcreteStorage();
+            const storage2: IStorage <string> = new ConcreteStorage();
+
+            storage1.initialize();
+            storage1.set(0, 'foo');
+
+            storage2.initialize();
+            storage2.set(1, 'bar');
+
+            storage1.mergeWith(storage2, false);
+
+            assert.deepEqual(storage1.getStorage(), ['foo', 'bar']);
+        });
+    });
+});

+ 126 - 0
test/unit-tests/storages/MapStorage.spec.ts

@@ -0,0 +1,126 @@
+import { assert } from 'chai';
+
+import { IStorage } from '../../../src/interfaces/storages/IStorage';
+
+import { MapStorage } from '../../../src/storages/MapStorage';
+
+class ConcreteStorage extends MapStorage <string> {
+    constructor () {
+        super();
+    }
+}
+
+describe('MapStorage', () => {
+    describe('initialize (...args: any[]): void', () => {
+        it('should throws an error when storage isn\'t initialized', () => {
+            const storage: IStorage <string> = new ConcreteStorage();
+
+            assert.throws(() => storage.set('foo', 'bar'), Error);
+        });
+    });
+
+    describe('getStorage (): Map <string | number, T>', () => {
+        it('should returns storage', () => {
+            const storage: IStorage <string> = new ConcreteStorage();
+
+            storage.initialize();
+
+            assert.instanceOf(storage.getStorage(), Map);
+        });
+    });
+
+    describe('get (key: string | number): T', () => {
+        it('should returns value from storage by key', () => {
+            const storage: IStorage <string> = new ConcreteStorage();
+
+            storage.initialize();
+            storage.set('foo', 'bar');
+
+            assert.equal(storage.get('foo'), 'bar');
+        });
+
+        it('should throw an error if value isn\'t exist', () => {
+            const storage: IStorage <string> = new ConcreteStorage();
+
+            storage.initialize();
+
+            assert.throws(() => storage.get('foo'), Error);
+        });
+    });
+
+    describe('getLength (): number', () => {
+        it('should returns length of storage', () => {
+            const storage: IStorage <string> = new ConcreteStorage();
+
+            storage.initialize();
+            storage.set('foo', 'bar');
+
+            assert.equal(storage.getLength(), 1);
+        });
+    });
+
+    describe('getKeyOf (value: T): string | number | null', () => {
+        it('should returns key of string value', () => {
+            const storage: IStorage <string> = new ConcreteStorage();
+
+            storage.initialize();
+            storage.set('foo', 'bar');
+
+            assert.equal(storage.getKeyOf('bar'), 'foo');
+        });
+
+        it('should returns key of object if objects in `set` and `get` are two linked objects', () => {
+            const storage: IStorage <Object> = new ConcreteStorage();
+            const object: Object = {
+                bar: 'baz'
+            };
+
+            storage.initialize();
+            storage.set('foo', object);
+
+            assert.equal(storage.getKeyOf(object), 'foo');
+        });
+
+        it('should return `null` if objects in `set` and `get` are two equal objects', () => {
+            const storage: IStorage <Object> = new ConcreteStorage();
+
+            storage.initialize();
+            storage.set('foo', {
+                bar: 'baz'
+            });
+
+            assert.equal(storage.getKeyOf({
+                bar: 'baz'
+            }), null);
+        });
+    });
+
+    describe('set (key: string | number, value: T): void', () => {
+        it('should set value to the storage', () => {
+            const storage: IStorage <string> = new ConcreteStorage();
+
+            storage.initialize();
+            storage.set('foo', 'bar');
+
+            assert.equal(storage.get('foo'), 'bar');
+            assert.equal(storage.getLength(), 1);
+        });
+    });
+
+    describe('mergeWith (storage: this, mergeId: boolean = false): void', () => {
+        it('should merge two storages', () => {
+            const storage1: IStorage <string> = new ConcreteStorage();
+            const storage2: IStorage <string> = new ConcreteStorage();
+
+            storage1.initialize();
+            storage1.set('foo', 'bar');
+
+            storage2.initialize();
+            storage2.set('baz', 'quux');
+
+            storage1.mergeWith(storage2, false);
+
+            assert.deepEqual(Array.from(storage1.getStorage()), [['foo', 'bar'], ['baz', 'quux']]);
+        });
+    });
+});

+ 0 - 28
test/unit-tests/utils/utils/Utils.spec.ts

@@ -53,34 +53,6 @@ describe('Utils', () => {
         });
     });
 
-    describe('mapGetFirstKeyOf(map: Map <any, any>, value: any): any', () => {
-        it('should returns key of map item', () => {
-            const map: Map <any, any> = new Map();
-
-            map.set('number1', 1);
-            map.set('number2', 2);
-            map.set('number3', 2);
-
-            map.set('string1', 'foo');
-            map.set('string2', 'bar');
-
-            map.set('object1', {item: 'value'});
-            map.set('object2', {item: 'value'});
-            map.set({key: 'object'}, [1, 2, 3]);
-
-            assert.deepEqual(Utils.mapGetFirstKeyOf(map, 1), 'number1');
-            assert.deepEqual(Utils.mapGetFirstKeyOf(map, 2), 'number2');
-
-            assert.deepEqual(Utils.mapGetFirstKeyOf(map, 'foo'), 'string1');
-            assert.deepEqual(Utils.mapGetFirstKeyOf(map, 'bar'), 'string2');
-
-            assert.deepEqual(Utils.mapGetFirstKeyOf(map, {item: 'value'}), 'object1');
-            assert.deepEqual(Utils.mapGetFirstKeyOf(map, [1, 2, 3]), {key: 'object'});
-
-            assert.deepEqual(Utils.mapGetFirstKeyOf(map, 3), null);
-        });
-    });
-
     describe('stringRotate (string: string, times: number): string', () => {
         let string: string;
 

+ 1 - 1
webpack.config.js

@@ -6,7 +6,7 @@ const webpack = require('webpack');
 const CheckerPlugin = require('awesome-typescript-loader').CheckerPlugin;
 
 function getLicenseText () {
-    return "/*\nCopyright (C) 2016 Timofey Kachalov <[email protected]>\n\n" +
+    return "/*\nCopyright (C) 2017 Timofey Kachalov <[email protected]>\n\n" +
         fs.readFileSync('./LICENSE.BSD', 'utf8') + "\n*/";
 }
 

Alguns ficheiros não foram mostrados porque muitos ficheiros mudaram neste diff