Просмотр исходного кода

Added ThroughIdentifierReplacer for code separate between ScopeIdentifierReplacer and ScopeThroughIdentifiersTransformer

sanex 4 лет назад
Родитель
Сommit
18f72a89c7

Разница между файлами не показана из-за своего большого размера
+ 0 - 0
dist/index.browser.js


Разница между файлами не показана из-за своего большого размера
+ 0 - 19120
dist/index.cli.js


Разница между файлами не показана из-за своего большого размера
+ 0 - 18065
dist/index.js


+ 1 - 0
src/container/ServiceIdentifiers.ts

@@ -56,6 +56,7 @@ export enum ServiceIdentifiers {
     IStringArrayScopeCallsWrapperNamesDataStorage = 'IStringArrayScopeCallsWrapperNamesDataStorage',
     IStringArrayStorage = 'IStringArrayStorage',
     IStringArrayStorageAnalyzer = 'IStringArrayStorageAnalyzer',
+    IThroughIdentifierReplacer = 'IThroughIdentifierReplacer',
     IVisitedLexicalScopeNodesStackStorage = 'IVisitedLexicalScopeNodesStackStorage',
     Newable__ICustomNode = 'Newable<ICustomNode>',
     Newable__TControlFlowStorage = 'Newable<TControlFlowStorage>',

+ 6 - 0
src/container/modules/node-transformers/RenameIdentifiersTransformersModule.ts

@@ -11,6 +11,8 @@ import { IdentifierReplacer } from '../../../node-transformers/rename-identifier
 import { LabeledStatementTransformer } from '../../../node-transformers/rename-identifiers-transformers/LabeledStatementTransformer';
 import { ScopeIdentifiersTransformer } from '../../../node-transformers/rename-identifiers-transformers/ScopeIdentifiersTransformer';
 import { ScopeThroughIdentifiersTransformer } from '../../../node-transformers/rename-identifiers-transformers/ScopeThroughIdentifiersTransformer';
+import { ThroughIdentifierReplacer } from '../../../node-transformers/rename-identifiers-transformers/through-replacer/ThroughIdentifierReplacer';
+import { IThroughIdentifierReplacer } from '../../../interfaces/node-transformers/rename-identifiers-transformers/replacer/IThroughIdentifierReplacer';
 
 export const renameIdentifiersTransformersModule: interfaces.ContainerModule = new ContainerModule((bind: interfaces.Bind) => {
     // rename identifiers transformers
@@ -34,4 +36,8 @@ export const renameIdentifiersTransformersModule: interfaces.ContainerModule = n
     bind<IIdentifierReplacer>(ServiceIdentifiers.IIdentifierReplacer)
         .to(IdentifierReplacer)
         .inSingletonScope();
+
+    bind<IThroughIdentifierReplacer>(ServiceIdentifiers.IThroughIdentifierReplacer)
+        .to(ThroughIdentifierReplacer)
+        .inSingletonScope();
 });

+ 0 - 6
src/interfaces/node-transformers/rename-identifiers-transformers/replacer/IIdentifierReplacer.ts

@@ -15,12 +15,6 @@ export interface IIdentifierReplacer {
      */
     storeLocalName (identifierNode: ESTree.Identifier, lexicalScopeNode: TNodeWithLexicalScope): void;
 
-    /**
-     * @param {Identifier} identifierNode
-     * @param {TNodeWithLexicalScope} lexicalScopeNode
-     */
-    storeThroughName (identifierNode: ESTree.Identifier, lexicalScopeNode: TNodeWithLexicalScope): void;
-
     /**
      * @param {Node} node
      * @param {TNodeWithLexicalScope} lexicalScopeNode

+ 14 - 0
src/interfaces/node-transformers/rename-identifiers-transformers/replacer/IThroughIdentifierReplacer.ts

@@ -0,0 +1,14 @@
+import * as ESTree from 'estree';
+
+export interface IThroughIdentifierReplacer {
+    /**
+     * @param {Identifier} identifierNode
+     */
+    store (identifierNode: ESTree.Identifier): void;
+
+    /**
+     * @param {Identifier} identifierNode
+     * @returns {Identifier}
+     */
+    replace (identifierNode: ESTree.Identifier): ESTree.Identifier;
+}

+ 102 - 19
src/node-transformers/dead-code-injection-transformers/DeadCodeInjectionIdentifiersTransformer.ts

@@ -1,58 +1,141 @@
 import { inject, injectable, } from 'inversify';
 import { ServiceIdentifiers } from '../../container/ServiceIdentifiers';
 
+import * as eslintScope from 'eslint-scope';
 import * as ESTree from 'estree';
 
 import { TNodeWithLexicalScope } from '../../types/node/TNodeWithLexicalScope';
 
-import { IIdentifierNamesCacheStorage } from '../../interfaces/storages/identifier-names-cache/IIdentifierNamesCacheStorage';
 import { IIdentifierReplacer } from '../../interfaces/node-transformers/rename-identifiers-transformers/replacer/IIdentifierReplacer';
 import { IOptions } from '../../interfaces/options/IOptions';
 import { IRandomGenerator } from '../../interfaces/utils/IRandomGenerator';
 import { IScopeIdentifiersTraverser } from '../../interfaces/node/IScopeIdentifiersTraverser';
+import { IScopeThroughIdentifiersTraverserCallbackData } from '../../interfaces/node/IScopeThroughIdentifiersTraverserCallbackData';
+import { IVisitor } from '../../interfaces/node-transformers/IVisitor';
 
-import { ScopeThroughIdentifiersTransformer } from '../rename-identifiers-transformers/ScopeThroughIdentifiersTransformer';
+import { NodeTransformationStage } from '../../enums/node-transformers/NodeTransformationStage';
+
+import { AbstractNodeTransformer } from '../AbstractNodeTransformer';
+import { NodeGuards } from '../../node/NodeGuards';
 
 /**
  * Renames all scope through identifiers for Dead Code Injection
  */
 @injectable()
-export class DeadCodeInjectionIdentifiersTransformer extends ScopeThroughIdentifiersTransformer {
+export class DeadCodeInjectionIdentifiersTransformer extends AbstractNodeTransformer {
+    /**
+     * @type {IIdentifierReplacer}
+     */
+    private readonly identifierReplacer: IIdentifierReplacer;
+
+    /**
+     * @type {IScopeIdentifiersTraverser}
+     */
+    private readonly scopeIdentifiersTraverser: IScopeIdentifiersTraverser;
+
     /**
      * @param {IIdentifierReplacer} identifierReplacer
      * @param {IRandomGenerator} randomGenerator
-     * @param {IScopeIdentifiersTraverser} scopeIdentifiersTraverser
-     * @param {IIdentifierNamesCacheStorage} identifierNamesCacheStorage
      * @param {IOptions} options
+     * @param {IScopeIdentifiersTraverser} scopeIdentifiersTraverser
      */
     public constructor (
         @inject(ServiceIdentifiers.IIdentifierReplacer) identifierReplacer: IIdentifierReplacer,
         @inject(ServiceIdentifiers.IRandomGenerator) randomGenerator: IRandomGenerator,
-        @inject(ServiceIdentifiers.IScopeIdentifiersTraverser)
-            scopeIdentifiersTraverser: IScopeIdentifiersTraverser,
-        @inject(ServiceIdentifiers.IIdentifierNamesCacheStorage)
-            identifierNamesCacheStorage: IIdentifierNamesCacheStorage,
-        @inject(ServiceIdentifiers.IOptions) options: IOptions
+        @inject(ServiceIdentifiers.IOptions) options: IOptions,
+        @inject(ServiceIdentifiers.IScopeIdentifiersTraverser) scopeIdentifiersTraverser: IScopeIdentifiersTraverser
     ) {
-        super(
-            identifierReplacer,
-            randomGenerator,
-            scopeIdentifiersTraverser,
-            identifierNamesCacheStorage,
-            options
+        super(randomGenerator, options);
+
+        this.identifierReplacer = identifierReplacer;
+        this.scopeIdentifiersTraverser = scopeIdentifiersTraverser;
+    }
+
+    /**
+     * @param {NodeTransformationStage} nodeTransformationStage
+     * @returns {IVisitor | null}
+     */
+    public getVisitor (nodeTransformationStage: NodeTransformationStage): IVisitor | null {
+        switch (nodeTransformationStage) {
+            case NodeTransformationStage.RenameIdentifiers:
+                return {
+                    enter: (node: ESTree.Node, parentNode: ESTree.Node | null): ESTree.Node | undefined => {
+                        if (parentNode && NodeGuards.isProgramNode(node)) {
+                            return this.transformNode(node, parentNode);
+                        }
+                    }
+                };
+
+            default:
+                return null;
+        }
+    }
+
+    /**
+     * @param {VariableDeclaration} programNode
+     * @param {NodeGuards} parentNode
+     * @returns {NodeGuards}
+     */
+    public transformNode (programNode: ESTree.Program, parentNode: ESTree.Node): ESTree.Node {
+        this.scopeIdentifiersTraverser.traverseScopeThroughIdentifiers(
+            programNode,
+            parentNode,
+            (data: IScopeThroughIdentifiersTraverserCallbackData) => {
+                const {
+                    reference,
+                    variableLexicalScopeNode
+                } = data;
+
+                this.transformScopeThroughIdentifiers(reference, variableLexicalScopeNode);
+            }
         );
+
+        return programNode;
+    }
+
+    /**
+     * @param {Reference} reference
+     * @param {TNodeWithLexicalScope} lexicalScopeNode
+     */
+    private transformScopeThroughIdentifiers (
+        reference: eslintScope.Reference,
+        lexicalScopeNode: TNodeWithLexicalScope,
+    ): void {
+        if (reference.resolved) {
+            return;
+        }
+
+        const identifier: ESTree.Identifier = reference.identifier;
+
+        this.storeIdentifierName(identifier, lexicalScopeNode);
+        this.replaceIdentifierName(identifier, lexicalScopeNode, reference);
     }
 
     /**
-     * Override parent method, because we have to store only local scope names
-     *
      * @param {Identifier} identifierNode
      * @param {TNodeWithLexicalScope} lexicalScopeNode
      */
-    protected override storeIdentifierName (
+    private storeIdentifierName (
         identifierNode: ESTree.Identifier,
         lexicalScopeNode: TNodeWithLexicalScope
     ): void {
         this.identifierReplacer.storeLocalName(identifierNode, lexicalScopeNode);
     }
+
+    /**
+     * @param {Identifier} identifierNode
+     * @param {TNodeWithLexicalScope} lexicalScopeNode
+     * @param {Variable} reference
+     */
+    private replaceIdentifierName (
+        identifierNode: ESTree.Identifier,
+        lexicalScopeNode: TNodeWithLexicalScope,
+        reference: eslintScope.Reference
+    ): void {
+        const newIdentifier: ESTree.Identifier = this.identifierReplacer
+            .replace(identifierNode, lexicalScopeNode);
+
+        // rename of identifier
+        reference.identifier.name = newIdentifier.name;
+    }
 }

+ 17 - 39
src/node-transformers/rename-identifiers-transformers/ScopeThroughIdentifiersTransformer.ts

@@ -6,12 +6,11 @@ import * as ESTree from 'estree';
 
 import { TNodeWithLexicalScope } from '../../types/node/TNodeWithLexicalScope';
 
-import { IIdentifierNamesCacheStorage } from '../../interfaces/storages/identifier-names-cache/IIdentifierNamesCacheStorage';
-import { IIdentifierReplacer } from '../../interfaces/node-transformers/rename-identifiers-transformers/replacer/IIdentifierReplacer';
 import { IOptions } from '../../interfaces/options/IOptions';
 import { IRandomGenerator } from '../../interfaces/utils/IRandomGenerator';
 import { IScopeIdentifiersTraverser } from '../../interfaces/node/IScopeIdentifiersTraverser';
 import { IScopeThroughIdentifiersTraverserCallbackData } from '../../interfaces/node/IScopeThroughIdentifiersTraverserCallbackData';
+import { IThroughIdentifierReplacer } from '../../interfaces/node-transformers/rename-identifiers-transformers/replacer/IThroughIdentifierReplacer';
 import { IVisitor } from '../../interfaces/node-transformers/IVisitor';
 
 import { NodeTransformationStage } from '../../enums/node-transformers/NodeTransformationStage';
@@ -25,41 +24,31 @@ import { NodeGuards } from '../../node/NodeGuards';
 @injectable()
 export class ScopeThroughIdentifiersTransformer extends AbstractNodeTransformer {
     /**
-     * @type {IIdentifierNamesCacheStorage}
-     */
-    protected readonly identifierNamesCacheStorage: IIdentifierNamesCacheStorage;
-
-    /**
-     * @type {IIdentifierReplacer}
+     * @type {IScopeIdentifiersTraverser}
      */
-    protected readonly identifierReplacer: IIdentifierReplacer;
+    protected readonly scopeIdentifiersTraverser: IScopeIdentifiersTraverser;
 
     /**
-     * @type {IScopeIdentifiersTraverser}
+     * @type {IThroughIdentifierReplacer}
      */
-    protected readonly scopeIdentifiersTraverser: IScopeIdentifiersTraverser;
+    protected readonly throughIdentifierReplacer: IThroughIdentifierReplacer;
 
     /**
-     * @param {IIdentifierReplacer} identifierReplacer
-     * @param {IRandomGenerator} randomGenerator
+     * @param {IThroughIdentifierReplacer} throughIdentifierReplacer
      * @param {IScopeIdentifiersTraverser} scopeIdentifiersTraverser
-     * @param {IIdentifierNamesCacheStorage} identifierNamesCacheStorage
+     * @param {IRandomGenerator} randomGenerator
      * @param {IOptions} options
      */
     public constructor (
-        @inject(ServiceIdentifiers.IIdentifierReplacer) identifierReplacer: IIdentifierReplacer,
+        @inject(ServiceIdentifiers.IThroughIdentifierReplacer) throughIdentifierReplacer: IThroughIdentifierReplacer,
+        @inject(ServiceIdentifiers.IScopeIdentifiersTraverser) scopeIdentifiersTraverser: IScopeIdentifiersTraverser,
         @inject(ServiceIdentifiers.IRandomGenerator) randomGenerator: IRandomGenerator,
-        @inject(ServiceIdentifiers.IScopeIdentifiersTraverser)
-            scopeIdentifiersTraverser: IScopeIdentifiersTraverser,
-        @inject(ServiceIdentifiers.IIdentifierNamesCacheStorage)
-            identifierNamesCacheStorage: IIdentifierNamesCacheStorage,
         @inject(ServiceIdentifiers.IOptions) options: IOptions
     ) {
         super(randomGenerator, options);
 
-        this.identifierReplacer = identifierReplacer;
+        this.throughIdentifierReplacer = throughIdentifierReplacer;
         this.scopeIdentifiersTraverser = scopeIdentifiersTraverser;
-        this.identifierNamesCacheStorage = identifierNamesCacheStorage;
     }
 
     /**
@@ -93,15 +82,13 @@ export class ScopeThroughIdentifiersTransformer extends AbstractNodeTransformer
             parentNode,
             (data: IScopeThroughIdentifiersTraverserCallbackData) => {
                 const {
-                    isGlobalDeclaration,
                     reference,
                     variableLexicalScopeNode
                 } = data;
 
                 this.transformScopeThroughIdentifiers(
                     reference,
-                    variableLexicalScopeNode,
-                    isGlobalDeclaration
+                    variableLexicalScopeNode
                 );
             }
         );
@@ -112,12 +99,10 @@ export class ScopeThroughIdentifiersTransformer extends AbstractNodeTransformer
     /**
      * @param {Reference} reference
      * @param {TNodeWithLexicalScope} lexicalScopeNode
-     * @param {boolean} isGlobalDeclaration
      */
     protected transformScopeThroughIdentifiers (
         reference: eslintScope.Reference,
-        lexicalScopeNode: TNodeWithLexicalScope,
-        isGlobalDeclaration: boolean
+        lexicalScopeNode: TNodeWithLexicalScope
     ): void {
         if (reference.resolved) {
             return;
@@ -125,35 +110,28 @@ export class ScopeThroughIdentifiersTransformer extends AbstractNodeTransformer
 
         const identifier: ESTree.Identifier = reference.identifier;
 
-        this.storeIdentifierName(identifier, lexicalScopeNode, isGlobalDeclaration);
-        this.replaceIdentifierName(identifier, lexicalScopeNode, reference);
+        this.storeIdentifierName(identifier);
+        this.replaceIdentifierName(identifier, reference);
     }
 
     /**
      * @param {Identifier} identifierNode
-     * @param {TNodeWithLexicalScope} lexicalScopeNode
-     * @param {boolean} isGlobalDeclaration
      */
     protected storeIdentifierName (
-        identifierNode: ESTree.Identifier,
-        lexicalScopeNode: TNodeWithLexicalScope,
-        isGlobalDeclaration: boolean
+        identifierNode: ESTree.Identifier
     ): void {
-        this.identifierReplacer.storeThroughName(identifierNode, lexicalScopeNode);
+        this.throughIdentifierReplacer.store(identifierNode);
     }
 
     /**
      * @param {Identifier} identifierNode
-     * @param {TNodeWithLexicalScope} lexicalScopeNode
      * @param {Variable} reference
      */
     protected replaceIdentifierName (
         identifierNode: ESTree.Identifier,
-        lexicalScopeNode: TNodeWithLexicalScope,
         reference: eslintScope.Reference
     ): void {
-        const newIdentifier: ESTree.Identifier = this.identifierReplacer
-            .replace(identifierNode, lexicalScopeNode);
+        const newIdentifier: ESTree.Identifier = this.throughIdentifierReplacer.replace(identifierNode);
 
         // rename of identifier
         reference.identifier.name = newIdentifier.name;

+ 0 - 31
src/node-transformers/rename-identifiers-transformers/replacer/IdentifierReplacer.ts

@@ -105,37 +105,6 @@ export class IdentifierReplacer implements IIdentifierReplacer {
         namesMap.set(identifierName, newIdentifierName);
     }
 
-    /**
-     * Store identifier node `name` of `through` identifiers as key in map with value from identifier names cache.
-     * Reserved name will be ignored.
-     *
-     * @param {Node} identifierNode
-     * @param {TNodeWithLexicalScope} lexicalScopeNode
-     */
-    public storeThroughName (identifierNode: ESTree.Identifier, lexicalScopeNode: TNodeWithLexicalScope): void {
-        const identifierName: string = identifierNode.name;
-
-        if (this.isReservedName(identifierName)) {
-            return;
-        }
-
-        const newIdentifierName: string | null = this.identifierNamesCacheStorage.get(identifierName) ?? null;
-
-        if (!newIdentifierName) {
-            return;
-        }
-
-        if (!this.blockScopesMap.has(lexicalScopeNode)) {
-            this.blockScopesMap.set(lexicalScopeNode, new Map());
-        }
-
-        const namesMap: Map<string, string> = <Map<string, string>>this.blockScopesMap.get(lexicalScopeNode);
-
-        namesMap.set(identifierName, newIdentifierName);
-
-        this.identifierNamesCacheStorage.set(identifierName, newIdentifierName);
-    }
-
     /**
      * @param {Identifier} identifierNode
      * @param {TNodeWithLexicalScope} lexicalScopeNode

+ 84 - 0
src/node-transformers/rename-identifiers-transformers/through-replacer/ThroughIdentifierReplacer.ts

@@ -0,0 +1,84 @@
+import { inject, injectable, } from 'inversify';
+import { ServiceIdentifiers } from '../../../container/ServiceIdentifiers';
+
+import * as ESTree from 'estree';
+
+import { IIdentifierNamesCacheStorage } from '../../../interfaces/storages/identifier-names-cache/IIdentifierNamesCacheStorage';
+import { IOptions } from '../../../interfaces/options/IOptions';
+import { IThroughIdentifierReplacer } from '../../../interfaces/node-transformers/rename-identifiers-transformers/replacer/IThroughIdentifierReplacer';
+
+import { NodeFactory } from '../../../node/NodeFactory';
+
+@injectable()
+export class ThroughIdentifierReplacer implements IThroughIdentifierReplacer {
+    /**
+     * @type {IIdentifierNamesCacheStorage}
+     */
+    private readonly identifierNamesCacheStorage: IIdentifierNamesCacheStorage;
+
+    /**
+     * @type {IOptions}
+     */
+    private readonly options: IOptions;
+
+    /**
+     * @param {IIdentifierNamesCacheStorage} identifierNamesCacheStorage
+     * @param {IOptions} options
+     */
+    public constructor (
+        @inject(ServiceIdentifiers.IIdentifierNamesCacheStorage)
+            identifierNamesCacheStorage: IIdentifierNamesCacheStorage,
+        @inject(ServiceIdentifiers.IOptions) options: IOptions
+    ) {
+        this.identifierNamesCacheStorage = identifierNamesCacheStorage;
+        this.options = options;
+    }
+
+    /**
+     * Store identifier node `name` of `through` identifiers as key in map with value from identifier names cache.
+     * Reserved name will be ignored.
+     *
+     * @param {Node} identifierNode
+     */
+    public store (identifierNode: ESTree.Identifier): void {
+        const identifierName: string = identifierNode.name;
+
+        if (this.isReservedName(identifierName)) {
+            return;
+        }
+
+        const newIdentifierName: string | null = this.identifierNamesCacheStorage.get(identifierName) ?? null;
+
+        if (!newIdentifierName) {
+            return;
+        }
+
+        this.identifierNamesCacheStorage.set(identifierName, newIdentifierName);
+    }
+
+    /**
+     * @param {Identifier} identifierNode
+     * @returns {Identifier}
+     */
+    public replace (identifierNode: ESTree.Identifier): ESTree.Identifier {
+        const identifierName: string = this.identifierNamesCacheStorage.get(identifierNode.name)
+            ?? identifierNode.name;
+
+        return NodeFactory.identifierNode(identifierName);
+    }
+
+    /**
+     * @param {string} name
+     * @returns {boolean}
+     */
+    private isReservedName (name: string): boolean {
+        if (!this.options.reservedNames.length) {
+            return false;
+        }
+
+        return this.options.reservedNames
+            .some((reservedName: string) => {
+                return new RegExp(reservedName, 'g').exec(name) !== null;
+            });
+    }
+}

+ 131 - 0
test/functional-tests/options/identifier-names-cache/Validation.spec.ts

@@ -0,0 +1,131 @@
+import { assert } from 'chai';
+
+import { JavaScriptObfuscator } from '../../../../src/JavaScriptObfuscatorFacade';
+
+import { NO_ADDITIONAL_NODES_PRESET } from '../../../../src/options/presets/NoCustomNodes';
+
+describe('`identifierNamesCache` validation', () => {
+    describe('IsPrimitiveDictionary', () => {
+        describe('Variant #1: positive validation', () => {
+            describe('Variant #1: object with existing identifier names cache', () => {
+                let testFunc: () => string;
+
+                beforeEach(() => {
+                    testFunc = () => JavaScriptObfuscator.obfuscate(
+                        '',
+                        {
+                            ...NO_ADDITIONAL_NODES_PRESET,
+                            identifierNamesCache: {
+                                foo: '_0x123456'
+                            }
+                        }
+                    ).getObfuscatedCode();
+                });
+
+                it('should pass validation', () => {
+                    assert.doesNotThrow(testFunc);
+                });
+            });
+
+            describe('Variant #2: object with empty identifier names cache', () => {
+                let testFunc: () => string;
+
+                beforeEach(() => {
+                    testFunc = () => JavaScriptObfuscator.obfuscate(
+                        '',
+                        {
+                            ...NO_ADDITIONAL_NODES_PRESET,
+                            identifierNamesCache: {}
+                        }
+                    ).getObfuscatedCode();
+                });
+
+                it('should pass validation', () => {
+                    assert.doesNotThrow(testFunc);
+                });
+            });
+
+            describe('Variant #3: `null` value', () => {
+                let testFunc: () => string;
+
+                beforeEach(() => {
+                    testFunc = () => JavaScriptObfuscator.obfuscate(
+                        '',
+                        {
+                            ...NO_ADDITIONAL_NODES_PRESET,
+                            identifierNamesCache: null
+                        }
+                    ).getObfuscatedCode();
+                });
+
+                it('should pass validation', () => {
+                    assert.doesNotThrow(testFunc);
+                });
+            });
+        });
+
+        describe('Variant #2: negative validation', () => {
+            const expectedError: string = 'Passed value must be a dictionary with `string` values or `null` value';
+
+            describe('Variant #1: string value', () => {
+                let testFunc: () => string;
+
+                beforeEach(() => {
+                    testFunc = () => JavaScriptObfuscator.obfuscate(
+                        '',
+                        {
+                            ...NO_ADDITIONAL_NODES_PRESET,
+                            identifierNamesCache: <any>'cache'
+                        }
+                    ).getObfuscatedCode();
+                });
+
+                it('should not pass validation', () => {
+                    assert.throws(testFunc, expectedError);
+                });
+            });
+
+            describe('Variant #2: object with number values', () => {
+                let testFunc: () => string;
+
+                beforeEach(() => {
+                    testFunc = () => JavaScriptObfuscator.obfuscate(
+                        '',
+                        {
+                            ...NO_ADDITIONAL_NODES_PRESET,
+                            identifierNamesCache: {
+                                foo: <any>1,
+                                bar: <any>2,
+                            }
+                        }
+                    ).getObfuscatedCode();
+                });
+
+                it('should not pass validation', () => {
+                    assert.throws(testFunc, expectedError);
+                });
+            });
+
+            describe('Variant #3: object with mixed values', () => {
+                let testFunc: () => string;
+
+                beforeEach(() => {
+                    testFunc = () => JavaScriptObfuscator.obfuscate(
+                        '',
+                        {
+                            ...NO_ADDITIONAL_NODES_PRESET,
+                            identifierNamesCache: {
+                                foo: <any>1,
+                                bar: '_0x1234567',
+                            }
+                        }
+                    ).getObfuscatedCode();
+                });
+
+                it('should not pass validation', () => {
+                    assert.throws(testFunc, expectedError);
+                });
+            });
+        });
+    });
+});

+ 1 - 0
test/index.spec.ts

@@ -131,6 +131,7 @@ import './functional-tests/node-transformers/string-array-transformers/string-ar
 import './functional-tests/node-transformers/string-array-transformers/string-array-transformer/StringArrayTransformer.spec';
 import './functional-tests/options/OptionsNormalizer.spec';
 import './functional-tests/options/domain-lock/Validation.spec';
+import './functional-tests/options/identifier-names-cache/Validation.spec';
 import './functional-tests/storages/string-array-transformers/string-array-storage/StringArrayStorage.spec';
 
 /**

Некоторые файлы не были показаны из-за большого количества измененных файлов