瀏覽代碼

Merge pull request #927 from javascript-obfuscator/names-cache

Identifier names cache option
Timofey Kachalov 4 年之前
父節點
當前提交
5b5d0eb2db
共有 84 個文件被更改,包括 2608 次插入322 次删除
  1. 5 0
      CHANGELOG.md
  2. 84 1
      README.md
  3. 0 0
      dist/index.browser.js
  4. 0 0
      dist/index.cli.js
  5. 0 0
      dist/index.js
  6. 3 3
      index.d.ts
  7. 4 4
      package.json
  8. 14 13
      src/JavaScriptObfuscator.ts
  9. 5 5
      src/JavaScriptObfuscatorFacade.ts
  10. 33 18
      src/cli/JavaScriptObfuscatorCLI.ts
  11. 98 0
      src/cli/utils/IdentifierNamesCacheFileUtils.ts
  12. 1 1
      src/cli/utils/ObfuscatedCodeFileUtils.ts
  13. 13 13
      src/cli/utils/SourceCodeFileUtils.ts
  14. 11 11
      src/container/InversifyContainerFacade.ts
  15. 5 2
      src/container/ServiceIdentifiers.ts
  16. 11 0
      src/container/modules/node-transformers/RenameIdentifiersTransformersModule.ts
  17. 12 0
      src/container/modules/storages/StoragesModule.ts
  18. 1 0
      src/enums/node-transformers/NodeTransformer.ts
  19. 3 3
      src/interfaces/IJavaScriptObfsucator.ts
  20. 9 0
      src/interfaces/node-transformers/rename-identifiers-transformers/replacer/IThroughIdentifierReplacer.ts
  21. 1 0
      src/interfaces/node/IScopeThroughIdentifiersTraverserCallbackData.ts
  22. 1 0
      src/interfaces/options/ICLIOptions.ts
  23. 2 0
      src/interfaces/options/IOptions.ts
  24. 0 13
      src/interfaces/source-code/IObfuscatedCode.ts
  25. 19 0
      src/interfaces/source-code/IObfuscationResult.ts
  26. 7 0
      src/interfaces/storages/IMapStorage.ts
  27. 4 0
      src/interfaces/storages/identifier-names-cache/IGlobalIdentifierNamesCacheStorage.ts
  28. 4 0
      src/interfaces/storages/identifier-names-cache/IPropertyIdentifierNamesCacheStorage.ts
  29. 141 0
      src/node-transformers/dead-code-injection-transformers/DeadCodeInjectionIdentifiersTransformer.ts
  30. 2 2
      src/node-transformers/dead-code-injection-transformers/DeadCodeInjectionTransformer.ts
  31. 22 39
      src/node-transformers/rename-identifiers-transformers/ScopeThroughIdentifiersTransformer.ts
  32. 17 2
      src/node-transformers/rename-identifiers-transformers/replacer/IdentifierReplacer.ts
  33. 64 0
      src/node-transformers/rename-identifiers-transformers/through-replacer/ThroughIdentifierReplacer.ts
  34. 22 2
      src/node-transformers/rename-properties-transformers/replacer/RenamePropertiesReplacer.ts
  35. 1 0
      src/node-transformers/rename-properties-transformers/replacer/ReservedDomProperties.json
  36. 2 0
      src/node/ScopeIdentifiersTraverser.ts
  37. 8 0
      src/options/Options.ts
  38. 2 0
      src/options/OptionsNormalizer.ts
  39. 32 0
      src/options/normalizer-rules/IdentifierNamesCacheRule.ts
  40. 1 0
      src/options/presets/Default.ts
  41. 87 0
      src/options/validators/IsIdentifierNamesCache.ts
  42. 43 3
      src/source-code/ObfuscationResult.ts
  43. 8 0
      src/storages/MapStorage.ts
  44. 29 0
      src/storages/identifier-names-cache/GlobalIdentifierNamesCacheStorage.ts
  45. 29 0
      src/storages/identifier-names-cache/PropertyIdentifierNamesCacheStorage.ts
  46. 7 0
      src/types/TIdentifierNamesCache.ts
  47. 3 0
      src/types/TIdentifierNamesCacheDictionary.ts
  48. 2 2
      src/types/TObfuscationResultsObject.ts
  49. 0 3
      src/types/container/source-code/TObfuscatedCodeFactory.ts
  50. 3 0
      src/types/container/source-code/TObfuscationResultFactory.ts
  51. 15 13
      test/dev/dev.ts
  52. 1 1
      test/fixtures/compile-performance.js
  53. 7 7
      test/functional-tests/custom-code-helpers/domain-lock/templates/DomainLockNodeTemplate.spec.ts
  54. 7 7
      test/functional-tests/custom-code-helpers/string-array/templates/string-array-calls-wrapper-node-template/StringArrayCallsWrapperTemplate.spec.ts
  55. 11 11
      test/functional-tests/custom-code-helpers/string-array/templates/string-array-rotate-function-template/StringArrayRotateFunctionTemplate.spec.ts
  56. 7 7
      test/functional-tests/custom-code-helpers/string-array/templates/string-array-template/StringArrayTemplate.spec.ts
  57. 141 15
      test/functional-tests/javascript-obfuscator/JavaScriptObfuscator.spec.ts
  58. 9 0
      test/functional-tests/javascript-obfuscator/fixtures/identifier-names-cache-1.js
  59. 9 0
      test/functional-tests/javascript-obfuscator/fixtures/identifier-names-cache-2.js
  60. 98 0
      test/functional-tests/node-transformers/rename-identifiers-transformers/scope-through-identifiers-transformer/class-declaration/ClassDeclaration.spec.ts
  61. 3 0
      test/functional-tests/node-transformers/rename-identifiers-transformers/scope-through-identifiers-transformer/class-declaration/fixtures/class-call-with-declaration.js
  62. 1 0
      test/functional-tests/node-transformers/rename-identifiers-transformers/scope-through-identifiers-transformer/class-declaration/fixtures/class-call-without-declaration.js
  63. 98 0
      test/functional-tests/node-transformers/rename-identifiers-transformers/scope-through-identifiers-transformer/function-declaration/FunctionDeclaration.spec.ts
  64. 3 0
      test/functional-tests/node-transformers/rename-identifiers-transformers/scope-through-identifiers-transformer/function-declaration/fixtures/function-call-with-declaration.js
  65. 1 0
      test/functional-tests/node-transformers/rename-identifiers-transformers/scope-through-identifiers-transformer/function-declaration/fixtures/function-call-without-declaration.js
  66. 214 0
      test/functional-tests/node-transformers/rename-identifiers-transformers/scope-through-identifiers-transformer/variable-declaration/VariableDeclaration.spec.ts
  67. 3 0
      test/functional-tests/node-transformers/rename-identifiers-transformers/scope-through-identifiers-transformer/variable-declaration/fixtures/variable-reference-with-declaration.js
  68. 1 0
      test/functional-tests/node-transformers/rename-identifiers-transformers/scope-through-identifiers-transformer/variable-declaration/fixtures/variable-reference-without-declaration-global-scope.js
  69. 5 0
      test/functional-tests/node-transformers/rename-identifiers-transformers/scope-through-identifiers-transformer/variable-declaration/fixtures/variable-reference-without-declaration-local-scope.js
  70. 117 0
      test/functional-tests/node-transformers/rename-properties-transformers/rename-properties-transformer/RenamePropertiesTransformer.spec.ts
  71. 5 0
      test/functional-tests/node-transformers/rename-properties-transformers/rename-properties-transformer/fixtures/property-identifier-names-cache.js
  72. 134 0
      test/functional-tests/options/OptionsNormalizer.spec.ts
  73. 258 0
      test/functional-tests/options/identifier-names-cache/Validation.spec.ts
  74. 10 3
      test/index.spec.ts
  75. 211 0
      test/unit-tests/cli/utils/IdentifierNamesCacheFileUtils.spec.ts
  76. 48 48
      test/unit-tests/cli/utils/ObfuscatedCodeFileUtils.spec.ts
  77. 18 18
      test/unit-tests/cli/utils/SourceCodeFileUtils.spec.ts
  78. 7 7
      test/unit-tests/javascript-obfuscator/JavaScriptObfuscator.spec.ts
  79. 15 15
      test/unit-tests/source-code/ObfuscationResult.spec.ts
  80. 21 0
      test/unit-tests/storages/MapStorage.spec.ts
  81. 110 0
      test/unit-tests/storages/identifier-names-cache/GlobalIdentifierNamesCacheStorage.spec.ts
  82. 110 0
      test/unit-tests/storages/identifier-names-cache/PropertyIdentifierNamesCacheStorage.spec.ts
  83. 2 2
      tsconfig.json
  84. 33 28
      yarn.lock

+ 5 - 0
CHANGELOG.md

@@ -1,5 +1,10 @@
 Change Log
 
+v2.14.0
+---
+* Added `identifierNamesCache` option for reading and writing identifier names cache. See `README.md`.
+* **CLI**:  Added `--identifier-names-cache-path` option for reading and writing identifier names cache. See `README.md`.
+
 v2.13.0
 ---
 * Fixed invalid code generation for start/middle rest arguments when `controlFlowFlattenig` option is enabled. Fixed https://github.com/javascript-obfuscator/javascript-obfuscator/issues/920

+ 84 - 1
README.md

@@ -230,7 +230,8 @@ function _0x1054 (_0x14a2a4, _0x5a6b22) {
 Returns `ObfuscationResult` object which contains two public methods:
 
 * `getObfuscatedCode()` - returns `string` with obfuscated code;
-* `getSourceMap()` - if [`sourceMap`](#sourcemap) option is enabled - returns `string` with source map or an empty string if [`sourceMapMode`](#sourcemapmode) option is set as `inline`.
+* `getSourceMap()` - if [`sourceMap`](#sourcemap) option is enabled - returns `string` with source map or an empty string if [`sourceMapMode`](#sourcemapmode) option is set as `inline`;
+* `getIdentifierNamesCache()` - returns object with identifier names cache if `identifierNamesCache` option is enabled, `null` overwise.
 
 Calling `toString()` for `ObfuscationResult` object will return `string` with obfuscated code.
 
@@ -361,6 +362,7 @@ Following options are available for the JS Obfuscator:
     disableConsoleOutput: false,
     domainLock: [],
     forceTransformStrings: [],
+    identifierNamesCache: null,
     identifierNamesGenerator: 'hexadecimal',
     identifiersDictionary: [],
     identifiersPrefix: '',
@@ -421,6 +423,7 @@ Following options are available for the JS Obfuscator:
     --domain-lock '<list>' (comma separated)
     --exclude '<list>' (comma separated)
     --force-transform-strings '<list>' (comma separated)
+    --identifier-names-cache-path <string>
     --identifier-names-generator <string> [dictionary, hexadecimal, mangled, mangled-shuffled]
     --identifiers-dictionary '<list>' (comma separated)
     --identifiers-prefix <string>
@@ -712,6 +715,84 @@ Example:
 	}
 ```
 
+### `identifierNamesCache`
+Type: `Object | null` Default: `null`
+
+The main goal for this option is the ability to use the same identifier names during obfuscation of multiple sources/files.
+
+Currently the two types of the identifiers are supported:
+- Global identifiers:
+    * All global identifiers will be written to the cache;
+    * All matched **undeclared** global identifiers will be replaced by the values from the cache.
+- Property identifiers, only when `renameProperties` option is enabled:
+    * All property identifiers will be written to the cache;
+    * All matched property identifiers will be replaced by the values from the cache.
+
+#### Node.js API
+If a `null` value is passed, completely disables the cache.
+
+If an empty object (`{}`) is passed, enables the writing identifier names to the cache-object (`TIdentifierNamesCache` type). This cache-object will be accessed through the `getIdentifierNamesCache` method call of `ObfuscationResult` object.
+
+The resulting cache-object can be next used as `identifierNamesGenerator` option value for using these names during obfuscation of all matched identifier names of next sources.
+
+Example:
+```
+const source1ObfuscationResult = JavaScriptObfuscator.obfuscate(
+    `
+        function foo(arg) {
+           console.log(arg)
+        }
+        
+        function bar() {
+            var bark = 2;
+        }
+    `,
+    {
+        compact: false,
+        identifierNamesCache: {},
+        renameGlobals: true
+    }
+)
+
+console.log(source1ObfuscationResult.getIdentifierNamesCache());
+/*{ 
+    globalIdentifiers: {
+        foo: '_0x5de86d',
+        bar: '_0x2a943b'
+    }
+}*/
+
+
+
+const source2ObfuscationResult = JavaScriptObfuscator.obfuscate(
+    `
+        // Expecting that these global functions are defined in another obfuscated file
+        foo(1);
+        bar();
+        
+        // Expecting that this global function is defined in third-party package
+        baz();
+    `,
+    {
+        compact: false,
+        identifierNamesCache: source1ObfuscationResult.getIdentifierNamesCache(),
+        renameGlobals: true
+    }
+)
+
+console.log(source2ObfuscationResult.getObfuscatedCode());
+// _0x5de86d(0x1);
+// _0x2a943b();
+// baz();
+```
+
+#### CLI
+CLI has a different option `--identifier-names-cache-path` that allows defining a path to the existing `.json` file that will be used to read and write identifier names cache.
+
+If a path to the empty file will be passed - identifier names cache will be written to that file.
+
+This file with existing cache can be used again as `--identifier-names-cache-path` option value for using these names during obfuscation of all matched identifier names of the next files.
+
 ### `identifierNamesGenerator`
 Type: `string` Default: `hexadecimal`
 
@@ -834,6 +915,8 @@ Specifies `renameProperties` option mode:
 * `safe` - default behaviour after `2.11.0` release. Trying to rename properties in a more safe way to prevent runtime errors. With this mode some properties will be excluded from renaming.
 * `unsafe` - default behaviour before `2.11.0` release. Renames properties in an unsafe way without any restrictions.
 
+If one file is using properties from other file, use [`identifierNamesCache`](#identifiernamescache) option to keep the same property names between these files.
+
 ### `reservedNames`
 Type: `string[]` Default: `[]`
 

文件差異過大導致無法顯示
+ 0 - 0
dist/index.browser.js


文件差異過大導致無法顯示
+ 0 - 0
dist/index.cli.js


文件差異過大導致無法顯示
+ 0 - 0
dist/index.js


+ 3 - 3
index.d.ts

@@ -3,18 +3,18 @@ import { TInputOptions } from './src/types/options/TInputOptions';
 import { TObfuscationResultsObject } from './src/types/TObfuscationResultsObject';
 import { TOptionsPreset } from './src/types/options/TOptionsPreset';
 
-import { IObfuscatedCode } from './src/interfaces/source-code/IObfuscatedCode';
+import { IObfuscationResult } from './src/interfaces/source-code/IObfuscationResult';
 
 export type ObfuscatorOptions = TInputOptions;
 
-export interface ObfuscatedCode extends IObfuscatedCode {}
+export interface ObfuscationResult extends IObfuscationResult {}
 
 /**
  * @param {string} sourceCode
  * @param {ObfuscatorOptions} inputOptions
  * @returns {ObfuscatedCode}
  */
-export function obfuscate (sourceCode: string, inputOptions?: ObfuscatorOptions): ObfuscatedCode;
+export function obfuscate (sourceCode: string, inputOptions?: ObfuscatorOptions): ObfuscationResult;
 
 /**
  * @param {TSourceCodesObject} sourceCodesObject

+ 4 - 4
package.json

@@ -1,6 +1,6 @@
 {
   "name": "javascript-obfuscator",
-  "version": "2.13.0",
+  "version": "2.14.0",
   "description": "JavaScript obfuscator",
   "keywords": [
     "obfuscator",
@@ -24,7 +24,7 @@
     "@javascript-obfuscator/escodegen": "2.2.0",
     "@javascript-obfuscator/estraverse": "5.3.0",
     "@nuxtjs/opencollective": "0.3.2",
-    "acorn": "8.2.4",
+    "acorn": "8.3.0",
     "assert": "2.0.0",
     "chalk": "4.1.1",
     "chance": "1.1.7",
@@ -68,8 +68,8 @@
     "chai-exclude": "2.0.3",
     "cross-env": "7.0.3",
     "eslint": "7.27.0",
-    "eslint-plugin-import": "2.23.3",
-    "eslint-plugin-jsdoc": "35.0.0",
+    "eslint-plugin-import": "2.23.4",
+    "eslint-plugin-jsdoc": "35.1.0",
     "eslint-plugin-no-null": "1.0.2",
     "eslint-plugin-prefer-arrow": "1.2.3",
     "eslint-plugin-unicorn": "32.0.1",

+ 14 - 13
src/JavaScriptObfuscator.ts

@@ -5,13 +5,13 @@ import * as acorn from 'acorn';
 import * as escodegen from '@javascript-obfuscator/escodegen';
 import * as ESTree from 'estree';
 
-import { TObfuscatedCodeFactory } from './types/container/source-code/TObfuscatedCodeFactory';
+import { TObfuscationResultFactory } from './types/container/source-code/TObfuscationResultFactory';
 
 import { ICodeTransformersRunner } from './interfaces/code-transformers/ICodeTransformersRunner';
 import { IGeneratorOutput } from './interfaces/IGeneratorOutput';
 import { IJavaScriptObfuscator } from './interfaces/IJavaScriptObfsucator';
 import { ILogger } from './interfaces/logger/ILogger';
-import { IObfuscatedCode } from './interfaces/source-code/IObfuscatedCode';
+import { IObfuscationResult } from './interfaces/source-code/IObfuscationResult';
 import { IOptions } from './interfaces/options/IOptions';
 import { IRandomGenerator } from './interfaces/utils/IRandomGenerator';
 import { INodeTransformersRunner } from './interfaces/node-transformers/INodeTransformersRunner';
@@ -87,6 +87,7 @@ export class JavaScriptObfuscator implements IJavaScriptObfuscator {
         NodeTransformer.ObjectPatternPropertiesTransformer,
         NodeTransformer.ParentificationTransformer,
         NodeTransformer.ScopeIdentifiersTransformer,
+        NodeTransformer.ScopeThroughIdentifiersTransformer,
         NodeTransformer.SplitStringTransformer,
         NodeTransformer.StringArrayRotateFunctionTransformer,
         NodeTransformer.StringArrayScopeCallsWrapperTransformer,
@@ -108,9 +109,9 @@ export class JavaScriptObfuscator implements IJavaScriptObfuscator {
     private readonly logger: ILogger;
 
     /**
-     * @type {TObfuscatedCodeFactory}
+     * @type {TObfuscationResultFactory}
      */
-    private readonly obfuscatedCodeFactory: TObfuscatedCodeFactory;
+    private readonly obfuscationResultFactory: TObfuscationResultFactory;
 
     /**
      * @type {IOptions}
@@ -131,7 +132,7 @@ export class JavaScriptObfuscator implements IJavaScriptObfuscator {
      * @param {ICodeTransformersRunner} codeTransformersRunner
      * @param {INodeTransformersRunner} nodeTransformersRunner
      * @param {IRandomGenerator} randomGenerator
-     * @param {TObfuscatedCodeFactory} obfuscatedCodeFactory
+     * @param {TObfuscationResultFactory} obfuscatedCodeFactory
      * @param {ILogger} logger
      * @param {IOptions} options
      */
@@ -139,23 +140,23 @@ export class JavaScriptObfuscator implements IJavaScriptObfuscator {
         @inject(ServiceIdentifiers.ICodeTransformersRunner) codeTransformersRunner: ICodeTransformersRunner,
         @inject(ServiceIdentifiers.INodeTransformersRunner) nodeTransformersRunner: INodeTransformersRunner,
         @inject(ServiceIdentifiers.IRandomGenerator) randomGenerator: IRandomGenerator,
-        @inject(ServiceIdentifiers.Factory__IObfuscatedCode) obfuscatedCodeFactory: TObfuscatedCodeFactory,
+        @inject(ServiceIdentifiers.Factory__IObfuscationResult) obfuscatedCodeFactory: TObfuscationResultFactory,
         @inject(ServiceIdentifiers.ILogger) logger: ILogger,
         @inject(ServiceIdentifiers.IOptions) options: IOptions
     ) {
         this.codeTransformersRunner = codeTransformersRunner;
         this.nodeTransformersRunner = nodeTransformersRunner;
         this.randomGenerator = randomGenerator;
-        this.obfuscatedCodeFactory = obfuscatedCodeFactory;
+        this.obfuscationResultFactory = obfuscatedCodeFactory;
         this.logger = logger;
         this.options = options;
     }
 
     /**
      * @param {string} sourceCode
-     * @returns {IObfuscatedCode}
+     * @returns {IObfuscationResult}
      */
-    public obfuscate (sourceCode: string): IObfuscatedCode {
+    public obfuscate (sourceCode: string): IObfuscationResult {
         if (typeof sourceCode !== 'string') {
             sourceCode = '';
         }
@@ -183,7 +184,7 @@ export class JavaScriptObfuscator implements IJavaScriptObfuscator {
         const obfuscationTime: number = (Date.now() - timeStart) / 1000;
         this.logger.success(LoggingMessage.ObfuscationCompleted, obfuscationTime);
 
-        return this.getObfuscatedCode(generatorOutput);
+        return this.getObfuscationResult(generatorOutput);
     }
 
     /**
@@ -268,10 +269,10 @@ export class JavaScriptObfuscator implements IJavaScriptObfuscator {
 
     /**
      * @param {IGeneratorOutput} generatorOutput
-     * @returns {IObfuscatedCode}
+     * @returns {IObfuscationResult}
      */
-    private getObfuscatedCode (generatorOutput: IGeneratorOutput): IObfuscatedCode {
-        return this.obfuscatedCodeFactory(generatorOutput.code, generatorOutput.map);
+    private getObfuscationResult (generatorOutput: IGeneratorOutput): IObfuscationResult {
+        return this.obfuscationResultFactory(generatorOutput.code, generatorOutput.map);
     }
 
     /**

+ 5 - 5
src/JavaScriptObfuscatorFacade.ts

@@ -9,7 +9,7 @@ import { TOptionsPreset } from './types/options/TOptionsPreset';
 
 import { IInversifyContainerFacade } from './interfaces/container/IInversifyContainerFacade';
 import { IJavaScriptObfuscator } from './interfaces/IJavaScriptObfsucator';
-import { IObfuscatedCode } from './interfaces/source-code/IObfuscatedCode';
+import { IObfuscationResult } from './interfaces/source-code/IObfuscationResult';
 
 import { InversifyContainerFacade } from './container/InversifyContainerFacade';
 import { Options } from './options/Options';
@@ -24,20 +24,20 @@ class JavaScriptObfuscatorFacade {
     /**
      * @param {string} sourceCode
      * @param {TInputOptions} inputOptions
-     * @returns {IObfuscatedCode}
+     * @returns {IObfuscationResult}
      */
-    public static obfuscate (sourceCode: string, inputOptions: TInputOptions = {}): IObfuscatedCode {
+    public static obfuscate (sourceCode: string, inputOptions: TInputOptions = {}): IObfuscationResult {
         const inversifyContainerFacade: IInversifyContainerFacade = new InversifyContainerFacade();
 
         inversifyContainerFacade.load(sourceCode, '', inputOptions);
 
         const javaScriptObfuscator: IJavaScriptObfuscator = inversifyContainerFacade
             .get<IJavaScriptObfuscator>(ServiceIdentifiers.IJavaScriptObfuscator);
-        const obfuscatedCode: IObfuscatedCode = javaScriptObfuscator.obfuscate(sourceCode);
+        const obfuscationResult: IObfuscationResult = javaScriptObfuscator.obfuscate(sourceCode);
 
         inversifyContainerFacade.unload();
 
-        return obfuscatedCode;
+        return obfuscationResult;
     }
 
     /**

+ 33 - 18
src/cli/JavaScriptObfuscatorCLI.ts

@@ -7,7 +7,7 @@ import { TInputOptions } from '../types/options/TInputOptions';
 
 import { IFileData } from '../interfaces/cli/IFileData';
 import { IInitializable } from '../interfaces/IInitializable';
-import { IObfuscatedCode } from '../interfaces/source-code/IObfuscatedCode';
+import { IObfuscationResult } from '../interfaces/source-code/IObfuscationResult';
 
 import { initializable } from '../decorators/Initializable';
 
@@ -27,10 +27,11 @@ import { ArraySanitizer } from './sanitizers/ArraySanitizer';
 import { BooleanSanitizer } from './sanitizers/BooleanSanitizer';
 
 import { CLIUtils } from './utils/CLIUtils';
+import { IdentifierNamesCacheFileUtils } from './utils/IdentifierNamesCacheFileUtils';
 import { JavaScriptObfuscator } from '../JavaScriptObfuscatorFacade';
 import { Logger } from '../logger/Logger';
-import { ObfuscatedCodeWriter } from './utils/ObfuscatedCodeWriter';
-import { SourceCodeReader } from './utils/SourceCodeReader';
+import { ObfuscatedCodeFileUtils } from './utils/ObfuscatedCodeFileUtils';
+import { SourceCodeFileUtils } from './utils/SourceCodeFileUtils';
 import { Utils } from '../utils/Utils';
 
 export class JavaScriptObfuscatorCLI implements IInitializable {
@@ -57,6 +58,12 @@ export class JavaScriptObfuscatorCLI implements IInitializable {
     @initializable()
     private commands!: commander.CommanderStatic;
 
+    /**
+     * @type {IdentifierNamesCacheFileUtils}
+     */
+    @initializable()
+    private identifierNamesCacheFileUtils!: IdentifierNamesCacheFileUtils;
+
     /**
      * @type {TInputCLIOptions}
      */
@@ -70,16 +77,16 @@ export class JavaScriptObfuscatorCLI implements IInitializable {
     private inputPath!: string;
 
     /**
-     * @type {SourceCodeReader}
+     * @type {SourceCodeFileUtils}
      */
     @initializable()
-    private sourceCodeReader!: SourceCodeReader;
+    private sourceCodeFileUtils!: SourceCodeFileUtils;
 
     /**
-     * @type {ObfuscatedCodeWriter}
+     * @type {ObfuscatedCodeFileUtils}
      */
     @initializable()
-    private obfuscatedCodeWriter!: ObfuscatedCodeWriter;
+    private obfuscatedCodeFileUtils!: ObfuscatedCodeFileUtils;
 
     /**
      * @type {string[]}
@@ -144,14 +151,15 @@ export class JavaScriptObfuscatorCLI implements IInitializable {
 
         this.inputPath = path.normalize(this.commands.args[0] || '');
         this.inputCLIOptions = JavaScriptObfuscatorCLI.buildOptions(this.commands.opts());
-        this.sourceCodeReader = new SourceCodeReader(
+        this.sourceCodeFileUtils = new SourceCodeFileUtils(
             this.inputPath,
             this.inputCLIOptions
         );
-        this.obfuscatedCodeWriter = new ObfuscatedCodeWriter(
+        this.obfuscatedCodeFileUtils = new ObfuscatedCodeFileUtils(
             this.inputPath,
             this.inputCLIOptions
         );
+        this.identifierNamesCacheFileUtils = new IdentifierNamesCacheFileUtils(this.inputCLIOptions.identifierNamesCachePath);
     }
 
     public run (): void {
@@ -163,7 +171,7 @@ export class JavaScriptObfuscatorCLI implements IInitializable {
             return;
         }
 
-        const sourceCodeData: IFileData[] = this.sourceCodeReader.readSourceCode();
+        const sourceCodeData: IFileData[] = this.sourceCodeFileUtils.readSourceCode();
 
         this.processSourceCodeData(sourceCodeData);
     }
@@ -238,6 +246,10 @@ export class JavaScriptObfuscatorCLI implements IInitializable {
                 'Enables force transformation of string literals, which being matched by passed RegExp patterns (comma separated)',
                 ArraySanitizer
             )
+            .option(
+                '--identifier-names-cache-path <string>',
+                'Sets path for identifier names cache'
+            )
             .option(
                 '--identifier-names-generator <string>',
                 'Sets identifier names generator. ' +
@@ -430,7 +442,7 @@ export class JavaScriptObfuscatorCLI implements IInitializable {
      */
     private processSourceCodeData (sourceCodeData: IFileData[]): void {
         sourceCodeData.forEach(({ filePath, content }: IFileData, index: number) => {
-            const outputCodePath: string = this.obfuscatedCodeWriter.getOutputCodePath(filePath);
+            const outputCodePath: string = this.obfuscatedCodeFileUtils.getOutputCodePath(filePath);
 
             try {
                 Logger.log(
@@ -466,6 +478,7 @@ export class JavaScriptObfuscatorCLI implements IInitializable {
     ): void {
         const options: TInputOptions = {
             ...this.inputCLIOptions,
+            identifierNamesCache: this.identifierNamesCacheFileUtils.readFile(),
             inputFileName: path.basename(inputCodePath),
             ...sourceCodeIndex !== null && {
                 identifiersPrefix: Utils.getIdentifiersPrefixForMultipleSources(
@@ -492,9 +505,10 @@ export class JavaScriptObfuscatorCLI implements IInitializable {
         outputCodePath: string,
         options: TInputOptions
     ): void {
-        const obfuscatedCode: string = JavaScriptObfuscator.obfuscate(sourceCode, options).getObfuscatedCode();
+        const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(sourceCode, options);
 
-        this.obfuscatedCodeWriter.writeFile(outputCodePath, obfuscatedCode);
+        this.obfuscatedCodeFileUtils.writeFile(outputCodePath, obfuscationResult.getObfuscatedCode());
+        this.identifierNamesCacheFileUtils.writeFile(obfuscationResult.getIdentifierNamesCache());
     }
 
     /**
@@ -507,7 +521,7 @@ export class JavaScriptObfuscatorCLI implements IInitializable {
         outputCodePath: string,
         options: TInputOptions
     ): void {
-        const outputSourceMapPath: string = this.obfuscatedCodeWriter.getOutputSourceMapPath(
+        const outputSourceMapPath: string = this.obfuscatedCodeFileUtils.getOutputSourceMapPath(
             outputCodePath,
             options.sourceMapFileName ?? ''
         );
@@ -517,12 +531,13 @@ export class JavaScriptObfuscatorCLI implements IInitializable {
             sourceMapFileName: path.basename(outputSourceMapPath)
         };
 
-        const obfuscatedCode: IObfuscatedCode = JavaScriptObfuscator.obfuscate(sourceCode, options);
+        const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(sourceCode, options);
 
-        this.obfuscatedCodeWriter.writeFile(outputCodePath, obfuscatedCode.getObfuscatedCode());
+        this.obfuscatedCodeFileUtils.writeFile(outputCodePath, obfuscationResult.getObfuscatedCode());
+        this.identifierNamesCacheFileUtils.writeFile(obfuscationResult.getIdentifierNamesCache());
 
-        if (options.sourceMapMode === SourceMapMode.Separate && obfuscatedCode.getSourceMap()) {
-            this.obfuscatedCodeWriter.writeFile(outputSourceMapPath, obfuscatedCode.getSourceMap());
+        if (options.sourceMapMode === SourceMapMode.Separate && obfuscationResult.getSourceMap()) {
+            this.obfuscatedCodeFileUtils.writeFile(outputSourceMapPath, obfuscationResult.getSourceMap());
         }
     }
 }

+ 98 - 0
src/cli/utils/IdentifierNamesCacheFileUtils.ts

@@ -0,0 +1,98 @@
+import * as fs from 'fs';
+import * as path from 'path';
+
+import { TIdentifierNamesCache } from '../../types/TIdentifierNamesCache';
+
+import { IFileData } from '../../interfaces/cli/IFileData';
+
+import { JavaScriptObfuscatorCLI } from '../JavaScriptObfuscatorCLI';
+
+/**
+ * Utils to work with identifier names cache file
+ */
+export class IdentifierNamesCacheFileUtils {
+    /**
+     * @type {string}
+     */
+    private static readonly identifierNamesCacheExtension: string = '.json';
+
+    /**
+     * @type {string}
+     */
+    private readonly identifierNamesCachePath: string | undefined;
+
+    /**
+     * @param {string} identifierNamesCachePath
+     */
+    public constructor (identifierNamesCachePath: string | undefined) {
+        this.identifierNamesCachePath = identifierNamesCachePath;
+    }
+
+    /**
+     * @param {string} filePath
+     * @returns {boolean}
+     */
+    private static isValidFilePath (filePath: string): boolean {
+        try {
+            return fs.statSync(filePath).isFile()
+                && path.extname(filePath) === IdentifierNamesCacheFileUtils.identifierNamesCacheExtension;
+        } catch {
+            return false;
+        }
+    }
+
+    /**
+     * @param {string} filePath
+     * @returns {IFileData}
+     */
+    private static readFile (filePath: string): IFileData {
+        return {
+            filePath: path.normalize(filePath),
+            content: fs.readFileSync(filePath, JavaScriptObfuscatorCLI.encoding)
+        };
+    }
+
+    /**
+     * @returns {TIdentifierNamesCache | null}
+     */
+    public readFile (): TIdentifierNamesCache | null {
+        if (!this.identifierNamesCachePath) {
+            return null;
+        }
+
+        if (!IdentifierNamesCacheFileUtils.isValidFilePath(this.identifierNamesCachePath)) {
+            throw new ReferenceError(`Given identifier names cache path must be a valid ${
+                IdentifierNamesCacheFileUtils.identifierNamesCacheExtension
+            } file path`);
+        }
+
+        const fileData: IFileData = IdentifierNamesCacheFileUtils.readFile(this.identifierNamesCachePath);
+
+        if (!fileData.content) {
+            // Initial state of identifier names cache file
+            return {};
+        }
+
+        try {
+            // Already written identifier names cache file
+            return JSON.parse(fileData.content);
+        } catch {
+            throw new ReferenceError('Identifier names cache file must contains a json dictionary with identifier names');
+        }
+    }
+
+    /**
+     * @param {TIdentifierNamesCache} identifierNamesCache
+     */
+    public writeFile (identifierNamesCache: TIdentifierNamesCache): void {
+        if (!this.identifierNamesCachePath) {
+            return;
+        }
+
+        const identifierNamesCacheJson: string = JSON.stringify(identifierNamesCache);
+
+        fs.writeFileSync(this.identifierNamesCachePath, identifierNamesCacheJson, {
+            encoding: JavaScriptObfuscatorCLI.encoding
+        });
+    }
+}

+ 1 - 1
src/cli/utils/ObfuscatedCodeWriter.ts → src/cli/utils/ObfuscatedCodeFileUtils.ts

@@ -8,7 +8,7 @@ import { StringSeparator } from '../../enums/StringSeparator';
 
 import { JavaScriptObfuscatorCLI } from '../JavaScriptObfuscatorCLI';
 
-export class ObfuscatedCodeWriter {
+export class ObfuscatedCodeFileUtils {
     /**
      * @type {string}
      */

+ 13 - 13
src/cli/utils/SourceCodeReader.ts → src/cli/utils/SourceCodeFileUtils.ts

@@ -8,7 +8,7 @@ import { IFileData } from '../../interfaces/cli/IFileData';
 
 import { JavaScriptObfuscatorCLI } from '../JavaScriptObfuscatorCLI';
 
-export class SourceCodeReader {
+export class SourceCodeFileUtils {
     /**
      * @type {string}
      */
@@ -80,7 +80,7 @@ export class SourceCodeReader {
      * @returns {boolean}
      */
     private static isValidDirectory (directoryPath: string, excludePatterns: string[] = []): boolean {
-        return !SourceCodeReader.isExcludedPath(directoryPath, excludePatterns);
+        return !SourceCodeFileUtils.isExcludedPath(directoryPath, excludePatterns);
     }
 
     /**
@@ -91,7 +91,7 @@ export class SourceCodeReader {
     private static isValidFile (filePath: string, excludePatterns: string[] = []): boolean {
         return JavaScriptObfuscatorCLI.availableInputExtensions.includes(path.extname(filePath))
             && !filePath.includes(JavaScriptObfuscatorCLI.obfuscatedFilePrefix)
-            && !SourceCodeReader.isExcludedPath(filePath, excludePatterns);
+            && !SourceCodeFileUtils.isExcludedPath(filePath, excludePatterns);
     }
 
     /**
@@ -110,15 +110,15 @@ export class SourceCodeReader {
      */
     public readSourceCode (): IFileData[] {
         if (
-            SourceCodeReader.isFilePath(this.inputPath)
-            && SourceCodeReader.isValidFile(this.inputPath, this.options.exclude)
+            SourceCodeFileUtils.isFilePath(this.inputPath)
+            && SourceCodeFileUtils.isValidFile(this.inputPath, this.options.exclude)
         ) {
-            return [SourceCodeReader.readFile(this.inputPath)];
+            return [SourceCodeFileUtils.readFile(this.inputPath)];
         }
 
         if (
-            SourceCodeReader.isDirectoryPath(this.inputPath)
-            && SourceCodeReader.isValidDirectory(this.inputPath, this.options.exclude)
+            SourceCodeFileUtils.isDirectoryPath(this.inputPath)
+            && SourceCodeFileUtils.isValidDirectory(this.inputPath, this.options.exclude)
         ) {
             return this.readDirectoryRecursive(this.inputPath);
         }
@@ -142,8 +142,8 @@ export class SourceCodeReader {
                 const filePath: string = path.join(directoryPath, fileName);
 
                 if (
-                    SourceCodeReader.isDirectoryPath(filePath)
-                    && SourceCodeReader.isValidDirectory(filePath, this.options.exclude)
+                    SourceCodeFileUtils.isDirectoryPath(filePath)
+                    && SourceCodeFileUtils.isValidDirectory(filePath, this.options.exclude)
                 ) {
                     filesData.push(...this.readDirectoryRecursive(filePath));
 
@@ -151,10 +151,10 @@ export class SourceCodeReader {
                 }
 
                 if (
-                    SourceCodeReader.isFilePath(filePath)
-                    && SourceCodeReader.isValidFile(filePath, this.options.exclude)
+                    SourceCodeFileUtils.isFilePath(filePath)
+                    && SourceCodeFileUtils.isValidFile(filePath, this.options.exclude)
                 ) {
-                    const fileData: IFileData = SourceCodeReader.readFile(filePath);
+                    const fileData: IFileData = SourceCodeFileUtils.readFile(filePath);
 
                     filesData.push(fileData);
 

+ 11 - 11
src/container/InversifyContainerFacade.ts

@@ -28,7 +28,7 @@ import { ICodeTransformersRunner } from '../interfaces/code-transformers/ICodeTr
 import { IInversifyContainerFacade } from '../interfaces/container/IInversifyContainerFacade';
 import { IJavaScriptObfuscator } from '../interfaces/IJavaScriptObfsucator';
 import { ILogger } from '../interfaces/logger/ILogger';
-import { IObfuscatedCode } from '../interfaces/source-code/IObfuscatedCode';
+import { IObfuscationResult } from '../interfaces/source-code/IObfuscationResult';
 import { ISourceCode } from '../interfaces/source-code/ISourceCode';
 import { INodeTransformersRunner } from '../interfaces/node-transformers/INodeTransformersRunner';
 
@@ -36,7 +36,7 @@ import { CodeTransformersRunner } from '../code-transformers/CodeTransformersRun
 import { JavaScriptObfuscator } from '../JavaScriptObfuscator';
 import { Logger } from '../logger/Logger';
 import { NodeTransformersRunner } from '../node-transformers/NodeTransformersRunner';
-import { ObfuscatedCode } from '../source-code/ObfuscatedCode';
+import { ObfuscationResult } from '../source-code/ObfuscationResult';
 import { SourceCode } from '../source-code/SourceCode';
 
 export class InversifyContainerFacade implements IInversifyContainerFacade {
@@ -181,19 +181,19 @@ export class InversifyContainerFacade implements IInversifyContainerFacade {
             .inSingletonScope();
 
         this.container
-            .bind<IObfuscatedCode>(ServiceIdentifiers.IObfuscatedCode)
-            .to(ObfuscatedCode);
+            .bind<IObfuscationResult>(ServiceIdentifiers.IObfuscationResult)
+            .to(ObfuscationResult);
 
         this.container
-            .bind<IObfuscatedCode>(ServiceIdentifiers.Factory__IObfuscatedCode)
-            .toFactory<IObfuscatedCode>((context: interfaces.Context) => {
-                return (obfuscatedCodeAsString: string, sourceMapAsString: string): IObfuscatedCode => {
-                    const obfuscatedCode: IObfuscatedCode = context.container
-                        .get<IObfuscatedCode>(ServiceIdentifiers.IObfuscatedCode);
+            .bind<IObfuscationResult>(ServiceIdentifiers.Factory__IObfuscationResult)
+            .toFactory<IObfuscationResult>((context: interfaces.Context) => {
+                return (obfuscatedCodeAsString: string, sourceMapAsString: string): IObfuscationResult => {
+                    const obfuscationResult: IObfuscationResult = context.container
+                        .get<IObfuscationResult>(ServiceIdentifiers.IObfuscationResult);
 
-                    obfuscatedCode.initialize(obfuscatedCodeAsString, sourceMapAsString);
+                    obfuscationResult.initialize(obfuscatedCodeAsString, sourceMapAsString);
 
-                    return obfuscatedCode;
+                    return obfuscationResult;
                 };
             });
 

+ 5 - 2
src/container/ServiceIdentifiers.ts

@@ -9,7 +9,7 @@ export enum ServiceIdentifiers {
     Factory__IIdentifierNamesGenerator = 'Factory<IIdentifierNamesGenerator>',
     Factory__INodeGuard = 'Factory<INodeGuard>',
     Factory__INodeTransformer = 'Factory<INodeTransformer[]>',
-    Factory__IObfuscatedCode = 'Factory<IObfuscatedCode>',
+    Factory__IObfuscationResult = 'Factory<IObfuscationResult>',
     Factory__IObjectExpressionKeysTransformerCustomNode = 'Factory<IObjectExpressionKeysTransformerCustomNode>',
     Factory__IObjectExpressionExtractor = 'Factory<IObjectExpressionExtractor>',
     Factory__IStringArrayCustomNode = 'Factory<IStringArrayCustomNode>',
@@ -29,6 +29,7 @@ export enum ServiceIdentifiers {
     ICustomCodeHelperFormatter = 'ICustomCodeHelperFormatter',
     ICustomCodeHelperObfuscator = 'ICustomCodeHelperObfuscator',
     IEscapeSequenceEncoder = 'IEscapeSequenceEncoder',
+    IGlobalIdentifierNamesCacheStorage = 'IGlobalIdentifierNamesCacheStorage',
     IIdentifierNamesGenerator = 'IIdentifierNamesGenerator',
     IIdentifierReplacer = 'IIdentifierReplacer',
     IJavaScriptObfuscator = 'IJavaScriptObfuscator',
@@ -40,10 +41,11 @@ export enum ServiceIdentifiers {
     INodeTransformerNamesGroupsBuilder = 'INodeTransformerNamesGroupsBuilder',
     INodeTransformersRunner = 'INodeTransformersRunner',
     INumberNumericalExpressionAnalyzer = 'INumberNumericalExpressionAnalyzer',
-    IObfuscatedCode = 'IObfuscatedCode',
+    IObfuscationResult = 'IObfuscationResult',
     IOptions = 'IOptions',
     IOptionsNormalizer = 'IOptionsNormalizer',
     IPrevailingKindOfVariablesAnalyzer = 'IPrevailingKindOfVariablesAnalyzer',
+    IPropertyIdentifierNamesCacheStorage = 'IPropertyIdentifierNamesCacheStorage',
     IObjectExpressionExtractor = 'IObjectExpressionExtractor',
     IRandomGenerator = 'IRandomGenerator',
     IRenamePropertiesReplacer = 'IRenamePropertiesReplacer',
@@ -55,6 +57,7 @@ export enum ServiceIdentifiers {
     IStringArrayScopeCallsWrapperNamesDataStorage = 'IStringArrayScopeCallsWrapperNamesDataStorage',
     IStringArrayStorage = 'IStringArrayStorage',
     IStringArrayStorageAnalyzer = 'IStringArrayStorageAnalyzer',
+    IThroughIdentifierReplacer = 'IThroughIdentifierReplacer',
     IVisitedLexicalScopeNodesStackStorage = 'IVisitedLexicalScopeNodesStackStorage',
     Newable__ICustomNode = 'Newable<ICustomNode>',
     Newable__TControlFlowStorage = 'Newable<TControlFlowStorage>',

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

@@ -6,13 +6,20 @@ import { INodeTransformer } from '../../../interfaces/node-transformers/INodeTra
 
 import { NodeTransformer } from '../../../enums/node-transformers/NodeTransformer';
 
+import { DeadCodeInjectionIdentifiersTransformer } from '../../../node-transformers/dead-code-injection-transformers/DeadCodeInjectionIdentifiersTransformer';
 import { IdentifierReplacer } from '../../../node-transformers/rename-identifiers-transformers/replacer/IdentifierReplacer';
 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
+    bind<INodeTransformer>(ServiceIdentifiers.INodeTransformer)
+        .to(DeadCodeInjectionIdentifiersTransformer)
+        .whenTargetNamed(NodeTransformer.DeadCodeInjectionIdentifiersTransformer);
+
     bind<INodeTransformer>(ServiceIdentifiers.INodeTransformer)
         .to(LabeledStatementTransformer)
         .whenTargetNamed(NodeTransformer.LabeledStatementTransformer);
@@ -29,4 +36,8 @@ export const renameIdentifiersTransformersModule: interfaces.ContainerModule = n
     bind<IIdentifierReplacer>(ServiceIdentifiers.IIdentifierReplacer)
         .to(IdentifierReplacer)
         .inSingletonScope();
+
+    bind<IThroughIdentifierReplacer>(ServiceIdentifiers.IThroughIdentifierReplacer)
+        .to(ThroughIdentifierReplacer)
+        .inSingletonScope();
 });

+ 12 - 0
src/container/modules/storages/StoragesModule.ts

@@ -4,8 +4,10 @@ import { ServiceIdentifiers } from '../../ServiceIdentifiers';
 import { TControlFlowStorage } from '../../../types/storages/TControlFlowStorage';
 import { TCustomCodeHelperGroupStorage } from '../../../types/storages/TCustomCodeHelperGroupStorage';
 
+import { IGlobalIdentifierNamesCacheStorage } from '../../../interfaces/storages/identifier-names-cache/IGlobalIdentifierNamesCacheStorage';
 import { ILiteralNodesCacheStorage } from '../../../interfaces/storages/string-array-transformers/ILiteralNodesCacheStorage';
 import { IOptions } from '../../../interfaces/options/IOptions';
+import { IPropertyIdentifierNamesCacheStorage } from '../../../interfaces/storages/identifier-names-cache/IPropertyIdentifierNamesCacheStorage';
 import { IRandomGenerator } from '../../../interfaces/utils/IRandomGenerator';
 import { IStringArrayScopeCallsWrapperLexicalScopeDataStorage } from '../../../interfaces/storages/string-array-transformers/IStringArrayScopeCallsWrapperLexicalScopeDataStorage';
 import { IStringArrayScopeCallsWrapperNamesDataStorage } from '../../../interfaces/storages/string-array-transformers/IStringArrayScopeCallsWrapperNamesDataStorage';
@@ -14,7 +16,9 @@ import { IVisitedLexicalScopeNodesStackStorage } from '../../../interfaces/stora
 
 import { ControlFlowStorage } from '../../../storages/custom-nodes/ControlFlowStorage';
 import { CustomCodeHelperGroupStorage } from '../../../storages/custom-code-helpers/CustomCodeHelperGroupStorage';
+import { GlobalIdentifierNamesCacheStorage } from '../../../storages/identifier-names-cache/GlobalIdentifierNamesCacheStorage';
 import { LiteralNodesCacheStorage } from '../../../storages/string-array-transformers/LiteralNodesCacheStorage';
+import { PropertyIdentifierNamesCacheStorage } from '../../../storages/identifier-names-cache/PropertyIdentifierNamesCacheStorage';
 import { StringArrayScopeCallsWrapperLexicalScopeDataStorage } from '../../../storages/string-array-transformers/StringArrayScopeCallsWrapperLexicalScopeDataStorage';
 import { StringArrayScopeCallsWrapperNamesDataStorage } from '../../../storages/string-array-transformers/StringArrayScopeCallsWrapperNamesDataStorage';
 import { StringArrayStorage } from '../../../storages/string-array-transformers/StringArrayStorage';
@@ -26,10 +30,18 @@ export const storagesModule: interfaces.ContainerModule = new ContainerModule((b
         .to(CustomCodeHelperGroupStorage)
         .inSingletonScope();
 
+    bind<IGlobalIdentifierNamesCacheStorage>(ServiceIdentifiers.IGlobalIdentifierNamesCacheStorage)
+        .to(GlobalIdentifierNamesCacheStorage)
+        .inSingletonScope();
+
     bind<ILiteralNodesCacheStorage>(ServiceIdentifiers.ILiteralNodesCacheStorage)
         .to(LiteralNodesCacheStorage)
         .inSingletonScope();
 
+    bind<IPropertyIdentifierNamesCacheStorage>(ServiceIdentifiers.IPropertyIdentifierNamesCacheStorage)
+        .to(PropertyIdentifierNamesCacheStorage)
+        .inSingletonScope();
+
     bind<IStringArrayStorage>(ServiceIdentifiers.IStringArrayStorage)
         .to(StringArrayStorage)
         .inSingletonScope();

+ 1 - 0
src/enums/node-transformers/NodeTransformer.ts

@@ -4,6 +4,7 @@ export enum NodeTransformer {
     BlockStatementSimplifyTransformer = 'BlockStatementSimplifyTransformer',
     CommentsTransformer = 'CommentsTransformer',
     CustomCodeHelpersTransformer = 'CustomCodeHelpersTransformer',
+    DeadCodeInjectionIdentifiersTransformer = 'DeadCodeInjectionIdentifiersTransformer',
     DeadCodeInjectionTransformer = 'DeadCodeInjectionTransformer',
     DirectivePlacementTransformer = 'DirectivePlacementTransformer',
     EscapeSequenceTransformer = 'EscapeSequenceTransformer',

+ 3 - 3
src/interfaces/IJavaScriptObfsucator.ts

@@ -1,9 +1,9 @@
-import { IObfuscatedCode } from './source-code/IObfuscatedCode';
+import { IObfuscationResult } from './source-code/IObfuscationResult';
 
 export interface IJavaScriptObfuscator {
     /**
      * @param sourceCode
-     * @returns IObfuscatedCode
+     * @returns IObfuscationResult
      */
-    obfuscate (sourceCode: string): IObfuscatedCode;
+    obfuscate (sourceCode: string): IObfuscationResult;
 }

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

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

+ 1 - 0
src/interfaces/node/IScopeThroughIdentifiersTraverserCallbackData.ts

@@ -3,6 +3,7 @@ import * as eslintScope from 'eslint-scope';
 import { TNodeWithLexicalScope } from '../../types/node/TNodeWithLexicalScope';
 
 export interface IScopeThroughIdentifiersTraverserCallbackData {
+    isGlobalDeclaration: boolean;
     reference: eslintScope.Reference;
     variableLexicalScopeNode: TNodeWithLexicalScope;
 }

+ 1 - 0
src/interfaces/options/ICLIOptions.ts

@@ -3,6 +3,7 @@ import { IOptions } from './IOptions';
 export interface ICLIOptions extends IOptions {
     readonly config: string;
     readonly exclude: string[];
+    readonly identifierNamesCachePath: string;
     readonly output: string;
     readonly version: string;
 }

+ 2 - 0
src/interfaces/options/IOptions.ts

@@ -1,3 +1,4 @@
+import { TIdentifierNamesCache } from '../../types/TIdentifierNamesCache';
 import { TOptionsPreset } from '../../types/options/TOptionsPreset';
 import { TStringArrayIndexesType } from '../../types/options/TStringArrayIndexesType';
 import { TStringArrayEncoding } from '../../types/options/TStringArrayEncoding';
@@ -20,6 +21,7 @@ export interface IOptions {
     readonly disableConsoleOutput: boolean;
     readonly domainLock: string[];
     readonly forceTransformStrings: string[];
+    readonly identifierNamesCache: TIdentifierNamesCache;
     readonly identifierNamesGenerator: TTypeFromEnum<typeof IdentifierNamesGenerator>;
     readonly identifiersDictionary: string[];
     readonly identifiersPrefix: string;

+ 0 - 13
src/interfaces/source-code/IObfuscatedCode.ts

@@ -1,13 +0,0 @@
-import { IInitializable } from '../IInitializable';
-
-export interface IObfuscatedCode extends IInitializable <[string, string]> {
-    /**
-     * @return {string}
-     */
-    getObfuscatedCode (): string;
-
-    /**
-     * @return {string}
-     */
-    getSourceMap (): string;
-}

+ 19 - 0
src/interfaces/source-code/IObfuscationResult.ts

@@ -0,0 +1,19 @@
+import { TIdentifierNamesCache } from '../../types/TIdentifierNamesCache';
+import { IInitializable } from '../IInitializable';
+
+export interface IObfuscationResult extends IInitializable <[string, string]> {
+    /**
+     * @returns {TIdentifierNamesCache}
+     */
+    getIdentifierNamesCache (): TIdentifierNamesCache;
+
+    /**
+     * @return {string}
+     */
+    getObfuscatedCode (): string;
+
+    /**
+     * @return {string}
+     */
+    getSourceMap (): string;
+}

+ 7 - 0
src/interfaces/storages/IMapStorage.ts

@@ -1,3 +1,5 @@
+import { TDictionary } from '../../types/TDictionary';
+
 import { IInitializable } from '../IInitializable';
 
 export interface IMapStorage <K, V> extends IInitializable {
@@ -29,6 +31,11 @@ export interface IMapStorage <K, V> extends IInitializable {
      */
     getStorage (): Map <K, V>;
 
+    /**
+     * @returns {TDictionary<V>}
+     */
+    getStorageAsDictionary (): TDictionary<V>;
+
     /**
      * @returns string
      */

+ 4 - 0
src/interfaces/storages/identifier-names-cache/IGlobalIdentifierNamesCacheStorage.ts

@@ -0,0 +1,4 @@
+import { IMapStorage } from '../IMapStorage';
+
+// eslint-disable-next-line
+export interface IGlobalIdentifierNamesCacheStorage extends IMapStorage <string, string> {}

+ 4 - 0
src/interfaces/storages/identifier-names-cache/IPropertyIdentifierNamesCacheStorage.ts

@@ -0,0 +1,4 @@
+import { IMapStorage } from '../IMapStorage';
+
+// eslint-disable-next-line
+export interface IPropertyIdentifierNamesCacheStorage extends IMapStorage <string, string> {}

+ 141 - 0
src/node-transformers/dead-code-injection-transformers/DeadCodeInjectionIdentifiersTransformer.ts

@@ -0,0 +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 { 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 { 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 AbstractNodeTransformer {
+    /**
+     * @type {IIdentifierReplacer}
+     */
+    private readonly identifierReplacer: IIdentifierReplacer;
+
+    /**
+     * @type {IScopeIdentifiersTraverser}
+     */
+    private readonly scopeIdentifiersTraverser: IScopeIdentifiersTraverser;
+
+    /**
+     * @param {IIdentifierReplacer} identifierReplacer
+     * @param {IRandomGenerator} randomGenerator
+     * @param {IOptions} options
+     * @param {IScopeIdentifiersTraverser} scopeIdentifiersTraverser
+     */
+    public constructor (
+        @inject(ServiceIdentifiers.IIdentifierReplacer) identifierReplacer: IIdentifierReplacer,
+        @inject(ServiceIdentifiers.IRandomGenerator) randomGenerator: IRandomGenerator,
+        @inject(ServiceIdentifiers.IOptions) options: IOptions,
+        @inject(ServiceIdentifiers.IScopeIdentifiersTraverser) scopeIdentifiersTraverser: IScopeIdentifiersTraverser
+    ) {
+        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);
+    }
+
+    /**
+     * @param {Identifier} identifierNode
+     * @param {TNodeWithLexicalScope} lexicalScopeNode
+     */
+    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;
+    }
+}

+ 2 - 2
src/node-transformers/dead-code-injection-transformers/DeadCodeInjectionTransformer.ts

@@ -47,9 +47,9 @@ export class DeadCodeInjectionTransformer extends AbstractNodeTransformer {
      * @type {NodeTransformer[]}
      */
     private static readonly transformersToRenameBlockScopeIdentifiers: NodeTransformer[] = [
+        NodeTransformer.DeadCodeInjectionIdentifiersTransformer,
         NodeTransformer.LabeledStatementTransformer,
-        NodeTransformer.ScopeIdentifiersTransformer,
-        NodeTransformer.ScopeThroughIdentifiersTransformer
+        NodeTransformer.ScopeIdentifiersTransformer
     ];
 
     /**

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

@@ -6,11 +6,11 @@ import * as ESTree from 'estree';
 
 import { TNodeWithLexicalScope } from '../../types/node/TNodeWithLexicalScope';
 
-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';
@@ -19,35 +19,35 @@ import { AbstractNodeTransformer } from '../AbstractNodeTransformer';
 import { NodeGuards } from '../../node/NodeGuards';
 
 /**
- * Renames all through identifiers. Now used directly from Dead Code Injection transformer
+ * Renames all through identifiers
  */
 @injectable()
 export class ScopeThroughIdentifiersTransformer extends AbstractNodeTransformer {
     /**
-     * @type {IIdentifierReplacer}
+     * @type {IScopeIdentifiersTraverser}
      */
-    private readonly identifierReplacer: IIdentifierReplacer;
+    protected readonly scopeIdentifiersTraverser: IScopeIdentifiersTraverser;
 
     /**
-     * @type {IScopeIdentifiersTraverser}
+     * @type {IThroughIdentifierReplacer}
      */
-    private readonly scopeIdentifiersTraverser: IScopeIdentifiersTraverser;
+    protected readonly throughIdentifierReplacer: IThroughIdentifierReplacer;
 
     /**
-     * @param {IIdentifierReplacer} identifierReplacer
+     * @param {IThroughIdentifierReplacer} throughIdentifierReplacer
+     * @param {IScopeIdentifiersTraverser} scopeIdentifiersTraverser
      * @param {IRandomGenerator} randomGenerator
      * @param {IOptions} options
-     * @param {IScopeIdentifiersTraverser} scopeIdentifiersTraverser
      */
     public constructor (
-        @inject(ServiceIdentifiers.IIdentifierReplacer) identifierReplacer: IIdentifierReplacer,
+        @inject(ServiceIdentifiers.IThroughIdentifierReplacer) throughIdentifierReplacer: IThroughIdentifierReplacer,
+        @inject(ServiceIdentifiers.IScopeIdentifiersTraverser) scopeIdentifiersTraverser: IScopeIdentifiersTraverser,
         @inject(ServiceIdentifiers.IRandomGenerator) randomGenerator: IRandomGenerator,
-        @inject(ServiceIdentifiers.IOptions) options: IOptions,
-        @inject(ServiceIdentifiers.IScopeIdentifiersTraverser) scopeIdentifiersTraverser: IScopeIdentifiersTraverser
+        @inject(ServiceIdentifiers.IOptions) options: IOptions
     ) {
         super(randomGenerator, options);
 
-        this.identifierReplacer = identifierReplacer;
+        this.throughIdentifierReplacer = throughIdentifierReplacer;
         this.scopeIdentifiersTraverser = scopeIdentifiersTraverser;
     }
 
@@ -86,7 +86,10 @@ export class ScopeThroughIdentifiersTransformer extends AbstractNodeTransformer
                     variableLexicalScopeNode
                 } = data;
 
-                this.transformScopeThroughIdentifiers(reference, variableLexicalScopeNode);
+                this.transformScopeThroughIdentifiers(
+                    reference,
+                    variableLexicalScopeNode
+                );
             }
         );
 
@@ -97,43 +100,23 @@ export class ScopeThroughIdentifiersTransformer extends AbstractNodeTransformer
      * @param {Reference} reference
      * @param {TNodeWithLexicalScope} lexicalScopeNode
      */
-    private transformScopeThroughIdentifiers (
+    protected transformScopeThroughIdentifiers (
         reference: eslintScope.Reference,
-        lexicalScopeNode: TNodeWithLexicalScope,
+        lexicalScopeNode: TNodeWithLexicalScope
     ): void {
         if (reference.resolved) {
             return;
         }
 
-        const identifier: ESTree.Identifier = reference.identifier;
-
-        this.storeIdentifierName(identifier, lexicalScopeNode);
-        this.replaceIdentifierName(identifier, lexicalScopeNode, reference);
+        this.replaceIdentifierName(reference);
     }
 
     /**
-     * @param {Identifier} identifierNode
-     * @param {TNodeWithLexicalScope} lexicalScopeNode
-     */
-    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);
+    protected replaceIdentifierName (reference: eslintScope.Reference): void {
+        const identifier: ESTree.Identifier = reference.identifier;
+        const newIdentifier: ESTree.Identifier = this.throughIdentifierReplacer.replace(identifier);
 
         // rename of identifier
         reference.identifier.name = newIdentifier.name;

+ 17 - 2
src/node-transformers/rename-identifiers-transformers/replacer/IdentifierReplacer.ts

@@ -6,6 +6,7 @@ import * as ESTree from 'estree';
 import { TIdentifierNamesGeneratorFactory } from '../../../types/container/generators/TIdentifierNamesGeneratorFactory';
 import { TNodeWithLexicalScope } from '../../../types/node/TNodeWithLexicalScope';
 
+import { IGlobalIdentifierNamesCacheStorage } from '../../../interfaces/storages/identifier-names-cache/IGlobalIdentifierNamesCacheStorage';
 import { IIdentifierNamesGenerator } from '../../../interfaces/generators/identifier-names-generators/IIdentifierNamesGenerator';
 import { IIdentifierReplacer } from '../../../interfaces/node-transformers/rename-identifiers-transformers/replacer/IIdentifierReplacer';
 import { IOptions } from '../../../interfaces/options/IOptions';
@@ -14,6 +15,11 @@ import { NodeFactory } from '../../../node/NodeFactory';
 
 @injectable()
 export class IdentifierReplacer implements IIdentifierReplacer {
+    /**
+     * @type {IGlobalIdentifierNamesCacheStorage}
+     */
+    private readonly identifierNamesCacheStorage: IGlobalIdentifierNamesCacheStorage;
+
     /**
      * @type {IIdentifierNamesGenerator}
      */
@@ -31,19 +37,23 @@ export class IdentifierReplacer implements IIdentifierReplacer {
 
     /**
      * @param {TIdentifierNamesGeneratorFactory} identifierNamesGeneratorFactory
+     * @param {IGlobalIdentifierNamesCacheStorage} identifierNamesCacheStorage
      * @param {IOptions} options
      */
     public constructor (
         @inject(ServiceIdentifiers.Factory__IIdentifierNamesGenerator)
             identifierNamesGeneratorFactory: TIdentifierNamesGeneratorFactory,
+        @inject(ServiceIdentifiers.IGlobalIdentifierNamesCacheStorage)
+            identifierNamesCacheStorage: IGlobalIdentifierNamesCacheStorage,
         @inject(ServiceIdentifiers.IOptions) options: IOptions
     ) {
         this.options = options;
+        this.identifierNamesCacheStorage = identifierNamesCacheStorage;
         this.identifierNamesGenerator = identifierNamesGeneratorFactory(options);
     }
 
     /**
-     * Store `nodeName` of global identifiers as key in map with random name as value.
+     * Store identifier node `name` of global identifiers as key in map with random name as value.
      * Reserved name will be ignored.
      *
      * @param {Node} identifierNode
@@ -65,10 +75,15 @@ export class IdentifierReplacer implements IIdentifierReplacer {
         const namesMap: Map<string, string> = <Map<string, string>>this.blockScopesMap.get(lexicalScopeNode);
 
         namesMap.set(identifierName, newIdentifierName);
+
+        // Have to write all global identifier names to the identifier names cache storage
+        if (this.options.identifierNamesCache) {
+            this.identifierNamesCacheStorage.set(identifierName, newIdentifierName);
+        }
     }
 
     /**
-     * Store `nodeName` of local identifier as key in map with random name as value.
+     * Store identifier node `name` of local identifier as key in map with random name as value.
      * Reserved name will be ignored.
      *
      * @param {Identifier} identifierNode

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

@@ -0,0 +1,64 @@
+import { inject, injectable, } from 'inversify';
+import { ServiceIdentifiers } from '../../../container/ServiceIdentifiers';
+
+import * as ESTree from 'estree';
+
+import { IGlobalIdentifierNamesCacheStorage } from '../../../interfaces/storages/identifier-names-cache/IGlobalIdentifierNamesCacheStorage';
+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 {IGlobalIdentifierNamesCacheStorage}
+     */
+    private readonly identifierNamesCacheStorage: IGlobalIdentifierNamesCacheStorage;
+
+    /**
+     * @type {IOptions}
+     */
+    private readonly options: IOptions;
+
+    /**
+     * @param {IGlobalIdentifierNamesCacheStorage} identifierNamesCacheStorage
+     * @param {IOptions} options
+     */
+    public constructor (
+        @inject(ServiceIdentifiers.IGlobalIdentifierNamesCacheStorage)
+            identifierNamesCacheStorage: IGlobalIdentifierNamesCacheStorage,
+        @inject(ServiceIdentifiers.IOptions) options: IOptions
+    ) {
+        this.identifierNamesCacheStorage = identifierNamesCacheStorage;
+        this.options = options;
+    }
+
+    /**
+     * @param {Identifier} identifierNode
+     * @returns {Identifier}
+     */
+    public replace (identifierNode: ESTree.Identifier): ESTree.Identifier {
+        const identifierName: string = identifierNode.name;
+        const newIdentifierName: string = this.options.identifierNamesCache && !this.isReservedName(identifierName)
+            ? this.identifierNamesCacheStorage.get(identifierName) ?? identifierName
+            : identifierName;
+
+        return NodeFactory.identifierNode(newIdentifierName);
+    }
+
+    /**
+     * @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;
+            });
+    }
+}

+ 22 - 2
src/node-transformers/rename-properties-transformers/replacer/RenamePropertiesReplacer.ts

@@ -7,8 +7,9 @@ import * as ESTree from 'estree';
 import { TIdentifierNamesGeneratorFactory } from '../../../types/container/generators/TIdentifierNamesGeneratorFactory';
 
 import { IIdentifierNamesGenerator } from '../../../interfaces/generators/identifier-names-generators/IIdentifierNamesGenerator';
-import { IRenamePropertiesReplacer } from '../../../interfaces/node-transformers/rename-properties-transformers/replacer/IRenamePropertiesReplacer';
 import { IOptions } from '../../../interfaces/options/IOptions';
+import { IPropertyIdentifierNamesCacheStorage } from '../../../interfaces/storages/identifier-names-cache/IPropertyIdentifierNamesCacheStorage';
+import { IRenamePropertiesReplacer } from '../../../interfaces/node-transformers/rename-properties-transformers/replacer/IRenamePropertiesReplacer';
 
 // eslint-disable-next-line import/no-internal-modules
 import ReservedDomProperties from './ReservedDomProperties.json';
@@ -38,6 +39,11 @@ export class RenamePropertiesReplacer implements IRenamePropertiesReplacer {
      */
     private readonly excludedPropertyNames: Set<string> = new Set();
 
+    /**
+     * @type {IPropertyIdentifierNamesCacheStorage}
+     */
+    private readonly propertyIdentifierNamesCacheStorage: IPropertyIdentifierNamesCacheStorage;
+
     /**
      * @type {Map<string, string>}
      * @private
@@ -51,14 +57,18 @@ export class RenamePropertiesReplacer implements IRenamePropertiesReplacer {
 
     /**
      * @param {TIdentifierNamesGeneratorFactory} identifierNamesGeneratorFactory
+     * @param {IPropertyIdentifierNamesCacheStorage} propertyIdentifierNamesCacheStorage
      * @param {IOptions} options
      */
     public constructor (
         @inject(ServiceIdentifiers.Factory__IIdentifierNamesGenerator)
             identifierNamesGeneratorFactory: TIdentifierNamesGeneratorFactory,
+        @inject(ServiceIdentifiers.IPropertyIdentifierNamesCacheStorage)
+            propertyIdentifierNamesCacheStorage: IPropertyIdentifierNamesCacheStorage,
         @inject(ServiceIdentifiers.IOptions) options: IOptions
     ) {
         this.identifierNamesGenerator = identifierNamesGeneratorFactory(options);
+        this.propertyIdentifierNamesCacheStorage = propertyIdentifierNamesCacheStorage;
         this.options = options;
     }
 
@@ -99,7 +109,13 @@ export class RenamePropertiesReplacer implements IRenamePropertiesReplacer {
             return propertyName;
         }
 
-        let renamedPropertyName: string | null = this.propertyNamesMap.get(propertyName) ?? null;
+        let renamedPropertyName: string | null = this.options.identifierNamesCache
+            ? this.propertyIdentifierNamesCacheStorage.get(propertyName) ?? null
+            : null;
+
+        renamedPropertyName = renamedPropertyName
+            ?? this.propertyNamesMap.get(propertyName)
+            ?? null;
 
         if (renamedPropertyName !== null) {
             return renamedPropertyName;
@@ -108,6 +124,10 @@ export class RenamePropertiesReplacer implements IRenamePropertiesReplacer {
         renamedPropertyName = this.identifierNamesGenerator.generateNext();
         this.propertyNamesMap.set(propertyName, renamedPropertyName);
 
+        if (this.options.identifierNamesCache) {
+            this.propertyIdentifierNamesCacheStorage.set(propertyName, renamedPropertyName);
+        }
+
         return renamedPropertyName;
     }
 

+ 1 - 0
src/node-transformers/rename-properties-transformers/replacer/ReservedDomProperties.json

@@ -1029,6 +1029,7 @@
     "EQUALPOWER",
     "ERROR",
     "EXPONENTIAL_DISTANCE",
+    "exports",
     "Element",
     "ElementInternals",
     "ElementQuery",

+ 2 - 0
src/node/ScopeIdentifiersTraverser.ts

@@ -142,6 +142,7 @@ export class ScopeIdentifiersTraverser implements IScopeIdentifiersTraverser {
         const variableLexicalScopeNode: TNodeWithLexicalScope | null = NodeGuards.isNodeWithBlockLexicalScope(variableScope.block)
             ? variableScope.block
             : null;
+        const isGlobalDeclaration: boolean = ScopeIdentifiersTraverser.globalScopeNames.includes(variableScope.type);
 
         if (!variableLexicalScopeNode) {
             return;
@@ -149,6 +150,7 @@ export class ScopeIdentifiersTraverser implements IScopeIdentifiersTraverser {
 
         for (const reference of currentScope.through) {
             callback({
+                isGlobalDeclaration,
                 reference,
                 variableLexicalScopeNode
             });

+ 8 - 0
src/options/Options.ts

@@ -18,6 +18,7 @@ import {
     ValidatorOptions
 } from 'class-validator';
 
+import { TIdentifierNamesCache } from '../types/TIdentifierNamesCache';
 import { TInputOptions } from '../types/options/TInputOptions';
 import { TOptionsPreset } from '../types/options/TOptionsPreset';
 import { TRenamePropertiesMode } from '../types/options/TRenamePropertiesMode';
@@ -45,6 +46,7 @@ import { HIGH_OBFUSCATION_PRESET } from './presets/HighObfuscation';
 
 import { ValidationErrorsFormatter } from './ValidationErrorsFormatter';
 import { IsAllowedForObfuscationTargets } from './validators/IsAllowedForObfuscationTargets';
+import { IsIdentifierNamesCache } from './validators/IsIdentifierNamesCache';
 
 @injectable()
 export class Options implements IOptions {
@@ -142,6 +144,12 @@ export class Options implements IOptions {
     })
     public readonly forceTransformStrings!: string[];
 
+    /**
+     * @type {TIdentifierNamesCache}
+     */
+    @IsIdentifierNamesCache()
+    public readonly identifierNamesCache!: TIdentifierNamesCache;
+
     /**
      * @type {IdentifierNamesGenerator}
      */

+ 2 - 0
src/options/OptionsNormalizer.ts

@@ -9,6 +9,7 @@ import { ControlFlowFlatteningThresholdRule } from './normalizer-rules/ControlFl
 import { DeadCodeInjectionRule } from './normalizer-rules/DeadCodeInjectionRule';
 import { DeadCodeInjectionThresholdRule } from './normalizer-rules/DeadCodeInjectionThresholdRule';
 import { DomainLockRule } from './normalizer-rules/DomainLockRule';
+import { IdentifierNamesCacheRule } from './normalizer-rules/IdentifierNamesCacheRule';
 import { InputFileNameRule } from './normalizer-rules/InputFileNameRule';
 import { SeedRule } from './normalizer-rules/SeedRule';
 import { SelfDefendingRule } from './normalizer-rules/SelfDefendingRule';
@@ -29,6 +30,7 @@ export class OptionsNormalizer implements IOptionsNormalizer {
         DeadCodeInjectionRule,
         DeadCodeInjectionThresholdRule,
         DomainLockRule,
+        IdentifierNamesCacheRule,
         InputFileNameRule,
         SeedRule,
         SelfDefendingRule,

+ 32 - 0
src/options/normalizer-rules/IdentifierNamesCacheRule.ts

@@ -0,0 +1,32 @@
+import { TOptionsNormalizerRule } from '../../types/options/TOptionsNormalizerRule';
+
+import { IOptions } from '../../interfaces/options/IOptions';
+
+/**
+ * @param {IOptions} options
+ * @returns {IOptions}
+ */
+export const IdentifierNamesCacheRule: TOptionsNormalizerRule = (options: IOptions): IOptions => {
+    let identifierNamesCache = options.identifierNamesCache;
+
+    if (identifierNamesCache && !identifierNamesCache.globalIdentifiers) {
+        identifierNamesCache = {
+            ...identifierNamesCache,
+            globalIdentifiers: {}
+        };
+    }
+
+    if (identifierNamesCache && !identifierNamesCache.propertyIdentifiers) {
+        identifierNamesCache = {
+            ...identifierNamesCache,
+            propertyIdentifiers: {}
+        };
+    }
+
+    options = {
+        ...options,
+        identifierNamesCache
+    };
+
+    return options;
+};

+ 1 - 0
src/options/presets/Default.ts

@@ -22,6 +22,7 @@ export const DEFAULT_PRESET: TInputOptions = Object.freeze({
     domainLock: [],
     exclude: [],
     forceTransformStrings: [],
+    identifierNamesCache: null,
     identifierNamesGenerator: IdentifierNamesGenerator.HexadecimalIdentifierNamesGenerator,
     identifiersPrefix: '',
     identifiersDictionary: [],

+ 87 - 0
src/options/validators/IsIdentifierNamesCache.ts

@@ -0,0 +1,87 @@
+import equal from 'fast-deep-equal';
+import { registerDecorator, ValidationArguments, ValidationOptions } from 'class-validator';
+
+import { TIdentifierNamesCache } from '../../types/TIdentifierNamesCache';
+import { TIdentifierNamesCacheDictionary } from '../../types/TIdentifierNamesCacheDictionary';
+
+import { IOptions } from '../../interfaces/options/IOptions';
+
+import { DEFAULT_PRESET } from '../presets/Default';
+
+/**
+ * @param value
+ * @returns {boolean}
+ */
+const validateDictionary = (value: unknown | TIdentifierNamesCacheDictionary): boolean => {
+    if (value === undefined) {
+        return true;
+    }
+
+    if (typeof value !== 'object' || value === null) {
+        return false;
+    }
+
+    const objectValues: unknown[] = Object.values(value);
+
+    if (!objectValues.length) {
+        return true;
+    }
+
+    for (const objectValue of objectValues) {
+        if (typeof objectValue !== 'string') {
+            return false;
+        }
+    }
+
+    return true;
+};
+
+/**
+ * @param {ValidationOptions} validationOptions
+ * @returns {(options: IOptions, propertyName: keyof IOptions) => void}
+ */
+export function IsIdentifierNamesCache (
+    validationOptions?: ValidationOptions
+): (options: IOptions, propertyName: keyof IOptions) => void {
+    return (optionsObject: IOptions, propertyName: keyof IOptions): void => {
+        registerDecorator({
+            propertyName,
+            constraints: [],
+            name: 'IsIdentifierNamesCache',
+            options: validationOptions,
+            target: optionsObject.constructor,
+            validator: {
+                /**
+                 * @param value
+                 * @param {ValidationArguments} validationArguments
+                 * @returns {boolean}
+                 */
+                validate (value: unknown, validationArguments: ValidationArguments): boolean {
+                    const defaultValue: IOptions[keyof IOptions] | undefined = DEFAULT_PRESET[propertyName];
+                    const isDefaultValue: boolean = equal(value, defaultValue);
+
+                    if (isDefaultValue || value === null) {
+                        return true;
+                    }
+
+                    if (typeof value !== 'object') {
+                        return false;
+                    }
+
+                    if (!validateDictionary((<TIdentifierNamesCache>value)?.globalIdentifiers)) {
+                        return false;
+                    }
+
+                    return validateDictionary((<TIdentifierNamesCache>value)?.propertyIdentifiers);
+                },
+
+                /**
+                 * @returns {string}
+                 */
+                defaultMessage (): string {
+                    return 'Passed value must be an identifier names cache object or `null` value';
+                }
+            }
+        });
+    };
+}

+ 43 - 3
src/source-code/ObfuscatedCode.ts → src/source-code/ObfuscationResult.ts

@@ -1,15 +1,19 @@
 import { inject, injectable } from 'inversify';
 import { ServiceIdentifiers } from '../container/ServiceIdentifiers';
 
+import { TIdentifierNamesCache } from '../types/TIdentifierNamesCache';
+
 import { ICryptUtils } from '../interfaces/utils/ICryptUtils';
-import { IObfuscatedCode } from '../interfaces/source-code/IObfuscatedCode';
+import { IGlobalIdentifierNamesCacheStorage } from '../interfaces/storages/identifier-names-cache/IGlobalIdentifierNamesCacheStorage';
+import { IObfuscationResult } from '../interfaces/source-code/IObfuscationResult';
+import { IPropertyIdentifierNamesCacheStorage } from '../interfaces/storages/identifier-names-cache/IPropertyIdentifierNamesCacheStorage';
+import { IOptions } from '../interfaces/options/IOptions';
 
 import { initializable } from '../decorators/Initializable';
 import { SourceMapMode } from '../enums/source-map/SourceMapMode';
-import { IOptions } from '../interfaces/options/IOptions';
 
 @injectable()
-export class ObfuscatedCode implements IObfuscatedCode {
+export class ObfuscationResult implements IObfuscationResult {
     /**
      * @type {string}
      */
@@ -27,16 +31,38 @@ export class ObfuscatedCode implements IObfuscatedCode {
      */
     private readonly cryptUtils: ICryptUtils;
 
+    /**
+     * @type {IGlobalIdentifierNamesCacheStorage}
+     */
+    private readonly globalIdentifierNamesCacheStorage: IGlobalIdentifierNamesCacheStorage;
+
+    /**
+     * @type {IPropertyIdentifierNamesCacheStorage}
+     */
+    private readonly propertyIdentifierNamesCacheStorage: IPropertyIdentifierNamesCacheStorage;
+
     /**
      * @type {IOptions}
      */
     private readonly options: IOptions;
 
+    /**
+     * @param {ICryptUtils} cryptUtils
+     * @param {IGlobalIdentifierNamesCacheStorage} globalIdentifierNamesCacheStorage
+     * @param {IPropertyIdentifierNamesCacheStorage} propertyIdentifierNamesCacheStorage
+     * @param {IOptions} options
+     */
     public constructor (
         @inject(ServiceIdentifiers.ICryptUtils) cryptUtils: ICryptUtils,
+        @inject(ServiceIdentifiers.IGlobalIdentifierNamesCacheStorage)
+            globalIdentifierNamesCacheStorage: IGlobalIdentifierNamesCacheStorage,
+        @inject(ServiceIdentifiers.IPropertyIdentifierNamesCacheStorage)
+            propertyIdentifierNamesCacheStorage: IPropertyIdentifierNamesCacheStorage,
         @inject(ServiceIdentifiers.IOptions) options: IOptions
     ) {
         this.cryptUtils = cryptUtils;
+        this.globalIdentifierNamesCacheStorage = globalIdentifierNamesCacheStorage;
+        this.propertyIdentifierNamesCacheStorage = propertyIdentifierNamesCacheStorage;
         this.options = options;
     }
 
@@ -49,6 +75,20 @@ export class ObfuscatedCode implements IObfuscatedCode {
         this.sourceMap = sourceMap;
     }
 
+    /**
+     * @returns {string}
+     */
+    public getIdentifierNamesCache (): TIdentifierNamesCache {
+        if (!this.options.identifierNamesCache) {
+            return null;
+        }
+
+        return {
+            globalIdentifiers: this.globalIdentifierNamesCacheStorage.getStorageAsDictionary(),
+            propertyIdentifiers: this.propertyIdentifierNamesCacheStorage.getStorageAsDictionary()
+        };
+    }
+
     /**
      * @returns {string}
      */

+ 8 - 0
src/storages/MapStorage.ts

@@ -6,6 +6,7 @@ import { IOptions } from '../interfaces/options/IOptions';
 import { IRandomGenerator } from '../interfaces/utils/IRandomGenerator';
 
 import { initializable } from '../decorators/Initializable';
+import { TDictionary } from '../types/TDictionary';
 
 @injectable()
 export abstract class MapStorage <K, V> implements IMapStorage <K, V> {
@@ -99,6 +100,13 @@ export abstract class MapStorage <K, V> implements IMapStorage <K, V> {
         return this.storage;
     }
 
+    /**
+     * @returns {TDictionary<V>}
+     */
+    public getStorageAsDictionary (): TDictionary<V> {
+        return Object.fromEntries(this.storage);
+    }
+
     /**
      * @returns {string}
      */

+ 29 - 0
src/storages/identifier-names-cache/GlobalIdentifierNamesCacheStorage.ts

@@ -0,0 +1,29 @@
+import { inject, injectable, postConstruct } from 'inversify';
+import { ServiceIdentifiers } from '../../container/ServiceIdentifiers';
+
+import { IGlobalIdentifierNamesCacheStorage } from '../../interfaces/storages/identifier-names-cache/IGlobalIdentifierNamesCacheStorage';
+import { IOptions } from '../../interfaces/options/IOptions';
+import { IRandomGenerator } from '../../interfaces/utils/IRandomGenerator';
+
+import { MapStorage } from '../MapStorage';
+
+@injectable()
+export class GlobalIdentifierNamesCacheStorage extends MapStorage <string, string> implements IGlobalIdentifierNamesCacheStorage {
+   /**
+     * @param {IRandomGenerator} randomGenerator
+     * @param {IOptions} options
+     */
+    public constructor (
+        @inject(ServiceIdentifiers.IRandomGenerator) randomGenerator: IRandomGenerator,
+        @inject(ServiceIdentifiers.IOptions) options: IOptions
+    ) {
+        super(randomGenerator, options);
+    }
+
+    @postConstruct()
+    public override initialize (): void {
+       super.initialize();
+
+        this.storage = new Map(Object.entries(this.options.identifierNamesCache?.globalIdentifiers ?? {}));
+    }
+}

+ 29 - 0
src/storages/identifier-names-cache/PropertyIdentifierNamesCacheStorage.ts

@@ -0,0 +1,29 @@
+import { inject, injectable, postConstruct } from 'inversify';
+import { ServiceIdentifiers } from '../../container/ServiceIdentifiers';
+
+import { IOptions } from '../../interfaces/options/IOptions';
+import { IPropertyIdentifierNamesCacheStorage } from '../../interfaces/storages/identifier-names-cache/IPropertyIdentifierNamesCacheStorage';
+import { IRandomGenerator } from '../../interfaces/utils/IRandomGenerator';
+
+import { MapStorage } from '../MapStorage';
+
+@injectable()
+export class PropertyIdentifierNamesCacheStorage extends MapStorage <string, string> implements IPropertyIdentifierNamesCacheStorage {
+   /**
+     * @param {IRandomGenerator} randomGenerator
+     * @param {IOptions} options
+     */
+    public constructor (
+        @inject(ServiceIdentifiers.IRandomGenerator) randomGenerator: IRandomGenerator,
+        @inject(ServiceIdentifiers.IOptions) options: IOptions
+    ) {
+        super(randomGenerator, options);
+    }
+
+    @postConstruct()
+    public override initialize (): void {
+       super.initialize();
+
+        this.storage = new Map(Object.entries(this.options.identifierNamesCache?.propertyIdentifiers ?? {}));
+    }
+}

+ 7 - 0
src/types/TIdentifierNamesCache.ts

@@ -0,0 +1,7 @@
+import { TIdentifierNamesCacheDictionary } from './TIdentifierNamesCacheDictionary';
+
+export type TIdentifierNamesCache = {
+    globalIdentifiers?: TIdentifierNamesCacheDictionary;
+    propertyIdentifiers?: TIdentifierNamesCacheDictionary;
+} | null;
+

+ 3 - 0
src/types/TIdentifierNamesCacheDictionary.ts

@@ -0,0 +1,3 @@
+import { TDictionary } from './TDictionary';
+
+export type TIdentifierNamesCacheDictionary = TDictionary<string>;

+ 2 - 2
src/types/TObfuscationResultsObject.ts

@@ -1,3 +1,3 @@
-import { IObfuscatedCode } from '../interfaces/source-code/IObfuscatedCode';
+import { IObfuscationResult } from '../interfaces/source-code/IObfuscationResult';
 
-export type TObfuscationResultsObject <TSourceCodesObject> = {[key in keyof TSourceCodesObject]: IObfuscatedCode};
+export type TObfuscationResultsObject <TSourceCodesObject> = {[key in keyof TSourceCodesObject]: IObfuscationResult};

+ 0 - 3
src/types/container/source-code/TObfuscatedCodeFactory.ts

@@ -1,3 +0,0 @@
-import { IObfuscatedCode } from '../../../interfaces/source-code/IObfuscatedCode';
-
-export type TObfuscatedCodeFactory = (obfuscatedCode: string, sourceMap: string) => IObfuscatedCode;

+ 3 - 0
src/types/container/source-code/TObfuscationResultFactory.ts

@@ -0,0 +1,3 @@
+import { IObfuscationResult } from '../../../interfaces/source-code/IObfuscationResult';
+
+export type TObfuscationResultFactory = (obfuscatedCode: string, sourceMap: string) => IObfuscationResult;

+ 15 - 13
test/dev/dev.ts

@@ -1,31 +1,33 @@
 'use strict';
 
-import { NO_ADDITIONAL_NODES_PRESET } from '../../src/options/presets/NoCustomNodes';
-
 (function () {
     const JavaScriptObfuscator: any = require('../../index');
 
-    let obfuscatedCode: string = JavaScriptObfuscator.obfuscate(
+    let obfuscationResult = JavaScriptObfuscator.obfuscate(
         `
-            function foo(a, b, c, d) {
-              console.log(a, b, c, d)
+            function foo() {
+               global.baz = 3;
             }
             
             function bar(...args) {
-              foo(...args, 5)
+                console.log(2);
             }
-            
-            bar(...[1, 2, 3], 4)
         `,
         {
-            ...NO_ADDITIONAL_NODES_PRESET,
             compact: false,
-            controlFlowFlattening: true,
-            controlFlowFlatteningThreshold: 1,
-            identifierNamesGenerator: 'mangled'
+            identifierNamesCache: {
+                globalIdentifiers: {},
+                propertyIdentifiers: {}
+            },
+            renameGlobals: true,
+            renameProperties: true
         }
-    ).getObfuscatedCode();
+    );
+
+    let obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
+    let identifierNamesCache = obfuscationResult.getIdentifierNamesCache();
 
     console.log(obfuscatedCode);
     console.log(eval(obfuscatedCode));
+    console.log(identifierNamesCache);
 })();

+ 1 - 1
test/fixtures/compile-performance.js

@@ -33689,7 +33689,7 @@
         var _QueryWithRead = (function () {
             function _QueryWithRead(query, match) {
                 this.query = query;
-                this.read = __webpack_require__.i(__WEBPACK_IMPORTED_MODULE_2__facade_lang__["a" /* isPresent */])(query.meta.read) ? query.meta.read : match;
+                this.readFile = __webpack_require__.i(__WEBPACK_IMPORTED_MODULE_2__facade_lang__["a" /* isPresent */])(query.meta.read) ? query.meta.read : match;
             }
             return _QueryWithRead;
         }());

+ 7 - 7
test/functional-tests/custom-code-helpers/domain-lock/templates/DomainLockNodeTemplate.spec.ts

@@ -8,7 +8,7 @@ import { ServiceIdentifiers } from '../../../../../src/container/ServiceIdentifi
 
 import { ICryptUtils } from '../../../../../src/interfaces/utils/ICryptUtils';
 import { IInversifyContainerFacade } from '../../../../../src/interfaces/container/IInversifyContainerFacade';
-import { IObfuscatedCode } from '../../../../../src/interfaces/source-code/IObfuscatedCode';
+import { IObfuscationResult } from '../../../../../src/interfaces/source-code/IObfuscationResult';
 
 import { NO_ADDITIONAL_NODES_PRESET } from '../../../../../src/options/presets/NoCustomNodes';
 
@@ -631,7 +631,7 @@ describe('DomainLockTemplate', () => {
 
             beforeEach(() => {
                 const code: string = readFileAsString(__dirname + '/fixtures/prevailing-kind-of-variables-var.js');
-                const obfuscatedCodeObject: IObfuscatedCode = JavaScriptObfuscator.obfuscate(
+                const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
                     code,
                     {
                         ...NO_ADDITIONAL_NODES_PRESET,
@@ -641,7 +641,7 @@ describe('DomainLockTemplate', () => {
                     }
                 );
 
-                obfuscatedCode = obfuscatedCodeObject.getObfuscatedCode();
+                obfuscatedCode = obfuscationResult.getObfuscatedCode();
             });
 
             it('Should return correct kind of variables for domain lock code', () => {
@@ -659,7 +659,7 @@ describe('DomainLockTemplate', () => {
 
             beforeEach(() => {
                 const code: string = readFileAsString(__dirname + '/fixtures/prevailing-kind-of-variables-const.js');
-                const obfuscatedCodeObject: IObfuscatedCode = JavaScriptObfuscator.obfuscate(
+                const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
                     code,
                     {
                         ...NO_ADDITIONAL_NODES_PRESET,
@@ -669,7 +669,7 @@ describe('DomainLockTemplate', () => {
                     }
                 );
 
-                obfuscatedCode = obfuscatedCodeObject.getObfuscatedCode();
+                obfuscatedCode = obfuscationResult.getObfuscatedCode();
             });
 
             it('Should return correct kind of variables for domain lock code', () => {
@@ -687,7 +687,7 @@ describe('DomainLockTemplate', () => {
 
             beforeEach(() => {
                 const code: string = readFileAsString(__dirname + '/fixtures/prevailing-kind-of-variables-let.js');
-                const obfuscatedCodeObject: IObfuscatedCode = JavaScriptObfuscator.obfuscate(
+                const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
                     code,
                     {
                         ...NO_ADDITIONAL_NODES_PRESET,
@@ -697,7 +697,7 @@ describe('DomainLockTemplate', () => {
                     }
                 );
 
-                obfuscatedCode = obfuscatedCodeObject.getObfuscatedCode();
+                obfuscatedCode = obfuscationResult.getObfuscatedCode();
             });
 
             it('Should return correct kind of variables for domain lock code', () => {

+ 7 - 7
test/functional-tests/custom-code-helpers/string-array/templates/string-array-calls-wrapper-node-template/StringArrayCallsWrapperTemplate.spec.ts

@@ -8,7 +8,7 @@ import { ServiceIdentifiers } from '../../../../../../src/container/ServiceIdent
 
 import { ICryptUtilsStringArray } from '../../../../../../src/interfaces/utils/ICryptUtilsStringArray';
 import { IInversifyContainerFacade } from '../../../../../../src/interfaces/container/IInversifyContainerFacade';
-import { IObfuscatedCode } from '../../../../../../src/interfaces/source-code/IObfuscatedCode';
+import { IObfuscationResult } from '../../../../../../src/interfaces/source-code/IObfuscationResult';
 import { IRandomGenerator } from '../../../../../../src/interfaces/utils/IRandomGenerator';
 
 import { AtobTemplate } from '../../../../../../src/custom-code-helpers/string-array/templates/string-array-calls-wrapper/AtobTemplate';
@@ -277,7 +277,7 @@ describe('StringArrayCallsWrapperTemplate', () => {
 
             beforeEach(() => {
                 const code: string = readFileAsString(__dirname + '/fixtures/prevailing-kind-of-variables-var.js');
-                const obfuscatedCodeObject: IObfuscatedCode = JavaScriptObfuscator.obfuscate(
+                const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
                     code,
                     {
                         ...NO_ADDITIONAL_NODES_PRESET,
@@ -286,7 +286,7 @@ describe('StringArrayCallsWrapperTemplate', () => {
                     }
                 );
 
-                obfuscatedCode = obfuscatedCodeObject.getObfuscatedCode();
+                obfuscatedCode = obfuscationResult.getObfuscatedCode();
             });
 
             it('Should return correct kind of variables for string array calls wrapper code', () => {
@@ -304,7 +304,7 @@ describe('StringArrayCallsWrapperTemplate', () => {
 
             beforeEach(() => {
                 const code: string = readFileAsString(__dirname + '/fixtures/prevailing-kind-of-variables-const.js');
-                const obfuscatedCodeObject: IObfuscatedCode = JavaScriptObfuscator.obfuscate(
+                const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
                     code,
                     {
                         ...NO_ADDITIONAL_NODES_PRESET,
@@ -313,7 +313,7 @@ describe('StringArrayCallsWrapperTemplate', () => {
                     }
                 );
 
-                obfuscatedCode = obfuscatedCodeObject.getObfuscatedCode();
+                obfuscatedCode = obfuscationResult.getObfuscatedCode();
             });
 
             it('Should return correct kind of variables for string array calls wrapper code', () => {
@@ -331,7 +331,7 @@ describe('StringArrayCallsWrapperTemplate', () => {
 
             beforeEach(() => {
                 const code: string = readFileAsString(__dirname + '/fixtures/prevailing-kind-of-variables-let.js');
-                const obfuscatedCodeObject: IObfuscatedCode = JavaScriptObfuscator.obfuscate(
+                const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
                     code,
                     {
                         ...NO_ADDITIONAL_NODES_PRESET,
@@ -340,7 +340,7 @@ describe('StringArrayCallsWrapperTemplate', () => {
                     }
                 );
 
-                obfuscatedCode = obfuscatedCodeObject.getObfuscatedCode();
+                obfuscatedCode = obfuscationResult.getObfuscatedCode();
             });
 
             it('Should return correct kind of variables for string array calls wrapper code', () => {

+ 11 - 11
test/functional-tests/custom-code-helpers/string-array/templates/string-array-rotate-function-template/StringArrayRotateFunctionTemplate.spec.ts

@@ -2,7 +2,7 @@ import 'reflect-metadata';
 
 import { assert } from 'chai';
 
-import { IObfuscatedCode } from '../../../../../../src/interfaces/source-code/IObfuscatedCode';
+import { IObfuscationResult } from '../../../../../../src/interfaces/source-code/IObfuscationResult';
 
 import { NO_ADDITIONAL_NODES_PRESET } from '../../../../../../src/options/presets/NoCustomNodes';
 
@@ -19,7 +19,7 @@ describe('StringArrayRotateFunctionTemplate', () => {
 
             beforeEach(() => {
                 const code: string = readFileAsString(__dirname + '/fixtures/simple-input.js');
-                const obfuscatedCodeObject: IObfuscatedCode = JavaScriptObfuscator.obfuscate(
+                const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
                     code,
                     {
                         ...NO_ADDITIONAL_NODES_PRESET,
@@ -29,7 +29,7 @@ describe('StringArrayRotateFunctionTemplate', () => {
                     }
                 );
 
-                obfuscatedCode = obfuscatedCodeObject.getObfuscatedCode();
+                obfuscatedCode = obfuscationResult.getObfuscatedCode();
             });
 
             it('Should use computed member expression in `array.push` method', () => {
@@ -49,7 +49,7 @@ describe('StringArrayRotateFunctionTemplate', () => {
 
             beforeEach(() => {
                 const code: string = readFileAsString(__dirname + '/fixtures/simple-input.js');
-                const obfuscatedCodeObject: IObfuscatedCode = JavaScriptObfuscator.obfuscate(
+                const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
                     code,
                     {
                         ...NO_ADDITIONAL_NODES_PRESET,
@@ -59,7 +59,7 @@ describe('StringArrayRotateFunctionTemplate', () => {
                     }
                 );
 
-                obfuscatedCode = obfuscatedCodeObject.getObfuscatedCode();
+                obfuscatedCode = obfuscationResult.getObfuscatedCode();
             });
 
             it('Should use computed member expression in `array.shift` method', () => {
@@ -79,7 +79,7 @@ describe('StringArrayRotateFunctionTemplate', () => {
 
             beforeEach(() => {
                 const code: string = readFileAsString(__dirname + '/fixtures/prevailing-kind-of-variables-var.js');
-                const obfuscatedCodeObject: IObfuscatedCode = JavaScriptObfuscator.obfuscate(
+                const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
                     code,
                     {
                         ...NO_ADDITIONAL_NODES_PRESET,
@@ -89,7 +89,7 @@ describe('StringArrayRotateFunctionTemplate', () => {
                     }
                 );
 
-                obfuscatedCode = obfuscatedCodeObject.getObfuscatedCode();
+                obfuscatedCode = obfuscationResult.getObfuscatedCode();
             });
 
             it('Should return correct kind of variables for string array rotate function', () => {
@@ -107,7 +107,7 @@ describe('StringArrayRotateFunctionTemplate', () => {
 
             beforeEach(() => {
                 const code: string = readFileAsString(__dirname + '/fixtures/prevailing-kind-of-variables-const.js');
-                const obfuscatedCodeObject: IObfuscatedCode = JavaScriptObfuscator.obfuscate(
+                const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
                     code,
                     {
                         ...NO_ADDITIONAL_NODES_PRESET,
@@ -117,7 +117,7 @@ describe('StringArrayRotateFunctionTemplate', () => {
                     }
                 );
 
-                obfuscatedCode = obfuscatedCodeObject.getObfuscatedCode();
+                obfuscatedCode = obfuscationResult.getObfuscatedCode();
             });
 
             it('Should return correct kind of variables for string array rotate function', () => {
@@ -135,7 +135,7 @@ describe('StringArrayRotateFunctionTemplate', () => {
 
             beforeEach(() => {
                 const code: string = readFileAsString(__dirname + '/fixtures/prevailing-kind-of-variables-let.js');
-                const obfuscatedCodeObject: IObfuscatedCode = JavaScriptObfuscator.obfuscate(
+                const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
                     code,
                     {
                         ...NO_ADDITIONAL_NODES_PRESET,
@@ -145,7 +145,7 @@ describe('StringArrayRotateFunctionTemplate', () => {
                     }
                 );
 
-                obfuscatedCode = obfuscatedCodeObject.getObfuscatedCode();
+                obfuscatedCode = obfuscationResult.getObfuscatedCode();
             });
 
             it('Should return correct kind of variables for string array rotate function', () => {

+ 7 - 7
test/functional-tests/custom-code-helpers/string-array/templates/string-array-template/StringArrayTemplate.spec.ts

@@ -2,7 +2,7 @@ import 'reflect-metadata';
 
 import { assert } from 'chai';
 
-import { IObfuscatedCode } from '../../../../../../src/interfaces/source-code/IObfuscatedCode';
+import { IObfuscationResult } from '../../../../../../src/interfaces/source-code/IObfuscationResult';
 
 import { NO_ADDITIONAL_NODES_PRESET } from '../../../../../../src/options/presets/NoCustomNodes';
 
@@ -17,7 +17,7 @@ describe('StringArrayTemplate', () => {
 
             beforeEach(() => {
                 const code: string = readFileAsString(__dirname + '/fixtures/prevailing-kind-of-variables-var.js');
-                const obfuscatedCodeObject: IObfuscatedCode = JavaScriptObfuscator.obfuscate(
+                const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
                     code,
                     {
                         ...NO_ADDITIONAL_NODES_PRESET,
@@ -26,7 +26,7 @@ describe('StringArrayTemplate', () => {
                     }
                 );
 
-                obfuscatedCode = obfuscatedCodeObject.getObfuscatedCode();
+                obfuscatedCode = obfuscationResult.getObfuscatedCode();
             });
 
             it('Should return correct kind of variables for string array', () => {
@@ -44,7 +44,7 @@ describe('StringArrayTemplate', () => {
 
             beforeEach(() => {
                 const code: string = readFileAsString(__dirname + '/fixtures/prevailing-kind-of-variables-const.js');
-                const obfuscatedCodeObject: IObfuscatedCode = JavaScriptObfuscator.obfuscate(
+                const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
                     code,
                     {
                         ...NO_ADDITIONAL_NODES_PRESET,
@@ -53,7 +53,7 @@ describe('StringArrayTemplate', () => {
                     }
                 );
 
-                obfuscatedCode = obfuscatedCodeObject.getObfuscatedCode();
+                obfuscatedCode = obfuscationResult.getObfuscatedCode();
             });
 
             it('Should return correct kind of variables for string array', () => {
@@ -71,7 +71,7 @@ describe('StringArrayTemplate', () => {
 
             beforeEach(() => {
                 const code: string = readFileAsString(__dirname + '/fixtures/prevailing-kind-of-variables-let.js');
-                const obfuscatedCodeObject: IObfuscatedCode = JavaScriptObfuscator.obfuscate(
+                const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
                     code,
                     {
                         ...NO_ADDITIONAL_NODES_PRESET,
@@ -80,7 +80,7 @@ describe('StringArrayTemplate', () => {
                     }
                 );
 
-                obfuscatedCode = obfuscatedCodeObject.getObfuscatedCode();
+                obfuscatedCode = obfuscationResult.getObfuscatedCode();
             });
 
             it('Should return correct kind of variables for string array', () => {

+ 141 - 15
test/functional-tests/javascript-obfuscator/JavaScriptObfuscator.spec.ts

@@ -1,11 +1,12 @@
 import { assert } from 'chai';
 
 import { TDictionary } from '../../../src/types/TDictionary';
+import { TIdentifierNamesCache } from '../../../src/types/TIdentifierNamesCache';
 import { TInputOptions } from '../../../src/types/options/TInputOptions';
 import { TOptionsPreset } from '../../../src/types/options/TOptionsPreset';
 import { TTypeFromEnum } from '../../../src/types/utils/TTypeFromEnum';
 
-import { IObfuscatedCode } from '../../../src/interfaces/source-code/IObfuscatedCode';
+import { IObfuscationResult } from '../../../src/interfaces/source-code/IObfuscationResult';
 
 import { SourceMapMode } from '../../../src/enums/source-map/SourceMapMode';
 import { StringArrayEncoding } from '../../../src/enums/node-transformers/string-array-transformers/StringArrayEncoding';
@@ -32,15 +33,15 @@ describe('JavaScriptObfuscator', () => {
 
             beforeEach(() => {
                 const code: string = readFileAsString(__dirname + '/fixtures/simple-input-1.js');
-                const obfuscatedCodeObject: IObfuscatedCode = JavaScriptObfuscator.obfuscate(
+                const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
                     code,
                     {
                         ...NO_ADDITIONAL_NODES_PRESET
                     }
                 );
 
-                obfuscatedCode = obfuscatedCodeObject.getObfuscatedCode();
-                sourceMap = obfuscatedCodeObject.getSourceMap();
+                obfuscatedCode = obfuscationResult.getObfuscatedCode();
+                sourceMap = obfuscationResult.getSourceMap();
             });
 
             it('should return correct obfuscated code', () => {
@@ -111,7 +112,7 @@ describe('JavaScriptObfuscator', () => {
 
                 beforeEach(() => {
                     const code: string = readFileAsString(__dirname + '/fixtures/simple-input-1.js');
-                    const obfuscatedCodeObject: IObfuscatedCode = JavaScriptObfuscator.obfuscate(
+                    const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
                         code,
                         {
                             ...NO_ADDITIONAL_NODES_PRESET,
@@ -119,8 +120,8 @@ describe('JavaScriptObfuscator', () => {
                         }
                     );
 
-                    obfuscatedCode = obfuscatedCodeObject.getObfuscatedCode();
-                    sourceMap = JSON.parse(obfuscatedCodeObject.getSourceMap()).mappings;
+                    obfuscatedCode = obfuscationResult.getObfuscatedCode();
+                    sourceMap = JSON.parse(obfuscationResult.getSourceMap()).mappings;
                 });
 
                 it('should return correct obfuscated code', () => {
@@ -140,7 +141,7 @@ describe('JavaScriptObfuscator', () => {
 
                 beforeEach(() => {
                     const code: string = readFileAsString(__dirname + '/fixtures/simple-input-1.js');
-                    const obfuscatedCodeObject: IObfuscatedCode = JavaScriptObfuscator.obfuscate(
+                    const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
                         code,
                         {
                             ...NO_ADDITIONAL_NODES_PRESET,
@@ -149,8 +150,8 @@ describe('JavaScriptObfuscator', () => {
                         }
                     );
 
-                    obfuscatedCode = obfuscatedCodeObject.getObfuscatedCode();
-                    sourceMap = JSON.parse(obfuscatedCodeObject.getSourceMap()).mappings;
+                    obfuscatedCode = obfuscationResult.getObfuscatedCode();
+                    sourceMap = JSON.parse(obfuscationResult.getSourceMap()).mappings;
                 });
 
                 it('should return correct obfuscated code', () => {
@@ -174,16 +175,16 @@ describe('JavaScriptObfuscator', () => {
 
                 beforeEach(() => {
                     const code: string = readFileAsString(__dirname + '/fixtures/empty-input.js');
-                    const obfuscatedCodeObject: IObfuscatedCode = JavaScriptObfuscator.obfuscate(
+                    const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
                         code,
                         {
                             sourceMap: true
                         }
                     );
 
-                    obfuscatedCode = obfuscatedCodeObject.getObfuscatedCode();
+                    obfuscatedCode = obfuscationResult.getObfuscatedCode();
 
-                    const sourceMapObject: any = JSON.parse(obfuscatedCodeObject.getSourceMap());
+                    const sourceMapObject: any = JSON.parse(obfuscationResult.getSourceMap());
 
                     sourceMapNames = sourceMapObject.names;
                     sourceMapSources = sourceMapObject.sources;
@@ -886,7 +887,7 @@ describe('JavaScriptObfuscator', () => {
                     obfuscatedCode = JavaScriptObfuscator.obfuscate(code).getObfuscatedCode();
                 });
 
-                it('Match #!: should correctly obfuscate a import', () => {
+                it('Match #1: should correctly obfuscate a import', () => {
                     assert.match(obfuscatedCode, importRegExp);
                 });
 
@@ -912,6 +913,131 @@ describe('JavaScriptObfuscator', () => {
             });
         });
 
+        describe('identifier names cache generation', () => {
+            describe('Variant #1: `identifierNamesCache` and `renameGlobal` options are enabled. Existing cache is passed', () => {
+                const expectedIdentifierNamesCache: TIdentifierNamesCache = {
+                    globalIdentifiers: {
+                        foo: 'a',
+                        bar: 'b',
+                        baz: 'baz_value_from_cache'
+                    },
+                    propertyIdentifiers: {}
+                };
+
+                let identifierNamesCache: TIdentifierNamesCache;
+
+                beforeEach(() => {
+                    const code: string = readFileAsString(__dirname + '/fixtures/identifier-names-cache-1.js');
+
+                    identifierNamesCache = JavaScriptObfuscator.obfuscate(
+                        code,
+                        {
+                            ...NO_ADDITIONAL_NODES_PRESET,
+                            renameGlobals: true,
+                            identifierNamesCache: {
+                                globalIdentifiers: {
+                                    baz: 'baz_value_from_cache'
+                                },
+                                propertyIdentifiers: {}
+                            },
+                            identifierNamesGenerator: IdentifierNamesGenerator.MangledIdentifierNamesGenerator
+                        }
+                    ).getIdentifierNamesCache();
+                });
+
+                it('Match #1: should correctly generate identifier names cache', () => {
+                    assert.deepEqual(identifierNamesCache, expectedIdentifierNamesCache);
+                });
+            });
+
+            describe('Variant #2: `identifierNamesCache` and `renameGlobal` options are enabled', () => {
+                const expectedIdentifierNamesCache: TIdentifierNamesCache = {
+                    globalIdentifiers: {
+                        foo: 'a',
+                        bar: 'b'
+                    },
+                    propertyIdentifiers: {}
+                };
+
+                let identifierNamesCache: TIdentifierNamesCache;
+
+                beforeEach(() => {
+                    const code: string = readFileAsString(__dirname + '/fixtures/identifier-names-cache-1.js');
+
+                    identifierNamesCache = JavaScriptObfuscator.obfuscate(
+                        code,
+                        {
+                            ...NO_ADDITIONAL_NODES_PRESET,
+                            renameGlobals: true,
+                            identifierNamesCache: {
+                                globalIdentifiers: {},
+                                propertyIdentifiers: {}
+                            },
+                            identifierNamesGenerator: IdentifierNamesGenerator.MangledIdentifierNamesGenerator
+                        }
+                    ).getIdentifierNamesCache();
+                });
+
+                it('Match #1: should correctly generate identifier names cache', () => {
+                    assert.deepEqual(identifierNamesCache, expectedIdentifierNamesCache);
+                });
+            });
+
+            describe('Variant #3: `identifierNamesCache` and `renameGlobal` options are enabled. Source code without global variables', () => {
+                const expectedIdentifierNamesCache: TIdentifierNamesCache = {
+                    globalIdentifiers: {},
+                    propertyIdentifiers: {}
+                };
+
+                let identifierNamesCache: TIdentifierNamesCache;
+
+                beforeEach(() => {
+                    const code: string = readFileAsString(__dirname + '/fixtures/identifier-names-cache-2.js');
+
+                    identifierNamesCache = JavaScriptObfuscator.obfuscate(
+                        code,
+                        {
+                            ...NO_ADDITIONAL_NODES_PRESET,
+                            renameGlobals: true,
+                            identifierNamesCache: {
+                                globalIdentifiers: {},
+                                propertyIdentifiers: {}
+                            },
+                            identifierNamesGenerator: IdentifierNamesGenerator.MangledIdentifierNamesGenerator
+                        }
+                    ).getIdentifierNamesCache();
+                });
+
+                it('Match #1: should correctly generate identifier names cache', () => {
+                    assert.deepEqual(identifierNamesCache, expectedIdentifierNamesCache);
+                });
+            });
+
+            describe('Variant #4: `identifierNamesCache` option is disabled', () => {
+                const expectedIdentifierNamesCache: TIdentifierNamesCache = null;
+
+                let identifierNamesCache: TIdentifierNamesCache;
+
+                beforeEach(() => {
+                    const code: string = readFileAsString(__dirname + '/fixtures/identifier-names-cache-1.js');
+
+                    identifierNamesCache = JavaScriptObfuscator.obfuscate(
+                        code,
+                        {
+                            ...NO_ADDITIONAL_NODES_PRESET,
+                            renameGlobals: true,
+                            identifierNamesCache: null,
+                            identifierNamesGenerator: IdentifierNamesGenerator.MangledIdentifierNamesGenerator
+                        }
+                    ).getIdentifierNamesCache();
+                });
+
+                it('Match #1: should correctly generate identifier names cache', () => {
+                    assert.deepEqual(identifierNamesCache, expectedIdentifierNamesCache);
+                });
+            });
+        });
+
         describe('3.5k variables', function () {
             this.timeout(200000);
 
@@ -1224,7 +1350,7 @@ describe('JavaScriptObfuscator', () => {
         });
 
         describe('invalid source codes object', () => {
-            let testFunc: () => TDictionary<IObfuscatedCode>;
+            let testFunc: () => TDictionary<IObfuscationResult>;
 
             beforeEach(() => {
                 testFunc = () => JavaScriptObfuscator.obfuscateMultiple(

+ 9 - 0
test/functional-tests/javascript-obfuscator/fixtures/identifier-names-cache-1.js

@@ -0,0 +1,9 @@
+function foo(arg) {
+    console.log(arg)
+}
+
+function bar() {
+    var bark = 2;
+}
+
+baz();

+ 9 - 0
test/functional-tests/javascript-obfuscator/fixtures/identifier-names-cache-2.js

@@ -0,0 +1,9 @@
+(function () {
+    function foo(arg) {
+        console.log(arg)
+    }
+
+    function bar() {
+        var bark = 2;
+    }
+})();

+ 98 - 0
test/functional-tests/node-transformers/rename-identifiers-transformers/scope-through-identifiers-transformer/class-declaration/ClassDeclaration.spec.ts

@@ -0,0 +1,98 @@
+import { assert } from 'chai';
+
+import { NO_ADDITIONAL_NODES_PRESET } from '../../../../../../src/options/presets/NoCustomNodes';
+
+import { readFileAsString } from '../../../../../helpers/readFileAsString';
+
+import { JavaScriptObfuscator } from '../../../../../../src/JavaScriptObfuscatorFacade';
+
+describe('ScopeThroughIdentifiersTransformer ClassDeclaration identifiers', () => {
+    describe('Variant #1: class declaration is exist', () => {
+        const classNameIdentifierRegExp: RegExp = /class *(_0x[a-f0-9]{4,6}) *\{/;
+        const classCallIdentifierRegExp: RegExp = /new *(_0x[a-f0-9]{4,6}) *\( *\);/;
+
+        let obfuscatedCode: string;
+
+        before(() => {
+            const code: string = readFileAsString(__dirname + '/fixtures/class-call-with-declaration.js');
+
+            obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                code,
+                {
+                    ...NO_ADDITIONAL_NODES_PRESET,
+                    renameGlobals: true,
+                    identifierNamesCache: {
+                        globalIdentifiers: {
+                            'Foo': 'Foo_from_cache'
+                        },
+                        propertyIdentifiers: {}
+                    }
+                }
+            ).getObfuscatedCode();
+        });
+
+        it('should skip transformation of class name', () => {
+            assert.match(obfuscatedCode, classNameIdentifierRegExp);
+        });
+
+        it('should skip transformation of class name', () => {
+            assert.match(obfuscatedCode, classCallIdentifierRegExp);
+        });
+    });
+
+    describe('Variant #2: class declaration is missing', () => {
+        describe('Variant #1: transformation of class call identifier node name based on identifier names cache', () => {
+            const classCallIdentifierRegExp: RegExp = /new *Foo_from_cache *\( *\);/;
+
+            let obfuscatedCode: string;
+
+            before(() => {
+                const code: string = readFileAsString(__dirname + '/fixtures/class-call-without-declaration.js');
+
+                obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                    code,
+                    {
+                        ...NO_ADDITIONAL_NODES_PRESET,
+                        renameGlobals: true,
+                        identifierNamesCache: {
+                            globalIdentifiers: {
+                                'Foo': 'Foo_from_cache'
+                            },
+                            propertyIdentifiers: {}
+                        }
+                    }
+                ).getObfuscatedCode();
+            });
+
+            it('should transform class name', () => {
+                assert.match(obfuscatedCode, classCallIdentifierRegExp);
+            });
+        });
+
+        describe('Variant #2: ignore transformation of class call identifier node name when no identifier names cache value is available', () => {
+            const classCallIdentifierRegExp: RegExp = /new *Foo *\( *\);/;
+
+            let obfuscatedCode: string;
+
+            before(() => {
+                const code: string = readFileAsString(__dirname + '/fixtures/class-call-without-declaration.js');
+
+                obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                    code,
+                    {
+                        ...NO_ADDITIONAL_NODES_PRESET,
+                        renameGlobals: true,
+                        identifierNamesCache: {
+                            globalIdentifiers: {},
+                            propertyIdentifiers: {}
+                        }
+                    }
+                ).getObfuscatedCode();
+            });
+
+            it('should not transform class name', () => {
+                assert.match(obfuscatedCode, classCallIdentifierRegExp);
+            });
+        });
+    });
+});

+ 3 - 0
test/functional-tests/node-transformers/rename-identifiers-transformers/scope-through-identifiers-transformer/class-declaration/fixtures/class-call-with-declaration.js

@@ -0,0 +1,3 @@
+class Foo {}
+
+new Foo();

+ 1 - 0
test/functional-tests/node-transformers/rename-identifiers-transformers/scope-through-identifiers-transformer/class-declaration/fixtures/class-call-without-declaration.js

@@ -0,0 +1 @@
+new Foo();

+ 98 - 0
test/functional-tests/node-transformers/rename-identifiers-transformers/scope-through-identifiers-transformer/function-declaration/FunctionDeclaration.spec.ts

@@ -0,0 +1,98 @@
+import { assert } from 'chai';
+
+import { NO_ADDITIONAL_NODES_PRESET } from '../../../../../../src/options/presets/NoCustomNodes';
+
+import { readFileAsString } from '../../../../../helpers/readFileAsString';
+
+import { JavaScriptObfuscator } from '../../../../../../src/JavaScriptObfuscatorFacade';
+
+describe('ScopeThroughIdentifiersTransformer FunctionDeclaration identifiers', () => {
+    describe('Variant #1: function declaration is exist', () => {
+        const functionNameIdentifierRegExp: RegExp = /function *(_0x[a-f0-9]{4,6}) *\(\) *\{/;
+        const functionCallIdentifierRegExp: RegExp = /(_0x[a-f0-9]{4,6}) *\( *\);/;
+
+        let obfuscatedCode: string;
+
+        before(() => {
+            const code: string = readFileAsString(__dirname + '/fixtures/function-call-with-declaration.js');
+
+            obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                code,
+                {
+                    ...NO_ADDITIONAL_NODES_PRESET,
+                    renameGlobals: true,
+                    identifierNamesCache: {
+                        globalIdentifiers: {
+                            'foo': 'foo_from_cache'
+                        },
+                        propertyIdentifiers: {}
+                    }
+                }
+            ).getObfuscatedCode();
+        });
+
+        it('should skip transformation of function name', () => {
+            assert.match(obfuscatedCode, functionNameIdentifierRegExp);
+        });
+
+        it('should skip transformation of function name', () => {
+            assert.match(obfuscatedCode, functionCallIdentifierRegExp);
+        });
+    });
+
+    describe('Variant #2: function declaration is missing', () => {
+        describe('Variant #1: transformation of function call identifier node name based on identifier names cache', () => {
+            const functionCallIdentifierRegExp: RegExp = /foo_from_cache *\( *\);/;
+
+            let obfuscatedCode: string;
+
+            before(() => {
+                const code: string = readFileAsString(__dirname + '/fixtures/function-call-without-declaration.js');
+
+                obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                    code,
+                    {
+                        ...NO_ADDITIONAL_NODES_PRESET,
+                        renameGlobals: true,
+                        identifierNamesCache: {
+                            globalIdentifiers: {
+                                'foo': 'foo_from_cache'
+                            },
+                            propertyIdentifiers: {}
+                        }
+                    }
+                ).getObfuscatedCode();
+            });
+
+            it('should transform function name', () => {
+                assert.match(obfuscatedCode, functionCallIdentifierRegExp);
+            });
+        });
+
+        describe('Variant #2: ignore transformation of function call identifier node name when no identifier names cache value is available', () => {
+            const functionCallIdentifierRegExp: RegExp = /foo *\( *\);/;
+
+            let obfuscatedCode: string;
+
+            before(() => {
+                const code: string = readFileAsString(__dirname + '/fixtures/function-call-without-declaration.js');
+
+                obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                    code,
+                    {
+                        ...NO_ADDITIONAL_NODES_PRESET,
+                        renameGlobals: true,
+                        identifierNamesCache: {
+                            globalIdentifiers: {},
+                            propertyIdentifiers: {}
+                        }
+                    }
+                ).getObfuscatedCode();
+            });
+
+            it('should not transform function name', () => {
+                assert.match(obfuscatedCode, functionCallIdentifierRegExp);
+            });
+        });
+    });
+});

+ 3 - 0
test/functional-tests/node-transformers/rename-identifiers-transformers/scope-through-identifiers-transformer/function-declaration/fixtures/function-call-with-declaration.js

@@ -0,0 +1,3 @@
+function foo () {}
+
+foo();

+ 1 - 0
test/functional-tests/node-transformers/rename-identifiers-transformers/scope-through-identifiers-transformer/function-declaration/fixtures/function-call-without-declaration.js

@@ -0,0 +1 @@
+foo();

+ 214 - 0
test/functional-tests/node-transformers/rename-identifiers-transformers/scope-through-identifiers-transformer/variable-declaration/VariableDeclaration.spec.ts

@@ -0,0 +1,214 @@
+import { assert } from 'chai';
+
+import { NO_ADDITIONAL_NODES_PRESET } from '../../../../../../src/options/presets/NoCustomNodes';
+
+import { readFileAsString } from '../../../../../helpers/readFileAsString';
+
+import { JavaScriptObfuscator } from '../../../../../../src/JavaScriptObfuscatorFacade';
+
+describe('ScopeThroughIdentifiersTransformer VariableDeclaration identifiers', () => {
+    describe('Variant #1: variable declaration is exist', () => {
+        const variableIdentifierRegExp: RegExp = /var *(_0x[a-f0-9]{4,6}) *= *0x1/;
+        const variableReferenceIdentifierRegExp: RegExp = /(_0x[a-f0-9]{4,6});/;
+
+        let obfuscatedCode: string;
+
+        before(() => {
+            const code: string = readFileAsString(__dirname + '/fixtures/variable-reference-with-declaration.js');
+
+            obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                code,
+                {
+                    ...NO_ADDITIONAL_NODES_PRESET,
+                    renameGlobals: true,
+                    identifierNamesCache: {
+                        globalIdentifiers: {
+                            'foo': 'foo_from_cache'
+                        },
+                        propertyIdentifiers: {}
+                    }
+                }
+            ).getObfuscatedCode();
+        });
+
+        it('should skip transformation of variable declaration name', () => {
+            assert.match(obfuscatedCode, variableIdentifierRegExp);
+        });
+
+        it('should skip transformation of variable reference name', () => {
+            assert.match(obfuscatedCode, variableReferenceIdentifierRegExp);
+        });
+    });
+
+    describe('Variant #2: variable declaration is missing', () => {
+        describe('Variant #1: global variable reference scope', () => {
+            describe('Variant #1: identifier names cache value is exists', () => {
+                const variableReferenceIdentifierRegExp: RegExp = /foo_from_cache;/;
+
+                let obfuscatedCode: string;
+
+                before(() => {
+                    const code: string = readFileAsString(__dirname + '/fixtures/variable-reference-without-declaration-global-scope.js');
+
+                    obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                        code,
+                        {
+                            ...NO_ADDITIONAL_NODES_PRESET,
+                            renameGlobals: true,
+                            identifierNamesCache: {
+                                globalIdentifiers: {
+                                    'foo': 'foo_from_cache'
+                                },
+                                propertyIdentifiers: {}
+                            }
+                        }
+                    ).getObfuscatedCode();
+                });
+
+                it('should transform variable reference name', () => {
+                    assert.match(obfuscatedCode, variableReferenceIdentifierRegExp);
+                });
+            });
+
+            describe('Variant #2: ignore transformation of variable reference identifier node name when no identifier names cache value is available', () => {
+                const variableReferenceIdentifierRegExp: RegExp = /foo;/;
+
+                let obfuscatedCode: string;
+
+                before(() => {
+                    const code: string = readFileAsString(__dirname + '/fixtures/variable-reference-without-declaration-global-scope.js');
+
+                    obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                        code,
+                        {
+                            ...NO_ADDITIONAL_NODES_PRESET,
+                            renameGlobals: true,
+                            identifierNamesCache: {
+                                globalIdentifiers: {},
+                                propertyIdentifiers: {}
+                            }
+                        }
+                    ).getObfuscatedCode();
+                });
+
+                it('should not transform variable reference name', () => {
+                    assert.match(obfuscatedCode, variableReferenceIdentifierRegExp);
+                });
+            });
+
+            describe('Variant #3: ignore transformation of variable reference identifier node name when this name is reserved', () => {
+                const variableReferenceIdentifierRegExp: RegExp = /foo;/;
+
+                let obfuscatedCode: string;
+
+                before(() => {
+                    const code: string = readFileAsString(__dirname + '/fixtures/variable-reference-without-declaration-global-scope.js');
+
+                    obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                        code,
+                        {
+                            ...NO_ADDITIONAL_NODES_PRESET,
+                            renameGlobals: true,
+                            identifierNamesCache: {
+                                globalIdentifiers: {
+                                    'foo': 'foo_from_cache'
+                                },
+                                propertyIdentifiers: {}
+                            },
+                            reservedNames: ['^foo$']
+                        }
+                    ).getObfuscatedCode();
+                });
+
+                it('should not transform variable reference name', () => {
+                    assert.match(obfuscatedCode, variableReferenceIdentifierRegExp);
+                });
+            });
+        });
+
+        describe('Variant #2: local variable reference scope', () => {
+            describe('Variant #1: identifier names cache value is exists', () => {
+                const variableReferenceIdentifierRegExp: RegExp = /foo_from_cache;/;
+
+                let obfuscatedCode: string;
+
+                before(() => {
+                    const code: string = readFileAsString(__dirname + '/fixtures/variable-reference-without-declaration-local-scope.js');
+
+                    obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                        code,
+                        {
+                            ...NO_ADDITIONAL_NODES_PRESET,
+                            renameGlobals: true,
+                            identifierNamesCache: {
+                                globalIdentifiers: {
+                                    'foo': 'foo_from_cache'
+                                },
+                                propertyIdentifiers: {}
+                            }
+                        }
+                    ).getObfuscatedCode();
+                });
+
+                it('should transform variable reference name', () => {
+                    assert.match(obfuscatedCode, variableReferenceIdentifierRegExp);
+                });
+            });
+
+            describe('Variant #2: ignore transformation of variable reference identifier node name when no identifier names cache value is available', () => {
+                const variableReferenceIdentifierRegExp: RegExp = /foo;/;
+
+                let obfuscatedCode: string;
+
+                before(() => {
+                    const code: string = readFileAsString(__dirname + '/fixtures/variable-reference-without-declaration-local-scope.js');
+
+                    obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                        code,
+                        {
+                            ...NO_ADDITIONAL_NODES_PRESET,
+                            renameGlobals: true,
+                            identifierNamesCache: {
+                                globalIdentifiers: {},
+                                propertyIdentifiers: {}
+                            }
+                        }
+                    ).getObfuscatedCode();
+                });
+
+                it('should not transform variable reference name', () => {
+                    assert.match(obfuscatedCode, variableReferenceIdentifierRegExp);
+                });
+            });
+
+            describe('Variant #3 ignore transformation of variable reference identifier node name when this name is reserved', () => {
+                const variableReferenceIdentifierRegExp: RegExp = /foo;/;
+
+                let obfuscatedCode: string;
+
+                before(() => {
+                    const code: string = readFileAsString(__dirname + '/fixtures/variable-reference-without-declaration-local-scope.js');
+
+                    obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                        code,
+                        {
+                            ...NO_ADDITIONAL_NODES_PRESET,
+                            renameGlobals: true,
+                            identifierNamesCache: {
+                                globalIdentifiers: {
+                                    'foo': 'foo_from_cache'
+                                },
+                                propertyIdentifiers: {}
+                            },
+                            reservedNames: ['^foo$']
+                        }
+                    ).getObfuscatedCode();
+                });
+
+                it('should not transform variable reference name', () => {
+                    assert.match(obfuscatedCode, variableReferenceIdentifierRegExp);
+                });
+            });
+        });
+    });
+});

+ 3 - 0
test/functional-tests/node-transformers/rename-identifiers-transformers/scope-through-identifiers-transformer/variable-declaration/fixtures/variable-reference-with-declaration.js

@@ -0,0 +1,3 @@
+var foo = 1;
+
+foo;

+ 1 - 0
test/functional-tests/node-transformers/rename-identifiers-transformers/scope-through-identifiers-transformer/variable-declaration/fixtures/variable-reference-without-declaration-global-scope.js

@@ -0,0 +1 @@
+foo;

+ 5 - 0
test/functional-tests/node-transformers/rename-identifiers-transformers/scope-through-identifiers-transformer/variable-declaration/fixtures/variable-reference-without-declaration-local-scope.js

@@ -0,0 +1,5 @@
+(function () {
+    function bar() {
+        foo;
+    }
+})();

+ 117 - 0
test/functional-tests/node-transformers/rename-properties-transformers/rename-properties-transformer/RenamePropertiesTransformer.spec.ts

@@ -407,5 +407,122 @@ describe('RenamePropertiesTransformer', () => {
                 });
             });
         });
+
+        describe('Property identifier names from property identifier names cache', () => {
+            describe('Variant #1: no property identifier names in the cache', () => {
+                const property1RegExp: RegExp = /'(_0x[a-f0-9]{4,6})': *0x1/;
+                const property2RegExp: RegExp = /'(_0x[a-f0-9]{4,6})': *0x2/;
+                const property3RegExp: RegExp = /'(_0x[a-f0-9]{4,6})': *0x3/;
+
+                let obfuscatedCode: string;
+
+                before(() => {
+                    const code: string = readFileAsString(__dirname + '/fixtures/property-identifier-names-cache.js');
+
+                    obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                        code,
+                        {
+                            ...NO_ADDITIONAL_NODES_PRESET,
+                            renameProperties: true,
+                            identifierNamesCache: {
+                                globalIdentifiers: {},
+                                propertyIdentifiers: {}
+                            }
+                        }
+                    ).getObfuscatedCode();
+                });
+
+                it('Match #1: should rename property', () => {
+                    assert.match(obfuscatedCode, property1RegExp);
+                });
+
+                it('Match #2: should rename property', () => {
+                    assert.match(obfuscatedCode, property2RegExp);
+                });
+
+                it('Match #3: should rename property', () => {
+                    assert.match(obfuscatedCode, property3RegExp);
+                });
+            });
+
+            describe('Variant #2: existing property identifier names in the cache', () => {
+                const property1RegExp: RegExp = /'bar_from_cache': *0x1/;
+                const property2RegExp: RegExp = /'baz_from_cache': *0x2/;
+                const property3RegExp: RegExp = /'(_0x[a-f0-9]{4,6})': *0x3/;
+
+                let obfuscatedCode: string;
+
+                before(() => {
+                    const code: string = readFileAsString(__dirname + '/fixtures/property-identifier-names-cache.js');
+
+                    obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                        code,
+                        {
+                            ...NO_ADDITIONAL_NODES_PRESET,
+                            renameProperties: true,
+                            identifierNamesCache: {
+                                globalIdentifiers: {},
+                                propertyIdentifiers: {
+                                    bar: 'bar_from_cache',
+                                    baz: 'baz_from_cache'
+                                }
+                            }
+                        }
+                    ).getObfuscatedCode();
+                });
+
+                it('Match #1: should rename property based on the cache value', () => {
+                    assert.match(obfuscatedCode, property1RegExp);
+                });
+
+                it('Match #2: should rename property based on the cache value', () => {
+                    assert.match(obfuscatedCode, property2RegExp);
+                });
+
+                it('Match #3: should rename property', () => {
+                    assert.match(obfuscatedCode, property3RegExp);
+                });
+            });
+
+            describe('Variant #3: existing property identifier names in the cache, reserved name is defined', () => {
+                const property1RegExp: RegExp = /'bar_from_cache': *0x1/;
+                const property2RegExp: RegExp = /'baz': *0x2/;
+                const property3RegExp: RegExp = /'(_0x[a-f0-9]{4,6})': *0x3/;
+
+                let obfuscatedCode: string;
+
+                before(() => {
+                    const code: string = readFileAsString(__dirname + '/fixtures/property-identifier-names-cache.js');
+
+                    obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                        code,
+                        {
+                            ...NO_ADDITIONAL_NODES_PRESET,
+                            renameProperties: true,
+                            identifierNamesCache: {
+                                globalIdentifiers: {},
+                                propertyIdentifiers: {
+                                    bar: 'bar_from_cache',
+                                    baz: 'baz_from_cache'
+                                }
+                            },
+                            reservedNames: ['^baz$']
+                        }
+                    ).getObfuscatedCode();
+                });
+
+                it('Match #1: should rename property based on the cache value', () => {
+                    assert.match(obfuscatedCode, property1RegExp);
+                });
+
+                it('Match #2: should keep original property name', () => {
+                    assert.match(obfuscatedCode, property2RegExp);
+                });
+
+                it('Match #3: should rename property', () => {
+                    assert.match(obfuscatedCode, property3RegExp);
+                });
+            });
+        });
     });
 });

+ 5 - 0
test/functional-tests/node-transformers/rename-properties-transformers/rename-properties-transformer/fixtures/property-identifier-names-cache.js

@@ -0,0 +1,5 @@
+const foo = {
+    bar: 1,
+    baz: 2,
+    bark: 3
+};

+ 134 - 0
test/functional-tests/options/OptionsNormalizer.spec.ts

@@ -257,6 +257,140 @@ describe('OptionsNormalizer', () => {
             });
         });
 
+        describe('identifierNamesCacheRule', () => {
+            describe('Variant #1: all fields are exist with values', () => {
+                before(() => {
+                    optionsPreset = getNormalizedOptions({
+                        ...getDefaultOptions(),
+                        identifierNamesCache: {
+                            globalIdentifiers: {
+                                foo: '_0x123456'
+                            },
+                            propertyIdentifiers: {
+                                bar: '_0x654321'
+                            }
+                        }
+                    });
+
+                    expectedOptionsPreset = {
+                        ...getDefaultOptions(),
+                        identifierNamesCache: {
+                            globalIdentifiers: {
+                                foo: '_0x123456'
+                            },
+                            propertyIdentifiers: {
+                                bar: '_0x654321'
+                            }
+                        }
+                    };
+                });
+
+                it('should not normalize options preset', () => {
+                    assert.deepEqual(optionsPreset, expectedOptionsPreset);
+                });
+            });
+
+            describe('Variant #2: some fields are exist with values', () => {
+                before(() => {
+                    optionsPreset = getNormalizedOptions({
+                        ...getDefaultOptions(),
+                        identifierNamesCache: {
+                            globalIdentifiers: {
+                                foo: '_0x123456'
+                            },
+                            propertyIdentifiers: {}
+                        }
+                    });
+
+                    expectedOptionsPreset = {
+                        ...getDefaultOptions(),
+                        identifierNamesCache: {
+                            globalIdentifiers: {
+                                foo: '_0x123456'
+                            },
+                            propertyIdentifiers: {}
+                        }
+                    };
+                });
+
+                it('should not normalize options preset', () => {
+                    assert.deepEqual(optionsPreset, expectedOptionsPreset);
+                });
+            });
+
+            describe('Variant #3: all fields are exist with empty objects', () => {
+                before(() => {
+                    optionsPreset = getNormalizedOptions({
+                        ...getDefaultOptions(),
+                        identifierNamesCache: {
+                            globalIdentifiers: {},
+                            propertyIdentifiers: {}
+                        }
+                    });
+
+                    expectedOptionsPreset = {
+                        ...getDefaultOptions(),
+                        identifierNamesCache: {
+                            globalIdentifiers: {},
+                            propertyIdentifiers: {}
+                        }
+                    };
+                });
+
+                it('should not normalize options preset', () => {
+                    assert.deepEqual(optionsPreset, expectedOptionsPreset);
+                });
+            });
+
+            describe('Variant #4: some fields are missing', () => {
+                before(() => {
+                    optionsPreset = getNormalizedOptions({
+                        ...getDefaultOptions(),
+                        identifierNamesCache: {
+                            globalIdentifiers: {
+                                foo: '_0x123456'
+                            }
+                        }
+                    });
+
+                    expectedOptionsPreset = {
+                        ...getDefaultOptions(),
+                        identifierNamesCache: {
+                            globalIdentifiers: {
+                                foo: '_0x123456'
+                            },
+                            propertyIdentifiers: {}
+                        }
+                    };
+                });
+
+                it('should normalize options preset', () => {
+                    assert.deepEqual(optionsPreset, expectedOptionsPreset);
+                });
+            });
+
+            describe('Variant #5: all fields are missing', () => {
+                before(() => {
+                    optionsPreset = getNormalizedOptions({
+                        ...getDefaultOptions(),
+                        identifierNamesCache: {}
+                    });
+
+                    expectedOptionsPreset = {
+                        ...getDefaultOptions(),
+                        identifierNamesCache: {
+                            globalIdentifiers: {},
+                            propertyIdentifiers: {}
+                        }
+                    };
+                });
+
+                it('should normalize options preset', () => {
+                    assert.deepEqual(optionsPreset, expectedOptionsPreset);
+                });
+            });
+        });
+
         describe('seedRule', () => {
             describe('Variant #1: seed value is string', () => {
                 before(() => {

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

@@ -0,0 +1,258 @@
+import { assert } from 'chai';
+
+import { JavaScriptObfuscator } from '../../../../src/JavaScriptObfuscatorFacade';
+
+import { NO_ADDITIONAL_NODES_PRESET } from '../../../../src/options/presets/NoCustomNodes';
+
+describe('`identifierNamesCache` validation', () => {
+    describe('IsIdentifierNamesCache', () => {
+        describe('Variant #1: positive validation', () => {
+            describe('Variant #1: object with existing identifier names cached', () => {
+                let testFunc: () => string;
+
+                beforeEach(() => {
+                    testFunc = () => JavaScriptObfuscator.obfuscate(
+                        '',
+                        {
+                            ...NO_ADDITIONAL_NODES_PRESET,
+                            identifierNamesCache: {
+                                globalIdentifiers: {
+                                    foo: '_0x123456'
+                                },
+                                propertyIdentifiers: {
+                                    bar: '_0x654321'
+                                }
+                            }
+                        }
+                    ).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: {
+                                globalIdentifiers: {
+                                    foo: '_0x123456'
+                                },
+                                propertyIdentifiers: {}
+                            }
+                        }
+                    ).getObfuscatedCode();
+                });
+
+                it('should pass validation', () => {
+                    assert.doesNotThrow(testFunc);
+                });
+            });
+
+            describe('Variant #3: object with empty identifier names caches', () => {
+                let testFunc: () => string;
+
+                beforeEach(() => {
+                    testFunc = () => JavaScriptObfuscator.obfuscate(
+                        '',
+                        {
+                            ...NO_ADDITIONAL_NODES_PRESET,
+                            identifierNamesCache: {
+                                globalIdentifiers: {},
+                                propertyIdentifiers: {}
+                            }
+                        }
+                    ).getObfuscatedCode();
+                });
+
+                it('should pass validation', () => {
+                    assert.doesNotThrow(testFunc);
+                });
+            });
+
+            describe('Variant #4: object with some empty identifier names cache', () => {
+                let testFunc: () => string;
+
+                beforeEach(() => {
+                    testFunc = () => JavaScriptObfuscator.obfuscate(
+                        '',
+                        {
+                            ...NO_ADDITIONAL_NODES_PRESET,
+                            identifierNamesCache: {
+                                globalIdentifiers: {
+                                    foo: '_0x123456'
+                                }
+                            }
+                        }
+                    ).getObfuscatedCode();
+                });
+
+                it('should pass validation', () => {
+                    assert.doesNotThrow(testFunc);
+                });
+            });
+
+            describe('Variant #5: empty object', () => {
+                let testFunc: () => string;
+
+                beforeEach(() => {
+                    testFunc = () => JavaScriptObfuscator.obfuscate(
+                        '',
+                        {
+                            ...NO_ADDITIONAL_NODES_PRESET,
+                            identifierNamesCache: {}
+                        }
+                    ).getObfuscatedCode();
+                });
+
+                it('should pass validation', () => {
+                    assert.doesNotThrow(testFunc);
+                });
+            });
+
+            describe('Variant #5: `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 an identifier names cache object 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: cache with number values inside single cache', () => {
+                let testFunc: () => string;
+
+                beforeEach(() => {
+                    testFunc = () => JavaScriptObfuscator.obfuscate(
+                        '',
+                        {
+                            ...NO_ADDITIONAL_NODES_PRESET,
+                            identifierNamesCache: {
+                                globalIdentifiers: {
+                                    foo: <any>1,
+                                    bar: <any>2,
+                                },
+                                propertyIdentifiers: {}
+                            }
+                        }
+                    ).getObfuscatedCode();
+                });
+
+                it('should not pass validation', () => {
+                    assert.throws(testFunc, expectedError);
+                });
+            });
+
+            describe('Variant #3: cache with number values inside both caches', () => {
+                let testFunc: () => string;
+
+                beforeEach(() => {
+                    testFunc = () => JavaScriptObfuscator.obfuscate(
+                        '',
+                        {
+                            ...NO_ADDITIONAL_NODES_PRESET,
+                            identifierNamesCache: {
+                                globalIdentifiers: {
+                                    foo: <any>1,
+                                    bar: <any>2,
+                                },
+                                propertyIdentifiers: {
+                                    baz: <any>3,
+                                    bark: <any>4,
+                                }
+                            }
+                        }
+                    ).getObfuscatedCode();
+                });
+
+                it('should not pass validation', () => {
+                    assert.throws(testFunc, expectedError);
+                });
+            });
+
+            describe('Variant #4: cache with mixed values', () => {
+                let testFunc: () => string;
+
+                beforeEach(() => {
+                    testFunc = () => JavaScriptObfuscator.obfuscate(
+                        '',
+                        {
+                            ...NO_ADDITIONAL_NODES_PRESET,
+                            identifierNamesCache: {
+                                globalIdentifiers: {
+                                    foo: <any>1,
+                                    bar: '_0x1234567',
+                                },
+                                propertyIdentifiers: {
+                                    foo: '_0x123456'
+                                }
+                            }
+                        }
+                    ).getObfuscatedCode();
+                });
+
+                it('should not pass validation', () => {
+                    assert.throws(testFunc, expectedError);
+                });
+            });
+
+            describe('Variant #4: cache with nullable dictionary fields', () => {
+                let testFunc: () => string;
+
+                beforeEach(() => {
+                    testFunc = () => JavaScriptObfuscator.obfuscate(
+                        '',
+                        {
+                            ...NO_ADDITIONAL_NODES_PRESET,
+                            identifierNamesCache: {
+                                globalIdentifiers: <any>null,
+                                propertyIdentifiers: <any>null
+                            }
+                        }
+                    ).getObfuscatedCode();
+                });
+
+                it('should not pass validation', () => {
+                    assert.throws(testFunc, expectedError);
+                });
+            });
+        });
+    });
+});

+ 10 - 3
test/index.spec.ts

@@ -11,8 +11,9 @@ import './unit-tests/analyzers/string-array-storage-analyzer/StringArrayStorageA
 import './unit-tests/cli/sanitizers/ArraySanitizer.spec';
 import './unit-tests/cli/sanitizers/BooleanSanitizer.spec';
 import './unit-tests/cli/utils/CLIUtils.spec';
-import './unit-tests/cli/utils/ObfuscatedCodeWriter.spec';
-import './unit-tests/cli/utils/SourceCodeReader.spec';
+import './unit-tests/cli/utils/IdentifierNamesCacheFileUtils.spec';
+import './unit-tests/cli/utils/ObfuscatedCodeFileUtils.spec';
+import './unit-tests/cli/utils/SourceCodeFileUtils.spec';
 import './unit-tests/decorators/initializable/Initializable.spec';
 import './unit-tests/generators/identifier-names-generators/DictionarylIdentifierNamesGenerator.spec';
 import './unit-tests/generators/identifier-names-generators/HexadecimalIdentifierNamesGenerator.spec';
@@ -32,10 +33,12 @@ import './unit-tests/node/node-utils/NodeUtils.spec';
 import './unit-tests/node/numerical-expression-data-to-node-converter/NumericalExpressionDataToNodeConverter.spec';
 import './unit-tests/options/Options.spec';
 import './unit-tests/options/ValidationErrorsFormatter.spec';
-import './unit-tests/source-code/ObfuscatedCode.spec';
+import './unit-tests/source-code/ObfuscationResult.spec';
 import './unit-tests/source-code/SourceCode.spec';
 import './unit-tests/storages/ArrayStorage.spec';
 import './unit-tests/storages/MapStorage.spec';
+import './unit-tests/storages/identifier-names-cache/GlobalIdentifierNamesCacheStorage.spec';
+import './unit-tests/storages/identifier-names-cache/PropertyIdentifierNamesCacheStorage.spec';
 import './unit-tests/storages/string-array-transformers/literal-nodes-cache/LiteralNodesCacheStorage.spec';
 import './unit-tests/storages/string-array-transformers/string-array/StringArrayStorage.spec';
 import './unit-tests/storages/string-array-transformers/visited-lexical-scope-nodes-stack/VisitedLexicalScopeNodesStackStorage.spec';
@@ -118,6 +121,9 @@ import './functional-tests/node-transformers/rename-identifiers-transformers/sco
 import './functional-tests/node-transformers/rename-identifiers-transformers/scope-identifiers-transformer/function/Function.spec';
 import './functional-tests/node-transformers/rename-identifiers-transformers/scope-identifiers-transformer/import-declaration/ImportDeclaration.spec';
 import './functional-tests/node-transformers/rename-identifiers-transformers/scope-identifiers-transformer/variable-declaration/VariableDeclaration.spec';
+import './functional-tests/node-transformers/rename-identifiers-transformers/scope-through-identifiers-transformer/class-declaration/ClassDeclaration.spec';
+import './functional-tests/node-transformers/rename-identifiers-transformers/scope-through-identifiers-transformer/function-declaration/FunctionDeclaration.spec';
+import './functional-tests/node-transformers/rename-identifiers-transformers/scope-through-identifiers-transformer/variable-declaration/VariableDeclaration.spec';
 import './functional-tests/node-transformers/rename-properties-transformers/rename-properties-transformer/RenamePropertiesTransformer.spec';
 import './functional-tests/node-transformers/simplifying-transformers/block-statement-simplify-transformer/BlockStatementSimplifyTransformer.spec';
 import './functional-tests/node-transformers/simplifying-transformers/expression-statements-merge-transformer/ExpressionStatementsMergeTransformer.spec';
@@ -128,6 +134,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';
 
 /**

+ 211 - 0
test/unit-tests/cli/utils/IdentifierNamesCacheFileUtils.spec.ts

@@ -0,0 +1,211 @@
+import * as fs from 'fs';
+import * as mkdirp from 'mkdirp';
+import * as path from 'path';
+import * as rimraf from 'rimraf';
+
+import { assert } from 'chai';
+
+import { TIdentifierNamesCache } from '../../../../src/types/TIdentifierNamesCache';
+
+import { IdentifierNamesCacheFileUtils } from '../../../../src/cli/utils/IdentifierNamesCacheFileUtils';
+
+describe('IdentifierNamesCacheFileUtils', () => {
+    const expectedFilePathError: RegExp = /Given identifier names cache path must be/;
+    const expectedFileContentError: RegExp = /Identifier names cache file must contains/;
+    const expectedIdentifierNamesCache: TIdentifierNamesCache = {
+        globalIdentifiers: {
+            foo: '_0x123456'
+        },
+        propertyIdentifiers: {
+            bar: '_0x654321'
+        }
+    }
+    const fileContent: string = JSON.stringify(expectedIdentifierNamesCache);
+    const tmpDirectoryPath: string = path.join('test', 'tmp');
+
+    before(() => {
+        mkdirp.sync(tmpDirectoryPath);
+    });
+
+    describe('readFile', () => {
+        describe('Variant #1: input path is a file path', () => {
+            describe('Variant #1: `identifierNamesCachePath` is a valid cache path', () => {
+                describe('Variant #1: valid `json` as identifier names cache file content', () => {
+                    const tmpFileName: string = 'cache.json';
+                    const inputPath: string = path.join(tmpDirectoryPath, tmpFileName);
+
+                    let identifierNamesCache: TIdentifierNamesCache | null;
+
+                    before(() => {
+                        fs.writeFileSync(inputPath, fileContent);
+                        identifierNamesCache = new IdentifierNamesCacheFileUtils(inputPath).readFile();
+                    });
+
+                    it('should return valid identifier names cache', () => {
+                        assert.deepEqual(identifierNamesCache, expectedIdentifierNamesCache);
+                    });
+
+                    after(() => {
+                        fs.unlinkSync(inputPath);
+                    });
+                });
+
+                describe('Variant #2: invalid `json` as identifier names cache file content', () => {
+                    const tmpFileName: string = 'cache.json';
+                    const fileContent: string = '{globalIdentifiers: }';
+                    const inputPath: string = path.join(tmpDirectoryPath, tmpFileName);
+
+                    let testFunc: () => TIdentifierNamesCache | null;
+
+                    before(() => {
+                        fs.writeFileSync(inputPath, fileContent);
+                        testFunc = () => new IdentifierNamesCacheFileUtils(inputPath).readFile();
+                    });
+
+                    it('should throw an error', () => {
+                        assert.throws(testFunc, expectedFileContentError);
+                    });
+
+                    after(() => {
+                        fs.unlinkSync(inputPath);
+                    });
+                });
+
+                describe('Variant #3: some string as identifier names cache file content', () => {
+                    const tmpFileName: string = 'cache.json';
+                    const fileContent: string = 'cache string';
+                    const inputPath: string = path.join(tmpDirectoryPath, tmpFileName);
+
+                    let testFunc: () => TIdentifierNamesCache | null;
+
+                    before(() => {
+                        fs.writeFileSync(inputPath, fileContent);
+                        testFunc = () => new IdentifierNamesCacheFileUtils(inputPath).readFile();
+                    });
+
+                    it('should throw an error', () => {
+                        assert.throws(testFunc, expectedFileContentError);
+                    });
+
+                    after(() => {
+                        fs.unlinkSync(inputPath);
+                    });
+                });
+            });
+
+            describe('Variant #2: `identifierNamesCachePath` is a valid path with invalid extension', () => {
+                const tmpFileName: string = 'cache.js';
+                const inputPath: string = path.join(tmpDirectoryPath, tmpFileName);
+
+                let testFunc: () => void;
+
+                before(() => {
+                    fs.writeFileSync(inputPath, fileContent);
+                    testFunc = () => new IdentifierNamesCacheFileUtils(inputPath).readFile();
+                });
+
+                it('should throw an error if `identifierNamesCachePath` is not a valid path', () => {
+                    assert.throws(testFunc, expectedFilePathError);
+                });
+
+                after(() => {
+                    fs.unlinkSync(inputPath);
+                });
+            });
+
+            describe('Variant #3: `identifierNamesCachePath` is not a valid cache path', () => {
+                const tmpFileName: string = 'cache.js';
+                const inputPath: string = path.join(tmpDirectoryPath, tmpFileName);
+
+                let testFunc: () => void;
+
+                before(() => {
+                    testFunc = () => new IdentifierNamesCacheFileUtils(inputPath).readFile();
+                });
+
+                it('should throw an error if `identifierNamesCachePath` is not a valid path', () => {
+                    assert.throws(testFunc, expectedFilePathError);
+                });
+            });
+        });
+
+        describe('Variant #2: input path is a directory path', () => {
+            describe('Variant #1: `inputPath` is a valid path', () => {
+                let testFunc: () => TIdentifierNamesCache;
+
+                before(() => {
+                    testFunc = () => new IdentifierNamesCacheFileUtils(tmpDirectoryPath).readFile();
+                });
+
+                it('should throw an error if `identifierNamesCachePath` is a directory path', () => {
+                    assert.throws(testFunc, expectedFilePathError);
+                });
+            });
+        });
+    });
+
+    describe('writeFile', () => {
+        describe('Variant #1: Should write identifier names cache back to the file', () => {
+            describe('Variant #1: identifier names cache file does not exist', () => {
+                const expectedUpdatedIdentifierNamesCache: TIdentifierNamesCache = {
+                    ...expectedIdentifierNamesCache,
+                    globalIdentifiers: {
+                        ...expectedIdentifierNamesCache.globalIdentifiers,
+                        baz: '_0x987654'
+                    }
+                };
+
+                const tmpFileName: string = 'cache.json';
+                const inputPath: string = path.join(tmpDirectoryPath, tmpFileName);
+
+                let updatedIdentifierNamesCache: TIdentifierNamesCache | null;
+
+                before(() => {
+                    new IdentifierNamesCacheFileUtils(inputPath).writeFile(expectedUpdatedIdentifierNamesCache);
+                    updatedIdentifierNamesCache = JSON.parse(fs.readFileSync(inputPath, 'utf8'));
+                });
+
+                it('should correctly write updated identifier names to the cache file', () => {
+                    assert.deepEqual(updatedIdentifierNamesCache, expectedUpdatedIdentifierNamesCache);
+                });
+
+                after(() => {
+                    fs.unlinkSync(inputPath);
+                });
+            });
+
+            describe('Variant #2 identifier names cache file is exist', () => {
+                const expectedUpdatedIdentifierNamesCache: TIdentifierNamesCache = {
+                    ...expectedIdentifierNamesCache,
+                    globalIdentifiers: {
+                        ...expectedIdentifierNamesCache.globalIdentifiers,
+                        baz: '_0x987654'
+                    }
+                };
+
+                const tmpFileName: string = 'cache.json';
+                const inputPath: string = path.join(tmpDirectoryPath, tmpFileName);
+
+                let updatedIdentifierNamesCache: TIdentifierNamesCache | null;
+
+                before(() => {
+                    fs.writeFileSync(inputPath, fileContent);
+                    new IdentifierNamesCacheFileUtils(inputPath).writeFile(expectedUpdatedIdentifierNamesCache);
+                    updatedIdentifierNamesCache = JSON.parse(fs.readFileSync(inputPath, 'utf8'));
+                });
+
+                it('should correctly write updated identifier names to the cache file', () => {
+                    assert.deepEqual(updatedIdentifierNamesCache, expectedUpdatedIdentifierNamesCache);
+                });
+
+                after(() => {
+                    fs.unlinkSync(inputPath);
+                });
+            });
+        });
+    });
+
+    after(() => {
+        rimraf.sync(tmpDirectoryPath);
+    });
+});

+ 48 - 48
test/unit-tests/cli/utils/ObfuscatedCodeWriter.spec.ts → test/unit-tests/cli/utils/ObfuscatedCodeFileUtils.spec.ts

@@ -4,9 +4,9 @@ import * as mkdirp from 'mkdirp';
 import * as path from 'path';
 import * as rimraf from 'rimraf';
 
-import { ObfuscatedCodeWriter } from '../../../../src/cli/utils/ObfuscatedCodeWriter';
+import { ObfuscatedCodeFileUtils } from '../../../../src/cli/utils/ObfuscatedCodeFileUtils';
 
-describe('ObfuscatedCodeWriter', () => {
+describe('obfuscatedCodeFileUtils', () => {
     const tmpDirectoryPath: string = 'test/tmp';
 
     describe('getOutputCodePath', () => {
@@ -27,13 +27,13 @@ describe('ObfuscatedCodeWriter', () => {
             let outputCodePath: string;
 
             before(() => {
-                const obfuscatedCodeWriter: ObfuscatedCodeWriter = new ObfuscatedCodeWriter(
+                const obfuscatedCodeFileUtils: ObfuscatedCodeFileUtils = new ObfuscatedCodeFileUtils(
                     rawInputPath,
                     {
                         output: rawOutputPath
                     }
                 );
-                outputCodePath = obfuscatedCodeWriter.getOutputCodePath(inputPath);
+                outputCodePath = obfuscatedCodeFileUtils.getOutputCodePath(inputPath);
             });
 
             it('should return output path that equals to passed output file path', () => {
@@ -50,13 +50,13 @@ describe('ObfuscatedCodeWriter', () => {
             let outputCodePath: string;
 
             before(() => {
-                const obfuscatedCodeWriter: ObfuscatedCodeWriter = new ObfuscatedCodeWriter(
+                const obfuscatedCodeFileUtils: ObfuscatedCodeFileUtils = new ObfuscatedCodeFileUtils(
                     rawInputPath,
                     {
                         output: rawOutputPath
                     }
                 );
-                outputCodePath = obfuscatedCodeWriter.getOutputCodePath(inputPath);
+                outputCodePath = obfuscatedCodeFileUtils.getOutputCodePath(inputPath);
             });
 
             it('should return output path that equals to passed output directory with file name from actual file path', () => {
@@ -72,13 +72,13 @@ describe('ObfuscatedCodeWriter', () => {
             let testFunc: () => string;
 
             before(() => {
-                const obfuscatedCodeWriter: ObfuscatedCodeWriter = new ObfuscatedCodeWriter(
+                const obfuscatedCodeFileUtils: ObfuscatedCodeFileUtils = new ObfuscatedCodeFileUtils(
                     rawInputPath,
                     {
                         output: rawOutputPath
                     }
                 );
-                testFunc = () => obfuscatedCodeWriter.getOutputCodePath(inputPath);
+                testFunc = () => obfuscatedCodeFileUtils.getOutputCodePath(inputPath);
             });
 
             it('should throw an error if output path is a file path', () => {
@@ -100,13 +100,13 @@ describe('ObfuscatedCodeWriter', () => {
                 let outputCodePath: string;
 
                 before(() => {
-                    const obfuscatedCodeWriter: ObfuscatedCodeWriter = new ObfuscatedCodeWriter(
+                    const obfuscatedCodeFileUtils: ObfuscatedCodeFileUtils = new ObfuscatedCodeFileUtils(
                         rawInputPath,
                         {
                             output: rawOutputPath
                         }
                     );
-                    outputCodePath = obfuscatedCodeWriter.getOutputCodePath(inputPath);
+                    outputCodePath = obfuscatedCodeFileUtils.getOutputCodePath(inputPath);
                 });
 
                 it('should return output path that contains raw output path and actual file input path', () => {
@@ -127,13 +127,13 @@ describe('ObfuscatedCodeWriter', () => {
                 let outputCodePath: string;
 
                 before(() => {
-                    const obfuscatedCodeWriter: ObfuscatedCodeWriter = new ObfuscatedCodeWriter(
+                    const obfuscatedCodeFileUtils: ObfuscatedCodeFileUtils = new ObfuscatedCodeFileUtils(
                         rawInputPath,
                         {
                             output: rawOutputPath
                         }
                     );
-                    outputCodePath = obfuscatedCodeWriter.getOutputCodePath(inputPath);
+                    outputCodePath = obfuscatedCodeFileUtils.getOutputCodePath(inputPath);
                 });
 
                 it('should return output path that contains raw output path and actual file input path', () => {
@@ -155,13 +155,13 @@ describe('ObfuscatedCodeWriter', () => {
                 let outputCodePath: string;
 
                 before(() => {
-                    const obfuscatedCodeWriter: ObfuscatedCodeWriter = new ObfuscatedCodeWriter(
+                    const obfuscatedCodeFileUtils: ObfuscatedCodeFileUtils = new ObfuscatedCodeFileUtils(
                         rawInputPath,
                         {
                             output: rawOutputPath
                         }
                     );
-                    outputCodePath = obfuscatedCodeWriter.getOutputCodePath(inputPath);
+                    outputCodePath = obfuscatedCodeFileUtils.getOutputCodePath(inputPath);
                 });
 
                 it('should return output path that contains raw output path and actual file input path', () => {
@@ -184,13 +184,13 @@ describe('ObfuscatedCodeWriter', () => {
                 let outputCodePath: string;
 
                 before(() => {
-                    const obfuscatedCodeWriter: ObfuscatedCodeWriter = new ObfuscatedCodeWriter(
+                    const obfuscatedCodeFileUtils: ObfuscatedCodeFileUtils = new ObfuscatedCodeFileUtils(
                         rawInputPath,
                         {
                             output: rawOutputPath
                         }
                     );
-                    outputCodePath = obfuscatedCodeWriter.getOutputCodePath(inputPath);
+                    outputCodePath = obfuscatedCodeFileUtils.getOutputCodePath(inputPath);
                 });
 
                 it('should return output path that contains raw output path and actual file input path', () => {
@@ -211,13 +211,13 @@ describe('ObfuscatedCodeWriter', () => {
                 let outputCodePath: string;
 
                 before(() => {
-                    const obfuscatedCodeWriter: ObfuscatedCodeWriter = new ObfuscatedCodeWriter(
+                    const obfuscatedCodeFileUtils: ObfuscatedCodeFileUtils = new ObfuscatedCodeFileUtils(
                         rawInputPath,
                         {
                             output: rawOutputPath
                         }
                     );
-                    outputCodePath = obfuscatedCodeWriter.getOutputCodePath(inputPath);
+                    outputCodePath = obfuscatedCodeFileUtils.getOutputCodePath(inputPath);
                 });
 
                 it('should return output path that contains raw output path and actual file input path', () => {
@@ -238,13 +238,13 @@ describe('ObfuscatedCodeWriter', () => {
                 let outputCodePath: string;
 
                 before(() => {
-                    const obfuscatedCodeWriter: ObfuscatedCodeWriter = new ObfuscatedCodeWriter(
+                    const obfuscatedCodeFileUtils: ObfuscatedCodeFileUtils = new ObfuscatedCodeFileUtils(
                         rawInputPath,
                         {
                             output: rawOutputPath
                         }
                     );
-                    outputCodePath = obfuscatedCodeWriter.getOutputCodePath(inputPath);
+                    outputCodePath = obfuscatedCodeFileUtils.getOutputCodePath(inputPath);
                 });
 
                 it('should return output path that contains raw output path and actual file input path', () => {
@@ -280,13 +280,13 @@ describe('ObfuscatedCodeWriter', () => {
                     let outputCodePath: string;
 
                     before(() => {
-                        const obfuscatedCodeWriter: ObfuscatedCodeWriter = new ObfuscatedCodeWriter(
+                        const obfuscatedCodeFileUtils: ObfuscatedCodeFileUtils = new ObfuscatedCodeFileUtils(
                             rawInputPath,
                             {
                                 output: rawOutputPath
                             }
                         );
-                        outputCodePath = obfuscatedCodeWriter.getOutputCodePath(inputPath);
+                        outputCodePath = obfuscatedCodeFileUtils.getOutputCodePath(inputPath);
                     });
 
                     it('should return output path that contains raw output path and actual file input path', () => {
@@ -315,13 +315,13 @@ describe('ObfuscatedCodeWriter', () => {
             let outputSourceMapPath: string;
 
             before(() => {
-                const obfuscatedCodeWriter: ObfuscatedCodeWriter = new ObfuscatedCodeWriter(
+                const obfuscatedCodeFileUtils: ObfuscatedCodeFileUtils = new ObfuscatedCodeFileUtils(
                     rawInputPath,
                     {
                         output: rawOutputPath
                     }
                 );
-                outputSourceMapPath = obfuscatedCodeWriter.getOutputSourceMapPath(outputCodePath);
+                outputSourceMapPath = obfuscatedCodeFileUtils.getOutputSourceMapPath(outputCodePath);
             });
 
             it('should return output path for source map', () => {
@@ -337,13 +337,13 @@ describe('ObfuscatedCodeWriter', () => {
             let testFunc: () => string;
 
             before(() => {
-                const obfuscatedCodeWriter: ObfuscatedCodeWriter = new ObfuscatedCodeWriter(
+                const obfuscatedCodeFileUtils: ObfuscatedCodeFileUtils = new ObfuscatedCodeFileUtils(
                     rawInputPath,
                     {
                         output: rawOutputPath
                     }
                 );
-                testFunc = () => obfuscatedCodeWriter.getOutputSourceMapPath(outputCodePath);
+                testFunc = () => obfuscatedCodeFileUtils.getOutputSourceMapPath(outputCodePath);
             });
 
             it('should throw an error if output code path is a directory path and source map file name is not set', () => {
@@ -360,13 +360,13 @@ describe('ObfuscatedCodeWriter', () => {
             let outputSourceMapPath: string;
 
             before(() => {
-                const obfuscatedCodeWriter: ObfuscatedCodeWriter = new ObfuscatedCodeWriter(
+                const obfuscatedCodeFileUtils: ObfuscatedCodeFileUtils = new ObfuscatedCodeFileUtils(
                     rawInputPath,
                     {
                         output: rawOutputPath
                     }
                 );
-                outputSourceMapPath = obfuscatedCodeWriter.getOutputSourceMapPath(outputCodePath);
+                outputSourceMapPath = obfuscatedCodeFileUtils.getOutputSourceMapPath(outputCodePath);
             });
 
             it('should return output path for source map', () => {
@@ -384,13 +384,13 @@ describe('ObfuscatedCodeWriter', () => {
             let outputSourceMapPath: string;
 
             before(() => {
-                const obfuscatedCodeWriter: ObfuscatedCodeWriter = new ObfuscatedCodeWriter(
+                const obfuscatedCodeFileUtils: ObfuscatedCodeFileUtils = new ObfuscatedCodeFileUtils(
                     rawInputPath,
                     {
                         output: rawOutputPath
                     }
                 );
-                outputSourceMapPath = obfuscatedCodeWriter.getOutputSourceMapPath(outputCodePath, sourceMapFileName);
+                outputSourceMapPath = obfuscatedCodeFileUtils.getOutputSourceMapPath(outputCodePath, sourceMapFileName);
             });
 
             it('should return output path for source map', () => {
@@ -408,13 +408,13 @@ describe('ObfuscatedCodeWriter', () => {
             let outputSourceMapPath: string;
 
             before(() => {
-                const obfuscatedCodeWriter: ObfuscatedCodeWriter = new ObfuscatedCodeWriter(
+                const obfuscatedCodeFileUtils: ObfuscatedCodeFileUtils = new ObfuscatedCodeFileUtils(
                     rawInputPath,
                     {
                         output: rawOutputPath
                     }
                 );
-                outputSourceMapPath = obfuscatedCodeWriter.getOutputSourceMapPath(outputCodePath, sourceMapFileName);
+                outputSourceMapPath = obfuscatedCodeFileUtils.getOutputSourceMapPath(outputCodePath, sourceMapFileName);
             });
 
             it('should return output path for source map', () => {
@@ -432,13 +432,13 @@ describe('ObfuscatedCodeWriter', () => {
             let outputSourceMapPath: string;
 
             before(() => {
-                const obfuscatedCodeWriter: ObfuscatedCodeWriter = new ObfuscatedCodeWriter(
+                const obfuscatedCodeFileUtils: ObfuscatedCodeFileUtils = new ObfuscatedCodeFileUtils(
                     rawInputPath,
                     {
                         output: rawOutputPath
                     }
                 );
-                outputSourceMapPath = obfuscatedCodeWriter.getOutputSourceMapPath(outputCodePath, sourceMapFileName);
+                outputSourceMapPath = obfuscatedCodeFileUtils.getOutputSourceMapPath(outputCodePath, sourceMapFileName);
             });
 
             it('should return output path for source map', () => {
@@ -456,13 +456,13 @@ describe('ObfuscatedCodeWriter', () => {
             let outputSourceMapPath: string;
 
             before(() => {
-                const obfuscatedCodeWriter: ObfuscatedCodeWriter = new ObfuscatedCodeWriter(
+                const obfuscatedCodeFileUtils: ObfuscatedCodeFileUtils = new ObfuscatedCodeFileUtils(
                     rawInputPath,
                     {
                         output: rawOutputPath
                     }
                 );
-                outputSourceMapPath = obfuscatedCodeWriter.getOutputSourceMapPath(outputCodePath, sourceMapFileName);
+                outputSourceMapPath = obfuscatedCodeFileUtils.getOutputSourceMapPath(outputCodePath, sourceMapFileName);
             });
 
             it('should return output path for source map', () => {
@@ -480,13 +480,13 @@ describe('ObfuscatedCodeWriter', () => {
             let outputSourceMapPath: string;
 
             before(() => {
-                const obfuscatedCodeWriter: ObfuscatedCodeWriter = new ObfuscatedCodeWriter(
+                const obfuscatedCodeFileUtils: ObfuscatedCodeFileUtils = new ObfuscatedCodeFileUtils(
                     rawInputPath,
                     {
                         output: rawOutputPath
                     }
                 );
-                outputSourceMapPath = obfuscatedCodeWriter.getOutputSourceMapPath(outputCodePath, sourceMapFileName);
+                outputSourceMapPath = obfuscatedCodeFileUtils.getOutputSourceMapPath(outputCodePath, sourceMapFileName);
             });
 
             it('should return output path for source map', () => {
@@ -505,13 +505,13 @@ describe('ObfuscatedCodeWriter', () => {
                 let outputSourceMapPath: string;
 
                 before(() => {
-                    const obfuscatedCodeWriter: ObfuscatedCodeWriter = new ObfuscatedCodeWriter(
+                    const obfuscatedCodeFileUtils: ObfuscatedCodeFileUtils = new ObfuscatedCodeFileUtils(
                         rawInputPath,
                         {
                             output: rawOutputPath
                         }
                     );
-                    outputSourceMapPath = obfuscatedCodeWriter.getOutputSourceMapPath(outputCodePath, sourceMapFileName);
+                    outputSourceMapPath = obfuscatedCodeFileUtils.getOutputSourceMapPath(outputCodePath, sourceMapFileName);
                 });
 
                 it('should return output path for source map', () => {
@@ -529,13 +529,13 @@ describe('ObfuscatedCodeWriter', () => {
                 let outputSourceMapPath: string;
 
                 before(() => {
-                    const obfuscatedCodeWriter: ObfuscatedCodeWriter = new ObfuscatedCodeWriter(
+                    const obfuscatedCodeFileUtils: ObfuscatedCodeFileUtils = new ObfuscatedCodeFileUtils(
                         rawInputPath,
                         {
                             output: rawOutputPath
                         }
                     );
-                    outputSourceMapPath = obfuscatedCodeWriter.getOutputSourceMapPath(outputCodePath, sourceMapFileName);
+                    outputSourceMapPath = obfuscatedCodeFileUtils.getOutputSourceMapPath(outputCodePath, sourceMapFileName);
                 });
 
                 it('should return output path for source map', () => {
@@ -553,13 +553,13 @@ describe('ObfuscatedCodeWriter', () => {
                 let outputSourceMapPath: string;
 
                 before(() => {
-                    const obfuscatedCodeWriter: ObfuscatedCodeWriter = new ObfuscatedCodeWriter(
+                    const obfuscatedCodeFileUtils: ObfuscatedCodeFileUtils = new ObfuscatedCodeFileUtils(
                         rawInputPath,
                         {
                             output: rawOutputPath
                         }
                     );
-                    outputSourceMapPath = obfuscatedCodeWriter.getOutputSourceMapPath(outputCodePath, sourceMapFileName);
+                    outputSourceMapPath = obfuscatedCodeFileUtils.getOutputSourceMapPath(outputCodePath, sourceMapFileName);
                 });
 
                 it('should return output path for source map', () => {
@@ -577,13 +577,13 @@ describe('ObfuscatedCodeWriter', () => {
                 let outputSourceMapPath: string;
 
                 before(() => {
-                    const obfuscatedCodeWriter: ObfuscatedCodeWriter = new ObfuscatedCodeWriter(
+                    const obfuscatedCodeFileUtils: ObfuscatedCodeFileUtils = new ObfuscatedCodeFileUtils(
                         rawInputPath,
                         {
                             output: rawOutputPath
                         }
                     );
-                    outputSourceMapPath = obfuscatedCodeWriter.getOutputSourceMapPath(outputCodePath, sourceMapFileName);
+                    outputSourceMapPath = obfuscatedCodeFileUtils.getOutputSourceMapPath(outputCodePath, sourceMapFileName);
                 });
 
                 it('should return output path for source map', () => {
@@ -599,13 +599,13 @@ describe('ObfuscatedCodeWriter', () => {
             let testFunc: () => string;
 
             before(() => {
-                const obfuscatedCodeWriter: ObfuscatedCodeWriter = new ObfuscatedCodeWriter(
+                const obfuscatedCodeFileUtils: ObfuscatedCodeFileUtils = new ObfuscatedCodeFileUtils(
                     rawInputPath,
                     {
                         output: rawOutputPath
                     }
                 );
-                testFunc = () => obfuscatedCodeWriter.getOutputSourceMapPath('', '');
+                testFunc = () => obfuscatedCodeFileUtils.getOutputSourceMapPath('', '');
             });
 
             it('should throw an error if output code path is empty', () => {

+ 18 - 18
test/unit-tests/cli/utils/SourceCodeReader.spec.ts → test/unit-tests/cli/utils/SourceCodeFileUtils.spec.ts

@@ -7,9 +7,9 @@ import { assert } from 'chai';
 
 import { IFileData } from '../../../../src/interfaces/cli/IFileData';
 
-import { SourceCodeReader } from '../../../../src/cli/utils/SourceCodeReader';
+import { SourceCodeFileUtils } from '../../../../src/cli/utils/SourceCodeFileUtils';
 
-describe('SourceCodeReader', () => {
+describe('SourceCodeFileUtils', () => {
     const expectedError: RegExp = /Given input path must be a valid/;
     const fileContent: string = 'test';
     const tmpDirectoryPath: string = path.join('test', 'tmp');
@@ -32,7 +32,7 @@ describe('SourceCodeReader', () => {
 
                 before(() => {
                     fs.writeFileSync(inputPath, fileContent);
-                    filesData = new SourceCodeReader(inputPath, {}).readSourceCode();
+                    filesData = new SourceCodeFileUtils(inputPath, {}).readSourceCode();
                 });
 
                 it('should return valid files data', () => {
@@ -51,7 +51,7 @@ describe('SourceCodeReader', () => {
                 let testFunc: () => void;
 
                 before(() => {
-                    testFunc = () => new SourceCodeReader(inputPath, {}).readSourceCode();
+                    testFunc = () => new SourceCodeFileUtils(inputPath, {}).readSourceCode();
                 });
 
                 it('should throw an error if `inputPath` is not a valid path', () => {
@@ -67,7 +67,7 @@ describe('SourceCodeReader', () => {
 
                 before(() => {
                     fs.writeFileSync(inputPath, fileContent);
-                    testFunc = () => new SourceCodeReader(inputPath, {}).readSourceCode();
+                    testFunc = () => new SourceCodeFileUtils(inputPath, {}).readSourceCode();
                 });
 
                 it('should throw an error if `inputPath` has invalid extension', () => {
@@ -92,7 +92,7 @@ describe('SourceCodeReader', () => {
 
                     before(() => {
                         fs.writeFileSync(inputPath, fileContent);
-                        filesData = new SourceCodeReader(
+                        filesData = new SourceCodeFileUtils(
                             inputPath,
                             {
                                 exclude: [path.join('**', 'foo.js')]
@@ -118,7 +118,7 @@ describe('SourceCodeReader', () => {
 
                         before(() => {
                             fs.writeFileSync(inputPath, fileContent);
-                            testFunc = () => new SourceCodeReader(
+                            testFunc = () => new SourceCodeFileUtils(
                                 inputPath,
                                 {
                                     exclude: [path.join('**', tmpFileName)]
@@ -143,7 +143,7 @@ describe('SourceCodeReader', () => {
 
                         before(() => {
                             fs.writeFileSync(inputPath, fileContent);
-                            testFunc = () => new SourceCodeReader(
+                            testFunc = () => new SourceCodeFileUtils(
                                 inputPath,
                                 {
                                     exclude: [tmpFileName]
@@ -168,7 +168,7 @@ describe('SourceCodeReader', () => {
 
                         before(() => {
                             fs.writeFileSync(inputPath, fileContent);
-                            testFunc = () => new SourceCodeReader(
+                            testFunc = () => new SourceCodeFileUtils(
                                 inputPath,
                                 {
                                     exclude: [inputPath]
@@ -217,7 +217,7 @@ describe('SourceCodeReader', () => {
                     fs.writeFileSync(filePath2, fileContent);
                     fs.writeFileSync(filePath3, fileContent);
                     fs.writeFileSync(filePath4, fileContent);
-                    result = new SourceCodeReader(tmpDirectoryPath, {}).readSourceCode();
+                    result = new SourceCodeFileUtils(tmpDirectoryPath, {}).readSourceCode();
                 });
 
                 it('should return files data', () => {
@@ -238,7 +238,7 @@ describe('SourceCodeReader', () => {
                 let testFunc: () => void;
 
                 before(() => {
-                    testFunc = () => new SourceCodeReader(inputPath, {}).readSourceCode();
+                    testFunc = () => new SourceCodeFileUtils(inputPath, {}).readSourceCode();
                 });
 
                 it('should throw an error if `inputPath` is not a valid path', () => {
@@ -288,7 +288,7 @@ describe('SourceCodeReader', () => {
                     fs.writeFileSync(filePath2, fileContent);
                     fs.writeFileSync(filePath3, fileContent);
                     fs.writeFileSync(filePath4, fileContent);
-                    result = new SourceCodeReader(tmpDirectoryPath, {}).readSourceCode();
+                    result = new SourceCodeFileUtils(tmpDirectoryPath, {}).readSourceCode();
                 });
 
                 it('should return files data', () => {
@@ -334,7 +334,7 @@ describe('SourceCodeReader', () => {
                         fs.writeFileSync(filePath2, fileContent);
                         fs.writeFileSync(filePath3, fileContent);
                         fs.writeFileSync(filePath4, fileContent);
-                        result = new SourceCodeReader(
+                        result = new SourceCodeFileUtils(
                             tmpDirectoryPath,
                             {
                                 exclude: ['**/hawk.js']
@@ -383,7 +383,7 @@ describe('SourceCodeReader', () => {
                             fs.writeFileSync(filePath2, fileContent);
                             fs.writeFileSync(filePath3, fileContent);
                             fs.writeFileSync(filePath4, fileContent);
-                            result = new SourceCodeReader(
+                            result = new SourceCodeFileUtils(
                                 tmpDirectoryPath,
                                 {
                                     exclude: [
@@ -434,7 +434,7 @@ describe('SourceCodeReader', () => {
                             fs.writeFileSync(filePath2, fileContent);
                             fs.writeFileSync(filePath3, fileContent);
                             fs.writeFileSync(filePath4, fileContent);
-                            result = new SourceCodeReader(
+                            result = new SourceCodeFileUtils(
                                 tmpDirectoryPath,
                                 {
                                     exclude: [
@@ -485,7 +485,7 @@ describe('SourceCodeReader', () => {
                             fs.writeFileSync(filePath2, fileContent);
                             fs.writeFileSync(filePath3, fileContent);
                             fs.writeFileSync(filePath4, fileContent);
-                            result = new SourceCodeReader(
+                            result = new SourceCodeFileUtils(
                                 tmpDirectoryPath,
                                 {
                                     exclude: [
@@ -525,7 +525,7 @@ describe('SourceCodeReader', () => {
                             fs.writeFileSync(filePath2, fileContent);
                             fs.writeFileSync(filePath3, fileContent);
                             fs.writeFileSync(filePath4, fileContent);
-                            testFunc = () => new SourceCodeReader(
+                            testFunc = () => new SourceCodeFileUtils(
                                 tmpDirectoryPath,
                                 {
                                     exclude: [tmpDirectoryPath]
@@ -564,7 +564,7 @@ describe('SourceCodeReader', () => {
                 before(() => {
                     mkdirp.sync(tmpDirectoryWithDotPath);
                     fs.writeFileSync(filePath, fileContent);
-                    result = new SourceCodeReader(tmpDirectoryWithDotPath, {}).readSourceCode();
+                    result = new SourceCodeFileUtils(tmpDirectoryWithDotPath, {}).readSourceCode();
                 });
 
                 it('should return files data', () => {

+ 7 - 7
test/unit-tests/javascript-obfuscator/JavaScriptObfuscator.spec.ts

@@ -4,7 +4,7 @@ import { assert } from 'chai';
 
 import { IInversifyContainerFacade } from '../../../src/interfaces/container/IInversifyContainerFacade';
 import { IJavaScriptObfuscator } from '../../../src/interfaces/IJavaScriptObfsucator';
-import { IObfuscatedCode } from '../../../src/interfaces/source-code/IObfuscatedCode';
+import { IObfuscationResult } from '../../../src/interfaces/source-code/IObfuscationResult';
 
 import { NO_ADDITIONAL_NODES_PRESET } from '../../../src/options/presets/NoCustomNodes';
 
@@ -40,10 +40,10 @@ describe('JavaScriptObfuscator', () => {
                         .get<IJavaScriptObfuscator>(ServiceIdentifiers.IJavaScriptObfuscator);
 
 
-                    const obfuscatedCodeObject: IObfuscatedCode = javaScriptObfuscator.obfuscate(code);
+                    const obfuscationResult: IObfuscationResult = javaScriptObfuscator.obfuscate(code);
 
-                    obfuscatedCode = obfuscatedCodeObject.getObfuscatedCode();
-                    sourceMapObject = JSON.parse(obfuscatedCodeObject.getSourceMap());
+                    obfuscatedCode = obfuscationResult.getObfuscatedCode();
+                    sourceMapObject = JSON.parse(obfuscationResult.getSourceMap());
                 });
 
                 it('should link obfuscated code with source map', () => {
@@ -79,10 +79,10 @@ describe('JavaScriptObfuscator', () => {
                         .get<IJavaScriptObfuscator>(ServiceIdentifiers.IJavaScriptObfuscator);
 
 
-                    const obfuscatedCodeObject: IObfuscatedCode = javaScriptObfuscator.obfuscate(code);
+                    const obfuscationResult: IObfuscationResult = javaScriptObfuscator.obfuscate(code);
 
-                    obfuscatedCode = obfuscatedCodeObject.getObfuscatedCode();
-                    sourceMapObject = JSON.parse(obfuscatedCodeObject.getSourceMap());
+                    obfuscatedCode = obfuscationResult.getObfuscatedCode();
+                    sourceMapObject = JSON.parse(obfuscationResult.getSourceMap());
                 });
 
                 it('should properly add base url to source map import inside obfuscated code', () => {

+ 15 - 15
test/unit-tests/source-code/ObfuscatedCode.spec.ts → test/unit-tests/source-code/ObfuscationResult.spec.ts

@@ -7,7 +7,7 @@ import { assert } from 'chai';
 import { TTypeFromEnum } from '../../../src/types/utils/TTypeFromEnum';
 
 import { IInversifyContainerFacade } from '../../../src/interfaces/container/IInversifyContainerFacade';
-import { IObfuscatedCode } from '../../../src/interfaces/source-code/IObfuscatedCode';
+import { IObfuscationResult } from '../../../src/interfaces/source-code/IObfuscationResult';
 
 import { SourceMapMode } from '../../../src/enums/source-map/SourceMapMode';
 
@@ -20,13 +20,13 @@ import { InversifyContainerFacade } from '../../../src/container/InversifyContai
  * @param sourceMapFileName
  * @param sourceMapMode
  */
-function getObfuscatedCode (
+function getObfuscationResult (
     rawObfuscatedCode: string,
     sourceMap: string,
     sourceMapBaseUrl: string,
     sourceMapFileName: string,
     sourceMapMode: TTypeFromEnum<typeof SourceMapMode>
-): IObfuscatedCode {
+): IObfuscationResult {
     const inversifyContainerFacade: IInversifyContainerFacade = new InversifyContainerFacade();
 
     inversifyContainerFacade.load(
@@ -40,12 +40,12 @@ function getObfuscatedCode (
         }
     );
 
-    const obfuscatedCode: IObfuscatedCode = inversifyContainerFacade
-        .get<IObfuscatedCode>(ServiceIdentifiers.IObfuscatedCode);
+    const obfuscationResult: IObfuscationResult = inversifyContainerFacade
+        .get<IObfuscationResult>(ServiceIdentifiers.IObfuscationResult);
 
-    obfuscatedCode.initialize(rawObfuscatedCode, sourceMap);
+    obfuscationResult.initialize(rawObfuscatedCode, sourceMap);
 
-    return obfuscatedCode;
+    return obfuscationResult;
 }
 
 describe('ObfuscatedCode', () => {
@@ -53,10 +53,10 @@ describe('ObfuscatedCode', () => {
     const sourceMap: string = 'test';
 
     describe('constructor', () => {
-        let obfuscatedCode: IObfuscatedCode;
+        let obfuscationResult: IObfuscationResult;
 
         before(() => {
-            obfuscatedCode = getObfuscatedCode(
+            obfuscationResult = getObfuscationResult(
                 expectedObfuscatedCode,
                 sourceMap,
                 '',
@@ -65,8 +65,8 @@ describe('ObfuscatedCode', () => {
             );
         });
 
-        it('should return obfuscated code if `.toString()` was called on `ObfuscatedCode` object', () => {
-            assert.equal(obfuscatedCode.toString(), expectedObfuscatedCode);
+        it('should return obfuscation result if `.toString()` was called on `ObfuscationResult` object', () => {
+            assert.equal(obfuscationResult.toString(), expectedObfuscatedCode);
         });
     });
 
@@ -75,7 +75,7 @@ describe('ObfuscatedCode', () => {
 
         describe('source map doest\'t exist', () => {
             before(() => {
-                obfuscatedCode = getObfuscatedCode(
+                obfuscatedCode = getObfuscationResult(
                     expectedObfuscatedCode,
                     '',
                     '',
@@ -93,7 +93,7 @@ describe('ObfuscatedCode', () => {
             const regExp: RegExp = /data:application\/json;base64,dGVzdA==/;
 
             before(() => {
-                obfuscatedCode = getObfuscatedCode(
+                obfuscatedCode = getObfuscationResult(
                     expectedObfuscatedCode,
                     sourceMap,
                     '',
@@ -111,7 +111,7 @@ describe('ObfuscatedCode', () => {
             const regExp: RegExp = /sourceMappingURL=http:\/\/example\.com\/output\.js\.map/;
 
             before(() => {
-                obfuscatedCode = getObfuscatedCode(
+                obfuscatedCode = getObfuscationResult(
                     expectedObfuscatedCode,
                     sourceMap,
                     'http://example.com',
@@ -127,7 +127,7 @@ describe('ObfuscatedCode', () => {
 
         describe('source map mode is `separate`, `sourceMapUrl` is not set', () => {
             before(() => {
-                obfuscatedCode = getObfuscatedCode(
+                obfuscatedCode = getObfuscationResult(
                     expectedObfuscatedCode,
                     sourceMap,
                     '',

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

@@ -4,6 +4,8 @@ import { assert } from 'chai';
 
 import { ServiceIdentifiers } from '../../../src/container/ServiceIdentifiers';
 
+import { TDictionary } from '../../../src/types/TDictionary';
+
 import { IInversifyContainerFacade } from '../../../src/interfaces/container/IInversifyContainerFacade';
 import { IMapStorage } from '../../../src/interfaces/storages/IMapStorage';
 import { IOptions } from '../../../src/interfaces/options/IOptions';
@@ -212,6 +214,25 @@ describe('MapStorage', () => {
         });
     });
 
+    describe('getStorageAsDictionary', () => {
+        const expectedDictionary: TDictionary<string> = {
+            [storageKey]: storageValue
+        };
+
+        let storageAsDictionary: TDictionary<string>;
+
+        before(() => {
+            storage = getStorageInstance<string>();
+            storage.set(storageKey, storageValue);
+
+            storageAsDictionary = storage.getStorageAsDictionary();
+        });
+
+        it('should return storage as dictionary', () => {
+            assert.deepEqual(storageAsDictionary, expectedDictionary);
+        });
+    });
+
     describe('has', () => {
         describe('Variant #1: item is presenting in storage', () => {
             const expectedItemExistence: boolean = true;

+ 110 - 0
test/unit-tests/storages/identifier-names-cache/GlobalIdentifierNamesCacheStorage.spec.ts

@@ -0,0 +1,110 @@
+import 'reflect-metadata';
+
+import { assert } from 'chai';
+
+import { ServiceIdentifiers } from '../../../../src/container/ServiceIdentifiers';
+
+import { TDictionary } from '../../../../src/types/TDictionary';
+
+import { IGlobalIdentifierNamesCacheStorage } from '../../../../src/interfaces/storages/identifier-names-cache/IGlobalIdentifierNamesCacheStorage';
+import { IInversifyContainerFacade } from '../../../../src/interfaces/container/IInversifyContainerFacade';
+import { IOptions } from '../../../../src/interfaces/options/IOptions';
+import { IRandomGenerator } from '../../../../src/interfaces/utils/IRandomGenerator';
+
+import { DEFAULT_PRESET } from '../../../../src/options/presets/Default';
+
+import { GlobalIdentifierNamesCacheStorage } from '../../../../src/storages/identifier-names-cache/GlobalIdentifierNamesCacheStorage';
+import { InversifyContainerFacade } from '../../../../src/container/InversifyContainerFacade';
+
+/**
+ * @returns {IGlobalIdentifierNamesCacheStorage}
+ */
+const getStorageInstance = <V>(options: Partial<IOptions> = DEFAULT_PRESET): IGlobalIdentifierNamesCacheStorage => {
+    const inversifyContainerFacade: IInversifyContainerFacade = new InversifyContainerFacade();
+    inversifyContainerFacade.load('', '', {});
+
+    const storage: IGlobalIdentifierNamesCacheStorage = new GlobalIdentifierNamesCacheStorage (
+        inversifyContainerFacade.get<IRandomGenerator>(ServiceIdentifiers.IRandomGenerator),
+        {
+            ...DEFAULT_PRESET,
+            ...options as IOptions
+        }
+    );
+
+    storage.initialize();
+
+    return storage;
+};
+
+describe('GlobalIdentifierNamesCacheStorage', () => {
+    const storageKey: string = 'foo';
+    const storageValue: string = 'bar';
+
+    let storage: IGlobalIdentifierNamesCacheStorage;
+
+    describe('initialize', () => {
+        describe('Variant #1: `identifierNamesCache` option values is object', () => {
+            const expectedDictionary: TDictionary<string> = {
+                [storageKey]: storageValue
+            };
+
+            let dictionary: TDictionary<string>;
+
+            before(() => {
+                storage = getStorageInstance({
+                    identifierNamesCache: {
+                        globalIdentifiers: {
+                            [storageKey]: storageValue
+                        },
+                        propertyIdentifiers: {}
+                    }
+                });
+
+                dictionary = storage.getStorageAsDictionary();
+            });
+
+            it('should initialize storage with `identifierNamesStorage` option object', () => {
+                assert.deepEqual(dictionary, expectedDictionary);
+            });
+        });
+
+        describe('Variant #2: `identifierNamesCache` option values is empty object', () => {
+            const expectedDictionary: TDictionary<string> = {};
+
+            let dictionary: TDictionary<string>;
+
+            before(() => {
+                storage = getStorageInstance({
+                    identifierNamesCache: {
+                        globalIdentifiers: {},
+                        propertyIdentifiers: {}
+                    }
+                });
+
+                dictionary = storage.getStorageAsDictionary();
+            });
+
+            it('should initialize storage with `identifierNamesStorage` option object', () => {
+                assert.deepEqual(dictionary, expectedDictionary);
+            });
+        });
+
+        describe('Variant #3: `identifierNamesCache` option values is `null`', () => {
+            const expectedDictionary: TDictionary<string> = {};
+
+            let dictionary: TDictionary<string>;
+
+            before(() => {
+                storage = getStorageInstance({
+                    identifierNamesCache: null
+                });
+
+                dictionary = storage.getStorageAsDictionary();
+            });
+
+            it('should initialize storage with `identifierNamesStorage` option object', () => {
+                assert.deepEqual(dictionary, expectedDictionary);
+            });
+        });
+    });
+});

+ 110 - 0
test/unit-tests/storages/identifier-names-cache/PropertyIdentifierNamesCacheStorage.spec.ts

@@ -0,0 +1,110 @@
+import 'reflect-metadata';
+
+import { assert } from 'chai';
+
+import { ServiceIdentifiers } from '../../../../src/container/ServiceIdentifiers';
+
+import { TDictionary } from '../../../../src/types/TDictionary';
+
+import { IPropertyIdentifierNamesCacheStorage } from '../../../../src/interfaces/storages/identifier-names-cache/IPropertyIdentifierNamesCacheStorage';
+import { IInversifyContainerFacade } from '../../../../src/interfaces/container/IInversifyContainerFacade';
+import { IOptions } from '../../../../src/interfaces/options/IOptions';
+import { IRandomGenerator } from '../../../../src/interfaces/utils/IRandomGenerator';
+
+import { DEFAULT_PRESET } from '../../../../src/options/presets/Default';
+
+import { InversifyContainerFacade } from '../../../../src/container/InversifyContainerFacade';
+import { PropertyIdentifierNamesCacheStorage } from '../../../../src/storages/identifier-names-cache/PropertyIdentifierNamesCacheStorage';
+
+/**
+ * @returns {IPropertyIdentifierNamesCacheStorage}
+ */
+const getStorageInstance = <V>(options: Partial<IOptions> = DEFAULT_PRESET): IPropertyIdentifierNamesCacheStorage => {
+    const inversifyContainerFacade: IInversifyContainerFacade = new InversifyContainerFacade();
+    inversifyContainerFacade.load('', '', {});
+
+    const storage: IPropertyIdentifierNamesCacheStorage = new PropertyIdentifierNamesCacheStorage(
+        inversifyContainerFacade.get<IRandomGenerator>(ServiceIdentifiers.IRandomGenerator),
+        {
+            ...DEFAULT_PRESET,
+            ...options as IOptions
+        }
+    );
+
+    storage.initialize();
+
+    return storage;
+};
+
+describe('PropertyIdentifierNamesCacheStorage', () => {
+    const storageKey: string = 'foo';
+    const storageValue: string = 'bar';
+
+    let storage: IPropertyIdentifierNamesCacheStorage;
+
+    describe('initialize', () => {
+        describe('Variant #1: `identifierNamesCache` option values is object', () => {
+            const expectedDictionary: TDictionary<string> = {
+                [storageKey]: storageValue
+            };
+
+            let dictionary: TDictionary<string>;
+
+            before(() => {
+                storage = getStorageInstance({
+                    identifierNamesCache: {
+                        globalIdentifiers: {},
+                        propertyIdentifiers: {
+                            [storageKey]: storageValue
+                        }
+                    }
+                });
+
+                dictionary = storage.getStorageAsDictionary();
+            });
+
+            it('should initialize storage with `identifierNamesStorage` option object', () => {
+                assert.deepEqual(dictionary, expectedDictionary);
+            });
+        });
+
+        describe('Variant #2: `identifierNamesCache` option values is empty object', () => {
+            const expectedDictionary: TDictionary<string> = {};
+
+            let dictionary: TDictionary<string>;
+
+            before(() => {
+                storage = getStorageInstance({
+                    identifierNamesCache: {
+                        globalIdentifiers: {},
+                        propertyIdentifiers: {}
+                    }
+                });
+
+                dictionary = storage.getStorageAsDictionary();
+            });
+
+            it('should initialize storage with `identifierNamesStorage` option object', () => {
+                assert.deepEqual(dictionary, expectedDictionary);
+            });
+        });
+
+        describe('Variant #3: `identifierNamesCache` option values is `null`', () => {
+            const expectedDictionary: TDictionary<string> = {};
+
+            let dictionary: TDictionary<string>;
+
+            before(() => {
+                storage = getStorageInstance({
+                    identifierNamesCache: null
+                });
+
+                dictionary = storage.getStorageAsDictionary();
+            });
+
+            it('should initialize storage with `identifierNamesStorage` option object', () => {
+                assert.deepEqual(dictionary, expectedDictionary);
+            });
+        });
+    });
+});

+ 2 - 2
tsconfig.json

@@ -6,10 +6,10 @@
     "emitDecoratorMetadata": true,
     "experimentalDecorators": true,
     "lib": [
-      "es2017",
+      "es2019",
       "dom"
     ],
-    "target": "es2017",
+    "target": "es2018",
     "module": "commonjs",
     "resolveJsonModule": true,
     "esModuleInterop": true,

+ 33 - 28
yarn.lock

@@ -405,14 +405,14 @@
   resolved "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.2.tgz"
   integrity sha512-HyYEUDeIj5rRQU2Hk5HTB2uHsbRQpF70nvMhVzi+VJR0X+xNEhjPui4/kBf3VeH/wqD28PT4sVOm8qqLjBrSZg==
 
-"@es-joy/jsdoccomment@^0.7.2":
-  version "0.7.2"
-  resolved "https://registry.yarnpkg.com/@es-joy/jsdoccomment/-/jsdoccomment-0.7.2.tgz#5c0802982b28e7dcd381fbb1f52e6fd088525a31"
-  integrity sha512-i5p0VgxeCXbf5aPLPY9s9Fz6K5BkzYdbRCisw/vEY/FXAxUJ8SiAifPwkFUm0CJrmZ8tFBGW8bUtM7wiE4KTIA==
+"@es-joy/jsdoccomment@^0.8.0-alpha.2":
+  version "0.8.0-alpha.2"
+  resolved "https://registry.yarnpkg.com/@es-joy/jsdoccomment/-/jsdoccomment-0.8.0-alpha.2.tgz#78585147d8e6231270374dae528fe5b7b5587b5a"
+  integrity sha512-fjRY13Bh8sxDZkzO27U2R9L6xFqkh5fAbHuMGvGLXLfrTes8nTTMyOi6wIPt+CG0XPAxEUge8cDjhG+0aag6ew==
   dependencies:
     comment-parser "^1.1.5"
     esquery "^1.4.0"
-    jsdoctypeparser "^9.0.0"
+    jsdoc-type-pratt-parser "1.0.0-alpha.23"
 
 "@eslint/eslintrc@^0.4.1":
   version "0.4.1"
@@ -961,10 +961,10 @@ acorn-jsx@^5.3.1:
   resolved "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.1.tgz"
   integrity sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng==
 
-acorn@8.2.4:
-  version "8.2.4"
-  resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.2.4.tgz#caba24b08185c3b56e3168e97d15ed17f4d31fd0"
-  integrity sha512-Ibt84YwBDDA890eDiDCEqcbwvHlBvzzDkU2cGBBDDI1QWT12jTiXIOn2CIw5KK4i6N5Z2HUxwYjzriDyqaqqZg==
+acorn@8.3.0:
+  version "8.3.0"
+  resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.3.0.tgz#1193f9b96c4e8232f00b11a9edff81b2c8b98b88"
+  integrity sha512-tqPKHZ5CaBJw0Xmy0ZZvLs1qTV+BNFSyvn77ASXkpBNfIRk8ev26fKrD9iLGwGA9zedPao52GSHzq8lyZG0NUw==
 
 acorn@^7.4.0:
   version "7.4.0"
@@ -1855,10 +1855,10 @@ eslint-module-utils@^2.6.1:
     debug "^3.2.7"
     pkg-dir "^2.0.0"
 
[email protected].3:
-  version "2.23.3"
-  resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.23.3.tgz#8a1b073289fff03c4af0f04b6df956b7d463e191"
-  integrity sha512-wDxdYbSB55F7T5CC7ucDjY641VvKmlRwT0Vxh7PkY1mI4rclVRFWYfsrjDgZvwYYDZ5ee0ZtfFKXowWjqvEoRQ==
[email protected].4:
+  version "2.23.4"
+  resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.23.4.tgz#8dceb1ed6b73e46e50ec9a5bb2411b645e7d3d97"
+  integrity sha512-6/wP8zZRsnQFiR3iaPFgh5ImVRM1WN5NUWfTIRqwOdeiGJlBcSk82o1FEVq8yXmy4lkIzTo7YhHCIxlU/2HyEQ==
   dependencies:
     array-includes "^3.1.3"
     array.prototype.flat "^1.2.4"
@@ -1876,18 +1876,18 @@ [email protected]:
     resolve "^1.20.0"
     tsconfig-paths "^3.9.0"
 
-eslint-plugin-jsdoc@35.0.0:
-  version "35.0.0"
-  resolved "https://registry.yarnpkg.com/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-35.0.0.tgz#12634f12d1882a4f77b49aba2c4a17a7bc88cfcd"
-  integrity sha512-n92EO6g84qzjF4Lyvg+hDouMQTRHCKvW0hRobGRza0aqbG9fmmlS4p1x8cvPPAc0P87TmahMZnrP0F7hPOcAoQ==
+eslint-plugin-jsdoc@35.1.0:
+  version "35.1.0"
+  resolved "https://registry.yarnpkg.com/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-35.1.0.tgz#7a28bd53b2dfec2ab0e6eb8ee59dfabf40449f04"
+  integrity sha512-XfLaI9kzXW1mihqmeTWH+fn5Zw+sbX48Jcmwp9dftwqegj6yT/3FNnuIxmM5VyLX3AdoBNDc8p4fje7/ZxVQyg==
   dependencies:
-    "@es-joy/jsdoccomment" "^0.7.2"
+    "@es-joy/jsdoccomment" "^0.8.0-alpha.2"
     comment-parser "1.1.5"
     debug "^4.3.1"
     esquery "^1.4.0"
-    jsdoctypeparser "^9.0.0"
+    jsdoc-type-pratt-parser "^1.0.0"
     lodash "^4.17.21"
-    regextras "^0.7.1"
+    regextras "^0.8.0"
     semver "^7.3.5"
     spdx-expression-parse "^3.0.1"
 
@@ -2871,10 +2871,15 @@ js-yaml@^3.13.1:
     argparse "^1.0.7"
     esprima "^4.0.0"
 
-jsdoctypeparser@^9.0.0:
-  version "9.0.0"
-  resolved "https://registry.npmjs.org/jsdoctypeparser/-/jsdoctypeparser-9.0.0.tgz"
-  integrity sha512-jrTA2jJIL6/DAEILBEh2/w9QxCuwmvNXIry39Ay/HVfhE3o2yVV0U44blYkqdHA/OKloJEqvJy0xU+GSdE2SIw==
[email protected]:
+  version "1.0.0-alpha.23"
+  resolved "https://registry.yarnpkg.com/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-1.0.0-alpha.23.tgz#01c232d92b99b7e7ef52235ab8c9115137426639"
+  integrity sha512-COtimMd97eo5W0h6R9ISFj9ufg/9EiAzVAeQpKBJ1xJs/x8znWE155HGBDR2rwOuZsCes1gBXGmFVfvRZxGrhg==
+
+jsdoc-type-pratt-parser@^1.0.0:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-1.0.1.tgz#7926c40b17f41a95c8cd2dad52169b2209eb198b"
+  integrity sha512-+bq+6jEywRhrF06tCvjV0ew0XdFsaWTcmfpLE+42KbU6imm0TwoJrWclDVoo/8iGf0bO4SibYJLsduMWRD18kA==
 
 jsesc@^2.5.1:
   version "2.5.2"
@@ -3774,10 +3779,10 @@ regexpp@^3.1.0:
   resolved "https://registry.npmjs.org/regexpp/-/regexpp-3.1.0.tgz"
   integrity sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q==
 
-regextras@^0.7.1:
-  version "0.7.1"
-  resolved "https://registry.npmjs.org/regextras/-/regextras-0.7.1.tgz"
-  integrity sha512-9YXf6xtW+qzQ+hcMQXx95MOvfqXFgsKDZodX3qZB0x2n5Z94ioetIITsBtvJbiOyxa/6s9AtyweBLCdPmPko/w==
+regextras@^0.8.0:
+  version "0.8.0"
+  resolved "https://registry.yarnpkg.com/regextras/-/regextras-0.8.0.tgz#ec0f99853d4912839321172f608b544814b02217"
+  integrity sha512-k519uI04Z3SaY0fLX843MRXnDeG2+vHOFsyhiPZvNLe7r8rD2YNRjq4BQLZZ0oAr2NrtvZlICsXysGNFPGa3CQ==
 
 release-zalgo@^1.0.0:
   version "1.0.0"

部分文件因文件數量過多而無法顯示