Bläddra i källkod

Merge branch 'dev' into yarn

Conflicts:
	dist/index.js
	package.json
	scripts/test-coveralls
	scripts/test-full
	scripts/test-mocha
sanex3339 8 år sedan
förälder
incheckning
abd3bfbbf2
51 ändrade filer med 1122 tillägg och 365 borttagningar
  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
 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
 v0.8.6
 ---
 ---
 * additional fixes for https://github.com/javascript-obfuscator/javascript-obfuscator/issues/29
 * 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.
 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;
 * without any limits and sending data to a server;
-* compatible with ES6;
+* compatible with `es2015`;
 * tested on Angular2 bundle.
 * tested on Angular2 bundle.
 
 
 Online version: [javascriptobfuscator.herokuapp.com](https://javascriptobfuscator.herokuapp.com)
 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)
 [![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)
 [![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
 ## :warning: Important
 #####Obfuscate only the code that belongs to you. 
 #####Obfuscate only the code that belongs to you. 
 
 
@@ -122,7 +124,7 @@ For available options see [options](#options).
 Usage:
 Usage:
 ```sh
 ```sh
 javascript-obfuscator in.js [options]
 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`
 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`
 * `true` (`boolean`): encode `stringArray` values using `base64`
 * `false` (`boolean`): don't encode `stringArray` values
 * `false` (`boolean`): don't encode `stringArray` values
 * `'base64'` (`string`): encode `stringArray` values using `base64`
 * `'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`
 ### `stringArrayThreshold`
 Type: `number` Default: `0.8` Min: `0` Max: `1`
 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
 ## Preset Options
 ### High obfuscation, low performance
 ### High obfuscation, low performance
 
 
-Performance will 50-100% slower then without obfuscation
+Performance will 50-100% slower than without obfuscation
 
 
 ```javascript
 ```javascript
 {
 {
@@ -453,7 +455,7 @@ Performance will 50-100% slower then without obfuscation
 
 
 ### Medium obfuscation, optimal performance
 ### Medium obfuscation, optimal performance
 
 
-Performance will 30-35% slower then without obfuscation
+Performance will 30-35% slower than without obfuscation
 
 
 ```javascript
 ```javascript
 {
 {
@@ -474,7 +476,7 @@ Performance will 30-35% slower then without obfuscation
 
 
 ### Low obfuscation, High performance
 ### Low obfuscation, High performance
 
 
-Performance will slightly slower then without obfuscation
+Performance will slightly slower than without obfuscation
 
 
 ```javascript
 ```javascript
 {
 {
@@ -493,7 +495,7 @@ Performance will slightly slower then without obfuscation
 ```
 ```
 
 
 ## License
 ## 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
 Redistribution and use in source and binary forms, with or without
 modification, are permitted provided that the following conditions are met:
 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
 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 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
 (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.

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 230 - 167
dist/index.js


+ 7 - 7
package.json

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

+ 1 - 0
scripts/test-coveralls

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

+ 1 - 0
scripts/test-full

@@ -1,5 +1,6 @@
 #!/bin/bash
 #!/bin/bash
 
 
+yarn run test:removeTmpDir &&
 yarn run test:compile &&
 yarn run test:compile &&
 node test-tmp/test/dev/dev.js &&
 node test-tmp/test/dev/dev.js &&
 $(yarn bin)/istanbul cover $(yarn bin)/_mocha -- test-tmp/test/index.spec.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
 #!/bin/bash
 
 
+yarn run test:removeTmpDir &&
 yarn run test:compile &&
 yarn run test:compile &&
 $(yarn bin)/mocha test-tmp/test/index.spec.js &&
 $(yarn bin)/mocha test-tmp/test/index.spec.js &&
 yarn run test:removeTmpDir
 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 { TNodeTransformersFactory } from './types/container/TNodeTransformersFactory';
 import { TVisitorDirection } from './types/TVisitorDirection';
 import { TVisitorDirection } from './types/TVisitorDirection';
+import { TVisitorFunction } from './types/TVisitorFunction';
 
 
 import { ICustomNodeGroup } from './interfaces/custom-nodes/ICustomNodeGroup';
 import { ICustomNodeGroup } from './interfaces/custom-nodes/ICustomNodeGroup';
 import { IObfuscationEventEmitter } from './interfaces/event-emitters/IObfuscationEventEmitter';
 import { IObfuscationEventEmitter } from './interfaces/event-emitters/IObfuscationEventEmitter';
 import { IObfuscator } from './interfaces/IObfuscator';
 import { IObfuscator } from './interfaces/IObfuscator';
 import { IOptions } from './interfaces/options/IOptions';
 import { IOptions } from './interfaces/options/IOptions';
-import { INodeTransformer } from './interfaces/node-transformers/INodeTransformer';
 import { IStackTraceAnalyzer } from './interfaces/stack-trace-analyzer/IStackTraceAnalyzer';
 import { IStackTraceAnalyzer } from './interfaces/stack-trace-analyzer/IStackTraceAnalyzer';
 import { IStackTraceData } from './interfaces/stack-trace-analyzer/IStackTraceData';
 import { IStackTraceData } from './interfaces/stack-trace-analyzer/IStackTraceData';
 import { IStorage } from './interfaces/storages/IStorage';
 import { IStorage } from './interfaces/storages/IStorage';
 
 
 import { NodeTransformers } from './enums/container/NodeTransformers';
 import { NodeTransformers } from './enums/container/NodeTransformers';
-import { NodeType } from './enums/NodeType';
 import { ObfuscationEvents } from './enums/ObfuscationEvents';
 import { ObfuscationEvents } from './enums/ObfuscationEvents';
 import { VisitorDirection } from './enums/VisitorDirection';
 import { VisitorDirection } from './enums/VisitorDirection';
 
 
@@ -27,40 +26,34 @@ import { NodeUtils } from './node/NodeUtils';
 @injectable()
 @injectable()
 export class Obfuscator implements IObfuscator {
 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>}
      * @type {IStorage<ICustomNodeGroup>}
@@ -139,21 +132,17 @@ export class Obfuscator implements IObfuscator {
         if (this.options.controlFlowFlattening) {
         if (this.options.controlFlowFlattening) {
             astTree = this.transformAstTree(
             astTree = this.transformAstTree(
                 astTree,
                 astTree,
-                VisitorDirection.leave,
-                this.nodeTransformersFactory(Obfuscator.controlFlowTransformersMap)
+                Obfuscator.controlFlowTransformersList
             );
             );
         }
         }
 
 
         // second pass: nodes obfuscation
         // second pass: nodes obfuscation
         astTree = this.transformAstTree(
         astTree = this.transformAstTree(
             astTree,
             astTree,
-            VisitorDirection.enter,
-            this.nodeTransformersFactory(
-                new Map([
-                    ...Obfuscator.convertingTransformersMap,
-                    ...Obfuscator.obfuscatingTransformersMap
-                ])
-            )
+            [
+                ...Obfuscator.convertingTransformersList,
+                ...Obfuscator.obfuscatingTransformersList
+            ]
         );
         );
 
 
         this.obfuscationEventEmitter.emit(ObfuscationEvents.AfterObfuscation, astTree, stackTraceData);
         this.obfuscationEventEmitter.emit(ObfuscationEvents.AfterObfuscation, astTree, stackTraceData);
@@ -163,26 +152,67 @@ export class Obfuscator implements IObfuscator {
 
 
     /**
     /**
      * @param astTree
      * @param astTree
-     * @param direction
-     * @param nodeTransformersConcreteFactory
+     * @param nodeTransformers
      */
      */
     private transformAstTree (
     private transformAstTree (
         astTree: ESTree.Program,
         astTree: ESTree.Program,
-        direction: TVisitorDirection,
-        nodeTransformersConcreteFactory: (nodeType: string) => INodeTransformer[]
+        nodeTransformers: NodeTransformers[]
     ): ESTree.Program {
     ): ESTree.Program {
+        const visitors: estraverse.Visitor[] = nodeTransformers
+            .map((nodeTransformer: NodeTransformers): estraverse.Visitor => {
+                return this.nodeTransformersFactory(nodeTransformer).getVisitor();
+            });
+
         estraverse.replace(astTree, {
         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);
         .whenTargetNamed(NodeTransformers.VariableDeclarationTransformer);
 
 
     // node transformers factory
     // 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();
             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' {
 declare module 'estree' {
     interface BaseNode {
     interface BaseNode {
-        obfuscated?: boolean;
+        obfuscatedNode?: boolean;
         parentNode?: ESTree.Node;
         parentNode?: ESTree.Node;
-        skippedByControlFlow?: boolean;
     }
     }
 
 
     interface SimpleLiteral extends ESTree.BaseNode, ESTree.BaseExpression {
     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',
     ArrayExpression: 'ArrayExpression',
     ArrowFunctionExpression: 'ArrowFunctionExpression',
     ArrowFunctionExpression: 'ArrowFunctionExpression',
     AssignmentExpression: 'AssignmentExpression',
     AssignmentExpression: 'AssignmentExpression',
+    AssignmentPattern: 'AssignmentPattern',
     BinaryExpression: 'BinaryExpression',
     BinaryExpression: 'BinaryExpression',
     BlockStatement: 'BlockStatement',
     BlockStatement: 'BlockStatement',
     BreakStatement: 'BreakStatement',
     BreakStatement: 'BreakStatement',
@@ -22,6 +23,7 @@ export const NodeType: any = Utils.strEnumify({
     MemberExpression: 'MemberExpression',
     MemberExpression: 'MemberExpression',
     MethodDefinition: 'MethodDefinition',
     MethodDefinition: 'MethodDefinition',
     ObjectExpression: 'ObjectExpression',
     ObjectExpression: 'ObjectExpression',
+    ObjectPattern: 'ObjectPattern',
     Program: 'Program',
     Program: 'Program',
     Property: 'Property',
     Property: 'Property',
     ReturnStatement: 'ReturnStatement',
     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';
 import * as ESTree from 'estree';
 
 
 export interface INodeTransformer {
 export interface INodeTransformer {
+    /**
+     * @returns {estraverse.Visitor}
+     */
+    getVisitor (): estraverse.Visitor;
+
     /**
     /**
      * @param node
      * @param node
      * @param parentNode
      * @param parentNode

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

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

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

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

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

@@ -1,6 +1,7 @@
 import { injectable, inject } from 'inversify';
 import { injectable, inject } from 'inversify';
 import { ServiceIdentifiers } from '../../container/ServiceIdentifiers';
 import { ServiceIdentifiers } from '../../container/ServiceIdentifiers';
 
 
+import * as estraverse from 'estraverse';
 import * as ESTree from 'estree';
 import * as ESTree from 'estree';
 
 
 import { TCustomNodeFactory } from '../../types/container/TCustomNodeFactory';
 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 blockStatementNode
+     * @param parentNode
      * @returns {ESTree.Node}
      * @returns {ESTree.Node}
      */
      */
-    public transformNode (blockStatementNode: ESTree.BlockStatement): ESTree.Node {
+    public transformNode (blockStatementNode: ESTree.BlockStatement, parentNode: ESTree.Node): ESTree.Node {
         if (
         if (
             RandomGeneratorUtils.getRandomFloat(0, 1) > this.options.controlFlowFlatteningThreshold ||
             RandomGeneratorUtils.getRandomFloat(0, 1) > this.options.controlFlowFlatteningThreshold ||
             BlockStatementControlFlowTransformer.blockStatementHasProhibitedStatements(blockStatementNode)
             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 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 functionNode
+     * @param parentNode
      * @returns {ESTree.Node}
      * @returns {ESTree.Node}
      */
      */
-    public transformNode (functionNode: ESTree.Function): ESTree.Node {
+    public transformNode (functionNode: ESTree.Function, parentNode: ESTree.Node): ESTree.Node {
         if (Node.isArrowFunctionExpressionNode(functionNode)) {
         if (Node.isArrowFunctionExpressionNode(functionNode)) {
             return functionNode;
             return functionNode;
         }
         }

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

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

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

@@ -1,6 +1,7 @@
 import { injectable, inject } from 'inversify';
 import { injectable, inject } from 'inversify';
 import { ServiceIdentifiers } from '../../container/ServiceIdentifiers';
 import { ServiceIdentifiers } from '../../container/ServiceIdentifiers';
 
 
+import * as estraverse from 'estraverse';
 import * as ESTree from 'estree';
 import * as ESTree from 'estree';
 
 
 import { IOptions } from '../../interfaces/options/IOptions';
 import { IOptions } from '../../interfaces/options/IOptions';
@@ -35,6 +36,19 @@ export class MethodDefinitionTransformer extends AbstractNodeTransformer {
         super(options);
         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:
      * replaces:
      *     object.identifier = 1;
      *     object.identifier = 1;

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

@@ -1,6 +1,7 @@
 import { injectable, inject } from 'inversify';
 import { injectable, inject } from 'inversify';
 import { ServiceIdentifiers } from '../../container/ServiceIdentifiers';
 import { ServiceIdentifiers } from '../../container/ServiceIdentifiers';
 
 
+import * as estraverse from 'estraverse';
 import * as ESTree from 'estree';
 import * as ESTree from 'estree';
 
 
 import { IOptions } from '../../interfaces/options/IOptions';
 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 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 templateLiteralNode
      * @param parentNode
      * @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);
         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 catchClauseNode
+     * @param parentNode
      * @returns {ESTree.Node}
      * @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++;
         const nodeIdentifier: number = this.nodeIdentifier++;
 
 
         this.storeCatchClauseParam(catchClauseNode, nodeIdentifier);
         this.storeCatchClauseParam(catchClauseNode, nodeIdentifier);
@@ -76,7 +90,7 @@ export class CatchClauseTransformer extends AbstractNodeTransformer {
 
 
                     if (node.name !== newNodeName) {
                     if (node.name !== newNodeName) {
                         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);
         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 functionDeclarationNode
      * @param parentNode
      * @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);
         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 functionNode
+     * @param parentNode
      * @returns {ESTree.Node}
      * @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++;
         const nodeIdentifier: number = this.nodeIdentifier++;
 
 
         this.storeFunctionParams(functionNode, nodeIdentifier);
         this.storeFunctionParams(functionNode, nodeIdentifier);
@@ -61,9 +79,23 @@ export class FunctionTransformer extends AbstractNodeTransformer {
     private storeFunctionParams (functionNode: ESTree.Function, nodeIdentifier: number): void {
     private storeFunctionParams (functionNode: ESTree.Function, nodeIdentifier: number): void {
         functionNode.params
         functionNode.params
             .forEach((paramsNode: ESTree.Node) => {
             .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) {
                     if (node.name !== newNodeName) {
                         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);
         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 labeledStatementNode
+     * @param parentNode
      * @returns {ESTree.Node}
      * @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++;
         const nodeIdentifier: number = this.nodeIdentifier++;
 
 
         this.storeLabeledStatementName(labeledStatementNode, 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 { ServiceIdentifiers } from '../../container/ServiceIdentifiers';
 
 
 import * as escodegen from 'escodegen';
 import * as escodegen from 'escodegen';
+import * as estraverse from 'estraverse';
 import * as ESTree from 'estree';
 import * as ESTree from 'estree';
 
 
 import { IOptions } from '../../interfaces/options/IOptions';
 import { IOptions } from '../../interfaces/options/IOptions';
@@ -32,6 +33,19 @@ export class LiteralTransformer extends AbstractNodeTransformer {
         this.replacersFactory = replacersFactory;
         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 literalNode
      * @param parentNode
      * @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 { ServiceIdentifiers } from '../../container/ServiceIdentifiers';
 
 
 import * as escodegen from 'escodegen';
 import * as escodegen from 'escodegen';
+import * as estraverse from 'estraverse';
 import * as ESTree from 'estree';
 import * as ESTree from 'estree';
 
 
 import { IOptions } from '../../interfaces/options/IOptions';
 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 objectExpressionNode
+     * @param parentNode
      * @returns {ESTree.Node}
      * @returns {ESTree.Node}
      */
      */
-    public transformNode (objectExpressionNode: ESTree.ObjectExpression): ESTree.Node {
+    public transformNode (objectExpressionNode: ESTree.ObjectExpression, parentNode: ESTree.Node): ESTree.Node {
         objectExpressionNode.properties
         objectExpressionNode.properties
             .forEach((property: ESTree.Property) => {
             .forEach((property: ESTree.Property) => {
                 if (property.shorthand) {
                 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);
         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 variableDeclarationNode
      * @param parentNode
      * @param parentNode
@@ -83,9 +96,13 @@ export class VariableDeclarationTransformer extends AbstractNodeTransformer {
     private storeVariableNames (variableDeclarationNode: ESTree.VariableDeclaration, nodeIdentifier: number): void {
     private storeVariableNames (variableDeclarationNode: ESTree.VariableDeclaration, nodeIdentifier: number): void {
         variableDeclarationNode.declarations
         variableDeclarationNode.declarations
             .forEach((declarationNode: ESTree.VariableDeclarator) => {
             .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, {
         estraverse.replace(scopeNode, {
             enter: (node: ESTree.Node, parentNode: ESTree.Node): any => {
             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);
                     const newNodeName: string = this.identifierReplacer.replace(node.name, nodeIdentifier);
 
 
                     if (node.name !== newNodeName) {
                     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);
             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);
         this.stringLiteralHexadecimalIndexCache.set(value, hexadecimalIndex);
 
 
         return hexadecimalIndex;
         return hexadecimalIndex;

+ 41 - 0
src/node/Node.ts

@@ -13,6 +13,14 @@ export class Node {
         return node.type === NodeType.ArrowFunctionExpression;
         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
      * @param node
      * @returns {boolean}
      * @returns {boolean}
@@ -37,6 +45,14 @@ export class Node {
         return node.type === NodeType.CallExpression;
         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
      * @param node
      * @returns {boolean}
      * @returns {boolean}
@@ -122,6 +138,22 @@ export class Node {
         return node.type === NodeType.MemberExpression;
         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
      * @param node
      * @returns {boolean}
      * @returns {boolean}
@@ -168,6 +200,15 @@ export class Node {
         return !parentNodeIsPropertyNode && !parentNodeIsMemberExpressionNode && !Node.isLabelIdentifierNode(node, parentNode);
         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
      * @param node

+ 1 - 1
src/node/NodeUtils.ts

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

+ 22 - 9
src/storages/ArrayStorage.ts

@@ -8,6 +8,12 @@ import { RandomGeneratorUtils } from '../utils/RandomGeneratorUtils';
 
 
 @injectable()
 @injectable()
 export abstract class ArrayStorage <T> implements IStorage <T> {
 export abstract class ArrayStorage <T> implements IStorage <T> {
+    /**
+     * @type {T[]}
+     */
+    @initializable()
+    protected storage: T[];
+
     /**
     /**
      * @type {string}
      * @type {string}
      */
      */
@@ -15,10 +21,9 @@ export abstract class ArrayStorage <T> implements IStorage <T> {
     protected storageId: string;
     protected storageId: string;
 
 
     /**
     /**
-     * @type {T[]}
+     * @type {number}
      */
      */
-    @initializable()
-    protected storage: T[];
+    private storageLength: number = 0;
 
 
     /**
     /**
      * @param key
      * @param key
@@ -36,17 +41,19 @@ export abstract class ArrayStorage <T> implements IStorage <T> {
 
 
     /**
     /**
      * @param value
      * @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}
      * @returns {number}
      */
      */
     public getLength (): 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 key
      * @param value
      * @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 { initializable } from '../decorators/Initializable';
 
 
 import { RandomGeneratorUtils } from '../utils/RandomGeneratorUtils';
 import { RandomGeneratorUtils } from '../utils/RandomGeneratorUtils';
-import { Utils } from '../utils/Utils';
 
 
 @injectable()
 @injectable()
 export abstract class MapStorage <T> implements IStorage <T> {
 export abstract class MapStorage <T> implements IStorage <T> {
@@ -40,7 +39,13 @@ export abstract class MapStorage <T> implements IStorage <T> {
      * @returns {string | number | null}
      * @returns {string | number | null}
      */
      */
     public getKeyOf (value: T): 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';
 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 { JSFuck } from '../enums/JSFuck';
 
 
 import { RandomGeneratorUtils } from './RandomGeneratorUtils';
 import { RandomGeneratorUtils } from './RandomGeneratorUtils';
@@ -92,21 +91,6 @@ export class Utils {
         return number % 1 === 0;
         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
      * @param obj
      * @returns {T}
      * @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, controlFlowStorageCallRegExp1);
                     assert.match(obfuscatedCode, controlFlowStorageCallRegExp2);
                     assert.match(obfuscatedCode, controlFlowStorageCallRegExp2);
-                    assert.isOk(firstMatch);
-                    assert.isOk(secondMatch);
 
 
                     if (firstMatch === secondMatch) {
                     if (firstMatch === secondMatch) {
                         equalsValue++;
                         equalsValue++;
@@ -67,6 +65,8 @@ describe('BinaryExpressionControlFlowReplacer', () => {
                 }
                 }
 
 
                 assert.closeTo(equalsValue / samplesCount, expectedValue, delta);
                 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, controlFlowStorageCallRegExp1);
                     assert.match(obfuscatedCode, controlFlowStorageCallRegExp2);
                     assert.match(obfuscatedCode, controlFlowStorageCallRegExp2);
-                    assert.isOk(firstMatch);
-                    assert.isOk(secondMatch);
 
 
                     if (firstMatch === secondMatch) {
                     if (firstMatch === secondMatch) {
                         equalsValue++;
                         equalsValue++;
@@ -67,6 +65,8 @@ describe('CallExpressionControlFlowReplacer', () => {
                 }
                 }
 
 
                 assert.closeTo(equalsValue / samplesCount, expectedValue, delta);
                 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, controlFlowStorageCallRegExp1);
                     assert.match(obfuscatedCode, controlFlowStorageCallRegExp2);
                     assert.match(obfuscatedCode, controlFlowStorageCallRegExp2);
-                    assert.isOk(firstMatch);
-                    assert.isOk(secondMatch);
 
 
                     if (firstMatch === secondMatch) {
                     if (firstMatch === secondMatch) {
                         equalsValue++;
                         equalsValue++;
@@ -67,6 +65,8 @@ describe('LogicalExpressionControlFlowReplacer', () => {
                 }
                 }
 
 
                 assert.closeTo(equalsValue / samplesCount, expectedValue, delta);
                 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);
             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();
         const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
 
 
         it('shouldn\'t transform object pattern variable declarator', () => {
         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\);/;
             const variableUsageMatch: RegExp = /console\['\\x6c\\x6f\\x67'\]\(bar\);/;
 
 
-            assert.match(obfuscatedCode, objectPatternVariableDeclarator);
+            assert.match(obfuscatedCode, objectPatternVariableDeclaratorMatch);
             assert.match(obfuscatedCode, variableUsageMatch);
             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 () {
 (function () {
     var { bar } = { bar: 'foo' };
     var { bar } = { bar: 'foo' };
+
     console.log(bar);
     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/options/options-normalizer/OptionsNormalizer.spec';
 import './unit-tests/source-map-corrector/SourceMapCorrector.spec';
 import './unit-tests/source-map-corrector/SourceMapCorrector.spec';
 import './unit-tests/stack-trace-analyzer/stack-trace-analyzer/StackTraceAnalyzer.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/crypt-utils/CryptUtils.spec';
 import './unit-tests/utils/random-generator-utils/RandomGeneratorUtils.spec';
 import './unit-tests/utils/random-generator-utils/RandomGeneratorUtils.spec';
 import './unit-tests/utils/utils/Utils.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', () => {
     describe('stringRotate (string: string, times: number): string', () => {
         let string: string;
         let string: string;
 
 

+ 1 - 1
webpack.config.js

@@ -6,7 +6,7 @@ const webpack = require('webpack');
 const CheckerPlugin = require('awesome-typescript-loader').CheckerPlugin;
 const CheckerPlugin = require('awesome-typescript-loader').CheckerPlugin;
 
 
 function getLicenseText () {
 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*/";
         fs.readFileSync('./LICENSE.BSD', 'utf8') + "\n*/";
 }
 }
 
 

Vissa filer visades inte eftersom för många filer har ändrats