Browse Source

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

Identifier names cache option
Timofey Kachalov 4 years ago
parent
commit
5b5d0eb2db
84 changed files with 2608 additions and 322 deletions
  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
 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
 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
 * 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:
 Returns `ObfuscationResult` object which contains two public methods:
 
 
 * `getObfuscatedCode()` - returns `string` with obfuscated code;
 * `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.
 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,
     disableConsoleOutput: false,
     domainLock: [],
     domainLock: [],
     forceTransformStrings: [],
     forceTransformStrings: [],
+    identifierNamesCache: null,
     identifierNamesGenerator: 'hexadecimal',
     identifierNamesGenerator: 'hexadecimal',
     identifiersDictionary: [],
     identifiersDictionary: [],
     identifiersPrefix: '',
     identifiersPrefix: '',
@@ -421,6 +423,7 @@ Following options are available for the JS Obfuscator:
     --domain-lock '<list>' (comma separated)
     --domain-lock '<list>' (comma separated)
     --exclude '<list>' (comma separated)
     --exclude '<list>' (comma separated)
     --force-transform-strings '<list>' (comma separated)
     --force-transform-strings '<list>' (comma separated)
+    --identifier-names-cache-path <string>
     --identifier-names-generator <string> [dictionary, hexadecimal, mangled, mangled-shuffled]
     --identifier-names-generator <string> [dictionary, hexadecimal, mangled, mangled-shuffled]
     --identifiers-dictionary '<list>' (comma separated)
     --identifiers-dictionary '<list>' (comma separated)
     --identifiers-prefix <string>
     --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`
 ### `identifierNamesGenerator`
 Type: `string` Default: `hexadecimal`
 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.
 * `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.
 * `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`
 ### `reservedNames`
 Type: `string[]` Default: `[]`
 Type: `string[]` Default: `[]`
 
 

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


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


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


+ 3 - 3
index.d.ts

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

+ 4 - 4
package.json

@@ -1,6 +1,6 @@
 {
 {
   "name": "javascript-obfuscator",
   "name": "javascript-obfuscator",
-  "version": "2.13.0",
+  "version": "2.14.0",
   "description": "JavaScript obfuscator",
   "description": "JavaScript obfuscator",
   "keywords": [
   "keywords": [
     "obfuscator",
     "obfuscator",
@@ -24,7 +24,7 @@
     "@javascript-obfuscator/escodegen": "2.2.0",
     "@javascript-obfuscator/escodegen": "2.2.0",
     "@javascript-obfuscator/estraverse": "5.3.0",
     "@javascript-obfuscator/estraverse": "5.3.0",
     "@nuxtjs/opencollective": "0.3.2",
     "@nuxtjs/opencollective": "0.3.2",
-    "acorn": "8.2.4",
+    "acorn": "8.3.0",
     "assert": "2.0.0",
     "assert": "2.0.0",
     "chalk": "4.1.1",
     "chalk": "4.1.1",
     "chance": "1.1.7",
     "chance": "1.1.7",
@@ -68,8 +68,8 @@
     "chai-exclude": "2.0.3",
     "chai-exclude": "2.0.3",
     "cross-env": "7.0.3",
     "cross-env": "7.0.3",
     "eslint": "7.27.0",
     "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-no-null": "1.0.2",
     "eslint-plugin-prefer-arrow": "1.2.3",
     "eslint-plugin-prefer-arrow": "1.2.3",
     "eslint-plugin-unicorn": "32.0.1",
     "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 escodegen from '@javascript-obfuscator/escodegen';
 import * as ESTree from 'estree';
 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 { ICodeTransformersRunner } from './interfaces/code-transformers/ICodeTransformersRunner';
 import { IGeneratorOutput } from './interfaces/IGeneratorOutput';
 import { IGeneratorOutput } from './interfaces/IGeneratorOutput';
 import { IJavaScriptObfuscator } from './interfaces/IJavaScriptObfsucator';
 import { IJavaScriptObfuscator } from './interfaces/IJavaScriptObfsucator';
 import { ILogger } from './interfaces/logger/ILogger';
 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 { IOptions } from './interfaces/options/IOptions';
 import { IRandomGenerator } from './interfaces/utils/IRandomGenerator';
 import { IRandomGenerator } from './interfaces/utils/IRandomGenerator';
 import { INodeTransformersRunner } from './interfaces/node-transformers/INodeTransformersRunner';
 import { INodeTransformersRunner } from './interfaces/node-transformers/INodeTransformersRunner';
@@ -87,6 +87,7 @@ export class JavaScriptObfuscator implements IJavaScriptObfuscator {
         NodeTransformer.ObjectPatternPropertiesTransformer,
         NodeTransformer.ObjectPatternPropertiesTransformer,
         NodeTransformer.ParentificationTransformer,
         NodeTransformer.ParentificationTransformer,
         NodeTransformer.ScopeIdentifiersTransformer,
         NodeTransformer.ScopeIdentifiersTransformer,
+        NodeTransformer.ScopeThroughIdentifiersTransformer,
         NodeTransformer.SplitStringTransformer,
         NodeTransformer.SplitStringTransformer,
         NodeTransformer.StringArrayRotateFunctionTransformer,
         NodeTransformer.StringArrayRotateFunctionTransformer,
         NodeTransformer.StringArrayScopeCallsWrapperTransformer,
         NodeTransformer.StringArrayScopeCallsWrapperTransformer,
@@ -108,9 +109,9 @@ export class JavaScriptObfuscator implements IJavaScriptObfuscator {
     private readonly logger: ILogger;
     private readonly logger: ILogger;
 
 
     /**
     /**
-     * @type {TObfuscatedCodeFactory}
+     * @type {TObfuscationResultFactory}
      */
      */
-    private readonly obfuscatedCodeFactory: TObfuscatedCodeFactory;
+    private readonly obfuscationResultFactory: TObfuscationResultFactory;
 
 
     /**
     /**
      * @type {IOptions}
      * @type {IOptions}
@@ -131,7 +132,7 @@ export class JavaScriptObfuscator implements IJavaScriptObfuscator {
      * @param {ICodeTransformersRunner} codeTransformersRunner
      * @param {ICodeTransformersRunner} codeTransformersRunner
      * @param {INodeTransformersRunner} nodeTransformersRunner
      * @param {INodeTransformersRunner} nodeTransformersRunner
      * @param {IRandomGenerator} randomGenerator
      * @param {IRandomGenerator} randomGenerator
-     * @param {TObfuscatedCodeFactory} obfuscatedCodeFactory
+     * @param {TObfuscationResultFactory} obfuscatedCodeFactory
      * @param {ILogger} logger
      * @param {ILogger} logger
      * @param {IOptions} options
      * @param {IOptions} options
      */
      */
@@ -139,23 +140,23 @@ export class JavaScriptObfuscator implements IJavaScriptObfuscator {
         @inject(ServiceIdentifiers.ICodeTransformersRunner) codeTransformersRunner: ICodeTransformersRunner,
         @inject(ServiceIdentifiers.ICodeTransformersRunner) codeTransformersRunner: ICodeTransformersRunner,
         @inject(ServiceIdentifiers.INodeTransformersRunner) nodeTransformersRunner: INodeTransformersRunner,
         @inject(ServiceIdentifiers.INodeTransformersRunner) nodeTransformersRunner: INodeTransformersRunner,
         @inject(ServiceIdentifiers.IRandomGenerator) randomGenerator: IRandomGenerator,
         @inject(ServiceIdentifiers.IRandomGenerator) randomGenerator: IRandomGenerator,
-        @inject(ServiceIdentifiers.Factory__IObfuscatedCode) obfuscatedCodeFactory: TObfuscatedCodeFactory,
+        @inject(ServiceIdentifiers.Factory__IObfuscationResult) obfuscatedCodeFactory: TObfuscationResultFactory,
         @inject(ServiceIdentifiers.ILogger) logger: ILogger,
         @inject(ServiceIdentifiers.ILogger) logger: ILogger,
         @inject(ServiceIdentifiers.IOptions) options: IOptions
         @inject(ServiceIdentifiers.IOptions) options: IOptions
     ) {
     ) {
         this.codeTransformersRunner = codeTransformersRunner;
         this.codeTransformersRunner = codeTransformersRunner;
         this.nodeTransformersRunner = nodeTransformersRunner;
         this.nodeTransformersRunner = nodeTransformersRunner;
         this.randomGenerator = randomGenerator;
         this.randomGenerator = randomGenerator;
-        this.obfuscatedCodeFactory = obfuscatedCodeFactory;
+        this.obfuscationResultFactory = obfuscatedCodeFactory;
         this.logger = logger;
         this.logger = logger;
         this.options = options;
         this.options = options;
     }
     }
 
 
     /**
     /**
      * @param {string} sourceCode
      * @param {string} sourceCode
-     * @returns {IObfuscatedCode}
+     * @returns {IObfuscationResult}
      */
      */
-    public obfuscate (sourceCode: string): IObfuscatedCode {
+    public obfuscate (sourceCode: string): IObfuscationResult {
         if (typeof sourceCode !== 'string') {
         if (typeof sourceCode !== 'string') {
             sourceCode = '';
             sourceCode = '';
         }
         }
@@ -183,7 +184,7 @@ export class JavaScriptObfuscator implements IJavaScriptObfuscator {
         const obfuscationTime: number = (Date.now() - timeStart) / 1000;
         const obfuscationTime: number = (Date.now() - timeStart) / 1000;
         this.logger.success(LoggingMessage.ObfuscationCompleted, obfuscationTime);
         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
      * @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 { IInversifyContainerFacade } from './interfaces/container/IInversifyContainerFacade';
 import { IJavaScriptObfuscator } from './interfaces/IJavaScriptObfsucator';
 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 { InversifyContainerFacade } from './container/InversifyContainerFacade';
 import { Options } from './options/Options';
 import { Options } from './options/Options';
@@ -24,20 +24,20 @@ class JavaScriptObfuscatorFacade {
     /**
     /**
      * @param {string} sourceCode
      * @param {string} sourceCode
      * @param {TInputOptions} inputOptions
      * @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();
         const inversifyContainerFacade: IInversifyContainerFacade = new InversifyContainerFacade();
 
 
         inversifyContainerFacade.load(sourceCode, '', inputOptions);
         inversifyContainerFacade.load(sourceCode, '', inputOptions);
 
 
         const javaScriptObfuscator: IJavaScriptObfuscator = inversifyContainerFacade
         const javaScriptObfuscator: IJavaScriptObfuscator = inversifyContainerFacade
             .get<IJavaScriptObfuscator>(ServiceIdentifiers.IJavaScriptObfuscator);
             .get<IJavaScriptObfuscator>(ServiceIdentifiers.IJavaScriptObfuscator);
-        const obfuscatedCode: IObfuscatedCode = javaScriptObfuscator.obfuscate(sourceCode);
+        const obfuscationResult: IObfuscationResult = javaScriptObfuscator.obfuscate(sourceCode);
 
 
         inversifyContainerFacade.unload();
         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 { IFileData } from '../interfaces/cli/IFileData';
 import { IInitializable } from '../interfaces/IInitializable';
 import { IInitializable } from '../interfaces/IInitializable';
-import { IObfuscatedCode } from '../interfaces/source-code/IObfuscatedCode';
+import { IObfuscationResult } from '../interfaces/source-code/IObfuscationResult';
 
 
 import { initializable } from '../decorators/Initializable';
 import { initializable } from '../decorators/Initializable';
 
 
@@ -27,10 +27,11 @@ import { ArraySanitizer } from './sanitizers/ArraySanitizer';
 import { BooleanSanitizer } from './sanitizers/BooleanSanitizer';
 import { BooleanSanitizer } from './sanitizers/BooleanSanitizer';
 
 
 import { CLIUtils } from './utils/CLIUtils';
 import { CLIUtils } from './utils/CLIUtils';
+import { IdentifierNamesCacheFileUtils } from './utils/IdentifierNamesCacheFileUtils';
 import { JavaScriptObfuscator } from '../JavaScriptObfuscatorFacade';
 import { JavaScriptObfuscator } from '../JavaScriptObfuscatorFacade';
 import { Logger } from '../logger/Logger';
 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';
 import { Utils } from '../utils/Utils';
 
 
 export class JavaScriptObfuscatorCLI implements IInitializable {
 export class JavaScriptObfuscatorCLI implements IInitializable {
@@ -57,6 +58,12 @@ export class JavaScriptObfuscatorCLI implements IInitializable {
     @initializable()
     @initializable()
     private commands!: commander.CommanderStatic;
     private commands!: commander.CommanderStatic;
 
 
+    /**
+     * @type {IdentifierNamesCacheFileUtils}
+     */
+    @initializable()
+    private identifierNamesCacheFileUtils!: IdentifierNamesCacheFileUtils;
+
     /**
     /**
      * @type {TInputCLIOptions}
      * @type {TInputCLIOptions}
      */
      */
@@ -70,16 +77,16 @@ export class JavaScriptObfuscatorCLI implements IInitializable {
     private inputPath!: string;
     private inputPath!: string;
 
 
     /**
     /**
-     * @type {SourceCodeReader}
+     * @type {SourceCodeFileUtils}
      */
      */
     @initializable()
     @initializable()
-    private sourceCodeReader!: SourceCodeReader;
+    private sourceCodeFileUtils!: SourceCodeFileUtils;
 
 
     /**
     /**
-     * @type {ObfuscatedCodeWriter}
+     * @type {ObfuscatedCodeFileUtils}
      */
      */
     @initializable()
     @initializable()
-    private obfuscatedCodeWriter!: ObfuscatedCodeWriter;
+    private obfuscatedCodeFileUtils!: ObfuscatedCodeFileUtils;
 
 
     /**
     /**
      * @type {string[]}
      * @type {string[]}
@@ -144,14 +151,15 @@ export class JavaScriptObfuscatorCLI implements IInitializable {
 
 
         this.inputPath = path.normalize(this.commands.args[0] || '');
         this.inputPath = path.normalize(this.commands.args[0] || '');
         this.inputCLIOptions = JavaScriptObfuscatorCLI.buildOptions(this.commands.opts());
         this.inputCLIOptions = JavaScriptObfuscatorCLI.buildOptions(this.commands.opts());
-        this.sourceCodeReader = new SourceCodeReader(
+        this.sourceCodeFileUtils = new SourceCodeFileUtils(
             this.inputPath,
             this.inputPath,
             this.inputCLIOptions
             this.inputCLIOptions
         );
         );
-        this.obfuscatedCodeWriter = new ObfuscatedCodeWriter(
+        this.obfuscatedCodeFileUtils = new ObfuscatedCodeFileUtils(
             this.inputPath,
             this.inputPath,
             this.inputCLIOptions
             this.inputCLIOptions
         );
         );
+        this.identifierNamesCacheFileUtils = new IdentifierNamesCacheFileUtils(this.inputCLIOptions.identifierNamesCachePath);
     }
     }
 
 
     public run (): void {
     public run (): void {
@@ -163,7 +171,7 @@ export class JavaScriptObfuscatorCLI implements IInitializable {
             return;
             return;
         }
         }
 
 
-        const sourceCodeData: IFileData[] = this.sourceCodeReader.readSourceCode();
+        const sourceCodeData: IFileData[] = this.sourceCodeFileUtils.readSourceCode();
 
 
         this.processSourceCodeData(sourceCodeData);
         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)',
                 'Enables force transformation of string literals, which being matched by passed RegExp patterns (comma separated)',
                 ArraySanitizer
                 ArraySanitizer
             )
             )
+            .option(
+                '--identifier-names-cache-path <string>',
+                'Sets path for identifier names cache'
+            )
             .option(
             .option(
                 '--identifier-names-generator <string>',
                 '--identifier-names-generator <string>',
                 'Sets identifier names generator. ' +
                 'Sets identifier names generator. ' +
@@ -430,7 +442,7 @@ export class JavaScriptObfuscatorCLI implements IInitializable {
      */
      */
     private processSourceCodeData (sourceCodeData: IFileData[]): void {
     private processSourceCodeData (sourceCodeData: IFileData[]): void {
         sourceCodeData.forEach(({ filePath, content }: IFileData, index: number) => {
         sourceCodeData.forEach(({ filePath, content }: IFileData, index: number) => {
-            const outputCodePath: string = this.obfuscatedCodeWriter.getOutputCodePath(filePath);
+            const outputCodePath: string = this.obfuscatedCodeFileUtils.getOutputCodePath(filePath);
 
 
             try {
             try {
                 Logger.log(
                 Logger.log(
@@ -466,6 +478,7 @@ export class JavaScriptObfuscatorCLI implements IInitializable {
     ): void {
     ): void {
         const options: TInputOptions = {
         const options: TInputOptions = {
             ...this.inputCLIOptions,
             ...this.inputCLIOptions,
+            identifierNamesCache: this.identifierNamesCacheFileUtils.readFile(),
             inputFileName: path.basename(inputCodePath),
             inputFileName: path.basename(inputCodePath),
             ...sourceCodeIndex !== null && {
             ...sourceCodeIndex !== null && {
                 identifiersPrefix: Utils.getIdentifiersPrefixForMultipleSources(
                 identifiersPrefix: Utils.getIdentifiersPrefixForMultipleSources(
@@ -492,9 +505,10 @@ export class JavaScriptObfuscatorCLI implements IInitializable {
         outputCodePath: string,
         outputCodePath: string,
         options: TInputOptions
         options: TInputOptions
     ): void {
     ): 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,
         outputCodePath: string,
         options: TInputOptions
         options: TInputOptions
     ): void {
     ): void {
-        const outputSourceMapPath: string = this.obfuscatedCodeWriter.getOutputSourceMapPath(
+        const outputSourceMapPath: string = this.obfuscatedCodeFileUtils.getOutputSourceMapPath(
             outputCodePath,
             outputCodePath,
             options.sourceMapFileName ?? ''
             options.sourceMapFileName ?? ''
         );
         );
@@ -517,12 +531,13 @@ export class JavaScriptObfuscatorCLI implements IInitializable {
             sourceMapFileName: path.basename(outputSourceMapPath)
             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';
 import { JavaScriptObfuscatorCLI } from '../JavaScriptObfuscatorCLI';
 
 
-export class ObfuscatedCodeWriter {
+export class ObfuscatedCodeFileUtils {
     /**
     /**
      * @type {string}
      * @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';
 import { JavaScriptObfuscatorCLI } from '../JavaScriptObfuscatorCLI';
 
 
-export class SourceCodeReader {
+export class SourceCodeFileUtils {
     /**
     /**
      * @type {string}
      * @type {string}
      */
      */
@@ -80,7 +80,7 @@ export class SourceCodeReader {
      * @returns {boolean}
      * @returns {boolean}
      */
      */
     private static isValidDirectory (directoryPath: string, excludePatterns: string[] = []): 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 {
     private static isValidFile (filePath: string, excludePatterns: string[] = []): boolean {
         return JavaScriptObfuscatorCLI.availableInputExtensions.includes(path.extname(filePath))
         return JavaScriptObfuscatorCLI.availableInputExtensions.includes(path.extname(filePath))
             && !filePath.includes(JavaScriptObfuscatorCLI.obfuscatedFilePrefix)
             && !filePath.includes(JavaScriptObfuscatorCLI.obfuscatedFilePrefix)
-            && !SourceCodeReader.isExcludedPath(filePath, excludePatterns);
+            && !SourceCodeFileUtils.isExcludedPath(filePath, excludePatterns);
     }
     }
 
 
     /**
     /**
@@ -110,15 +110,15 @@ export class SourceCodeReader {
      */
      */
     public readSourceCode (): IFileData[] {
     public readSourceCode (): IFileData[] {
         if (
         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 (
         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);
             return this.readDirectoryRecursive(this.inputPath);
         }
         }
@@ -142,8 +142,8 @@ export class SourceCodeReader {
                 const filePath: string = path.join(directoryPath, fileName);
                 const filePath: string = path.join(directoryPath, fileName);
 
 
                 if (
                 if (
-                    SourceCodeReader.isDirectoryPath(filePath)
-                    && SourceCodeReader.isValidDirectory(filePath, this.options.exclude)
+                    SourceCodeFileUtils.isDirectoryPath(filePath)
+                    && SourceCodeFileUtils.isValidDirectory(filePath, this.options.exclude)
                 ) {
                 ) {
                     filesData.push(...this.readDirectoryRecursive(filePath));
                     filesData.push(...this.readDirectoryRecursive(filePath));
 
 
@@ -151,10 +151,10 @@ export class SourceCodeReader {
                 }
                 }
 
 
                 if (
                 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);
                     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 { IInversifyContainerFacade } from '../interfaces/container/IInversifyContainerFacade';
 import { IJavaScriptObfuscator } from '../interfaces/IJavaScriptObfsucator';
 import { IJavaScriptObfuscator } from '../interfaces/IJavaScriptObfsucator';
 import { ILogger } from '../interfaces/logger/ILogger';
 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 { ISourceCode } from '../interfaces/source-code/ISourceCode';
 import { INodeTransformersRunner } from '../interfaces/node-transformers/INodeTransformersRunner';
 import { INodeTransformersRunner } from '../interfaces/node-transformers/INodeTransformersRunner';
 
 
@@ -36,7 +36,7 @@ import { CodeTransformersRunner } from '../code-transformers/CodeTransformersRun
 import { JavaScriptObfuscator } from '../JavaScriptObfuscator';
 import { JavaScriptObfuscator } from '../JavaScriptObfuscator';
 import { Logger } from '../logger/Logger';
 import { Logger } from '../logger/Logger';
 import { NodeTransformersRunner } from '../node-transformers/NodeTransformersRunner';
 import { NodeTransformersRunner } from '../node-transformers/NodeTransformersRunner';
-import { ObfuscatedCode } from '../source-code/ObfuscatedCode';
+import { ObfuscationResult } from '../source-code/ObfuscationResult';
 import { SourceCode } from '../source-code/SourceCode';
 import { SourceCode } from '../source-code/SourceCode';
 
 
 export class InversifyContainerFacade implements IInversifyContainerFacade {
 export class InversifyContainerFacade implements IInversifyContainerFacade {
@@ -181,19 +181,19 @@ export class InversifyContainerFacade implements IInversifyContainerFacade {
             .inSingletonScope();
             .inSingletonScope();
 
 
         this.container
         this.container
-            .bind<IObfuscatedCode>(ServiceIdentifiers.IObfuscatedCode)
-            .to(ObfuscatedCode);
+            .bind<IObfuscationResult>(ServiceIdentifiers.IObfuscationResult)
+            .to(ObfuscationResult);
 
 
         this.container
         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__IIdentifierNamesGenerator = 'Factory<IIdentifierNamesGenerator>',
     Factory__INodeGuard = 'Factory<INodeGuard>',
     Factory__INodeGuard = 'Factory<INodeGuard>',
     Factory__INodeTransformer = 'Factory<INodeTransformer[]>',
     Factory__INodeTransformer = 'Factory<INodeTransformer[]>',
-    Factory__IObfuscatedCode = 'Factory<IObfuscatedCode>',
+    Factory__IObfuscationResult = 'Factory<IObfuscationResult>',
     Factory__IObjectExpressionKeysTransformerCustomNode = 'Factory<IObjectExpressionKeysTransformerCustomNode>',
     Factory__IObjectExpressionKeysTransformerCustomNode = 'Factory<IObjectExpressionKeysTransformerCustomNode>',
     Factory__IObjectExpressionExtractor = 'Factory<IObjectExpressionExtractor>',
     Factory__IObjectExpressionExtractor = 'Factory<IObjectExpressionExtractor>',
     Factory__IStringArrayCustomNode = 'Factory<IStringArrayCustomNode>',
     Factory__IStringArrayCustomNode = 'Factory<IStringArrayCustomNode>',
@@ -29,6 +29,7 @@ export enum ServiceIdentifiers {
     ICustomCodeHelperFormatter = 'ICustomCodeHelperFormatter',
     ICustomCodeHelperFormatter = 'ICustomCodeHelperFormatter',
     ICustomCodeHelperObfuscator = 'ICustomCodeHelperObfuscator',
     ICustomCodeHelperObfuscator = 'ICustomCodeHelperObfuscator',
     IEscapeSequenceEncoder = 'IEscapeSequenceEncoder',
     IEscapeSequenceEncoder = 'IEscapeSequenceEncoder',
+    IGlobalIdentifierNamesCacheStorage = 'IGlobalIdentifierNamesCacheStorage',
     IIdentifierNamesGenerator = 'IIdentifierNamesGenerator',
     IIdentifierNamesGenerator = 'IIdentifierNamesGenerator',
     IIdentifierReplacer = 'IIdentifierReplacer',
     IIdentifierReplacer = 'IIdentifierReplacer',
     IJavaScriptObfuscator = 'IJavaScriptObfuscator',
     IJavaScriptObfuscator = 'IJavaScriptObfuscator',
@@ -40,10 +41,11 @@ export enum ServiceIdentifiers {
     INodeTransformerNamesGroupsBuilder = 'INodeTransformerNamesGroupsBuilder',
     INodeTransformerNamesGroupsBuilder = 'INodeTransformerNamesGroupsBuilder',
     INodeTransformersRunner = 'INodeTransformersRunner',
     INodeTransformersRunner = 'INodeTransformersRunner',
     INumberNumericalExpressionAnalyzer = 'INumberNumericalExpressionAnalyzer',
     INumberNumericalExpressionAnalyzer = 'INumberNumericalExpressionAnalyzer',
-    IObfuscatedCode = 'IObfuscatedCode',
+    IObfuscationResult = 'IObfuscationResult',
     IOptions = 'IOptions',
     IOptions = 'IOptions',
     IOptionsNormalizer = 'IOptionsNormalizer',
     IOptionsNormalizer = 'IOptionsNormalizer',
     IPrevailingKindOfVariablesAnalyzer = 'IPrevailingKindOfVariablesAnalyzer',
     IPrevailingKindOfVariablesAnalyzer = 'IPrevailingKindOfVariablesAnalyzer',
+    IPropertyIdentifierNamesCacheStorage = 'IPropertyIdentifierNamesCacheStorage',
     IObjectExpressionExtractor = 'IObjectExpressionExtractor',
     IObjectExpressionExtractor = 'IObjectExpressionExtractor',
     IRandomGenerator = 'IRandomGenerator',
     IRandomGenerator = 'IRandomGenerator',
     IRenamePropertiesReplacer = 'IRenamePropertiesReplacer',
     IRenamePropertiesReplacer = 'IRenamePropertiesReplacer',
@@ -55,6 +57,7 @@ export enum ServiceIdentifiers {
     IStringArrayScopeCallsWrapperNamesDataStorage = 'IStringArrayScopeCallsWrapperNamesDataStorage',
     IStringArrayScopeCallsWrapperNamesDataStorage = 'IStringArrayScopeCallsWrapperNamesDataStorage',
     IStringArrayStorage = 'IStringArrayStorage',
     IStringArrayStorage = 'IStringArrayStorage',
     IStringArrayStorageAnalyzer = 'IStringArrayStorageAnalyzer',
     IStringArrayStorageAnalyzer = 'IStringArrayStorageAnalyzer',
+    IThroughIdentifierReplacer = 'IThroughIdentifierReplacer',
     IVisitedLexicalScopeNodesStackStorage = 'IVisitedLexicalScopeNodesStackStorage',
     IVisitedLexicalScopeNodesStackStorage = 'IVisitedLexicalScopeNodesStackStorage',
     Newable__ICustomNode = 'Newable<ICustomNode>',
     Newable__ICustomNode = 'Newable<ICustomNode>',
     Newable__TControlFlowStorage = 'Newable<TControlFlowStorage>',
     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 { 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 { IdentifierReplacer } from '../../../node-transformers/rename-identifiers-transformers/replacer/IdentifierReplacer';
 import { LabeledStatementTransformer } from '../../../node-transformers/rename-identifiers-transformers/LabeledStatementTransformer';
 import { LabeledStatementTransformer } from '../../../node-transformers/rename-identifiers-transformers/LabeledStatementTransformer';
 import { ScopeIdentifiersTransformer } from '../../../node-transformers/rename-identifiers-transformers/ScopeIdentifiersTransformer';
 import { ScopeIdentifiersTransformer } from '../../../node-transformers/rename-identifiers-transformers/ScopeIdentifiersTransformer';
 import { ScopeThroughIdentifiersTransformer } from '../../../node-transformers/rename-identifiers-transformers/ScopeThroughIdentifiersTransformer';
 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) => {
 export const renameIdentifiersTransformersModule: interfaces.ContainerModule = new ContainerModule((bind: interfaces.Bind) => {
     // rename identifiers transformers
     // rename identifiers transformers
+    bind<INodeTransformer>(ServiceIdentifiers.INodeTransformer)
+        .to(DeadCodeInjectionIdentifiersTransformer)
+        .whenTargetNamed(NodeTransformer.DeadCodeInjectionIdentifiersTransformer);
+
     bind<INodeTransformer>(ServiceIdentifiers.INodeTransformer)
     bind<INodeTransformer>(ServiceIdentifiers.INodeTransformer)
         .to(LabeledStatementTransformer)
         .to(LabeledStatementTransformer)
         .whenTargetNamed(NodeTransformer.LabeledStatementTransformer);
         .whenTargetNamed(NodeTransformer.LabeledStatementTransformer);
@@ -29,4 +36,8 @@ export const renameIdentifiersTransformersModule: interfaces.ContainerModule = n
     bind<IIdentifierReplacer>(ServiceIdentifiers.IIdentifierReplacer)
     bind<IIdentifierReplacer>(ServiceIdentifiers.IIdentifierReplacer)
         .to(IdentifierReplacer)
         .to(IdentifierReplacer)
         .inSingletonScope();
         .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 { TControlFlowStorage } from '../../../types/storages/TControlFlowStorage';
 import { TCustomCodeHelperGroupStorage } from '../../../types/storages/TCustomCodeHelperGroupStorage';
 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 { ILiteralNodesCacheStorage } from '../../../interfaces/storages/string-array-transformers/ILiteralNodesCacheStorage';
 import { IOptions } from '../../../interfaces/options/IOptions';
 import { IOptions } from '../../../interfaces/options/IOptions';
+import { IPropertyIdentifierNamesCacheStorage } from '../../../interfaces/storages/identifier-names-cache/IPropertyIdentifierNamesCacheStorage';
 import { IRandomGenerator } from '../../../interfaces/utils/IRandomGenerator';
 import { IRandomGenerator } from '../../../interfaces/utils/IRandomGenerator';
 import { IStringArrayScopeCallsWrapperLexicalScopeDataStorage } from '../../../interfaces/storages/string-array-transformers/IStringArrayScopeCallsWrapperLexicalScopeDataStorage';
 import { IStringArrayScopeCallsWrapperLexicalScopeDataStorage } from '../../../interfaces/storages/string-array-transformers/IStringArrayScopeCallsWrapperLexicalScopeDataStorage';
 import { IStringArrayScopeCallsWrapperNamesDataStorage } from '../../../interfaces/storages/string-array-transformers/IStringArrayScopeCallsWrapperNamesDataStorage';
 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 { ControlFlowStorage } from '../../../storages/custom-nodes/ControlFlowStorage';
 import { CustomCodeHelperGroupStorage } from '../../../storages/custom-code-helpers/CustomCodeHelperGroupStorage';
 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 { LiteralNodesCacheStorage } from '../../../storages/string-array-transformers/LiteralNodesCacheStorage';
+import { PropertyIdentifierNamesCacheStorage } from '../../../storages/identifier-names-cache/PropertyIdentifierNamesCacheStorage';
 import { StringArrayScopeCallsWrapperLexicalScopeDataStorage } from '../../../storages/string-array-transformers/StringArrayScopeCallsWrapperLexicalScopeDataStorage';
 import { StringArrayScopeCallsWrapperLexicalScopeDataStorage } from '../../../storages/string-array-transformers/StringArrayScopeCallsWrapperLexicalScopeDataStorage';
 import { StringArrayScopeCallsWrapperNamesDataStorage } from '../../../storages/string-array-transformers/StringArrayScopeCallsWrapperNamesDataStorage';
 import { StringArrayScopeCallsWrapperNamesDataStorage } from '../../../storages/string-array-transformers/StringArrayScopeCallsWrapperNamesDataStorage';
 import { StringArrayStorage } from '../../../storages/string-array-transformers/StringArrayStorage';
 import { StringArrayStorage } from '../../../storages/string-array-transformers/StringArrayStorage';
@@ -26,10 +30,18 @@ export const storagesModule: interfaces.ContainerModule = new ContainerModule((b
         .to(CustomCodeHelperGroupStorage)
         .to(CustomCodeHelperGroupStorage)
         .inSingletonScope();
         .inSingletonScope();
 
 
+    bind<IGlobalIdentifierNamesCacheStorage>(ServiceIdentifiers.IGlobalIdentifierNamesCacheStorage)
+        .to(GlobalIdentifierNamesCacheStorage)
+        .inSingletonScope();
+
     bind<ILiteralNodesCacheStorage>(ServiceIdentifiers.ILiteralNodesCacheStorage)
     bind<ILiteralNodesCacheStorage>(ServiceIdentifiers.ILiteralNodesCacheStorage)
         .to(LiteralNodesCacheStorage)
         .to(LiteralNodesCacheStorage)
         .inSingletonScope();
         .inSingletonScope();
 
 
+    bind<IPropertyIdentifierNamesCacheStorage>(ServiceIdentifiers.IPropertyIdentifierNamesCacheStorage)
+        .to(PropertyIdentifierNamesCacheStorage)
+        .inSingletonScope();
+
     bind<IStringArrayStorage>(ServiceIdentifiers.IStringArrayStorage)
     bind<IStringArrayStorage>(ServiceIdentifiers.IStringArrayStorage)
         .to(StringArrayStorage)
         .to(StringArrayStorage)
         .inSingletonScope();
         .inSingletonScope();

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

@@ -4,6 +4,7 @@ export enum NodeTransformer {
     BlockStatementSimplifyTransformer = 'BlockStatementSimplifyTransformer',
     BlockStatementSimplifyTransformer = 'BlockStatementSimplifyTransformer',
     CommentsTransformer = 'CommentsTransformer',
     CommentsTransformer = 'CommentsTransformer',
     CustomCodeHelpersTransformer = 'CustomCodeHelpersTransformer',
     CustomCodeHelpersTransformer = 'CustomCodeHelpersTransformer',
+    DeadCodeInjectionIdentifiersTransformer = 'DeadCodeInjectionIdentifiersTransformer',
     DeadCodeInjectionTransformer = 'DeadCodeInjectionTransformer',
     DeadCodeInjectionTransformer = 'DeadCodeInjectionTransformer',
     DirectivePlacementTransformer = 'DirectivePlacementTransformer',
     DirectivePlacementTransformer = 'DirectivePlacementTransformer',
     EscapeSequenceTransformer = 'EscapeSequenceTransformer',
     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 {
 export interface IJavaScriptObfuscator {
     /**
     /**
      * @param sourceCode
      * @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';
 import { TNodeWithLexicalScope } from '../../types/node/TNodeWithLexicalScope';
 
 
 export interface IScopeThroughIdentifiersTraverserCallbackData {
 export interface IScopeThroughIdentifiersTraverserCallbackData {
+    isGlobalDeclaration: boolean;
     reference: eslintScope.Reference;
     reference: eslintScope.Reference;
     variableLexicalScopeNode: TNodeWithLexicalScope;
     variableLexicalScopeNode: TNodeWithLexicalScope;
 }
 }

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

@@ -3,6 +3,7 @@ import { IOptions } from './IOptions';
 export interface ICLIOptions extends IOptions {
 export interface ICLIOptions extends IOptions {
     readonly config: string;
     readonly config: string;
     readonly exclude: string[];
     readonly exclude: string[];
+    readonly identifierNamesCachePath: string;
     readonly output: string;
     readonly output: string;
     readonly version: 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 { TOptionsPreset } from '../../types/options/TOptionsPreset';
 import { TStringArrayIndexesType } from '../../types/options/TStringArrayIndexesType';
 import { TStringArrayIndexesType } from '../../types/options/TStringArrayIndexesType';
 import { TStringArrayEncoding } from '../../types/options/TStringArrayEncoding';
 import { TStringArrayEncoding } from '../../types/options/TStringArrayEncoding';
@@ -20,6 +21,7 @@ export interface IOptions {
     readonly disableConsoleOutput: boolean;
     readonly disableConsoleOutput: boolean;
     readonly domainLock: string[];
     readonly domainLock: string[];
     readonly forceTransformStrings: string[];
     readonly forceTransformStrings: string[];
+    readonly identifierNamesCache: TIdentifierNamesCache;
     readonly identifierNamesGenerator: TTypeFromEnum<typeof IdentifierNamesGenerator>;
     readonly identifierNamesGenerator: TTypeFromEnum<typeof IdentifierNamesGenerator>;
     readonly identifiersDictionary: string[];
     readonly identifiersDictionary: string[];
     readonly identifiersPrefix: 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';
 import { IInitializable } from '../IInitializable';
 
 
 export interface IMapStorage <K, V> extends IInitializable {
 export interface IMapStorage <K, V> extends IInitializable {
@@ -29,6 +31,11 @@ export interface IMapStorage <K, V> extends IInitializable {
      */
      */
     getStorage (): Map <K, V>;
     getStorage (): Map <K, V>;
 
 
+    /**
+     * @returns {TDictionary<V>}
+     */
+    getStorageAsDictionary (): TDictionary<V>;
+
     /**
     /**
      * @returns string
      * @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[]}
      * @type {NodeTransformer[]}
      */
      */
     private static readonly transformersToRenameBlockScopeIdentifiers: NodeTransformer[] = [
     private static readonly transformersToRenameBlockScopeIdentifiers: NodeTransformer[] = [
+        NodeTransformer.DeadCodeInjectionIdentifiersTransformer,
         NodeTransformer.LabeledStatementTransformer,
         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 { TNodeWithLexicalScope } from '../../types/node/TNodeWithLexicalScope';
 
 
-import { IIdentifierReplacer } from '../../interfaces/node-transformers/rename-identifiers-transformers/replacer/IIdentifierReplacer';
 import { IOptions } from '../../interfaces/options/IOptions';
 import { IOptions } from '../../interfaces/options/IOptions';
 import { IRandomGenerator } from '../../interfaces/utils/IRandomGenerator';
 import { IRandomGenerator } from '../../interfaces/utils/IRandomGenerator';
 import { IScopeIdentifiersTraverser } from '../../interfaces/node/IScopeIdentifiersTraverser';
 import { IScopeIdentifiersTraverser } from '../../interfaces/node/IScopeIdentifiersTraverser';
 import { IScopeThroughIdentifiersTraverserCallbackData } from '../../interfaces/node/IScopeThroughIdentifiersTraverserCallbackData';
 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 { IVisitor } from '../../interfaces/node-transformers/IVisitor';
 
 
 import { NodeTransformationStage } from '../../enums/node-transformers/NodeTransformationStage';
 import { NodeTransformationStage } from '../../enums/node-transformers/NodeTransformationStage';
@@ -19,35 +19,35 @@ import { AbstractNodeTransformer } from '../AbstractNodeTransformer';
 import { NodeGuards } from '../../node/NodeGuards';
 import { NodeGuards } from '../../node/NodeGuards';
 
 
 /**
 /**
- * Renames all through identifiers. Now used directly from Dead Code Injection transformer
+ * Renames all through identifiers
  */
  */
 @injectable()
 @injectable()
 export class ScopeThroughIdentifiersTransformer extends AbstractNodeTransformer {
 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 {IRandomGenerator} randomGenerator
      * @param {IOptions} options
      * @param {IOptions} options
-     * @param {IScopeIdentifiersTraverser} scopeIdentifiersTraverser
      */
      */
     public constructor (
     public constructor (
-        @inject(ServiceIdentifiers.IIdentifierReplacer) identifierReplacer: IIdentifierReplacer,
+        @inject(ServiceIdentifiers.IThroughIdentifierReplacer) throughIdentifierReplacer: IThroughIdentifierReplacer,
+        @inject(ServiceIdentifiers.IScopeIdentifiersTraverser) scopeIdentifiersTraverser: IScopeIdentifiersTraverser,
         @inject(ServiceIdentifiers.IRandomGenerator) randomGenerator: IRandomGenerator,
         @inject(ServiceIdentifiers.IRandomGenerator) randomGenerator: IRandomGenerator,
-        @inject(ServiceIdentifiers.IOptions) options: IOptions,
-        @inject(ServiceIdentifiers.IScopeIdentifiersTraverser) scopeIdentifiersTraverser: IScopeIdentifiersTraverser
+        @inject(ServiceIdentifiers.IOptions) options: IOptions
     ) {
     ) {
         super(randomGenerator, options);
         super(randomGenerator, options);
 
 
-        this.identifierReplacer = identifierReplacer;
+        this.throughIdentifierReplacer = throughIdentifierReplacer;
         this.scopeIdentifiersTraverser = scopeIdentifiersTraverser;
         this.scopeIdentifiersTraverser = scopeIdentifiersTraverser;
     }
     }
 
 
@@ -86,7 +86,10 @@ export class ScopeThroughIdentifiersTransformer extends AbstractNodeTransformer
                     variableLexicalScopeNode
                     variableLexicalScopeNode
                 } = data;
                 } = data;
 
 
-                this.transformScopeThroughIdentifiers(reference, variableLexicalScopeNode);
+                this.transformScopeThroughIdentifiers(
+                    reference,
+                    variableLexicalScopeNode
+                );
             }
             }
         );
         );
 
 
@@ -97,43 +100,23 @@ export class ScopeThroughIdentifiersTransformer extends AbstractNodeTransformer
      * @param {Reference} reference
      * @param {Reference} reference
      * @param {TNodeWithLexicalScope} lexicalScopeNode
      * @param {TNodeWithLexicalScope} lexicalScopeNode
      */
      */
-    private transformScopeThroughIdentifiers (
+    protected transformScopeThroughIdentifiers (
         reference: eslintScope.Reference,
         reference: eslintScope.Reference,
-        lexicalScopeNode: TNodeWithLexicalScope,
+        lexicalScopeNode: TNodeWithLexicalScope
     ): void {
     ): void {
         if (reference.resolved) {
         if (reference.resolved) {
             return;
             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
      * @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
         // rename of identifier
         reference.identifier.name = newIdentifier.name;
         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 { TIdentifierNamesGeneratorFactory } from '../../../types/container/generators/TIdentifierNamesGeneratorFactory';
 import { TNodeWithLexicalScope } from '../../../types/node/TNodeWithLexicalScope';
 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 { IIdentifierNamesGenerator } from '../../../interfaces/generators/identifier-names-generators/IIdentifierNamesGenerator';
 import { IIdentifierReplacer } from '../../../interfaces/node-transformers/rename-identifiers-transformers/replacer/IIdentifierReplacer';
 import { IIdentifierReplacer } from '../../../interfaces/node-transformers/rename-identifiers-transformers/replacer/IIdentifierReplacer';
 import { IOptions } from '../../../interfaces/options/IOptions';
 import { IOptions } from '../../../interfaces/options/IOptions';
@@ -14,6 +15,11 @@ import { NodeFactory } from '../../../node/NodeFactory';
 
 
 @injectable()
 @injectable()
 export class IdentifierReplacer implements IIdentifierReplacer {
 export class IdentifierReplacer implements IIdentifierReplacer {
+    /**
+     * @type {IGlobalIdentifierNamesCacheStorage}
+     */
+    private readonly identifierNamesCacheStorage: IGlobalIdentifierNamesCacheStorage;
+
     /**
     /**
      * @type {IIdentifierNamesGenerator}
      * @type {IIdentifierNamesGenerator}
      */
      */
@@ -31,19 +37,23 @@ export class IdentifierReplacer implements IIdentifierReplacer {
 
 
     /**
     /**
      * @param {TIdentifierNamesGeneratorFactory} identifierNamesGeneratorFactory
      * @param {TIdentifierNamesGeneratorFactory} identifierNamesGeneratorFactory
+     * @param {IGlobalIdentifierNamesCacheStorage} identifierNamesCacheStorage
      * @param {IOptions} options
      * @param {IOptions} options
      */
      */
     public constructor (
     public constructor (
         @inject(ServiceIdentifiers.Factory__IIdentifierNamesGenerator)
         @inject(ServiceIdentifiers.Factory__IIdentifierNamesGenerator)
             identifierNamesGeneratorFactory: TIdentifierNamesGeneratorFactory,
             identifierNamesGeneratorFactory: TIdentifierNamesGeneratorFactory,
+        @inject(ServiceIdentifiers.IGlobalIdentifierNamesCacheStorage)
+            identifierNamesCacheStorage: IGlobalIdentifierNamesCacheStorage,
         @inject(ServiceIdentifiers.IOptions) options: IOptions
         @inject(ServiceIdentifiers.IOptions) options: IOptions
     ) {
     ) {
         this.options = options;
         this.options = options;
+        this.identifierNamesCacheStorage = identifierNamesCacheStorage;
         this.identifierNamesGenerator = identifierNamesGeneratorFactory(options);
         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.
      * Reserved name will be ignored.
      *
      *
      * @param {Node} identifierNode
      * @param {Node} identifierNode
@@ -65,10 +75,15 @@ export class IdentifierReplacer implements IIdentifierReplacer {
         const namesMap: Map<string, string> = <Map<string, string>>this.blockScopesMap.get(lexicalScopeNode);
         const namesMap: Map<string, string> = <Map<string, string>>this.blockScopesMap.get(lexicalScopeNode);
 
 
         namesMap.set(identifierName, newIdentifierName);
         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.
      * Reserved name will be ignored.
      *
      *
      * @param {Identifier} identifierNode
      * @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 { TIdentifierNamesGeneratorFactory } from '../../../types/container/generators/TIdentifierNamesGeneratorFactory';
 
 
 import { IIdentifierNamesGenerator } from '../../../interfaces/generators/identifier-names-generators/IIdentifierNamesGenerator';
 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 { 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
 // eslint-disable-next-line import/no-internal-modules
 import ReservedDomProperties from './ReservedDomProperties.json';
 import ReservedDomProperties from './ReservedDomProperties.json';
@@ -38,6 +39,11 @@ export class RenamePropertiesReplacer implements IRenamePropertiesReplacer {
      */
      */
     private readonly excludedPropertyNames: Set<string> = new Set();
     private readonly excludedPropertyNames: Set<string> = new Set();
 
 
+    /**
+     * @type {IPropertyIdentifierNamesCacheStorage}
+     */
+    private readonly propertyIdentifierNamesCacheStorage: IPropertyIdentifierNamesCacheStorage;
+
     /**
     /**
      * @type {Map<string, string>}
      * @type {Map<string, string>}
      * @private
      * @private
@@ -51,14 +57,18 @@ export class RenamePropertiesReplacer implements IRenamePropertiesReplacer {
 
 
     /**
     /**
      * @param {TIdentifierNamesGeneratorFactory} identifierNamesGeneratorFactory
      * @param {TIdentifierNamesGeneratorFactory} identifierNamesGeneratorFactory
+     * @param {IPropertyIdentifierNamesCacheStorage} propertyIdentifierNamesCacheStorage
      * @param {IOptions} options
      * @param {IOptions} options
      */
      */
     public constructor (
     public constructor (
         @inject(ServiceIdentifiers.Factory__IIdentifierNamesGenerator)
         @inject(ServiceIdentifiers.Factory__IIdentifierNamesGenerator)
             identifierNamesGeneratorFactory: TIdentifierNamesGeneratorFactory,
             identifierNamesGeneratorFactory: TIdentifierNamesGeneratorFactory,
+        @inject(ServiceIdentifiers.IPropertyIdentifierNamesCacheStorage)
+            propertyIdentifierNamesCacheStorage: IPropertyIdentifierNamesCacheStorage,
         @inject(ServiceIdentifiers.IOptions) options: IOptions
         @inject(ServiceIdentifiers.IOptions) options: IOptions
     ) {
     ) {
         this.identifierNamesGenerator = identifierNamesGeneratorFactory(options);
         this.identifierNamesGenerator = identifierNamesGeneratorFactory(options);
+        this.propertyIdentifierNamesCacheStorage = propertyIdentifierNamesCacheStorage;
         this.options = options;
         this.options = options;
     }
     }
 
 
@@ -99,7 +109,13 @@ export class RenamePropertiesReplacer implements IRenamePropertiesReplacer {
             return propertyName;
             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) {
         if (renamedPropertyName !== null) {
             return renamedPropertyName;
             return renamedPropertyName;
@@ -108,6 +124,10 @@ export class RenamePropertiesReplacer implements IRenamePropertiesReplacer {
         renamedPropertyName = this.identifierNamesGenerator.generateNext();
         renamedPropertyName = this.identifierNamesGenerator.generateNext();
         this.propertyNamesMap.set(propertyName, renamedPropertyName);
         this.propertyNamesMap.set(propertyName, renamedPropertyName);
 
 
+        if (this.options.identifierNamesCache) {
+            this.propertyIdentifierNamesCacheStorage.set(propertyName, renamedPropertyName);
+        }
+
         return renamedPropertyName;
         return renamedPropertyName;
     }
     }
 
 

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

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

+ 2 - 0
src/node/ScopeIdentifiersTraverser.ts

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

+ 8 - 0
src/options/Options.ts

@@ -18,6 +18,7 @@ import {
     ValidatorOptions
     ValidatorOptions
 } from 'class-validator';
 } from 'class-validator';
 
 
+import { TIdentifierNamesCache } from '../types/TIdentifierNamesCache';
 import { TInputOptions } from '../types/options/TInputOptions';
 import { TInputOptions } from '../types/options/TInputOptions';
 import { TOptionsPreset } from '../types/options/TOptionsPreset';
 import { TOptionsPreset } from '../types/options/TOptionsPreset';
 import { TRenamePropertiesMode } from '../types/options/TRenamePropertiesMode';
 import { TRenamePropertiesMode } from '../types/options/TRenamePropertiesMode';
@@ -45,6 +46,7 @@ import { HIGH_OBFUSCATION_PRESET } from './presets/HighObfuscation';
 
 
 import { ValidationErrorsFormatter } from './ValidationErrorsFormatter';
 import { ValidationErrorsFormatter } from './ValidationErrorsFormatter';
 import { IsAllowedForObfuscationTargets } from './validators/IsAllowedForObfuscationTargets';
 import { IsAllowedForObfuscationTargets } from './validators/IsAllowedForObfuscationTargets';
+import { IsIdentifierNamesCache } from './validators/IsIdentifierNamesCache';
 
 
 @injectable()
 @injectable()
 export class Options implements IOptions {
 export class Options implements IOptions {
@@ -142,6 +144,12 @@ export class Options implements IOptions {
     })
     })
     public readonly forceTransformStrings!: string[];
     public readonly forceTransformStrings!: string[];
 
 
+    /**
+     * @type {TIdentifierNamesCache}
+     */
+    @IsIdentifierNamesCache()
+    public readonly identifierNamesCache!: TIdentifierNamesCache;
+
     /**
     /**
      * @type {IdentifierNamesGenerator}
      * @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 { DeadCodeInjectionRule } from './normalizer-rules/DeadCodeInjectionRule';
 import { DeadCodeInjectionThresholdRule } from './normalizer-rules/DeadCodeInjectionThresholdRule';
 import { DeadCodeInjectionThresholdRule } from './normalizer-rules/DeadCodeInjectionThresholdRule';
 import { DomainLockRule } from './normalizer-rules/DomainLockRule';
 import { DomainLockRule } from './normalizer-rules/DomainLockRule';
+import { IdentifierNamesCacheRule } from './normalizer-rules/IdentifierNamesCacheRule';
 import { InputFileNameRule } from './normalizer-rules/InputFileNameRule';
 import { InputFileNameRule } from './normalizer-rules/InputFileNameRule';
 import { SeedRule } from './normalizer-rules/SeedRule';
 import { SeedRule } from './normalizer-rules/SeedRule';
 import { SelfDefendingRule } from './normalizer-rules/SelfDefendingRule';
 import { SelfDefendingRule } from './normalizer-rules/SelfDefendingRule';
@@ -29,6 +30,7 @@ export class OptionsNormalizer implements IOptionsNormalizer {
         DeadCodeInjectionRule,
         DeadCodeInjectionRule,
         DeadCodeInjectionThresholdRule,
         DeadCodeInjectionThresholdRule,
         DomainLockRule,
         DomainLockRule,
+        IdentifierNamesCacheRule,
         InputFileNameRule,
         InputFileNameRule,
         SeedRule,
         SeedRule,
         SelfDefendingRule,
         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: [],
     domainLock: [],
     exclude: [],
     exclude: [],
     forceTransformStrings: [],
     forceTransformStrings: [],
+    identifierNamesCache: null,
     identifierNamesGenerator: IdentifierNamesGenerator.HexadecimalIdentifierNamesGenerator,
     identifierNamesGenerator: IdentifierNamesGenerator.HexadecimalIdentifierNamesGenerator,
     identifiersPrefix: '',
     identifiersPrefix: '',
     identifiersDictionary: [],
     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 { inject, injectable } from 'inversify';
 import { ServiceIdentifiers } from '../container/ServiceIdentifiers';
 import { ServiceIdentifiers } from '../container/ServiceIdentifiers';
 
 
+import { TIdentifierNamesCache } from '../types/TIdentifierNamesCache';
+
 import { ICryptUtils } from '../interfaces/utils/ICryptUtils';
 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 { initializable } from '../decorators/Initializable';
 import { SourceMapMode } from '../enums/source-map/SourceMapMode';
 import { SourceMapMode } from '../enums/source-map/SourceMapMode';
-import { IOptions } from '../interfaces/options/IOptions';
 
 
 @injectable()
 @injectable()
-export class ObfuscatedCode implements IObfuscatedCode {
+export class ObfuscationResult implements IObfuscationResult {
     /**
     /**
      * @type {string}
      * @type {string}
      */
      */
@@ -27,16 +31,38 @@ export class ObfuscatedCode implements IObfuscatedCode {
      */
      */
     private readonly cryptUtils: ICryptUtils;
     private readonly cryptUtils: ICryptUtils;
 
 
+    /**
+     * @type {IGlobalIdentifierNamesCacheStorage}
+     */
+    private readonly globalIdentifierNamesCacheStorage: IGlobalIdentifierNamesCacheStorage;
+
+    /**
+     * @type {IPropertyIdentifierNamesCacheStorage}
+     */
+    private readonly propertyIdentifierNamesCacheStorage: IPropertyIdentifierNamesCacheStorage;
+
     /**
     /**
      * @type {IOptions}
      * @type {IOptions}
      */
      */
     private readonly options: IOptions;
     private readonly options: IOptions;
 
 
+    /**
+     * @param {ICryptUtils} cryptUtils
+     * @param {IGlobalIdentifierNamesCacheStorage} globalIdentifierNamesCacheStorage
+     * @param {IPropertyIdentifierNamesCacheStorage} propertyIdentifierNamesCacheStorage
+     * @param {IOptions} options
+     */
     public constructor (
     public constructor (
         @inject(ServiceIdentifiers.ICryptUtils) cryptUtils: ICryptUtils,
         @inject(ServiceIdentifiers.ICryptUtils) cryptUtils: ICryptUtils,
+        @inject(ServiceIdentifiers.IGlobalIdentifierNamesCacheStorage)
+            globalIdentifierNamesCacheStorage: IGlobalIdentifierNamesCacheStorage,
+        @inject(ServiceIdentifiers.IPropertyIdentifierNamesCacheStorage)
+            propertyIdentifierNamesCacheStorage: IPropertyIdentifierNamesCacheStorage,
         @inject(ServiceIdentifiers.IOptions) options: IOptions
         @inject(ServiceIdentifiers.IOptions) options: IOptions
     ) {
     ) {
         this.cryptUtils = cryptUtils;
         this.cryptUtils = cryptUtils;
+        this.globalIdentifierNamesCacheStorage = globalIdentifierNamesCacheStorage;
+        this.propertyIdentifierNamesCacheStorage = propertyIdentifierNamesCacheStorage;
         this.options = options;
         this.options = options;
     }
     }
 
 
@@ -49,6 +75,20 @@ export class ObfuscatedCode implements IObfuscatedCode {
         this.sourceMap = sourceMap;
         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}
      * @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 { IRandomGenerator } from '../interfaces/utils/IRandomGenerator';
 
 
 import { initializable } from '../decorators/Initializable';
 import { initializable } from '../decorators/Initializable';
+import { TDictionary } from '../types/TDictionary';
 
 
 @injectable()
 @injectable()
 export abstract class MapStorage <K, V> implements IMapStorage <K, V> {
 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;
         return this.storage;
     }
     }
 
 
+    /**
+     * @returns {TDictionary<V>}
+     */
+    public getStorageAsDictionary (): TDictionary<V> {
+        return Object.fromEntries(this.storage);
+    }
+
     /**
     /**
      * @returns {string}
      * @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';
 'use strict';
 
 
-import { NO_ADDITIONAL_NODES_PRESET } from '../../src/options/presets/NoCustomNodes';
-
 (function () {
 (function () {
     const JavaScriptObfuscator: any = require('../../index');
     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) {
             function bar(...args) {
-              foo(...args, 5)
+                console.log(2);
             }
             }
-            
-            bar(...[1, 2, 3], 4)
         `,
         `,
         {
         {
-            ...NO_ADDITIONAL_NODES_PRESET,
             compact: false,
             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(obfuscatedCode);
     console.log(eval(obfuscatedCode));
     console.log(eval(obfuscatedCode));
+    console.log(identifierNamesCache);
 })();
 })();

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

@@ -33689,7 +33689,7 @@
         var _QueryWithRead = (function () {
         var _QueryWithRead = (function () {
             function _QueryWithRead(query, match) {
             function _QueryWithRead(query, match) {
                 this.query = query;
                 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;
             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 { ICryptUtils } from '../../../../../src/interfaces/utils/ICryptUtils';
 import { IInversifyContainerFacade } from '../../../../../src/interfaces/container/IInversifyContainerFacade';
 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';
 import { NO_ADDITIONAL_NODES_PRESET } from '../../../../../src/options/presets/NoCustomNodes';
 
 
@@ -631,7 +631,7 @@ describe('DomainLockTemplate', () => {
 
 
             beforeEach(() => {
             beforeEach(() => {
                 const code: string = readFileAsString(__dirname + '/fixtures/prevailing-kind-of-variables-var.js');
                 const code: string = readFileAsString(__dirname + '/fixtures/prevailing-kind-of-variables-var.js');
-                const obfuscatedCodeObject: IObfuscatedCode = JavaScriptObfuscator.obfuscate(
+                const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
                     code,
                     code,
                     {
                     {
                         ...NO_ADDITIONAL_NODES_PRESET,
                         ...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', () => {
             it('Should return correct kind of variables for domain lock code', () => {
@@ -659,7 +659,7 @@ describe('DomainLockTemplate', () => {
 
 
             beforeEach(() => {
             beforeEach(() => {
                 const code: string = readFileAsString(__dirname + '/fixtures/prevailing-kind-of-variables-const.js');
                 const code: string = readFileAsString(__dirname + '/fixtures/prevailing-kind-of-variables-const.js');
-                const obfuscatedCodeObject: IObfuscatedCode = JavaScriptObfuscator.obfuscate(
+                const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
                     code,
                     code,
                     {
                     {
                         ...NO_ADDITIONAL_NODES_PRESET,
                         ...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', () => {
             it('Should return correct kind of variables for domain lock code', () => {
@@ -687,7 +687,7 @@ describe('DomainLockTemplate', () => {
 
 
             beforeEach(() => {
             beforeEach(() => {
                 const code: string = readFileAsString(__dirname + '/fixtures/prevailing-kind-of-variables-let.js');
                 const code: string = readFileAsString(__dirname + '/fixtures/prevailing-kind-of-variables-let.js');
-                const obfuscatedCodeObject: IObfuscatedCode = JavaScriptObfuscator.obfuscate(
+                const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
                     code,
                     code,
                     {
                     {
                         ...NO_ADDITIONAL_NODES_PRESET,
                         ...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', () => {
             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 { ICryptUtilsStringArray } from '../../../../../../src/interfaces/utils/ICryptUtilsStringArray';
 import { IInversifyContainerFacade } from '../../../../../../src/interfaces/container/IInversifyContainerFacade';
 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 { IRandomGenerator } from '../../../../../../src/interfaces/utils/IRandomGenerator';
 
 
 import { AtobTemplate } from '../../../../../../src/custom-code-helpers/string-array/templates/string-array-calls-wrapper/AtobTemplate';
 import { AtobTemplate } from '../../../../../../src/custom-code-helpers/string-array/templates/string-array-calls-wrapper/AtobTemplate';
@@ -277,7 +277,7 @@ describe('StringArrayCallsWrapperTemplate', () => {
 
 
             beforeEach(() => {
             beforeEach(() => {
                 const code: string = readFileAsString(__dirname + '/fixtures/prevailing-kind-of-variables-var.js');
                 const code: string = readFileAsString(__dirname + '/fixtures/prevailing-kind-of-variables-var.js');
-                const obfuscatedCodeObject: IObfuscatedCode = JavaScriptObfuscator.obfuscate(
+                const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
                     code,
                     code,
                     {
                     {
                         ...NO_ADDITIONAL_NODES_PRESET,
                         ...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', () => {
             it('Should return correct kind of variables for string array calls wrapper code', () => {
@@ -304,7 +304,7 @@ describe('StringArrayCallsWrapperTemplate', () => {
 
 
             beforeEach(() => {
             beforeEach(() => {
                 const code: string = readFileAsString(__dirname + '/fixtures/prevailing-kind-of-variables-const.js');
                 const code: string = readFileAsString(__dirname + '/fixtures/prevailing-kind-of-variables-const.js');
-                const obfuscatedCodeObject: IObfuscatedCode = JavaScriptObfuscator.obfuscate(
+                const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
                     code,
                     code,
                     {
                     {
                         ...NO_ADDITIONAL_NODES_PRESET,
                         ...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', () => {
             it('Should return correct kind of variables for string array calls wrapper code', () => {
@@ -331,7 +331,7 @@ describe('StringArrayCallsWrapperTemplate', () => {
 
 
             beforeEach(() => {
             beforeEach(() => {
                 const code: string = readFileAsString(__dirname + '/fixtures/prevailing-kind-of-variables-let.js');
                 const code: string = readFileAsString(__dirname + '/fixtures/prevailing-kind-of-variables-let.js');
-                const obfuscatedCodeObject: IObfuscatedCode = JavaScriptObfuscator.obfuscate(
+                const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
                     code,
                     code,
                     {
                     {
                         ...NO_ADDITIONAL_NODES_PRESET,
                         ...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', () => {
             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 { 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';
 import { NO_ADDITIONAL_NODES_PRESET } from '../../../../../../src/options/presets/NoCustomNodes';
 
 
@@ -19,7 +19,7 @@ describe('StringArrayRotateFunctionTemplate', () => {
 
 
             beforeEach(() => {
             beforeEach(() => {
                 const code: string = readFileAsString(__dirname + '/fixtures/simple-input.js');
                 const code: string = readFileAsString(__dirname + '/fixtures/simple-input.js');
-                const obfuscatedCodeObject: IObfuscatedCode = JavaScriptObfuscator.obfuscate(
+                const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
                     code,
                     code,
                     {
                     {
                         ...NO_ADDITIONAL_NODES_PRESET,
                         ...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', () => {
             it('Should use computed member expression in `array.push` method', () => {
@@ -49,7 +49,7 @@ describe('StringArrayRotateFunctionTemplate', () => {
 
 
             beforeEach(() => {
             beforeEach(() => {
                 const code: string = readFileAsString(__dirname + '/fixtures/simple-input.js');
                 const code: string = readFileAsString(__dirname + '/fixtures/simple-input.js');
-                const obfuscatedCodeObject: IObfuscatedCode = JavaScriptObfuscator.obfuscate(
+                const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
                     code,
                     code,
                     {
                     {
                         ...NO_ADDITIONAL_NODES_PRESET,
                         ...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', () => {
             it('Should use computed member expression in `array.shift` method', () => {
@@ -79,7 +79,7 @@ describe('StringArrayRotateFunctionTemplate', () => {
 
 
             beforeEach(() => {
             beforeEach(() => {
                 const code: string = readFileAsString(__dirname + '/fixtures/prevailing-kind-of-variables-var.js');
                 const code: string = readFileAsString(__dirname + '/fixtures/prevailing-kind-of-variables-var.js');
-                const obfuscatedCodeObject: IObfuscatedCode = JavaScriptObfuscator.obfuscate(
+                const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
                     code,
                     code,
                     {
                     {
                         ...NO_ADDITIONAL_NODES_PRESET,
                         ...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', () => {
             it('Should return correct kind of variables for string array rotate function', () => {
@@ -107,7 +107,7 @@ describe('StringArrayRotateFunctionTemplate', () => {
 
 
             beforeEach(() => {
             beforeEach(() => {
                 const code: string = readFileAsString(__dirname + '/fixtures/prevailing-kind-of-variables-const.js');
                 const code: string = readFileAsString(__dirname + '/fixtures/prevailing-kind-of-variables-const.js');
-                const obfuscatedCodeObject: IObfuscatedCode = JavaScriptObfuscator.obfuscate(
+                const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
                     code,
                     code,
                     {
                     {
                         ...NO_ADDITIONAL_NODES_PRESET,
                         ...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', () => {
             it('Should return correct kind of variables for string array rotate function', () => {
@@ -135,7 +135,7 @@ describe('StringArrayRotateFunctionTemplate', () => {
 
 
             beforeEach(() => {
             beforeEach(() => {
                 const code: string = readFileAsString(__dirname + '/fixtures/prevailing-kind-of-variables-let.js');
                 const code: string = readFileAsString(__dirname + '/fixtures/prevailing-kind-of-variables-let.js');
-                const obfuscatedCodeObject: IObfuscatedCode = JavaScriptObfuscator.obfuscate(
+                const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
                     code,
                     code,
                     {
                     {
                         ...NO_ADDITIONAL_NODES_PRESET,
                         ...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', () => {
             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 { 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';
 import { NO_ADDITIONAL_NODES_PRESET } from '../../../../../../src/options/presets/NoCustomNodes';
 
 
@@ -17,7 +17,7 @@ describe('StringArrayTemplate', () => {
 
 
             beforeEach(() => {
             beforeEach(() => {
                 const code: string = readFileAsString(__dirname + '/fixtures/prevailing-kind-of-variables-var.js');
                 const code: string = readFileAsString(__dirname + '/fixtures/prevailing-kind-of-variables-var.js');
-                const obfuscatedCodeObject: IObfuscatedCode = JavaScriptObfuscator.obfuscate(
+                const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
                     code,
                     code,
                     {
                     {
                         ...NO_ADDITIONAL_NODES_PRESET,
                         ...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', () => {
             it('Should return correct kind of variables for string array', () => {
@@ -44,7 +44,7 @@ describe('StringArrayTemplate', () => {
 
 
             beforeEach(() => {
             beforeEach(() => {
                 const code: string = readFileAsString(__dirname + '/fixtures/prevailing-kind-of-variables-const.js');
                 const code: string = readFileAsString(__dirname + '/fixtures/prevailing-kind-of-variables-const.js');
-                const obfuscatedCodeObject: IObfuscatedCode = JavaScriptObfuscator.obfuscate(
+                const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
                     code,
                     code,
                     {
                     {
                         ...NO_ADDITIONAL_NODES_PRESET,
                         ...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', () => {
             it('Should return correct kind of variables for string array', () => {
@@ -71,7 +71,7 @@ describe('StringArrayTemplate', () => {
 
 
             beforeEach(() => {
             beforeEach(() => {
                 const code: string = readFileAsString(__dirname + '/fixtures/prevailing-kind-of-variables-let.js');
                 const code: string = readFileAsString(__dirname + '/fixtures/prevailing-kind-of-variables-let.js');
-                const obfuscatedCodeObject: IObfuscatedCode = JavaScriptObfuscator.obfuscate(
+                const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
                     code,
                     code,
                     {
                     {
                         ...NO_ADDITIONAL_NODES_PRESET,
                         ...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', () => {
             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 { assert } from 'chai';
 
 
 import { TDictionary } from '../../../src/types/TDictionary';
 import { TDictionary } from '../../../src/types/TDictionary';
+import { TIdentifierNamesCache } from '../../../src/types/TIdentifierNamesCache';
 import { TInputOptions } from '../../../src/types/options/TInputOptions';
 import { TInputOptions } from '../../../src/types/options/TInputOptions';
 import { TOptionsPreset } from '../../../src/types/options/TOptionsPreset';
 import { TOptionsPreset } from '../../../src/types/options/TOptionsPreset';
 import { TTypeFromEnum } from '../../../src/types/utils/TTypeFromEnum';
 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 { SourceMapMode } from '../../../src/enums/source-map/SourceMapMode';
 import { StringArrayEncoding } from '../../../src/enums/node-transformers/string-array-transformers/StringArrayEncoding';
 import { StringArrayEncoding } from '../../../src/enums/node-transformers/string-array-transformers/StringArrayEncoding';
@@ -32,15 +33,15 @@ describe('JavaScriptObfuscator', () => {
 
 
             beforeEach(() => {
             beforeEach(() => {
                 const code: string = readFileAsString(__dirname + '/fixtures/simple-input-1.js');
                 const code: string = readFileAsString(__dirname + '/fixtures/simple-input-1.js');
-                const obfuscatedCodeObject: IObfuscatedCode = JavaScriptObfuscator.obfuscate(
+                const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
                     code,
                     code,
                     {
                     {
                         ...NO_ADDITIONAL_NODES_PRESET
                         ...NO_ADDITIONAL_NODES_PRESET
                     }
                     }
                 );
                 );
 
 
-                obfuscatedCode = obfuscatedCodeObject.getObfuscatedCode();
-                sourceMap = obfuscatedCodeObject.getSourceMap();
+                obfuscatedCode = obfuscationResult.getObfuscatedCode();
+                sourceMap = obfuscationResult.getSourceMap();
             });
             });
 
 
             it('should return correct obfuscated code', () => {
             it('should return correct obfuscated code', () => {
@@ -111,7 +112,7 @@ describe('JavaScriptObfuscator', () => {
 
 
                 beforeEach(() => {
                 beforeEach(() => {
                     const code: string = readFileAsString(__dirname + '/fixtures/simple-input-1.js');
                     const code: string = readFileAsString(__dirname + '/fixtures/simple-input-1.js');
-                    const obfuscatedCodeObject: IObfuscatedCode = JavaScriptObfuscator.obfuscate(
+                    const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
                         code,
                         code,
                         {
                         {
                             ...NO_ADDITIONAL_NODES_PRESET,
                             ...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', () => {
                 it('should return correct obfuscated code', () => {
@@ -140,7 +141,7 @@ describe('JavaScriptObfuscator', () => {
 
 
                 beforeEach(() => {
                 beforeEach(() => {
                     const code: string = readFileAsString(__dirname + '/fixtures/simple-input-1.js');
                     const code: string = readFileAsString(__dirname + '/fixtures/simple-input-1.js');
-                    const obfuscatedCodeObject: IObfuscatedCode = JavaScriptObfuscator.obfuscate(
+                    const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
                         code,
                         code,
                         {
                         {
                             ...NO_ADDITIONAL_NODES_PRESET,
                             ...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', () => {
                 it('should return correct obfuscated code', () => {
@@ -174,16 +175,16 @@ describe('JavaScriptObfuscator', () => {
 
 
                 beforeEach(() => {
                 beforeEach(() => {
                     const code: string = readFileAsString(__dirname + '/fixtures/empty-input.js');
                     const code: string = readFileAsString(__dirname + '/fixtures/empty-input.js');
-                    const obfuscatedCodeObject: IObfuscatedCode = JavaScriptObfuscator.obfuscate(
+                    const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
                         code,
                         code,
                         {
                         {
                             sourceMap: true
                             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;
                     sourceMapNames = sourceMapObject.names;
                     sourceMapSources = sourceMapObject.sources;
                     sourceMapSources = sourceMapObject.sources;
@@ -886,7 +887,7 @@ describe('JavaScriptObfuscator', () => {
                     obfuscatedCode = JavaScriptObfuscator.obfuscate(code).getObfuscatedCode();
                     obfuscatedCode = JavaScriptObfuscator.obfuscate(code).getObfuscatedCode();
                 });
                 });
 
 
-                it('Match #!: should correctly obfuscate a import', () => {
+                it('Match #1: should correctly obfuscate a import', () => {
                     assert.match(obfuscatedCode, importRegExp);
                     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 () {
         describe('3.5k variables', function () {
             this.timeout(200000);
             this.timeout(200000);
 
 
@@ -1224,7 +1350,7 @@ describe('JavaScriptObfuscator', () => {
         });
         });
 
 
         describe('invalid source codes object', () => {
         describe('invalid source codes object', () => {
-            let testFunc: () => TDictionary<IObfuscatedCode>;
+            let testFunc: () => TDictionary<IObfuscationResult>;
 
 
             beforeEach(() => {
             beforeEach(() => {
                 testFunc = () => JavaScriptObfuscator.obfuscateMultiple(
                 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('seedRule', () => {
             describe('Variant #1: seed value is string', () => {
             describe('Variant #1: seed value is string', () => {
                 before(() => {
                 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/ArraySanitizer.spec';
 import './unit-tests/cli/sanitizers/BooleanSanitizer.spec';
 import './unit-tests/cli/sanitizers/BooleanSanitizer.spec';
 import './unit-tests/cli/utils/CLIUtils.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/decorators/initializable/Initializable.spec';
 import './unit-tests/generators/identifier-names-generators/DictionarylIdentifierNamesGenerator.spec';
 import './unit-tests/generators/identifier-names-generators/DictionarylIdentifierNamesGenerator.spec';
 import './unit-tests/generators/identifier-names-generators/HexadecimalIdentifierNamesGenerator.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/node/numerical-expression-data-to-node-converter/NumericalExpressionDataToNodeConverter.spec';
 import './unit-tests/options/Options.spec';
 import './unit-tests/options/Options.spec';
 import './unit-tests/options/ValidationErrorsFormatter.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/source-code/SourceCode.spec';
 import './unit-tests/storages/ArrayStorage.spec';
 import './unit-tests/storages/ArrayStorage.spec';
 import './unit-tests/storages/MapStorage.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/literal-nodes-cache/LiteralNodesCacheStorage.spec';
 import './unit-tests/storages/string-array-transformers/string-array/StringArrayStorage.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';
 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/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/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-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/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/block-statement-simplify-transformer/BlockStatementSimplifyTransformer.spec';
 import './functional-tests/node-transformers/simplifying-transformers/expression-statements-merge-transformer/ExpressionStatementsMergeTransformer.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/node-transformers/string-array-transformers/string-array-transformer/StringArrayTransformer.spec';
 import './functional-tests/options/OptionsNormalizer.spec';
 import './functional-tests/options/OptionsNormalizer.spec';
 import './functional-tests/options/domain-lock/Validation.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';
 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 path from 'path';
 import * as rimraf from 'rimraf';
 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';
     const tmpDirectoryPath: string = 'test/tmp';
 
 
     describe('getOutputCodePath', () => {
     describe('getOutputCodePath', () => {
@@ -27,13 +27,13 @@ describe('ObfuscatedCodeWriter', () => {
             let outputCodePath: string;
             let outputCodePath: string;
 
 
             before(() => {
             before(() => {
-                const obfuscatedCodeWriter: ObfuscatedCodeWriter = new ObfuscatedCodeWriter(
+                const obfuscatedCodeFileUtils: ObfuscatedCodeFileUtils = new ObfuscatedCodeFileUtils(
                     rawInputPath,
                     rawInputPath,
                     {
                     {
                         output: rawOutputPath
                         output: rawOutputPath
                     }
                     }
                 );
                 );
-                outputCodePath = obfuscatedCodeWriter.getOutputCodePath(inputPath);
+                outputCodePath = obfuscatedCodeFileUtils.getOutputCodePath(inputPath);
             });
             });
 
 
             it('should return output path that equals to passed output file path', () => {
             it('should return output path that equals to passed output file path', () => {
@@ -50,13 +50,13 @@ describe('ObfuscatedCodeWriter', () => {
             let outputCodePath: string;
             let outputCodePath: string;
 
 
             before(() => {
             before(() => {
-                const obfuscatedCodeWriter: ObfuscatedCodeWriter = new ObfuscatedCodeWriter(
+                const obfuscatedCodeFileUtils: ObfuscatedCodeFileUtils = new ObfuscatedCodeFileUtils(
                     rawInputPath,
                     rawInputPath,
                     {
                     {
                         output: rawOutputPath
                         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', () => {
             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;
             let testFunc: () => string;
 
 
             before(() => {
             before(() => {
-                const obfuscatedCodeWriter: ObfuscatedCodeWriter = new ObfuscatedCodeWriter(
+                const obfuscatedCodeFileUtils: ObfuscatedCodeFileUtils = new ObfuscatedCodeFileUtils(
                     rawInputPath,
                     rawInputPath,
                     {
                     {
                         output: rawOutputPath
                         output: rawOutputPath
                     }
                     }
                 );
                 );
-                testFunc = () => obfuscatedCodeWriter.getOutputCodePath(inputPath);
+                testFunc = () => obfuscatedCodeFileUtils.getOutputCodePath(inputPath);
             });
             });
 
 
             it('should throw an error if output path is a file path', () => {
             it('should throw an error if output path is a file path', () => {
@@ -100,13 +100,13 @@ describe('ObfuscatedCodeWriter', () => {
                 let outputCodePath: string;
                 let outputCodePath: string;
 
 
                 before(() => {
                 before(() => {
-                    const obfuscatedCodeWriter: ObfuscatedCodeWriter = new ObfuscatedCodeWriter(
+                    const obfuscatedCodeFileUtils: ObfuscatedCodeFileUtils = new ObfuscatedCodeFileUtils(
                         rawInputPath,
                         rawInputPath,
                         {
                         {
                             output: rawOutputPath
                             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', () => {
                 it('should return output path that contains raw output path and actual file input path', () => {
@@ -127,13 +127,13 @@ describe('ObfuscatedCodeWriter', () => {
                 let outputCodePath: string;
                 let outputCodePath: string;
 
 
                 before(() => {
                 before(() => {
-                    const obfuscatedCodeWriter: ObfuscatedCodeWriter = new ObfuscatedCodeWriter(
+                    const obfuscatedCodeFileUtils: ObfuscatedCodeFileUtils = new ObfuscatedCodeFileUtils(
                         rawInputPath,
                         rawInputPath,
                         {
                         {
                             output: rawOutputPath
                             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', () => {
                 it('should return output path that contains raw output path and actual file input path', () => {
@@ -155,13 +155,13 @@ describe('ObfuscatedCodeWriter', () => {
                 let outputCodePath: string;
                 let outputCodePath: string;
 
 
                 before(() => {
                 before(() => {
-                    const obfuscatedCodeWriter: ObfuscatedCodeWriter = new ObfuscatedCodeWriter(
+                    const obfuscatedCodeFileUtils: ObfuscatedCodeFileUtils = new ObfuscatedCodeFileUtils(
                         rawInputPath,
                         rawInputPath,
                         {
                         {
                             output: rawOutputPath
                             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', () => {
                 it('should return output path that contains raw output path and actual file input path', () => {
@@ -184,13 +184,13 @@ describe('ObfuscatedCodeWriter', () => {
                 let outputCodePath: string;
                 let outputCodePath: string;
 
 
                 before(() => {
                 before(() => {
-                    const obfuscatedCodeWriter: ObfuscatedCodeWriter = new ObfuscatedCodeWriter(
+                    const obfuscatedCodeFileUtils: ObfuscatedCodeFileUtils = new ObfuscatedCodeFileUtils(
                         rawInputPath,
                         rawInputPath,
                         {
                         {
                             output: rawOutputPath
                             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', () => {
                 it('should return output path that contains raw output path and actual file input path', () => {
@@ -211,13 +211,13 @@ describe('ObfuscatedCodeWriter', () => {
                 let outputCodePath: string;
                 let outputCodePath: string;
 
 
                 before(() => {
                 before(() => {
-                    const obfuscatedCodeWriter: ObfuscatedCodeWriter = new ObfuscatedCodeWriter(
+                    const obfuscatedCodeFileUtils: ObfuscatedCodeFileUtils = new ObfuscatedCodeFileUtils(
                         rawInputPath,
                         rawInputPath,
                         {
                         {
                             output: rawOutputPath
                             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', () => {
                 it('should return output path that contains raw output path and actual file input path', () => {
@@ -238,13 +238,13 @@ describe('ObfuscatedCodeWriter', () => {
                 let outputCodePath: string;
                 let outputCodePath: string;
 
 
                 before(() => {
                 before(() => {
-                    const obfuscatedCodeWriter: ObfuscatedCodeWriter = new ObfuscatedCodeWriter(
+                    const obfuscatedCodeFileUtils: ObfuscatedCodeFileUtils = new ObfuscatedCodeFileUtils(
                         rawInputPath,
                         rawInputPath,
                         {
                         {
                             output: rawOutputPath
                             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', () => {
                 it('should return output path that contains raw output path and actual file input path', () => {
@@ -280,13 +280,13 @@ describe('ObfuscatedCodeWriter', () => {
                     let outputCodePath: string;
                     let outputCodePath: string;
 
 
                     before(() => {
                     before(() => {
-                        const obfuscatedCodeWriter: ObfuscatedCodeWriter = new ObfuscatedCodeWriter(
+                        const obfuscatedCodeFileUtils: ObfuscatedCodeFileUtils = new ObfuscatedCodeFileUtils(
                             rawInputPath,
                             rawInputPath,
                             {
                             {
                                 output: rawOutputPath
                                 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', () => {
                     it('should return output path that contains raw output path and actual file input path', () => {
@@ -315,13 +315,13 @@ describe('ObfuscatedCodeWriter', () => {
             let outputSourceMapPath: string;
             let outputSourceMapPath: string;
 
 
             before(() => {
             before(() => {
-                const obfuscatedCodeWriter: ObfuscatedCodeWriter = new ObfuscatedCodeWriter(
+                const obfuscatedCodeFileUtils: ObfuscatedCodeFileUtils = new ObfuscatedCodeFileUtils(
                     rawInputPath,
                     rawInputPath,
                     {
                     {
                         output: rawOutputPath
                         output: rawOutputPath
                     }
                     }
                 );
                 );
-                outputSourceMapPath = obfuscatedCodeWriter.getOutputSourceMapPath(outputCodePath);
+                outputSourceMapPath = obfuscatedCodeFileUtils.getOutputSourceMapPath(outputCodePath);
             });
             });
 
 
             it('should return output path for source map', () => {
             it('should return output path for source map', () => {
@@ -337,13 +337,13 @@ describe('ObfuscatedCodeWriter', () => {
             let testFunc: () => string;
             let testFunc: () => string;
 
 
             before(() => {
             before(() => {
-                const obfuscatedCodeWriter: ObfuscatedCodeWriter = new ObfuscatedCodeWriter(
+                const obfuscatedCodeFileUtils: ObfuscatedCodeFileUtils = new ObfuscatedCodeFileUtils(
                     rawInputPath,
                     rawInputPath,
                     {
                     {
                         output: rawOutputPath
                         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', () => {
             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;
             let outputSourceMapPath: string;
 
 
             before(() => {
             before(() => {
-                const obfuscatedCodeWriter: ObfuscatedCodeWriter = new ObfuscatedCodeWriter(
+                const obfuscatedCodeFileUtils: ObfuscatedCodeFileUtils = new ObfuscatedCodeFileUtils(
                     rawInputPath,
                     rawInputPath,
                     {
                     {
                         output: rawOutputPath
                         output: rawOutputPath
                     }
                     }
                 );
                 );
-                outputSourceMapPath = obfuscatedCodeWriter.getOutputSourceMapPath(outputCodePath);
+                outputSourceMapPath = obfuscatedCodeFileUtils.getOutputSourceMapPath(outputCodePath);
             });
             });
 
 
             it('should return output path for source map', () => {
             it('should return output path for source map', () => {
@@ -384,13 +384,13 @@ describe('ObfuscatedCodeWriter', () => {
             let outputSourceMapPath: string;
             let outputSourceMapPath: string;
 
 
             before(() => {
             before(() => {
-                const obfuscatedCodeWriter: ObfuscatedCodeWriter = new ObfuscatedCodeWriter(
+                const obfuscatedCodeFileUtils: ObfuscatedCodeFileUtils = new ObfuscatedCodeFileUtils(
                     rawInputPath,
                     rawInputPath,
                     {
                     {
                         output: rawOutputPath
                         output: rawOutputPath
                     }
                     }
                 );
                 );
-                outputSourceMapPath = obfuscatedCodeWriter.getOutputSourceMapPath(outputCodePath, sourceMapFileName);
+                outputSourceMapPath = obfuscatedCodeFileUtils.getOutputSourceMapPath(outputCodePath, sourceMapFileName);
             });
             });
 
 
             it('should return output path for source map', () => {
             it('should return output path for source map', () => {
@@ -408,13 +408,13 @@ describe('ObfuscatedCodeWriter', () => {
             let outputSourceMapPath: string;
             let outputSourceMapPath: string;
 
 
             before(() => {
             before(() => {
-                const obfuscatedCodeWriter: ObfuscatedCodeWriter = new ObfuscatedCodeWriter(
+                const obfuscatedCodeFileUtils: ObfuscatedCodeFileUtils = new ObfuscatedCodeFileUtils(
                     rawInputPath,
                     rawInputPath,
                     {
                     {
                         output: rawOutputPath
                         output: rawOutputPath
                     }
                     }
                 );
                 );
-                outputSourceMapPath = obfuscatedCodeWriter.getOutputSourceMapPath(outputCodePath, sourceMapFileName);
+                outputSourceMapPath = obfuscatedCodeFileUtils.getOutputSourceMapPath(outputCodePath, sourceMapFileName);
             });
             });
 
 
             it('should return output path for source map', () => {
             it('should return output path for source map', () => {
@@ -432,13 +432,13 @@ describe('ObfuscatedCodeWriter', () => {
             let outputSourceMapPath: string;
             let outputSourceMapPath: string;
 
 
             before(() => {
             before(() => {
-                const obfuscatedCodeWriter: ObfuscatedCodeWriter = new ObfuscatedCodeWriter(
+                const obfuscatedCodeFileUtils: ObfuscatedCodeFileUtils = new ObfuscatedCodeFileUtils(
                     rawInputPath,
                     rawInputPath,
                     {
                     {
                         output: rawOutputPath
                         output: rawOutputPath
                     }
                     }
                 );
                 );
-                outputSourceMapPath = obfuscatedCodeWriter.getOutputSourceMapPath(outputCodePath, sourceMapFileName);
+                outputSourceMapPath = obfuscatedCodeFileUtils.getOutputSourceMapPath(outputCodePath, sourceMapFileName);
             });
             });
 
 
             it('should return output path for source map', () => {
             it('should return output path for source map', () => {
@@ -456,13 +456,13 @@ describe('ObfuscatedCodeWriter', () => {
             let outputSourceMapPath: string;
             let outputSourceMapPath: string;
 
 
             before(() => {
             before(() => {
-                const obfuscatedCodeWriter: ObfuscatedCodeWriter = new ObfuscatedCodeWriter(
+                const obfuscatedCodeFileUtils: ObfuscatedCodeFileUtils = new ObfuscatedCodeFileUtils(
                     rawInputPath,
                     rawInputPath,
                     {
                     {
                         output: rawOutputPath
                         output: rawOutputPath
                     }
                     }
                 );
                 );
-                outputSourceMapPath = obfuscatedCodeWriter.getOutputSourceMapPath(outputCodePath, sourceMapFileName);
+                outputSourceMapPath = obfuscatedCodeFileUtils.getOutputSourceMapPath(outputCodePath, sourceMapFileName);
             });
             });
 
 
             it('should return output path for source map', () => {
             it('should return output path for source map', () => {
@@ -480,13 +480,13 @@ describe('ObfuscatedCodeWriter', () => {
             let outputSourceMapPath: string;
             let outputSourceMapPath: string;
 
 
             before(() => {
             before(() => {
-                const obfuscatedCodeWriter: ObfuscatedCodeWriter = new ObfuscatedCodeWriter(
+                const obfuscatedCodeFileUtils: ObfuscatedCodeFileUtils = new ObfuscatedCodeFileUtils(
                     rawInputPath,
                     rawInputPath,
                     {
                     {
                         output: rawOutputPath
                         output: rawOutputPath
                     }
                     }
                 );
                 );
-                outputSourceMapPath = obfuscatedCodeWriter.getOutputSourceMapPath(outputCodePath, sourceMapFileName);
+                outputSourceMapPath = obfuscatedCodeFileUtils.getOutputSourceMapPath(outputCodePath, sourceMapFileName);
             });
             });
 
 
             it('should return output path for source map', () => {
             it('should return output path for source map', () => {
@@ -505,13 +505,13 @@ describe('ObfuscatedCodeWriter', () => {
                 let outputSourceMapPath: string;
                 let outputSourceMapPath: string;
 
 
                 before(() => {
                 before(() => {
-                    const obfuscatedCodeWriter: ObfuscatedCodeWriter = new ObfuscatedCodeWriter(
+                    const obfuscatedCodeFileUtils: ObfuscatedCodeFileUtils = new ObfuscatedCodeFileUtils(
                         rawInputPath,
                         rawInputPath,
                         {
                         {
                             output: rawOutputPath
                             output: rawOutputPath
                         }
                         }
                     );
                     );
-                    outputSourceMapPath = obfuscatedCodeWriter.getOutputSourceMapPath(outputCodePath, sourceMapFileName);
+                    outputSourceMapPath = obfuscatedCodeFileUtils.getOutputSourceMapPath(outputCodePath, sourceMapFileName);
                 });
                 });
 
 
                 it('should return output path for source map', () => {
                 it('should return output path for source map', () => {
@@ -529,13 +529,13 @@ describe('ObfuscatedCodeWriter', () => {
                 let outputSourceMapPath: string;
                 let outputSourceMapPath: string;
 
 
                 before(() => {
                 before(() => {
-                    const obfuscatedCodeWriter: ObfuscatedCodeWriter = new ObfuscatedCodeWriter(
+                    const obfuscatedCodeFileUtils: ObfuscatedCodeFileUtils = new ObfuscatedCodeFileUtils(
                         rawInputPath,
                         rawInputPath,
                         {
                         {
                             output: rawOutputPath
                             output: rawOutputPath
                         }
                         }
                     );
                     );
-                    outputSourceMapPath = obfuscatedCodeWriter.getOutputSourceMapPath(outputCodePath, sourceMapFileName);
+                    outputSourceMapPath = obfuscatedCodeFileUtils.getOutputSourceMapPath(outputCodePath, sourceMapFileName);
                 });
                 });
 
 
                 it('should return output path for source map', () => {
                 it('should return output path for source map', () => {
@@ -553,13 +553,13 @@ describe('ObfuscatedCodeWriter', () => {
                 let outputSourceMapPath: string;
                 let outputSourceMapPath: string;
 
 
                 before(() => {
                 before(() => {
-                    const obfuscatedCodeWriter: ObfuscatedCodeWriter = new ObfuscatedCodeWriter(
+                    const obfuscatedCodeFileUtils: ObfuscatedCodeFileUtils = new ObfuscatedCodeFileUtils(
                         rawInputPath,
                         rawInputPath,
                         {
                         {
                             output: rawOutputPath
                             output: rawOutputPath
                         }
                         }
                     );
                     );
-                    outputSourceMapPath = obfuscatedCodeWriter.getOutputSourceMapPath(outputCodePath, sourceMapFileName);
+                    outputSourceMapPath = obfuscatedCodeFileUtils.getOutputSourceMapPath(outputCodePath, sourceMapFileName);
                 });
                 });
 
 
                 it('should return output path for source map', () => {
                 it('should return output path for source map', () => {
@@ -577,13 +577,13 @@ describe('ObfuscatedCodeWriter', () => {
                 let outputSourceMapPath: string;
                 let outputSourceMapPath: string;
 
 
                 before(() => {
                 before(() => {
-                    const obfuscatedCodeWriter: ObfuscatedCodeWriter = new ObfuscatedCodeWriter(
+                    const obfuscatedCodeFileUtils: ObfuscatedCodeFileUtils = new ObfuscatedCodeFileUtils(
                         rawInputPath,
                         rawInputPath,
                         {
                         {
                             output: rawOutputPath
                             output: rawOutputPath
                         }
                         }
                     );
                     );
-                    outputSourceMapPath = obfuscatedCodeWriter.getOutputSourceMapPath(outputCodePath, sourceMapFileName);
+                    outputSourceMapPath = obfuscatedCodeFileUtils.getOutputSourceMapPath(outputCodePath, sourceMapFileName);
                 });
                 });
 
 
                 it('should return output path for source map', () => {
                 it('should return output path for source map', () => {
@@ -599,13 +599,13 @@ describe('ObfuscatedCodeWriter', () => {
             let testFunc: () => string;
             let testFunc: () => string;
 
 
             before(() => {
             before(() => {
-                const obfuscatedCodeWriter: ObfuscatedCodeWriter = new ObfuscatedCodeWriter(
+                const obfuscatedCodeFileUtils: ObfuscatedCodeFileUtils = new ObfuscatedCodeFileUtils(
                     rawInputPath,
                     rawInputPath,
                     {
                     {
                         output: rawOutputPath
                         output: rawOutputPath
                     }
                     }
                 );
                 );
-                testFunc = () => obfuscatedCodeWriter.getOutputSourceMapPath('', '');
+                testFunc = () => obfuscatedCodeFileUtils.getOutputSourceMapPath('', '');
             });
             });
 
 
             it('should throw an error if output code path is empty', () => {
             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 { 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 expectedError: RegExp = /Given input path must be a valid/;
     const fileContent: string = 'test';
     const fileContent: string = 'test';
     const tmpDirectoryPath: string = path.join('test', 'tmp');
     const tmpDirectoryPath: string = path.join('test', 'tmp');
@@ -32,7 +32,7 @@ describe('SourceCodeReader', () => {
 
 
                 before(() => {
                 before(() => {
                     fs.writeFileSync(inputPath, fileContent);
                     fs.writeFileSync(inputPath, fileContent);
-                    filesData = new SourceCodeReader(inputPath, {}).readSourceCode();
+                    filesData = new SourceCodeFileUtils(inputPath, {}).readSourceCode();
                 });
                 });
 
 
                 it('should return valid files data', () => {
                 it('should return valid files data', () => {
@@ -51,7 +51,7 @@ describe('SourceCodeReader', () => {
                 let testFunc: () => void;
                 let testFunc: () => void;
 
 
                 before(() => {
                 before(() => {
-                    testFunc = () => new SourceCodeReader(inputPath, {}).readSourceCode();
+                    testFunc = () => new SourceCodeFileUtils(inputPath, {}).readSourceCode();
                 });
                 });
 
 
                 it('should throw an error if `inputPath` is not a valid path', () => {
                 it('should throw an error if `inputPath` is not a valid path', () => {
@@ -67,7 +67,7 @@ describe('SourceCodeReader', () => {
 
 
                 before(() => {
                 before(() => {
                     fs.writeFileSync(inputPath, fileContent);
                     fs.writeFileSync(inputPath, fileContent);
-                    testFunc = () => new SourceCodeReader(inputPath, {}).readSourceCode();
+                    testFunc = () => new SourceCodeFileUtils(inputPath, {}).readSourceCode();
                 });
                 });
 
 
                 it('should throw an error if `inputPath` has invalid extension', () => {
                 it('should throw an error if `inputPath` has invalid extension', () => {
@@ -92,7 +92,7 @@ describe('SourceCodeReader', () => {
 
 
                     before(() => {
                     before(() => {
                         fs.writeFileSync(inputPath, fileContent);
                         fs.writeFileSync(inputPath, fileContent);
-                        filesData = new SourceCodeReader(
+                        filesData = new SourceCodeFileUtils(
                             inputPath,
                             inputPath,
                             {
                             {
                                 exclude: [path.join('**', 'foo.js')]
                                 exclude: [path.join('**', 'foo.js')]
@@ -118,7 +118,7 @@ describe('SourceCodeReader', () => {
 
 
                         before(() => {
                         before(() => {
                             fs.writeFileSync(inputPath, fileContent);
                             fs.writeFileSync(inputPath, fileContent);
-                            testFunc = () => new SourceCodeReader(
+                            testFunc = () => new SourceCodeFileUtils(
                                 inputPath,
                                 inputPath,
                                 {
                                 {
                                     exclude: [path.join('**', tmpFileName)]
                                     exclude: [path.join('**', tmpFileName)]
@@ -143,7 +143,7 @@ describe('SourceCodeReader', () => {
 
 
                         before(() => {
                         before(() => {
                             fs.writeFileSync(inputPath, fileContent);
                             fs.writeFileSync(inputPath, fileContent);
-                            testFunc = () => new SourceCodeReader(
+                            testFunc = () => new SourceCodeFileUtils(
                                 inputPath,
                                 inputPath,
                                 {
                                 {
                                     exclude: [tmpFileName]
                                     exclude: [tmpFileName]
@@ -168,7 +168,7 @@ describe('SourceCodeReader', () => {
 
 
                         before(() => {
                         before(() => {
                             fs.writeFileSync(inputPath, fileContent);
                             fs.writeFileSync(inputPath, fileContent);
-                            testFunc = () => new SourceCodeReader(
+                            testFunc = () => new SourceCodeFileUtils(
                                 inputPath,
                                 inputPath,
                                 {
                                 {
                                     exclude: [inputPath]
                                     exclude: [inputPath]
@@ -217,7 +217,7 @@ describe('SourceCodeReader', () => {
                     fs.writeFileSync(filePath2, fileContent);
                     fs.writeFileSync(filePath2, fileContent);
                     fs.writeFileSync(filePath3, fileContent);
                     fs.writeFileSync(filePath3, fileContent);
                     fs.writeFileSync(filePath4, fileContent);
                     fs.writeFileSync(filePath4, fileContent);
-                    result = new SourceCodeReader(tmpDirectoryPath, {}).readSourceCode();
+                    result = new SourceCodeFileUtils(tmpDirectoryPath, {}).readSourceCode();
                 });
                 });
 
 
                 it('should return files data', () => {
                 it('should return files data', () => {
@@ -238,7 +238,7 @@ describe('SourceCodeReader', () => {
                 let testFunc: () => void;
                 let testFunc: () => void;
 
 
                 before(() => {
                 before(() => {
-                    testFunc = () => new SourceCodeReader(inputPath, {}).readSourceCode();
+                    testFunc = () => new SourceCodeFileUtils(inputPath, {}).readSourceCode();
                 });
                 });
 
 
                 it('should throw an error if `inputPath` is not a valid path', () => {
                 it('should throw an error if `inputPath` is not a valid path', () => {
@@ -288,7 +288,7 @@ describe('SourceCodeReader', () => {
                     fs.writeFileSync(filePath2, fileContent);
                     fs.writeFileSync(filePath2, fileContent);
                     fs.writeFileSync(filePath3, fileContent);
                     fs.writeFileSync(filePath3, fileContent);
                     fs.writeFileSync(filePath4, fileContent);
                     fs.writeFileSync(filePath4, fileContent);
-                    result = new SourceCodeReader(tmpDirectoryPath, {}).readSourceCode();
+                    result = new SourceCodeFileUtils(tmpDirectoryPath, {}).readSourceCode();
                 });
                 });
 
 
                 it('should return files data', () => {
                 it('should return files data', () => {
@@ -334,7 +334,7 @@ describe('SourceCodeReader', () => {
                         fs.writeFileSync(filePath2, fileContent);
                         fs.writeFileSync(filePath2, fileContent);
                         fs.writeFileSync(filePath3, fileContent);
                         fs.writeFileSync(filePath3, fileContent);
                         fs.writeFileSync(filePath4, fileContent);
                         fs.writeFileSync(filePath4, fileContent);
-                        result = new SourceCodeReader(
+                        result = new SourceCodeFileUtils(
                             tmpDirectoryPath,
                             tmpDirectoryPath,
                             {
                             {
                                 exclude: ['**/hawk.js']
                                 exclude: ['**/hawk.js']
@@ -383,7 +383,7 @@ describe('SourceCodeReader', () => {
                             fs.writeFileSync(filePath2, fileContent);
                             fs.writeFileSync(filePath2, fileContent);
                             fs.writeFileSync(filePath3, fileContent);
                             fs.writeFileSync(filePath3, fileContent);
                             fs.writeFileSync(filePath4, fileContent);
                             fs.writeFileSync(filePath4, fileContent);
-                            result = new SourceCodeReader(
+                            result = new SourceCodeFileUtils(
                                 tmpDirectoryPath,
                                 tmpDirectoryPath,
                                 {
                                 {
                                     exclude: [
                                     exclude: [
@@ -434,7 +434,7 @@ describe('SourceCodeReader', () => {
                             fs.writeFileSync(filePath2, fileContent);
                             fs.writeFileSync(filePath2, fileContent);
                             fs.writeFileSync(filePath3, fileContent);
                             fs.writeFileSync(filePath3, fileContent);
                             fs.writeFileSync(filePath4, fileContent);
                             fs.writeFileSync(filePath4, fileContent);
-                            result = new SourceCodeReader(
+                            result = new SourceCodeFileUtils(
                                 tmpDirectoryPath,
                                 tmpDirectoryPath,
                                 {
                                 {
                                     exclude: [
                                     exclude: [
@@ -485,7 +485,7 @@ describe('SourceCodeReader', () => {
                             fs.writeFileSync(filePath2, fileContent);
                             fs.writeFileSync(filePath2, fileContent);
                             fs.writeFileSync(filePath3, fileContent);
                             fs.writeFileSync(filePath3, fileContent);
                             fs.writeFileSync(filePath4, fileContent);
                             fs.writeFileSync(filePath4, fileContent);
-                            result = new SourceCodeReader(
+                            result = new SourceCodeFileUtils(
                                 tmpDirectoryPath,
                                 tmpDirectoryPath,
                                 {
                                 {
                                     exclude: [
                                     exclude: [
@@ -525,7 +525,7 @@ describe('SourceCodeReader', () => {
                             fs.writeFileSync(filePath2, fileContent);
                             fs.writeFileSync(filePath2, fileContent);
                             fs.writeFileSync(filePath3, fileContent);
                             fs.writeFileSync(filePath3, fileContent);
                             fs.writeFileSync(filePath4, fileContent);
                             fs.writeFileSync(filePath4, fileContent);
-                            testFunc = () => new SourceCodeReader(
+                            testFunc = () => new SourceCodeFileUtils(
                                 tmpDirectoryPath,
                                 tmpDirectoryPath,
                                 {
                                 {
                                     exclude: [tmpDirectoryPath]
                                     exclude: [tmpDirectoryPath]
@@ -564,7 +564,7 @@ describe('SourceCodeReader', () => {
                 before(() => {
                 before(() => {
                     mkdirp.sync(tmpDirectoryWithDotPath);
                     mkdirp.sync(tmpDirectoryWithDotPath);
                     fs.writeFileSync(filePath, fileContent);
                     fs.writeFileSync(filePath, fileContent);
-                    result = new SourceCodeReader(tmpDirectoryWithDotPath, {}).readSourceCode();
+                    result = new SourceCodeFileUtils(tmpDirectoryWithDotPath, {}).readSourceCode();
                 });
                 });
 
 
                 it('should return files data', () => {
                 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 { IInversifyContainerFacade } from '../../../src/interfaces/container/IInversifyContainerFacade';
 import { IJavaScriptObfuscator } from '../../../src/interfaces/IJavaScriptObfsucator';
 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';
 import { NO_ADDITIONAL_NODES_PRESET } from '../../../src/options/presets/NoCustomNodes';
 
 
@@ -40,10 +40,10 @@ describe('JavaScriptObfuscator', () => {
                         .get<IJavaScriptObfuscator>(ServiceIdentifiers.IJavaScriptObfuscator);
                         .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', () => {
                 it('should link obfuscated code with source map', () => {
@@ -79,10 +79,10 @@ describe('JavaScriptObfuscator', () => {
                         .get<IJavaScriptObfuscator>(ServiceIdentifiers.IJavaScriptObfuscator);
                         .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', () => {
                 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 { TTypeFromEnum } from '../../../src/types/utils/TTypeFromEnum';
 
 
 import { IInversifyContainerFacade } from '../../../src/interfaces/container/IInversifyContainerFacade';
 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';
 import { SourceMapMode } from '../../../src/enums/source-map/SourceMapMode';
 
 
@@ -20,13 +20,13 @@ import { InversifyContainerFacade } from '../../../src/container/InversifyContai
  * @param sourceMapFileName
  * @param sourceMapFileName
  * @param sourceMapMode
  * @param sourceMapMode
  */
  */
-function getObfuscatedCode (
+function getObfuscationResult (
     rawObfuscatedCode: string,
     rawObfuscatedCode: string,
     sourceMap: string,
     sourceMap: string,
     sourceMapBaseUrl: string,
     sourceMapBaseUrl: string,
     sourceMapFileName: string,
     sourceMapFileName: string,
     sourceMapMode: TTypeFromEnum<typeof SourceMapMode>
     sourceMapMode: TTypeFromEnum<typeof SourceMapMode>
-): IObfuscatedCode {
+): IObfuscationResult {
     const inversifyContainerFacade: IInversifyContainerFacade = new InversifyContainerFacade();
     const inversifyContainerFacade: IInversifyContainerFacade = new InversifyContainerFacade();
 
 
     inversifyContainerFacade.load(
     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', () => {
 describe('ObfuscatedCode', () => {
@@ -53,10 +53,10 @@ describe('ObfuscatedCode', () => {
     const sourceMap: string = 'test';
     const sourceMap: string = 'test';
 
 
     describe('constructor', () => {
     describe('constructor', () => {
-        let obfuscatedCode: IObfuscatedCode;
+        let obfuscationResult: IObfuscationResult;
 
 
         before(() => {
         before(() => {
-            obfuscatedCode = getObfuscatedCode(
+            obfuscationResult = getObfuscationResult(
                 expectedObfuscatedCode,
                 expectedObfuscatedCode,
                 sourceMap,
                 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', () => {
         describe('source map doest\'t exist', () => {
             before(() => {
             before(() => {
-                obfuscatedCode = getObfuscatedCode(
+                obfuscatedCode = getObfuscationResult(
                     expectedObfuscatedCode,
                     expectedObfuscatedCode,
                     '',
                     '',
                     '',
                     '',
@@ -93,7 +93,7 @@ describe('ObfuscatedCode', () => {
             const regExp: RegExp = /data:application\/json;base64,dGVzdA==/;
             const regExp: RegExp = /data:application\/json;base64,dGVzdA==/;
 
 
             before(() => {
             before(() => {
-                obfuscatedCode = getObfuscatedCode(
+                obfuscatedCode = getObfuscationResult(
                     expectedObfuscatedCode,
                     expectedObfuscatedCode,
                     sourceMap,
                     sourceMap,
                     '',
                     '',
@@ -111,7 +111,7 @@ describe('ObfuscatedCode', () => {
             const regExp: RegExp = /sourceMappingURL=http:\/\/example\.com\/output\.js\.map/;
             const regExp: RegExp = /sourceMappingURL=http:\/\/example\.com\/output\.js\.map/;
 
 
             before(() => {
             before(() => {
-                obfuscatedCode = getObfuscatedCode(
+                obfuscatedCode = getObfuscationResult(
                     expectedObfuscatedCode,
                     expectedObfuscatedCode,
                     sourceMap,
                     sourceMap,
                     'http://example.com',
                     'http://example.com',
@@ -127,7 +127,7 @@ describe('ObfuscatedCode', () => {
 
 
         describe('source map mode is `separate`, `sourceMapUrl` is not set', () => {
         describe('source map mode is `separate`, `sourceMapUrl` is not set', () => {
             before(() => {
             before(() => {
-                obfuscatedCode = getObfuscatedCode(
+                obfuscatedCode = getObfuscationResult(
                     expectedObfuscatedCode,
                     expectedObfuscatedCode,
                     sourceMap,
                     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 { ServiceIdentifiers } from '../../../src/container/ServiceIdentifiers';
 
 
+import { TDictionary } from '../../../src/types/TDictionary';
+
 import { IInversifyContainerFacade } from '../../../src/interfaces/container/IInversifyContainerFacade';
 import { IInversifyContainerFacade } from '../../../src/interfaces/container/IInversifyContainerFacade';
 import { IMapStorage } from '../../../src/interfaces/storages/IMapStorage';
 import { IMapStorage } from '../../../src/interfaces/storages/IMapStorage';
 import { IOptions } from '../../../src/interfaces/options/IOptions';
 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('has', () => {
         describe('Variant #1: item is presenting in storage', () => {
         describe('Variant #1: item is presenting in storage', () => {
             const expectedItemExistence: boolean = true;
             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,
     "emitDecoratorMetadata": true,
     "experimentalDecorators": true,
     "experimentalDecorators": true,
     "lib": [
     "lib": [
-      "es2017",
+      "es2019",
       "dom"
       "dom"
     ],
     ],
-    "target": "es2017",
+    "target": "es2018",
     "module": "commonjs",
     "module": "commonjs",
     "resolveJsonModule": true,
     "resolveJsonModule": true,
     "esModuleInterop": 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"
   resolved "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.2.tgz"
   integrity sha512-HyYEUDeIj5rRQU2Hk5HTB2uHsbRQpF70nvMhVzi+VJR0X+xNEhjPui4/kBf3VeH/wqD28PT4sVOm8qqLjBrSZg==
   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:
   dependencies:
     comment-parser "^1.1.5"
     comment-parser "^1.1.5"
     esquery "^1.4.0"
     esquery "^1.4.0"
-    jsdoctypeparser "^9.0.0"
+    jsdoc-type-pratt-parser "1.0.0-alpha.23"
 
 
 "@eslint/eslintrc@^0.4.1":
 "@eslint/eslintrc@^0.4.1":
   version "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"
   resolved "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.1.tgz"
   integrity sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng==
   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:
 acorn@^7.4.0:
   version "7.4.0"
   version "7.4.0"
@@ -1855,10 +1855,10 @@ eslint-module-utils@^2.6.1:
     debug "^3.2.7"
     debug "^3.2.7"
     pkg-dir "^2.0.0"
     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:
   dependencies:
     array-includes "^3.1.3"
     array-includes "^3.1.3"
     array.prototype.flat "^1.2.4"
     array.prototype.flat "^1.2.4"
@@ -1876,18 +1876,18 @@ [email protected]:
     resolve "^1.20.0"
     resolve "^1.20.0"
     tsconfig-paths "^3.9.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:
   dependencies:
-    "@es-joy/jsdoccomment" "^0.7.2"
+    "@es-joy/jsdoccomment" "^0.8.0-alpha.2"
     comment-parser "1.1.5"
     comment-parser "1.1.5"
     debug "^4.3.1"
     debug "^4.3.1"
     esquery "^1.4.0"
     esquery "^1.4.0"
-    jsdoctypeparser "^9.0.0"
+    jsdoc-type-pratt-parser "^1.0.0"
     lodash "^4.17.21"
     lodash "^4.17.21"
-    regextras "^0.7.1"
+    regextras "^0.8.0"
     semver "^7.3.5"
     semver "^7.3.5"
     spdx-expression-parse "^3.0.1"
     spdx-expression-parse "^3.0.1"
 
 
@@ -2871,10 +2871,15 @@ js-yaml@^3.13.1:
     argparse "^1.0.7"
     argparse "^1.0.7"
     esprima "^4.0.0"
     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:
 jsesc@^2.5.1:
   version "2.5.2"
   version "2.5.2"
@@ -3774,10 +3779,10 @@ regexpp@^3.1.0:
   resolved "https://registry.npmjs.org/regexpp/-/regexpp-3.1.0.tgz"
   resolved "https://registry.npmjs.org/regexpp/-/regexpp-3.1.0.tgz"
   integrity sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q==
   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:
 release-zalgo@^1.0.0:
   version "1.0.0"
   version "1.0.0"

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