Browse Source

Merge pull request #972 from javascript-obfuscator/tab-self-defending-and-string-array

2.19.0
Timofey Kachalov 3 years ago
parent
commit
5ca1ef5e51
69 changed files with 1476 additions and 947 deletions
  1. 1 2
      .github/workflows/ci.yml
  2. 2 1
      .gitignore
  3. 4 0
      .husky/pre-commit
  4. 1 0
      .npmignore
  5. 7 0
      CHANGELOG.md
  6. 0 1
      dist/index.browser.js
  7. 0 52
      dist/index.browser.js.LICENSE.txt
  8. 0 1
      dist/index.cli.js
  9. 0 23
      dist/index.cli.js.LICENSE.txt
  10. 0 1
      dist/index.js
  11. 0 23
      dist/index.js.LICENSE.txt
  12. 0 38
      index.d.ts
  13. 39 0
      index.ts
  14. 11 9
      package.json
  15. 3 3
      src/container/modules/custom-code-helpers/CustomCodeHelpersModule.ts
  16. 3 15
      src/custom-code-helpers/self-defending/SelfDefendingCodeHelper.ts
  17. 6 6
      src/custom-code-helpers/self-defending/group/SelfDefendingCodeHelperGroup.ts
  18. 0 22
      src/custom-code-helpers/self-defending/templates/SelfDefendingNoEvalTemplate.ts
  19. 6 9
      src/custom-code-helpers/self-defending/templates/SelfDefendingTemplate.ts
  20. 6 4
      src/custom-code-helpers/string-array/StringArrayCallsWrapperBase64CodeHelper.ts
  21. 8 9
      src/custom-code-helpers/string-array/StringArrayCallsWrapperCodeHelper.ts
  22. 13 8
      src/custom-code-helpers/string-array/StringArrayCallsWrapperRc4CodeHelper.ts
  23. 8 5
      src/custom-code-helpers/string-array/StringArrayCodeHelper.ts
  24. 5 5
      src/custom-code-helpers/string-array/StringArrayRotateFunctionCodeHelper.ts
  25. 33 17
      src/custom-code-helpers/string-array/group/StringArrayCodeHelperGroup.ts
  26. 11 3
      src/custom-code-helpers/string-array/templates/string-array-calls-wrapper/AtobTemplate.ts
  27. 1 1
      src/custom-code-helpers/string-array/templates/string-array-calls-wrapper/StringArrayBase64DecodeTemplate.ts
  28. 3 1
      src/custom-code-helpers/string-array/templates/string-array-calls-wrapper/StringArrayCallsWrapperTemplate.ts
  29. 1 1
      src/custom-code-helpers/string-array/templates/string-array-calls-wrapper/StringArrayRC4DecodeTemplate.ts
  30. 6 4
      src/custom-code-helpers/string-array/templates/string-array-rotate-function/StringArrayRotateFunctionTemplate.ts
  31. 9 1
      src/custom-code-helpers/string-array/templates/string-array/StringArrayTemplate.ts
  32. 1 1
      src/enums/custom-code-helpers/CustomCodeHelper.ts
  33. 27 8
      src/node-transformers/string-array-transformers/StringArrayRotateFunctionTransformer.ts
  34. 3 3
      src/storages/string-array-transformers/StringArrayStorage.ts
  35. 13 0
      src/tsconfig.declarations.json
  36. 12 30
      test/dev/dev.ts
  37. 3 2
      test/functional-tests/code-transformers/preparing-transformers/hashbang-operator-transformer/HashbangOperatorTransformer.spec.ts
  38. 0 156
      test/functional-tests/custom-code-helpers/self-defending/templates/SelfDefendingNoEvalTemplate.spec.ts
  39. 145 31
      test/functional-tests/custom-code-helpers/self-defending/templates/SelfDefendingTemplate.spec.ts
  40. 48 6
      test/functional-tests/custom-code-helpers/string-array/StringArrayCallsWrapperCodeHelper.spec.ts
  41. 32 11
      test/functional-tests/custom-code-helpers/string-array/StringArrayCodeHelper.spec.ts
  42. 2 2
      test/functional-tests/custom-code-helpers/string-array/StringArrayRotateFunctionCodeHelper.spec.ts
  43. 36 18
      test/functional-tests/custom-code-helpers/string-array/group/StringArrayCodeHelperGroup.spec.ts
  44. 569 197
      test/functional-tests/custom-code-helpers/string-array/templates/string-array-calls-wrapper-node-template/StringArrayCallsWrapperTemplate.spec.ts
  45. 4 3
      test/functional-tests/custom-code-helpers/string-array/templates/string-array-template/StringArrayTemplate.spec.ts
  46. 74 23
      test/functional-tests/generators/identifier-names-generators/dictionary-identifier-names-generator/DictionaryIdentifierNamesGenerator.spec.ts
  47. 0 1
      test/functional-tests/generators/identifier-names-generators/dictionary-identifier-names-generator/fixtures/string-array-storage-name-conflict-2.js
  48. 25 6
      test/functional-tests/generators/identifier-names-generators/mangled-identifier-names-generator/MangledIdentifierNamesGenerator.spec.ts
  49. 23 7
      test/functional-tests/javascript-obfuscator/JavaScriptObfuscator.spec.ts
  50. 5 4
      test/functional-tests/node-transformers/converting-transformers/class-field-transformer/ClassFieldTransformer.spec.ts
  51. 3 2
      test/functional-tests/node-transformers/converting-transformers/member-expression-transformer/MemberExpressionTransformer.spec.ts
  52. 11 4
      test/functional-tests/node-transformers/finalizing-transformers/directive-placement-transformer/DirectivePlacementTransformer.spec.ts
  53. 2 1
      test/functional-tests/node-transformers/finalizing-transformers/escape-sequence-transformer/EscapeSequenceTransformer.spec.ts
  54. 5 1
      test/functional-tests/node-transformers/initializing-transformers/comments-transformer/CommentsTransformer.spec.ts
  55. 4 3
      test/functional-tests/node-transformers/preparing-transformers/eval-call-expression-transformer/EvalCallExpressionTransformer.spec.ts
  56. 3 2
      test/functional-tests/node-transformers/preparing-transformers/obfuscating-guards/black-list-obfuscating-guard/BlackListObfuscatingGuard.spec.ts
  57. 8 8
      test/functional-tests/node-transformers/preparing-transformers/obfuscating-guards/conditional-comment-obfuscating-guard/ConditionalCommentObfuscatingGuard.spec.ts
  58. 9 2
      test/functional-tests/node-transformers/preparing-transformers/variable-preserve-transformer/VariablePreserveTransformer.spec.ts
  59. 59 1
      test/functional-tests/node-transformers/string-array-transformers/string-array-rotate-function-transformer/StringArrayRotateFunctionTransformer.spec.ts
  60. 4 0
      test/functional-tests/node-transformers/string-array-transformers/string-array-rotate-function-transformer/fixtures/early-successful-comparison.js
  61. 0 11
      test/functional-tests/node-transformers/string-array-transformers/string-array-scope-calls-wrapper-transformer/StringArrayScopeCallsWrapperTransformer.spec.ts
  62. 13 7
      test/functional-tests/node-transformers/string-array-transformers/string-array-transformer/StringArrayTransformer.spec.ts
  63. 11 10
      test/functional-tests/storages/string-array-transformers/string-array-storage/StringArrayStorage.spec.ts
  64. 10 7
      test/helpers/beautifyCode.ts
  65. 28 0
      test/helpers/get-string-array-regexp.ts
  66. 13 0
      test/helpers/minimizeCode.ts
  67. 0 1
      test/index.spec.ts
  68. 5 3
      test/runtime-tests/JavaScriptObfuscatorRuntime.spec.ts
  69. 79 110
      yarn.lock

+ 1 - 2
.github/workflows/ci.yml

@@ -41,8 +41,7 @@ jobs:
           path: '**/node_modules'
           key: ${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }}
       - run: yarn install
-      - run: yarn run eslint
-      - run: yarn run test
+      - run: yarn run build
       - run: yarn run test:mocha-coverage:report
       - name: Coveralls
         uses: coverallsapp/github-action@master

+ 2 - 1
.gitignore

@@ -1,8 +1,9 @@
-.awcache
 .DS_Store
 .idea
 .nyc_output
 coverage
+/dist
+/types
 npm-debug.log
 *.js.map
 /coverage

+ 4 - 0
.husky/pre-commit

@@ -0,0 +1,4 @@
+#!/bin/sh
+. "$(dirname "$0")/_/husky.sh"
+
+npm run precommit

+ 1 - 0
.npmignore

@@ -6,4 +6,5 @@ coverage
 examples
 images
 webpack
+src
 test

+ 7 - 0
CHANGELOG.md

@@ -1,5 +1,12 @@
 Change Log
 
+v2.19.0
+---
+* Fixed very rare cases when `rotateStringArray` couldn't rotate array properly
+* Improved `selfDefending` option
+* Installed `npm` package now has `types` directory and doesn't have `src` directory
+* Fixed https://github.com/javascript-obfuscator/javascript-obfuscator/issues/959
+
 v2.18.1
 ---
 * Updated `@javascript-obfuscator/escodegen` with fixed generation of private property names

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


+ 0 - 52
dist/index.browser.js.LICENSE.txt

@@ -1,52 +0,0 @@
-/*!
- * Determine if an object is a Buffer
- *
- * @author   Feross Aboukhadijeh <https://feross.org>
- * @license  MIT
- */
-
-/*!
- * The buffer module from node.js, for the browser.
- *
- * @author   Feross Aboukhadijeh <[email protected]> <http://feross.org>
- * @license  MIT
- */
-
-/*!
-Copyright (C) 2016-2020 Timofey Kachalov <[email protected]>
-
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions are met:
-
-  * Redistributions of source code must retain the above copyright
-    notice, this list of conditions and the following disclaimer.
-  * Redistributions in binary form must reproduce the above copyright
-    notice, this list of conditions and the following disclaimer in the
-    documentation and/or other materials provided with the distribution.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
-DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
-(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
-LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
-ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
-THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-*/
-
-/*! *****************************************************************************
-Copyright (C) Microsoft. All rights reserved.
-Licensed under the Apache License, Version 2.0 (the "License"); you may not use
-this file except in compliance with the License. You may obtain a copy of the
-License at http://www.apache.org/licenses/LICENSE-2.0
-
-THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
-WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
-MERCHANTABLITY OR NON-INFRINGEMENT.
-
-See the Apache Version 2.0 License for specific language governing permissions
-and limitations under the License.
-***************************************************************************** */

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


+ 0 - 23
dist/index.cli.js.LICENSE.txt

@@ -1,23 +0,0 @@
-/*!
-Copyright (C) 2016-2020 Timofey Kachalov <[email protected]>
-
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions are met:
-
-  * Redistributions of source code must retain the above copyright
-    notice, this list of conditions and the following disclaimer.
-  * Redistributions in binary form must reproduce the above copyright
-    notice, this list of conditions and the following disclaimer in the
-    documentation and/or other materials provided with the distribution.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
-DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
-(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
-LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
-ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
-THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-*/

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


+ 0 - 23
dist/index.js.LICENSE.txt

@@ -1,23 +0,0 @@
-/*!
-Copyright (C) 2016-2020 Timofey Kachalov <[email protected]>
-
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions are met:
-
-  * Redistributions of source code must retain the above copyright
-    notice, this list of conditions and the following disclaimer.
-  * Redistributions in binary form must reproduce the above copyright
-    notice, this list of conditions and the following disclaimer in the
-    documentation and/or other materials provided with the distribution.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
-DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
-(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
-LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
-ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
-THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-*/

+ 0 - 38
index.d.ts

@@ -1,38 +0,0 @@
-import { TDictionary } from './src/types/TDictionary';
-import { TInputOptions } from './src/types/options/TInputOptions';
-import { TObfuscationResultsObject } from './src/types/TObfuscationResultsObject';
-import { TOptionsPreset } from './src/types/options/TOptionsPreset';
-
-import { IObfuscationResult } from './src/interfaces/source-code/IObfuscationResult';
-
-export type ObfuscatorOptions = TInputOptions;
-
-export interface ObfuscationResult extends IObfuscationResult {}
-
-/**
- * @param {string} sourceCode
- * @param {ObfuscatorOptions} inputOptions
- * @returns {ObfuscatedCode}
- */
-export function obfuscate (sourceCode: string, inputOptions?: ObfuscatorOptions): ObfuscationResult;
-
-/**
- * @param {TSourceCodesObject} sourceCodesObject
- * @param {TInputOptions} inputOptions
- * @returns {TObfuscationResultsObject<TSourceCodesObject>}
- */
-export function obfuscateMultiple <TSourceCodesObject extends TDictionary<string>> (
-    sourceCodesObject: TSourceCodesObject,
-    inputOptions?: TInputOptions
-): TObfuscationResultsObject<TSourceCodesObject>;
-
-/**
- * @param {TOptionsPreset} optionsPreset
- * @returns {TInputOptions}
- */
-export function getOptionsByPreset (optionsPreset: TOptionsPreset): TInputOptions;
-
-/**
- * @type {string}
- */
-export const version: string;

+ 39 - 0
index.ts

@@ -1,5 +1,44 @@
 "use strict";
 
+import { TDictionary } from './src/types/TDictionary';
+import { TInputOptions } from './src/types/options/TInputOptions';
+import { TObfuscationResultsObject } from './src/types/TObfuscationResultsObject';
+import { TOptionsPreset } from './src/types/options/TOptionsPreset';
+
+import { IObfuscationResult } from './src/interfaces/source-code/IObfuscationResult';
+
 import { JavaScriptObfuscator } from './src/JavaScriptObfuscatorFacade';
 
+export type ObfuscatorOptions = TInputOptions;
+
+export interface ObfuscationResult extends IObfuscationResult {}
+
+/**
+ * @param {string} sourceCode
+ * @param {ObfuscatorOptions} inputOptions
+ * @returns {ObfuscatedCode}
+ */
+export declare function obfuscate (sourceCode: string, inputOptions?: ObfuscatorOptions): ObfuscationResult;
+
+/**
+ * @param {TSourceCodesObject} sourceCodesObject
+ * @param {TInputOptions} inputOptions
+ * @returns {TObfuscationResultsObject<TSourceCodesObject>}
+ */
+export declare function obfuscateMultiple <TSourceCodesObject extends TDictionary<string>> (
+    sourceCodesObject: TSourceCodesObject,
+    inputOptions?: TInputOptions
+): TObfuscationResultsObject<TSourceCodesObject>;
+
+/**
+ * @param {TOptionsPreset} optionsPreset
+ * @returns {TInputOptions}
+ */
+export declare function getOptionsByPreset (optionsPreset: TOptionsPreset): TInputOptions;
+
+/**
+ * @type {string}
+ */
+export declare const version: string;
+
 module.exports = JavaScriptObfuscator;

+ 11 - 9
package.json

@@ -1,6 +1,6 @@
 {
   "name": "javascript-obfuscator",
-  "version": "2.18.1",
+  "version": "2.19.0",
   "description": "JavaScript obfuscator",
   "keywords": [
     "obfuscator",
@@ -19,7 +19,7 @@
   "bin": {
     "javascript-obfuscator": "./bin/javascript-obfuscator"
   },
-  "types": "index.d.ts",
+  "types": "types/index.d.ts",
   "dependencies": {
     "@javascript-obfuscator/escodegen": "2.2.1",
     "@javascript-obfuscator/estraverse": "5.3.0",
@@ -52,6 +52,7 @@
     "@types/eslint-scope": "3.7.1",
     "@types/estraverse": "5.1.1",
     "@types/estree": "0.0.50",
+    "@types/js-beautify": "1.13.2",
     "@types/js-string-escape": "1.0.0",
     "@types/md5": "2.3.1",
     "@types/mkdirp": "1.0.2",
@@ -75,13 +76,15 @@
     "eslint-plugin-unicorn": "34.0.1",
     "fork-ts-checker-notifier-webpack-plugin": "4.0.0",
     "fork-ts-checker-webpack-plugin": "6.2.12",
+    "husky": "^7.0.1",
+    "js-beautify": "1.14.0",
     "mocha": "9.0.2",
     "nyc": "15.1.0",
     "pjson": "1.0.9",
-    "pre-commit": "1.2.2",
     "rimraf": "3.0.2",
     "sinon": "11.1.1",
-    "source-map-resolve": "^0.6.0",
+    "source-map-resolve": "0.6.0",
+    "terser": "^5.7.1",
     "threads": "1.6.5",
     "ts-loader": "9.2.3",
     "ts-node": "10.1.0",
@@ -111,12 +114,11 @@
     "test": "yarn run test:full",
     "eslint": "eslint src/**/*.ts",
     "git:addFiles": "git add .",
-    "postinstall": "opencollective"
+    "postinstall": "opencollective",
+    "precommit": "npm run build",
+    "prepublishOnly": "npm run build && tsc --project ./src/tsconfig.declarations.json",
+    "prepare": "husky install"
   },
-  "pre-commit": [
-    "build",
-    "git:addFiles"
-  ],
   "author": {
     "name": "Timofey Kachalov"
   },

+ 3 - 3
src/container/modules/custom-code-helpers/CustomCodeHelpersModule.ts

@@ -24,7 +24,7 @@ import { DebugProtectionFunctionIntervalCodeHelper } from '../../../custom-code-
 import { DebugProtectionFunctionCodeHelper } from '../../../custom-code-helpers/debug-protection/DebugProtectionFunctionCodeHelper';
 import { DomainLockCodeHelper } from '../../../custom-code-helpers/domain-lock/DomainLockCodeHelper';
 import { CallsControllerFunctionCodeHelper } from '../../../custom-code-helpers/calls-controller/CallsControllerFunctionCodeHelper';
-import { SelfDefendingUnicodeCodeHelper } from '../../../custom-code-helpers/self-defending/SelfDefendingUnicodeCodeHelper';
+import { SelfDefendingCodeHelper } from '../../../custom-code-helpers/self-defending/SelfDefendingCodeHelper';
 import { StringArrayCallsWrapperCodeHelper } from '../../../custom-code-helpers/string-array/StringArrayCallsWrapperCodeHelper';
 import { StringArrayCallsWrapperBase64CodeHelper } from '../../../custom-code-helpers/string-array/StringArrayCallsWrapperBase64CodeHelper';
 import { StringArrayCallsWrapperRc4CodeHelper } from '../../../custom-code-helpers/string-array/StringArrayCallsWrapperRc4CodeHelper';
@@ -58,8 +58,8 @@ export const customCodeHelpersModule: interfaces.ContainerModule = new Container
         .whenTargetNamed(CustomCodeHelper.CallsControllerFunction);
 
     bind<ICustomCodeHelper>(ServiceIdentifiers.ICustomCodeHelper)
-        .to(SelfDefendingUnicodeCodeHelper)
-        .whenTargetNamed(CustomCodeHelper.SelfDefendingUnicode);
+        .to(SelfDefendingCodeHelper)
+        .whenTargetNamed(CustomCodeHelper.SelfDefending);
 
     bind<ICustomCodeHelper>(ServiceIdentifiers.ICustomCodeHelper)
         .to(StringArrayCallsWrapperCodeHelper)

+ 3 - 15
src/custom-code-helpers/self-defending/SelfDefendingUnicodeCodeHelper.ts → src/custom-code-helpers/self-defending/SelfDefendingCodeHelper.ts

@@ -9,19 +9,15 @@ import { ICustomCodeHelperObfuscator } from '../../interfaces/custom-code-helper
 import { IOptions } from '../../interfaces/options/IOptions';
 import { IRandomGenerator } from '../../interfaces/utils/IRandomGenerator';
 
-import { ObfuscationTarget } from '../../enums/ObfuscationTarget';
-
 import { initializable } from '../../decorators/Initializable';
 
 import { SelfDefendingTemplate } from './templates/SelfDefendingTemplate';
-import { SelfDefendingNoEvalTemplate } from './templates/SelfDefendingNoEvalTemplate';
 
 import { AbstractCustomCodeHelper } from '../AbstractCustomCodeHelper';
 import { NodeUtils } from '../../node/NodeUtils';
-import { GlobalVariableNoEvalTemplate } from '../common/templates/GlobalVariableNoEvalTemplate';
 
 @injectable()
-export class SelfDefendingUnicodeCodeHelper extends AbstractCustomCodeHelper {
+export class SelfDefendingCodeHelper extends AbstractCustomCodeHelper {
     /**
      * @type {string}
      */
@@ -79,17 +75,9 @@ export class SelfDefendingUnicodeCodeHelper extends AbstractCustomCodeHelper {
      * @returns {string}
      */
     protected override getCodeHelperTemplate (): string {
-        const globalVariableTemplate: string = this.options.target !== ObfuscationTarget.BrowserNoEval
-            ? this.getGlobalVariableTemplate()
-            : GlobalVariableNoEvalTemplate();
-        const selfDefendingTemplate: string = this.options.target !== ObfuscationTarget.BrowserNoEval
-            ? SelfDefendingTemplate()
-            : SelfDefendingNoEvalTemplate();
-
-        return this.customCodeHelperFormatter.formatTemplate(selfDefendingTemplate, {
+        return this.customCodeHelperFormatter.formatTemplate(SelfDefendingTemplate(), {
             callControllerFunctionName: this.callsControllerFunctionName,
-            selfDefendingFunctionName: this.selfDefendingFunctionName,
-            globalVariableTemplate
+            selfDefendingFunctionName: this.selfDefendingFunctionName
         });
     }
 }

+ 6 - 6
src/custom-code-helpers/self-defending/group/SelfDefendingCodeHelperGroup.ts

@@ -21,7 +21,7 @@ import { AbstractCustomCodeHelperGroup } from '../../AbstractCustomCodeHelperGro
 import { CallsControllerFunctionCodeHelper } from '../../calls-controller/CallsControllerFunctionCodeHelper';
 import { NodeAppender } from '../../../node/NodeAppender';
 import { NodeLexicalScopeUtils } from '../../../node/NodeLexicalScopeUtils';
-import { SelfDefendingUnicodeCodeHelper } from '../SelfDefendingUnicodeCodeHelper';
+import { SelfDefendingCodeHelper } from '../SelfDefendingCodeHelper';
 
 @injectable()
 export class SelfDefendingCodeHelperGroup extends AbstractCustomCodeHelperGroup {
@@ -84,8 +84,8 @@ export class SelfDefendingCodeHelperGroup extends AbstractCustomCodeHelperGroup
 
         // selfDefendingUnicode helper nodes append
         this.appendCustomNodeIfExist(
-            CustomCodeHelper.SelfDefendingUnicode,
-            (customCodeHelper: ICustomCodeHelper<TInitialData<SelfDefendingUnicodeCodeHelper>>) => {
+            CustomCodeHelper.SelfDefending,
+            (customCodeHelper: ICustomCodeHelper<TInitialData<SelfDefendingCodeHelper>>) => {
                 customCodeHelper.initialize(callsControllerFunctionName, selfDefendingFunctionName);
 
                 NodeAppender.prepend(selfDefendingFunctionHostNode, customCodeHelper.getNode());
@@ -110,12 +110,12 @@ export class SelfDefendingCodeHelperGroup extends AbstractCustomCodeHelperGroup
             return;
         }
 
-        const selfDefendingUnicodeCodeHelper: ICustomCodeHelper<TInitialData<SelfDefendingUnicodeCodeHelper>> =
-            this.customCodeHelperFactory(CustomCodeHelper.SelfDefendingUnicode);
+        const selfDefendingCodeHelper: ICustomCodeHelper<TInitialData<SelfDefendingCodeHelper>> =
+            this.customCodeHelperFactory(CustomCodeHelper.SelfDefending);
         const callsControllerFunctionCodeHelper: ICustomCodeHelper<TInitialData<CallsControllerFunctionCodeHelper>> =
             this.customCodeHelperFactory(CustomCodeHelper.CallsControllerFunction);
 
-        this.customCodeHelpers.set(CustomCodeHelper.SelfDefendingUnicode, selfDefendingUnicodeCodeHelper);
+        this.customCodeHelpers.set(CustomCodeHelper.SelfDefending, selfDefendingCodeHelper);
         this.customCodeHelpers.set(CustomCodeHelper.CallsControllerFunction, callsControllerFunctionCodeHelper);
     }
 }

+ 0 - 22
src/custom-code-helpers/self-defending/templates/SelfDefendingNoEvalTemplate.ts

@@ -1,22 +0,0 @@
-/**
- * SelfDefendingTemplate. Enters code in infinity loop.
- *
- * @returns {string}
- */
-export function SelfDefendingNoEvalTemplate (): string {
-    return `
-        const {selfDefendingFunctionName} = {callControllerFunctionName}(this, function () {
-            {globalVariableTemplate}
-        
-            const test = function () {
-                const regExp = new that.RegExp('^([^ ]+( +[^ ]+)+)+[^ ]}');
-                
-                return !regExp.test({selfDefendingFunctionName});
-            };
-            
-            return test();
-        });
-        
-        {selfDefendingFunctionName}();
-    `;
-}

+ 6 - 9
src/custom-code-helpers/self-defending/templates/SelfDefendingTemplate.ts

@@ -6,15 +6,12 @@
 export function SelfDefendingTemplate (): string {
     return `
         const {selfDefendingFunctionName} = {callControllerFunctionName}(this, function () {
-            const test = function () {
-                const regExp = test
-                    .constructor('return /" + this + "/')()
-                    .constructor('^([^ ]+( +[^ ]+)+)+[^ ]}');
-                
-                return !regExp.test({selfDefendingFunctionName});
-            };
-            
-            return test();
+            return {selfDefendingFunctionName}
+                .toString()
+                .search('(((.+)+)+)+$')
+                .toString()
+                .constructor({selfDefendingFunctionName})
+                .search('(((.+)+)+)+$');
         });
         
         {selfDefendingFunctionName}();

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

@@ -13,9 +13,12 @@ export class StringArrayCallsWrapperBase64CodeHelper extends StringArrayCallsWra
     protected override getDecodeStringArrayTemplate (): string {
         const atobFunctionName: string = this.randomGenerator.getRandomString(6);
 
-        const atobPolyfill: string = this.customCodeHelperFormatter.formatTemplate(AtobTemplate(), {
-            atobFunctionName: atobFunctionName
-        });
+        const atobPolyfill: string = this.customCodeHelperFormatter.formatTemplate(
+            AtobTemplate(this.options.selfDefending),
+            {
+                atobFunctionName: atobFunctionName
+            }
+        );
 
         const selfDefendingCode: string = this.getSelfDefendingTemplate();
 
@@ -25,7 +28,6 @@ export class StringArrayCallsWrapperBase64CodeHelper extends StringArrayCallsWra
                 atobPolyfill,
                 atobFunctionName,
                 selfDefendingCode,
-                stringArrayName: this.stringArrayName,
                 stringArrayCallsWrapperName: this.stringArrayCallsWrapperName,
                 stringArrayCacheName: this.stringArrayCacheName
             }

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

@@ -30,13 +30,13 @@ export class StringArrayCallsWrapperCodeHelper extends AbstractCustomCodeHelper
      * @type {string}
      */
     @initializable()
-    protected stringArrayName!: string;
+    protected stringArrayCallsWrapperName!: string;
 
     /**
      * @type {string}
      */
     @initializable()
-    protected stringArrayCallsWrapperName!: string;
+    protected stringArrayFunctionName!: string;
 
     /**
      * @type {string}
@@ -78,16 +78,16 @@ export class StringArrayCallsWrapperCodeHelper extends AbstractCustomCodeHelper
     }
 
     /**
-     * @param {string} stringArrayName
+     * @param {string} stringArrayFunctionName
      * @param {string} stringArrayCallsWrapperName
      * @param {number} indexShiftAmount
      */
     public initialize (
-        stringArrayName: string,
+        stringArrayFunctionName: string,
         stringArrayCallsWrapperName: string,
         indexShiftAmount: number
     ): void {
-        this.stringArrayName = stringArrayName;
+        this.stringArrayFunctionName = stringArrayFunctionName;
         this.stringArrayCallsWrapperName = stringArrayCallsWrapperName;
         this.indexShiftAmount = indexShiftAmount;
 
@@ -108,14 +108,14 @@ export class StringArrayCallsWrapperCodeHelper extends AbstractCustomCodeHelper
     protected override getCodeHelperTemplate (): string {
         const decodeCodeHelperTemplate: string = this.getDecodeStringArrayTemplate();
 
-        const preservedNames: string[] = [`^${this.stringArrayName}$`];
+        const preservedNames: string[] = [`^${this.stringArrayFunctionName}$`];
 
         return this.customCodeHelperObfuscator.obfuscateTemplate(
             this.customCodeHelperFormatter.formatTemplate(StringArrayCallsWrapperTemplate(), {
                 decodeCodeHelperTemplate,
                 stringArrayCallsWrapperName: this.stringArrayCallsWrapperName,
-                stringArrayName: this.stringArrayName,
                 stringArrayCacheName: this.stringArrayCacheName,
+                stringArrayFunctionName: this.stringArrayFunctionName,
                 indexShiftAmount: this.indexShiftAmount
             }),
             {
@@ -145,8 +145,7 @@ export class StringArrayCallsWrapperCodeHelper extends AbstractCustomCodeHelper
                 this.escapeSequenceEncoder
             ),
             {
-                stringArrayCallsWrapperName: this.stringArrayCallsWrapperName,
-                stringArrayName: this.stringArrayName
+                stringArrayCallsWrapperName: this.stringArrayCallsWrapperName
             }
         );
     }

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

@@ -15,13 +15,19 @@ export class StringArrayCallsWrapperRc4CodeHelper extends StringArrayCallsWrappe
         const atobFunctionName: string = this.randomGenerator.getRandomString(6);
         const rc4FunctionName: string = this.randomGenerator.getRandomString(6);
 
-        const atobPolyfill: string = this.customCodeHelperFormatter.formatTemplate(AtobTemplate(), {
-            atobFunctionName
-        });
-        const rc4Polyfill: string = this.customCodeHelperFormatter.formatTemplate(Rc4Template(), {
-            atobFunctionName,
-            rc4FunctionName
-        });
+        const atobPolyfill: string = this.customCodeHelperFormatter.formatTemplate(
+            AtobTemplate(this.options.selfDefending),
+            {
+                atobFunctionName
+            }
+        );
+        const rc4Polyfill: string = this.customCodeHelperFormatter.formatTemplate(
+            Rc4Template(),
+            {
+                atobFunctionName,
+                rc4FunctionName
+            }
+        );
 
         const selfDefendingCode: string = this.getSelfDefendingTemplate();
 
@@ -32,7 +38,6 @@ export class StringArrayCallsWrapperRc4CodeHelper extends StringArrayCallsWrappe
                 rc4FunctionName,
                 rc4Polyfill,
                 selfDefendingCode,
-                stringArrayName: this.stringArrayName,
                 stringArrayCallsWrapperName: this.stringArrayCallsWrapperName,
                 stringArrayCacheName: this.stringArrayCacheName
             }

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

@@ -31,7 +31,7 @@ export class StringArrayCodeHelper extends AbstractCustomCodeHelper {
      * @type {string}
      */
     @initializable()
-    private stringArrayName!: string;
+    private stringArrayFunctionName!: string;
 
     /**
      * @param {TIdentifierNamesGeneratorFactory} identifierNamesGeneratorFactory
@@ -59,14 +59,14 @@ export class StringArrayCodeHelper extends AbstractCustomCodeHelper {
 
     /**
      * @param {IStringArrayStorage} stringArrayStorage
-     * @param {string} stringArrayName
+     * @param {string} stringArrayFunctionName
      */
     public initialize (
         stringArrayStorage: IStringArrayStorage,
-        stringArrayName: string
+        stringArrayFunctionName: string
     ): void {
         this.stringArrayStorage = stringArrayStorage;
-        this.stringArrayName = stringArrayName;
+        this.stringArrayFunctionName = stringArrayFunctionName;
     }
 
     /**
@@ -81,8 +81,11 @@ export class StringArrayCodeHelper extends AbstractCustomCodeHelper {
      * @returns {string}
      */
     protected override getCodeHelperTemplate (): string {
+        const stringArrayName: string = this.identifierNamesGenerator.generateNext();
+
         return this.customCodeHelperFormatter.formatTemplate(StringArrayTemplate(), {
-            stringArrayName: this.stringArrayName,
+            stringArrayFunctionName: this.stringArrayFunctionName,
+            stringArrayName: stringArrayName,
             stringArrayStorageItems: this.getEncodedStringArrayStorageItems()
         });
     }

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

@@ -35,7 +35,7 @@ export class StringArrayRotateFunctionCodeHelper extends AbstractCustomCodeHelpe
      * @type {string}
      */
     @initializable()
-    private stringArrayName!: string;
+    private stringArrayFunctionName!: string;
 
     /**
      * @param {TIdentifierNamesGeneratorFactory} identifierNamesGeneratorFactory
@@ -62,16 +62,16 @@ export class StringArrayRotateFunctionCodeHelper extends AbstractCustomCodeHelpe
     }
 
     /**
-     * @param {string} stringArrayName
+     * @param {string} stringArrayFunctionName
      * @param {number} comparisonValue
      * @param {Expression} comparisonExpressionNode
      */
     public initialize (
-        stringArrayName: string,
+        stringArrayFunctionName: string,
         comparisonValue: number,
         comparisonExpressionNode: Expression
     ): void {
-        this.stringArrayName = stringArrayName;
+        this.stringArrayFunctionName = stringArrayFunctionName;
         this.comparisonValue = comparisonValue;
         this.comparisonExpressionNode = comparisonExpressionNode;
     }
@@ -95,7 +95,7 @@ export class StringArrayRotateFunctionCodeHelper extends AbstractCustomCodeHelpe
             {
                 comparisonExpressionCode,
                 comparisonValue: this.comparisonValue,
-                stringArrayName: this.stringArrayName
+                stringArrayFunctionName: this.stringArrayFunctionName
             }
         );
     }

+ 33 - 17
src/custom-code-helpers/string-array/group/StringArrayCodeHelperGroup.ts

@@ -5,6 +5,7 @@ import { TCustomCodeHelperFactory } from '../../../types/container/custom-code-h
 import { TIdentifierNamesGeneratorFactory } from '../../../types/container/generators/TIdentifierNamesGeneratorFactory';
 import { TInitialData } from '../../../types/TInitialData';
 import { TNodeWithStatements } from '../../../types/node/TNodeWithStatements';
+import { TStatement } from '../../../types/node/TStatement';
 import { TStringArrayEncoding } from '../../../types/options/TStringArrayEncoding';
 
 import { ICallsGraphData } from '../../../interfaces/analyzers/calls-graph-analyzer/ICallsGraphData';
@@ -22,7 +23,6 @@ import { AbstractCustomCodeHelperGroup } from '../../AbstractCustomCodeHelperGro
 import { NodeAppender } from '../../../node/NodeAppender';
 import { StringArrayCallsWrapperCodeHelper } from '../StringArrayCallsWrapperCodeHelper';
 import { StringArrayCodeHelper } from '../StringArrayCodeHelper';
-import { TStatement } from '../../../types/node/TStatement';
 
 @injectable()
 export class StringArrayCodeHelperGroup extends AbstractCustomCodeHelperGroup {
@@ -82,31 +82,31 @@ export class StringArrayCodeHelperGroup extends AbstractCustomCodeHelperGroup {
         }
 
         // stringArray helper nodes append
+        const scopeStatements: TStatement[] = NodeAppender.getScopeStatements(nodeWithStatements);
+
         this.appendCustomNodeIfExist(
             CustomCodeHelper.StringArray,
             (customCodeHelper: ICustomCodeHelper<TInitialData<StringArrayCodeHelper>>) => {
-                NodeAppender.prepend(nodeWithStatements, customCodeHelper.getNode());
+                NodeAppender.insertAtIndex(
+                    nodeWithStatements,
+                    customCodeHelper.getNode(),
+                    this.getScopeStatementRandomIndex(scopeStatements)
+                );
             }
         );
 
         // stringArrayCallsWrapper helper nodes append
-        const stringArrayEncodingsLength: number = this.options.stringArrayEncoding.length;
-        // Stating from index 1 and forward. 0 index is reserved for string array itself.
-        let randomIndex: number = 1;
-        for (let i = 0; i < stringArrayEncodingsLength; i++, randomIndex++) {
-            const stringArrayEncoding: TStringArrayEncoding = this.options.stringArrayEncoding[i];
+        for (const stringArrayEncoding of this.options.stringArrayEncoding) {
             const stringArrayCallsWrapperCodeHelperName: CustomCodeHelper = this.getStringArrayCallsWrapperCodeHelperName(stringArrayEncoding);
 
-            const scopeStatements: TStatement[] = NodeAppender.getScopeStatements(nodeWithStatements);
-            randomIndex = this.randomGenerator.getRandomInteger(
-                randomIndex,
-                scopeStatements.length - 1
-            );
-
             this.appendCustomNodeIfExist(
                 stringArrayCallsWrapperCodeHelperName,
                 (customCodeHelper: ICustomCodeHelper<TInitialData<StringArrayCallsWrapperCodeHelper>>) => {
-                    NodeAppender.insertAtIndex(nodeWithStatements, customCodeHelper.getNode(), randomIndex);
+                    NodeAppender.insertAtIndex(
+                        nodeWithStatements,
+                        customCodeHelper.getNode(),
+                        this.getScopeStatementRandomIndex(scopeStatements)
+                    );
                 }
             );
         }
@@ -119,12 +119,16 @@ export class StringArrayCodeHelperGroup extends AbstractCustomCodeHelperGroup {
             return;
         }
 
+        const stringArrayFunctionName: string = this.stringArrayStorage.getStorageName();
+
         // stringArray helper initialize
         const stringArrayCodeHelper: ICustomCodeHelper<TInitialData<StringArrayCodeHelper>> =
             this.customCodeHelperFactory(CustomCodeHelper.StringArray);
-        const stringArrayName: string = this.stringArrayStorage.getStorageName();
 
-        stringArrayCodeHelper.initialize(this.stringArrayStorage, stringArrayName);
+        stringArrayCodeHelper.initialize(
+            this.stringArrayStorage,
+            stringArrayFunctionName
+        );
         this.customCodeHelpers.set(CustomCodeHelper.StringArray, stringArrayCodeHelper);
 
         // stringArrayCallsWrapper helper initialize
@@ -133,8 +137,9 @@ export class StringArrayCodeHelperGroup extends AbstractCustomCodeHelperGroup {
             const stringArrayCallsWrapperCodeHelper: ICustomCodeHelper<TInitialData<StringArrayCallsWrapperCodeHelper>> =
                 this.customCodeHelperFactory(stringArrayCallsWrapperCodeHelperName);
             const stringArrayCallsWrapperName: string = this.stringArrayStorage.getStorageCallsWrapperName(stringArrayEncoding);
+
             stringArrayCallsWrapperCodeHelper.initialize(
-                stringArrayName,
+                stringArrayFunctionName,
                 stringArrayCallsWrapperName,
                 this.stringArrayStorage.getIndexShiftAmount()
             );
@@ -152,4 +157,15 @@ export class StringArrayCodeHelperGroup extends AbstractCustomCodeHelperGroup {
                 .stringArrayCallsWrapperCodeHelperMap.get(stringArrayEncoding)
             ?? CustomCodeHelper.StringArrayCallsWrapper;
     }
+
+    /**
+     * @param {TStatement[]} scopeStatements
+     * @returns {number}
+     */
+    private getScopeStatementRandomIndex (scopeStatements: TStatement[]): number {
+        return this.randomGenerator.getRandomInteger(
+            0,
+            Math.max(0, scopeStatements.length)
+        );
+    }
 }

+ 11 - 3
src/custom-code-helpers/string-array/templates/string-array-calls-wrapper/AtobTemplate.ts

@@ -5,19 +5,27 @@ import { base64alphabetSwapped } from '../../../../constants/Base64AlphabetSwapp
  *
  * @returns {string}
  */
-export function AtobTemplate (): string {
+export function AtobTemplate (selfDefending: boolean): string {
     return `
         var {atobFunctionName} = function (input) {
             const chars = '${base64alphabetSwapped}';
 
             let output = '';
             let tempEncodedString = '';
+            ${selfDefending ? 'let func = output + {atobFunctionName};' : ''}
             
             for (
                 let bc = 0, bs, buffer, idx = 0;
                 buffer = input.charAt(idx++);
-                ~buffer && (bs = bc % 4 ? bs * 64 + buffer : buffer,
-                    bc++ % 4) ? output += String.fromCharCode(255 & bs >> (-2 * bc & 6)) : 0
+                ~buffer && (bs = bc % 4 ? bs * 64 + buffer : buffer, bc++ % 4)
+                    ? output += ${((): string => {
+                        const basePart: string = 'String.fromCharCode(255 & bs >> (-2 * bc & 6))';
+                        
+                        return selfDefending
+                            ? `((func.charCodeAt(idx + 10) - 10 !== 0) ? ${basePart} : bc)`
+                            : basePart;
+                    })()}
+                    : 0
             ) {
                 buffer = chars.indexOf(buffer);
             }

+ 1 - 1
src/custom-code-helpers/string-array/templates/string-array-calls-wrapper/StringArrayBase64DecodeTemplate.ts

@@ -22,7 +22,7 @@ export function StringArrayBase64DecodeTemplate (
             {stringArrayCallsWrapperName}.${initializedIdentifier} = true;
         }
                   
-        const firstValue = {stringArrayName}[0];
+        const firstValue = stringArray[0];
         const cacheKey = index + firstValue;
         const cachedValue = {stringArrayCacheName}[cacheKey];
         

+ 3 - 1
src/custom-code-helpers/string-array/templates/string-array-calls-wrapper/StringArrayCallsWrapperTemplate.ts

@@ -7,10 +7,12 @@
 export function StringArrayCallsWrapperTemplate (): string {
     return `
         function {stringArrayCallsWrapperName} ({stringArrayCacheName}, key) {
+            const stringArray = {stringArrayFunctionName}();
+            
             {stringArrayCallsWrapperName} = function (index, key) {
                 index = index - {indexShiftAmount};
                 
-                let value = {stringArrayName}[index];
+                let value = stringArray[index];
                 
                 {decodeCodeHelperTemplate}
             

+ 1 - 1
src/custom-code-helpers/string-array/templates/string-array-calls-wrapper/StringArrayRC4DecodeTemplate.ts

@@ -24,7 +24,7 @@ export function StringArrayRC4DecodeTemplate (
             {stringArrayCallsWrapperName}.${initializedIdentifier} = true;
         }
   
-        const firstValue = {stringArrayName}[0];
+        const firstValue = stringArray[0];
         const cacheKey = index + firstValue;
         const cachedValue = {stringArrayCacheName}[cacheKey];
 

+ 6 - 4
src/custom-code-helpers/string-array/templates/string-array-rotate-function/StringArrayRotateFunctionTemplate.ts

@@ -3,7 +3,9 @@
  */
 export function StringArrayRotateFunctionTemplate (): string {
     return `
-        (function (array, comparisonValue) {
+        (function (stringArrayFunction, comparisonValue) {
+            const stringArray = stringArrayFunction();
+        
             while (true) {
                 try {
                     const expression = {comparisonExpressionCode};
@@ -11,12 +13,12 @@ export function StringArrayRotateFunctionTemplate (): string {
                     if (expression === comparisonValue) {
                         break;
                     } else {
-                        array['push'](array['shift']());
+                        stringArray['push'](stringArray['shift']());
                     }
                 } catch (e) {
-                    array['push'](array['shift']());
+                    stringArray['push'](stringArray['shift']());
                 }
             }
-        })({stringArrayName}, {comparisonValue});
+        })({stringArrayFunctionName}, {comparisonValue});
     `;
 }

+ 9 - 1
src/custom-code-helpers/string-array/templates/string-array/StringArrayTemplate.ts

@@ -3,6 +3,14 @@
  */
 export function StringArrayTemplate (): string {
     return `
-        const {stringArrayName} = [{stringArrayStorageItems}];
+        function {stringArrayFunctionName} () {
+            const {stringArrayName} = [{stringArrayStorageItems}];
+            
+            {stringArrayFunctionName} = function () {
+                return {stringArrayName};
+            };
+            
+            return {stringArrayFunctionName}();
+        }
     `;
 }

+ 1 - 1
src/enums/custom-code-helpers/CustomCodeHelper.ts

@@ -5,7 +5,7 @@ export enum CustomCodeHelper {
     DebugProtectionFunctionInterval = 'DebugProtectionFunctionInterval',
     DebugProtectionFunction = 'DebugProtectionFunction',
     DomainLock = 'DomainLock',
-    SelfDefendingUnicode = 'SelfDefendingUnicode',
+    SelfDefending = 'SelfDefending',
     StringArray = 'StringArray',
     StringArrayCallsWrapper = 'StringArrayCallsWrapper',
     StringArrayCallsWrapperBase64 = 'StringArrayCallsWrapperBase64',

+ 27 - 8
src/node-transformers/string-array-transformers/StringArrayRotateFunctionTransformer.ts

@@ -219,30 +219,40 @@ export class StringArrayRotateFunctionTransformer extends AbstractNodeTransforme
      * @returns {TStatement}
      */
     private getStringArrayRotateFunctionNode (): TStatement {
-        const comparisonValue: number = this.randomGenerator.getRandomInteger(100000, 1_000_000);
+        const comparisonValue: number = this.getComparisonValue();
         const comparisonExpressionNumberNumericalExpressionData: TNumberNumericalExpressionData =
             this.numberNumericalExpressionAnalyzer.analyze(
                 comparisonValue,
                 StringArrayRotateFunctionTransformer.comparisonExpressionAdditionalPartsCount
             );
 
+        let index: number = 1;
         const comparisonExpressionNode: ESTree.Expression = NumericalExpressionDataToNodeConverter.convertIntegerNumberData(
             comparisonExpressionNumberNumericalExpressionData,
             ((number: number, isPositiveNumber) => {
+                const multipliedNumber: number = number * index;
                 const literalNode: ESTree.Literal = NodeFactory.literalNode(
-                    `${number}${this.randomGenerator.getRandomString(6)}`
+                    `${multipliedNumber}${this.randomGenerator.getRandomString(6)}`
                 );
                 const parseIntCallExpression: ESTree.CallExpression = NodeFactory.callExpressionNode(
                     NodeFactory.identifierNode('parseInt'),
                     [literalNode]
                 );
 
-                return isPositiveNumber
-                    ? parseIntCallExpression
-                    : NodeFactory.unaryExpressionNode(
-                        '-',
-                        parseIntCallExpression
-                    );
+                const binaryExpressionNode: ESTree.BinaryExpression = NodeFactory.binaryExpressionNode(
+                    '/',
+                    isPositiveNumber
+                        ? parseIntCallExpression
+                        : NodeFactory.unaryExpressionNode(
+                            '-',
+                            parseIntCallExpression
+                        ),
+                    NodeFactory.literalNode(index, index.toString())
+                );
+
+                index++;
+
+                return binaryExpressionNode;
             })
         );
 
@@ -265,4 +275,13 @@ export class StringArrayRotateFunctionTransformer extends AbstractNodeTransforme
     private isComparisonExpressionStringLiteralNode (stringLiteralNode: TStringLiteralNode): boolean {
         return /\d/.test(stringLiteralNode.value);
     }
+
+    /**
+     * Extracted to a standalone method to correctly stub this behaviour
+     *
+     * @returns {number}
+     */
+    private getComparisonValue (): number {
+        return this.randomGenerator.getRandomInteger(100000, 1_000_000);
+    }
 }

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

@@ -52,7 +52,7 @@ export class StringArrayStorage extends MapStorage <`${string}-${TStringArrayEnc
     /**
      * @type {number}
      */
-    private static readonly stringArrayNameLength: number = 4;
+    private static readonly stringArrayFunctionNameLength: number = 4;
 
     /**
      * @type {IArrayUtils}
@@ -181,7 +181,7 @@ export class StringArrayStorage extends MapStorage <`${string}-${TStringArrayEnc
     public override getStorageId (): string {
         if (!this.stringArrayStorageName) {
             this.stringArrayStorageName = this.identifierNamesGenerator
-                .generateForGlobalScope(StringArrayStorage.stringArrayNameLength);
+                .generateForGlobalScope(StringArrayStorage.stringArrayFunctionNameLength);
         }
 
         return this.stringArrayStorageName;
@@ -200,7 +200,7 @@ export class StringArrayStorage extends MapStorage <`${string}-${TStringArrayEnc
         }
 
         const newStorageCallsWrapperName: string = this.identifierNamesGenerator
-            .generateForGlobalScope(StringArrayStorage.stringArrayNameLength);
+            .generateForGlobalScope(StringArrayStorage.stringArrayFunctionNameLength);
 
         this.stringArrayStorageCallsWrapperNamesMap.set(
             stringArrayEncoding,

+ 13 - 0
src/tsconfig.declarations.json

@@ -0,0 +1,13 @@
+{
+  "extends": "./tsconfig.node.json",
+  "compilerOptions": {
+    "outDir": "../types",
+    "declaration": true,
+    "emitDeclarationOnly": true,
+    "skipLibCheck": true
+  },
+  "include": [
+    "../src",
+    "../index.ts"
+  ]
+}

+ 12 - 30
test/dev/dev.ts

@@ -1,43 +1,25 @@
 'use strict';
 
-import { NO_ADDITIONAL_NODES_PRESET } from '../../src/options/presets/NoCustomNodes';
+import { readFileAsString } from '../helpers/readFileAsString';
 
 (function () {
     const JavaScriptObfuscator: any = require('../../index');
+    const code: string = readFileAsString(__dirname + '/../functional-tests/javascript-obfuscator/fixtures/custom-nodes-identifier-names-collision.js');
 
-    let obfuscatedCode: string = JavaScriptObfuscator.obfuscate(
-        `
-            class Test {
-                constructor () {
-                    let test = {}
-                }
-                
-                static methodA = () => {
-                    console.log('method_A');
-                }
-                
-                methodB () {
-                    console.log('method_B');
-                    
-                    Test.methodA();
-                }
-            }
-            
-            const instance = new Test();
-            
-            Test.methodA();
-            instance.methodB();
-        `,
+    let obfuscationResult = JavaScriptObfuscator.obfuscate(
+        code,
         {
-            ...NO_ADDITIONAL_NODES_PRESET,
+            identifierNamesGenerator: 'mangled',
             compact: false,
             stringArray: true,
-            stringArrayThreshold: 1,
-            transformObjectKeys: true,
-            renameProperties: true
+            seed: 429105580
         }
-    ).getObfuscatedCode();
+    );
+
+    let obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
+    let identifierNamesCache = obfuscationResult.getIdentifierNamesCache();
 
     console.log(obfuscatedCode);
     console.log(eval(obfuscatedCode));
-})();
+    console.log(identifierNamesCache);
+})();

+ 3 - 2
test/functional-tests/code-transformers/preparing-transformers/hashbang-operator-transformer/HashbangOperatorTransformer.spec.ts

@@ -2,6 +2,7 @@ import { assert } from 'chai';
 
 import { NO_ADDITIONAL_NODES_PRESET } from '../../../../../src/options/presets/NoCustomNodes';
 
+import { getStringArrayRegExp } from '../../../../helpers/get-string-array-regexp';
 import { readFileAsString } from '../../../../helpers/readFileAsString';
 
 import { JavaScriptObfuscator } from '../../../../../src/JavaScriptObfuscatorFacade';
@@ -59,8 +60,8 @@ describe('HashbangOperatorTransformer', () => {
 
     describe('Variant #3: `stringArray` option enabled', () => {
         const regExp: RegExp = new RegExp(
-            `^#!\/usr\/bin\/env node${lineSeparator}` +
-            `var _0x(\\w){4} *= *\\['abc'];`
+            `^#!\/usr\/bin\/env node${lineSeparator}.*` +
+            `${getStringArrayRegExp(['abc']).source}`
         );
 
         let obfuscatedCode: string;

+ 0 - 156
test/functional-tests/custom-code-helpers/self-defending/templates/SelfDefendingNoEvalTemplate.spec.ts

@@ -1,156 +0,0 @@
-import { assert } from 'chai';
-
-import { ObfuscationTarget } from '../../../../../src/enums/ObfuscationTarget';
-
-import { NO_ADDITIONAL_NODES_PRESET } from '../../../../../src/options/presets/NoCustomNodes';
-
-import { IdentifierNamesGenerator } from '../../../../../src/enums/generators/identifier-names-generators/IdentifierNamesGenerator';
-
-import { evaluateInWorker } from '../../../../helpers/evaluateInWorker';
-import { readFileAsString } from '../../../../helpers/readFileAsString';
-import { beautifyCode } from '../../../../helpers/beautifyCode';
-
-import { JavaScriptObfuscator } from '../../../../../src/JavaScriptObfuscatorFacade';
-
-describe('SelfDefendingNoEvalTemplate', function () {
-    const evaluationTimeout: number = 3500;
-
-    this.timeout(10000);
-
-    describe('Variant #1: correctly obfuscate code with `HexadecimalIdentifierNamesGenerator``', () => {
-        const expectedEvaluationResult: number = 1;
-
-        let obfuscatedCode: string,
-            evaluationResult: number = 0;
-
-        before(() => {
-            const code: string = readFileAsString(__dirname + '/fixtures/input.js');
-
-            obfuscatedCode = JavaScriptObfuscator.obfuscate(
-                code,
-                {
-                    ...NO_ADDITIONAL_NODES_PRESET,
-                    selfDefending: true,
-                    identifierNamesGenerator: IdentifierNamesGenerator.HexadecimalIdentifierNamesGenerator,
-                    target: ObfuscationTarget.BrowserNoEval
-                }
-            ).getObfuscatedCode();
-
-            return evaluateInWorker(obfuscatedCode, evaluationTimeout)
-                .then((result: string | null) => {
-                    if (!result) {
-                        return;
-                    }
-
-                    evaluationResult = parseInt(result, 10);
-                });
-        });
-
-        it('should correctly evaluate code with enabled self defending', () => {
-            assert.equal(evaluationResult, expectedEvaluationResult);
-        });
-    });
-
-    describe('Variant #2: correctly obfuscate code with `MangledIdentifierNamesGenerator` option', () => {
-        const expectedEvaluationResult: number = 1;
-
-        let obfuscatedCode: string,
-            evaluationResult: number = 0;
-
-        before(() => {
-            const code: string = readFileAsString(__dirname + '/fixtures/input.js');
-
-            obfuscatedCode = JavaScriptObfuscator.obfuscate(
-                code,
-                {
-                    ...NO_ADDITIONAL_NODES_PRESET,
-                    selfDefending: true,
-                    identifierNamesGenerator: IdentifierNamesGenerator.MangledIdentifierNamesGenerator,
-                    target: ObfuscationTarget.BrowserNoEval
-                }
-            ).getObfuscatedCode();
-
-            return evaluateInWorker(obfuscatedCode, evaluationTimeout)
-                .then((result: string | null) => {
-                    if (!result) {
-                        return;
-                    }
-
-                    evaluationResult = parseInt(result, 10);
-                });
-        });
-
-        it('should correctly evaluate code with enabled self defending', () => {
-            assert.equal(evaluationResult, expectedEvaluationResult);
-        });
-    });
-
-    describe('Variant #3: correctly obfuscate code with `DictionaryIdentifierNamesGenerator` option', () => {
-        const expectedEvaluationResult: number = 1;
-
-        let obfuscatedCode: string,
-            evaluationResult: number = 0;
-
-        before(() => {
-            const code: string = readFileAsString(__dirname + '/fixtures/input.js');
-
-            obfuscatedCode = JavaScriptObfuscator.obfuscate(
-                code,
-                {
-                    ...NO_ADDITIONAL_NODES_PRESET,
-                    selfDefending: true,
-                    identifierNamesGenerator: IdentifierNamesGenerator.DictionaryIdentifierNamesGenerator,
-                    identifiersDictionary: ['foo', 'bar', 'baz', 'bark', 'hawk', 'eagle'],
-                    target: ObfuscationTarget.BrowserNoEval
-                }
-            ).getObfuscatedCode();
-
-            return evaluateInWorker(obfuscatedCode, evaluationTimeout)
-                .then((result: string | null) => {
-                    if (!result) {
-                        return;
-                    }
-
-                    evaluationResult = parseInt(result, 10);
-                });
-        });
-
-        it('should correctly evaluate code with enabled self defending', () => {
-            assert.equal(evaluationResult, expectedEvaluationResult);
-        });
-    });
-
-    describe('Variant #4: obfuscated code with beautified self defending code', () => {
-        const expectedEvaluationResult: number = 0;
-
-        let obfuscatedCode: string,
-            evaluationResult: number = 0;
-
-        before(() => {
-            const code: string = readFileAsString(__dirname + '/fixtures/input.js');
-
-            obfuscatedCode = JavaScriptObfuscator.obfuscate(
-                code,
-                {
-                    ...NO_ADDITIONAL_NODES_PRESET,
-                    selfDefending: true,
-                    target: ObfuscationTarget.BrowserNoEval
-                }
-            ).getObfuscatedCode();
-            obfuscatedCode = beautifyCode(obfuscatedCode);
-
-            return evaluateInWorker(obfuscatedCode, evaluationTimeout)
-                .then((result: string | null) => {
-                    if (!result) {
-                        return;
-                    }
-
-                    evaluationResult = parseInt(result, 10);
-                });
-        });
-
-        it('should enter code in infinity loop', () => {
-            assert.equal(evaluationResult, expectedEvaluationResult);
-        });
-    });
-});

+ 145 - 31
test/functional-tests/custom-code-helpers/self-defending/templates/SelfDefendingTemplate.spec.ts

@@ -1,7 +1,6 @@
 import { assert } from 'chai';
 
-
-import { NO_ADDITIONAL_NODES_PRESET } from '../../../../../src/options/presets/NoCustomNodes';
+import { TInputOptions } from '../../../../../src/types/options/TInputOptions';
 
 import { IdentifierNamesGenerator } from '../../../../../src/enums/generators/identifier-names-generators/IdentifierNamesGenerator';
 
@@ -12,9 +11,15 @@ import { readFileAsString } from '../../../../helpers/readFileAsString';
 import { JavaScriptObfuscator } from '../../../../../src/JavaScriptObfuscatorFacade';
 
 describe('SelfDefendingTemplate', function () {
-    const evaluationTimeout: number = 3500;
+    const correctEvaluationTimeout: number = 100;
+    const redosEvaluationTimeout: number = 10000;
+
+    const baseOptions: TInputOptions = {
+        optionsPreset: 'high-obfuscation',
+        debugProtection: false
+    };
 
-    this.timeout(10000);
+    this.timeout(30000);
 
     describe('Variant #1: correctly obfuscate code with `HexadecimalIdentifierNamesGenerator``', () => {
         const expectedEvaluationResult: number = 1;
@@ -28,13 +33,13 @@ describe('SelfDefendingTemplate', function () {
             obfuscatedCode = JavaScriptObfuscator.obfuscate(
                 code,
                 {
-                    ...NO_ADDITIONAL_NODES_PRESET,
+                    ...baseOptions,
                     selfDefending: true,
                     identifierNamesGenerator: IdentifierNamesGenerator.HexadecimalIdentifierNamesGenerator
                 }
             ).getObfuscatedCode();
 
-            return evaluateInWorker(obfuscatedCode, evaluationTimeout)
+            return evaluateInWorker(obfuscatedCode, correctEvaluationTimeout)
                 .then((result: string | null) => {
                     if (!result) {
                         return;
@@ -61,13 +66,13 @@ describe('SelfDefendingTemplate', function () {
             obfuscatedCode = JavaScriptObfuscator.obfuscate(
                 code,
                 {
-                    ...NO_ADDITIONAL_NODES_PRESET,
+                    baseOptions,
                     selfDefending: true,
                     identifierNamesGenerator: IdentifierNamesGenerator.MangledIdentifierNamesGenerator
                 }
             ).getObfuscatedCode();
 
-            return evaluateInWorker(obfuscatedCode, evaluationTimeout)
+            return evaluateInWorker(obfuscatedCode, correctEvaluationTimeout)
                 .then((result: string | null) => {
                     if (!result) {
                         return;
@@ -94,14 +99,14 @@ describe('SelfDefendingTemplate', function () {
             obfuscatedCode = JavaScriptObfuscator.obfuscate(
                 code,
                 {
-                    ...NO_ADDITIONAL_NODES_PRESET,
+                    baseOptions,
                     selfDefending: true,
                     identifierNamesGenerator: IdentifierNamesGenerator.DictionaryIdentifierNamesGenerator,
                     identifiersDictionary: ['foo', 'bar', 'baz', 'bark', 'hawk', 'eagle']
                 }
             ).getObfuscatedCode();
 
-            return evaluateInWorker(obfuscatedCode, evaluationTimeout)
+            return evaluateInWorker(obfuscatedCode, correctEvaluationTimeout)
                 .then((result: string | null) => {
                     if (!result) {
                         return;
@@ -117,35 +122,144 @@ describe('SelfDefendingTemplate', function () {
     });
 
     describe('Variant #4: obfuscated code with beautified self defending code', () => {
-        const expectedEvaluationResult: number = 0;
+        describe('Variant #1: beautify with spaces', () => {
+            const expectedEvaluationResult: number = 0;
 
-        let obfuscatedCode: string,
-            evaluationResult: number = 0;
+            let obfuscatedCode: string,
+                evaluationResult: number = 0;
 
-        before(() => {
-            const code: string = readFileAsString(__dirname + '/fixtures/input.js');
+            before(() => {
+                const code: string = readFileAsString(__dirname + '/fixtures/input.js');
 
-            obfuscatedCode = JavaScriptObfuscator.obfuscate(
-                code,
-                {
-                    ...NO_ADDITIONAL_NODES_PRESET,
-                    selfDefending: true
-                }
-            ).getObfuscatedCode();
-            obfuscatedCode = beautifyCode(obfuscatedCode);
+                obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                    code,
+                    {
+                        ...baseOptions,
+                        selfDefending: true
+                    }
+                ).getObfuscatedCode();
+                obfuscatedCode = beautifyCode(obfuscatedCode, 'space');
+
+                return evaluateInWorker(obfuscatedCode, redosEvaluationTimeout)
+                    .then((result: string | null) => {
+                        if (!result) {
+                            return;
+                        }
+
+                        evaluationResult = parseInt(result, 10);
+                    });
+            });
+
+            it('should enter code in infinity loop', () => {
+                assert.equal(evaluationResult, expectedEvaluationResult);
+            });
+        });
 
-            return evaluateInWorker(obfuscatedCode, evaluationTimeout)
-                .then((result: string | null) => {
-                    if (!result) {
-                        return;
+        describe('Variant #2: beautify with tabs', () => {
+            const expectedEvaluationResult: number = 0;
+
+            let obfuscatedCode: string,
+                evaluationResult: number = 0;
+
+            before(() => {
+                const code: string = readFileAsString(__dirname + '/fixtures/input.js');
+
+                obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                    code,
+                    {
+                        ...baseOptions,
+                        selfDefending: true
                     }
+                ).getObfuscatedCode();
+                obfuscatedCode = beautifyCode(obfuscatedCode, 'tab');
+
+                return evaluateInWorker(obfuscatedCode, redosEvaluationTimeout)
+                    .then((result: string | null) => {
+                        if (!result) {
+                            return;
+                        }
+
+                        evaluationResult = parseInt(result, 10);
+                    });
+            });
+
+            it('should enter code in infinity loop', () => {
+                assert.equal(evaluationResult, expectedEvaluationResult);
+            });
+        });
+    });
 
-                    evaluationResult = parseInt(result, 10);
-                });
+    describe('Variant #5: JavaScript obfuscator code', () => {
+        describe('Variant #1: correct evaluation', () => {
+            const evaluationTimeout: number = 5000;
+            const expectedEvaluationResult: string = 'var foo=0x1;';
+
+            let obfuscatedCode: string,
+                evaluationResult: string = '';
+
+            before(() => {
+                const code: string = readFileAsString(process.cwd() + '/dist/index.js');
+
+                obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                    `
+                        ${code}
+                        module.exports.obfuscate('var foo = 1;').getObfuscatedCode();
+                    `,
+                    {
+                        selfDefending: true,
+                        identifierNamesGenerator: IdentifierNamesGenerator.HexadecimalIdentifierNamesGenerator
+                    }
+                ).getObfuscatedCode();
+
+                return evaluateInWorker(obfuscatedCode, evaluationTimeout)
+                    .then((result: string | null) => {
+                        if (!result) {
+                            return;
+                        }
+
+                        evaluationResult = result;
+                    });
+            });
+
+            it('should correctly evaluate code with enabled self defending', () => {
+                assert.equal(evaluationResult, expectedEvaluationResult);
+            });
         });
 
-        it('should enter code in infinity loop', () => {
-            assert.equal(evaluationResult, expectedEvaluationResult);
+        describe('Variant #2: beautify with spaces', () => {
+            const expectedEvaluationResult: string = '';
+
+            let obfuscatedCode: string,
+                evaluationResult: string = '';
+
+            before(() => {
+                const code: string = readFileAsString(process.cwd() + '/dist/index.js');
+
+                obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                    `
+                        ${code}
+                        module.exports.obfuscate('var foo = 1;').getObfuscatedCode();
+                    `,
+                    {
+                        selfDefending: true,
+                        identifierNamesGenerator: IdentifierNamesGenerator.HexadecimalIdentifierNamesGenerator
+                    }
+                ).getObfuscatedCode();
+                obfuscatedCode = beautifyCode(obfuscatedCode, 'space');
+
+                return evaluateInWorker(obfuscatedCode, redosEvaluationTimeout)
+                    .then((result: string | null) => {
+                        if (!result) {
+                            return;
+                        }
+
+                        evaluationResult = result;
+                    });
+            });
+
+            it('should enter code in infinity loop', () => {
+                assert.equal(evaluationResult, expectedEvaluationResult);
+            });
         });
     });
 });

+ 48 - 6
test/functional-tests/custom-code-helpers/string-array/StringArrayCallsWrapperCodeHelper.spec.ts

@@ -10,7 +10,48 @@ import { readFileAsString } from '../../../helpers/readFileAsString';
 import { JavaScriptObfuscator } from '../../../../src/JavaScriptObfuscatorFacade';
 
 describe('StringArrayCallsWrapperCodeHelper', () => {
-    const regExp: RegExp = /_0x([a-f0-9]){4,6} *= *_0x([a-f0-9]){4,6} *- *0x0\;/;
+    const stringCallsWrapperRegExp: RegExp = /function _0x([a-f0-9]){4} *\(_0x([a-f0-9]){4,6} *,_0x([a-f0-9]){4,6}\)/;
+
+    describe('`stringArray` option is set', () => {
+        const samplesCount: number = 100;
+        const stringArrayCallsWrapperAtFirstPositionRegExp: RegExp = new RegExp(`^${stringCallsWrapperRegExp.source}`);
+        const variableDeclarationAtFirstPositionRegExp: RegExp = /^var test *= *_0x([a-f0-9]){4}\(0x0\);/;
+
+        let obfuscatedCode: string;
+        let stringArrayCallsWrapperAtFirstPositionMatchesCount: number = 0;
+        let variableDeclarationAtFirstPositionMatchesCount: number = 0;
+
+        before(() => {
+            const code: string = readFileAsString(__dirname + '/fixtures/simple-input.js');
+
+            for (let i = 0; i < samplesCount; i++) {
+                obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                    code,
+                    {
+                        ...NO_ADDITIONAL_NODES_PRESET,
+                        stringArray: true,
+                        stringArrayThreshold: 1
+                    }
+                ).getObfuscatedCode();
+
+                if (obfuscatedCode.match(stringArrayCallsWrapperAtFirstPositionRegExp)) {
+                    stringArrayCallsWrapperAtFirstPositionMatchesCount++;
+                }
+
+                if (obfuscatedCode.match(variableDeclarationAtFirstPositionRegExp)) {
+                    variableDeclarationAtFirstPositionMatchesCount++;
+                }
+            }
+        });
+
+        it('Match #1: should correctly append code helper into the obfuscated code at random index', () => {
+            assert.isAbove(stringArrayCallsWrapperAtFirstPositionMatchesCount, 1);
+        });
+
+        it('Match #2: should correctly append code helper into the obfuscated code at random index', () => {
+            assert.isAbove(variableDeclarationAtFirstPositionMatchesCount, 1);
+        });
+    });
 
     describe('`stringArray` option is set', () => {
         let obfuscatedCode: string;
@@ -29,7 +70,7 @@ describe('StringArrayCallsWrapperCodeHelper', () => {
         });
 
         it('should correctly append code helper into the obfuscated code', () => {
-            assert.match(obfuscatedCode, regExp);
+            assert.match(obfuscatedCode, stringCallsWrapperRegExp);
         });
     });
 
@@ -49,16 +90,17 @@ describe('StringArrayCallsWrapperCodeHelper', () => {
         });
 
         it('shouldn\'t append code helper into the obfuscated code', () => {
-            assert.notMatch(obfuscatedCode, regExp);
+            assert.notMatch(obfuscatedCode, stringCallsWrapperRegExp);
         });
     });
 
     describe('Preserve string array name', () => {
         const callsWrapperRegExp: RegExp = new RegExp(`` +
             `function *b *\\(c, *d\\) *{ *` +
-                `b *= *function *\\(e, *f\\) *{` +
-                    `e *= *e *- *0x0; *` +
-                    `var g *= *a\\[e]; *` +
+                `var e *= *a\\(\\); *` +
+                `b *= *function *\\(f, *g\\) *{` +
+                    `f *= *f *- *0x0; *` +
+                    `var h *= *e\\[f]; *` +
         ``);
 
         let obfuscatedCode: string;

+ 32 - 11
test/functional-tests/custom-code-helpers/string-array/StringArrayCodeHelper.spec.ts

@@ -2,31 +2,52 @@ import { assert } from 'chai';
 
 import { NO_ADDITIONAL_NODES_PRESET } from '../../../../src/options/presets/NoCustomNodes';
 
+import { getStringArrayRegExp } from '../../../helpers/get-string-array-regexp';
 import { readFileAsString } from '../../../helpers/readFileAsString';
 
 import { JavaScriptObfuscator } from '../../../../src/JavaScriptObfuscatorFacade';
 
 describe('StringArrayCodeHelper', () => {
-    const regExp: RegExp = /^var _0x([a-f0-9]){4} *= *\[/;
+    const stringArrayRegExp: RegExp = getStringArrayRegExp(['test']);
 
     describe('`stringArray` option is set', () => {
+        const samplesCount: number = 100;
+        const stringArrayAtFirstPositionRegExp: RegExp = new RegExp(`^${stringArrayRegExp.source}`);
+        const variableDeclarationAtFirstPositionRegExp: RegExp = /^var test *= *_0x([a-f0-9]){4}\(0x0\);/;
+
         let obfuscatedCode: string;
+        let stringArrayAtFirstPositionMatchesCount: number = 0;
+        let variableDeclarationAtFirstPositionMatchesCount: number = 0;
 
         before(() => {
             const code: string = readFileAsString(__dirname + '/fixtures/simple-input.js');
 
-            obfuscatedCode = JavaScriptObfuscator.obfuscate(
-                code,
-                {
-                    ...NO_ADDITIONAL_NODES_PRESET,
-                    stringArray: true,
-                    stringArrayThreshold: 1
+            for (let i = 0; i < samplesCount; i++) {
+                obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                    code,
+                    {
+                        ...NO_ADDITIONAL_NODES_PRESET,
+                        stringArray: true,
+                        stringArrayThreshold: 1
+                    }
+                ).getObfuscatedCode();
+
+                if (obfuscatedCode.match(stringArrayAtFirstPositionRegExp)) {
+                    stringArrayAtFirstPositionMatchesCount++;
                 }
-            ).getObfuscatedCode();
+
+                if (obfuscatedCode.match(variableDeclarationAtFirstPositionRegExp)) {
+                    variableDeclarationAtFirstPositionMatchesCount++;
+                }
+            }
+        });
+
+        it('Match #1: should correctly append code helper into the obfuscated code at random index', () => {
+            assert.isAbove(stringArrayAtFirstPositionMatchesCount, 1);
         });
 
-        it('should correctly append code helper into the obfuscated code', () => {
-            assert.match(obfuscatedCode, regExp);
+        it('Match #2: should correctly append code helper into the obfuscated code at random index', () => {
+            assert.isAbove(variableDeclarationAtFirstPositionMatchesCount, 1);
         });
     });
 
@@ -46,7 +67,7 @@ describe('StringArrayCodeHelper', () => {
         });
 
         it('shouldn\'t append code helper into the obfuscated code', () => {
-            assert.notMatch(obfuscatedCode, regExp);
+            assert.notMatch(obfuscatedCode, stringArrayRegExp);
         });
     });
 });

+ 2 - 2
test/functional-tests/custom-code-helpers/string-array/StringArrayRotateFunctionCodeHelper.spec.ts

@@ -84,8 +84,8 @@ describe('StringArrayRotateFunctionCodeHelper', () => {
     });
 
     describe('Preserve string array name', () => {
-        const arrayRotateRegExp: RegExp = /c\['push']\(c\['shift']\(\)\);/;
-        const comparisonRegExp: RegExp = /if *\(e *=== *d\) *{/;
+        const arrayRotateRegExp: RegExp = /e\['push']\(e\['shift']\(\)\);/;
+        const comparisonRegExp: RegExp = /if *\(f *=== *d\) *{/;
 
         let obfuscatedCode: string;
 

+ 36 - 18
test/functional-tests/custom-code-helpers/string-array/group/StringArrayCodeHelperGroup.spec.ts

@@ -5,41 +5,59 @@ import { IdentifierNamesGenerator } from '../../../../../src/enums/generators/id
 
 import { NO_ADDITIONAL_NODES_PRESET } from '../../../../../src/options/presets/NoCustomNodes';
 
+import { getRegExpMatch } from '../../../../helpers/getRegExpMatch';
 import { readFileAsString } from '../../../../helpers/readFileAsString';
 
 import { JavaScriptObfuscator } from '../../../../../src/JavaScriptObfuscatorFacade';
 
 describe('StringArrayCodeHelperGroup', () => {
     const regExp: RegExp = new RegExp(
-        'function *b *\\(\\w, *\\w\\) *{.*return \\w;}.*' +
-        'function *c *\\(\\w, *\\w\\) *{.*return \\w;}.*' +
-        'function *d *\\(\\w, *\\w\\) *{.*return \\w;}'
+        'function *\\w *\\(\\w, *\\w\\) *{.*return \\w;}.*' +
+        'function *\\w *\\(\\w, *\\w\\) *{.*return \\w;}.*' +
+        'function *\\w *\\(\\w, *\\w\\) *{.*return \\w;}'
+    );
+    const stringArrayCallsWrapperRegExp: RegExp = new RegExp(
+        `function *(\\w) *\\(\\w, *\\w\\) *{.*return \\w;}.*`
     );
 
     describe('StringArrayCallsWrapper code helper names', () => {
+        const stringArrayCallsWrapperNames: Set<string> = new Set();
+        const samplesCount: number = 30;
         let obfuscatedCode: string;
 
+        const expectedUniqStringArrayCallsWrapperNamesCount: number = 3;
+
         before(() => {
             const code: string = readFileAsString(__dirname + '/fixtures/simple-input.js');
 
-            obfuscatedCode = JavaScriptObfuscator.obfuscate(
-                code,
-                {
-                    ...NO_ADDITIONAL_NODES_PRESET,
-                    identifierNamesGenerator: IdentifierNamesGenerator.MangledIdentifierNamesGenerator,
-                    stringArray: true,
-                    stringArrayThreshold: 1,
-                    stringArrayEncoding: [
-                        StringArrayEncoding.None,
-                        StringArrayEncoding.Base64,
-                        StringArrayEncoding.Rc4
-                    ]
-                }
-            ).getObfuscatedCode();
+            for (let i = 0; i < samplesCount; i++) {
+                obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                    code,
+                    {
+                        ...NO_ADDITIONAL_NODES_PRESET,
+                        identifierNamesGenerator: IdentifierNamesGenerator.MangledIdentifierNamesGenerator,
+                        stringArray: true,
+                        stringArrayThreshold: 1,
+                        stringArrayEncoding: [
+                            StringArrayEncoding.None,
+                            StringArrayEncoding.Base64,
+                            StringArrayEncoding.Rc4
+                        ]
+                    }
+                ).getObfuscatedCode();
+
+                const callsWrapperName: string = getRegExpMatch(obfuscatedCode, stringArrayCallsWrapperRegExp);
+
+                stringArrayCallsWrapperNames.add(callsWrapperName);
+            }
         });
 
-        it('should place multiple StringArrayCallsWrapper code helper names in the correct order', () => {
+        it('should correct place all StringArrayCallsWrapper code helpers', () => {
             assert.match(obfuscatedCode, regExp);
         });
+
+        it('should place multiple StringArrayCallsWrapper code helper names in the random order', () => {
+            assert.equal(stringArrayCallsWrapperNames.size, expectedUniqStringArrayCallsWrapperNamesCount);
+        });
     });
 });

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

@@ -16,16 +16,19 @@ import { Rc4Template } from '../../../../../../src/custom-code-helpers/string-ar
 import { StringArrayBase64DecodeTemplate } from '../../../../../../src/custom-code-helpers/string-array/templates/string-array-calls-wrapper/StringArrayBase64DecodeTemplate';
 import { StringArrayCallsWrapperTemplate } from '../../../../../../src/custom-code-helpers/string-array/templates/string-array-calls-wrapper/StringArrayCallsWrapperTemplate';
 import { StringArrayRC4DecodeTemplate } from '../../../../../../src/custom-code-helpers/string-array/templates/string-array-calls-wrapper/StringArrayRC4DecodeTemplate';
+import { StringArrayTemplate } from '../../../../../../src/custom-code-helpers/string-array/templates/string-array/StringArrayTemplate';
 
 import { NO_ADDITIONAL_NODES_PRESET } from '../../../../../../src/options/presets/NoCustomNodes';
 
 import { InversifyContainerFacade } from '../../../../../../src/container/InversifyContainerFacade';
 import { JavaScriptObfuscator } from '../../../../../../src/JavaScriptObfuscatorFacade';
+import { minimizeCode } from '../../../../../helpers/minimizeCode';
 import { readFileAsString } from '../../../../../helpers/readFileAsString';
 import { swapLettersCase } from '../../../../../helpers/swapLettersCase';
 
 describe('StringArrayCallsWrapperTemplate', () => {
     const stringArrayName: string = 'stringArrayName';
+    const stringArrayFunctionName: string = 'stringArrayFunctionName';
     const stringArrayCallsWrapperName: string = 'stringArrayCallsWrapperName';
     const stringArrayCacheName: string = 'stringArrayCache';
     const atobFunctionName: string = 'atob';
@@ -45,243 +48,612 @@ describe('StringArrayCallsWrapperTemplate', () => {
     });
 
     describe('Variant #1: `base64` encoding', () => {
-        describe('Variant #1: index shift amount is `0`', () => {
-            const index: string = '0x0';
-
-            const indexShiftAmount: number = 0;
-
-            const expectedDecodedValue: string = 'test1';
-
-            let decodedValue: string;
-
-            before(() => {
-                const atobPolyfill = format(AtobTemplate(), {
-                    atobFunctionName
-                });
-                const atobDecodeTemplate: string = format(
-                    StringArrayBase64DecodeTemplate(randomGenerator),
-                    {
-                        atobPolyfill,
-                        atobFunctionName,
-                        selfDefendingCode: '',
+        describe('Variant #1: `selfDefending` option is disabled', () => {
+            const selfDefendingEnabled: boolean = false;
+
+            describe('Variant #1: index shift amount is `0`', () => {
+                const index: string = '0x0';
+
+                const indexShiftAmount: number = 0;
+
+                const expectedDecodedValue: string = 'test1';
+
+                let decodedValue: string;
+
+                before(() => {
+                    const stringArrayTemplate = format(StringArrayTemplate(), {
+                        stringArrayName,
+                        stringArrayFunctionName,
+                        stringArrayStorageItems: `'${cryptUtilsSwappedAlphabet.btoa('test1')}'`
+                    });
+                    const atobPolyfill = format(AtobTemplate(selfDefendingEnabled), {
+                        atobFunctionName
+                    });
+                    const atobDecodeTemplate: string = format(
+                        StringArrayBase64DecodeTemplate(randomGenerator),
+                        {
+                            atobPolyfill,
+                            atobFunctionName,
+                            selfDefendingCode: '',
+                            stringArrayCacheName,
+                            stringArrayCallsWrapperName
+                        }
+                    );
+                    const stringArrayCallsWrapperTemplate: string = format(StringArrayCallsWrapperTemplate(), {
+                        decodeCodeHelperTemplate: atobDecodeTemplate,
+                        indexShiftAmount,
                         stringArrayCacheName,
-                        stringArrayCallsWrapperName
-                    }
-                );
-                const stringArrayCallsWrapperTemplate: string = format(StringArrayCallsWrapperTemplate(), {
-                    decodeCodeHelperTemplate: atobDecodeTemplate,
-                    indexShiftAmount,
-                    stringArrayCacheName,
-                    stringArrayCallsWrapperName,
-                    stringArrayName
+                        stringArrayCallsWrapperName,
+                        stringArrayFunctionName
+                    });
+
+                    decodedValue = Function(`
+                        ${stringArrayTemplate}
+                    
+                        ${stringArrayCallsWrapperTemplate}
+                        
+                        return ${stringArrayCallsWrapperName}(${index});
+                    `)();
                 });
 
-                decodedValue = Function(`
-                var ${stringArrayName} = ['${cryptUtilsSwappedAlphabet.btoa('test1')}'];
-            
-                ${stringArrayCallsWrapperTemplate}
-                
-                return ${stringArrayCallsWrapperName}(${index});
-            `)();
-            });
-
-            it('should correctly return decoded value', () => {
-                assert.deepEqual(decodedValue, expectedDecodedValue);
+                it('should correctly return decoded value', () => {
+                    assert.deepEqual(decodedValue, expectedDecodedValue);
+                });
             });
-        });
-
-        describe('Variant #2: index shift amount is `5`', () => {
-            const index: string = '0x5';
-
-            const indexShiftAmount: number = 5;
-
-            const expectedDecodedValue: string = 'test1';
 
-            let decodedValue: string;
-
-            before(() => {
-                const atobPolyfill = format(AtobTemplate(), {
-                    atobFunctionName
-                });
-                const atobDecodeTemplate: string = format(
-                    StringArrayBase64DecodeTemplate(randomGenerator),
-                    {
-                        atobPolyfill,
-                        atobFunctionName,
-                        selfDefendingCode: '',
+            describe('Variant #2: index shift amount is `5`', () => {
+                const index: string = '0x5';
+
+                const indexShiftAmount: number = 5;
+
+                const expectedDecodedValue: string = 'test1';
+
+                let decodedValue: string;
+
+                before(() => {
+                    const stringArrayTemplate = format(StringArrayTemplate(), {
+                        stringArrayName,
+                        stringArrayFunctionName,
+                        stringArrayStorageItems: `'${cryptUtilsSwappedAlphabet.btoa('test1')}'`
+                    });
+                    const atobPolyfill = format(AtobTemplate(selfDefendingEnabled), {
+                        atobFunctionName
+                    });
+                    const atobDecodeTemplate: string = format(
+                        StringArrayBase64DecodeTemplate(randomGenerator),
+                        {
+                            atobPolyfill,
+                            atobFunctionName,
+                            selfDefendingCode: '',
+                            stringArrayCacheName,
+                            stringArrayCallsWrapperName
+                        }
+                    );
+                    const stringArrayCallsWrapperTemplate: string = format(StringArrayCallsWrapperTemplate(), {
+                        decodeCodeHelperTemplate: atobDecodeTemplate,
+                        indexShiftAmount,
                         stringArrayCacheName,
-                        stringArrayCallsWrapperName
-                    }
-                );
-                const stringArrayCallsWrapperTemplate: string = format(StringArrayCallsWrapperTemplate(), {
-                    decodeCodeHelperTemplate: atobDecodeTemplate,
-                    indexShiftAmount,
-                    stringArrayCacheName,
-                    stringArrayCallsWrapperName,
-                    stringArrayName
+                        stringArrayCallsWrapperName,
+                        stringArrayFunctionName
+                    });
+
+                    decodedValue = Function(`
+                        ${stringArrayTemplate}
+                    
+                        ${stringArrayCallsWrapperTemplate}
+                        
+                        return ${stringArrayCallsWrapperName}(${index});
+                    `)();
                 });
 
-                decodedValue = Function(`
-                var ${stringArrayName} = ['${cryptUtilsSwappedAlphabet.btoa('test1')}'];
-            
-                ${stringArrayCallsWrapperTemplate}
-                
-                return ${stringArrayCallsWrapperName}(${index});
-            `)();
+                it('should correctly return decoded value', () => {
+                    assert.deepEqual(decodedValue, expectedDecodedValue);
+                });
             });
 
-            it('should correctly return decoded value', () => {
-                assert.deepEqual(decodedValue, expectedDecodedValue);
+            describe('Variant #3: no regexp inside atob template', () => {
+                const indexShiftAmount: number = 0;
+
+                const expectedRegExpTestValue: string = '12345';
+
+                let decodedValue: string;
+
+                before(() => {
+                    const stringArrayTemplate = format(StringArrayTemplate(), {
+                        stringArrayName,
+                        stringArrayFunctionName,
+                        stringArrayStorageItems: `'${swapLettersCase('c3RyaQ==')}'`
+                    });
+                    const atobPolyfill = format(AtobTemplate(selfDefendingEnabled), {
+                        atobFunctionName
+                    });
+                    const atobDecodeTemplate: string = format(
+                        StringArrayBase64DecodeTemplate(randomGenerator),
+                        {
+                            atobPolyfill,
+                            atobFunctionName,
+                            selfDefendingCode: '',
+                            stringArrayCacheName,
+                            stringArrayCallsWrapperName
+                        }
+                    );
+                    const stringArrayCallsWrapperTemplate: string = format(StringArrayCallsWrapperTemplate(), {
+                        decodeCodeHelperTemplate: atobDecodeTemplate,
+                        indexShiftAmount,
+                        stringArrayCacheName,
+                        stringArrayCallsWrapperName,
+                        stringArrayFunctionName
+                    });
+
+                    decodedValue = Function(`
+                        ${stringArrayTemplate}
+                    
+                        ${stringArrayCallsWrapperTemplate}
+                        
+                        /(.+)/.test("12345");
+                        ${stringArrayCallsWrapperName}(0x0);
+                                        
+                        return RegExp.$1;
+                    `)();
+                });
+
+                it('should correctly return RegExp.$1 match without mutation by atob template', () => {
+                    assert.deepEqual(decodedValue, expectedRegExpTestValue);
+                });
             });
         });
 
-        describe('Variant #3: no regexp inside atob template', () => {
-            const indexShiftAmount: number = 0;
-
-            const expectedRegExpTestValue: string = '12345';
-
-            let decodedValue: string;
-
-            before(() => {
-                const atobPolyfill = format(AtobTemplate(), {
-                    atobFunctionName
-                });
-                const atobDecodeTemplate: string = format(
-                    StringArrayBase64DecodeTemplate(randomGenerator),
-                    {
-                        atobPolyfill,
-                        atobFunctionName,
-                        selfDefendingCode: '',
-                        stringArrayCacheName,
-                        stringArrayCallsWrapperName
-                    }
-                );
-                const stringArrayCallsWrapperTemplate: string = format(StringArrayCallsWrapperTemplate(), {
-                    decodeCodeHelperTemplate: atobDecodeTemplate,
-                    indexShiftAmount,
-                    stringArrayCacheName,
-                    stringArrayCallsWrapperName,
-                    stringArrayName
+        describe('Variant #2: `selfDefending` option is enabled', () => {
+            const selfDefendingEnabled: boolean = true;
+
+            describe('Variant #1: correct code evaluation for single-line code', () => {
+                describe('Variant #1: long decoded string', () => {
+                    const index: string = '0x0';
+
+                    const indexShiftAmount: number = 0;
+
+                    const expectedDecodedValue: string = 'test1test1';
+
+                    let decodedValue: string;
+
+                    before(async() => {
+                        const stringArrayTemplate = format(StringArrayTemplate(), {
+                            stringArrayName,
+                            stringArrayFunctionName,
+                            stringArrayStorageItems: `'${cryptUtilsSwappedAlphabet.btoa('test1test1')}'`
+                        });
+                        const atobPolyfill = format(AtobTemplate(selfDefendingEnabled), {
+                            atobFunctionName
+                        });
+                        const atobDecodeTemplate: string = format(
+                            StringArrayBase64DecodeTemplate(randomGenerator),
+                            {
+                                atobPolyfill,
+                                atobFunctionName,
+                                selfDefendingCode: '',
+                                stringArrayCacheName,
+                                stringArrayCallsWrapperName
+                            }
+                        );
+                        const stringArrayCallsWrapperTemplate: string = await minimizeCode(
+                            format(StringArrayCallsWrapperTemplate(), {
+                                decodeCodeHelperTemplate: atobDecodeTemplate,
+                                indexShiftAmount,
+                                stringArrayCacheName,
+                                stringArrayCallsWrapperName,
+                                stringArrayFunctionName
+                            })
+                        );
+
+                        decodedValue = Function(`
+                            ${stringArrayTemplate}
+                        
+                            ${stringArrayCallsWrapperTemplate}
+                            
+                            return ${stringArrayCallsWrapperName}(${index});
+                        `)();
+                    });
+
+                    it('should correctly return decoded value', () => {
+                        assert.deepEqual(decodedValue, expectedDecodedValue);
+                    });
                 });
 
-                decodedValue = Function(`
-                var ${stringArrayName} = ['${swapLettersCase('c3RyaQ==')}'];
-            
-                ${stringArrayCallsWrapperTemplate}
-                
-                /(.+)/.test("12345");
-                ${stringArrayCallsWrapperName}(0x0);
-                                
-                return RegExp.$1;
-            `)();
+                describe('Variant #2: 3-characters decoded string', () => {
+                    const index: string = '0x0';
+
+                    const indexShiftAmount: number = 0;
+
+                    const expectedDecodedValue: string = 'foo';
+
+                    let decodedValue: string;
+
+                    before(async() => {
+                        const stringArrayTemplate = format(StringArrayTemplate(), {
+                            stringArrayName,
+                            stringArrayFunctionName,
+                            stringArrayStorageItems: `'${cryptUtilsSwappedAlphabet.btoa('foo')}'`
+                        });
+                        const atobPolyfill = format(AtobTemplate(selfDefendingEnabled), {
+                            atobFunctionName
+                        });
+                        const atobDecodeTemplate: string = format(
+                            StringArrayBase64DecodeTemplate(randomGenerator),
+                            {
+                                atobPolyfill,
+                                atobFunctionName,
+                                selfDefendingCode: '',
+                                stringArrayCacheName,
+                                stringArrayCallsWrapperName
+                            }
+                        );
+                        const stringArrayCallsWrapperTemplate: string = await minimizeCode(
+                            format(StringArrayCallsWrapperTemplate(), {
+                                decodeCodeHelperTemplate: atobDecodeTemplate,
+                                indexShiftAmount,
+                                stringArrayCacheName,
+                                stringArrayCallsWrapperName,
+                                stringArrayFunctionName
+                            })
+                        );
+
+                        decodedValue = Function(`
+                            ${stringArrayTemplate}
+                        
+                            ${stringArrayCallsWrapperTemplate}
+                            
+                            return ${stringArrayCallsWrapperName}(${index});
+                        `)();
+                    });
+
+                    it('should correctly return decoded value', () => {
+                        assert.deepEqual(decodedValue, expectedDecodedValue);
+                    });
+                });
             });
 
-            it('should correctly return RegExp.$1 match without mutation by atob template', () => {
-                assert.deepEqual(decodedValue, expectedRegExpTestValue);
+            describe('Variant #2: invalid code evaluation for multi-line code', () => {
+                describe('Variant #1: long decoded string', () => {
+                    const index: string = '0x0';
+
+                    const indexShiftAmount: number = 0;
+
+                    const expectedDecodedValue: string = 'test18est1';
+
+                    let decodedValue: string;
+
+                    before(() => {
+                        const stringArrayTemplate = format(StringArrayTemplate(), {
+                            stringArrayName,
+                            stringArrayFunctionName,
+                            stringArrayStorageItems: `'${cryptUtilsSwappedAlphabet.btoa('test1test1')}'`
+                        });
+                        const atobPolyfill = format(AtobTemplate(selfDefendingEnabled), {
+                            atobFunctionName
+                        });
+                        const atobDecodeTemplate: string = format(
+                            StringArrayBase64DecodeTemplate(randomGenerator),
+                            {
+                                atobPolyfill,
+                                atobFunctionName,
+                                selfDefendingCode: '',
+                                stringArrayCacheName,
+                                stringArrayCallsWrapperName
+                            }
+                        );
+                        const stringArrayCallsWrapperTemplate: string = format(StringArrayCallsWrapperTemplate(), {
+                            decodeCodeHelperTemplate: atobDecodeTemplate,
+                            indexShiftAmount,
+                            stringArrayCacheName,
+                            stringArrayCallsWrapperName,
+                            stringArrayFunctionName
+                        });
+
+                        decodedValue = Function(`
+                            ${stringArrayTemplate}
+                        
+                            ${stringArrayCallsWrapperTemplate}
+                            
+                            return ${stringArrayCallsWrapperName}(${index});
+                        `)();
+                    });
+
+                    it('should return invalid decoded value', () => {
+                        assert.deepEqual(decodedValue, expectedDecodedValue);
+                    });
+                });
+
+                describe('Variant #2: 3-characters decoded string', () => {
+                    const index: string = '0x0';
+
+                    const indexShiftAmount: number = 0;
+
+                    const expectedDecodedValue: string = 'foo';
+
+                    let decodedValue: string;
+
+                    before(() => {
+                        const stringArrayTemplate = format(StringArrayTemplate(), {
+                            stringArrayName,
+                            stringArrayFunctionName,
+                            stringArrayStorageItems: `'${cryptUtilsSwappedAlphabet.btoa('foo')}'`
+                        });
+                        const atobPolyfill = format(AtobTemplate(selfDefendingEnabled), {
+                            atobFunctionName
+                        });
+                        const atobDecodeTemplate: string = format(
+                            StringArrayBase64DecodeTemplate(randomGenerator),
+                            {
+                                atobPolyfill,
+                                atobFunctionName,
+                                selfDefendingCode: '',
+                                stringArrayCacheName,
+                                stringArrayCallsWrapperName
+                            }
+                        );
+                        const stringArrayCallsWrapperTemplate: string = format(StringArrayCallsWrapperTemplate(), {
+                            decodeCodeHelperTemplate: atobDecodeTemplate,
+                            indexShiftAmount,
+                            stringArrayCacheName,
+                            stringArrayCallsWrapperName,
+                            stringArrayFunctionName
+                        });
+
+                        decodedValue = Function(`
+                            ${stringArrayTemplate}
+                        
+                            ${stringArrayCallsWrapperTemplate}
+                            
+                            return ${stringArrayCallsWrapperName}(${index});
+                        `)();
+                    });
+
+                    it('should return invalid decoded value', () => {
+                        assert.deepEqual(decodedValue, expectedDecodedValue);
+                    });
+                });
             });
         });
     });
 
     describe('Variant #2: `rc4` encoding', () => {
-        describe('Variant #1: index shift amount is `0`', () => {
-            const index: string = '0x0';
-            const key: string = 'key';
+        describe('Variant #1: `selfDefending` option is disabled', () => {
+            const selfDefendingEnabled: boolean = false;
 
-            const indexShiftAmount: number = 0;
+            describe('Variant #1: index shift amount is `0`', () => {
+                const index: string = '0x0';
+                const key: string = 'key';
 
-            const expectedDecodedValue: string = 'test1';
+                const indexShiftAmount: number = 0;
 
-            let decodedValue: string;
+                const expectedDecodedValue: string = 'test1';
 
-            before(() => {
-                const atobPolyfill = format(AtobTemplate(), {
-                    atobFunctionName
-                });
-                const rc4Polyfill = format(Rc4Template(), {
-                    atobFunctionName,
-                    rc4FunctionName
-                });
-                const rc4decodeCodeHelperTemplate: string = format(
-                    StringArrayRC4DecodeTemplate(randomGenerator),
-                    {
-                        atobPolyfill,
-                        rc4Polyfill,
-                        rc4FunctionName,
-                        selfDefendingCode: '',
+                let decodedValue: string;
+
+                before(() => {
+                    const stringArrayTemplate = format(StringArrayTemplate(), {
+                        stringArrayName,
+                        stringArrayFunctionName,
+                        stringArrayStorageItems: `'${cryptUtilsSwappedAlphabet.btoa(cryptUtilsSwappedAlphabet.rc4('test1', key))}'`
+                    });
+                    const atobPolyfill = format(AtobTemplate(selfDefendingEnabled), {
+                        atobFunctionName
+                    });
+                    const rc4Polyfill = format(Rc4Template(), {
+                        atobFunctionName,
+                        rc4FunctionName
+                    });
+                    const rc4decodeCodeHelperTemplate: string = format(
+                        StringArrayRC4DecodeTemplate(randomGenerator),
+                        {
+                            atobPolyfill,
+                            rc4Polyfill,
+                            rc4FunctionName,
+                            selfDefendingCode: '',
+                            stringArrayCacheName,
+                            stringArrayCallsWrapperName
+                        }
+                    );
+                    const stringArrayCallsWrapperTemplate: string = format(StringArrayCallsWrapperTemplate(), {
+                        decodeCodeHelperTemplate: rc4decodeCodeHelperTemplate,
+                        indexShiftAmount,
                         stringArrayCacheName,
-                        stringArrayCallsWrapperName
-                    }
-                );
-                const stringArrayCallsWrapperTemplate: string = format(StringArrayCallsWrapperTemplate(), {
-                    decodeCodeHelperTemplate: rc4decodeCodeHelperTemplate,
-                    indexShiftAmount,
-                    stringArrayCacheName,
-                    stringArrayCallsWrapperName,
-                    stringArrayName
+                        stringArrayCallsWrapperName,
+                        stringArrayFunctionName
+                    });
+
+                    decodedValue = Function(`
+                        ${stringArrayTemplate}
+                    
+                        ${stringArrayCallsWrapperTemplate}
+                        
+                        return ${stringArrayCallsWrapperName}('${index}', '${key}');
+                    `)();
                 });
 
-                decodedValue = Function(`
-                var ${stringArrayName} = ['${cryptUtilsSwappedAlphabet.btoa(cryptUtilsSwappedAlphabet.rc4('test1', key))}'];
-            
-                ${stringArrayCallsWrapperTemplate}
-                
-                return ${stringArrayCallsWrapperName}('${index}', '${key}');
-            `)();
-            });
-
-            it('should correctly return decoded value', () => {
-                assert.deepEqual(decodedValue, expectedDecodedValue);
+                it('should correctly return decoded value', () => {
+                    assert.deepEqual(decodedValue, expectedDecodedValue);
+                });
             });
-        });
 
-        describe('Variant #2: index shift amount is `5`', () => {
-            const index: string = '0x5';
-            const key: string = 'key';
+            describe('Variant #2: index shift amount is `5`', () => {
+                const index: string = '0x5';
+                const key: string = 'key';
 
-            const indexShiftAmount: number = 5;
+                const indexShiftAmount: number = 5;
 
-            const expectedDecodedValue: string = 'test1';
+                const expectedDecodedValue: string = 'test1';
 
-            let decodedValue: string;
+                let decodedValue: string;
 
-            before(() => {
-                const atobPolyfill = format(AtobTemplate(), {
-                    atobFunctionName
-                });
-                const rc4Polyfill = format(Rc4Template(), {
-                    atobFunctionName,
-                    rc4FunctionName
-                });
-                const rc4decodeCodeHelperTemplate: string = format(
-                    StringArrayRC4DecodeTemplate(randomGenerator),
-                    {
-                        atobPolyfill,
-                        rc4Polyfill,
-                        rc4FunctionName,
-                        selfDefendingCode: '',
+                before(() => {
+                    const stringArrayTemplate = format(StringArrayTemplate(), {
+                        stringArrayName,
+                        stringArrayFunctionName,
+                        stringArrayStorageItems: `'${cryptUtilsSwappedAlphabet.btoa(cryptUtilsSwappedAlphabet.rc4('test1', key))}'`
+                    });
+                    const atobPolyfill = format(AtobTemplate(selfDefendingEnabled), {
+                        atobFunctionName
+                    });
+                    const rc4Polyfill = format(Rc4Template(), {
+                        atobFunctionName,
+                        rc4FunctionName
+                    });
+                    const rc4decodeCodeHelperTemplate: string = format(
+                        StringArrayRC4DecodeTemplate(randomGenerator),
+                        {
+                            atobPolyfill,
+                            rc4Polyfill,
+                            rc4FunctionName,
+                            selfDefendingCode: '',
+                            stringArrayCacheName,
+                            stringArrayCallsWrapperName
+                        }
+                    );
+                    const stringArrayCallsWrapperTemplate: string = format(StringArrayCallsWrapperTemplate(), {
+                        decodeCodeHelperTemplate: rc4decodeCodeHelperTemplate,
+                        indexShiftAmount,
                         stringArrayCacheName,
-                        stringArrayCallsWrapperName
-                    }
-                );
-                const stringArrayCallsWrapperTemplate: string = format(StringArrayCallsWrapperTemplate(), {
-                    decodeCodeHelperTemplate: rc4decodeCodeHelperTemplate,
-                    indexShiftAmount,
-                    stringArrayCacheName,
-                    stringArrayCallsWrapperName,
-                    stringArrayName
+                        stringArrayCallsWrapperName,
+                        stringArrayFunctionName
+                    });
+
+                    decodedValue = Function(`
+                        ${stringArrayTemplate}
+                    
+                        ${stringArrayCallsWrapperTemplate}
+                        
+                        return ${stringArrayCallsWrapperName}('${index}', '${key}');
+                    `)();
                 });
 
-                decodedValue = Function(`
-                var ${stringArrayName} = ['${cryptUtilsSwappedAlphabet.btoa(cryptUtilsSwappedAlphabet.rc4('test1', key))}'];
-            
-                ${stringArrayCallsWrapperTemplate}
-                
-                return ${stringArrayCallsWrapperName}('${index}', '${key}');
-            `)();
+                it('should correctly return decoded value', () => {
+                    assert.deepEqual(decodedValue, expectedDecodedValue);
+                });
             });
+        });
 
-            it('should correctly return decoded value', () => {
-                assert.deepEqual(decodedValue, expectedDecodedValue);
+        describe('Variant #2: `selfDefending` option is enabled', () => {
+            const selfDefendingEnabled: boolean = true;
+
+            describe('Variant #1: correct code evaluation for single-line code', () => {
+                describe('Variant #1: long decoded string', () => {
+                    const index: string = '0x0';
+                    const key: string = 'key';
+
+                    const indexShiftAmount: number = 0;
+
+                    const expectedDecodedValue: string = 'test1';
+
+                    let decodedValue: string;
+
+                    before(async() => {
+                        const stringArrayTemplate = format(StringArrayTemplate(), {
+                            stringArrayName,
+                            stringArrayFunctionName,
+                            stringArrayStorageItems: `'${cryptUtilsSwappedAlphabet.btoa(cryptUtilsSwappedAlphabet.rc4('test1', key))}'`
+                        });
+                        const atobPolyfill = format(AtobTemplate(selfDefendingEnabled), {
+                            atobFunctionName
+                        });
+                        const rc4Polyfill = format(Rc4Template(), {
+                            atobFunctionName,
+                            rc4FunctionName
+                        });
+                        const rc4decodeCodeHelperTemplate: string = format(
+                            StringArrayRC4DecodeTemplate(randomGenerator),
+                            {
+                                atobPolyfill,
+                                rc4Polyfill,
+                                rc4FunctionName,
+                                selfDefendingCode: '',
+                                stringArrayCacheName,
+                                stringArrayCallsWrapperName
+                            }
+                        );
+                        const stringArrayCallsWrapperTemplate: string = await minimizeCode(
+                            format(StringArrayCallsWrapperTemplate(), {
+                                decodeCodeHelperTemplate: rc4decodeCodeHelperTemplate,
+                                indexShiftAmount,
+                                stringArrayCacheName,
+                                stringArrayCallsWrapperName,
+                                stringArrayFunctionName
+                            })
+                        );
+
+                        console.log(stringArrayCallsWrapperTemplate);
+
+                        decodedValue = Function(`
+                            ${stringArrayTemplate}
+                        
+                            ${stringArrayCallsWrapperTemplate}
+                            
+                            return ${stringArrayCallsWrapperName}('${index}', '${key}');
+                        `)();
+                    });
+
+                    it('should correctly return decoded value', () => {
+                        assert.deepEqual(decodedValue, expectedDecodedValue);
+                    });
+                });
+            });
+
+            describe('Variant #2: invalid code evaluation for multi-line code', () => {
+                describe('Variant #1: long decoded string', () => {
+                    const index: string = '0x0';
+                    const key: string = 'key';
+
+                    const indexShiftAmount: number = 0;
+
+                    const expectedDecodedValue: string = 'test\u001c';
+
+                    let decodedValue: string;
+
+                    before(() => {
+                        const stringArrayTemplate = format(StringArrayTemplate(), {
+                            stringArrayName,
+                            stringArrayFunctionName,
+                            stringArrayStorageItems: `'${cryptUtilsSwappedAlphabet.btoa(cryptUtilsSwappedAlphabet.rc4('test1', key))}'`
+                        });
+                        const atobPolyfill = format(AtobTemplate(selfDefendingEnabled), {
+                            atobFunctionName
+                        });
+                        const rc4Polyfill = format(Rc4Template(), {
+                            atobFunctionName,
+                            rc4FunctionName
+                        });
+                        const rc4decodeCodeHelperTemplate: string = format(
+                            StringArrayRC4DecodeTemplate(randomGenerator),
+                            {
+                                atobPolyfill,
+                                rc4Polyfill,
+                                rc4FunctionName,
+                                selfDefendingCode: '',
+                                stringArrayCacheName,
+                                stringArrayCallsWrapperName
+                            }
+                        );
+                        const stringArrayCallsWrapperTemplate: string = format(StringArrayCallsWrapperTemplate(), {
+                            decodeCodeHelperTemplate: rc4decodeCodeHelperTemplate,
+                            indexShiftAmount,
+                            stringArrayCacheName,
+                            stringArrayCallsWrapperName,
+                            stringArrayFunctionName
+                        });
+
+                        decodedValue = Function(`
+                            ${stringArrayTemplate}
+                        
+                            ${stringArrayCallsWrapperTemplate}
+                            
+                            return ${stringArrayCallsWrapperName}('${index}', '${key}');
+                        `)();
+                    });
+
+                    it('should correctly return decoded value', () => {
+                        assert.deepEqual(decodedValue, expectedDecodedValue);
+                    });
+                });
             });
         });
     });

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

@@ -7,13 +7,14 @@ import { IObfuscationResult } from '../../../../../../src/interfaces/source-code
 import { NO_ADDITIONAL_NODES_PRESET } from '../../../../../../src/options/presets/NoCustomNodes';
 
 import { JavaScriptObfuscator } from '../../../../../../src/JavaScriptObfuscatorFacade';
+import { getStringArrayRegExp } from '../../../../../helpers/get-string-array-regexp';
 import { readFileAsString } from '../../../../../helpers/readFileAsString';
 
 describe('StringArrayTemplate', () => {
     describe('Prevailing kind of variables', () => {
         describe('`var` kind', () => {
             let obfuscatedCode: string,
-                stringArrayRegExp: RegExp = /var (_0x(\w){4}) *= *\['.*'];/;
+                stringArrayRegExp: RegExp = getStringArrayRegExp(['foo'], {kind: 'var'});
 
             beforeEach(() => {
                 const code: string = readFileAsString(__dirname + '/fixtures/prevailing-kind-of-variables-var.js');
@@ -40,7 +41,7 @@ describe('StringArrayTemplate', () => {
 
         describe('`const` kind', () => {
             let obfuscatedCode: string,
-                stringArrayRegExp: RegExp = /const (_0x(\w){4}) *= *\['.*'];/;
+                stringArrayRegExp: RegExp = getStringArrayRegExp(['foo'], {kind: 'const'});
 
             beforeEach(() => {
                 const code: string = readFileAsString(__dirname + '/fixtures/prevailing-kind-of-variables-const.js');
@@ -67,7 +68,7 @@ describe('StringArrayTemplate', () => {
 
         describe('`let` kind', () => {
             let obfuscatedCode: string,
-                stringArrayRegExp: RegExp = /const (_0x(\w){4}) *= *\['.*'];/;
+                stringArrayRegExp: RegExp = getStringArrayRegExp(['foo'], {kind: 'const'});
 
             beforeEach(() => {
                 const code: string = readFileAsString(__dirname + '/fixtures/prevailing-kind-of-variables-let.js');

+ 74 - 23
test/functional-tests/generators/identifier-names-generators/dictionary-identifier-names-generator/DictionaryIdentifierNamesGenerator.spec.ts

@@ -4,8 +4,9 @@ import { IdentifierNamesGenerator } from '../../../../../src/enums/generators/id
 
 import { NO_ADDITIONAL_NODES_PRESET } from '../../../../../src/options/presets/NoCustomNodes';
 
-import { readFileAsString } from '../../../../helpers/readFileAsString';
 import { getRegExpMatch } from '../../../../helpers/getRegExpMatch';
+import { getStringArrayRegExp } from '../../../../helpers/get-string-array-regexp';
+import { readFileAsString } from '../../../../helpers/readFileAsString';
 
 import { JavaScriptObfuscator } from '../../../../../src/JavaScriptObfuscatorFacade';
 
@@ -13,10 +14,19 @@ describe('DictionaryIdentifierNamesGenerator', () => {
     describe('generateWithPrefix', () => {
         describe('Variant #1: should not generate same name for string array as existing name in code', () => {
             describe('Variant #1: `renameGlobals` option is disabled', () => {
-                const stringArrayStorageRegExp: RegExp = /const a[cABC] *= *\['_aa', *'_ab'];/;
-                const variableDeclarationIdentifierNameRegExp1: RegExp = /const aa *= *a[cABC]\(0x0\);/;
-                const variableDeclarationIdentifierNameRegExp2: RegExp = /const ab *= *a[cABC]\(0x1\);/;
+                const stringArrayStorageRegExp: RegExp = getStringArrayRegExp(
+                    ['_aa', '_ab'],
+                    {
+                        name: '\\w*',
+                        kind: 'const'
+                    }
+                );
+                const variableDeclarationIdentifierNameRegExp1: RegExp = /const (\w*) *= *\w*\(0x0\);/;
+                const variableDeclarationIdentifierNameRegExp2: RegExp = /const (\w*) *= *\w*\(0x1\);/;
 
+                let stringArrayName: string = '';
+                let variableDeclarationIdentifierName1: string = '';
+                let variableDeclarationIdentifierName2: string = '';
                 let obfuscatedCode: string;
 
                 before(() => {
@@ -34,26 +44,45 @@ describe('DictionaryIdentifierNamesGenerator', () => {
                             stringArrayThreshold: 1
                         }
                     ).getObfuscatedCode();
+
+                    stringArrayName = getRegExpMatch(obfuscatedCode, stringArrayStorageRegExp);
+                    variableDeclarationIdentifierName1 = getRegExpMatch(
+                        obfuscatedCode,
+                        variableDeclarationIdentifierNameRegExp1
+                    );
+                    variableDeclarationIdentifierName2 = getRegExpMatch(
+                        obfuscatedCode,
+                        variableDeclarationIdentifierNameRegExp2
+                    );
                 });
 
-                it('Match #1: should generate correct identifier for string array', () => {
+                it('Should generate correct identifier for string array', () => {
                     assert.match(obfuscatedCode, stringArrayStorageRegExp);
                 });
 
-                it('Match #2: should keep identifier name for existing variable declaration', () => {
-                    assert.match(obfuscatedCode, variableDeclarationIdentifierNameRegExp1);
+                it('Should keep identifier name for existing variable declaration #1', () => {
+                    assert.notEqual(stringArrayName, variableDeclarationIdentifierName1);
                 });
 
-                it('Match #3: should keep identifier name for existing variable declaration', () => {
-                    assert.match(obfuscatedCode, variableDeclarationIdentifierNameRegExp2);
+                it('Should keep identifier name for existing variable declaration #2', () => {
+                    assert.notEqual(stringArrayName, variableDeclarationIdentifierName2);
                 });
             });
 
             describe('Variant #2: `renameGlobals` option is enabled', () => {
-                const stringArrayStorageRegExp: RegExp = /const a[cABC] *= *\['_aa', *'_ab'];/;
-                const lastVariableDeclarationIdentifierNameRegExp1: RegExp = /const a[cABC] *= *a[cABC]\(0x0\);/;
-                const lastVariableDeclarationIdentifierNameRegExp2: RegExp = /const a[cABC] *= *a[cABC]\(0x1\);/;
+                const stringArrayStorageRegExp: RegExp = getStringArrayRegExp(
+                    ['_aa', '_ab'],
+                    {
+                        name: '\\w*',
+                        kind: 'const'
+                    }
+                );
+                const variableDeclarationIdentifierNameRegExp1: RegExp = /const (\w*) *= *\w*\(0x0\);/;
+                const variableDeclarationIdentifierNameRegExp2: RegExp = /const (\w*) *= *\w*\(0x1\);/;
 
+                let stringArrayName: string = '';
+                let variableDeclarationIdentifierName1: string = '';
+                let variableDeclarationIdentifierName2: string = '';
                 let obfuscatedCode: string;
 
                 before(() => {
@@ -64,7 +93,7 @@ describe('DictionaryIdentifierNamesGenerator', () => {
                         {
                             ...NO_ADDITIONAL_NODES_PRESET,
                             identifierNamesGenerator: IdentifierNamesGenerator.DictionaryIdentifierNamesGenerator,
-                            identifiersDictionary: ['a', 'b', 'c'],
+                            identifiersDictionary: ['a', 'b', 'c', 'd'],
                             identifiersPrefix: 'a',
                             renameGlobals: true,
                             transformObjectKeys: true,
@@ -72,18 +101,28 @@ describe('DictionaryIdentifierNamesGenerator', () => {
                             stringArrayThreshold: 1
                         }
                     ).getObfuscatedCode();
+
+                    stringArrayName = getRegExpMatch(obfuscatedCode, stringArrayStorageRegExp);
+                    variableDeclarationIdentifierName1 = getRegExpMatch(
+                        obfuscatedCode,
+                        variableDeclarationIdentifierNameRegExp1
+                    );
+                    variableDeclarationIdentifierName2 = getRegExpMatch(
+                        obfuscatedCode,
+                        variableDeclarationIdentifierNameRegExp2
+                    );
                 });
 
-                it('Match #1: should generate correct identifier for string array', () => {
+                it('Should generate correct identifier for string array', () => {
                     assert.match(obfuscatedCode, stringArrayStorageRegExp);
                 });
 
-                it('Match #2: should keep identifier name for existing variable declaration', () => {
-                    assert.match(obfuscatedCode, lastVariableDeclarationIdentifierNameRegExp1);
+                it('Should keep identifier name for existing variable declaration #1', () => {
+                    assert.notEqual(stringArrayName, variableDeclarationIdentifierName1);
                 });
 
-                it('Match #3: should keep identifier name for existing variable declaration', () => {
-                    assert.match(obfuscatedCode, lastVariableDeclarationIdentifierNameRegExp2);
+                it('Should keep identifier name for existing variable declaration #2', () => {
+                    assert.notEqual(stringArrayName, variableDeclarationIdentifierName2);
                 });
             });
         });
@@ -91,11 +130,17 @@ describe('DictionaryIdentifierNamesGenerator', () => {
         describe('Variant #2: should not generate same prefixed name for identifier in code as prefixed name of string array', function () {
             this.timeout(10000);
 
-            const samplesCount: number = 20;
+            const samplesCount: number = 30;
 
             describe('Variant #1: `renameGlobals` option is disabled', () => {
-                const stringArrayStorageRegExp: RegExp = /const ([abAB]{1,3}) *= *\['first', *'abc'];/;
-                const variableDeclarationIdentifierNameRegExp: RegExp = /const ([abAB]{1,3}){1,2} *= *[abAB]{1,3}\(0x0\);/;
+                const stringArrayStorageRegExp: RegExp = getStringArrayRegExp(
+                    ['first', 'abc'],
+                    {
+                        name: '\\w*',
+                        kind: 'const'
+                    }
+                );
+                const variableDeclarationIdentifierNameRegExp: RegExp = /const (\w*) *= *\w*\(0x0\);/;
 
                 let isIdentifiersAreConflicted: boolean = false;
 
@@ -133,8 +178,14 @@ describe('DictionaryIdentifierNamesGenerator', () => {
             });
 
             describe('Variant #2: `renameGlobals` option is enabled', () => {
-                const stringArrayStorageRegExp: RegExp = /const ([abAB]{1,3}) *= *\['first', *'abc'];/;
-                const variableDeclarationIdentifierNameRegExp: RegExp = /const ([abAB]{1,3}){1,2} *= *[abAB]{1,3}\(0x0\);/;
+                const stringArrayStorageRegExp: RegExp = getStringArrayRegExp(
+                    ['first', 'abc'],
+                    {
+                        name: '\\w*',
+                        kind: 'const'
+                    }
+                );
+                const variableDeclarationIdentifierNameRegExp: RegExp = /const (\w*) *= *\w*\(0x0\);/;
 
                 let isIdentifiersAreConflicted: boolean = false;
 

+ 0 - 1
test/functional-tests/generators/identifier-names-generators/dictionary-identifier-names-generator/fixtures/string-array-storage-name-conflict-2.js

@@ -1,5 +1,4 @@
 function foo () {
     const testA = 'first';
     const testB = 'abc';
-    const testC = 'abc';
 }

+ 25 - 6
test/functional-tests/generators/identifier-names-generators/mangled-identifier-names-generator/MangledIdentifierNamesGenerator.spec.ts

@@ -4,6 +4,7 @@ import { IdentifierNamesGenerator } from '../../../../../src/enums/generators/id
 
 import { NO_ADDITIONAL_NODES_PRESET } from '../../../../../src/options/presets/NoCustomNodes';
 
+import { getStringArrayRegExp } from '../../../../helpers/get-string-array-regexp';
 import { readFileAsString } from '../../../../helpers/readFileAsString';
 
 import { JavaScriptObfuscator } from '../../../../../src/JavaScriptObfuscatorFacade';
@@ -12,7 +13,10 @@ describe('MangledIdentifierNamesGenerator', () => {
     describe('generateWithPrefix', () => {
         describe('Variant #1: should not generate same name for string array as existing name in code', () => {
             describe('Variant #1: `renameGlobals` option is disabled', () => {
-                const stringArrayStorageRegExp: RegExp = /const ab *= *\['abc'];/;
+                const stringArrayStorageRegExp: RegExp = getStringArrayRegExp(['abc'], {
+                    kind: 'const',
+                    name: 'ab'
+                });
                 const lastVariableDeclarationIdentifierNameRegExp: RegExp = /const aa *= *ac\(0x0\);/;
 
                 let obfuscatedCode: string;
@@ -43,7 +47,10 @@ describe('MangledIdentifierNamesGenerator', () => {
             });
 
             describe('Variant #2: `renameGlobals` option is enabled', () => {
-                const stringArrayStorageRegExp: RegExp = /const ab *= *\['abc'];/;
+                const stringArrayStorageRegExp: RegExp = getStringArrayRegExp(['abc'], {
+                    kind: 'const',
+                    name: 'ab'
+                });
                 const lastVariableDeclarationIdentifierNameRegExp: RegExp = /const aB *= *ac\(0x0\);/;
 
                 let obfuscatedCode: string;
@@ -77,7 +84,10 @@ describe('MangledIdentifierNamesGenerator', () => {
 
         describe('Variant #2: should not generate same prefixed name for identifier in code as prefixed name of string array', () => {
             describe('Variant #1: `renameGlobals` option is disabled', () => {
-                const stringArrayStorageRegExp: RegExp = /const aa *= *\['abc', *'last'];/;
+                const stringArrayStorageRegExp: RegExp = getStringArrayRegExp(['abc', 'last'], {
+                    kind: 'const',
+                    name: 'aa'
+                });
                 const functionDeclarationIdentifierNameRegExp: RegExp = /function foo *\(\) *{/;
                 const lastVariableDeclarationIdentifierNameRegExp: RegExp = /const ac *= *ab\(0x1\);/;
 
@@ -113,7 +123,10 @@ describe('MangledIdentifierNamesGenerator', () => {
             });
 
             describe('Variant #2: `renameGlobals` option is enabled', () => {
-                const stringArrayStorageRegExp: RegExp = /const aa *= *\['abc', *'last'];/;
+                const stringArrayStorageRegExp: RegExp = getStringArrayRegExp(['abc', 'last'], {
+                    kind: 'const',
+                    name: 'aa'
+                });
                 const functionDeclarationIdentifierNameRegExp: RegExp = /function ac *\(\) *{/;
                 const lastVariableDeclarationIdentifierNameRegExp: RegExp = /const ad *= *ab\(0x1\);/;
 
@@ -221,7 +234,10 @@ describe('MangledIdentifierNamesGenerator', () => {
 
         describe('Variant #2: Should generate different names set for different lexical scopes when string array is enabled', () => {
             describe('Variant #1: `renameGlobals` option is disabled', () => {
-                const stringArrayIdentifierRegExp: RegExp = /var a *= *\['abc'];/;
+                const stringArrayIdentifierRegExp: RegExp = getStringArrayRegExp(['abc'], {
+                    kind: 'var',
+                    name: 'a'
+                });
                 const variableIdentifierRegExp: RegExp = /var foo *= *b\(0x0\);/;
                 const functionDeclarationRegExp1: RegExp = /function bar *\(c, *d\) *{}/;
                 const functionDeclarationRegExp2: RegExp = /function baz *\(c, *d\) *{}/;
@@ -260,7 +276,10 @@ describe('MangledIdentifierNamesGenerator', () => {
             });
 
             describe('Variant #2: `renameGlobals` option is enabled', () => {
-                const stringArrayIdentifierRegExp: RegExp = /var a *= *\['abc'];/;
+                const stringArrayIdentifierRegExp: RegExp = getStringArrayRegExp(['abc'], {
+                    kind: 'var',
+                    name: 'a'
+                });
                 const variableIdentifierRegExp: RegExp = /var c *= *b\(0x0\);/;
                 const functionDeclarationRegExp1: RegExp = /function d *\(f, *g\) *{}/;
                 const functionDeclarationRegExp2: RegExp = /function e *\(f, *g\) *{}/;

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

@@ -27,6 +27,7 @@ import { OptionsPreset } from '../../../src/enums/options/presets/OptionsPreset'
 
 import { buildLargeCode } from '../../helpers/buildLargeCode';
 import { getRegExpMatch } from '../../helpers/getRegExpMatch';
+import { getStringArrayRegExp } from '../../helpers/get-string-array-regexp';
 import { readFileAsString } from '../../helpers/readFileAsString';
 
 describe('JavaScriptObfuscator', () => {
@@ -444,8 +445,13 @@ describe('JavaScriptObfuscator', () => {
             });
 
             describe('Variant #4: with `stringArray`, `renameGlobals` and `identifiersPrefix` options', () => {
-                const stringArrayRegExp: RegExp = /^var foo_0x(\w){4} *= *\['abc'\];/;
-                const stringArrayCallRegExp: RegExp = /var foo_0x(\w){4,6} *= *foo_0x(\w){4}\(0x0\);$/;
+                const stringArrayRegExp: RegExp = new RegExp(
+                    'function foo_0x([a-f0-9]){4} *\\(\\) *{' +
+                        'var _0x([a-f0-9]){4,6} *= *\\[\'abc\'];.*' +
+                        'return foo_0x([a-f0-9]){4}\\(\\); *' +
+                    '}'
+                );
+                const stringArrayCallRegExp: RegExp = /var foo_0x(\w){4,6} *= *foo_0x(\w){4}\(0x0\);/;
 
                 let obfuscatedCode: string;
 
@@ -536,8 +542,8 @@ describe('JavaScriptObfuscator', () => {
         });
 
         describe('latin literal variable value', () => {
-            const stringArrayLatinRegExp: RegExp = /^var _0x(\w){4} *= *\['abc'\];/;
-            const stringArrayCallRegExp: RegExp = /var test *= *_0x(\w){4}\(0x0\);$/;
+            const stringArrayLatinRegExp: RegExp = getStringArrayRegExp(['abc']);
+            const stringArrayCallRegExp: RegExp = /var test *= *_0x(\w){4}\(0x0\);/;
 
             let obfuscatedCode: string;
 
@@ -564,8 +570,13 @@ describe('JavaScriptObfuscator', () => {
         });
 
         describe('cyrillic literal variable value', () => {
-            const stringArrayCyrillicRegExp: RegExp = /^var _0x(\w){4} *= *\['абц'\];/;
-            const stringArrayCallRegExp: RegExp = /var test *= *_0x(\w){4}\(0x0\);$/;
+            const stringArrayCyrillicRegExp: RegExp = new RegExp(
+                'function _0x(\\w){4} *\\(\\) *{' +
+                    'var _0x([a-f0-9]){4,6} *= *\\[\'абц\'];.*' +
+                    'return _0x(\\w){4}\\(\\); *' +
+                '}'
+            );
+            const stringArrayCallRegExp: RegExp = /var test *= *_0x(\w){4}\(0x0\);/;
 
             let obfuscatedCode: string;
 
@@ -689,7 +700,12 @@ describe('JavaScriptObfuscator', () => {
                 const code1: string = readFileAsString(__dirname + '/fixtures/simple-input-cyrillic.js');
                 const code2: string = readFileAsString(__dirname + '/fixtures/simple-input-2.js');
 
-                const regExp: RegExp = /var (_0x(\w){4}) *= *\['.*'\];/;
+                const regExp: RegExp = new RegExp(
+                    'function _0x(\\w){4} *\\(\\) *{' +
+                        'var _0x([a-f0-9]){4,6} *= *\\[\'.*\'];.*' +
+                        'return _0x(\\w){4}\\(\\); *' +
+                    '}'
+                );
 
                 let match1: string,
                     match2: string;

+ 5 - 4
test/functional-tests/node-transformers/converting-transformers/class-field-transformer/ClassFieldTransformer.spec.ts

@@ -2,6 +2,7 @@ import { assert } from 'chai';
 
 import { NO_ADDITIONAL_NODES_PRESET } from '../../../../../src/options/presets/NoCustomNodes';
 
+import { getStringArrayRegExp } from '../../../../helpers/get-string-array-regexp';
 import { readFileAsString } from '../../../../helpers/readFileAsString';
 
 import { JavaScriptObfuscator } from '../../../../../src/JavaScriptObfuscatorFacade';
@@ -31,7 +32,7 @@ describe('ClassFieldTransformer', () => {
             });
 
             describe('Variant #2: `stringArray` option is enabled', () => {
-                const stringArrayRegExp: RegExp = /var _0x([a-f0-9]){4} *= *\['property', *'bar'\];/;
+                const stringArrayRegExp: RegExp = getStringArrayRegExp(['property', 'bar']);
                 const stringArrayCallRegExp: RegExp = /\[_0x([a-f0-9]){4}\(0x1\)\]\(\)\{\}/;
 
                 let obfuscatedCode: string;
@@ -103,7 +104,7 @@ describe('ClassFieldTransformer', () => {
             });
 
             describe('Variant #2: `stringArray` option is enabled', () => {
-                const stringArrayRegExp: RegExp = /var _0x([a-f0-9]){4} *= *\['property', *'constructor', *'bar'];/;
+                const stringArrayRegExp: RegExp = getStringArrayRegExp(['property', 'constructor', 'bar']);
                 const stringArrayCallRegExp: RegExp = /\[_0x([a-f0-9]){4}\(0x2\)\]\(\)\{\}/;
 
                 let obfuscatedCode: string;
@@ -203,7 +204,7 @@ describe('ClassFieldTransformer', () => {
             });
 
             describe('Variant #2: `stringArray` option is enabled', () => {
-                const stringArrayRegExp: RegExp = /var _0x([a-f0-9]){4} *= *\['property', *'bar'\];/;
+                const stringArrayRegExp: RegExp = getStringArrayRegExp(['property', 'bar']);
                 const stringArrayCallRegExp: RegExp = /\[_0x([a-f0-9]){4}\(0x0\)\] *= *0x1;/;
 
                 let obfuscatedCode: string;
@@ -254,7 +255,7 @@ describe('ClassFieldTransformer', () => {
             });
 
             describe('Variant #2: `stringArray` option is enabled', () => {
-                const stringArrayRegExp: RegExp = /var _0x([a-f0-9]){4} *= *\['property', *'constructor', *'bar'];/;
+                const stringArrayRegExp: RegExp = getStringArrayRegExp(['property', 'constructor', 'bar']);
                 const stringArrayCallRegExp: RegExp = /\[_0x([a-f0-9]){4}\(0x0\)\] *= *0x1;/;
 
                 let obfuscatedCode: string;

+ 3 - 2
test/functional-tests/node-transformers/converting-transformers/member-expression-transformer/MemberExpressionTransformer.spec.ts

@@ -2,6 +2,7 @@ import { assert } from 'chai';
 
 import { NO_ADDITIONAL_NODES_PRESET } from '../../../../../src/options/presets/NoCustomNodes';
 
+import { getStringArrayRegExp } from '../../../../helpers/get-string-array-regexp';
 import { readFileAsString } from '../../../../helpers/readFileAsString';
 
 import { JavaScriptObfuscator } from '../../../../../src/JavaScriptObfuscatorFacade';
@@ -30,7 +31,7 @@ describe('MemberExpressionTransformer', () => {
         });
 
         describe('`stringArray` option is enabled', () => {
-            const stringArrayRegExp: RegExp = /var _0x([a-f0-9]){4} *= *\['log'\];/;
+            const stringArrayRegExp: RegExp = getStringArrayRegExp(['log']);
             const stringArrayCallRegExp: RegExp = /var test *= *console\[_0x([a-f0-9]){4}\(0x0\)\];/;
 
             let obfuscatedCode: string;
@@ -60,7 +61,7 @@ describe('MemberExpressionTransformer', () => {
 
     describe('transformation of member expression node with square brackets', () => {
         describe('Variant #1: square brackets literal ', () => {
-            const stringArrayRegExp: RegExp = /var _0x([a-f0-9]){4} *= *\['log'\];/;
+            const stringArrayRegExp: RegExp = getStringArrayRegExp(['log']);
             const stringArrayCallRegExp: RegExp = /var test *= *console\[_0x([a-f0-9]){4}\(0x0\)\];/;
 
             let obfuscatedCode: string;

+ 11 - 4
test/functional-tests/node-transformers/finalizing-transformers/directive-placement-transformer/DirectivePlacementTransformer.spec.ts

@@ -2,6 +2,7 @@ import { assert } from 'chai';
 
 import { NO_ADDITIONAL_NODES_PRESET } from '../../../../../src/options/presets/NoCustomNodes';
 
+import { getStringArrayRegExp } from '../../../../helpers/get-string-array-regexp';
 import { readFileAsString } from '../../../../helpers/readFileAsString';
 
 import { JavaScriptObfuscator } from '../../../../../src/JavaScriptObfuscatorFacade';
@@ -11,7 +12,10 @@ describe('DirectivePlacementTransformer', function () {
 
     describe('Variant #1: program scope', () => {
         describe('Variant #1: directive at the top of program scope', () => {
-            const directiveRegExp: RegExp = /^'use strict'; *var _0x([a-f0-9]){4} *= *\['test'];/;
+            const directiveRegExp: RegExp = new RegExp(
+                '^\'use strict\';.*' +
+                getStringArrayRegExp(['test']).source
+            );
 
             let obfuscatedCode: string;
 
@@ -34,9 +38,8 @@ describe('DirectivePlacementTransformer', function () {
         });
 
         describe('Variant #2: directive-like string literal at the middle of program scope', () => {
+            const stringArrayStorageRegExp: RegExp = getStringArrayRegExp(['test', 'use\\\\x20strict']);
             const directiveRegExp: RegExp = new RegExp(
-                '^var _0x([a-f0-9]){4} *= *\\[\'test\', *\'use\\\\x20strict\']; *' +
-                '.*?' +
                 'var test *= *_0x([a-f0-9]){4}\\(0x0\\);.*' +
                 '_0x([a-f0-9]){4}\\(0x1\\);'
             );
@@ -56,7 +59,11 @@ describe('DirectivePlacementTransformer', function () {
                 ).getObfuscatedCode();
             });
 
-            it('should keep directive-like string literal at the middle of program scope', () => {
+            it('should add directive-like string literal to the string array', () => {
+                assert.match(obfuscatedCode, stringArrayStorageRegExp);
+            });
+
+            it('should add call to the directive-like string literal from the string array', () => {
                 assert.match(obfuscatedCode, directiveRegExp);
             });
         });

+ 2 - 1
test/functional-tests/node-transformers/finalizing-transformers/escape-sequence-transformer/EscapeSequenceTransformer.spec.ts

@@ -5,6 +5,7 @@ import { NO_ADDITIONAL_NODES_PRESET } from '../../../../../src/options/presets/N
 import { IdentifierNamesGenerator } from '../../../../../src/enums/generators/identifier-names-generators/IdentifierNamesGenerator';
 import { StringArrayIndexesType } from '../../../../../src/enums/node-transformers/string-array-transformers/StringArrayIndexesType';
 
+import { getStringArrayRegExp } from '../../../../helpers/get-string-array-regexp';
 import { readFileAsString } from '../../../../helpers/readFileAsString';
 
 import { JavaScriptObfuscator } from '../../../../../src/JavaScriptObfuscatorFacade';
@@ -57,7 +58,7 @@ describe('EscapeSequenceTransformer', function () {
     });
 
     describe('Variant #3: `unicodeEscapeSequence` and `stringArray` options are enabled', () => {
-        const stringArrayRegExp: RegExp = /^var _0x([a-f0-9]){4} *= *\['\\x74\\x65\\x73\\x74'\];/;
+        const stringArrayRegExp: RegExp = getStringArrayRegExp(['\\\\x74\\\\x65\\\\x73\\\\x74']);
         const stringArrayCallRegExp: RegExp = /var test *= *_0x([a-f0-9]){4}\('\\x30\\x78\\x30'\);/;
 
         let obfuscatedCode: string;

+ 5 - 1
test/functional-tests/node-transformers/initializing-transformers/comments-transformer/CommentsTransformer.spec.ts

@@ -5,6 +5,7 @@ import { NO_ADDITIONAL_NODES_PRESET } from '../../../../../src/options/presets/N
 import { readFileAsString } from '../../../../helpers/readFileAsString';
 
 import { JavaScriptObfuscator } from '../../../../../src/JavaScriptObfuscatorFacade';
+import { getStringArrayRegExp } from '../../../../helpers/get-string-array-regexp';
 
 describe('CommentsTransformer', () => {
     const lineSeparatorEscaped: string = '\\r?\\n';
@@ -142,7 +143,10 @@ describe('CommentsTransformer', () => {
 
     describe('Variant #7: simple comment with preserved words and additional code helper is inserted', () => {
         describe('Variant #1: `stringArray` code helper', () => {
-            const regExp: RegExp = /^\/\/ *@license *test *comment *\n*var _0x([a-f0-9]){4} *= *\['abc'];/;
+            const regExp: RegExp = new RegExp(
+                '^\\/\\/ *@license *test *comment *\\n*.*' +
+                getStringArrayRegExp(['abc']).source
+            );
 
             let obfuscatedCode: string;
 

+ 4 - 3
test/functional-tests/node-transformers/preparing-transformers/eval-call-expression-transformer/EvalCallExpressionTransformer.spec.ts

@@ -4,10 +4,11 @@ import { NO_ADDITIONAL_NODES_PRESET } from '../../../../../src/options/presets/N
 
 import { StringArrayIndexesType } from '../../../../../src/enums/node-transformers/string-array-transformers/StringArrayIndexesType';
 
+import { getRegExpMatch } from '../../../../helpers/getRegExpMatch';
+import { getStringArrayRegExp } from '../../../../helpers/get-string-array-regexp';
 import { readFileAsString } from '../../../../helpers/readFileAsString';
 
 import { JavaScriptObfuscator } from '../../../../../src/JavaScriptObfuscatorFacade';
-import { getRegExpMatch } from '../../../../helpers/getRegExpMatch';
 
 describe('EvalCallExpressionTransformer', () => {
     describe('Variant #1: identifier reference', () => {
@@ -95,7 +96,7 @@ describe('EvalCallExpressionTransformer', () => {
 
     describe('Variant #4: string array calls wrapper call', () => {
         describe('Variant #1: hexadecimal number indexes type', () => {
-            const stringArrayRegExp: RegExp = /var _0x([a-f0-9]){4} *= *\['log', *'bar'];/;
+            const stringArrayRegExp: RegExp = getStringArrayRegExp(['log', 'bar']);
             const stringArrayCallsWrapperRegExp: RegExp = /eval *\('console\[_0x([a-f0-9]){4,6}\(0\)]\(_0x([a-f0-9]){4,6}\(1\)\);'\);/;
 
             let obfuscatedCode: string;
@@ -126,7 +127,7 @@ describe('EvalCallExpressionTransformer', () => {
         });
 
         describe('Variant #1: hexadecimal numeric string indexes type', () => {
-            const stringArrayRegExp: RegExp = /var _0x([a-f0-9]){4} *= *\['log', *'bar'];/;
+            const stringArrayRegExp: RegExp = getStringArrayRegExp(['log', 'bar']);
             const stringArrayCallsWrapperRegExp: RegExp = /eval *\('console\[_0x([a-f0-9]){4,6}\(\\'0x0\\'\)]\(_0x([a-f0-9]){4,6}\(\\'0x1\\'\)\);'\);/;
 
             let obfuscatedCode: string;

+ 3 - 2
test/functional-tests/node-transformers/preparing-transformers/obfuscating-guards/black-list-obfuscating-guard/BlackListObfuscatingGuard.spec.ts

@@ -4,14 +4,15 @@ import { JavaScriptObfuscator } from '../../../../../../src/JavaScriptObfuscator
 
 import { NO_ADDITIONAL_NODES_PRESET } from '../../../../../../src/options/presets/NoCustomNodes';
 
+import { getStringArrayRegExp } from '../../../../../helpers/get-string-array-regexp';
 import { readFileAsString } from '../../../../../helpers/readFileAsString';
 
 describe('BlackListObfuscatingGuard', () => {
     describe('check', () => {
         describe('`\'use strict\';` operator', () => {
             const useStrictOperatorRegExp: RegExp = /'use *strict';/;
-            const stringArrayLatinRegExp: RegExp = /var _0x(\w){4} *= *\['abc'\];/;
-            const stringArrayCallRegExp: RegExp = /var test *= *_0x(\w){4}\(0x0\);$/;
+            const stringArrayLatinRegExp: RegExp = getStringArrayRegExp(['abc']);
+            const stringArrayCallRegExp: RegExp = /var test *= *_0x(\w){4}\(0x0\)/;
 
             let obfuscatedCode: string;
 

+ 8 - 8
test/functional-tests/node-transformers/preparing-transformers/obfuscating-guards/conditional-comment-obfuscating-guard/ConditionalCommentObfuscatingGuard.spec.ts

@@ -10,9 +10,9 @@ describe('ConditionalCommentObfuscatingGuard', () => {
     describe('check', () => {
         describe('Variant #1: `disable` conditional comment', () => {
             const disableConditionalCommentRegExp: RegExp = /\/\/ *javascript-obfuscator:disable/;
-            const obfuscatedVariableDeclarationRegExp: RegExp = /var _0x([a-f0-9]){4,6} *= *0x1;/;
+            const obfuscatedVariableDeclarationRegExp: RegExp = /var _0x([a-f0-9]){5,6} *= *0x1;/;
             const ignoredVariableDeclarationRegExp: RegExp = /var bar *= *2;/;
-            const consoleLogRegExp: RegExp = /console.log\(_0x([a-f0-9]){4,6}\);/;
+            const consoleLogRegExp: RegExp = /console.log\(_0x([a-f0-9]){5,6}\);/;
 
             let obfuscatedCode: string;
 
@@ -47,8 +47,8 @@ describe('ConditionalCommentObfuscatingGuard', () => {
         describe('Variant #2: `disable` and `enable` conditional comments #1', () => {
             const disableConditionalCommentRegExp: RegExp = /\/\/ *javascript-obfuscator:disable/;
             const enableConditionalCommentRegExp: RegExp = /\/\/ *javascript-obfuscator:enable/;
-            const obfuscatedVariableDeclaration1RegExp: RegExp = /var _0x([a-f0-9]){4,6} *= *0x1;/;
-            const obfuscatedVariableDeclaration2RegExp: RegExp = /var _0x([a-f0-9]){4,6} *= *0x3;/;
+            const obfuscatedVariableDeclaration1RegExp: RegExp = /var _0x([a-f0-9]){5,6} *= *0x1;/;
+            const obfuscatedVariableDeclaration2RegExp: RegExp = /var _0x([a-f0-9]){5,6} *= *0x3;/;
             const ignoredVariableDeclarationRegExp: RegExp = /var bar *= *2;/;
 
             let obfuscatedCode: string;
@@ -87,7 +87,7 @@ describe('ConditionalCommentObfuscatingGuard', () => {
 
         describe('Variant #3: `disable` and `enable` conditional comments #2', () => {
             const ignoredVariableDeclarationRegExp: RegExp = /var foo *= *1;/;
-            const obfuscatedVariableDeclarationRegExp: RegExp = /var _0x([a-f0-9]){4,6} *= *0x2;/;
+            const obfuscatedVariableDeclarationRegExp: RegExp = /var _0x([a-f0-9]){5,6} *= *0x2;/;
 
             let obfuscatedCode: string;
 
@@ -139,13 +139,13 @@ describe('ConditionalCommentObfuscatingGuard', () => {
         });
 
         describe('Variant #5: `disable` and `enable` conditional comments with dead code injection', () => {
-            const obfuscatedFunctionExpressionRegExp: RegExp = /var _0x([a-f0-9]){4,6} *= *function *\(_0x([a-f0-9]){4,6}, *_0x([a-f0-9]){4,6}, *_0x([a-f0-9]){4,6}\) *{/g;
+            const obfuscatedFunctionExpressionRegExp: RegExp = /var _0x([a-f0-9]){5,6} *= *function *\(_0x([a-f0-9]){5,6}, *_0x([a-f0-9]){5,6}, *_0x([a-f0-9]){5,6}\) *{/g;
             const expectedObfuscatedFunctionExpressionLength: number = 3;
 
             const ignoredFunctionExpression1RegExp: RegExp = /var bar *= *function *\(a, *b, *c\) *{/;
             const ignoredFunctionExpression2RegExp: RegExp = /var baz *= *function *\(a, *b, *c\) *{/;
 
-            const obfuscatedFunctionCallRegExp: RegExp = /_0x([a-f0-9]){4,6}\( *\);/g;
+            const obfuscatedFunctionCallRegExp: RegExp = /_0x([a-f0-9]){5,6}\( *\);/g;
             const expectedObfuscatedFunctionCallsLength: number = 3;
 
             const ignoredFunctionCall1RegExp: RegExp = /bar\( *\);/;
@@ -209,7 +209,7 @@ describe('ConditionalCommentObfuscatingGuard', () => {
         });
 
         describe('Variant #6: `disable` and `enable` conditional comments with control flow flattening', () => {
-            const obfuscatedVariableDeclarationRegExp: RegExp = /var _0x([a-f0-9]){4,6} *= *_0x([a-f0-9]){4,6}\['[a-zA-Z0-9]{1,5}'];/;
+            const obfuscatedVariableDeclarationRegExp: RegExp = /var _0x([a-f0-9]){5,6} *= *_0x([a-f0-9]){5,6}\['[a-zA-Z0-9]{1,5}'];/;
             const ignoredVariableDeclarationRegExp: RegExp = /var bar *= *'bar';/;
 
             let obfuscatedCode: string;

+ 9 - 2
test/functional-tests/node-transformers/preparing-transformers/variable-preserve-transformer/VariablePreserveTransformer.spec.ts

@@ -2,6 +2,7 @@ import { assert } from 'chai';
 
 import { NO_ADDITIONAL_NODES_PRESET } from '../../../../../src/options/presets/NoCustomNodes';
 
+import { getStringArrayRegExp } from '../../../../helpers/get-string-array-regexp';
 import { readFileAsString } from '../../../../helpers/readFileAsString';
 import { stubNodeTransformers } from '../../../../helpers/stubNodeTransformers';
 
@@ -11,7 +12,10 @@ import { ObjectPatternPropertiesTransformer } from '../../../../../src/node-tran
 describe('VariablePreserveTransformer', () => {
     describe('Variant #1: string array storage name conflicts with identifier name', () => {
         describe('Variant #1: `renameGlobals` option is disabled', () => {
-            const stringArrayStorageNameRegExp: RegExp = /const b *= *\['abc'];/;
+            const stringArrayStorageNameRegExp: RegExp = getStringArrayRegExp(['abc'], {
+                kind: 'const',
+                name: 'b'
+            });
             const identifierNameRegExp: RegExp = /const a *= *c\(0x0\);/;
 
             let obfuscatedCode: string;
@@ -40,7 +44,10 @@ describe('VariablePreserveTransformer', () => {
         });
 
         describe('Variant #2: `renameGlobals` option is enabled', () => {
-            const stringArrayStorageNameRegExp: RegExp = /const b *= *\['abc'];/;
+            const stringArrayStorageNameRegExp: RegExp = getStringArrayRegExp(['abc'], {
+                kind: 'const',
+                name: 'b'
+            });
             const identifierNameRegExp: RegExp = /const d *= *c\(0x0\);/;
 
             let obfuscatedCode: string;

+ 59 - 1
test/functional-tests/node-transformers/string-array-transformers/string-array-rotate-function-transformer/StringArrayRotateFunctionTransformer.spec.ts

@@ -1,7 +1,9 @@
 import { assert } from 'chai';
+import * as sinon from 'sinon';
 
 import { NO_ADDITIONAL_NODES_PRESET } from '../../../../../src/options/presets/NoCustomNodes';
 
+import { evaluateInWorker } from '../../../../helpers/evaluateInWorker';
 import { readFileAsString } from '../../../../helpers/readFileAsString';
 
 import { IdentifierNamesGenerator } from '../../../../../src/enums/generators/identifier-names-generators/IdentifierNamesGenerator';
@@ -10,8 +12,12 @@ import { StringArrayIndexesType } from '../../../../../src/enums/node-transforme
 import { StringArrayWrappersType } from '../../../../../src/enums/node-transformers/string-array-transformers/StringArrayWrappersType';
 
 import { JavaScriptObfuscator } from '../../../../../src/JavaScriptObfuscatorFacade';
+import { NumberNumericalExpressionAnalyzer } from '../../../../../src/analyzers/number-numerical-expression-analyzer/NumberNumericalExpressionAnalyzer';
+import { StringArrayRotateFunctionTransformer } from '../../../../../src/node-transformers/string-array-transformers/StringArrayRotateFunctionTransformer';
+
+describe('StringArrayRotateFunctionTransformer', function () {
+    this.timeout(120000);
 
-describe('StringArrayRotateFunctionTransformer', () => {
     describe('Code helper append', () => {
         const regExp: RegExp = /while *\(!!\[]\) *\{/;
 
@@ -195,5 +201,57 @@ describe('StringArrayRotateFunctionTransformer', () => {
                 assert.equal(hasRuntimeErrors, false);
             });
         });
+
+        describe('Prevent early successful comparison', () => {
+            const evaluationTimeout:  number = 1000;
+            const samplesCount: number = 100;
+
+            let numberNumericalExpressionAnalyzerAnalyzeStub: sinon.SinonStub;
+            let stringArrayRotateFunctionTransformerGetComparisonValueStub: sinon.SinonStub;
+
+            let obfuscatedCode: string;
+            let evaluationError: Error | null = null;
+
+            before(async () => {
+                stringArrayRotateFunctionTransformerGetComparisonValueStub = sinon
+                    .stub(<any>StringArrayRotateFunctionTransformer.prototype, 'getComparisonValue')
+                    .returns(5);
+                numberNumericalExpressionAnalyzerAnalyzeStub = sinon
+                    .stub(NumberNumericalExpressionAnalyzer.prototype, 'analyze')
+                    .returns([[1, 2], 0, 3]);
+
+                const code: string = readFileAsString(__dirname + '/fixtures/early-successful-comparison.js');
+
+                for (let i = 0; i < samplesCount; i++) {
+                    obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                        code,
+                        {
+                            seed: i,
+                            rotateStringArray: true,
+                            shuffleStringArray: true,
+                            stringArray: true,
+                            stringArrayThreshold: 1
+                        }
+                    ).getObfuscatedCode();
+
+                    try {
+                        await evaluateInWorker(obfuscatedCode, evaluationTimeout);
+                    } catch (error) {
+                        evaluationError = error
+
+                        break;
+                    }
+                }
+            });
+
+            it('should correctly evaluate code', () => {
+                assert.equal(evaluationError, null);
+            });
+
+            after(() => {
+                numberNumericalExpressionAnalyzerAnalyzeStub.restore();
+                stringArrayRotateFunctionTransformerGetComparisonValueStub.restore();
+            })
+        });
     });
 });

+ 4 - 0
test/functional-tests/node-transformers/string-array-transformers/string-array-rotate-function-transformer/fixtures/early-successful-comparison.js

@@ -0,0 +1,4 @@
+function hi() {
+    console.log("Hello World!");
+}
+hi();

+ 0 - 11
test/functional-tests/node-transformers/string-array-transformers/string-array-scope-calls-wrapper-transformer/StringArrayScopeCallsWrapperTransformer.spec.ts

@@ -651,11 +651,9 @@ describe('StringArrayScopeCallsWrapperTransformer', function () {
                         '}';
 
                     const stringArrayScopeCallsWrapperRegExp1: RegExp = new RegExp(
-                        'const a *= *\\[.*?];.*?' +
                         getStringArrayCallsWrapperMatch('f')
                     );
                     const stringArrayScopeCallsWrapperRegExp2: RegExp = new RegExp(
-                        'const a *= *\\[.*?];.*?' +
                         'function test *\\( *\\) *{.*' +
                             `${getStringArrayCallsWrapperMatch('g')}.*?` +
                         '}'
@@ -723,11 +721,9 @@ describe('StringArrayScopeCallsWrapperTransformer', function () {
                         '}';
 
                     const stringArrayScopeCallsWrapperRegExp1: RegExp = new RegExp(
-                        'const a *= *\\[.*?];.*?' +
                         getStringArrayCallsWrapperMatch('f')
                     );
                     const stringArrayScopeCallsWrapperRegExp2: RegExp = new RegExp(
-                        'const a *= *\\[.*?];.*?' +
                         'function test *\\( *\\) *{.*' +
                             `${getStringArrayCallsWrapperMatch('g')}.*?` +
                         '}'
@@ -791,13 +787,11 @@ describe('StringArrayScopeCallsWrapperTransformer', function () {
 
             describe('Variant #2: correct chained calls', () => {
                 const stringArrayScopeCallsWrapperRegExp1: RegExp = new RegExp(
-                    'const a *= *\\[.*?];.*?' +
                     'function *f *\\(c, *d\\) *{' +
                         `return b\\([cd] *-(?: -)?${hexadecimalIndexMatch}, *[cd]\\);` +
                     '}.*'
                 );
                 const stringArrayScopeCallsWrapperRegExp2: RegExp = new RegExp(
-                    'const a *= *\\[.*?];.*?' +
                     'function test *\\( *\\) *{.*' +
                         'function *g *\\(c, *d\\) *{' +
                             `return f\\(` +
@@ -863,7 +857,6 @@ describe('StringArrayScopeCallsWrapperTransformer', function () {
 
             describe('Variant #3: no wrappers on a root scope', () => {
                 const stringArrayScopeCallsWrapperRegExp: RegExp = new RegExp(
-                    'const a *= *\\[.*?];.*' +
                     'function test *\\( *\\) *{.*' +
                         'function *f*\\(c, *d\\) *{' +
                             `return b\\([cd] *-(?: -)?${hexadecimalIndexMatch}, *[cd]\\);` +
@@ -1088,13 +1081,11 @@ describe('StringArrayScopeCallsWrapperTransformer', function () {
                         .join(', *');
 
                     const stringArrayScopeCallsWrapperRegExp1: RegExp = new RegExp(
-                        'const a *= *\\[.*?];.*?' +
                         'function *f *\\(c, *d, *e, *h, *i\\) *{' +
                             `return b\\([cdehi] *-(?: -)?${hexadecimalIndexMatch}, *[cdehi]\\);` +
                         '}.*'
                     );
                     const stringArrayScopeCallsWrapperRegExp2: RegExp = new RegExp(
-                        'const a *= *\\[.*?];.*?' +
                         'function test *\\( *\\) *{.*' +
                             'function *g *\\(c, *d, *e, *h, *i\\) *{' +
                                 `return f\\(` +
@@ -1169,13 +1160,11 @@ describe('StringArrayScopeCallsWrapperTransformer', function () {
                         .join(', *');
 
                     const stringArrayScopeCallsWrapperRegExp1: RegExp = new RegExp(
-                        'const a *= *\\[.*?];.*?' +
                         'function *f *\\(c, *d, *e, *h, *i\\) *{' +
                             `return b\\([cdehi] *-(?: -)?${hexadecimalIndexMatch}, *[cdehi]\\);` +
                         '}.*'
                     );
                     const stringArrayScopeCallsWrapperRegExp2: RegExp = new RegExp(
-                        'const a *= *\\[.*?];.*?' +
                         'function test *\\( *\\) *{.*' +
                             'function *g *\\(c, *d, *e, *h, *i\\) *{' +
                                 `return f\\(` +

+ 13 - 7
test/functional-tests/node-transformers/string-array-transformers/string-array-transformer/StringArrayTransformer.spec.ts

@@ -7,6 +7,7 @@ import { StringArrayWrappersType } from '../../../../../src/enums/node-transform
 
 import { NO_ADDITIONAL_NODES_PRESET } from '../../../../../src/options/presets/NoCustomNodes';
 
+import { getStringArrayRegExp } from '../../../../helpers/get-string-array-regexp';
 import { readFileAsString } from '../../../../helpers/readFileAsString';
 import { getRegExpMatch } from '../../../../helpers/getRegExpMatch';
 import { swapLettersCase } from '../../../../helpers/swapLettersCase';
@@ -17,7 +18,7 @@ describe('StringArrayTransformer', function () {
     this.timeout(120000);
 
     describe('Variant #1: default behaviour', () => {
-        const stringArrayRegExp: RegExp = /^var _0x([a-f0-9]){4} *= *\['test'\];/;
+        const stringArrayRegExp: RegExp = getStringArrayRegExp(['test']);
         const stringArrayCallRegExp: RegExp = /var test *= *_0x([a-f0-9]){4}\(0x0\);/;
 
         let obfuscatedCode: string;
@@ -355,7 +356,7 @@ describe('StringArrayTransformer', function () {
     });
 
     describe('Variant #5: same literal node values', () => {
-        const stringArrayRegExp: RegExp = /^var _0x([a-f0-9]){4} *= *\['test'\];/;
+        const stringArrayRegExp: RegExp = getStringArrayRegExp(['test']);
         const stringArrayCallRegExp: RegExp = /var test *= *_0x([a-f0-9]){4}\(0x0\);/;
 
         let obfuscatedCode: string;
@@ -406,7 +407,12 @@ describe('StringArrayTransformer', function () {
     });
 
     describe('Variant #7: base64 encoding', () => {
-        const stringArrayRegExp: RegExp = new RegExp(`^var _0x([a-f0-9]){4} *= *\\['${swapLettersCase('dGVzdA')}'];`);
+        const stringArrayRegExp: RegExp = new RegExp(
+            'function _0x([a-f0-9]){4} *\\(\\) *{' +
+                `var _0x([a-f0-9]){4,6} *= *\\[\'${swapLettersCase('dGVzdA')}\'];.*` +
+                'return _0x([a-f0-9]){4}\\(\\); *' +
+            '}'
+        );
         const stringArrayCallRegExp: RegExp = /var test *= *_0x([a-f0-9]){4}\(0x0\);/;
 
         let obfuscatedCode: string;
@@ -506,8 +512,8 @@ describe('StringArrayTransformer', function () {
             const expectedMatchesChance: number = 0.5;
             const expectedMatchesDelta: number = 0.15;
 
-            const noneEncodingRegExp: RegExp = /^var _0x([a-f0-9]){4} *= *\['test'\];/;
-            const base64EncodingRegExp: RegExp = /^var _0x([a-f0-9]){4} *= *\['DgvZDa'\];/;
+            const noneEncodingRegExp: RegExp = getStringArrayRegExp(['test']);
+            const base64EncodingRegExp: RegExp = getStringArrayRegExp(['DgvZDa']);
 
             let noneEncodingMatchesCount: number = 0;
             let base64EncodingMatchesCount: number = 0;
@@ -1080,7 +1086,7 @@ describe('StringArrayTransformer', function () {
 
     describe('Variant #16: object expression key literal', () => {
         describe('Variant #1: base key literal', () => {
-            const stringArrayRegExp: RegExp = /^var _0x([a-f0-9]){4} *= *\['bar'];/;
+            const stringArrayRegExp: RegExp = getStringArrayRegExp(['bar'])
             const objectExpressionRegExp: RegExp = /var test *= *{'foo' *: *_0x([a-f0-9]){4}\(0x0\)};/;
 
             let obfuscatedCode: string;
@@ -1108,7 +1114,7 @@ describe('StringArrayTransformer', function () {
         });
 
         describe('Variant #2: computed key literal', () => {
-            const stringArrayRegExp: RegExp = /^var _0x([a-f0-9]){4} *= *\['foo', *'bar'];/;
+            const stringArrayRegExp: RegExp = getStringArrayRegExp(['foo', 'bar'])
             const objectExpressionRegExp: RegExp = /var test *= *{\[_0x([a-f0-9]){4}\(0x0\)] *: *_0x([a-f0-9]){4}\(0x1\)};/;
 
             let obfuscatedCode: string;

+ 11 - 10
test/functional-tests/storages/string-array-transformers/string-array-storage/StringArrayStorage.spec.ts

@@ -2,6 +2,7 @@ import { assert } from 'chai';
 
 import { NO_ADDITIONAL_NODES_PRESET } from '../../../../../src/options/presets/NoCustomNodes';
 
+import { getStringArrayRegExp } from '../../../../helpers/get-string-array-regexp';
 import { readFileAsString } from '../../../../helpers/readFileAsString';
 
 import { JavaScriptObfuscator } from '../../../../../src/JavaScriptObfuscatorFacade';
@@ -15,7 +16,7 @@ describe('StringArrayStorage', () => {
             const delta: number = 0.1;
             const expectedVariantProbability: number = 1;
 
-            const stringArrayVariantRegExp: RegExp = /var _0x([a-f0-9]){4} *= *\[(?:'.*?', *)?'test'(?:, *'.*?')?];/g;
+            const stringArrayVariantRegExp: RegExp = /var.*= *\[(?:'.*?', *)?'test'(?:, *'.*?')?];.*/;
             const literalNodeVariantRegExp: RegExp = /var test *= *_0x([a-f0-9]){4}\(0x.\);/g;
 
             let stringArrayVariantProbability: number,
@@ -74,8 +75,8 @@ describe('StringArrayStorage', () => {
             const literalNodeVariantsCount: number = 1;
 
             const stringArrayVariantRegExps: RegExp[] = [
-                /var _0x([a-f0-9]){4} *= *\['foo', *'bar', *'baz'(?:, *'.*?')+];/g,
-                /var _0x([a-f0-9]){4} *= *\[(?:'.*?', *)+'foo', *'bar', *'baz'];/g
+                /var.*= *\['foo', *'bar', *'baz'(?:, *'.*?')+];.*/,
+                /var.*= *\[(?:'.*?', *)+'foo', *'bar', *'baz'];.*/,
             ];
             const literalNodeVariantRegExps: RegExp[] = [
                 new RegExp(
@@ -155,7 +156,7 @@ describe('StringArrayStorage', () => {
             const delta: number = 0.1;
             const expectedVariantProbability: number = 1;
 
-            const stringArrayVariantRegExp1: RegExp = /var _0x([a-f0-9]){4} *= *\['test'];/g;
+            const stringArrayVariantRegExp1: RegExp = getStringArrayRegExp(['test']);
             const literalNodeVariant1RegExp: RegExp = /var test *= *_0x([a-f0-9]){4}\(0x0\);/g;
 
             let stringArrayVariant1Probability: number,
@@ -212,12 +213,12 @@ describe('StringArrayStorage', () => {
             const variantsCount: number = 6;
 
             const stringArrayVariantRegExps: RegExp[] = [
-                /var _0x([a-f0-9]){4} *= *\['foo', *'bar', *'baz'];/g,
-                /var _0x([a-f0-9]){4} *= *\['foo', *'baz', *'bar'];/g,
-                /var _0x([a-f0-9]){4} *= *\['bar', *'foo', *'baz'];/g,
-                /var _0x([a-f0-9]){4} *= *\['bar', *'baz', *'foo'];/g,
-                /var _0x([a-f0-9]){4} *= *\['baz', *'foo', *'bar'];/g,
-                /var _0x([a-f0-9]){4} *= *\['baz', *'bar', *'foo'];/g
+                getStringArrayRegExp(['foo', 'bar', 'baz']),
+                getStringArrayRegExp(['foo', 'baz', 'bar']),
+                getStringArrayRegExp(['bar', 'foo', 'baz']),
+                getStringArrayRegExp(['bar', 'baz', 'foo']),
+                getStringArrayRegExp(['baz', 'foo', 'bar']),
+                getStringArrayRegExp(['baz', 'bar', 'foo'])
             ];
 
             const literalNodeVariantRegExps: RegExp[] = [

+ 10 - 7
test/helpers/beautifyCode.ts

@@ -1,13 +1,16 @@
+const beautify = require('js-beautify').js;
+
 /**
- * Adds some spaces between some language constructions
+ * Beautifies code
  *
  * @param {string} code
+ * @param {" " | "  "} character
  * @returns {string}
  */
-export function beautifyCode (code: string): string {
-    return code
-        .replace(/function\(\){/g, 'function () {')
-        .replace(/(!?=+)/g, ' $1 ')
-        .replace(/,/g, ', ')
-        .replace(/;/g, '; ');
+export function beautifyCode (code: string, character: 'space' | 'tab'): string {
+    const indentCharacter: string = character === 'space' ? '\x20' : '\x09';
+
+    return beautify(code, {
+        indent_char: indentCharacter
+    });
 }

+ 28 - 0
test/helpers/get-string-array-regexp.ts

@@ -0,0 +1,28 @@
+const defaultOptions = {
+    name: '_0x([a-f0-9]){4}',
+    kind: 'var'
+}
+
+/**
+ * Returns string array RegExp
+ *
+ * @returns {RegExp}
+ */
+export function getStringArrayRegExp(
+    stringArrayItems: string[],
+    options: Partial<typeof defaultOptions> = {}
+): RegExp {
+    const mergedOptions = {
+        ...defaultOptions,
+        ...options
+    };
+
+    const {name, kind} = mergedOptions;
+
+    return new RegExp(
+        `function (${name}) *\\(\\) *{` +
+            `${kind}.*= *\\[${stringArrayItems.map((item: string) => `\'${item}\'`).join(',')}];.*` +
+            `return ${name}\\(\\); *` +
+        '}'
+    );
+}

+ 13 - 0
test/helpers/minimizeCode.ts

@@ -0,0 +1,13 @@
+import {minify} from 'terser';
+
+/**
+ * Minimizes code
+ *
+ * @param {string} code
+ * @returns {string}
+ */
+export async function minimizeCode (code: string): Promise<string> {
+    const result = await minify(code);
+
+    return result.code ?? '';
+}

+ 0 - 1
test/index.spec.ts

@@ -66,7 +66,6 @@ import './functional-tests/custom-code-helpers/debug-protection/templates/DebugP
 import './functional-tests/custom-code-helpers/domain-lock/DomainLockCodeHelper.spec';
 import './functional-tests/custom-code-helpers/domain-lock/templates/DomainLockNodeTemplate.spec';
 import './functional-tests/custom-code-helpers/self-defending/SelfDefendingCodeHelper.spec';
-import './functional-tests/custom-code-helpers/self-defending/templates/SelfDefendingNoEvalTemplate.spec';
 import './functional-tests/custom-code-helpers/self-defending/templates/SelfDefendingTemplate.spec';
 import './functional-tests/custom-code-helpers/string-array/StringArrayCallsWrapperCodeHelper.spec';
 import './functional-tests/custom-code-helpers/string-array/StringArrayCodeHelper.spec';

+ 5 - 3
test/runtime-tests/JavaScriptObfuscatorRuntime.spec.ts

@@ -207,14 +207,16 @@ describe('JavaScriptObfuscator runtime eval', function () {
             beforeEach(() => {
                 const code: string = readFileAsString(process.cwd() + '/dist/index.js');
 
-                const obfuscatedCode: string = JavaScriptObfuscator.obfuscate(
+                const obfuscationResult = JavaScriptObfuscator.obfuscate(
                     code,
                     {
                         ...baseOptions,
                         ...options,
                         renameProperties: false
                     }
-                ).getObfuscatedCode();
+                );
+                const obfuscatorOptions = obfuscationResult.getOptions();
+                const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
 
                 return evaluateInWorker(
                     `
@@ -232,7 +234,7 @@ describe('JavaScriptObfuscator runtime eval', function () {
                         evaluationResult = result;
                     })
                     .catch((error: Error) => {
-                        evaluationResult = `${error.message}. ${error.stack}. Code: ${obfuscatedCode}`;
+                        evaluationResult = `${error.message}. ${error.stack}. Options: ${JSON.stringify(obfuscatorOptions)} Code: ${obfuscationResult}`;
                     });
             });
 

+ 79 - 110
yarn.lock

@@ -644,6 +644,11 @@
     "@types/minimatch" "*"
     "@types/node" "*"
 
+"@types/[email protected]":
+  version "1.13.2"
+  resolved "https://registry.yarnpkg.com/@types/js-beautify/-/js-beautify-1.13.2.tgz#49783f6c6c68558738139e612b64b4f1a275383e"
+  integrity sha512-crV/441NhrynLIclg94i1wV6nX/6rU9ByUyn4muCrsL0HPd3nBzrt6kpQ9MQOB+HeYgLcRARteNJcbnYkp5OwA==
+
 "@types/[email protected]":
   version "1.0.0"
   resolved "https://registry.npmjs.org/@types/js-string-escape/-/js-string-escape-1.0.0.tgz"
@@ -982,6 +987,11 @@
   resolved "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz"
   integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==
 
+abbrev@1:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8"
+  integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==
+
 acorn-jsx@^5.2.0:
   version "5.2.0"
   resolved "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.2.0.tgz"
@@ -1512,7 +1522,7 @@ [email protected]:
   resolved "https://registry.yarnpkg.com/commander/-/commander-8.0.0.tgz#1da2139548caef59bd23e66d18908dfb54b02258"
   integrity sha512-Xvf85aAtu6v22+E5hfVoLHqyul/jyxh91zvqk/ioJTQuJR7Z78n7H558vMPKanPSRgIEeZemT92I2g9Y8LPbSQ==
 
-commander@^2.20.0:
+commander@^2.19.0, commander@^2.20.0:
   version "2.20.3"
   resolved "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz"
   integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
@@ -1537,15 +1547,13 @@ [email protected]:
   resolved "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz"
   integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=
 
-concat-stream@^1.4.7:
-  version "1.6.2"
-  resolved "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz"
-  integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==
+config-chain@^1.1.12:
+  version "1.1.13"
+  resolved "https://registry.yarnpkg.com/config-chain/-/config-chain-1.1.13.tgz#fad0795aa6a6cdaff9ed1b68e9dff94372c232f4"
+  integrity sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==
   dependencies:
-    buffer-from "^1.0.0"
-    inherits "^2.0.3"
-    readable-stream "^2.2.2"
-    typedarray "^0.0.6"
+    ini "^1.3.4"
+    proto-list "~1.2.1"
 
 consola@^2.15.0:
   version "2.15.0"
@@ -1559,11 +1567,6 @@ convert-source-map@^1.7.0:
   dependencies:
     safe-buffer "~5.1.1"
 
-core-util-is@~1.0.0:
-  version "1.0.2"
-  resolved "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz"
-  integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=
-
 cosmiconfig@^6.0.0:
   version "6.0.0"
   resolved "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz"
@@ -1587,15 +1590,6 @@ [email protected]:
   dependencies:
     cross-spawn "^7.0.1"
 
-cross-spawn@^5.0.1:
-  version "5.1.0"
-  resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz"
-  integrity sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=
-  dependencies:
-    lru-cache "^4.0.1"
-    shebang-command "^1.2.0"
-    which "^1.2.9"
-
 cross-spawn@^7.0.0:
   version "7.0.1"
   resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.1.tgz"
@@ -1740,6 +1734,16 @@ doctrine@^3.0.0:
   dependencies:
     esutils "^2.0.2"
 
+editorconfig@^0.15.3:
+  version "0.15.3"
+  resolved "https://registry.yarnpkg.com/editorconfig/-/editorconfig-0.15.3.tgz#bef84c4e75fb8dcb0ce5cee8efd51c15999befc5"
+  integrity sha512-M9wIMFx96vq0R4F+gRpY3o2exzb8hEj/n9S8unZtHSvYjibBp/iMufSzvmOcV/laG0ZtuTVGtiJggPOSW2r93g==
+  dependencies:
+    commander "^2.19.0"
+    lru-cache "^4.1.5"
+    semver "^5.6.0"
+    sigmund "^1.0.1"
+
 electron-to-chromium@^1.3.723:
   version "1.3.736"
   resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.736.tgz#f632d900a1f788dab22fec9c62ec5c9c8f0c4052"
@@ -2552,6 +2556,11 @@ human-signals@^2.1.0:
   resolved "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz"
   integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==
 
+husky@^7.0.1:
+  version "7.0.1"
+  resolved "https://registry.yarnpkg.com/husky/-/husky-7.0.1.tgz#579f4180b5da4520263e8713cc832942b48e1f1c"
+  integrity sha512-gceRaITVZ+cJH9sNHqx5tFwbzlLCVxtVZcusME8JYQ8Edy5mpGDOqD8QBCdMhpyo9a+JXddnujQ4rpY2Ff9SJA==
+
 ignore@^4.0.6:
   version "4.0.6"
   resolved "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz"
@@ -2596,11 +2605,16 @@ inflight@^1.0.4:
     once "^1.3.0"
     wrappy "1"
 
-inherits@2, inherits@^2.0.3, inherits@~2.0.3:
+inherits@2, inherits@^2.0.3:
   version "2.0.4"
   resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz"
   integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
 
+ini@^1.3.4:
+  version "1.3.8"
+  resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c"
+  integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==
+
 interpret@^2.2.0:
   version "2.2.0"
   resolved "https://registry.npmjs.org/interpret/-/interpret-2.2.0.tgz"
@@ -2836,11 +2850,6 @@ [email protected]:
   resolved "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz"
   integrity sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=
 
-isarray@~1.0.0:
-  version "1.0.0"
-  resolved "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz"
-  integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=
-
 isexe@^2.0.0:
   version "2.0.0"
   resolved "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz"
@@ -2924,6 +2933,16 @@ jest-worker@^27.0.2:
     merge-stream "^2.0.0"
     supports-color "^8.0.0"
 
[email protected]:
+  version "1.14.0"
+  resolved "https://registry.yarnpkg.com/js-beautify/-/js-beautify-1.14.0.tgz#2ce790c555d53ce1e3d7363227acf5dc69024c2d"
+  integrity sha512-yuck9KirNSCAwyNJbqW+BxJqJ0NLJ4PwBUzQQACl5O3qHMBXVkXb/rD0ilh/Lat/tn88zSZ+CAHOlk0DsY7GuQ==
+  dependencies:
+    config-chain "^1.1.12"
+    editorconfig "^0.15.3"
+    glob "^7.1.3"
+    nopt "^5.0.0"
+
 [email protected]:
   version "1.0.1"
   resolved "https://registry.npmjs.org/js-string-escape/-/js-string-escape-1.0.1.tgz"
@@ -3113,7 +3132,7 @@ [email protected]:
     chalk "^4.1.0"
     is-unicode-supported "^0.1.0"
 
-lru-cache@^4.0.1:
+lru-cache@^4.1.5:
   version "4.1.5"
   resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz"
   integrity sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==
@@ -3335,6 +3354,13 @@ node-releases@^1.1.71:
   resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.72.tgz#14802ab6b1039a79a0c7d662b610a5bbd76eacbe"
   integrity sha512-LLUo+PpH3dU6XizX3iVoubUNheF/owjXCZZ5yACDxNnPtgFuludV1ZL3ayK1kVep42Rmm0+R9/Y60NQbZ2bifw==
 
+nopt@^5.0.0:
+  version "5.0.0"
+  resolved "https://registry.yarnpkg.com/nopt/-/nopt-5.0.0.tgz#530942bb58a512fccafe53fe210f13a25355dc88"
+  integrity sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==
+  dependencies:
+    abbrev "1"
+
 normalize-package-data@^2.3.2, normalize-package-data@^2.5.0:
   version "2.5.0"
   resolved "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz"
@@ -3501,11 +3527,6 @@ optionator@^0.9.1:
     type-check "^0.4.0"
     word-wrap "^1.2.3"
 
-os-shim@^0.1.2:
-  version "0.1.3"
-  resolved "https://registry.npmjs.org/os-shim/-/os-shim-0.1.3.tgz"
-  integrity sha1-a2LDeRz3kJ6jXtRuF2WLtBfLORc=
-
 p-limit@^1.1.0:
   version "1.3.0"
   resolved "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz"
@@ -3697,15 +3718,6 @@ pluralize@^8.0.0:
   resolved "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz"
   integrity sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==
 
[email protected]:
-  version "1.2.2"
-  resolved "https://registry.npmjs.org/pre-commit/-/pre-commit-1.2.2.tgz"
-  integrity sha1-287g7p3nI15X95xW186UZBpp7sY=
-  dependencies:
-    cross-spawn "^5.0.1"
-    spawn-sync "^1.0.15"
-    which "1.2.x"
-
 prelude-ls@^1.2.1:
   version "1.2.1"
   resolved "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz"
@@ -3716,11 +3728,6 @@ prelude-ls@~1.1.2:
   resolved "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz"
   integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=
 
-process-nextick-args@~2.0.0:
-  version "2.0.1"
-  resolved "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz"
-  integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==
-
 process-on-spawn@^1.0.0:
   version "1.0.0"
   resolved "https://registry.npmjs.org/process-on-spawn/-/process-on-spawn-1.0.0.tgz"
@@ -3738,6 +3745,11 @@ progress@^2.0.0:
   resolved "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz"
   integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==
 
+proto-list@~1.2.1:
+  version "1.2.4"
+  resolved "https://registry.yarnpkg.com/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849"
+  integrity sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk=
+
 pseudomap@^1.0.2:
   version "1.0.2"
   resolved "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz"
@@ -3796,19 +3808,6 @@ read-pkg@^5.2.0:
     parse-json "^5.0.0"
     type-fest "^0.6.0"
 
-readable-stream@^2.2.2:
-  version "2.3.7"
-  resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz"
-  integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==
-  dependencies:
-    core-util-is "~1.0.0"
-    inherits "~2.0.3"
-    isarray "~1.0.0"
-    process-nextick-args "~2.0.0"
-    safe-buffer "~5.1.1"
-    string_decoder "~1.1.1"
-    util-deprecate "~1.0.1"
-
 readdirp@~3.5.0:
   version "3.5.0"
   resolved "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz"
@@ -3945,7 +3944,7 @@ safe-buffer@^5.1.0, safe-buffer@^5.1.2:
   resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz"
   integrity sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==
 
-safe-buffer@~5.1.0, safe-buffer@~5.1.1:
+safe-buffer@~5.1.1:
   version "5.1.2"
   resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz"
   integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
@@ -3984,7 +3983,7 @@ schema-utils@^3.1.0:
     ajv "^6.12.5"
     ajv-keywords "^3.5.2"
 
-"semver@2 || 3 || 4 || 5", semver@^5.4.1:
+"semver@2 || 3 || 4 || 5", semver@^5.4.1, semver@^5.6.0:
   version "5.7.1"
   resolved "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz"
   integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==
@@ -4039,13 +4038,6 @@ shallow-clone@^3.0.0:
   dependencies:
     kind-of "^6.0.2"
 
-shebang-command@^1.2.0:
-  version "1.2.0"
-  resolved "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz"
-  integrity sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=
-  dependencies:
-    shebang-regex "^1.0.0"
-
 shebang-command@^2.0.0:
   version "2.0.0"
   resolved "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz"
@@ -4053,11 +4045,6 @@ shebang-command@^2.0.0:
   dependencies:
     shebang-regex "^3.0.0"
 
-shebang-regex@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz"
-  integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=
-
 shebang-regex@^3.0.0:
   version "3.0.0"
   resolved "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz"
@@ -4068,6 +4055,11 @@ shellwords@^0.1.1:
   resolved "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz"
   integrity sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==
 
+sigmund@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/sigmund/-/sigmund-1.0.1.tgz#3ff21f198cad2175f9f3b781853fd94d0d19b590"
+  integrity sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA=
+
 signal-exit@^3.0.2:
   version "3.0.2"
   resolved "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz"
@@ -4109,7 +4101,7 @@ source-list-map@^2.0.1:
   resolved "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz"
   integrity sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==
 
-source-map-resolve@^0.6.0:
[email protected]:
   version "0.6.0"
   resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.6.0.tgz#3d9df87e236b53f16d01e58150fc7711138e5ed2"
   integrity sha512-KXBr9d/fO/bWo97NXsPIAW1bFSBOuCnjbNTBMO7N59hsv5i9yzRDfcYwwt0l04+VqnKC+EwzvJZIP/qkuMgR/w==
@@ -4140,14 +4132,6 @@ source-map@~0.7.2:
   resolved "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz"
   integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==
 
-spawn-sync@^1.0.15:
-  version "1.0.15"
-  resolved "https://registry.npmjs.org/spawn-sync/-/spawn-sync-1.0.15.tgz"
-  integrity sha1-sAeZVX63+wyDdsKdROih6mfldHY=
-  dependencies:
-    concat-stream "^1.4.7"
-    os-shim "^0.1.2"
-
 spawn-wrap@^2.0.0:
   version "2.0.0"
   resolved "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-2.0.0.tgz"
@@ -4269,13 +4253,6 @@ string.prototype.trimstart@^1.0.4:
     call-bind "^1.0.2"
     define-properties "^1.1.3"
 
-string_decoder@~1.1.1:
-  version "1.1.1"
-  resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz"
-  integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==
-  dependencies:
-    safe-buffer "~5.1.0"
-
 [email protected]:
   version "2.1.0"
   resolved "https://registry.npmjs.org/stringz/-/stringz-2.1.0.tgz"
@@ -4398,6 +4375,15 @@ terser@^5.7.0:
     source-map "~0.7.2"
     source-map-support "~0.5.19"
 
+terser@^5.7.1:
+  version "5.7.1"
+  resolved "https://registry.yarnpkg.com/terser/-/terser-5.7.1.tgz#2dc7a61009b66bb638305cb2a824763b116bf784"
+  integrity sha512-b3e+d5JbHAe/JSjwsC3Zn55wsBIM7AsHLjKxT31kGCldgbpFePaFo+PiddtO6uwRZWRw7sPXmAN8dTW61xmnSg==
+  dependencies:
+    commander "^2.20.0"
+    source-map "~0.7.2"
+    source-map-support "~0.5.19"
+
 test-exclude@^6.0.0:
   version "6.0.0"
   resolved "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz"
@@ -4537,11 +4523,6 @@ typedarray-to-buffer@^3.1.5:
   dependencies:
     is-typedarray "^1.0.0"
 
-typedarray@^0.0.6:
-  version "0.0.6"
-  resolved "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz"
-  integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=
-
 [email protected]:
   version "4.4.0-beta"
   resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.4.0-beta.tgz#a5b8a3a0d260fa5ce84daa3ab58f7102ad19a655"
@@ -4569,11 +4550,6 @@ uri-js@^4.2.2:
   dependencies:
     punycode "^2.1.0"
 
-util-deprecate@~1.0.1:
-  version "1.0.2"
-  resolved "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz"
-  integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=
-
 util@^0.12.0:
   version "0.12.3"
   resolved "https://registry.npmjs.org/util/-/util-0.12.3.tgz"
@@ -4719,13 +4695,6 @@ which-typed-array@^1.1.2:
     has-symbols "^1.0.1"
     is-typed-array "^1.1.3"
 
[email protected]:
-  version "1.2.14"
-  resolved "https://registry.npmjs.org/which/-/which-1.2.14.tgz"
-  integrity sha1-mofEN48D6CfOyvGs31bHNsAcFOU=
-  dependencies:
-    isexe "^2.0.0"
-
 [email protected], which@^2.0.1:
   version "2.0.2"
   resolved "https://registry.npmjs.org/which/-/which-2.0.2.tgz"
@@ -4733,7 +4702,7 @@ [email protected], which@^2.0.1:
   dependencies:
     isexe "^2.0.0"
 
-which@^1.2.9, which@^1.3.1:
+which@^1.3.1:
   version "1.3.1"
   resolved "https://registry.npmjs.org/which/-/which-1.3.1.tgz"
   integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==

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