浏览代码

Sync with master

sanex 4 年之前
父节点
当前提交
6d70a5abcf
共有 100 个文件被更改,包括 1959 次插入629 次删除
  1. 7 1
      CHANGELOG.md
  2. 84 1
      README.md
  3. 0 0
      dist/index.browser.js
  4. 304 167
      dist/index.cli.js
  5. 237 206
      dist/index.js
  6. 3 3
      index.d.ts
  7. 17 17
      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 1
      src/custom-code-helpers/calls-controller/CallsControllerFunctionCodeHelper.ts
  19. 1 1
      src/custom-code-helpers/console-output/ConsoleOutputDisableCodeHelper.ts
  20. 1 1
      src/custom-code-helpers/debug-protection/DebugProtectionFunctionCallCodeHelper.ts
  21. 1 1
      src/custom-code-helpers/debug-protection/DebugProtectionFunctionCodeHelper.ts
  22. 1 1
      src/custom-code-helpers/debug-protection/DebugProtectionFunctionIntervalCodeHelper.ts
  23. 1 1
      src/custom-code-helpers/domain-lock/DomainLockCodeHelper.ts
  24. 1 1
      src/custom-code-helpers/self-defending/SelfDefendingUnicodeCodeHelper.ts
  25. 1 1
      src/custom-code-helpers/string-array/StringArrayCallsWrapperBase64CodeHelper.ts
  26. 1 1
      src/custom-code-helpers/string-array/StringArrayCallsWrapperCodeHelper.ts
  27. 1 1
      src/custom-code-helpers/string-array/StringArrayCallsWrapperRc4CodeHelper.ts
  28. 1 1
      src/custom-code-helpers/string-array/StringArrayCodeHelper.ts
  29. 1 1
      src/custom-code-helpers/string-array/StringArrayRotateFunctionCodeHelper.ts
  30. 12 10
      src/custom-nodes/control-flow-flattening-nodes/CallExpressionFunctionNode.ts
  31. 1 1
      src/custom-nodes/dead-code-injection-nodes/BlockStatementDeadCodeInjectionNode.ts
  32. 1 0
      src/enums/node-transformers/NodeTransformer.ts
  33. 1 1
      src/generators/identifier-names-generators/MangledIdentifierNamesGenerator.ts
  34. 2 2
      src/generators/identifier-names-generators/MangledShuffledIdentifierNamesGenerator.ts
  35. 3 3
      src/interfaces/IJavaScriptObfsucator.ts
  36. 9 0
      src/interfaces/node-transformers/rename-identifiers-transformers/replacer/IThroughIdentifierReplacer.ts
  37. 1 0
      src/interfaces/node/IScopeThroughIdentifiersTraverserCallbackData.ts
  38. 1 0
      src/interfaces/options/ICLIOptions.ts
  39. 2 0
      src/interfaces/options/IOptions.ts
  40. 0 13
      src/interfaces/source-code/IObfuscatedCode.ts
  41. 19 0
      src/interfaces/source-code/IObfuscationResult.ts
  42. 7 0
      src/interfaces/storages/IMapStorage.ts
  43. 4 0
      src/interfaces/storages/identifier-names-cache/IGlobalIdentifierNamesCacheStorage.ts
  44. 4 0
      src/interfaces/storages/identifier-names-cache/IPropertyIdentifierNamesCacheStorage.ts
  45. 1 1
      src/node-transformers/converting-transformers/NumberLiteralTransformer.ts
  46. 1 1
      src/node-transformers/converting-transformers/SplitStringTransformer.ts
  47. 141 0
      src/node-transformers/dead-code-injection-transformers/DeadCodeInjectionIdentifiersTransformer.ts
  48. 2 2
      src/node-transformers/dead-code-injection-transformers/DeadCodeInjectionTransformer.ts
  49. 1 1
      src/node-transformers/finalizing-transformers/DirectivePlacementTransformer.ts
  50. 1 1
      src/node-transformers/finalizing-transformers/EscapeSequenceTransformer.ts
  51. 2 2
      src/node-transformers/preparing-transformers/CustomCodeHelpersTransformer.ts
  52. 2 2
      src/node-transformers/preparing-transformers/EvalCallExpressionTransformer.ts
  53. 2 2
      src/node-transformers/preparing-transformers/MetadataTransformer.ts
  54. 2 2
      src/node-transformers/preparing-transformers/ObfuscatingGuardsTransformer.ts
  55. 1 1
      src/node-transformers/preparing-transformers/VariablePreserveTransformer.ts
  56. 22 39
      src/node-transformers/rename-identifiers-transformers/ScopeThroughIdentifiersTransformer.ts
  57. 17 2
      src/node-transformers/rename-identifiers-transformers/replacer/IdentifierReplacer.ts
  58. 64 0
      src/node-transformers/rename-identifiers-transformers/through-replacer/ThroughIdentifierReplacer.ts
  59. 22 2
      src/node-transformers/rename-properties-transformers/replacer/RenamePropertiesReplacer.ts
  60. 1 0
      src/node-transformers/rename-properties-transformers/replacer/ReservedDomProperties.json
  61. 2 2
      src/node-transformers/simplifying-transformers/AbstractStatementSimplifyTransformer.ts
  62. 1 1
      src/node-transformers/simplifying-transformers/BlockStatementSimplifyTransformer.ts
  63. 1 1
      src/node-transformers/simplifying-transformers/IfStatementSimplifyTransformer.ts
  64. 1 1
      src/node-transformers/string-array-transformers/StringArrayScopeCallsWrapperTransformer.ts
  65. 1 1
      src/node-transformers/string-array-transformers/StringArrayTransformer.ts
  66. 2 0
      src/node/ScopeIdentifiersTraverser.ts
  67. 8 0
      src/options/Options.ts
  68. 2 0
      src/options/OptionsNormalizer.ts
  69. 32 0
      src/options/normalizer-rules/IdentifierNamesCacheRule.ts
  70. 1 0
      src/options/presets/Default.ts
  71. 87 0
      src/options/validators/IsIdentifierNamesCache.ts
  72. 43 3
      src/source-code/ObfuscationResult.ts
  73. 8 0
      src/storages/MapStorage.ts
  74. 1 1
      src/storages/custom-code-helpers/CustomCodeHelperGroupStorage.ts
  75. 29 0
      src/storages/identifier-names-cache/GlobalIdentifierNamesCacheStorage.ts
  76. 29 0
      src/storages/identifier-names-cache/PropertyIdentifierNamesCacheStorage.ts
  77. 3 3
      src/storages/string-array-transformers/StringArrayStorage.ts
  78. 7 0
      src/types/TIdentifierNamesCache.ts
  79. 3 0
      src/types/TIdentifierNamesCacheDictionary.ts
  80. 2 2
      src/types/TObfuscationResultsObject.ts
  81. 0 3
      src/types/container/source-code/TObfuscatedCodeFactory.ts
  82. 3 0
      src/types/container/source-code/TObfuscationResultFactory.ts
  83. 2 2
      src/utils/CryptUtilsStringArray.ts
  84. 1 1
      test/fixtures/compile-performance.js
  85. 7 7
      test/functional-tests/custom-code-helpers/domain-lock/templates/DomainLockNodeTemplate.spec.ts
  86. 7 7
      test/functional-tests/custom-code-helpers/string-array/templates/string-array-calls-wrapper-node-template/StringArrayCallsWrapperTemplate.spec.ts
  87. 11 11
      test/functional-tests/custom-code-helpers/string-array/templates/string-array-rotate-function-template/StringArrayRotateFunctionTemplate.spec.ts
  88. 7 7
      test/functional-tests/custom-code-helpers/string-array/templates/string-array-template/StringArrayTemplate.spec.ts
  89. 141 15
      test/functional-tests/javascript-obfuscator/JavaScriptObfuscator.spec.ts
  90. 9 0
      test/functional-tests/javascript-obfuscator/fixtures/identifier-names-cache-1.js
  91. 9 0
      test/functional-tests/javascript-obfuscator/fixtures/identifier-names-cache-2.js
  92. 67 3
      test/functional-tests/node-transformers/control-flow-transformers/control-flow-replacers/call-expression-control-flow-replacer/CallExpressionControlFlowReplacer.spec.ts
  93. 0 0
      test/functional-tests/node-transformers/control-flow-transformers/control-flow-replacers/call-expression-control-flow-replacer/fixtures/rest-as-last-call-argument.js
  94. 7 0
      test/functional-tests/node-transformers/control-flow-transformers/control-flow-replacers/call-expression-control-flow-replacer/fixtures/rest-as-middle-call-argument.js
  95. 6 0
      test/functional-tests/node-transformers/control-flow-transformers/control-flow-replacers/call-expression-control-flow-replacer/fixtures/rest-as-start-call-argument.js
  96. 98 0
      test/functional-tests/node-transformers/rename-identifiers-transformers/scope-through-identifiers-transformer/class-declaration/ClassDeclaration.spec.ts
  97. 3 0
      test/functional-tests/node-transformers/rename-identifiers-transformers/scope-through-identifiers-transformer/class-declaration/fixtures/class-call-with-declaration.js
  98. 1 0
      test/functional-tests/node-transformers/rename-identifiers-transformers/scope-through-identifiers-transformer/class-declaration/fixtures/class-call-without-declaration.js
  99. 98 0
      test/functional-tests/node-transformers/rename-identifiers-transformers/scope-through-identifiers-transformer/function-declaration/FunctionDeclaration.spec.ts
  100. 3 0
      test/functional-tests/node-transformers/rename-identifiers-transformers/scope-through-identifiers-transformer/function-declaration/fixtures/function-call-with-declaration.js

+ 7 - 1
CHANGELOG.md

@@ -1,11 +1,17 @@
 Change Log
 
-v2.14.0
+v2.15.0
 ---
 * Added support of `es2022` features: private identifiers and class properties
 
+v2.14.0
+---
+* Added `identifierNamesCache` option for reading and writing identifier names cache. See `README.md`.
+* **CLI**:  Added `--identifier-names-cache-path` option for reading and writing identifier names cache. See `README.md`.
+
 v2.13.0
 ---
+* Fixed invalid code generation for start/middle rest arguments when `controlFlowFlattenig` option is enabled. Fixed https://github.com/javascript-obfuscator/javascript-obfuscator/issues/920
 * **Internal**: Added support of `node@16` and dropped support of `node@10`. This should not affect obfuscated code
 
 v2.12.0

+ 84 - 1
README.md

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

文件差异内容过多而无法显示
+ 0 - 0
dist/index.browser.js


文件差异内容过多而无法显示
+ 304 - 167
dist/index.cli.js


文件差异内容过多而无法显示
+ 237 - 206
dist/index.js


+ 3 - 3
index.d.ts

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

+ 17 - 17
package.json

@@ -1,6 +1,6 @@
 {
   "name": "javascript-obfuscator",
-  "version": "2.14.0",
+  "version": "2.15.0",
   "description": "JavaScript obfuscator",
   "keywords": [
     "obfuscator",
@@ -24,7 +24,7 @@
     "@javascript-obfuscator/escodegen": "2.2.0",
     "@javascript-obfuscator/estraverse": "5.3.0",
     "@nuxtjs/opencollective": "0.3.2",
-    "acorn": "8.2.4",
+    "acorn": "8.3.0",
     "assert": "2.0.0",
     "chalk": "4.1.1",
     "chance": "1.1.7",
@@ -47,7 +47,7 @@
   "devDependencies": {
     "@istanbuljs/nyc-config-typescript": "1.0.1",
     "@types/chai": "4.2.18",
-    "@types/chance": "1.1.1",
+    "@types/chance": "1.1.2",
     "@types/escodegen": "0.0.6",
     "@types/eslint-scope": "3.7.0",
     "@types/estraverse": "5.1.0",
@@ -57,35 +57,35 @@
     "@types/mkdirp": "1.0.1",
     "@types/mocha": "8.2.2",
     "@types/multimatch": "4.0.0",
-    "@types/node": "15.0.2",
+    "@types/node": "15.6.1",
     "@types/rimraf": "3.0.0",
-    "@types/sinon": "10.0.0",
+    "@types/sinon": "10.0.1",
     "@types/string-template": "1.0.2",
     "@types/webpack-env": "1.16.0",
-    "@typescript-eslint/eslint-plugin": "4.22.1",
-    "@typescript-eslint/parser": "4.22.1",
+    "@typescript-eslint/eslint-plugin": "4.25.0",
+    "@typescript-eslint/parser": "4.25.0",
     "chai": "4.3.4",
     "chai-exclude": "2.0.3",
     "cross-env": "7.0.3",
-    "eslint": "7.26.0",
-    "eslint-plugin-import": "2.22.1",
-    "eslint-plugin-jsdoc": "33.1.0",
+    "eslint": "7.27.0",
+    "eslint-plugin-import": "2.23.4",
+    "eslint-plugin-jsdoc": "35.1.0",
     "eslint-plugin-no-null": "1.0.2",
     "eslint-plugin-prefer-arrow": "1.2.3",
-    "eslint-plugin-unicorn": "31.0.0",
+    "eslint-plugin-unicorn": "32.0.1",
     "fork-ts-checker-notifier-webpack-plugin": "4.0.0",
-    "fork-ts-checker-webpack-plugin": "6.2.6",
+    "fork-ts-checker-webpack-plugin": "6.2.10",
     "mocha": "8.4.0",
     "nyc": "15.1.0",
     "pjson": "1.0.9",
     "pre-commit": "1.2.2",
     "rimraf": "3.0.2",
-    "sinon": "10.0.0",
+    "sinon": "11.1.1",
     "threads": "1.6.4",
-    "ts-loader": "9.1.2",
-    "ts-node": "9.1.1",
-    "typescript": "beta",
-    "webpack": "5.36.2",
+    "ts-loader": "9.2.2",
+    "ts-node": "10.0.0",
+    "typescript": "4.3.2",
+    "webpack": "5.38.1",
     "webpack-cli": "4.7.0",
     "webpack-node-externals": "3.0.0"
   },

+ 14 - 13
src/JavaScriptObfuscator.ts

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

+ 5 - 5
src/JavaScriptObfuscatorFacade.ts

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

+ 33 - 18
src/cli/JavaScriptObfuscatorCLI.ts

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

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

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

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

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

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

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

+ 11 - 11
src/container/InversifyContainerFacade.ts

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

+ 5 - 2
src/container/ServiceIdentifiers.ts

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

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

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

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

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

+ 1 - 1
src/custom-code-helpers/calls-controller/CallsControllerFunctionCodeHelper.ts

@@ -76,7 +76,7 @@ export class CallsControllerFunctionCodeHelper extends AbstractCustomCodeHelper
     /**
      * @returns {string}
      */
-    protected getCodeHelperTemplate (): string {
+    protected override getCodeHelperTemplate (): string {
         if (this.nodeTransformationStage === NodeTransformationStage.Finalizing) {
             return this.customCodeHelperObfuscator.obfuscateTemplate(
                 this.customCodeHelperFormatter.formatTemplate(SingleCallControllerTemplate(), {

+ 1 - 1
src/custom-code-helpers/console-output/ConsoleOutputDisableCodeHelper.ts

@@ -77,7 +77,7 @@ export class ConsoleOutputDisableCodeHelper extends AbstractCustomCodeHelper {
     /**
      * @returns {string}
      */
-    protected getCodeHelperTemplate (): string {
+    protected override getCodeHelperTemplate (): string {
         const globalVariableTemplate: string = this.options.target !== ObfuscationTarget.BrowserNoEval
             ? this.getGlobalVariableTemplate()
             : GlobalVariableNoEvalTemplate();

+ 1 - 1
src/custom-code-helpers/debug-protection/DebugProtectionFunctionCallCodeHelper.ts

@@ -74,7 +74,7 @@ export class DebugProtectionFunctionCallCodeHelper extends AbstractCustomCodeHel
     /**
      * @returns {string}
      */
-    protected getCodeHelperTemplate (): string {
+    protected override getCodeHelperTemplate (): string {
         return this.customCodeHelperFormatter.formatTemplate(DebugProtectionFunctionCallTemplate(), {
             debugProtectionFunctionName: this.debugProtectionFunctionName,
             callControllerFunctionName: this.callsControllerFunctionName

+ 1 - 1
src/custom-code-helpers/debug-protection/DebugProtectionFunctionCodeHelper.ts

@@ -70,7 +70,7 @@ export class DebugProtectionFunctionCodeHelper extends AbstractCustomCodeHelper
     /**
      * @returns {string}
      */
-    protected getCodeHelperTemplate (): string {
+    protected override getCodeHelperTemplate (): string {
         const debuggerTemplate: string = this.options.target !== ObfuscationTarget.BrowserNoEval
             ? DebuggerTemplate()
             : DebuggerTemplateNoEval();

+ 1 - 1
src/custom-code-helpers/debug-protection/DebugProtectionFunctionIntervalCodeHelper.ts

@@ -66,7 +66,7 @@ export class DebugProtectionFunctionIntervalCodeHelper extends AbstractCustomCod
     /**
      * @returns {string}
      */
-    protected getCodeHelperTemplate (): string {
+    protected override getCodeHelperTemplate (): string {
         return this.customCodeHelperFormatter.formatTemplate(DebugProtectionFunctionIntervalTemplate(), {
             debugProtectionFunctionName: this.debugProtectionFunctionName
         });

+ 1 - 1
src/custom-code-helpers/domain-lock/DomainLockCodeHelper.ts

@@ -87,7 +87,7 @@ export class DomainLockCodeHelper extends AbstractCustomCodeHelper {
     /**
      * @returns {string}
      */
-    protected getCodeHelperTemplate (): string {
+    protected override getCodeHelperTemplate (): string {
         const domainsString: string = this.options.domainLock.join(';');
         const [hiddenDomainsString, diff]: string[] = this.cryptUtils.hideString(
             domainsString,

+ 1 - 1
src/custom-code-helpers/self-defending/SelfDefendingUnicodeCodeHelper.ts

@@ -78,7 +78,7 @@ export class SelfDefendingUnicodeCodeHelper extends AbstractCustomCodeHelper {
     /**
      * @returns {string}
      */
-    protected getCodeHelperTemplate (): string {
+    protected override getCodeHelperTemplate (): string {
         const globalVariableTemplate: string = this.options.target !== ObfuscationTarget.BrowserNoEval
             ? this.getGlobalVariableTemplate()
             : GlobalVariableNoEvalTemplate();

+ 1 - 1
src/custom-code-helpers/string-array/StringArrayCallsWrapperBase64CodeHelper.ts

@@ -10,7 +10,7 @@ export class StringArrayCallsWrapperBase64CodeHelper extends StringArrayCallsWra
     /**
      * @returns {string}
      */
-    protected getDecodeStringArrayTemplate (): string {
+    protected override getDecodeStringArrayTemplate (): string {
         const atobFunctionName: string = this.randomGenerator.getRandomString(6);
 
         const atobPolyfill: string = this.customCodeHelperFormatter.formatTemplate(AtobTemplate(), {

+ 1 - 1
src/custom-code-helpers/string-array/StringArrayCallsWrapperCodeHelper.ts

@@ -97,7 +97,7 @@ export class StringArrayCallsWrapperCodeHelper extends AbstractCustomCodeHelper
     /**
      * @returns {string}
      */
-    protected getCodeHelperTemplate (): string {
+    protected override getCodeHelperTemplate (): string {
         const decodeCodeHelperTemplate: string = this.getDecodeStringArrayTemplate();
 
         const preservedNames: string[] = [`^${this.stringArrayName}$`];

+ 1 - 1
src/custom-code-helpers/string-array/StringArrayCallsWrapperRc4CodeHelper.ts

@@ -11,7 +11,7 @@ export class StringArrayCallsWrapperRc4CodeHelper extends StringArrayCallsWrappe
     /**
      * @returns {string}
      */
-    protected getDecodeStringArrayTemplate (): string {
+    protected override getDecodeStringArrayTemplate (): string {
         const atobFunctionName: string = this.randomGenerator.getRandomString(6);
 
         const atobPolyfill: string = this.customCodeHelperFormatter.formatTemplate(AtobTemplate(), {

+ 1 - 1
src/custom-code-helpers/string-array/StringArrayCodeHelper.ts

@@ -80,7 +80,7 @@ export class StringArrayCodeHelper extends AbstractCustomCodeHelper {
     /**
      * @returns {string}
      */
-    protected getCodeHelperTemplate (): string {
+    protected override getCodeHelperTemplate (): string {
         return this.customCodeHelperFormatter.formatTemplate(StringArrayTemplate(), {
             stringArrayName: this.stringArrayName,
             stringArrayStorageItems: this.getEncodedStringArrayStorageItems()

+ 1 - 1
src/custom-code-helpers/string-array/StringArrayRotateFunctionCodeHelper.ts

@@ -87,7 +87,7 @@ export class StringArrayRotateFunctionCodeHelper extends AbstractCustomCodeHelpe
     /**
      * @returns {string}
      */
-    protected getCodeHelperTemplate (): string {
+    protected override getCodeHelperTemplate (): string {
         const comparisonExpressionCode: string = NodeUtils.convertStructureToCode([this.comparisonExpressionNode]);
 
         return this.customCodeHelperFormatter.formatTemplate(

+ 12 - 10
src/custom-nodes/control-flow-flattening-nodes/CallExpressionFunctionNode.ts

@@ -68,16 +68,18 @@ export class CallExpressionFunctionNode extends AbstractCustomNode {
 
             const baseIdentifierNode: ESTree.Identifier = NodeFactory.identifierNode(`param${i + 1}`);
 
-            params.push(
-                isSpreadCallArgument
-                    ? NodeFactory.restElementNode(baseIdentifierNode)
-                    : baseIdentifierNode
-            );
-            callArguments.push(
-                isSpreadCallArgument
-                    ? NodeFactory.spreadElementNode(baseIdentifierNode)
-                    : baseIdentifierNode
-            );
+            if (isSpreadCallArgument) {
+                params.push(NodeFactory.restElementNode(baseIdentifierNode));
+                callArguments.push(NodeFactory.spreadElementNode(baseIdentifierNode));
+
+                const isMiddleSpreadCallArgument: boolean = i < argumentsLength - 1;
+                if (isMiddleSpreadCallArgument) {
+                    break;
+                }
+            } else {
+                params.push(baseIdentifierNode);
+                callArguments.push(baseIdentifierNode);
+            }
         }
 
         const structure: TStatement = NodeFactory.expressionStatementNode(

+ 1 - 1
src/custom-nodes/dead-code-injection-nodes/BlockStatementDeadCodeInjectionNode.ts

@@ -64,7 +64,7 @@ export class BlockStatementDeadCodeInjectionNode extends AbstractCustomNode {
      *
      * @returns {TStatement[]}
      */
-    public getNode (): TStatement[] {
+    public override getNode (): TStatement[] {
         return this.getNodeStructure();
     }
 

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

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

+ 1 - 1
src/generators/identifier-names-generators/MangledIdentifierNamesGenerator.ts

@@ -165,7 +165,7 @@ export class MangledIdentifierNamesGenerator extends AbstractIdentifierNamesGene
      * @param {string} mangledName
      * @returns {boolean}
      */
-    public isValidIdentifierName (mangledName: string): boolean {
+    public override isValidIdentifierName (mangledName: string): boolean {
         return super.isValidIdentifierName(mangledName)
             && !MangledIdentifierNamesGenerator.reservedNamesSet.has(mangledName);
     }

+ 2 - 2
src/generators/identifier-names-generators/MangledShuffledIdentifierNamesGenerator.ts

@@ -58,7 +58,7 @@ export class MangledShuffledIdentifierNamesGenerator extends MangledIdentifierNa
     /**
      * @returns {string[]}
      */
-    protected getNameSequence (): string[] {
+    protected override getNameSequence (): string[] {
         return MangledShuffledIdentifierNamesGenerator.shuffledNameSequence;
     }
 
@@ -66,7 +66,7 @@ export class MangledShuffledIdentifierNamesGenerator extends MangledIdentifierNa
      * @param {string} previousMangledName
      * @returns {string}
      */
-    protected generateNewMangledName (previousMangledName: string): string {
+    protected override generateNewMangledName (previousMangledName: string): string {
         return super.generateNewMangledName(previousMangledName);
     }
 }

+ 3 - 3
src/interfaces/IJavaScriptObfsucator.ts

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

+ 1 - 1
src/node-transformers/converting-transformers/NumberLiteralTransformer.ts

@@ -22,7 +22,7 @@ export class NumberLiteralTransformer extends AbstractNodeTransformer {
      *
      * @type {NodeTransformer[]}
      */
-    public readonly runAfter: NodeTransformer[] = [
+    public override readonly runAfter: NodeTransformer[] = [
         NodeTransformer.NumberToNumericalExpressionTransformer
     ];
 

+ 1 - 1
src/node-transformers/converting-transformers/SplitStringTransformer.ts

@@ -31,7 +31,7 @@ export class SplitStringTransformer extends AbstractNodeTransformer {
     /**
      * @type {NodeTransformer[]}
      */
-    public runAfter: NodeTransformer[] = [
+    public override runAfter: NodeTransformer[] = [
         NodeTransformer.ObjectExpressionKeysTransformer,
         NodeTransformer.TemplateLiteralTransformer
     ];

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

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

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

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

+ 1 - 1
src/node-transformers/finalizing-transformers/DirectivePlacementTransformer.ts

@@ -28,7 +28,7 @@ export class DirectivePlacementTransformer extends AbstractNodeTransformer {
     /**
      * @type {NodeTransformer[]}
      */
-    public readonly runAfter: NodeTransformer[] = [
+    public override readonly runAfter: NodeTransformer[] = [
         NodeTransformer.CustomCodeHelpersTransformer
     ];
 

+ 1 - 1
src/node-transformers/finalizing-transformers/EscapeSequenceTransformer.ts

@@ -22,7 +22,7 @@ export class EscapeSequenceTransformer extends AbstractNodeTransformer {
     /**
      * @type {NodeTransformer[]}
      */
-    public readonly runAfter: NodeTransformer[] = [
+    public override readonly runAfter: NodeTransformer[] = [
         NodeTransformer.CustomCodeHelpersTransformer
     ];
 

+ 2 - 2
src/node-transformers/preparing-transformers/CustomCodeHelpersTransformer.ts

@@ -26,9 +26,9 @@ import { NodeGuards } from '../../node/NodeGuards';
 @injectable()
 export class CustomCodeHelpersTransformer extends AbstractNodeTransformer {
     /**
-     * @type {NodeTransformer.ParentificationTransformer[]}
+     * @type {NodeTransformer[]}
      */
-    public readonly runAfter: NodeTransformer[] = [
+    public override readonly runAfter: NodeTransformer[] = [
         NodeTransformer.ParentificationTransformer,
         NodeTransformer.VariablePreserveTransformer
     ];

+ 2 - 2
src/node-transformers/preparing-transformers/EvalCallExpressionTransformer.ts

@@ -19,9 +19,9 @@ import { StringUtils } from '../../utils/StringUtils';
 @injectable()
 export class EvalCallExpressionTransformer extends AbstractNodeTransformer {
     /**
-     * @type {NodeTransformer.NodeTransformer[]}
+     * @type {NodeTransformer[]}
      */
-    public readonly runAfter: NodeTransformer[] = [
+    public override readonly runAfter: NodeTransformer[] = [
         NodeTransformer.EscapeSequenceTransformer,
         NodeTransformer.ParentificationTransformer,
         NodeTransformer.VariablePreserveTransformer

+ 2 - 2
src/node-transformers/preparing-transformers/MetadataTransformer.ts

@@ -20,9 +20,9 @@ import { NodeMetadata } from '../../node/NodeMetadata';
 @injectable()
 export class MetadataTransformer extends AbstractNodeTransformer {
     /**
-     * @type {NodeTransformer.ParentificationTransformer[]}
+     * @type {NodeTransformer[]}
      */
-    public readonly runAfter: NodeTransformer[] = [
+    public override readonly runAfter: NodeTransformer[] = [
         NodeTransformer.ParentificationTransformer,
         NodeTransformer.VariablePreserveTransformer
     ];

+ 2 - 2
src/node-transformers/preparing-transformers/ObfuscatingGuardsTransformer.ts

@@ -36,9 +36,9 @@ export class ObfuscatingGuardsTransformer extends AbstractNodeTransformer {
     ];
 
     /**
-     * @type {NodeTransformer.ParentificationTransformer[]}
+     * @type {NodeTransformer[]}
      */
-    public readonly runAfter: NodeTransformer[] = [
+    public override readonly runAfter: NodeTransformer[] = [
         NodeTransformer.ParentificationTransformer,
         NodeTransformer.VariablePreserveTransformer
     ];

+ 1 - 1
src/node-transformers/preparing-transformers/VariablePreserveTransformer.ts

@@ -26,7 +26,7 @@ export class VariablePreserveTransformer extends AbstractNodeTransformer {
     /**
      * @type {NodeTransformer.ParentificationTransformer[]}
      */
-    public readonly runAfter: NodeTransformer[] = [
+    public override readonly runAfter: NodeTransformer[] = [
         NodeTransformer.ParentificationTransformer
     ];
 

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

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

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

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

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

@@ -0,0 +1,64 @@
+import { inject, injectable, } from 'inversify';
+import { ServiceIdentifiers } from '../../../container/ServiceIdentifiers';
+
+import * as ESTree from 'estree';
+
+import { IGlobalIdentifierNamesCacheStorage } from '../../../interfaces/storages/identifier-names-cache/IGlobalIdentifierNamesCacheStorage';
+import { IOptions } from '../../../interfaces/options/IOptions';
+import { IThroughIdentifierReplacer } from '../../../interfaces/node-transformers/rename-identifiers-transformers/replacer/IThroughIdentifierReplacer';
+
+import { NodeFactory } from '../../../node/NodeFactory';
+
+@injectable()
+export class ThroughIdentifierReplacer implements IThroughIdentifierReplacer {
+    /**
+     * @type {IGlobalIdentifierNamesCacheStorage}
+     */
+    private readonly identifierNamesCacheStorage: IGlobalIdentifierNamesCacheStorage;
+
+    /**
+     * @type {IOptions}
+     */
+    private readonly options: IOptions;
+
+    /**
+     * @param {IGlobalIdentifierNamesCacheStorage} identifierNamesCacheStorage
+     * @param {IOptions} options
+     */
+    public constructor (
+        @inject(ServiceIdentifiers.IGlobalIdentifierNamesCacheStorage)
+            identifierNamesCacheStorage: IGlobalIdentifierNamesCacheStorage,
+        @inject(ServiceIdentifiers.IOptions) options: IOptions
+    ) {
+        this.identifierNamesCacheStorage = identifierNamesCacheStorage;
+        this.options = options;
+    }
+
+    /**
+     * @param {Identifier} identifierNode
+     * @returns {Identifier}
+     */
+    public replace (identifierNode: ESTree.Identifier): ESTree.Identifier {
+        const identifierName: string = identifierNode.name;
+        const newIdentifierName: string = this.options.identifierNamesCache && !this.isReservedName(identifierName)
+            ? this.identifierNamesCacheStorage.get(identifierName) ?? identifierName
+            : identifierName;
+
+        return NodeFactory.identifierNode(newIdentifierName);
+    }
+
+    /**
+     * @param {string} name
+     * @returns {boolean}
+     */
+    private isReservedName (name: string): boolean {
+        if (!this.options.reservedNames.length) {
+            return false;
+        }
+
+        return this.options.reservedNames
+            .some((reservedName: string) => {
+                return new RegExp(reservedName, 'g').exec(name) !== null;
+            });
+    }
+}

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

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

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

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

+ 2 - 2
src/node-transformers/simplifying-transformers/AbstractStatementSimplifyTransformer.ts

@@ -22,7 +22,7 @@ export abstract class AbstractStatementSimplifyTransformer extends AbstractNodeT
     /**
      * @type {NodeTransformer[]}
      */
-    public readonly runAfter: NodeTransformer[] = [
+    public override readonly runAfter: NodeTransformer[] = [
         NodeTransformer.ExpressionStatementsMergeTransformer,
         NodeTransformer.VariableDeclarationsMergeTransformer
     ];
@@ -189,7 +189,7 @@ export abstract class AbstractStatementSimplifyTransformer extends AbstractNodeT
      * @param {ESTree.Node} parentNode
      * @returns {ESTree.Node}
      */
-    public abstract transformNode (
+    public abstract override transformNode (
         statementNode: ESTree.Statement,
         parentNode: ESTree.Node
     ): ESTree.Node;

+ 1 - 1
src/node-transformers/simplifying-transformers/BlockStatementSimplifyTransformer.ts

@@ -24,7 +24,7 @@ export class BlockStatementSimplifyTransformer extends AbstractStatementSimplify
     /**
      * @type {NodeTransformer[]}
      */
-    public readonly runAfter: NodeTransformer[] = [
+    public override readonly runAfter: NodeTransformer[] = [
         NodeTransformer.VariableDeclarationsMergeTransformer
     ];
 

+ 1 - 1
src/node-transformers/simplifying-transformers/IfStatementSimplifyTransformer.ts

@@ -262,7 +262,7 @@ export class IfStatementSimplifyTransformer extends AbstractStatementSimplifyTra
      * @param {IStatementSimplifyData} statementSimplifyData
      * @returns {ESTree.Statement}
      */
-    protected getPartialStatement (statementSimplifyData: IStatementSimplifyData): ESTree.Statement {
+    protected override getPartialStatement (statementSimplifyData: IStatementSimplifyData): ESTree.Statement {
         const partialStatement: ESTree.Statement = super.getPartialStatement(statementSimplifyData);
 
         if (!NodeGuards.isBlockStatementNode(partialStatement)) {

+ 1 - 1
src/node-transformers/string-array-transformers/StringArrayScopeCallsWrapperTransformer.ts

@@ -38,7 +38,7 @@ export class StringArrayScopeCallsWrapperTransformer extends AbstractNodeTransfo
     /**
      * @type {NodeTransformer[]}
      */
-    public readonly runAfter: NodeTransformer[] = [
+    public override readonly runAfter: NodeTransformer[] = [
         NodeTransformer.StringArrayRotateFunctionTransformer
     ];
 

+ 1 - 1
src/node-transformers/string-array-transformers/StringArrayTransformer.ts

@@ -53,7 +53,7 @@ export class StringArrayTransformer extends AbstractNodeTransformer {
     /**
      * @type {NodeTransformer[]}
      */
-    public readonly runAfter: NodeTransformer[] = [
+    public override readonly runAfter: NodeTransformer[] = [
         NodeTransformer.StringArrayRotateFunctionTransformer
     ];
 

+ 2 - 0
src/node/ScopeIdentifiersTraverser.ts

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

+ 8 - 0
src/options/Options.ts

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

+ 2 - 0
src/options/OptionsNormalizer.ts

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

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

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

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

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

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

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

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

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

+ 8 - 0
src/storages/MapStorage.ts

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

+ 1 - 1
src/storages/custom-code-helpers/CustomCodeHelperGroupStorage.ts

@@ -45,7 +45,7 @@ export class CustomCodeHelperGroupStorage extends MapStorage <string, ICustomCod
     }
 
     @postConstruct()
-    public initialize (): void {
+    public override initialize (): void {
         super.initialize();
 
         CustomCodeHelperGroupStorage.customCodeHelperGroupsList.forEach((customCodeHelperGroupName: CustomCodeHelperGroup) => {

+ 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 ?? {}));
+    }
+}

+ 3 - 3
src/storages/string-array-transformers/StringArrayStorage.ts

@@ -130,7 +130,7 @@ export class StringArrayStorage extends MapStorage <`${string}-${TStringArrayEnc
     }
 
     @postConstruct()
-    public initialize (): void {
+    public override initialize (): void {
         super.initialize();
 
         this.indexShiftAmount = this.options.stringArrayIndexShift
@@ -150,7 +150,7 @@ export class StringArrayStorage extends MapStorage <`${string}-${TStringArrayEnc
     /**
      * @param {string} value
      */
-    public get (value: string): IStringArrayStorageItemData {
+    public override get (value: string): IStringArrayStorageItemData {
         return this.getOrSetIfDoesNotExist(value);
     }
 
@@ -178,7 +178,7 @@ export class StringArrayStorage extends MapStorage <`${string}-${TStringArrayEnc
     /**
      * @returns {string}
      */
-    public getStorageId (): string {
+    public override getStorageId (): string {
         if (!this.stringArrayStorageName) {
             this.stringArrayStorageName = this.identifierNamesGenerator
                 .generateForGlobalScope(StringArrayStorage.stringArrayNameLength);

+ 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;

+ 2 - 2
src/utils/CryptUtilsStringArray.ts

@@ -13,7 +13,7 @@ export class CryptUtilsStringArray extends CryptUtils implements ICryptUtilsStri
     /**
      * @type {string}
      */
-    protected readonly base64Alphabet: string = base64alphabetSwapped;
+    protected override readonly base64Alphabet: string = base64alphabetSwapped;
 
     /**
      * @param {IRandomGenerator} randomGenerator
@@ -30,7 +30,7 @@ export class CryptUtilsStringArray extends CryptUtils implements ICryptUtilsStri
      * @param {string} string
      * @returns {string}
      */
-    public btoa (string: string): string {
+    public override btoa (string: string): string {
         const output = super.btoa(string);
 
         return output.replace(/=+$/, '');

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

+ 67 - 3
test/functional-tests/node-transformers/control-flow-transformers/control-flow-replacers/call-expression-control-flow-replacer/CallExpressionControlFlowReplacer.spec.ts

@@ -118,7 +118,71 @@ describe('CallExpressionControlFlowReplacer', function () {
             });
         });
 
-        describe('Variant #4 - rest call argument', () => {
+        describe('Variant #4 - rest as start call argument', () => {
+            const controlFlowStorageCallRegExp: RegExp = /_0x([a-f0-9]){4,6}\['\w{5}']\(_0x([a-f0-9]){4,6}, *\.\.\._0x([a-f0-9]){4,6}, *_0x([a-f0-9]){4,6}\);/;
+            const controlFlowStorageNodeRegExp: RegExp = new RegExp(`` +
+                `'\\w{5}' *: *function *\\(_0x([a-f0-9]){4,6}, *\.\.\._0x([a-f0-9]){4,6}\\) *\\{` +
+                    `return *_0x([a-f0-9]){4,6}\\(\.\.\._0x([a-f0-9]){4,6}\\);` +
+                `\\}` +
+            ``);
+
+            let obfuscatedCode: string;
+
+            before(() => {
+                const code: string = readFileAsString(__dirname + '/fixtures/rest-as-start-call-argument.js');
+
+                obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                    code,
+                    {
+                        ...NO_ADDITIONAL_NODES_PRESET,
+                        controlFlowFlattening: true,
+                        controlFlowFlatteningThreshold: 1
+                    }
+                ).getObfuscatedCode();
+            });
+
+            it('should replace call expression node with call to control flow storage node', () => {
+                assert.match(obfuscatedCode, controlFlowStorageCallRegExp);
+            });
+
+            it('should keep rest parameter and rest call argument, but remove all function parameters after rest parameter', () => {
+                assert.match(obfuscatedCode, controlFlowStorageNodeRegExp);
+            });
+        });
+
+        describe('Variant #5 - rest as middle call argument', () => {
+            const controlFlowStorageCallRegExp: RegExp = /_0x([a-f0-9]){4,6}\['\w{5}']\(_0x([a-f0-9]){4,6}, *_0x([a-f0-9]){4,6}, *\.\.\._0x([a-f0-9]){4,6}, *_0x([a-f0-9]){4,6}\);/;
+            const controlFlowStorageNodeRegExp: RegExp = new RegExp(`` +
+                `'\\w{5}' *: *function *\\(_0x([a-f0-9]){4,6}, *_0x([a-f0-9]){4,6}, *\.\.\._0x([a-f0-9]){4,6}\\) *\\{` +
+                    `return *_0x([a-f0-9]){4,6}\\(_0x([a-f0-9]){4,6}, *\.\.\._0x([a-f0-9]){4,6}\\);` +
+                `\\}` +
+            ``);
+
+            let obfuscatedCode: string;
+
+            before(() => {
+                const code: string = readFileAsString(__dirname + '/fixtures/rest-as-middle-call-argument.js');
+
+                obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                    code,
+                    {
+                        ...NO_ADDITIONAL_NODES_PRESET,
+                        controlFlowFlattening: true,
+                        controlFlowFlatteningThreshold: 1
+                    }
+                ).getObfuscatedCode();
+            });
+
+            it('should replace call expression node with call to control flow storage node', () => {
+                assert.match(obfuscatedCode, controlFlowStorageCallRegExp);
+            });
+
+            it('should keep rest parameter and rest call argument, but remove all function parameters after rest parameter', () => {
+                assert.match(obfuscatedCode, controlFlowStorageNodeRegExp);
+            });
+        });
+
+        describe('Variant #6 - rest as last call argument', () => {
             const controlFlowStorageCallRegExp: RegExp = /_0x([a-f0-9]){4,6}\['\w{5}']\(_0x([a-f0-9]){4,6}, *_0x([a-f0-9]){4,6}, *\.\.\._0x([a-f0-9]){4,6}\);/;
             const controlFlowStorageNodeRegExp: RegExp = new RegExp(`` +
                 `'\\w{5}' *: *function *\\(_0x([a-f0-9]){4,6}, *_0x([a-f0-9]){4,6}, *\.\.\._0x([a-f0-9]){4,6}\\) *\\{` +
@@ -129,7 +193,7 @@ describe('CallExpressionControlFlowReplacer', function () {
             let obfuscatedCode: string;
 
             before(() => {
-                const code: string = readFileAsString(__dirname + '/fixtures/rest-call-argument.js');
+                const code: string = readFileAsString(__dirname + '/fixtures/rest-as-last-call-argument.js');
 
                 obfuscatedCode = JavaScriptObfuscator.obfuscate(
                     code,
@@ -145,7 +209,7 @@ describe('CallExpressionControlFlowReplacer', function () {
                 assert.match(obfuscatedCode, controlFlowStorageCallRegExp);
             });
 
-            it('should keep spread parameter and rest call argument inside control flow storage node function', () => {
+            it('should keep rest parameter and rest call argument inside control flow storage node function', () => {
                 assert.match(obfuscatedCode, controlFlowStorageNodeRegExp);
             });
         });

+ 0 - 0
test/functional-tests/node-transformers/control-flow-transformers/control-flow-replacers/call-expression-control-flow-replacer/fixtures/rest-call-argument.js → test/functional-tests/node-transformers/control-flow-transformers/control-flow-replacers/call-expression-control-flow-replacer/fixtures/rest-as-last-call-argument.js


+ 7 - 0
test/functional-tests/node-transformers/control-flow-transformers/control-flow-replacers/call-expression-control-flow-replacer/fixtures/rest-as-middle-call-argument.js

@@ -0,0 +1,7 @@
+(function () {
+    const log = console.log;
+    const first = 'foo';
+    const rest = ['bar', 'baz', 'bark'];
+    const last = 'hawk';
+    log(first, ...rest, last);
+})();

+ 6 - 0
test/functional-tests/node-transformers/control-flow-transformers/control-flow-replacers/call-expression-control-flow-replacer/fixtures/rest-as-start-call-argument.js

@@ -0,0 +1,6 @@
+(function () {
+    const log = console.log;
+    const rest = ['foo', 'bar', 'baz'];
+    const last = 'bark';
+    log(...rest, last);
+})();

+ 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();

部分文件因为文件数量过多而无法显示