瀏覽代碼

Fixed https://github.com/javascript-obfuscator/javascript-obfuscator/issues/271

sanex3339 7 年之前
父節點
當前提交
76d5ea8c27
共有 20 個文件被更改,包括 591 次插入212 次删除
  1. 1 0
      CHANGELOG.md
  2. 0 0
      dist/index.browser.js
  3. 0 0
      dist/index.cli.js
  4. 0 0
      dist/index.js
  5. 1 1
      package.json
  6. 2 0
      src/container/ServiceIdentifiers.ts
  7. 1 1
      src/container/modules/analyzers/AnalyzersModule.ts
  8. 21 0
      src/container/modules/node-transformers/ConvertingTransformersModule.ts
  9. 4 0
      src/enums/node-transformers/converting-transformers/properties-extractors/PropertiesExtractor.ts
  10. 13 0
      src/interfaces/node-transformers/converting-transformers/properties-extractors/IPropertiesExtractor.ts
  11. 21 210
      src/node-transformers/converting-transformers/ObjectExpressionKeysTransformer.ts
  12. 228 0
      src/node-transformers/converting-transformers/properties-extractors/AbstractPropertiesExtractor.ts
  13. 45 0
      src/node-transformers/converting-transformers/properties-extractors/AssignmentExpressionPropertiesExtractor.ts
  14. 117 0
      src/node-transformers/converting-transformers/properties-extractors/VariableDeclaratorPropertiesExtractor.ts
  15. 5 0
      src/types/container/node-transformers/TPropertiesExtractorFactory.d.ts
  16. 115 0
      test/functional-tests/node-transformers/converting-transformers/object-expression-keys-transformer/ObjectExpressionKeysTransformer.spec.ts
  17. 4 0
      test/functional-tests/node-transformers/converting-transformers/object-expression-keys-transformer/fixtures/variable-declarator-with-object-call-1.js
  18. 5 0
      test/functional-tests/node-transformers/converting-transformers/object-expression-keys-transformer/fixtures/variable-declarator-with-object-call-2.js
  19. 4 0
      test/functional-tests/node-transformers/converting-transformers/object-expression-keys-transformer/fixtures/variable-declarator-with-object-call-ignore-1.js
  20. 4 0
      test/functional-tests/node-transformers/converting-transformers/object-expression-keys-transformer/fixtures/variable-declarator-with-object-call-ignore-2.js

+ 1 - 0
CHANGELOG.md

@@ -3,6 +3,7 @@ Change Log
 v0.17.0
 ---
 * **Browser version**: Added browser version dist
+* Fixed https://github.com/javascript-obfuscator/javascript-obfuscator/issues/271
 * Fixed https://github.com/javascript-obfuscator/javascript-obfuscator/issues/264
 * Fixed https://github.com/javascript-obfuscator/javascript-obfuscator/issues/260
 * Fixed https://github.com/javascript-obfuscator/javascript-obfuscator/issues/252

File diff suppressed because it is too large
+ 0 - 0
dist/index.browser.js


File diff suppressed because it is too large
+ 0 - 0
dist/index.cli.js


File diff suppressed because it is too large
+ 0 - 0
dist/index.js


+ 1 - 1
package.json

@@ -1,6 +1,6 @@
 {
   "name": "javascript-obfuscator",
-  "version": "0.17.0-dev.0",
+  "version": "0.17.0-dev.1",
   "description": "JavaScript obfuscator",
   "keywords": [
     "obfuscator",

+ 2 - 0
src/container/ServiceIdentifiers.ts

@@ -11,6 +11,7 @@ export enum ServiceIdentifiers {
     Factory__INodeTransformer = 'Factory<INodeTransformer[]>',
     Factory__IObfuscationResult = 'Factory<IObfuscationResult>',
     Factory__IObfuscatingReplacer = 'Factory<IObfuscatingReplacer>',
+    Factory__IPropertiesExtractor = 'Factory<IPropertiesExtractor>',
     Factory__TControlFlowStorage = 'Factory<TControlFlowStorage>',
     IArrayUtils = 'IArrayUtils',
     ICalleeDataExtractor = 'ICalleeDataExtractor',
@@ -30,6 +31,7 @@ export enum ServiceIdentifiers {
     IOptions = 'IOptions',
     IOptionsNormalizer = 'IOptionsNormalizer',
     IObfuscatingReplacer = 'IObfuscatingReplacer',
+    IPropertiesExtractor = 'IPropertiesExtractor',
     IRandomGenerator = 'IRandomGenerator',
     ISourceCode = 'ISourceCode',
     ISourceMapCorrector = 'ISourceMapCorrector',

+ 1 - 1
src/container/modules/analyzers/AnalyzersModule.ts

@@ -30,7 +30,7 @@ export const analyzersModule: interfaces.ContainerModule = new ContainerModule((
         .to(ObjectExpressionCalleeDataExtractor)
         .whenTargetNamed(CalleeDataExtractor.ObjectExpressionCalleeDataExtractor);
 
-    // node transformers factory
+    // callee data extractor factory
     bind<ICalleeDataExtractor>(ServiceIdentifiers.Factory__ICalleeDataExtractor)
         .toFactory<ICalleeDataExtractor>(InversifyContainerFacade
             .getCacheFactory<CalleeDataExtractor, ICalleeDataExtractor>(

+ 21 - 0
src/container/modules/node-transformers/ConvertingTransformersModule.ts

@@ -1,15 +1,20 @@
 import { ContainerModule, interfaces } from 'inversify';
+import { InversifyContainerFacade } from '../../InversifyContainerFacade';
 import { ServiceIdentifiers } from '../../ServiceIdentifiers';
 
 import { INodeTransformer } from '../../../interfaces/node-transformers/INodeTransformer';
+import { IPropertiesExtractor } from '../../../interfaces/node-transformers/converting-transformers/properties-extractors/IPropertiesExtractor';
 
 import { NodeTransformer } from '../../../enums/node-transformers/NodeTransformer';
+import { PropertiesExtractor } from '../../../enums/node-transformers/converting-transformers/properties-extractors/PropertiesExtractor';
 
+import { AssignmentExpressionPropertiesExtractor } from '../../../node-transformers/converting-transformers/properties-extractors/AssignmentExpressionPropertiesExtractor';
 import { MemberExpressionTransformer } from '../../../node-transformers/converting-transformers/MemberExpressionTransformer';
 import { MethodDefinitionTransformer } from '../../../node-transformers/converting-transformers/MethodDefinitionTransformer';
 import { ObjectExpressionKeysTransformer } from '../../../node-transformers/converting-transformers/ObjectExpressionKeysTransformer';
 import { ObjectExpressionTransformer } from '../../../node-transformers/converting-transformers/ObjectExpressionTransformer';
 import { TemplateLiteralTransformer } from '../../../node-transformers/converting-transformers/TemplateLiteralTransformer';
+import { VariableDeclaratorPropertiesExtractor } from '../../../node-transformers/converting-transformers/properties-extractors/VariableDeclaratorPropertiesExtractor';
 
 export const convertingTransformersModule: interfaces.ContainerModule = new ContainerModule((bind: interfaces.Bind) => {
     // converting transformers
@@ -32,4 +37,20 @@ export const convertingTransformersModule: interfaces.ContainerModule = new Cont
     bind<INodeTransformer>(ServiceIdentifiers.INodeTransformer)
         .to(TemplateLiteralTransformer)
         .whenTargetNamed(NodeTransformer.TemplateLiteralTransformer);
+
+    // properties extractors
+    bind<IPropertiesExtractor>(ServiceIdentifiers.IPropertiesExtractor)
+        .to(AssignmentExpressionPropertiesExtractor)
+        .whenTargetNamed(PropertiesExtractor.AssignmentExpressionPropertiesExtractor);
+
+    bind<IPropertiesExtractor>(ServiceIdentifiers.IPropertiesExtractor)
+        .to(VariableDeclaratorPropertiesExtractor)
+        .whenTargetNamed(PropertiesExtractor.VariableDeclaratorPropertiesExtractor);
+
+    // properties extractor factory
+    bind<IPropertiesExtractor>(ServiceIdentifiers.Factory__IPropertiesExtractor)
+        .toFactory<IPropertiesExtractor>(InversifyContainerFacade
+            .getCacheFactory<PropertiesExtractor, IPropertiesExtractor>(
+                ServiceIdentifiers.IPropertiesExtractor
+            ));
 });

+ 4 - 0
src/enums/node-transformers/converting-transformers/properties-extractors/PropertiesExtractor.ts

@@ -0,0 +1,4 @@
+export enum PropertiesExtractor {
+    AssignmentExpressionPropertiesExtractor = 'AssignmentExpressionPropertiesExtractor',
+    VariableDeclaratorPropertiesExtractor = 'VariableDeclaratorPropertiesExtractor'
+}

+ 13 - 0
src/interfaces/node-transformers/converting-transformers/properties-extractors/IPropertiesExtractor.ts

@@ -0,0 +1,13 @@
+import * as ESTree from 'estree';
+
+export interface IPropertiesExtractor {
+    /**
+     * @param {ObjectExpression} objectExpressionNode
+     * @param {Node} hostNode
+     * @returns {Node}
+     */
+    extract (
+        objectExpressionNode: ESTree.ObjectExpression,
+        hostNode: ESTree.Node
+    ): ESTree.Node;
+}

+ 21 - 210
src/node-transformers/converting-transformers/ObjectExpressionKeysTransformer.ts

@@ -3,74 +3,49 @@ import { ServiceIdentifiers } from '../../container/ServiceIdentifiers';
 
 import * as ESTree from 'estree';
 
-import { TNodeWithScope } from '../../types/node/TNodeWithScope';
+import { TPropertiesExtractorFactory } from '../../types/container/node-transformers/TPropertiesExtractorFactory';
 
 import { IOptions } from '../../interfaces/options/IOptions';
+import { IPropertiesExtractor } from '../../interfaces/node-transformers/converting-transformers/properties-extractors/IPropertiesExtractor';
 import { IRandomGenerator } from '../../interfaces/utils/IRandomGenerator';
 import { IVisitor } from '../../interfaces/node-transformers/IVisitor';
 
+import { NodeType } from '../../enums/node/NodeType';
+import { PropertiesExtractor } from '../../enums/node-transformers/converting-transformers/properties-extractors/PropertiesExtractor';
 import { TransformationStage } from '../../enums/node-transformers/TransformationStage';
 
 import { AbstractNodeTransformer } from '../AbstractNodeTransformer';
-import { NodeAppender } from '../../node/NodeAppender';
-import { NodeFactory } from '../../node/NodeFactory';
 import { NodeGuards } from '../../node/NodeGuards';
-import { NodeUtils } from '../../node/NodeUtils';
 
 @injectable()
 export class ObjectExpressionKeysTransformer extends AbstractNodeTransformer {
     /**
-     * @type {Map<ESTree.ObjectExpression, TNodeWithScope>}
+     * @type {Map<string, PropertiesExtractor>}
      */
-    private readonly cachedHostScopesMap: Map <ESTree.ObjectExpression, TNodeWithScope> = new Map();
+    private static readonly propertiesExtractorsMap: Map <string, PropertiesExtractor> = new Map([
+        [NodeType.AssignmentExpression, PropertiesExtractor.AssignmentExpressionPropertiesExtractor],
+        [NodeType.VariableDeclarator, PropertiesExtractor.VariableDeclaratorPropertiesExtractor]
+    ]);
 
     /**
-     * @type {Map<ESTree.ObjectExpression, ESTree.Statement>}
+     * @type {TPropertiesExtractorFactory}
      */
-    private readonly cachedHostStatementsMap: Map <ESTree.ObjectExpression, ESTree.Statement> = new Map();
+    private readonly propertiesExtractorFactory: TPropertiesExtractorFactory;
 
     /**
+     * @param {TPropertiesExtractorFactory} propertiesExtractorFactory
      * @param {IRandomGenerator} randomGenerator
      * @param {IOptions} options
      */
     constructor (
+        @inject(ServiceIdentifiers.Factory__IPropertiesExtractor)
+            propertiesExtractorFactory: TPropertiesExtractorFactory,
         @inject(ServiceIdentifiers.IRandomGenerator) randomGenerator: IRandomGenerator,
         @inject(ServiceIdentifiers.IOptions) options: IOptions
     ) {
         super(randomGenerator, options);
-    }
-
-    /**
-     * @param {Property} propertyNode
-     * @returns {string | null}
-     */
-    private static getPropertyNodeKeyName (propertyNode: ESTree.Property): string | null {
-        if (!propertyNode.key) {
-            return null;
-        }
-
-        const propertyKeyNode: ESTree.Expression = propertyNode.key;
-
-        if (NodeGuards.isLiteralNode(propertyKeyNode) && typeof propertyKeyNode.value === 'string') {
-            return propertyKeyNode.value;
-        }
 
-        if (NodeGuards.isIdentifierNode(propertyKeyNode)) {
-            return propertyKeyNode.name;
-        }
-
-        return null;
-    }
-
-    /**
-     * @param {Node} node
-     * @returns {propertyValueNode is Pattern}
-     */
-    private static isProhibitedPattern (node: ESTree.Node): node is ESTree.Pattern {
-        return NodeGuards.isObjectPatternNode(node)
-            || NodeGuards.isArrayPatternNode(node)
-            || NodeGuards.isAssignmentPatternNode(node)
-            || NodeGuards.isRestElementNode(node);
+        this.propertiesExtractorFactory = propertiesExtractorFactory;
     }
 
     /**
@@ -116,180 +91,16 @@ export class ObjectExpressionKeysTransformer extends AbstractNodeTransformer {
             return objectExpressionNode;
         }
 
-        if (NodeGuards.isVariableDeclaratorNode(parentNode)) {
-            return this.transformWithVariableDeclaratorHost(objectExpressionNode, parentNode);
-        }
-
-        if (NodeGuards.isAssignmentExpressionNode(parentNode)) {
-            return this.transformWithAssignmentExpressionHost(objectExpressionNode, parentNode);
-        }
+        const propertiesExtractorName: PropertiesExtractor | undefined = ObjectExpressionKeysTransformer
+            .propertiesExtractorsMap
+            .get(parentNode.type);
 
-        return objectExpressionNode;
-    }
-
-    /**
-     * @param {Property[]} properties
-     * @param {Expression} memberExpressionHostNode
-     * @returns {[ExpressionStatement[] , number[]]}
-     */
-    private extractPropertiesToExpressionStatements (
-        properties: ESTree.Property[],
-        memberExpressionHostNode: ESTree.Expression
-    ): [ESTree.ExpressionStatement[], number[]] {
-        const propertiesLength: number = properties.length;
-        const expressionStatements: ESTree.ExpressionStatement[] = [];
-        const removablePropertyIds: number[] = [];
-
-        for (let i: number = 0; i < propertiesLength; i++) {
-            const property: ESTree.Property = properties[i];
-            const propertyValue: ESTree.Expression | ESTree.Pattern = property.value;
-
-            // invalid property nodes
-            if (ObjectExpressionKeysTransformer.isProhibitedPattern(propertyValue)) {
-                continue;
-            }
-
-            /**
-             * Stage 1: extract property node key names
-             */
-            const propertyKeyName: string | null = ObjectExpressionKeysTransformer.getPropertyNodeKeyName(property);
-
-            if (!propertyKeyName) {
-                continue;
-            }
-
-            /**
-             * Stage 2: creating new expression statement node with member expression based on removed property
-             */
-            const shouldCreateLiteralNode: boolean = !property.computed
-                || (property.computed && !!property.key && NodeGuards.isLiteralNode(property.key));
-            const memberExpressionProperty: ESTree.Expression = shouldCreateLiteralNode
-                ? NodeFactory.literalNode(propertyKeyName)
-                : NodeFactory.identifierNode(propertyKeyName);
-            const memberExpressionNode: ESTree.MemberExpression = NodeFactory
-                .memberExpressionNode(memberExpressionHostNode, memberExpressionProperty, true);
-            const expressionStatementNode: ESTree.ExpressionStatement = NodeFactory.expressionStatementNode(
-                NodeFactory.assignmentExpressionNode('=', memberExpressionNode, propertyValue)
-            );
-
-            /**
-             * Stage 3: recursively processing nested object expressions
-             */
-            if (NodeGuards.isObjectExpressionNode(property.value)) {
-                this.transformObjectExpressionNode(property.value, memberExpressionNode);
-            }
-
-            /**
-             * Stage 4: filling arrays
-             */
-            expressionStatements.push(expressionStatementNode);
-            removablePropertyIds.push(i);
-        }
-
-        return [expressionStatements, removablePropertyIds];
-    }
-
-    /**
-     * @param {ObjectExpression} objectExpressionNode
-     * @param {Statement} hostStatement
-     * @returns {TNodeWithScope}
-     */
-    private getHostScopeNode (
-        objectExpressionNode: ESTree.ObjectExpression,
-        hostStatement: ESTree.Statement
-    ): TNodeWithScope {
-        if (this.cachedHostScopesMap.has(objectExpressionNode)) {
-            return <TNodeWithScope>this.cachedHostScopesMap.get(objectExpressionNode);
-        }
-
-        const scopeNode: TNodeWithScope = NodeUtils.getScopeOfNode(hostStatement);
-
-        this.cachedHostScopesMap.set(objectExpressionNode, scopeNode);
-
-        return scopeNode;
-    }
-
-    /**
-     * Returns host statement of object expression node
-     *
-     * @param {NodeGuards} objectExpressionNode
-     * @returns {Node}
-     */
-    private getHostStatement (objectExpressionNode: ESTree.ObjectExpression): ESTree.Statement {
-        if (this.cachedHostStatementsMap.has(objectExpressionNode)) {
-            return <ESTree.Statement>this.cachedHostStatementsMap.get(objectExpressionNode);
-        }
-
-        const hostStatement: ESTree.Statement = NodeUtils.getRootStatementOfNode(objectExpressionNode);
-
-        this.cachedHostStatementsMap.set(objectExpressionNode, hostStatement);
-
-        return hostStatement;
-    }
-
-    /**
-     * @param {ObjectExpression} objectExpressionNode
-     * @param {VariableDeclarator} hostNode
-     * @returns {Node}
-     */
-    private transformWithVariableDeclaratorHost (
-        objectExpressionNode: ESTree.ObjectExpression,
-        hostNode: ESTree.VariableDeclarator
-    ): ESTree.Node {
-        // should pass only Expression nodes as MemberExpression.object value
-        if (!NodeGuards.isIdentifierNode(hostNode.id)) {
+        if (!propertiesExtractorName) {
             return objectExpressionNode;
         }
 
-        return this.transformObjectExpressionNode(
-            objectExpressionNode,
-            hostNode.id
-        );
-    }
-
-    /**
-     * @param {ObjectExpression} objectExpressionNode
-     * @param {AssignmentExpression} hostNode
-     * @returns {Node}
-     */
-    private transformWithAssignmentExpressionHost (
-        objectExpressionNode: ESTree.ObjectExpression,
-        hostNode: ESTree.AssignmentExpression
-    ): ESTree.Node {
-        const leftNode: ESTree.MemberExpression | ESTree.Pattern = hostNode.left;
-
-        // left node shouldn't be as Pattern node
-        if (ObjectExpressionKeysTransformer.isProhibitedPattern(leftNode)) {
-            return objectExpressionNode;
-        }
-
-        return this.transformObjectExpressionNode(
-            objectExpressionNode,
-            leftNode
-        );
-    }
-
-    /**
-     * @param {ObjectExpression} objectExpressionNode
-     * @param {Expression} memberExpressionHostNode
-     * @returns {Node}
-     */
-    private transformObjectExpressionNode (
-        objectExpressionNode: ESTree.ObjectExpression,
-        memberExpressionHostNode: ESTree.Expression
-    ): ESTree.Node {
-        const properties: ESTree.Property[] = objectExpressionNode.properties;
-        const [expressionStatements, removablePropertyIds]: [ESTree.ExpressionStatement[], number[]] = this
-            .extractPropertiesToExpressionStatements(properties, memberExpressionHostNode);
-
-        const hostStatement: ESTree.Statement = this.getHostStatement(objectExpressionNode);
-        const scopeNode: TNodeWithScope = this.getHostScopeNode(objectExpressionNode, hostStatement);
-
-        objectExpressionNode.properties = properties.filter((property: ESTree.Property, index: number) =>
-            !removablePropertyIds.includes(index)
-        );
-        NodeAppender.insertAfter(scopeNode, expressionStatements, hostStatement);
+        const propertiesExtractor: IPropertiesExtractor = this.propertiesExtractorFactory(propertiesExtractorName);
 
-        return objectExpressionNode;
+        return propertiesExtractor.extract(objectExpressionNode, parentNode);
     }
 }

+ 228 - 0
src/node-transformers/converting-transformers/properties-extractors/AbstractPropertiesExtractor.ts

@@ -0,0 +1,228 @@
+import { inject, injectable } from 'inversify';
+import { ServiceIdentifiers } from '../../../container/ServiceIdentifiers';
+
+import * as ESTree from 'estree';
+
+import { TNodeWithScope } from '../../../types/node/TNodeWithScope';
+
+import { IOptions } from '../../../interfaces/options/IOptions';
+import { IPropertiesExtractor } from '../../../interfaces/node-transformers/converting-transformers/properties-extractors/IPropertiesExtractor';
+import { IRandomGenerator } from '../../../interfaces/utils/IRandomGenerator';
+
+import { NodeAppender } from '../../../node/NodeAppender';
+import { NodeFactory } from '../../../node/NodeFactory';
+import { NodeGuards } from '../../../node/NodeGuards';
+import { NodeUtils } from '../../../node/NodeUtils';
+
+@injectable()
+export abstract class AbstractPropertiesExtractor implements IPropertiesExtractor {
+    /**
+     * @type {Map<ESTree.ObjectExpression, TNodeWithScope>}
+     */
+    protected readonly cachedHostScopesMap: Map <ESTree.ObjectExpression, TNodeWithScope> = new Map();
+
+    /**
+     * @type {Map<ESTree.ObjectExpression, ESTree.Statement>}
+     */
+    protected readonly cachedHostStatementsMap: Map <ESTree.ObjectExpression, ESTree.Statement> = new Map();
+
+
+    /**
+     * @type {IOptions}
+     */
+    protected readonly options: IOptions;
+
+    /**
+     * @type {IRandomGenerator}
+     */
+    protected readonly randomGenerator: IRandomGenerator;
+
+    /**
+     * @param {IRandomGenerator} randomGenerator
+     * @param {IOptions} options
+     */
+    constructor (
+        @inject(ServiceIdentifiers.IRandomGenerator) randomGenerator: IRandomGenerator,
+        @inject(ServiceIdentifiers.IOptions) options: IOptions
+    ) {
+        this.randomGenerator = randomGenerator;
+        this.options = options;
+    }
+
+    /**
+     * @param {Property} propertyNode
+     * @returns {string | null}
+     */
+    protected static getPropertyNodeKeyName (propertyNode: ESTree.Property): string | null {
+        if (!propertyNode.key) {
+            return null;
+        }
+
+        const propertyKeyNode: ESTree.Expression = propertyNode.key;
+
+        if (NodeGuards.isLiteralNode(propertyKeyNode) && typeof propertyKeyNode.value === 'string') {
+            return propertyKeyNode.value;
+        }
+
+        if (NodeGuards.isIdentifierNode(propertyKeyNode)) {
+            return propertyKeyNode.name;
+        }
+
+        return null;
+    }
+
+    /**
+     * @param {Node} node
+     * @returns {propertyValueNode is Pattern}
+     */
+    protected static isProhibitedPattern (node: ESTree.Node): node is ESTree.Pattern {
+        return NodeGuards.isObjectPatternNode(node)
+            || NodeGuards.isArrayPatternNode(node)
+            || NodeGuards.isAssignmentPatternNode(node)
+            || NodeGuards.isRestElementNode(node);
+    }
+
+    /**
+     * @param {ObjectExpression} objectExpressionNode
+     * @param {Node} hostNode
+     * @returns {Node}
+     */
+    public abstract extract (
+        objectExpressionNode: ESTree.ObjectExpression,
+        hostNode: ESTree.Node
+    ): ESTree.Node;
+
+    /**
+     * @param {Property[]} properties
+     * @param {Expression} memberExpressionHostNode
+     * @returns {[ExpressionStatement[] , number[]]}
+     */
+    protected extractPropertiesToExpressionStatements (
+        properties: ESTree.Property[],
+        memberExpressionHostNode: ESTree.Expression
+    ): [ESTree.ExpressionStatement[], number[]] {
+        const propertiesLength: number = properties.length;
+        const expressionStatements: ESTree.ExpressionStatement[] = [];
+        const removablePropertyIds: number[] = [];
+
+        for (let i: number = 0; i < propertiesLength; i++) {
+            const property: ESTree.Property = properties[i];
+            const propertyValue: ESTree.Expression | ESTree.Pattern = property.value;
+
+            // invalid property nodes
+            if (AbstractPropertiesExtractor.isProhibitedPattern(propertyValue)) {
+                continue;
+            }
+
+            /**
+             * Stage 1: extract property node key names
+             */
+            const propertyKeyName: string | null = AbstractPropertiesExtractor.getPropertyNodeKeyName(property);
+
+            if (!propertyKeyName) {
+                continue;
+            }
+
+            /**
+             * Stage 2: creating new expression statement node with member expression based on removed property
+             */
+            const shouldCreateLiteralNode: boolean = !property.computed
+                || (property.computed && !!property.key && NodeGuards.isLiteralNode(property.key));
+            const memberExpressionProperty: ESTree.Expression = shouldCreateLiteralNode
+                ? NodeFactory.literalNode(propertyKeyName)
+                : NodeFactory.identifierNode(propertyKeyName);
+            const memberExpressionNode: ESTree.MemberExpression = NodeFactory
+                .memberExpressionNode(memberExpressionHostNode, memberExpressionProperty, true);
+            const expressionStatementNode: ESTree.ExpressionStatement = NodeFactory.expressionStatementNode(
+                NodeFactory.assignmentExpressionNode('=', memberExpressionNode, propertyValue)
+            );
+
+            /**
+             * Stage 3: recursively processing nested object expressions
+             */
+            if (NodeGuards.isObjectExpressionNode(property.value)) {
+                this.transformObjectExpressionNode(property.value, memberExpressionNode);
+            }
+
+            /**
+             * Stage 4: filling arrays
+             */
+            expressionStatements.push(expressionStatementNode);
+            removablePropertyIds.push(i);
+        }
+
+        return [expressionStatements, removablePropertyIds];
+    }
+
+    /**
+     * @param {ObjectExpression} objectExpressionNode
+     * @param {number[]} removablePropertyIds
+     */
+    protected filterExtractedObjectExpressionProperties (
+        objectExpressionNode: ESTree.ObjectExpression,
+        removablePropertyIds: number[]
+    ): void {
+        objectExpressionNode.properties = objectExpressionNode.properties
+            .filter((property: ESTree.Property, index: number) => !removablePropertyIds.includes(index));
+    }
+
+    /**
+     * @param {ObjectExpression} objectExpressionNode
+     * @param {Expression} memberExpressionHostNode
+     * @returns {Node}
+     */
+    protected transformObjectExpressionNode (
+        objectExpressionNode: ESTree.ObjectExpression,
+        memberExpressionHostNode: ESTree.Expression
+    ): ESTree.Node {
+        const properties: ESTree.Property[] = objectExpressionNode.properties;
+        const [expressionStatements, removablePropertyIds]: [ESTree.ExpressionStatement[], number[]] = this
+            .extractPropertiesToExpressionStatements(properties, memberExpressionHostNode);
+
+        const hostStatement: ESTree.Statement = this.getHostStatement(objectExpressionNode);
+        const scopeNode: TNodeWithScope = this.getHostScopeNode(objectExpressionNode, hostStatement);
+
+        this.filterExtractedObjectExpressionProperties(objectExpressionNode, removablePropertyIds);
+        NodeAppender.insertAfter(scopeNode, expressionStatements, hostStatement);
+
+        return objectExpressionNode;
+    }
+
+    /**
+     * @param {ObjectExpression} objectExpressionNode
+     * @param {Statement} hostStatement
+     * @returns {TNodeWithScope}
+     */
+    private getHostScopeNode (
+        objectExpressionNode: ESTree.ObjectExpression,
+        hostStatement: ESTree.Statement
+    ): TNodeWithScope {
+        if (this.cachedHostScopesMap.has(objectExpressionNode)) {
+            return <TNodeWithScope>this.cachedHostScopesMap.get(objectExpressionNode);
+        }
+
+        const scopeNode: TNodeWithScope = NodeUtils.getScopeOfNode(hostStatement);
+
+        this.cachedHostScopesMap.set(objectExpressionNode, scopeNode);
+
+        return scopeNode;
+    }
+
+    /**
+     * Returns host statement of object expression node
+     *
+     * @param {NodeGuards} objectExpressionNode
+     * @returns {Node}
+     */
+    private getHostStatement (objectExpressionNode: ESTree.ObjectExpression): ESTree.Statement {
+        if (this.cachedHostStatementsMap.has(objectExpressionNode)) {
+            return <ESTree.Statement>this.cachedHostStatementsMap.get(objectExpressionNode);
+        }
+
+        const hostStatement: ESTree.Statement = NodeUtils.getRootStatementOfNode(objectExpressionNode);
+
+        this.cachedHostStatementsMap.set(objectExpressionNode, hostStatement);
+
+        return hostStatement;
+    }
+}

+ 45 - 0
src/node-transformers/converting-transformers/properties-extractors/AssignmentExpressionPropertiesExtractor.ts

@@ -0,0 +1,45 @@
+import { inject, injectable } from 'inversify';
+import { ServiceIdentifiers } from '../../../container/ServiceIdentifiers';
+
+import * as ESTree from 'estree';
+
+import { IOptions } from '../../../interfaces/options/IOptions';
+import { IRandomGenerator } from '../../../interfaces/utils/IRandomGenerator';
+
+import { AbstractPropertiesExtractor } from './AbstractPropertiesExtractor';
+
+@injectable()
+export class AssignmentExpressionPropertiesExtractor extends AbstractPropertiesExtractor {
+    /**
+     * @param {IRandomGenerator} randomGenerator
+     * @param {IOptions} options
+     */
+    constructor (
+        @inject(ServiceIdentifiers.IRandomGenerator) randomGenerator: IRandomGenerator,
+        @inject(ServiceIdentifiers.IOptions) options: IOptions
+    ) {
+        super(randomGenerator, options);
+    }
+
+    /**
+     * @param {ObjectExpression} objectExpressionNode
+     * @param {AssignmentExpression} hostNode
+     * @returns {Node}
+     */
+    public extract (
+        objectExpressionNode: ESTree.ObjectExpression,
+        hostNode: ESTree.AssignmentExpression
+    ): ESTree.Node {
+        const leftNode: ESTree.MemberExpression | ESTree.Pattern = hostNode.left;
+
+        // left node shouldn't be as Pattern node
+        if (AbstractPropertiesExtractor.isProhibitedPattern(leftNode)) {
+            return objectExpressionNode;
+        }
+
+        return this.transformObjectExpressionNode(
+            objectExpressionNode,
+            leftNode
+        );
+    }
+}

+ 117 - 0
src/node-transformers/converting-transformers/properties-extractors/VariableDeclaratorPropertiesExtractor.ts

@@ -0,0 +1,117 @@
+import { inject, injectable } from 'inversify';
+import { ServiceIdentifiers } from '../../../container/ServiceIdentifiers';
+
+import * as estraverse from 'estraverse';
+import * as ESTree from 'estree';
+
+import { IOptions } from '../../../interfaces/options/IOptions';
+import { IRandomGenerator } from '../../../interfaces/utils/IRandomGenerator';
+
+import { AbstractPropertiesExtractor } from './AbstractPropertiesExtractor';
+import { NodeGuards } from '../../../node/NodeGuards';
+
+@injectable()
+export class VariableDeclaratorPropertiesExtractor extends AbstractPropertiesExtractor {
+    /**
+     * @param {IRandomGenerator} randomGenerator
+     * @param {IOptions} options
+     */
+    constructor (
+        @inject(ServiceIdentifiers.IRandomGenerator) randomGenerator: IRandomGenerator,
+        @inject(ServiceIdentifiers.IOptions) options: IOptions
+    ) {
+        super(randomGenerator, options);
+    }
+
+    /**
+     * @param {ObjectExpression} objectExpressionNode
+     * @param {VariableDeclarator} hostNode
+     * @returns {Node}
+     */
+    public extract (
+        objectExpressionNode: ESTree.ObjectExpression,
+        hostNode: ESTree.VariableDeclarator
+    ): ESTree.Node {
+        if (
+            !NodeGuards.isIdentifierNode(hostNode.id)
+            || this.isProhibitedObjectExpressionNode(objectExpressionNode, hostNode.id)
+        ) {
+            return objectExpressionNode;
+        }
+
+        return this.transformObjectExpressionNode(objectExpressionNode, hostNode.id);
+    }
+
+    /**
+     * @param {ObjectExpression} objectExpressionNode
+     * @returns {VariableDeclarator}
+     */
+    private getHostVariableDeclaratorNode (objectExpressionNode: ESTree.ObjectExpression): ESTree.VariableDeclarator | never {
+        const { parentNode } = objectExpressionNode;
+
+        if (!parentNode || !NodeGuards.isVariableDeclaratorNode(parentNode)) {
+            throw new Error('Cannot get `VariableDeclarator` node for `ObjectExpression` node');
+        }
+
+        return parentNode;
+    }
+
+    /**
+     * @param {VariableDeclarator} variableDeclaratorNode
+     * @returns {VariableDeclaration}
+     */
+    private getHostVariableDeclarationNode (variableDeclaratorNode: ESTree.VariableDeclarator): ESTree.VariableDeclaration | never {
+        const { parentNode } = variableDeclaratorNode;
+
+        if (!parentNode || !NodeGuards.isVariableDeclarationNode(parentNode)) {
+            throw new Error('Cannot get `VariableDeclaration` node for `VariableDeclarator` node');
+        }
+
+        return parentNode;
+    }
+
+    /**
+     * @param {ObjectExpression} objectExpressionNode
+     * @param {Identifier} memberExpressionHostNode
+     * @returns {boolean}
+     */
+    private isProhibitedObjectExpressionNode (
+        objectExpressionNode: ESTree.ObjectExpression,
+        memberExpressionHostNode: ESTree.Identifier
+    ): boolean {
+        const hostVariableDeclarator: ESTree.VariableDeclarator = this.getHostVariableDeclaratorNode(objectExpressionNode);
+        const hostVariableDeclaration: ESTree.VariableDeclaration = this.getHostVariableDeclarationNode(hostVariableDeclarator);
+        const { declarations } = hostVariableDeclaration;
+
+        // avoid unnecessary checks
+        if (declarations.length === 1) {
+            return false;
+        }
+
+        const indexOfDeclarator: number = declarations.indexOf(hostVariableDeclarator);
+        const declaratorsAfterCurrentDeclarator: ESTree.VariableDeclarator[] = declarations.slice(indexOfDeclarator);
+
+        let isProhibitedObjectExpressionNode: boolean = false;
+
+        // should mark node as prohibited if that node using inside other variable declarators
+        declaratorsAfterCurrentDeclarator.forEach((variableDeclarator: ESTree.VariableDeclarator) => {
+            estraverse.traverse(variableDeclarator, {
+                enter: (node: ESTree.Node): estraverse.VisitorOption | ESTree.Node => {
+                    if (
+                        NodeGuards.isMemberExpressionNode(node)
+                        && NodeGuards.isIdentifierNode(node.object)
+                        && node.object.name === memberExpressionHostNode.name
+                    ) {
+                        isProhibitedObjectExpressionNode = true;
+
+                        return estraverse.VisitorOption.Break;
+                    }
+
+                    return node;
+                }
+            });
+        });
+
+       return isProhibitedObjectExpressionNode;
+    }
+}

+ 5 - 0
src/types/container/node-transformers/TPropertiesExtractorFactory.d.ts

@@ -0,0 +1,5 @@
+import { IPropertiesExtractor } from '../../../interfaces/node-transformers/converting-transformers/properties-extractors/IPropertiesExtractor';
+
+import { PropertiesExtractor } from '../../../enums/node-transformers/converting-transformers/properties-extractors/PropertiesExtractor';
+
+export type TPropertiesExtractorFactory = (propertiesExtractorName: PropertiesExtractor) => IPropertiesExtractor;

+ 115 - 0
test/functional-tests/node-transformers/converting-transformers/object-expression-keys-transformer/ObjectExpressionKeysTransformer.spec.ts

@@ -342,6 +342,65 @@ describe('ObjectExpressionKeysTransformer', () => {
                 assert.match(obfuscatedCode,  regExp);
             });
         });
+
+        describe('Variant #5: variable declarator with object call', () => {
+            describe('Variant #1', () => {
+                const match: string = `` +
+                    `const *${variableMatch} *= *{}; *` +
+                    `${variableMatch}\\['foo'] *= *'foo'; *` +
+                    `const *${variableMatch} *= *${variableMatch}\\['foo'];` +
+                ``;
+                const regExp: RegExp = new RegExp(match);
+
+                let obfuscatedCode: string;
+
+                before(() => {
+                    const code: string = readFileAsString(__dirname + '/fixtures/variable-declarator-with-object-call-1.js');
+                    const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                        code,
+                        {
+                            ...NO_ADDITIONAL_NODES_PRESET,
+                            transformObjectKeys: true
+                        }
+                    );
+
+                    obfuscatedCode = obfuscationResult.getObfuscatedCode();
+                });
+
+                it('should correctly transform object keys', () => {
+                    assert.match(obfuscatedCode,  regExp);
+                });
+            });
+
+            describe('Variant #2', () => {
+                const match: string = `` +
+                    `const *${variableMatch} *= *0x1, *` +
+                        `${variableMatch} *= *{}; *` +
+                    `${variableMatch}\\['foo'] *= *'foo'; *` +
+                    `const *${variableMatch} *= *${variableMatch}\\['foo'];` +
+                ``;
+                const regExp: RegExp = new RegExp(match);
+
+                let obfuscatedCode: string;
+
+                before(() => {
+                    const code: string = readFileAsString(__dirname + '/fixtures/variable-declarator-with-object-call-2.js');
+                    const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                        code,
+                        {
+                            ...NO_ADDITIONAL_NODES_PRESET,
+                            transformObjectKeys: true
+                        }
+                    );
+
+                    obfuscatedCode = obfuscationResult.getObfuscatedCode();
+                });
+
+                it('should correctly transform object keys', () => {
+                    assert.match(obfuscatedCode,  regExp);
+                });
+            });
+        });
     });
 
     describe('Ignore transformation', () => {
@@ -395,5 +454,61 @@ describe('ObjectExpressionKeysTransformer', () => {
                 assert.match(obfuscatedCode,  regExp);
             });
         });
+
+        describe('Variant #3: variable declarator object call inside other variable declarator', () => {
+            describe('Variant #1', () => {
+                const match: string = `` +
+                    `const *${variableMatch} *= *{'foo': *'foo'}, *` +
+                        `${variableMatch} *= *${variableMatch}\\['foo'];` +
+                ``;
+                const regExp: RegExp = new RegExp(match);
+
+                let obfuscatedCode: string;
+
+                before(() => {
+                    const code: string = readFileAsString(__dirname + '/fixtures/variable-declarator-with-object-call-ignore-1.js');
+                    const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                        code,
+                        {
+                            ...NO_ADDITIONAL_NODES_PRESET,
+                            transformObjectKeys: true
+                        }
+                    );
+
+                    obfuscatedCode = obfuscationResult.getObfuscatedCode();
+                });
+
+                it('shouldn\'t transform object keys', () => {
+                    assert.match(obfuscatedCode,  regExp);
+                });
+            });
+
+            describe('Variant #2', () => {
+                const match: string = `` +
+                    `const *${variableMatch} *= *{'foo': *'foo'}, *` +
+                        `${variableMatch} *= *\\[${variableMatch}\\['foo']];` +
+                ``;
+                const regExp: RegExp = new RegExp(match);
+
+                let obfuscatedCode: string;
+
+                before(() => {
+                    const code: string = readFileAsString(__dirname + '/fixtures/variable-declarator-with-object-call-ignore-2.js');
+                    const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                        code,
+                        {
+                            ...NO_ADDITIONAL_NODES_PRESET,
+                            transformObjectKeys: true
+                        }
+                    );
+
+                    obfuscatedCode = obfuscationResult.getObfuscatedCode();
+                });
+
+                it('shouldn\'t transform object keys', () => {
+                    assert.match(obfuscatedCode,  regExp);
+                });
+            });
+        });
     });
 });

+ 4 - 0
test/functional-tests/node-transformers/converting-transformers/object-expression-keys-transformer/fixtures/variable-declarator-with-object-call-1.js

@@ -0,0 +1,4 @@
+(function () {
+    const object = {foo: 'foo'};
+    const variable = object.foo;
+})();

+ 5 - 0
test/functional-tests/node-transformers/converting-transformers/object-expression-keys-transformer/fixtures/variable-declarator-with-object-call-2.js

@@ -0,0 +1,5 @@
+(function () {
+    const test = 1,
+        object = {foo: 'foo'};
+    const variable = object.foo;
+})();

+ 4 - 0
test/functional-tests/node-transformers/converting-transformers/object-expression-keys-transformer/fixtures/variable-declarator-with-object-call-ignore-1.js

@@ -0,0 +1,4 @@
+(function () {
+    const object = {foo: 'foo'},
+        variable = object.foo;
+})();

+ 4 - 0
test/functional-tests/node-transformers/converting-transformers/object-expression-keys-transformer/fixtures/variable-declarator-with-object-call-ignore-2.js

@@ -0,0 +1,4 @@
+(function () {
+    const object = {foo: 'foo'},
+        variable = [object.foo];
+})();

Some files were not shown because too many files changed in this diff